So, I just dived in and started trying to implement duration as a role.
role ABC::Duration {
has $.ticks;
our multi sub duration-from-parse($top) is export {
ABC::Duration.new(:ticks($top.Int || 1));
}
our multi sub duration-from-parse($top, $bottom) is export {
if +($top // 0) == 0 && +($bottom // 0) == 0 {
ABC::Duration.new(:ticks(1/2));
} else {
ABC::Duration.new(:ticks(($top.Int || 1) / ($bottom.Int || 1)));
}
}
our method Str() {
given $.ticks {
when 1 { "---"; } # for debugging, should be ""
when 1/2 { "/"; }
when Int { .Str; }
when Rat { .perl; }
die "Duration must be Int or Rat, but it's { .WHAT }";
}
}
}
The corresponding action for an explicit note length is
method note_length($/) {
if $<note_length_denominator> {
make duration-from-parse($<top> ?? $<top>[0] !! "", $<note_length_denominator>[0]<bottom>[0]);
} else {
make duration-from-parse($<top> ?? $<top>[0] !! "");
}
}
I messed around a bunch trying to make "e" does Duration work as a type, but eventually gave up and just coded an ABC::Note type:
class ABC::Note does ABC::Duration {
has $.pitch;
has $.is-tie;
method new($pitch, ABC::Duration $duration, $is-tie) {
say :$duration.perl;
self.bless(*, :$pitch, :ticks($duration.ticks), :$is-tie);
}
}
with corresponding action
method mnote($/) {
make ABC::Note.new(~$<pitch>,
$<note_length> ?? $<note_length>[0].ast !! ABC::Duration.new(1),
$<tie> eq '-');
}
So… that seems to work okay so far. But it does raise some issues for me.
1. I’m finding this $<top> ?? $<top>[0] !! "" pattern to be very repetitive. Surely there must be a better way to do it? (errr… wait a minute, could that be just $<top>[0] // ""?)
2. I don’t mind the proliferation of small classes in my code, that corresponds nicely to what we are modeling. But I am starting to mind the corresponding proliferation of small source files. Is there a better way to organize things?
… and that’s all I can remember at the moment. I think I’ll wander off and have breakfast.