// name: playmidi.ck
// desc: example to render a MIDI file using ChucK

// path
NRev reverb => dac;
// just a bit of reverb
0.025 => reverb.mix;

// the MIDI file in object
MidiFileIn min;
// the MIDI message shuttle
MidiMsg msg;

// the filename
string filename;
// if no command line arguments provided
if( me.args() == 0 ) me.dir() + "bwv772.mid" => filename;
// else use that filename
else me.arg(0) => filename;

// open the file; exit on error
if( !min.open(filename) ) me.exit();

// print out 
cherr <= "----------" <= IO.newline();
cherr <= "MIDI file: " <= IO.newline()
      <= " |- " <= filename <= IO.newline()
      <= " |- contains " <= min.numTracks() <= " tracks" <= IO.newline()
      <= " |- TPQ: " <= min.tpq() <= IO.newline()
      <= " |- BPM: " <= min.bpm() <= IO.newline();
// print
cherr <= "----------" <= IO.newline();
cherr <= "playing..." <= IO.newline();
cherr <= "----------" <= IO.newline();

// flag
int done;

// for each track, spork a separate shred with the track number
for( int t; t < min.numTracks(); t++ )
    spork ~ doTrack( t, 1 );

// keeping track of how many tracks are done
while( done < min.numTracks() )
    1::second => now;

// done
cherr <= "done; cleaning up..." <= IO.newline();

// close the file
min.close();

// entry point for each shred assigned to a track
fun void doTrack( int track, float speed )
{
    // hack polyphony
    Wurley s[4];
    // for each voice
    for( int i; i < s.size(); i++ )
    {
        // set gain
        .5 => s[i].gain;
        // connect to output
        s[i] => reverb;
    }
    // voice number for quick polyphony
    int v;
    
    // read through all MIDI messages on track
    while( min.read( msg, track ) )
    {
        // this means no more MIDI events at current time; advance time
        if( msg.when > 0::second )
            msg.when * speed => now; // speed of 1 is nominal
        
        // catch NOTEON messages (lower nibble == 0x90)
        if( (msg.data1 & 0xF0) == 0x90 && msg.data2 > 0 && msg.data3 > 0 )
        {
            // get the pitch and convert to frequencey; set
            msg.data2 => Std.mtof => s[v].freq;
            // velocity data; note on
            msg.data3/127.0 => s[v].noteOn;
            // cycle the voices
            (v+1)%s.size() => v;

            // log
            cherr <= "NOTE ON track:" <= track <= " pitch:" <= msg.data2 <=" velocity:" <= msg.data3 <= " when: " <= msg.when/ms <= IO.newline(); 
        }
        // other messages
        else
        {
            // log
            // cherr <= "----EVENT (unhandled) track:" <= track <= " type:" <= (msg.data1&0xF0)
            //      <= " data2:" <= msg.data2 <= " data3:" <= msg.data3 <= IO.newline(); 
        }
    }
    
    // done with track
    done++;
}
