zeroclickinfo-goodies/lib/DDG/Goodie/ChordDiagrams.pm

236 lines
6.4 KiB
Perl
Raw Normal View History

2015-11-19 15:40:59 -08:00
package DDG::Goodie::ChordDiagrams;
# ABSTRACT: For getting the fingering for chords on popular strings instruments
use DDG::Goodie;
2015-12-03 18:43:39 -08:00
use SVG;
2016-01-20 14:15:25 -08:00
use JSON;
2015-12-21 16:55:02 -08:00
# docs: http://search.cpan.org/~ronan/SVG-2.33/lib/SVG/Manual.pm
2015-11-19 15:40:59 -08:00
zci answer_type => "chord_diagrams";
zci is_cached => 1;
name "Chord";
primary_example_queries "C ukulele chord", "F# minor guitar tab";
2015-11-19 15:28:17 -08:00
secondary_example_queries "Ebmaj7 ukulele chord";
description "Shows a tab representing the correct frets, for a given chord, on a given strings instrument";
code_url "https://github.com/duckduckgo/zeroclickinfo-goodies/blob/master/lib/DDG/Goodie/Chord.pm";
2015-11-19 15:28:17 -08:00
topics "music";
category "reference";
2015-11-19 13:19:26 -08:00
attribution github => ["http://github.com/gerhuyy", "gerhuyy"],
web => ["http://charliethe.ninja", "charles-l"],
github => ["http://github.com/charles-l", "charles-l"];
triggers any => "chord", "tab";
2016-01-20 14:15:25 -08:00
local $/;
open(my $fh, '<', share('guitar.json'));
my $json_text = <$fh>;
my $new_chords = decode_json($json_text);
# Store the instruments that the program will respond to, with a
# list storing the note of each string in order. (Add one to note
# for sharps, and subtract one for flats)
2016-01-20 15:27:15 -08:00
my @instruments = (
"guitar"
);
2016-01-20 14:15:25 -08:00
# create svg X for muted strings
sub mk_x {
my $svg = shift;
my $x = shift;
my $y = shift;
my $size = shift;
$svg->line(
x1=>$x - $size/2,
y1=>$y - $size/2,
x2=>$x + $size/2,
y2=>$y + $size/2,
style=>{
'stroke'=>'black',
'stroke-width'=>'2'
});
$svg->line(
x1=>$x - $size/2,
y1=>$y + $size/2,
x2=>$x + $size/2,
y2=>$y - $size/2,
style=>{
'stroke'=>'black',
'stroke-width'=>'2'
});
};
2015-12-03 18:43:39 -08:00
# Generate chord SVG
sub gen_svg {
my (%opts) = @_;
my $svg = SVG->new(width=>$opts{"width"}, height=>$opts{"height"});
2015-12-04 16:27:26 -08:00
my $top_pad = 30;
2015-12-03 19:21:56 -08:00
$svg->line(
2016-01-20 14:15:25 -08:00
x1=>0,
y1=>$top_pad,
x2=>$opts{"width"},
y2=>$top_pad,
style=>{
'stroke'=>'black',
'stroke-width'=>'4'
});
my $fret_dist = (($opts{"height"} - $top_pad) / ($opts{"frets"}));
for (my $i = 0; $i < $opts{"frets"}; $i++) {
$svg->line(
2015-12-03 19:21:56 -08:00
x1=>0,
2016-01-20 14:15:25 -08:00
y1=>$top_pad + 2 + $i * $fret_dist,
2015-12-03 19:21:56 -08:00
x2=>$opts{"width"},
2016-01-20 14:15:25 -08:00
y2=>$top_pad + 2 + $i * $fret_dist,
2015-12-03 19:21:56 -08:00
style=>{
'stroke'=>'black',
2016-01-20 14:15:25 -08:00
'stroke-width'=>'2'
2015-12-03 19:21:56 -08:00
});
}
for (my $i = 0; $i < $opts{"strings"}; $i++) {
$svg->line(
2016-01-20 14:15:25 -08:00
x1=>1 + $i * (($opts{"width"} - 2) / ($opts{"strings"} - 1)),
y1=>$top_pad,
x2=>1 + $i * (($opts{"width"} - 2) / ($opts{"strings"} - 1)),
y2=>$opts{"height"},
style=>{
'stroke'=>'black',
'stroke-width'=>'2'
});
2015-12-03 19:21:56 -08:00
}
2015-12-04 15:45:55 -08:00
my $i = 0;
my $p_dist = ($opts{"width"} - 2) / ($opts{"strings"} - 1);
for my $p (@{$opts{"points"}}) {
2015-12-21 16:55:02 -08:00
last if ($i >= $opts{"strings"});
2015-12-04 16:23:19 -08:00
my $fill = 'black';
$fill = 'none' if ($p == 0);
2016-01-20 14:15:25 -08:00
if ($p == -1) {
mk_x($svg,
$i * $p_dist + 1,
$top_pad - $fret_dist/2 + 1,
10)
} else {
$svg->circle(
2015-12-04 16:23:19 -08:00
cx=>$i * $p_dist + 1,
2016-02-16 11:53:25 -08:00
cy=>$top_pad + $fret_dist * $p - $fret_dist/2 + 2,
2015-12-04 16:23:19 -08:00
r=>5,
style=>{
'stroke'=>'black',
'stroke-width'=>2,
'fill'=>$fill
});
2016-01-20 14:15:25 -08:00
}
2015-12-04 15:45:55 -08:00
$i++;
}
2015-12-03 18:43:39 -08:00
return $svg;
};
# The input parser. Uses regex to find the key to put the chord in, and the
# chord if they are conjoined.
# Also greps through the input words, looking for matches within the
# chords and instrument hashes
sub items{
my @words = split(" ", lc $_[0]);
$_[0] = join("sharp", split("#", $_[0]));
2016-01-20 16:42:21 -08:00
my ($temp, $key, $mod, $chord, $dom, $temp2) = /( |^)([a-g])(sharp|b|)(m|min|minor|M|maj|major|sus[24]|aug9?|)(5|7|9|11|13|)( |$)/i ;
if(/( |^)(5|7|9)( |$)/i){
($temp, $dom, $temp2) = /( |^)(5|7|9|11|13)( |$)/i;
}
if(/( |^)(5|7|9)th( |$)/i){
($temp, $dom, $temp2) = /( |^)(5|7|9|11|13)th( |$)/i;
}
2015-11-19 13:09:39 -08:00
my %mod_hash = (sharp => 1, b => -1);
if (defined $mod) {
$mod = $mod_hash{$mod} || 0;
}
2016-01-20 15:27:15 -08:00
if(defined $chord && ($chord eq "m" || $chord =~ /(min|minor)/i)){
2016-01-20 14:29:01 -08:00
$chord = "min";
2015-11-07 15:03:36 -08:00
}elsif(defined $chord && ($chord eq "M" || $chord =~ /(maj|major)/i)){
2016-01-20 14:29:01 -08:00
$chord = "maj";
2015-11-07 15:03:36 -08:00
}elsif(defined $chord && $chord =~ /sus[24]/i){
2015-11-19 13:09:39 -08:00
$chord = lc $chord;
2016-01-20 16:42:21 -08:00
}elsif(defined $chord && $chord =~ /aug/i){
$chord = lc $chord;
}elsif($dom){
$chord = "dominant";
}else{
2016-01-20 14:29:01 -08:00
$chord = "maj";
};
if(!$dom){
2016-01-20 16:42:21 -08:00
$dom = "";
};
2016-01-20 15:27:15 -08:00
my $instr = grep(@instruments, @words);
return $instr, $chord, uc $key, $mod, $dom;
};
2016-01-20 14:15:25 -08:00
sub get_chord {
my $chord = shift;
my $name = shift; # maj, 5, min, etc.
foreach(@$new_chords) {
if (grep(/^$chord$/, @{%$_{'root'}})) {
foreach(@{%$_{'types'}}) {
if(%$_{'name'} eq $name) {
return(\@{%$_{'variations'}});
}
}
}
}
2016-01-20 15:27:15 -08:00
return undef;
2016-01-20 14:15:25 -08:00
};
# Handle statement
handle remainder => sub {
my ($instr_name, $chord_name, $key_name, $mod, $dom) = items($_);
2015-11-07 15:03:36 -08:00
if((defined $instr_name) && (defined $chord_name) && (defined $key_name)){
2016-01-20 15:27:15 -08:00
my $strings = 6;
2016-01-20 14:29:01 -08:00
my $length = 4;
2016-01-20 16:44:23 -08:00
my $input = join(" ", (uc $key_name) . (($mod == -1)? "b" :(($mod == 1)? "#" : "" )), $chord_name . $dom, "guitar chord");
2015-11-19 15:28:17 -08:00
2016-01-20 15:27:15 -08:00
if ($mod == -1) {
$mod = 'b';
} elsif ($mod == 1) {
$mod = '#'
} else {
$mod = '';
}
2016-01-20 16:42:21 -08:00
my $r = get_chord($key_name . $mod, $chord_name . $dom);
return if not defined $r;
2016-02-16 12:12:49 -08:00
my @results = @{$r};
@results = map {
svg => gen_svg(
'width'=>100,
'height'=>120,
'frets'=>$length,
'strings'=>$strings,
'points'=> $_,
)->xmlify,
}, @results;
2016-01-20 14:29:01 -08:00
return 'chord_diagrams', structured_answer => {
id => 'chord_diagrams',
name => 'Music',
2016-02-16 12:12:49 -08:00
data => \@results,
2016-01-20 14:29:01 -08:00
templates => {
2016-02-16 11:53:25 -08:00
detail => 0,
item => 'base_item',
2016-01-20 14:29:01 -08:00
options => {
2016-02-16 11:53:25 -08:00
url => "www.google.com",
2016-01-20 14:29:01 -08:00
content => 'DDH.chord_diagrams.detail'
2016-02-16 11:53:25 -08:00
},
variants => {
tile => 'narrow'
2016-01-20 14:29:01 -08:00
}
},
meta => {}
2015-11-19 15:28:17 -08:00
};
};
2015-11-07 15:03:36 -08:00
return;
};
1;