Superparticular samchillian

From Xenharmonic Wiki
Jump to: navigation, search
//ChucK code for Superparticular Samchillian
//Samchillian idea by Leon Gruenbaum
//superparticular-ratio implementation by Jacob Barton

//paste these lines into a new document in miniAudicle, or save text as a .ck to run in command-line

//change these to match your input/output device
0 => int inDeviceNum;
1 => int outDeviceNum;

class MicroRobinMidiIO
{
    MidiIn min;
    MidiOut mouse;
    MidiMsg inmsg, outmsg;
    0 => int ctr;

    //int rr[128][3]; don't think we need this now.
    // index = note number?
    // column 0 = channel sent to
    // column 1 = note number sent
    int chans[14]; // list of channels used
    float holds[16];     // pitches of on notes, zero if off.
    [0,1,2,3,4,5,6,7,8,10,11,12,13,14] @=> chans; //exclude channel 10 (drums) & 16 (send channel)

    //microtuning stuff

    // PitchBend
    // input: pitch (midi note number float) & velocity of desired note
    // action: sends appropriate pitchbend message
    //         (assuming pitchbend range = +/- 2 semitones)
    // output: note number required for correct frequency to be realized
    // sends pitchbend, assuming +/- wholestep pitchbend range
    // returns note number required for correct frequency
    fun int PitchBend(float pitch, int velocity)
    {
        //send pitchbend
        224 + chans[ctr] => outmsg.data1;
        0 => outmsg.data2;
        Math.round((pitch % 1.0) * 32.0 + 64.0) $ int => outmsg.data3;
        mouse.send(outmsg);
        return Math.floor(pitch) $ int;
    }

    // StartRelay
    // input: number of MIDI device, MidiTransform to be used
    // creates a loop ~ should be sporked
    fun void StartRelay(int deviceNum, MidiTransform mt)
    {

        if( !min.open(inDeviceNum)) me.exit();
        if( !mouse.open(outDeviceNum)) me.exit();

        // print out device that was opened
        <<< min.num(), " -> ", min.name() >>>;
        <<< mouse.num(), " -> ", mouse.name() >>>;

        while ( true)
        {
            min => now;

            while( min.recv(inmsg))
            {
                if( inmsg.data1 % 16 == 0) // only receive on channel 1
                {
                    <<< "r ", inmsg.data1 / 16, inmsg.data1 % 16, inmsg.data2, inmsg.data3>>>;

                    if( inmsg.data1 / 16 == 9)
                    {
                        mt.NoteOn(inmsg.data2, inmsg.data3);
                    }
                    if( inmsg.data1 / 16 == 8)
                    {
                        mt.NoteOff(inmsg.data2, inmsg.data3);
                    }
                    if( inmsg.data1 / 16 == 12)
                    {
                        //prog change apply to channels 1-16
                        //works!
                        inmsg.data1 - (inmsg.data1 % 16) => int base;
                        for(0=>int i; i<15; i++)
                        {
                            base + chans[i] => outmsg.data1;
                            inmsg.data2 => outmsg.data2;
                            i++;
                            if( i == 15)
                            {
                                0 => outmsg.data3;
                                mouse.send(outmsg);
                                break;
                            }

                            base + chans[i] => outmsg.data3;
                            mouse.send(outmsg);
                            inmsg.data2 => outmsg.data1;
                            i++;
                            if( i == 15)
                            {
                                0 => outmsg.data2 => outmsg.data3;
                                mouse.send(outmsg);
                                break;
                            }
                            base + chans[i] => outmsg.data2;
                            inmsg.data2 => outmsg.data3;
                            mouse.send(outmsg);
                        }
                    }

                    if( inmsg.data1 / 16 == 11)
                    {
                        //apply any controller data to channels 1-16
                        inmsg.data1 - (inmsg.data1 % 16) => int base;
                        inmsg.data2 => outmsg.data2;
                        inmsg.data3 => outmsg.data3;
                        for(0=>int i; i<16; i++)
                        {
                            base + i => outmsg.data1;
                            mouse.send(outmsg);
                        }
                    }
                }
            }
        }
    }
    // NoteOn
    // input: pitch in midi note-number extended, velocity
    // action: sends a MIDI pitchbend + note-on message to mouse on the current channel
    //     keeping track of holds
    fun void NoteOn(float nn, int velocity)
    {
        IncrementCtr();
        nn => holds[chans[ctr%14]];
        PitchBend(nn, velocity) => outmsg.data2;
        // note on, right channel
        144 + chans[ctr%14] => outmsg.data1;

        velocity => outmsg.data3;
        mouse.send(outmsg);
        //<<< "s ", outmsg.data1 / 16, outmsg.data1 % 16, outmsg.data2, outmsg.data3>>>;
    }

    // increments mod-14 counter, skipping over channels with
    // notes already on them, if possible.
    fun void IncrementCtr()
    {
        ctr;
        0 => int i;
        for( i; i<14; i++)
        {
            if ( holds[chans[(ctr+i) % 14]] == 0.0)
            {    (ctr + i) % 14 => ctr; return; }
        }
        (ctr + 1) % 14 => ctr;
    }

    // NoteOff
    // input: pitch & note-off velocity
    // action: finds the pitch & offs it.
    fun void NoteOff(float nn, int velocity)
    {
        0 => int c;
        for(c; c<16; c++)
        {
            if(holds[c] == nn) // we found the pitch!
            {
                128 + c => outmsg.data1;
                Math.floor(nn) $ int => outmsg.data2;
                velocity => outmsg.data3;
                mouse.send(outmsg);
                //<<< "s ", outmsg.data1 / 16, outmsg.data1 % 16, outmsg.data2, outmsg.data3>>>;
                0.0 => holds[c];
                return;
            }
        }
        <<<"MISS", nn>>>;
        // we couldn't find the pitch!
        // don't do anything.
    }

    fun void ControlChange(int channel, int prognum, int val)
    {
        128 + channel => outmsg.data1;
        prognum => outmsg.data2;
        val => outmsg.data3;
    }

}

class MidiTransform
{
    // superclass for MIDI transformers to be used by MicroRobinMidiIO
    MicroRobinMidiIO myIO;
    fun void LinkToIO(MicroRobinMidiIO io)
    {
        io @=> myIO;
    }
    fun void NoteOn( int nn, int vel)
    {
        return;
    }
    fun void NoteOff( int nn, int vel)
    {
        return;
    }
}

class SuperparticularSamchillian extends MidiTransform
{
    57. => float resentFreq;
    57. => float prevFreq;

    62 => int keyboardCenterNN;

    float prevFreqsByKey[128];

    //set pans funky
    //for(0=>int f; f<16; f++)
    //{
    //    myIO.ControlChange(f, 9, f*8);
    //}

    fun void NoteOn( int nn, int vel)
    {
        if(nn == keyboardCenterNN)
        {
            myIO.NoteOn( Std.ftom(prevFreq), vel);
        }
        else
        {
            if(nn < keyboardCenterNN)
            {
                // superparticular!
                1. + 1./(keyboardCenterNN - nn) /=> prevFreq;
                myIO.NoteOn( Std.ftom(prevFreq), vel);
            }
            else
            {
                if(nn > keyboardCenterNN)
                {
                    1. + 1./(nn - keyboardCenterNN) *=> prevFreq;
                    myIO.NoteOn( Std.ftom(prevFreq), vel);
                }
            }
        }
        Std.ftom(prevFreq) => prevFreqsByKey[nn];
    }


    fun void NoteOff( int nn, int vel)
    {
        myIO.NoteOff( prevFreqsByKey[nn], vel);
    }

}

MicroRobinMidiIO mrmio;
SuperparticularSamchillian easy;
easy.LinkToIO(mrmio);
spork ~ mrmio.StartRelay( 1, easy);

1::second => now;

KBHit kb;

while(true)
{
    kb => now;

    while( kb.more() )
    {
        kb.getchar() => int c;
        <<< "ascii:", c>>>;
        easy.NoteOn(c, 88);
    }
}