#!raku

use Audio::PortMIDI;

class Mode {
    my %modes = ionian      =>    [0,2,4,5,7,9,11,12],
                dorian      =>    [0,2,3,5,7,9,10,12],
                phrygian    =>    [0,1,3,5,7,8,10,12],
                lydian      =>    [0,2,4,6,7,9,11,12],
                mixolydian  =>    [0,2,4,5,7,9,10,12],
                aeolian     =>    [0,2,3,5,7,9,10,12],
                locrian     =>    [0,1,3,5,6,8,10,12],
                major       =>    [0,2,4,5,7,9,11,12],
                pentatonic  =>    [0,2,4,7,9,11,13,15];

    has Str $.mode is required;
    has Int $.root is required;
    has Int @!notes;
    method notes() {
        if !@!notes.elems {
            @!notes = %modes{$!mode}.map({ $_ + $!root });
        }
        @!notes;
    }
}

sub MAIN(Str :$mode = 'mixolydian', Int :$root = 25, Int :$output = 3, Int :$bpm = 120, Int :$channel = 0) {
    my $pm = Audio::PortMIDI.new;
    my $s = $pm.open-output($output,32);

    my $my-mode = Mode.new(:$root, :$mode);

    my $seconds-per-quarter = 60 / $bpm;
    my @a = ($seconds-per-quarter/8, $seconds-per-quarter/4,);

    loop {
        my $start = now;
        my $n = ($my-mode.notes.pick + (0,0,0,12,24,0).pick);
        my $v = (80 .. 127).pick;
        my $on = Audio::PortMIDI::Event.new(:$channel, event-type => NoteOn, data-one => $n, data-two => $v, timestamp => $++);
        $s.write($on);
        sleep @a.pick;
        my $off = Audio::PortMIDI::Event.new(:$channel, event-type => NoteOff, data-one => $n, data-two => $v, timestamp => $++);
        $s.write($off);
        my $l = ($seconds-per-quarter/4) - (now - $start);
        sleep $l if $l > 0;
    }
}

# vim: expandtab shiftwidth=4 ft=raku
