diff options
Diffstat (limited to 'chordspeller.pl')
-rwxr-xr-x | chordspeller.pl | 858 |
1 files changed, 858 insertions, 0 deletions
diff --git a/chordspeller.pl b/chordspeller.pl new file mode 100755 index 0000000..66b498d --- /dev/null +++ b/chordspeller.pl @@ -0,0 +1,858 @@ +#!/usr/bin/perl -w + +our $self = $0; +$self =~ s,.*/,,; + +our $root = 0; +our $flat2 = 1; +our $second = 2; +our $flat3 = 3; +our $third = 4; +our $fourth = 5; +our $flat5 = 6; +our $fifth = 7; +our $flat6 = $sharp5 = 8; +our $sixth = 9; +our $flat7 = 10; +our $seventh = 11; +our $octave = 12; +our $ninth = $octave + $second; +our $tenth = $octave + $third; +our $eleventh = $octave + $fourth; +our $thirteenth = $octave + $sixth; + +our @majorscale = (0, 2, 4, 5, 7, 9, 11); +our @minorscale = (0, 2, 3, 5, 7, 8, 10); +our @major_chord_types = (qw/maj min min maj maj min dim/); + +our @intervalnames = ( + qw/r b2 2 b3 3 4 b5 5 b6 6 b7 7/ ); + +# TODO: lots more chords +# also, try to parse the notation instead of having a fixed table. +# also also, this is the only place in the program where a ninth is +# really a ninth (14 semitones up) instead of being reduced to a 2nd +# (2 semitones up). + +# btw, "diminished" means "diminished triad", no 7th (older jazz books +# used "diminished" to mean "diminished triad plus 7th", which we're +# calling "diminished 7th" here) + +our %shapes = ( + 5 => [ $root, $fifth ], + no5 => [ $root, $third ], + maj => [ $root, $third, $fifth ], + min => [ $root, $flat3, $fifth ], + 'min(no5)' => [ $root, $flat3 ], + 7 => [ $root, $third, $fifth, $flat7 ], + maj7 => [ $root, $third, $fifth, $seventh ], + min7 => [ $root, $flat3, $fifth, $flat7 ], + sus2 => [ $root, $second, $fifth ], + sus4 => [ $root, $fourth, $fifth ], + add9 => [ $root, $third, $fifth, $ninth ], + 9 => [ $root, $third, $fifth, $seventh, $ninth ], + 'min/maj7' => [ $root, $flat3, $fifth, $seventh ], + aug => [ $root, $third, $sharp5 ], + aug7 => [ $root, $third, $sharp5, $flat7 ], + dim => [ $root, $flat3, $flat5 ], + dim7 => [ $root, $flat3, $flat5, $sixth ], + adim => [ $root, $flat3, $flat5, $flat7 ], + 'dim(no7)' => [ $root, $flat3, $flat5 ], + 'dim(no3)' => [ $root, $flat5 ], + 'dim7(no3)' => [ $root, $flat5, $flat7 ], + 6 => [ $root, $third, $fifth, $sixth ], + min6 => [ $root, $flat3, $fifth, $sixth ], + '6/9' => [ $root, $third, $fifth, $sixth, $ninth ], + 'min6/9' => [ $root, $flat3, $fifth, $sixth, $ninth ], + 'maj7(no3)' => [ $root, $fifth, $seventh ], + 'maj7(no5)' => [ $root, $third, $seventh ], + '7(no3)' => [ $root, $fifth, $flat7 ], + '7(no5)' => [ $root, $third, $flat7 ], + 'm7(no5)' => [ $root, $flat3, $flat7 ], + '7#9' => [ $root, $third, $fifth, $flat7, $flat3 ], + '7#9(no5)' => [ $root, $third, $flat7, $flat3 ], + 'm/add11' => [ $root, $flat3, $fifth, $eleventh ], + 'add11' => [ $root, $third, $fifth, $eleventh ], + '11' => [ $root, $third, $fifth, $flat7, $eleventh ], + 'min11' => [ $root, $flat3, $fifth, $flat7, $eleventh ], + 'm/add13' => [ $root, $flat3, $fifth, $thirteenth ], + 'add13' => [ $root, $third, $fifth, $thirteenth ], + '13' => [ $root, $third, $fifth, $flat7, $eleventh, $thirteenth ], + 'min13' => [ $root, $flat3, $fifth, $flat7, $eleventh, $thirteenth ], +); + +our @sharpnames = ( 'c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b' ); +our @flatnames = ( 'c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'bb', 'b' ); +our $notenames = \@sharpnames; +our @sharpkeys = (0, 7, 2, 9, 4, 11); +our @keysigs = ( + 'nat', '5b', '2#', '3b', '4#', '1b', '6b', '1#', '4b', '3#', '2b', '5#'); + +our $transpose = 0; + +our @openstrings; +our $fretpos = 0; +our $fretspan = 3; +our $allowpartial = 0; # set with -p +our $allowholes = 1; # set with -l + +our %notevals = ( + 'c' => 0, + 'c#' => 1, + 'db' => 1, + 'd' => 2, + 'd#' => 3, + 'eb' => 3, + 'e' => 4, + 'f' => 5, + 'f#' => 6, + 'gb' => 6, + 'g' => 7, + 'g#' => 8, + 'ab' => 8, + 'a' => 9, + 'a#' => 10, + 'bb' => 10, + 'b' => 11 ); + +our @chordints = (qw/I bII II bIII III IV bV V bVI VI bVII VIII/); + +our @keynotes = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); +our @chords = (); +our $key; # undef = guess +our $forceroot = 0; # set with -r + +sub fix_note { + my $note = shift; + $note %= 12; + return $note; +} + +sub get_note_name { + return ucfirst($$notenames[fix_note($_[0])]); +} + +sub name_to_note { + my $note = lc($_[0]); + return $notevals{$note}; +} + +sub add_interval { + return fix_note($_[0] + $_[1]); +} + +sub get_interval { + return fix_note($_[0] - $_[1]); +} + +sub determine_chord { + my $root = shift; + my @notes = @_; + + for my $name (sort keys %shapes) { + my $shape = $shapes{$name}; + my $rootname = $$notenames[$root]; + my @trynotes; + for(@$shape) { + push @trynotes, add_interval($root, $_); + } + + @trynotes = sort { $a <=> $b } @trynotes; + + if(@trynotes != @notes) { +#warn "$rootname$name: extra notes\n"; + } elsif(@trynotes < @notes) { +# warn "$rootname$name: missing notes\n"; + } else { +# warn "$rootname$name: same # of notes\n"; + my $match = 1; + for(my $i=0; $i<@notes; $i++) { +#warn $notes[$i] . ", " . $trynotes[$i]; + $match = 0 if $notes[$i] != $trynotes[$i]; + } + return $rootname . $name if $match; + } + } + + return undef; +} + +sub discover_chord { + my @frets = split /[^x\d]/i, $_[0]; + + my %notes; + my $root; # undef = try them all + for(my $string = 0; $string < @openstrings; $string++) { + next if (not defined $frets[$string]) || ($frets[$string] =~ /x/i); + my $note = ($openstrings[$string] + $frets[$string]) % 12; + if($forceroot && not defined $root) { + $root = $note; + } + $notes{$note}++; + } + + my @notes = sort { $a <=> $b } keys %notes; + print "discover_chord(): notes are "; + print "$$notenames[$_] " for @notes; + print "\n"; + +#warn "@notes"; + + if(defined $root) { + return determine_chord($root, @notes); + } else { + for(@notes) { + $ret = determine_chord($_, @notes); + return $ret if $ret; + } + } + + return undef; +} + +sub parse_chord { + my $chord = lc(shift); + my $fingering; + my @frets; + + parse_tuning($tunings{std}) unless @openstrings; + + # tablature input instead of chord symbol? + if($chord =~ /^[x\d]/) { + @frets = split /[^x\d]/i, $chord; + my $gotchord = discover_chord($chord); + if(!$gotchord) { + warn "$self: can't figure out chord name for fingering $chord\n"; + return 0; + } + $chord = $gotchord; + } + + # alternate notation + $chord =~ s/\+/aug/; + $chord =~ s/\-/dim/; + + # rudimentary support for english (e.g. "A minor 7") + $chord =~ s/flat/b/gi; + $chord =~ s/sharp/#/gi; + $chord =~ s/minor/m/gi; + $chord =~ s/major/maj/gi; + $chord =~ s/\s//g; + + my ($note, $type) = ($chord =~ /^([a-g][#b]?)(.*)?/); + + unless($note) { + warn "$self: Invalid chord $chord\n"; + return 0; + } + + $type = "maj" unless $type; + + $type =~ s/^m(#|b|\d|$)/min$1/; + + my $shape = $shapes{$type}; + + unless($shape) { + warn "$self: Unknown chord type $type\n"; + return 0; + } + + my $val = $notevals{$note}; + + unless(defined $val) { + warn "$self: Unknown note $note\n"; + return 0; + } + + $val = ($val + $transpose) % 12; + $note = $$notenames[$val]; + + my @notes; + for(@$shape) { + my $n = ($val + $_) % 12; + $keynotes[$n]++; + } + + my $parsed = [ $val, $note, $type ]; + if(@frets) { + for(@frets) { + $_ = undef if $_ =~ /x/i; + } + $fingering = render_fingering($val, \@frets); + } else { + for(my $startfret = $fretpos; $startfret < $fretpos + 12; $startfret++) { + $fingering = find_guitar_chord($startfret, $parsed); + last if $fingering; + } + } + push @$parsed, $fingering if $fingering; + push @chords, $parsed; + + return 1; +} + +sub dump_chord_shapes { + for(sort keys %shapes) { + my $shape = $shapes{$_}; + print "$_: "; + for(@$shape) { + print $intervalnames[$_ % 12] . " "; + } + print "\n"; + } +} + +sub print_chord { + my $c = shift; + my ($val, $note, $type, $fingering) = @$c; + my $shape = $shapes{$type}; + + $type =~ s/maj$//; # in output, say A, not Amaj + + my @notes; + for(@$shape) { + $_ = fix_note($_); # TODO: this turns e.g. 9ths into 2nds + my $n = add_interval($val, $_); + push @notes, get_note_name($n) . "=" . $intervalnames[$_]; + } + +#my $chordname = ucfirst($note) . $type; + my $chordname = get_note_name($val) . $type; + if(defined($key)) { + my $keyint = $val - $key; + $keyint += 12 if $keyint < 0; + my $chordint = $chordints[$keyint]; + if($type =~ /(min|dim)/) { # TODO: this heuristic is lame + $chordint = lc($chordint); + } + $chordname .= " (" . $chordint . ")"; + } + + print $chordname . ": " . join(" ", @notes) . "\n"; + + if($fingering) { + print $fingering . "\n"; + } else { + print "(couldn't find fingering at given fret pos)\n"; + } + print "\n"; +} + +sub parse_tuning { + my $tuning = shift; + my $offset = shift || 0; + + $tuning =~ s/\s//g; # allow & ignore spaces + $tuning = lc $tuning; # allow uppercase + + die "$self: Invalid tuning '$tuning'\n" unless $tuning =~ /^[a-g#]+$/; + + my @notes = ($tuning =~ /([a-g][#]?)/g); + die "$self: Invalid tuning '$tuning'\n" unless @notes; + + @openstrings = (); + my @names; + for(@notes) { + my $note = fix_note(add_interval($notevals{$_}, $offset)); + push @openstrings, $note; + push @names, get_note_name($note); + } + + print "Tuning: " . join(" ", @names) . "\n"; +} + +# Tuning list isn't really meant to be exhaustive, but should include +# plenty of variants... +our @tuning_list = ( + [ 'std', 'eadgbe', 'Standard Guitar [default]' ], + [ 'eb', 'd#g#c#f#a#d#', 'Guitar 1/2 step flat (Eb)' ], + [ 'd', 'dgcfad', 'Guitar 1 step flat (D)' ], + [ 'c#', 'c#f#beg#c#', 'Guitar 1 1/2 steps flat (C#)' ], + [ 'c', 'cfa#d#gc', 'Guitar 2 steps flat (C)' ], + [ 'od', 'dadf#ad', 'Open D' ], + [ 'og', 'dgdgbd', 'Open G' ], + [ 'oe', 'ebeg#be', 'Open E' ], + [ 'oeb', 'd#a#d#ga#d#', 'Open Eb' ], + [ 'oa', 'eac#eae', 'Open A' ], + [ 'dd', 'dadgbe', 'Drop D' ], + [ 'dc#', 'c#g#c#f#a#d#', 'Drop C#' ], + [ 'dc', 'cgcfad', 'Drop C' ], + [ 'nst', 'cgdaeg', 'Fripp\'s New Standard Tuning' ], + [ 'b', 'eadg', 'Bass (4-string)' ], + [ '5', 'beadg', 'Bass (5-string)' ], + [ '6', 'beadgc', 'Bass (6-string)' ], + [ '6b', 'beadgb', 'Bass (6-string alt)' ], + [ 'r', 'beadf#b', 'Baritone Guitar or alt 6-string bass' ], + [ '7', 'beadgbe', '7-string Guitar (low B)' ], + [ '7h', 'eadgbea', '7-string Guitar (high A)' ], + [ '7d', 'aeadgbe', '7-string Guitar (drop A)' ], + [ 't', 'gcfa#de', 'Terz Guitar' ], + [ 'm', 'gdae', 'Mandolin' ], + [ 'l', 'eadf#be', 'Lute' ], + [ 'h', 'adgbe', 'Mexican Vihuela' ], + [ 'cv', 'cgda', 'Cello or Viola' ], + [ 'v', 'gdae', 'Violin' ], + [ 'vs', 'adae', 'Violin (scordatura)' ], + [ 'u', 'gcae', 'Ukulele' ], + [ 'ub', 'dgbe', 'Baritone Ukulele' ], +); + +our %tunings; + +sub tuning_help { + for(@tuning_list) { + my ($tag, $notes, $name) = @$_; + printf("-t%-4s %s (same as -t%s)\n", $tag, $name, $notes); + } + exit 0; +} + +sub usage { + warn <<EOF; +Usage: $self [globalopts] [chordopts] <chord> [[chordopts] <chord>] ... + +Chords can be either standard notation (e.g. A, Bm, C#maj7), or comma- +separated fret positions, low to high, (e.g. 3,2,0,0,3,3 is a G in +standard guitar tuning). Use x to indicate strings not played (e.g. +x,x,0,2,3,2 is a commonly-used D chord). + +Global Options: + -t<notes> Set tuning, low string to high. Default is eadgbe. Instrument + is assumed to have as many strings as there are notes. Do not + specify notes as flats (because the letter b is used for both + the b note and the flat symbol). Use e.g. d# instead of eb. + -t<type> Set one of the built-in tunings. Use -th or -thelp for list. + -x<x> Transpose x semitones (x is positive or negative integer) + -D Dump all known chord types + -k<key> Set the key for all chords (default is to guess key) + +Per-Chord Options: Once set, these stay in effect for subsequent chords. + + -b Print flat note names (e.g. Db instead of C#) + -s Print sharp note names (e.g. C# instead of Db) [default] + -f<x> Place chord at fret position x (x=0 default, means open strings). + (Actually, this is the minimum position: if no useful notes + are found at x, we try x+1, x+2, ..., x+12) + -s<x> Maximum fret span. Default is 3. + -v Find variant chord voicing. Doesn't always do anything useful. + (for example, this gives 3,2,0,0,3,3 instead of 3,2,0,0,0,3 + for an open G chord in standard tuning) + -n Find normal voicing (default), turns off previous -v option. + -r Force lowest bass note to be root of chord. + -i Allow inversions (turns off -r). This is the default. + -p Allow partial voicings (e.g. only C and E for a C major chord). + Use with caution, especially with 7, 9, etc. chords (the + matcher will consider a C major triad to match C7 or C9). +EOF + exit 0; + +# future options: +# -l Loose matching. Accepts chord fingerings with "holes" in +# the middle (e.g. 65X766 for A#). +# -o Include open strings even in non-open -f positions. +# -a Chart all chord voicings found for each chord (default is to +# stop after first one is found). May be combined with -f, e.g. +# -f5 -a means "all chord voicings from 5th fret up". +# -c<x> Capo on fret x. +# -T Tablature notation instead of chord charts, for -t option. +# -m[file] Generate MIDI file of chords. With [file], output to file. +# Without [file], play using timidity. +# -O<type> Output type: text (default), ps, pdf, html, maybe xml someday. +# -w<x> Set output width for text output. Default: $COLUMNS from +# environment, or 80 if not set. +} + +sub has_hole { + my @voicing = @_; + shift @voicing while not defined $voicing[0]; + pop @voicing while not defined $voicing[-1]; + return 1 if @voicing == 0; # whoopsie! + for(@voicing) { + return 1 if not defined $_; + } + return 0; +} + +# for each string, go thru the note values for frets $fretpos to +# $fretpos+$fretspan, looking at each note in @vals. +# TODO: de-shittify this. It was thrown together between 2 and 5AM, +# and it looks like it. +sub find_guitar_chord { + my $startfret = shift; + my $c = shift; + my ($val, $note, $type) = @$c; + my $shape = $shapes{$type}; + my @voicing; + my $string; + my $fret; + + $note = uc $note; + $type =~ s/maj$//; # in output, say A, not Amaj + + my @vals; + for(@$shape) { + my $n = ($val + $_) % 12; + push @vals, $n; + } + @vals = sort @vals; # for e.g. 9 and add9 chords + + my $chordname = ucfirst($note) . $type; + + push @voicing, undef for @openstrings; + + my %foundvals; + my $rootfound = 0; + for($string=0; $string<@openstrings; $string++) { + my ($start, $end, $step); + if($findbackwards) { + $start = $startfret + $fretspan; + $end = $startfret - 1; + $step = -1; + } else { + $start = $startfret; + $end = $startfret + $fretspan + 1; + $step = 1; + } +FRET: + for($fret = $start; $fret != $end; $fret+=$step) { + my $fretval = ($openstrings[$string] + $fret) % 12; +VAL: + for(@vals) { +#warn "\$string==$string \$fret==$fret \$fretval==$fretval \$_==$_\n"; + if($_ == $fretval) { + if($forceroot && !$rootfound && $_ != $val) { + next VAL; + } + $rootfound = 1 if $_ == $val; + $voicing[$string] = $fret; + $foundvals{$_}++; + last FRET; + } + } + } + } + + if(scalar keys %foundvals < @vals) { + if($allowpartial) { +#warn "$note$type: didn't find all note values, return partial chord\n"; + } else { +#warn "$note$type: didn't find all note values, return nothing\n"; + return undef; + } + } + + if(not $allowholes) { + if(has_hole(@voicing)) { +#warn "$note$type: has a hole\n"; + return undef; + } + } + + my $r = render_fingering($val, \@voicing); + return $r; +} + +sub render_fingering { + my $val = $_[0]; + my @voicing = @{$_[1]}; + my ($lowfret, $highfret) = (999, -1); + for(@voicing) { + next unless defined $_; + $lowfret = $_ if $_ < $lowfret; + $highfret = $_ if $_ > $highfret; + } + + my @grid; + my ($intervals, $names); + for($string=0; $string<@openstrings; $string++) { + my $fret = $voicing[$string]; + if(not defined($fret)) { + $intervals .= " "; + $names .= " "; + } else { + my $noteval = ($openstrings[$string] + $fret) % 12; + my $interval = $noteval - $val; + $interval += 12 if $interval < 0; + $interval %= 12; + $intervals .= sprintf("%3s", $intervalnames[$interval]); + $names .= sprintf("%3s", ucfirst $$notenames[$noteval]); + } + + for($lowfret..$highfret) { + my $empty = "|"; + if($_ == $lowfret && $lowfret == 0) { + $empty = "+"; + } + + $grid[$_ - $lowfret][$string] = $empty; + } + + if(defined($fret)) { + $grid[$fret-$lowfret][$string] = $fret; + } else { + $grid[0][$string] = 'X'; + } + } + + my @rendered; + for(@grid) { + $_->[0] =~ s/^/ / if length($_->[0]) == 1; + $_->[0] =~ s/^/ /; + for($string=1; $string<@openstrings; $string++) { + $_->[$string] =~ s/^/-/ if length($_->[$string]) == 1; + $_->[$string] =~ s/^/-/; + } + push @rendered, join("", @$_); + } + push @rendered, $names; + push @rendered, $intervals; + return join("\n", @rendered); +} + +# TODO: make this smarter. Currently it completely ignores accidentals +# and takes the first match it finds. Also it gives up if it can't get +# an exact match with a major scale (e.g. if given C and G chords only, +# it gives up, whereas it really should guess either C or G as the key) +# Probably need some kind of weighting system. +sub guess_key { + my ($k, $n, $ret, @possibles); + for $k (0..11) { + my $found = 1; + for $n (0, 2, 4, 5, 7, 9, 11) { + $found = 0 if not $keynotes[($k + $n) % 12]; + } + if($found) { + push @possibles, uc($$notenames[$k]); + $ret = $k unless defined $ret; + } + } + + if(@possibles == 0) { + warn "$self: Can't guess key, use -k to set\n"; + } elsif(@possibles > 1) { + warn "$self: Possible keys: " . join(" ", @possibles) . + ", guessing " . $possibles[0] . ", use -k to set\n"; + } + + if($ret) { + if(grep { $_ == $ret } @sharpkeys) { + $notenames = \@sharpnames; + } else { + $notenames = \@flatnames; + } + } + + return $ret; +} + +sub parse_opt { + my $opt = shift; + $opt =~ s/^-+//; + + if($opt eq 'h') { + usage(); + } elsif($opt eq 'b') { + $notenames = \@flatnames; + } elsif($opt eq 's') { + $notenames = \@sharpnames; + } elsif($opt =~ /^x([+-]?\d+)/) { + $transpose = $1 + 0; + } elsif($opt =~ /^k([a-g][#b]?)(m?)/) { + my $keyopt = $1; + my $minor = $2; + $key = $notevals{lc $keyopt}; + if(defined($key) and $minor) { + $key += 3; + $key %= 12; + } + if(defined($key)) { + if(grep { $_ == $key } @sharpkeys) { + $notenames = \@sharpnames; + } else { + $notenames = \@flatnames; + } + } else { + warn "$self: Invalid key '$1', ignoring\n"; + } + } elsif($opt =~ /^f(\d+)/) { + $fretpos = $1 + 0; + } elsif($opt =~ /^s(\d+)/) { + $fretspan = $1 + 0; + } elsif($opt =~ /^t(.*)/) { + my $offset = 0; + my $tuning = $1; + if($tuning =~ s/([-+]\d+)$//) { + $offset = $1; + } + + if($tuning =~ /^h(?:elp)?/i) { + tuning_help(); + } elsif($tunings{$tuning}) { + parse_tuning($tunings{$tuning}, $offset); + } else { + parse_tuning($tuning, $offset); + } + } elsif($opt eq 'r') { + $forceroot = 1; + } elsif($opt eq 'i') { + $forceroot = 0; + } elsif($opt eq 'n') { + $findbackwards = 0; + } elsif($opt eq 'v') { + $findbackwards = 1; + } elsif($opt eq 'p') { + $allowpartial = 1; +#} elsif($opt eq 'l') { +#$allowholes = 0; + } elsif($opt eq 'd') { + dump_chord_shapes(); + } else { + warn "$self: Invalid option '$_', use -h for help\n"; + } +} + +# main() + +for(@tuning_list) { + $tunings{$_->[0]} = $_->[1]; +} + + +for(@ARGV) { + if(/^-/) { + parse_opt($_); + } else { + parse_chord($_); + } +} + +if((defined $key) && !@chords) { +# got a -k but no chords, show all triad chords in this key. + for(my $i = 0; $i < @majorscale; $i++) { + my $note = add_interval($majorscale[$i], $key); + my $name = $$notenames[$note]; + my $type = $major_chord_types[$i]; + parse_chord($name . $type); + } +} + +unless(@chords) { + warn "$self: Found no valid chords\n"; + usage(); + exit 1; +} + +unless(defined($key)) { + $key = guess_key(); +} + +for(@chords) { + print_chord($_); +} + +if(defined $key) { + my $keyname = ucfirst($$notenames[$key]); + my $minkey = fix_note($key - 3); + my $minkeyname = ucfirst($$notenames[$minkey]); + print "Key: " . $keyname . " (" . $minkeyname . "m), " . $keysigs[$key] . "\n"; + print $keyname . " major scale: "; + for(@majorscale) { + my $note = ($key + $_) % 12; + print ucfirst($$notenames[$note]) . " "; + } + print "\n"; + print $minkeyname . " minor scale: "; + for(@minorscale) { + my $note = ($minkey + $_) % 12; + print ucfirst($$notenames[$note]) . " "; + } + print "\n"; +} + +# examples: + +# I know how to finger some chords, and need to know what they're called, +# what notes are in them, and what key (if any) those chords imply: +# chordspeller.pl 3,2,0,0,3,3 x,0,2,2,2,0 x,x,0,2,3,2 + +# I know the chords in a song, and need to know the key of the song: +# chordspeller.pl G A D + +# I know the chords in a song, and need to transpose it from G to A +# (up 2 semitones): +# chordspeller.pl -x2 G C Am D + +# Any of the above can be done in any tuning other than standard guitar +# tuning by way of a -t<tuning> argument (see the help for the list). + +__END__ +---=cut-here=------- +Gmin (ii) +10-10--|--|--|-10 + | | | | | | + |--|--|--|-11--| + | | | | | | + |--|-12-12--+--| + | | | | | | + |--|--|--|--+--| + | | | | | | + D G D G Bb D + 5 r 5 r b3 5 + +---=cut-here=------- + +Each chord is 20x12 with fretspan 3 and 6 strings (actually they're 19x11 +with 3 columns and one row of padding). This allows a printer page that's +80x66 chars to have a 4x5 grid of them (20 chords), with some room left +at the top/bottom for things like the key, scales, etc. Also a 80x24 +xterm can display a 4x2 grid (8 chords). + +Formula: +width = strings*3+1 + 3(padding) +height = (fretspan+1) * 2 + 1(padding) + 1(title) + 1(notes) + 1(intervals) + +Compressed form: +---=cut-here=------- +Gmin (ii) +10-10--|--|--|-10 + |--|--|--|-11--| + |--|-12-12--|--| + |--|--|--|--|--| + D G D G Bb D + 5 r 5 r b3 5 + +---=cut-here=------- +height = (fretspan+1) + 1(padding) + 1(title) + 1(notes) + 1(intervals) + +6string, span3, you get 20x8, or 4x8 on 80x66 page, or 4x3 on 80x25 xterm. + + +Very compressed form: +---=cut-here= +Gmin (ii) +1010-|-|-|10 + |-|-|-|11-| + |-|1212-|-| + |-|-|-|-|-| + D G D GBb D + 5 r 5 rb3 5 + +---=cut-here= +width = strings*2 + 1(padding) +height = (fretspan+1) + 1(padding) + 1(title) + 1(notes) + 1(intervals) + +13x8. Gives 6x8 on paper, 6x3 on xterm, but harder to read... not so bad +if all frets are single-digit though: + +---=cut-here= +G7 (I) + +-+-0-0-0-+ + |-|-|-|-|-1 + |-2-|-|-|-| + 3-|-|-|-|-| + G B D G B G + 1 3 5 1 3b7 + +---=cut-here= +(notice the + instead of |, in the 0 position) + |