|
| 1 | +// in this example the Linnstrument displays an interface to perform pitches |
| 2 | +// by their likelihood under the NotePredictor model instead of by MIDI number |
| 3 | + |
| 4 | +// the grid in the upper left gives control of pitches from |
| 5 | +// the single most likely (cyan) to least likely (pink). |
| 6 | + |
| 7 | +// the white pad samples pitches stochastically from the model. |
| 8 | +// the lone yellow pad resets the model. |
| 9 | + |
| 10 | +// model predictions are conditioned on performed timing and velocity. |
| 11 | + |
| 12 | +( |
| 13 | +~use_linn = true; // use linnstrument |
| 14 | +~gui = false; // use keyboard GUI |
| 15 | +MIDIIn.connectAll; |
| 16 | +b = NetAddr.new("127.0.0.1", 9999); |
| 17 | +Server.default.options.inDevice_("Built-in Microph"); |
| 18 | +Server.default.options.outDevice_("Built-in Output"); |
| 19 | +// Server.default.options.inDevice_("mic-buds"); |
| 20 | +// Server.default.options.outDevice_("mic-buds"); |
| 21 | +~gui.if{ |
| 22 | + k = MIDIKeyboard.new(bounds: Rect(0, 0, 500, 100), octaves:11, startnote:0) |
| 23 | +}; |
| 24 | +~linn_reset = { |
| 25 | + ~linn.allLightsOff; |
| 26 | + ~linn.setNoteOnAction({arg x,y,degree,freq,amp; |
| 27 | + var idx; |
| 28 | + [x,y,amp].postln; |
| 29 | + (y<5).if{ |
| 30 | + idx = case |
| 31 | + {y==0}{x} |
| 32 | + {y==1}{x*2+5} |
| 33 | + {y==2}{x*4+16} |
| 34 | + {y==3}{x*8+40} |
| 35 | + {y==4}{127-x}; |
| 36 | + idx.postln; |
| 37 | + ~midi_handle.(amp*127, idx); |
| 38 | + }{ |
| 39 | + case |
| 40 | + {x==0}{ |
| 41 | + \sample.postln; |
| 42 | + ~midi_handle.(amp*127, nil);} |
| 43 | + {x==2}{ |
| 44 | + \reset.postln; |
| 45 | + ~model_reset.() |
| 46 | + } |
| 47 | + } |
| 48 | + }); |
| 49 | + ~linn.lightOn(0,0,4); //0 |
| 50 | + 4.do{arg x; ~linn.lightOn(x+1,0,3)}; //1-4 |
| 51 | + 5.do{arg x; ~linn.lightOn(x, 1, 10)}; //5-13 |
| 52 | + 5.do{arg x; ~linn.lightOn(x, 2, 2)}; //16-32 |
| 53 | + 5.do{arg x; ~linn.lightOn(x, 3, 9)}; //40-72 |
| 54 | + ~linn.lightOn(0,4,11); //127 |
| 55 | + 4.do{arg x; ~linn.lightOn(x+1,4,1)}; //126-123 |
| 56 | + |
| 57 | + ~linn.lightOn(0, 6, 8); // sample |
| 58 | + ~linn.lightOn(2, 6, 2); // reset |
| 59 | + |
| 60 | + |
| 61 | +}; |
| 62 | +s.waitForBoot{ |
| 63 | + ~use_linn.if{ |
| 64 | + ~linn = IILinnstrument.new(nil); |
| 65 | + ~linn_reset.(); |
| 66 | + } |
| 67 | +}; |
| 68 | +) |
| 69 | + |
| 70 | +OSCdef.trace(false) |
| 71 | +// ~linn_reset.() |
| 72 | + |
| 73 | +( |
| 74 | +SynthDef(\pluck, { |
| 75 | + var vel = \vel.kr; |
| 76 | + var signal = Saw.ar(\freq.kr(20), 0.2) * EnvGate.new(1); |
| 77 | + var fr = 2.pow(Decay.ar(Impulse.ar(0), 3)*6*vel+8); |
| 78 | + signal = BLowPass.ar(signal, fr)*vel; |
| 79 | + Out.ar([0,1], signal); |
| 80 | +}).add |
| 81 | +) |
| 82 | + |
| 83 | +// ~linn.setNoteOnAction({}); ~linn.setNoteOffAction({}); |
| 84 | + |
| 85 | + |
| 86 | +// measure round-trip latency |
| 87 | +( |
| 88 | +OSCdef(\return, { |
| 89 | + arg msg, time, addr, recvPort; |
| 90 | + (Process.elapsedTime - t).postln; |
| 91 | +}, '/prediction', nil); |
| 92 | +t = Process.elapsedTime; |
| 93 | +b.sendMsg("/predictor/predict", |
| 94 | + \pitch, 60+12.rand, \time, 0, \vel, 0, \fix_time, 0, \fix_vel, 0); |
| 95 | +) |
| 96 | + |
| 97 | +// set the delay for more precise timing |
| 98 | +~delay = 0.01; |
| 99 | + |
| 100 | + |
| 101 | +// NetAddr.localAddr // retrieve the current IP and port |
| 102 | +// thisProcess.openPorts; // list all open ports |
| 103 | + |
| 104 | +// model chooses pitches |
| 105 | +( |
| 106 | +~gate = 1; |
| 107 | + |
| 108 | +~model_reset = { |
| 109 | + ~last_pitch = nil; |
| 110 | + ~last_dt = nil; |
| 111 | + ~last_vel = nil; |
| 112 | + t = Process.elapsedTime; |
| 113 | + b.sendMsg("/predictor/reset"); |
| 114 | + y!?{y.free}; |
| 115 | + y = nil; |
| 116 | + b.sendMsg("/predictor/predict", \pitch, 128, \time, 0, \vel, 0); |
| 117 | + |
| 118 | +}; |
| 119 | + |
| 120 | +~model_reset.(); |
| 121 | + |
| 122 | +// footswitch |
| 123 | +MIDIdef.program(\switch, { |
| 124 | + arg num, chan, src; |
| 125 | + num.switch |
| 126 | + {1}{~gate = 0} |
| 127 | + {2}{~gate = 1} |
| 128 | + {3}{ |
| 129 | + ~gate = 0; |
| 130 | + SystemClock.clear; |
| 131 | + b.sendMsg("/predictor/reset"); |
| 132 | + y.release; |
| 133 | + SystemClock.clear; |
| 134 | + }; |
| 135 | + ~gate.postln; |
| 136 | +}); |
| 137 | + |
| 138 | + |
| 139 | +// MIDI from controller |
| 140 | +~midi_handle = { |
| 141 | + arg vel, pitch, chan, src; |
| 142 | + var t2 = Process.elapsedTime; |
| 143 | + var dt = t2-(t?t2); //time since last note |
| 144 | + |
| 145 | + // release the previous note |
| 146 | + y.release(0.1); |
| 147 | + |
| 148 | + // attack the current note with the old pitch |
| 149 | + y = Synth(\pluck, [\freq, ~last_pitch.midicps, \vel, vel/127]); |
| 150 | + |
| 151 | + // get a new prediction in light of last note, |
| 152 | + // fixing dt and vel to performed values so just pitch is predicted |
| 153 | + pitch.notNil.if{ |
| 154 | + b.sendMsg("/predictor/predict", |
| 155 | + \pitch, ~last_pitch, \time, ~last_dt, \vel, ~last_vel, |
| 156 | + \index_pitch, pitch, \fix_time, dt, \fix_vel, vel); |
| 157 | + }{ |
| 158 | + b.sendMsg("/predictor/predict", |
| 159 | + \pitch, ~last_pitch, \time, ~last_dt, \vel, ~last_vel, |
| 160 | + \fix_time, dt, \fix_vel, vel); |
| 161 | + }; |
| 162 | + |
| 163 | + ~last_dt = dt; |
| 164 | + ~last_vel = vel; |
| 165 | + |
| 166 | + // mark time of current note |
| 167 | + t = t2; |
| 168 | +}; |
| 169 | + |
| 170 | +~use_linn.not.if{ |
| 171 | + MIDIdef.noteOn(\input, ~midi_handle); |
| 172 | +}; |
| 173 | + |
| 174 | +// OSC return from python |
| 175 | +OSCdef(\return, { |
| 176 | + arg msg, time, addr, recvPort; |
| 177 | + var pitch = msg[1]; // MIDI number of predicted note |
| 178 | + var dt = msg[2]; // time to predicted note |
| 179 | + var vel = msg[3]; // velocity 0-127 |
| 180 | + |
| 181 | + // store the pitch and immediately set (unless there is no synth, |
| 182 | + // indicating this is the first note) |
| 183 | + ~last_pitch = pitch; |
| 184 | + ~last_dt.isNil.if{~last_dt = dt}; |
| 185 | + ~last_vel.isNil.if{~last_vel = vel}; |
| 186 | + y!?{y.set(\freq, ~last_pitch.midicps)}; |
| 187 | + |
| 188 | + [pitch, dt, vel].postln; |
| 189 | + |
| 190 | +}, "/prediction", nil); |
| 191 | +) |
| 192 | + |
| 193 | +~model_reset.() |
| 194 | +// send a note manually if you don't have a midi controller |
| 195 | +MIDIdef.all[\input].func.(64, 16) //velocity, "pitch" |
| 196 | + |
| 197 | +// load another model |
| 198 | +// b.sendMsg("/predictor/load", "/path/to/checkpoint"); |
0 commit comments