//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);
}
}