//--------------------------------------------------------------------
// name: LiSa-munger3.ck
// desc: Live sampling utilities for ChucK
//
// author: Dan Trueman, 2007
//
// These three example files demonstrate a couple ways to approach
// granular sampling with ChucK and LiSa. All are modeled after the
// munger~ from PeRColate. One of the cool things about doing this
// in ChucK is that there is a lot more ready flexibility in
// designing grain playback patterns; rolling one's own idiosyncratic
// munger is a lot easier.
//
// Example 3 (below) uses the same structure as Example 2, but
// replicates the groovy tune munging from the original munger~
// helppatch (with pitch transposition filtering and all!).
//--------------------------------------------------------------------

// oscillator as source
SinOsc s => dac;
s.freq( 440 );
s.gain( 0.25 );

// play the munger song!
Envelope fsmooth => blackhole;
spork ~ playtune(250::ms);
spork ~ smoothtune(20::ms);

// transposition table
[0, 4, 7, -2, 12, 15] @=> int pitchtable[];

// use three buffers to avoid clicks
LiSa lisa[3];
// allocated buffer size -- remains static
1::second => dur bufferlen;
// portion of the buffer size to use -- can vary
0.1::second => dur reclooplen;
// indices for rec and play buffers
0 => int recbuf;
2 => int playbuf;

// go through each LiSa
for( 0 => int i; i < lisa.size(); i++ )
{
    // set LiSa params
    lisa[i].duration(bufferlen);
    lisa[i].loopEndRec(reclooplen);
    lisa[i].maxVoices(30);
    lisa[i].clear();
    lisa[i].gain(0.2);
    // if you want to retain earlier passes through the recording buff when loop recording:
    // lisa[i].feedback(0.5); 
    // ramp at extremes of record buffer while recording
    lisa[i].recRamp(20::ms);
    lisa[i].record(0);
    // connect s to each LiSa    
    s => lisa[i] => dac;
}

// start recording in the recbuf LiSa
lisa[recbuf].record(1);

// create grains, rotate record and play bufs as needed
// shouldn't click as long as the grainlen < bufferlen
while( true )
{
    // will update record and playbufs to use every reclooplen
    now + reclooplen => time later;

    // toss some grains
    while( now < later )
    {
        Math.random2f(0, 6) $ int => int newpitch; // choose a transposition from the table
        Std.mtof(pitchtable[newpitch] + 60)/Std.mtof(60) => float newrate;
        Math.random2f(50, 100) * 1::ms => dur newdur; // create a random duration for the grain

        // spork off a grain!
        spork ~ getgrain( playbuf, newdur, 20::ms, 20::ms, newrate );

        // wait a bit.... then do it again, until we reach reclooplen
        5::ms => now;
    }

    // rotate the record LiSa
    lisa[recbuf++].record( 0 );
    if( recbuf == lisa.size() ) 0 => recbuf;
    lisa[recbuf].record( 1 );

    // rotate the play LiSa
    playbuf++;
    if( playbuf == lisa.size() ) 0 => playbuf;
}

// for sporking grains; can do lots of different stuff here -- just one example here
fun void getgrain(int which, dur grainlen, dur rampup, dur rampdown, float rate)
{
    // get an available voice
    lisa[which].getVoice() => int newvoice;
    
    // make sure we got a valid voice   
    if( newvoice > -1 )
    {
        // set play rate
        lisa[which].rate(newvoice, rate);
        // set play position
        lisa[which].playPos(newvoice, Math.random2f(0,1) * reclooplen);
        // set ramp up duration
        lisa[which].rampUp(newvoice, rampup);
        // wait for grain length (minus the ramp-up and -down)
        (grainlen - (rampup + rampdown)) => now;
        // set ramp down duration
        lisa[which].rampDown(newvoice, rampdown);
        // for ramp down duration
        rampdown => now;
    }
}

// the munger song lives! thanks to luke dubois....
fun void playtune( dur notelen )
{       
    // notes
    [45, 45, 57, 57, 45, 57, 57, 47, 55, 47, 59, 60, 60, 57, 57, 57] @=> int notes[];
    // note counter
    0 => int notectr;

    // set frequency to first note
    Std.mtof(notes[0]) => fsmooth.value => s.freq;

    // infinite time-loop
    while( true )
    {
        // set frequency target
        Std.mtof(notes[notectr++] + 12) => fsmooth.target;
        // wrap note counter if reach end
        if(notectr == notes.size()) 0 => notectr;
        // wait for note length
        notelen => now;
    }
}

// smooth note to note
fun void smoothtune( dur smoothtime )
{
    // set smooth duration
    fsmooth.duration( smoothtime );
    while( true )
    {
        fsmooth.value() => s.freq;
        1::ms => now;
    }
}
