Merge pull request #539 from wilkox/frequency_spectrum_plot
Add visual to Frequency Spectrum goodiemaster
commit
4e1eb737ea
2
dist.ini
2
dist.ini
|
@ -75,6 +75,8 @@ Telephony::CountryDialingCodes = 1.04
|
|||
URI::Escape::XS = 0.12
|
||||
DateTime::Calendar::Chinese = 1.00
|
||||
DateTime::Event::Chinese = 1.00
|
||||
SVG = 2.59
|
||||
Lingua::EN::Inflect = 1.895
|
||||
DateTime::Event::Sunrise = 0
|
||||
Geo::Coordinates::DecimalDegrees = 0.09
|
||||
Math::SigFigs = 1.09
|
||||
|
|
|
@ -2,10 +2,10 @@ package DDG::Goodie::FrequencySpectrum;
|
|||
# ABSTRACT: describe the nature of various wave frequencies.
|
||||
|
||||
use strict;
|
||||
|
||||
use SVG;
|
||||
use DDG::Goodie;
|
||||
|
||||
triggers end => "hz","khz","mhz","ghz","thz","hertz","kilohertz","gigahertz","megahertz","terahertz";
|
||||
use Lingua::EN::Inflect qw(WORDLIST);
|
||||
use Math::SigFigs qw(:all);
|
||||
|
||||
zci answer_type => "frequency_spectrum";
|
||||
zci is_cached => 1;
|
||||
|
@ -17,235 +17,687 @@ name 'FrequencySpectrum';
|
|||
code_url 'https://github.com/duckduckgo/zeroclickinfo-goodies/blob/master/lib/DDG/Goodie/FrequencySpectrum.pm';
|
||||
category 'physical_properties';
|
||||
topics 'science';
|
||||
attribution web => "https://machinepublishers.com",
|
||||
twitter => 'machinepub';
|
||||
attribution web => "https://machinepublishers.com", twitter => 'machinepub';
|
||||
|
||||
sub THOUSAND { 1000 };
|
||||
sub MILLION { 1000000 };
|
||||
sub BILLION { 1000000000 };
|
||||
sub TRILLION { 1000000000000 };
|
||||
#Javascript to dynamically resize and/or hide elements
|
||||
my $dynamicwidths = <<EOF;
|
||||
<script type="text/javascript">
|
||||
|
||||
#reference: https://en.wikipedia.org/wiki/Radio_spectrum
|
||||
#Radio spectrum ranges along with example uses
|
||||
my $radio_ranges =
|
||||
[
|
||||
[ "3", "29", "ELF band used by pipeline inspection gauges."],
|
||||
[ "30", "299", "SLF band used by submarine communication systems."],
|
||||
[ "300", "2999", "ULF band used by mine cave communication systems."],
|
||||
[ "3000", "29999", "VLF band used by government time stations and navigation systems."],
|
||||
[ "30000", "299999", "LF band used by AM broadcasts, government time stations, navigation systems, and weather alert systems."],
|
||||
[ "300000", "2999999", "MF band used by AM broadcasts, navigation systems, and ship-to-shore communication systems."],
|
||||
[ "3000000", "29999999", "HF band used by international shortwave broadcasts, aviation systems, government time stations, weather stations, and amateur radio."],
|
||||
[ "30000000", "299999999", "VHF band used by FM broadcasts, televisions, amateur radio, marine communication systems, and air traffic control."],
|
||||
[ "300000000", "2999999999", "UHF band used by televisions, cordless phones, cell phones, pagers, walkie-talkies, and satellites."],
|
||||
[ "3000000000", "29999999999", "SHF band used by microwave ovens, wireless LANs, cell phones, and satellites."],
|
||||
[ "30000000000", "299999999999", "EHF band used by radio telescopes, security screening systems, and point-to-point high-bandwidth devices."],
|
||||
[ "300000000000", "3000000000000", "THF band used by satellites and radio telescopes."],
|
||||
];
|
||||
// Get the marker label and tag
|
||||
var markerlabel, markertag
|
||||
markerlabel = document.getElementById("marker_label")
|
||||
markertag = document.getElementById("marker_tag")
|
||||
|
||||
#reference: https://en.wikipedia.org/wiki/Color
|
||||
#Color ranges. Some colors are controversial but these are fairly well accepted.
|
||||
my $color_ranges =
|
||||
[
|
||||
[ "400000000000000", "479999999999999", "red" ],
|
||||
[ "480000000000000", "504999999999999", "orange" ],
|
||||
[ "505000000000000", "524999999999999", "yellow" ],
|
||||
[ "525000000000000", "574999999999999", "green" ],
|
||||
[ "575000000000000", "609999999999999", "cyan" ],
|
||||
[ "610000000000000", "667999999999999", "blue" ],
|
||||
[ "668000000000000", "714999999999999", "indigo" ],
|
||||
[ "715000000000000", "800000000000000", "violet" ],
|
||||
];
|
||||
// Firefox (and possbily other browers) have a problem with the
|
||||
// getBBox function. For now, I'll work around this by simply
|
||||
// hiding the marker tag if getBBox() is not available.
|
||||
try {
|
||||
|
||||
# reference: https://en.wikipedia.org/wiki/Musical_acoustics
|
||||
#Ranges for common instruments
|
||||
my $instrument_ranges =
|
||||
[
|
||||
[ "87", "1046", "human voice" ],
|
||||
[ "82.407", "329.63", "bass vocalists" ],
|
||||
[ "87.307", "349.23", "baritone vocalists" ],
|
||||
[ "130.81", "440.00", "tenor vocalists" ],
|
||||
[ "196.00", "698.46", "alto vocalists" ],
|
||||
[ "220.00", "880.00", "mezzo-soprano vocalists" ],
|
||||
[ "261.63", "880.00", "soprano vocalists" ],
|
||||
[ "41.203", "523.25", "double-bass" ],
|
||||
[ "130.81", "1760.00", "viola" ],
|
||||
[ "196.00", "2637.00", "violin" ],
|
||||
[ "82.41", "1046.5", "guitar" ],
|
||||
[ "196.00", "1396.9", "mandolin" ],
|
||||
[ "130.81", "1046.5", "banjo" ],
|
||||
[ "27.500", "4186.0", "piano" ],
|
||||
[ "38.891", "440.00", "tuba" ],
|
||||
[ "82.407", "523.25", "trombone" ],
|
||||
[ "164.81", "932.33", "trumpet" ],
|
||||
[ "207.65", "1244.5", "saxophone" ],
|
||||
[ "261.63", "2093.0", "flute" ],
|
||||
[ "146.83", "1864.7", "clarinet" ],
|
||||
[ "58.270", "783.99", "bassoon" ],
|
||||
[ "233.08", "1760.0", "oboe" ],
|
||||
];
|
||||
// Resize marker to fit text
|
||||
bbox = markerlabel.getBBox()
|
||||
markerlabel.setAttribute("x", bbox.x)
|
||||
markertag.setAttribute("x", bbox.x - (bbox.width / 2))
|
||||
markertag.setAttribute("y", bbox.y + 1)
|
||||
markertag.setAttribute("width", bbox.width)
|
||||
markertag.setAttribute("height", bbox.height)
|
||||
|
||||
# Reference: https://en.wikipedia.org/wiki/Ultraviolet
|
||||
my $ultraviolet_ranges =
|
||||
[
|
||||
[ 7.495*(10**14), 3*(10**16), "UV light is found in sunlight and is emitted by electric arcs and specialized lights such as mercury lamps and black lights." ],
|
||||
];
|
||||
// If the marker tag is wider than the window - 80 px, hide it
|
||||
if (bbox.width > (wwidth - 80)) {
|
||||
markerlabel.style.visibility = "hidden"
|
||||
markertag.style.visibility = "hidden"
|
||||
}
|
||||
|
||||
# Reference: https://en.wikipedia.org/wiki/X-ray
|
||||
my $xray_ranges =
|
||||
[
|
||||
[ 3*(10**16), 3*(10**19), "X-rays are used for various medical and industrial uses such as radiographs and CT scans. "],
|
||||
];
|
||||
// If getBBox() not available, hide the tag and label
|
||||
} catch(err) {
|
||||
markerlabel.style.visibility = "hidden"
|
||||
markertag.style.visibility = "hidden"
|
||||
}
|
||||
|
||||
// When window is too small, remove marker label and tag
|
||||
// and abbreviate major range (y-axis) labels
|
||||
var wwidth, majrangelabels
|
||||
wwidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
|
||||
majrangelabels = document.getElementsByClassName("major_range_label")
|
||||
if (wwidth < 500) {
|
||||
|
||||
# Reference:
|
||||
my $gamma_ranges =
|
||||
[
|
||||
[ 10**19, 10**24, "Gamma rays are can be used to treat cancer and for diagnostic purposes." ],
|
||||
];
|
||||
// Marker tag and label
|
||||
markerlabel.style.visibility = "hidden"
|
||||
markertag.style.visibility = "hidden"
|
||||
|
||||
// Major range labels
|
||||
for (var i = majrangelabels.length - 1; i >= 0; i--) {
|
||||
var labeltext
|
||||
labeltext = majrangelabels[i].childNodes[1].childNodes[0].textContent
|
||||
if (labeltext === "Radio") {
|
||||
majrangelabels[i].childNodes[1].childNodes[0].textContent = "Rad."
|
||||
} else if (labeltext === "Infrared") {
|
||||
majrangelabels[i].childNodes[1].childNodes[0].textContent = "Inf."
|
||||
} else if (labeltext === "Visible light") {
|
||||
majrangelabels[i].childNodes[1].childNodes[0].textContent = "Vis."
|
||||
} else if (labeltext === "Ultraviolet") {
|
||||
majrangelabels[i].childNodes[1].childNodes[0].textContent = "UV"
|
||||
} else if (labeltext === "X-ray") {
|
||||
majrangelabels[i].childNodes[1].childNodes[0].textContent = "X-ray"
|
||||
} else if (labeltext === "Gamma") {
|
||||
majrangelabels[i].childNodes[1].childNodes[0].textContent = "Gam."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
EOF
|
||||
|
||||
#Regex to match a valid query
|
||||
# Used for trigger and later for parsing
|
||||
my $frequencySpectrumQR = qr/
|
||||
^(?<quantity>[\d,]+(\.\d+)?) #Number, maybe with commas, maybe a decimal
|
||||
\s? #Optional space between number and unit
|
||||
( #Unit can be frequency (Hz) or wavelength (nm)
|
||||
(?<factor>k|kilo|m|mega|g|giga|t|tera)? #Optional SI prefix for Hz
|
||||
(?:hz|hertz) #Hz
|
||||
|
|
||||
(?<wavelength>nanom(et(er|re)s?)?|nm) #nm
|
||||
)
|
||||
$/ix;
|
||||
|
||||
triggers query_raw => qr/$frequencySpectrumQR/i;
|
||||
|
||||
#The distance light travels in a vacuum in one second,
|
||||
# expressed in nanometres
|
||||
my $nanometreLightSecond = 2.99792458 * (10 ** 17);
|
||||
|
||||
#SI prefixes
|
||||
my %factors = (
|
||||
k => {
|
||||
multiplier => 1e3,
|
||||
cased => 'k'
|
||||
},
|
||||
m => {
|
||||
multiplier => 1e6,
|
||||
cased => 'M'
|
||||
},
|
||||
g => {
|
||||
multiplier => 1e9,
|
||||
cased => 'G'
|
||||
},
|
||||
t => {
|
||||
multiplier => 1e12,
|
||||
cased => 'T'
|
||||
}
|
||||
);
|
||||
|
||||
#Load electromagnetic frequency ranges
|
||||
# References:
|
||||
# https://en.wikipedia.org/wiki/Radio_spectrum
|
||||
# https://en.wikipedia.org/wiki/Ultraviolet
|
||||
# https://en.wikipedia.org/wiki/X-ray
|
||||
# https://en.wikipedia.org/wiki/Color
|
||||
my @electromagnetic;
|
||||
foreach (split /\n/, share("electromagnetic.txt")->slurp) {
|
||||
my @range = split /\t/, $_;
|
||||
push @electromagnetic, {
|
||||
subspectrum => $range[0],
|
||||
min => $range[1],
|
||||
max => $range[2],
|
||||
description => $range[3]
|
||||
};
|
||||
}
|
||||
|
||||
#Frequency ranges for EM subspectra
|
||||
# Hardcoded to control graphical layout
|
||||
my %emSpectrum = (
|
||||
'radio' => {
|
||||
min => 0,
|
||||
max => 3000000000000,
|
||||
track => 1
|
||||
},
|
||||
'infrared' => {
|
||||
min => 3000000000000,
|
||||
max => 400000000000000,
|
||||
track => 2
|
||||
},
|
||||
'visible light' => {
|
||||
min => 400000000000000,
|
||||
max => 800000000000000,
|
||||
track => 3
|
||||
},
|
||||
'ultraviolet' => {
|
||||
min => 749500000000000,
|
||||
max => 30000000000000000,
|
||||
track => 4
|
||||
},
|
||||
'x-ray' => {
|
||||
min => 30000000000000000,
|
||||
max => 30000000000000000000,
|
||||
track => 5
|
||||
},
|
||||
'gamma' => {
|
||||
min => 30000000000000000000,
|
||||
max => 3000000000000000000000000,
|
||||
track => 6
|
||||
}
|
||||
);
|
||||
|
||||
#Load audible frequency ranges
|
||||
#Reference: https://en.wikipedia.org/wiki/Musical_acoustics
|
||||
my @audible;
|
||||
foreach (split /\n/, share("audible.txt")->slurp) {
|
||||
my @range = split /\t/, $_;
|
||||
push @audible, {
|
||||
min => $range[0],
|
||||
max => $range[1],
|
||||
produced_by => $range[2]
|
||||
};
|
||||
}
|
||||
|
||||
#Query is intitially processed here. First normalize the query format,
|
||||
#normalize the units, and then calculate information about the frequency range.
|
||||
handle query => sub {
|
||||
return unless $_ =~ m/^[\d,.]+\s\w+$/;
|
||||
return unless my $freq = normalize_freq($_);
|
||||
|
||||
my $freq_hz;
|
||||
my $hz_abbrev;
|
||||
my $freq_formatted;
|
||||
#Query components
|
||||
(my $quantity = $+{quantity}) =~ s/,//g;
|
||||
my $factor = $+{factor} || 0;
|
||||
my $wavelength = $+{wavelength} || 0;
|
||||
|
||||
if($freq =~ m/^(.+?)\s(?:hz|hertz)$/i) {
|
||||
$freq_hz = $1;
|
||||
} elsif($freq =~ m/^(.+?)\s(?:khz|kilohertz)$/i) {
|
||||
$freq_hz = $1 * THOUSAND;
|
||||
} elsif($freq =~ m/^(.+?)\s(?:mhz|megahertz)$/i) {
|
||||
$freq_hz = $1 * MILLION;
|
||||
} elsif($freq =~ m/^(.+?)\s(?:ghz|gigahertz)$/i) {
|
||||
$freq_hz = $1 * BILLION;
|
||||
} elsif($freq =~ m/^(.+?)\s(?:thz|terahertz)$/i) {
|
||||
$freq_hz = $1 * TRILLION;
|
||||
} else {
|
||||
#unexpected case
|
||||
return;
|
||||
}
|
||||
#Answer components
|
||||
my $freq_hz;
|
||||
my $freq_formatted;
|
||||
my $answer;
|
||||
my $html;
|
||||
|
||||
if($freq_hz >= TRILLION){
|
||||
$hz_abbrev = "THz";
|
||||
$freq_formatted = $freq_hz / TRILLION;
|
||||
} elsif($freq_hz >= BILLION) {
|
||||
$hz_abbrev = "GHz";
|
||||
$freq_formatted = $freq_hz / BILLION;
|
||||
} elsif($freq_hz >= MILLION) {
|
||||
$hz_abbrev = "MHz";
|
||||
$freq_formatted = $freq_hz / MILLION;
|
||||
} elsif($freq_hz >= THOUSAND) {
|
||||
$hz_abbrev = "kHz";
|
||||
$freq_formatted = $freq_hz / THOUSAND;
|
||||
} else {
|
||||
$hz_abbrev = "Hz";
|
||||
$freq_formatted = $freq_hz;
|
||||
}
|
||||
#If wavelength provided, convert to frequency in hz
|
||||
if ($wavelength) {
|
||||
$wavelength = $quantity;
|
||||
$freq_hz = $nanometreLightSecond / $wavelength;
|
||||
my $freq_thz = FormatSigFigs($freq_hz / $factors{'t'}{'multiplier'}, 5);
|
||||
$freq_formatted = "$freq_thz THz (wavelength $quantity nm)";
|
||||
|
||||
$freq = $freq_formatted . " " . $hz_abbrev;
|
||||
|
||||
return prepare_result($freq, $freq_hz);
|
||||
};
|
||||
|
||||
#Normalize the frequency, attempting to discern between region differences
|
||||
#in number formatting. Filter out clearly invalid queries.
|
||||
sub normalize_freq{
|
||||
my $freq = $_;
|
||||
|
||||
if($freq =~ /(\d+\.){2,}|([.]{2,})|([,]{2,})/) {
|
||||
return;
|
||||
}
|
||||
|
||||
# Remove commas.
|
||||
$freq =~ s/,//g;
|
||||
|
||||
return $freq;
|
||||
};
|
||||
|
||||
#Take the frequency and look at which ranges it falls in.
|
||||
#Build up the result string.
|
||||
sub prepare_result {
|
||||
my $freq = $_[0];
|
||||
my $freq_hz = $_[1];
|
||||
my $color = match_in_ranges(int($freq_hz), $color_ranges);
|
||||
my $radio = match_in_ranges(int($freq_hz), $radio_ranges) unless $color;
|
||||
my $instruments = matches_in_ranges($freq_hz, $instrument_ranges) unless $color;
|
||||
|
||||
my $ultraviolet = matches_in_ranges($freq_hz, $ultraviolet_ranges);
|
||||
my $xray = matches_in_ranges($freq_hz, $xray_ranges);
|
||||
my $gamma = matches_in_ranges($freq_hz, $gamma_ranges);
|
||||
|
||||
my $text_result = "";
|
||||
my $more_at = '';
|
||||
if($radio) {
|
||||
$text_result = $freq . " is a radio frequency in the " . $radio;
|
||||
$more_at = 'https://en.wikipedia.org/wiki/Radio_spectrum';
|
||||
} elsif($color) {
|
||||
$text_result = $freq . " is an electromagnetic frequency of " . $color . " light.";
|
||||
$more_at = 'https://en.wikipedia.org/wiki/Color';
|
||||
}
|
||||
if($instruments) {
|
||||
$more_at = 'https://en.wikipedia.org/wiki/Musical_acoustics';
|
||||
$instruments =~ s/,\s([a-zA-Z\s-]+)$/, and $1/;
|
||||
if($radio) {
|
||||
$text_result = $text_result . "\n" . $freq . " is also an audible frequency which can be produced by " . $instruments . ".";
|
||||
} else {
|
||||
$text_result = $freq . " is an audible frequency which can be produced by " . $instruments . ".";
|
||||
}
|
||||
#If frequency provided, convert to hz
|
||||
} else {
|
||||
my $prefix = $factor ? lc substr($factor, 0, 1) : 0;
|
||||
my $factor = $factor ? $factors{$prefix}{'multiplier'} : 1;
|
||||
$freq_hz = $quantity * $factor;
|
||||
my $hz_formatted = $prefix ? $factors{$prefix}{'cased'} . 'Hz' : 'Hz';
|
||||
$freq_formatted = $quantity . ' ' . $hz_formatted;
|
||||
}
|
||||
|
||||
if($ultraviolet) {
|
||||
$more_at = 'https://en.wikipedia.org/wiki/Ultraviolet';
|
||||
$text_result = $ultraviolet;
|
||||
}
|
||||
if($xray) {
|
||||
$more_at = 'https://en.wikipedia.org/wiki/X-ray';
|
||||
$text_result = $xray;
|
||||
}
|
||||
if($gamma) {
|
||||
$more_at = 'https://en.wikipedia.org/wiki/Gamma_ray';
|
||||
$text_result = $gamma;
|
||||
#Look for a match in the electromagnetic spectrum
|
||||
my $emMatch = match_electromagnetic($freq_hz);
|
||||
if ($emMatch) {
|
||||
|
||||
#Don't show result for wavelengths outside the
|
||||
# visual spectrum
|
||||
return if $wavelength and not $emMatch->{subspectrum} eq 'visible light';
|
||||
|
||||
my $emDescription = $freq_formatted . ' is a ' . $emMatch->{subspectrum} . ' frequency' . $emMatch->{description} . '.';
|
||||
|
||||
$answer .= $emDescription;
|
||||
$html .= $emDescription;
|
||||
|
||||
#Add a plot to the html
|
||||
#Prepare parameters
|
||||
my $rangeMin = 0;
|
||||
my $rangeMax = 10000000000000000000000000;
|
||||
my $bandMin = $emMatch->{min};
|
||||
my $bandMax = $emMatch->{max};
|
||||
my $subspectrum = $emMatch->{subspectrum};
|
||||
|
||||
#Set up the plot panel
|
||||
my $plot = generate_plot($rangeMin, $rangeMax, scalar keys %emSpectrum);
|
||||
|
||||
#Add a major range for each subspectrum (e.g. radio or UV)
|
||||
foreach (sort {$emSpectrum{$a}{'track'} <=> $emSpectrum{$b}{'track'} } keys %emSpectrum) {
|
||||
$plot = add_major_range($plot, $emSpectrum{$_}{'min'}, $emSpectrum{$_}{'max'}, $_, $emSpectrum{$_}{'track'});
|
||||
}
|
||||
|
||||
#Add a minor range for the band (unless the band is the subspectrum)
|
||||
if (! ($emSpectrum{$subspectrum}{'min'} == $bandMin && $emSpectrum{$subspectrum}{'max'} == $bandMax)) {
|
||||
# NOTE: Skipping this per @wtrsld's comp, but leaving code in
|
||||
# until design is finalised
|
||||
#$plot = add_minor_range($plot, $bandMin, $bandMax, $emSpectrum{$subspectrum}{'track'});
|
||||
}
|
||||
|
||||
#Add a marker for the query frequency
|
||||
# Colour the marker if frequency is in visible spectrum
|
||||
my $markerRGB;
|
||||
if ($emMatch->{subspectrum} eq 'visible light') {
|
||||
$markerRGB = frequency_to_RGB($freq_hz);
|
||||
} else {
|
||||
$markerRGB = '#F7614F';
|
||||
}
|
||||
$plot = add_marker($plot, $freq_hz, $markerRGB, $freq_formatted);
|
||||
|
||||
#Generate the SVG
|
||||
$html .= $plot->{svg}->xmlify;
|
||||
|
||||
}
|
||||
|
||||
if($text_result) {
|
||||
(my $html_result = $text_result) =~ s/\n/<br>/g;
|
||||
$html_result .= "<br><a href='$more_at'>More at Wikipedia</a>";
|
||||
$text_result .= "\nMore at $more_at";
|
||||
return $text_result, html => $html_result, heading => "$freq (Frequency Spectrum)";
|
||||
#Look for matches in the audible spectrum
|
||||
# NOTE: Audible frequency results are currently being suppressed,
|
||||
# as the resulting IA is too long. This will be revisited when
|
||||
# better stying is available.
|
||||
#my @audibleMatches = @{match_audible($freq_hz)};
|
||||
my @audibleMatches = ();
|
||||
if (@audibleMatches) {
|
||||
|
||||
my $audibleDescription = $freq_formatted . ' is';
|
||||
$audibleDescription .= ' also' if $emMatch;
|
||||
$audibleDescription .= ' an audible frequency which can be produced by ';
|
||||
|
||||
my @producers;
|
||||
push @producers, $_->{produced_by} for @audibleMatches;
|
||||
$audibleDescription .= WORDLIST(@producers, {cong => 'and'});
|
||||
$audibleDescription .= '.';
|
||||
|
||||
$answer .= $audibleDescription;
|
||||
$html .= $audibleDescription;
|
||||
|
||||
#Add a plot to the HTML
|
||||
#Basic plot parameters
|
||||
my $rangeMin = 10;
|
||||
my $rangeMax = 10000;
|
||||
|
||||
#Set up the background panel
|
||||
# A 'track' is a row in the plot/categorical variable on the y axis
|
||||
(my $plot, my $transform) = generate_plot($rangeMin, $rangeMax, scalar @audibleMatches);
|
||||
|
||||
#Add a track with a major range for each producer
|
||||
my $track = 0;
|
||||
foreach my $match (@audibleMatches) {
|
||||
++$track;
|
||||
my $freqRangeMin = $match->{min};
|
||||
my $freqRangeMax = $match->{max};
|
||||
my $label = $match->{produced_by};
|
||||
$plot = add_major_range($plot, $transform, $freqRangeMin, $freqRangeMax, $label, $track);
|
||||
}
|
||||
|
||||
#Add a marker for the query frequency
|
||||
$plot = add_marker($plot, $freq_hz, scalar @audibleMatches, '#000', $freq_formatted);
|
||||
|
||||
#Generate the SVG
|
||||
$html .= $plot->xmlify;
|
||||
}
|
||||
|
||||
return $answer, html => wrap_html($html) if $answer;
|
||||
return;
|
||||
};
|
||||
|
||||
#Find which single range applies.
|
||||
sub match_in_ranges {
|
||||
my $freq = $_[0];
|
||||
my $ranges = $_[1];
|
||||
|
||||
foreach my $range (@$ranges) {
|
||||
if($freq >= $range->[0] && $freq <= $range->[1]){
|
||||
return $range->[2];
|
||||
}
|
||||
#Find match in the electromagnetic spectrum
|
||||
sub match_electromagnetic {
|
||||
my $freq_hz = shift;
|
||||
foreach (@electromagnetic) {
|
||||
return $_ if ($_->{min} <= $freq_hz) && ($_->{max} >= $freq_hz);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
#Find any number of ranges which apply.
|
||||
sub matches_in_ranges {
|
||||
my $freq = $_[0];
|
||||
my $ranges = $_[1];
|
||||
#Find matches in the audible spectrum
|
||||
sub match_audible {
|
||||
my $freq_hz = shift;
|
||||
my @matches;
|
||||
foreach my $range (@$ranges) {
|
||||
if($freq >= $range->[0] && $freq <= $range->[1]) {
|
||||
push(@matches, $range->[2]);
|
||||
}
|
||||
foreach (@audible) {
|
||||
push @matches, $_ if ($_->{min} <= $freq_hz) && ($_->{max} >= $freq_hz);
|
||||
}
|
||||
return \@matches;
|
||||
}
|
||||
|
||||
sub generate_plot {
|
||||
|
||||
#Set up plot parameters
|
||||
my $plot = {};
|
||||
|
||||
#Range (passed)
|
||||
$plot->{rangeMin} = $_[0];
|
||||
$plot->{rangeMax} = $_[1];
|
||||
|
||||
#Number of tracks (passed)
|
||||
$plot->{tracks} = $_[2];
|
||||
|
||||
#Height of a single track
|
||||
$plot->{trackHeight} = 25;
|
||||
|
||||
#Height of a band
|
||||
$plot->{bandHeight} = 18;
|
||||
|
||||
#Padding between edge of band and edge of track
|
||||
$plot->{bandGutter} = ($plot->{trackHeight} - $plot->{bandHeight}) / 2;
|
||||
|
||||
#Total height of panel (area where data is plotted/Cartesian plane)
|
||||
$plot->{panelHeight} = $plot->{tracks} * $plot->{trackHeight};
|
||||
|
||||
#Padding
|
||||
$plot->{leftGutter} = 20;
|
||||
$plot->{rightGutter} = 5;
|
||||
$plot->{bottomGutter} = 50;
|
||||
$plot->{topGutter} = 30;
|
||||
|
||||
#Plot width is dynamic, always expressed as percentage
|
||||
$plot->{width} = 100;
|
||||
|
||||
#Plot height is fixed, and depends on number of tracks (passed)
|
||||
# and the track height
|
||||
$plot->{height} = $plot->{panelHeight} + $plot->{bottomGutter} + $plot->{topGutter};
|
||||
|
||||
#Initialise SVG
|
||||
$plot->{svg} = SVG->new(height => $plot->{height}, class => 'zci--plot');
|
||||
|
||||
#If the difference betweeen the range
|
||||
# minimum and maximum is two orders of
|
||||
# magnitude or greater, use a log10 scale
|
||||
my $log10 = int(log10($plot->{rangeMax})) - int(log10($plot->{rangeMin})) >= 2 ? 1 : 0;
|
||||
|
||||
#Build a transformation function to map values to
|
||||
# x coordinates
|
||||
my $transform;
|
||||
if ($log10) {
|
||||
$plot->{transform} = sub {
|
||||
my $value = shift;
|
||||
my $unit = ($plot->{width} - $plot->{leftGutter} - $plot->{rightGutter}) / (log10($plot->{rangeMax}) - log10($plot->{rangeMin}));
|
||||
return $plot->{leftGutter} + ((log10($value) - log10($plot->{rangeMin})) * $unit);
|
||||
};
|
||||
|
||||
} else {
|
||||
$plot->{transform} = sub {
|
||||
my $value = shift;
|
||||
my $unit = ($plot->{width} - $plot->{leftGutter} - $plot->{rightGutter}) / ($plot->{rangeMax} - $plot->{rangeMin});
|
||||
return $plot->{leftGutter} + (($value - $plot->{rangeMin}) * $unit);
|
||||
};
|
||||
}
|
||||
|
||||
return join(", ", @matches);
|
||||
};
|
||||
#Add plot background
|
||||
$plot->{svg}->group(
|
||||
class => 'plot_background',
|
||||
)->rect(
|
||||
width => '100%',
|
||||
height => $plot->{height},
|
||||
x => 0,
|
||||
y => 0
|
||||
);
|
||||
|
||||
#Add panel background
|
||||
$plot->{svg}->group(
|
||||
class => 'plot_panel',
|
||||
)->rect(
|
||||
width => ($plot->{width} - $plot->{leftGutter} - $plot->{rightGutter}) . '%',
|
||||
height => $plot->{panelHeight},
|
||||
x => $plot->{leftGutter} . '%',
|
||||
y => $plot->{topGutter},
|
||||
);
|
||||
|
||||
#Calculate x-axis tick locations
|
||||
my @ticks;
|
||||
# If we're using a log10 scale, put a tick at
|
||||
# each power of 10 between range min and max
|
||||
if ($log10) {
|
||||
@ticks = map { 10 ** $_ } int(log10($plot->{rangeMin})) .. int(log10($plot->{rangeMax}));
|
||||
|
||||
#If we're using a linear scale, put a tick at every
|
||||
# integer multiple at the order of magnitude of
|
||||
# range max
|
||||
} else {
|
||||
my $order = 10 ** int(log10($plot->{rangeMax}));
|
||||
@ticks = map { $_ * $order } int($plot->{rangeMin} / $order) + 1 .. int($plot->{rangeMax} / $order);
|
||||
unshift(@ticks, $plot->{rangeMin}) unless $ticks[0] == $plot->{rangeMin};
|
||||
push(@ticks, $plot->{rangeMax}) unless $ticks[-1] == $plot->{rangeMax};
|
||||
}
|
||||
|
||||
#If there are more than 10 ticks, remove every
|
||||
# second tick until there are 10 or fewer
|
||||
while (scalar @ticks > 10) {
|
||||
@ticks = @ticks[grep !($_ % 2), 0..$#ticks];
|
||||
}
|
||||
|
||||
#Draw ticks
|
||||
my $xAxis = $plot->{svg}->group (
|
||||
id => 'x_axis',
|
||||
);
|
||||
foreach (@ticks) {
|
||||
|
||||
my $x = $plot->{transform}->($_);
|
||||
|
||||
#Draw tick line
|
||||
# NOTE: Currently skipping this per wtrsld's redesign
|
||||
#my $tick = $xAxis->group();
|
||||
#$tick->line(
|
||||
#x1 => $x . '%',
|
||||
#x2 => $x . '%',
|
||||
#y1 => $plot->{panelHeight} + $plot->{topGutter},
|
||||
#y2 => $plot->{panelHeight} + $plot->{topGutter} + 4,
|
||||
#class => 'x_axis_tick'
|
||||
#);
|
||||
|
||||
#Annotate tick
|
||||
my $text = $xAxis->text(
|
||||
dy => '1em',
|
||||
x => $x . '%',
|
||||
y => $plot->{panelHeight} + $plot->{topGutter} + 4,
|
||||
'text-anchor' => 'middle',
|
||||
class => 'x_axis_text'
|
||||
);
|
||||
if ($log10 && $_ > 10) {
|
||||
$text->tag('tspan', -cdata => '10');
|
||||
$text->tag(
|
||||
'tspan',
|
||||
'baseline-shift' => 'super',
|
||||
dy => '-0.2em', #Superscripts need an extra nudge
|
||||
dx => '-0.5em', #Bring superscript close to parent
|
||||
-cdata => log10($_),
|
||||
style => { 'font-size' => '0.5em' },
|
||||
);
|
||||
} else {
|
||||
$text->tag('tspan', -cdata => $_);
|
||||
}
|
||||
}
|
||||
|
||||
#Add x-axis gridlines
|
||||
# NOTE: Currently skipping this per wtrsld's redesign
|
||||
#my $gridlines = $plot->{svg}->group (
|
||||
#class => 'x_axis_gridline',
|
||||
#);
|
||||
#foreach (@ticks) {
|
||||
#my $x = $plot->{transform}->($_);
|
||||
#my $line = $gridlines->group();
|
||||
#$line->line(
|
||||
#x1 => $x . '%',
|
||||
#x2 => $x . '%',
|
||||
#y1 => $plot->{topGutter},
|
||||
#y2 => $plot->{panelHeight} + $plot->{topGutter}
|
||||
#);
|
||||
#}
|
||||
|
||||
#Add a label to the x-axis
|
||||
my $xAxisLabel = $xAxis->text(
|
||||
dy => '1em',
|
||||
x => '50%',
|
||||
y => $plot->{panelHeight} + $plot->{topGutter} + 25,
|
||||
'text-anchor' => 'middle',
|
||||
class => 'x_axis_label'
|
||||
);
|
||||
$xAxisLabel->tag('tspan', -cdata => 'Frequency (Hz)');
|
||||
|
||||
#Add axis lines
|
||||
my $axislines = $plot->{svg}->group (
|
||||
class => 'axis_line',
|
||||
);
|
||||
my $xaxisline = $axislines->group();
|
||||
$xaxisline->line(
|
||||
x1 => $plot->{transform}->(0) . '%',
|
||||
x2 => $plot->{transform}->($plot->{rangeMax}) . '%',
|
||||
y1 => $plot->{panelHeight} + $plot->{topGutter},
|
||||
y2 => $plot->{panelHeight} + $plot->{topGutter}
|
||||
);
|
||||
my $yaxisline = $axislines->group();
|
||||
$yaxisline->line(
|
||||
x1 => $plot->{transform}->(0) . '%',
|
||||
x2 => $plot->{transform}->(0) . '%',
|
||||
y1 => $plot->{topGutter},
|
||||
y2 => $plot->{topGutter} + $plot->{panelHeight}
|
||||
);
|
||||
|
||||
return($plot);
|
||||
}
|
||||
|
||||
#Add a minor range to a plot panel
|
||||
sub add_minor_range {
|
||||
|
||||
(my $plot, my $rangeMin, my $rangeMax, my $track) = @_;
|
||||
|
||||
#Add rectangle for range
|
||||
my $minorRange = $plot->{svg}->group(id => 'minor_range_' . $track);
|
||||
my $minorRangeRect = $minorRange->group();
|
||||
$minorRangeRect->rect(
|
||||
class => 'minor_range',
|
||||
x => $plot->{transform}->($rangeMin) . '%',
|
||||
width => $plot->{transform}->($rangeMax) - $plot->{transform}->($rangeMin) . '%',
|
||||
y => ($plot->{trackHeight} * ($track - 1)) + $plot->{bandGutter} + $plot->{topGutter},
|
||||
height => $plot->{bandHeight},
|
||||
);
|
||||
|
||||
return $plot;
|
||||
}
|
||||
|
||||
#Add a major frequency range to a plot panel
|
||||
sub add_major_range {
|
||||
|
||||
(my $plot, my $rangeMin, my $rangeMax, my $label, my $track) = @_;
|
||||
|
||||
#Add rectangle for range
|
||||
my $majorRange = $plot->{svg}->group(id => 'major_range_' . $label);
|
||||
my $majorRangeRect = $majorRange->group();
|
||||
$majorRangeRect->rect(
|
||||
class => 'major_range',
|
||||
x => $plot->{transform}->($rangeMin) . '%',
|
||||
width => $plot->{transform}->($rangeMax) - $plot->{transform}->($rangeMin) . '%',
|
||||
y => ($plot->{trackHeight} * ($track - 1)) + $plot->{bandGutter} + $plot->{topGutter},
|
||||
height => $plot->{bandHeight},
|
||||
);
|
||||
|
||||
#Add label for range on the y-axis
|
||||
my $x;
|
||||
my $anchor;
|
||||
$x = $plot->{leftGutter} - 1;
|
||||
$anchor = 'end';
|
||||
my $majorRangeLabel = $majorRange->group();
|
||||
my $majorRangeLabelText = $majorRangeLabel->text(
|
||||
x => $x . '%',
|
||||
y => ($plot->{trackHeight} * ($track - 1)) + (2 * $plot->{bandGutter}) + $plot->{topGutter},
|
||||
dy => ($plot->{trackHeight} / 2) - 5,
|
||||
'text-anchor' => $anchor,
|
||||
class => 'major_range_label'
|
||||
);
|
||||
$majorRangeLabel->tag('tspan', -cdata => ucfirst($label));
|
||||
|
||||
return $plot;
|
||||
}
|
||||
|
||||
#Add a marker (vertical line) to a plot panel
|
||||
sub add_marker {
|
||||
|
||||
(my $plot, my $markerValue, my $RGB, my $freq_formatted) = @_;
|
||||
|
||||
#Add marker rect
|
||||
my $markerWidth = 1; #This is dynamically resized by $dynamicwidths
|
||||
my $markerHeight = 14;
|
||||
my $markerGutter = ($plot->{topGutter} - $markerHeight - 1) / 2;
|
||||
$plot->{svg}->group(
|
||||
class => 'marker_tag',
|
||||
)->rect(
|
||||
id => 'marker_tag',
|
||||
width => $markerWidth,
|
||||
height => $markerHeight,
|
||||
x => $plot->{transform}->($markerValue) - ($markerWidth / 2) . '%',
|
||||
y => $plot->{topGutter} - $markerGutter - $markerHeight + 1, #Extra pixel to account for plot border
|
||||
style => { 'fill' => $RGB }
|
||||
);
|
||||
|
||||
#Add marker label
|
||||
my $markerLabel = $plot->{svg}->group();
|
||||
my $markerLabelText = $markerLabel->text(
|
||||
x => $plot->{transform}->($markerValue) . '%',
|
||||
y => $plot->{topGutter} - $markerGutter - ($markerHeight / 2) + 4,
|
||||
'text-anchor' => 'middle',
|
||||
class => 'marker_label'
|
||||
);
|
||||
$markerLabel->tag('tspan', id => 'marker_label', -cdata => ucfirst($freq_formatted));
|
||||
|
||||
#Add marker line
|
||||
$plot->{svg}->group(
|
||||
class => 'marker'
|
||||
)->line(
|
||||
id => 'marker',
|
||||
x1 => $plot->{transform}->($markerValue) . '%',
|
||||
x2 => $plot->{transform}->($markerValue) . '%',
|
||||
y1 => $plot->{topGutter} - $markerGutter,
|
||||
y2 => $plot->{topGutter} + $plot->{panelHeight},
|
||||
style => { 'stroke' => $RGB },
|
||||
);
|
||||
|
||||
return $plot;
|
||||
}
|
||||
|
||||
#Wrap html
|
||||
sub wrap_html {
|
||||
return <<EOF;
|
||||
<!--[if lte IE 8]><div class="ie8-display-none">
|
||||
<![endif]-->
|
||||
<!--[if gte IE 9]><!-->
|
||||
<div class='zci--conversions text--primary'>$_[0]</div>
|
||||
<![endif]-->
|
||||
$dynamicwidths
|
||||
EOF
|
||||
}
|
||||
|
||||
#Get log10 of a number
|
||||
sub log10 {
|
||||
my $n = shift;
|
||||
return 0 if $n == 0;
|
||||
return log($n)/log(10);
|
||||
}
|
||||
|
||||
#Convert a visible light frequency in Hz to RGB
|
||||
# Adapted from http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm
|
||||
sub frequency_to_RGB {
|
||||
|
||||
my $frequency = shift;
|
||||
my $wavelength = $nanometreLightSecond / $frequency;
|
||||
my @RGB;
|
||||
|
||||
if (($wavelength >= 380) && ($wavelength < 440)) {
|
||||
@RGB = (
|
||||
-($wavelength - 440) / (440 - 380),
|
||||
0,
|
||||
1,
|
||||
);
|
||||
} elsif (($wavelength >= 440) && ($wavelength < 490)) {
|
||||
@RGB = (
|
||||
0,
|
||||
($wavelength - 440) / (490 - 440),
|
||||
1,
|
||||
);
|
||||
} elsif (($wavelength >= 490) && ($wavelength < 510)) {
|
||||
@RGB = (
|
||||
0,
|
||||
1,
|
||||
-($wavelength - 510) / (510 - 490),
|
||||
);
|
||||
} elsif (($wavelength >= 510) && ($wavelength < 580)) {
|
||||
@RGB = (
|
||||
($wavelength - 510) / (580 - 510),
|
||||
1,
|
||||
0,
|
||||
);
|
||||
} elsif (($wavelength >= 580) && ($wavelength < 645)) {
|
||||
@RGB = (
|
||||
1,
|
||||
-($wavelength - 645) / (645 - 580),
|
||||
0,
|
||||
);
|
||||
} elsif (($wavelength >= 645) && ($wavelength <= 780)) {
|
||||
@RGB = (1, 0, 0);
|
||||
} else {
|
||||
@RGB = (0, 0, 0);
|
||||
}
|
||||
|
||||
#Convert to hex
|
||||
return '#' . join('', map { sprintf "%02x", $_ * 255 } @RGB);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
87 1046 human voice
|
||||
82.407 329.63 bass vocalists
|
||||
87.307 349.23 baritone vocalists
|
||||
130.81 440.00 tenor vocalists
|
||||
196.00 698.46 alto vocalists
|
||||
220.00 880.00 mezzo-soprano vocalists
|
||||
261.63 880.00 soprano vocalists
|
||||
41.203 523.25 double-bass
|
||||
130.81 1760.00 viola
|
||||
196.00 2637.00 violin
|
||||
82.41 1046.5 guitar
|
||||
196.00 1396.9 mandolin
|
||||
130.81 1046.5 banjo
|
||||
27.500 4186.0 piano
|
||||
38.891 440.00 tuba
|
||||
82.407 523.25 trombone
|
||||
164.81 932.33 trumpet
|
||||
207.65 1244.5 saxophone
|
||||
261.63 2093.0 flute
|
||||
146.83 1864.7 clarinet
|
||||
58.270 783.99 bassoon
|
||||
233.08 1760.0 oboe
|
|
@ -0,0 +1,24 @@
|
|||
radio 3 30 in the ELF band used by pipeline inspection gauges
|
||||
radio 30 300 in the SLF band used by submarine communication systems
|
||||
radio 300 3000 in the ULF band used by mine cave communication systems
|
||||
radio 3000 30000 in the VLF band used by government time stations and navigation systems
|
||||
radio 30000 300000 in the LF band used by AM broadcasts, government time stations, navigation systems, and weather alert systems
|
||||
radio 300000 3000000 in the MF band used by AM broadcasts, navigation systems, and ship-to-shore communication systems
|
||||
radio 3000000 30000000 in the HF band used by international shortwave broadcasts, aviation systems, government time stations, weather stations, and amateur radio
|
||||
radio 30000000 300000000 in the VHF band used by FM broadcasts, televisions, amateur radio, marine communication systems, and air traffic control
|
||||
radio 300000000 3000000000 in the UHF band used by televisions, cordless phones, cell phones, pagers, walkie-talkies, and satellites
|
||||
radio 3000000000 30000000000 in the SHF band used by microwave ovens, wireless LANs, cell phones, and satellites
|
||||
radio 30000000000 300000000000 in the EHF band used by radio telescopes, security screening systems, and point-to-point high-bandwidth devices
|
||||
radio 300000000000 3000000000000 in the THF band used by satellites and radio telescopes
|
||||
infrared 3000000000000 400000000000000 . Infrared light is found in sunlight and has a variety of industrial, commercial, scientific and household uses
|
||||
visible light 400000000000000 480000000000000 corresponding to the color red
|
||||
visible light 480000000000000 505000000000000 corresponding to the color orange
|
||||
visible light 505000000000000 525000000000000 corresponding to the color yellow
|
||||
visible light 525000000000000 575000000000000 corresponding to the color green
|
||||
visible light 575000000000000 610000000000000 corresponding to the color cyan
|
||||
visible light 610000000000000 668000000000000 corresponding to the color blue
|
||||
visible light 668000000000000 715000000000000 corresponding to the color indigo
|
||||
visible light 715000000000000 800000000000000 corresponding to the color violet
|
||||
ultraviolet 749500000000000 30000000000000000 . UV light is found in sunlight and is emitted by electric arcs and specialized lights such as mercury lamps and black lights
|
||||
x-ray 30000000000000000 30000000000000000000 . X-rays are used for various medical and industrial uses such as radiographs and CT scans
|
||||
gamma 30000000000000000000 3000000000000000000000000 . Gamma rays can be used to treat cancer and for diagnostic purposes
|
|
@ -0,0 +1,80 @@
|
|||
/* Conditional for IE8 and lower */
|
||||
.ie8-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.zci--answer .zci--conversions {
|
||||
font-size: 1.5em;
|
||||
font-weight: 300;
|
||||
padding-top: .25em;
|
||||
padding-bottom: .25em;
|
||||
}
|
||||
|
||||
.zci--conversions {
|
||||
width: 90%;
|
||||
height: 50;
|
||||
}
|
||||
|
||||
.zci--plot {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.zci--conversions .plot_background {
|
||||
fill: #FFFFFF;
|
||||
stroke: #D1D1D1;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.zci--conversions .plot_panel {
|
||||
fill: #F6F6F6;
|
||||
}
|
||||
|
||||
.zci--conversions .axis_line {
|
||||
stroke: #DCDCDC;
|
||||
}
|
||||
|
||||
.zci--conversions .x_axis_tick {
|
||||
stroke: #DCDCDC;
|
||||
}
|
||||
|
||||
.zci--conversions .x_axis_text {
|
||||
font-size: 0.7em;
|
||||
fill: #303030;
|
||||
}
|
||||
|
||||
.zci--conversions .x_axis_label {
|
||||
font-size: 0.6em;
|
||||
fill: #ACACAC;
|
||||
}
|
||||
|
||||
.zci--conversions .x_axis_gridline {
|
||||
stroke: #DCDCDC;
|
||||
}
|
||||
|
||||
.zci--conversions .major_range {
|
||||
fill: #9DCFE1;
|
||||
stroke: #9DCFE1;
|
||||
}
|
||||
|
||||
.zci--conversions .major_range_label {
|
||||
font-size: 0.7em;
|
||||
fill: #303030;
|
||||
}
|
||||
|
||||
.zci--conversions .minor_range {
|
||||
fill: #80C4DE;
|
||||
}
|
||||
|
||||
.zci--conversions .marker {
|
||||
stroke-width: 2px;
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
.zci--conversions .marker_tag {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.zci--conversions .marker_label {
|
||||
font-size: 0.5em;
|
||||
fill: #FFFFFF;
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
#!/usr/bin/env perl
|
||||
|
||||
# NOTE: Audible frequency results are currently being suppressed,
|
||||
# as the resulting IA is too long. This will be revisited when
|
||||
# better stying is available.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
@ -11,39 +15,103 @@ zci is_cached => 1;
|
|||
|
||||
ddg_goodie_test(
|
||||
['DDG::Goodie::FrequencySpectrum'],
|
||||
|
||||
#Primary example
|
||||
'50 hz' => test_zci(
|
||||
'50 Hz is a radio frequency in the SLF band used by submarine communication systems.
|
||||
50 Hz is also an audible frequency which can be produced by double-bass, piano, and tuba.
|
||||
More at https://en.wikipedia.org/wiki/Musical_acoustics',
|
||||
html => "50 Hz is a radio frequency in the SLF band used by submarine communication systems.<br>50 Hz is also an audible frequency which can be produced by double-bass, piano, and tuba.<br><a href='https://en.wikipedia.org/wiki/Musical_acoustics'>More at Wikipedia</a>",
|
||||
heading => '50 Hz (Frequency Spectrum)'
|
||||
#qr/radio.+SLF.+audible.+double-bass.+piano.+tuba/,
|
||||
qr/radio/,
|
||||
html => qr/radio/
|
||||
),
|
||||
|
||||
#Secondary example
|
||||
'400 thz' => test_zci(
|
||||
'400 THz is an electromagnetic frequency of red light.
|
||||
More at https://en.wikipedia.org/wiki/Color',
|
||||
html => "400 THz is an electromagnetic frequency of red light.<br><a href='https://en.wikipedia.org/wiki/Color'>More at Wikipedia</a>",
|
||||
heading => '400 THz (Frequency Spectrum)'
|
||||
qr/infrared/,
|
||||
html => qr/infrared/
|
||||
),
|
||||
|
||||
'4 thz' => undef,
|
||||
|
||||
#Misc
|
||||
'1,000 hz' => test_zci(
|
||||
'1 kHz is a radio frequency in the ULF band used by mine cave communication systems.
|
||||
1 kHz is also an audible frequency which can be produced by human voice, viola, violin, guitar, mandolin, banjo, piano, saxophone, flute, clarinet, and oboe.
|
||||
More at https://en.wikipedia.org/wiki/Musical_acoustics',
|
||||
html => "1 kHz is a radio frequency in the ULF band used by mine cave communication systems.<br>1 kHz is also an audible frequency which can be produced by human voice, viola, violin, guitar, mandolin, banjo, piano, saxophone, flute, clarinet, and oboe.<br><a href='https://en.wikipedia.org/wiki/Musical_acoustics'>More at Wikipedia</a>",
|
||||
heading => '1 kHz (Frequency Spectrum)'
|
||||
#qr/radio.+audible.+human.+voice.+viola.+violin.+guitar.+mandolin.+banjo.+piano.+saxophone.+flute.+clarinet.+oboe/,
|
||||
qr/radio/,
|
||||
html => qr/radio.+/
|
||||
),
|
||||
|
||||
'1000000.99 hz' => test_zci(
|
||||
'1.00000099 MHz is a radio frequency in the MF band used by AM broadcasts, navigation systems, and ship-to-shore communication systems.
|
||||
More at https://en.wikipedia.org/wiki/Radio_spectrum',
|
||||
html => "1.00000099 MHz is a radio frequency in the MF band used by AM broadcasts, navigation systems, and ship-to-shore communication systems.<br><a href='https://en.wikipedia.org/wiki/Radio_spectrum'>More at Wikipedia</a>",
|
||||
heading => '1.00000099 MHz (Frequency Spectrum)',
|
||||
qr/radio.+MF/,
|
||||
html => qr/radio.+MF/
|
||||
),
|
||||
'29.1 hz' => test_zci(
|
||||
qr/radio.+ELF/,
|
||||
html => qr/radio.+ELF/
|
||||
),
|
||||
|
||||
#No whitespace between number and unit
|
||||
'50hz' => test_zci(
|
||||
#qr/radio.+SLF.+audible.+double-bass.+piano.+tuba/,
|
||||
qr/radio/,
|
||||
html => qr/radio/
|
||||
),
|
||||
'400terahertz' => test_zci(
|
||||
qr/infrared/,
|
||||
html => qr/infrared/
|
||||
),
|
||||
|
||||
#Mixed case
|
||||
'400 THz' => test_zci(
|
||||
qr/infrared/,
|
||||
html => qr/infrared/
|
||||
),
|
||||
|
||||
'1000 HZ' => test_zci(
|
||||
#qr/radio.+audible.+human.+voice.+viola.+violin.+guitar.+mandolin.+banjo.+piano.+saxophone.+flute.+clarinet.+oboe/,
|
||||
qr/radio/,
|
||||
html => qr/radio.+/
|
||||
),
|
||||
|
||||
#Commas in number
|
||||
'1,000,000.99 hz' => test_zci(
|
||||
qr/radio.+MF/,
|
||||
html => qr/radio.+MF/
|
||||
),
|
||||
|
||||
#Can you test with all the colours of the wind?
|
||||
'650 nm' => test_zci(
|
||||
qr/visible.+red/,
|
||||
html => qr/visible.+red/
|
||||
),
|
||||
'610 nanometers' => test_zci(
|
||||
qr/visible.+orange/,
|
||||
html => qr/visible.+orange/
|
||||
),
|
||||
'580 nanometres' => test_zci(
|
||||
qr/visible.+yellow/,
|
||||
html => qr/visible.+yellow/
|
||||
),
|
||||
'536 nanometer' => test_zci(
|
||||
qr/visible.+green/,
|
||||
html => qr/visible.+green/
|
||||
),
|
||||
'478.1 nm' => test_zci(
|
||||
qr/visible.+blue/,
|
||||
html => qr/visible.+blue/
|
||||
),
|
||||
'380.000000000 nanometres' => test_zci(
|
||||
qr/visible.+violet/,
|
||||
html => qr/visible.+violet/
|
||||
),
|
||||
|
||||
#Only visible light wavelengths should trigger
|
||||
'0.1 nm' => undef,
|
||||
'100 nm' => undef,
|
||||
'800 nm' => undef,
|
||||
'10000 nm' => undef,
|
||||
|
||||
#Malformed frequencies/wavelengths should not trigger
|
||||
'1000.000..99 hz' => undef,
|
||||
'15 kilo hertz' => undef,
|
||||
'100,123 jiggahz' => undef,
|
||||
'hertz' => undef,
|
||||
'terahz' => undef,
|
||||
'600 nmeters' => undef,
|
||||
);
|
||||
|
||||
done_testing;
|
||||
|
|
Loading…
Reference in New Issue