Wednesday, December 30, 2009

Key signatures

After making my last post, I was brimming over with confidence. Obviously I'd done all the hard bits, and it was just a matter of putting the pieces together, right?

Wrong. Perl 6 makes parsing the ABC data so darned easy that the next bit seems completely unreasonable. At least, I haven't figured out a way to make it elegant yet. Figuring out which sharps or flats are in a key signature is tricky stuff, and as far as I can figure, Perl 6 doesn't really have any clever tools to make it easier.
sub key_signature($key_signature_name)
{
my %keys = (
'C' => 0,
'G' => 1,
'D' => 2,
'A' => 3,
'E' => 4,
'B' => 5,
'F#' => 6,
'C#' => 7,
'F' => -1,
'Bb' => -2,
'Eb' => -3,
'Ab' => -4,
'Db' => -5,
'Gb' => -6,
'Cb' => -7
);

my $match = $key_signature_name ~~ m/ <ABC::basenote> ('#' | 'b')? \h* (\w*) /;
die "Illegal key signature\n" unless $match ~~ Match;
my $lookup = [~] $match<ABC::basenote>.uc, $match[0];
my $sharps = %keys{$lookup};

if ($match[1].defined) {
given ~($match[1]) {
when "" { }
when /^maj/ { }
when /^ion/ { }
when /^mix/ { $sharps -= 1; }
when /^dor/ { $sharps -= 2; }
when /^m/ { $sharps -= 3; }
when /^aeo/ { $sharps -= 3; }
when /^phr/ { $sharps -= 4; }
when /^loc/ { $sharps -= 5; }
when /^lyd/ { $sharps += 1; }
default { die "Unknown mode {$match[1]} requested"; }
}
}

my @sharp_notes = <F C G D A E B>;
my %hash;

given $sharps {
when 1..7 { for ^$sharps -> $i { %hash{@sharp_notes[$i]} = "^"; } }
when -7..-1 { for ^(-$sharps) -> $i { %hash{@sharp_notes[6-$i]} = "_"; } }
}

return %hash;
}

Basically, we do a match to rip the key signature name into its component pieces. The classic Highland piping key of "Amix", for instance, needs to be recognized as "A", no sharp or flat, "mix"olydian. We use the first two bits to lookup the corresponding major key signature, then use the last bit as a modifier. When we're done, we have the number of sharps or (if negative) flats. We then use that count to figure out which notes need to be sharp or flat.

It doesn't sound that bad, but it was pretty tricky to implement. It also (likely) has some holes in it. For example, the ugly key signture of C-flat minor (4 flats and 3 double flats!) will fail. Of course, any sane person would write that as B minor (two sharps). Also, the ABC spec allows you to explicitly specify exceptions to the normal key signature rules. I haven't even tried to implement that yet.

This is definitely one of those cases were any suggested improvements will be very welcome.

No comments:

Post a Comment