Skip to content
This repository was archived by the owner on Nov 23, 2023. It is now read-only.

Commit f7d2828

Browse files
redo sinusoid embeddings, use for velocity; fixes to data augmentation; move train script into package; allow specifying dependency matrix to forward; rename to notochord; bump version
1 parent fd53601 commit f7d2828

File tree

20 files changed

+309
-2183
lines changed

20 files changed

+309
-2183
lines changed

Readme.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Structure
22

33
- iipyper: python package for easy MIDI, OSC, event loops
4-
- notepredictor: python package for defining+training pytorch RNN models
4+
- notochord: python package for defining+training pytorch RNN models
55
- notebooks: jupyter notebooks
66
- scripts: helper scripts for training, data preprocessing etc
77
- examples:
88
- iipyper: basic usage for iipyper
9-
- notepredictor: interactive MIDI apps with notepredictor and SuperCollider
9+
- notochord: interactive MIDI apps with notochord and SuperCollider
1010
- bela: [Bela](https://bela.io) examples in C++, Pure Data and so on
1111
- faust: [Faust](https://faustdoc.grame.fr/) examples
1212
- tidalcycles [TidalCycles](https://tidalcycles.org) examples
@@ -18,23 +18,23 @@
1818
```
1919
conda env create -f environment.yml
2020
conda activate iil-python-tools
21-
pip install -e notepredictor
21+
pip install -e notochord
2222
pip install -e iipyper
2323
```
2424

25-
# notepredictor
25+
# notochord
2626
## Train a model
2727
```
28-
python scripts/lakh_prep.py --data_path /path/to/midi/files --dest_path /path/to/data/storage
29-
python scripts/train_notes.py --data_dir /path/to/data/storage --log_dir /path/for/tensorboard logs --model_dir /path/for/checkpoints train
28+
python notochord/scripts/lakh_prep.py --data_path /path/to/midi/files --dest_path /path/to/data/storage
29+
python notochord/train.py --data_dir /path/to/data/storage --log_dir /path/for/tensorboard logs --model_dir /path/for/checkpoints train
3030
```
3131

3232
## Run OSC app
3333

3434
```
35-
python examples/notepredictor/server.py --checkpoint /path/to/my/model.ckpt
35+
python examples/notochord/server.py --checkpoint /path/to/my/model.ckpt
3636
```
37-
step through `examples/notepredictor/generate.scd` in SuperCollider IDE
37+
step through `examples/notochord/generate.scd` in SuperCollider IDE
3838

3939
# Develop
4040

examples/bela/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Unless otherwise stated, these examples assume a set up of a Bela device connect
88
- `cpp-osc-resonator`: controlling a resonant filter
99
- `cpp-osc-resonators`: controlling a bank of resonant filters
1010
- `cpp-osc-gui-sliders`: Bela GUI with sliders controlled by `iipyper`
11-
- `cpp-osc-notepredictor-trill`: using Trill sensors to control `iipyper` `notepredictor` parameters
11+
- `cpp-osc-notochord-trill`: using Trill sensors to control `iipyper` `notochord` parameters
1212

1313
## Pure Data
1414
- `osc-receive`: receive OSC from `iipyper`
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// in this example the Linnstrument displays an interface to perform pitches
2-
// by their likelihood under the NotePredictor model instead of by MIDI number
2+
// by their likelihood under the Notochord model instead of by MIDI number
33

44
// the grid in the upper left gives control of pitches from
55
// the single most likely (cyan) to least likely (pink).
Lines changed: 94 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ b.sendMsg("/predictor/predict",
130130
// duet with the model
131131
// feeds the model's predictions back to it as well as player input
132132
(
133+
~velocity_temp = 1;
133134
~instrument_temp = 0.95;
134135
~pitch_temp = 0.9;
135136
~rhythm_temp = 1;
@@ -176,7 +177,8 @@ b.sendMsg("/predictor/reset");
176177
~duet.if{kw.add(\exclude_instrument); kw.add(~player_inst)};
177178
~global_kws.(kw, [
178179
\min_time, \max_time, \allow_end,
179-
\instrument_temp, \pitch_temp, \rhythm_temp, \timing_temp
180+
\instrument_temp, \pitch_temp, \rhythm_temp, \timing_temp,
181+
\velocity_temp
180182
]);
181183

182184
// kw.postln;
@@ -208,16 +210,19 @@ MIDIdef.program(\switch, {
208210
// MIDI from controller
209211
MIDIdef.noteOff(\input_off, {
210212
arg vel, pitch, chan, src;
211-
var t2 = Process.elapsedTime;
212-
var dt = t2-(t?(t2-~delay)); //time since last note
213+
var t2, dt;
213214
var inst = ~player_inst;
214215
var port_chan = ~prog2portchan.(inst);
215216

217+
// mark time of current note
218+
t2 = Process.elapsedTime;
219+
dt = t2-(t?(t2-~delay));
220+
t = t2;
221+
~player_t = t;
222+
216223
// cancel any pending predictions
217224
~clear_pending_events.();
218225
// ~pending_predictions.postln;
219-
//get a new prediction in light of current note
220-
~predict_event.(inst, pitch, dt, 0);
221226

222227
~player_sound.if{
223228
// release the previous note
@@ -227,9 +232,8 @@ MIDIdef.noteOff(\input_off, {
227232
port_chan[\port].noteOff(port_chan[\chan], pitch);
228233
};
229234

230-
// mark time of current note
231-
t = t2;
232-
~player_t = t;
235+
//get a new prediction in light of current note
236+
~predict_event.(inst, pitch, dt, 0);
233237

234238
~step = ~step + 1;
235239

@@ -241,17 +245,19 @@ MIDIdef.noteOff(\input_off, {
241245
});
242246
MIDIdef.noteOn(\input_on, {
243247
arg vel, pitch, chan, src;
244-
var t2 = Process.elapsedTime;
245-
var dt = t2-(t?(t2-~delay)); //time since last note
248+
var t2, dt;
246249
var inst = ~player_inst;
247250
var port_chan = ~prog2portchan.(inst);
248251

252+
// mark time of current note
253+
t2 = Process.elapsedTime;
254+
dt = t2-(t?(t2-~delay));
255+
t = t2;
256+
~player_t = t;
249257

250258
// cancel any pending events
251259
~clear_pending_events.();
252260
// ~pending_predictions.postln;
253-
//get a new prediction in light of current note
254-
~predict_event.(inst, pitch, dt, vel);
255261

256262
~player_sound.if{
257263
// release the previous note (if not properly released by noteoff)
@@ -266,12 +272,10 @@ MIDIdef.noteOn(\input_on, {
266272
port_chan[\port].noteOn(port_chan[\chan], pitch, vel);
267273
};
268274

269-
// mark time of current note
270-
t = t2;
271-
~player_t = t;
275+
//get a new prediction in light of current note
276+
~predict_event.(inst, pitch, dt, vel);
272277

273278
~step = ~step + 1;
274-
// ~step = 0;
275279

276280
~player_held[pitch] = true;
277281

@@ -290,127 +294,140 @@ OSCdef(\return, {
290294
var step = msg[6];
291295
var port_chan = ~prog2portchan.(inst);
292296

293-
294297
// time-to-next note gets 'censored' by the model
295298
// when over a threshold, in this case 10 seconds,
296299
// meaning it just predicts 10s rather than any longer time
297300
var censor = dt>=10.0;
298301

299302
// there may be multiple outstanding queries to the model. when there are, it means
300-
// the earlier prediction is already voided, so it should dropped here.
303+
// the earlier prediction is already voided, so it should be dropped here.
301304
var gate_event = (
302305
~gate
303306
// && (~duet && (inst==~player_inst)).not
304307
&& censor.not
305-
&& (~pending_predictions==1));
308+
&& (~pending_predictions==1)
309+
&& (end==0)
310+
);
306311

307312
~pending_predictions = ~pending_predictions-1;
308-
309313
gate_event.if{
310-
// cancel any pending notes
314+
// cancel any pending events
311315
// since they are voided by the new prediction
312316
~clear_pending_events.();
313317
// schedule the predicted note
314318
~pending_events.add(Routine{
315319
var t2, dt_actual;
316-
(dt-~delay).sleep;
317-
(end==1).if{
318-
// in this case don't schedule a note, and reset the model
319-
// b.sendMsg("/predictor/reset");
320-
//release the last note
321-
~synths.do(_.release(1.0));
322-
~release_all.(127);
323-
// unset time so next note will have dt=0
324-
// t = nil;
325-
// \reset.postln
326-
\end.postln;
320+
// wait until predicted time
321+
// TODO: this could be made exact by passing timestamps thru OSC?
322+
(dt-~delay).max(0).sleep;
323+
324+
// get the actual time since last event
325+
t2 = Process.elapsedTime;
326+
dt_actual = t2 - t;
327+
// mark the actual time of current note
328+
t = t2;
329+
~machine_t = t;
330+
331+
// play the current note
332+
~synths[pitch]!?(_.release(0.05));
333+
// send MIDI
334+
port_chan[\port].noteOff(port_chan[\chan], pitch, 64);
335+
(vel > 0).if{
336+
~use_synth.if{
337+
~synths[pitch] = Synth(\pluck, [
338+
\freq, pitch.midicps, \vel, vel/127]);
339+
};
340+
// send MIDI
341+
port_chan[\port].noteOn(port_chan[\chan], pitch, vel)
327342
}{
328-
t2 = Process.elapsedTime;
329-
dt_actual = t2 - t;
343+
~synths[pitch] = nil;
344+
};
330345

331-
// feed model its own prediction as input
332-
~predict_event.(inst, pitch, dt_actual, vel);
346+
// feed model its own prediction as input
347+
~predict_event.(inst, pitch, dt_actual, vel);
348+
349+
// crudely draw note on piano GUI
350+
/*~gui.if{
351+
AppClock.sched(0,{k.keyDown(pitch)});
352+
AppClock.sched(0.2,{k.keyUp(pitch)});
353+
}*/
354+
// post the current note
355+
[\model, step, dt, inst, pitch, vel].postln;
333356

334-
// play the current note
335-
~synths[pitch]!?(_.release(0.05));
336-
// send MIDI
337-
port_chan[\port].noteOff(port_chan[\chan], pitch, 64);
338-
(vel > 0).if{
339-
~use_synth.if{
340-
~synths[pitch] = Synth(\pluck, [
341-
\freq, pitch.midicps, \vel, vel/127]);
342-
};
343-
// send MIDI
344-
port_chan[\port].noteOn(port_chan[\chan], pitch, vel)
345-
}{
346-
~synths[pitch] = nil;
347-
};
348-
// mark the actual time of current note
349-
t = t2;
350-
~machine_t = t;
351-
// crudely draw note on piano GUI
352-
/*~gui.if{
353-
AppClock.sched(0,{k.keyDown(pitch)});
354-
AppClock.sched(0.2,{k.keyUp(pitch)});
355-
}*/
356-
// post the current note
357-
[\model, step, dt, inst, pitch, vel, end].postln;
358-
};
359357
~step = ~step+1;
360358
// [\late, dt_actual-dt].postln;
361359
nil;
362-
}.play(SystemClock))};
360+
}.play(SystemClock));
361+
};
362+
(end==1).if{
363+
// b.sendMsg("/predictor/reset");
364+
//release the last note
365+
~synths.do(_.release(1.0));
366+
~release_all.(127);
367+
// unset time so next note will have dt=0
368+
// t = nil;
369+
// \reset.postln
370+
\end.postln;
371+
};
363372
}, "/prediction", nil);
364373
)
365374

366375
(
376+
t = nil;
367377
~gate = false;
368378
~duet = false;
379+
~allow_end = true;
369380
~instrument_temp = 0.95;
370381
~pitch_temp = 0.9;
371-
~rhythm_temp = 0.9;
372-
~timing_temp = 0.05;
382+
~rhythm_temp = 1;
383+
~timing_temp = 0.1;
384+
~min_time = nil;//~delay;
385+
~velocity_temp = 0;
373386
// send a note manually if you don't have a MIDI controller:
374387
~clear_pending_events.();
375388
~synths.do(_.release(1.0));
376389
~release_all.(0);
377390
b.sendMsg("/predictor/reset");
378-
// ~player_inst = 128.rand+1;
379-
// {MIDIdef.all[\input_on].func.value(128.rand, 128.rand)}.defer(0.1);
380-
~player_inst = 272;
381-
{~gate = true; MIDIdef.all[\input_on].func.value(128.rand, 60.rand+30)}.defer(0.2);
391+
~player_inst = 128.rand+1;
392+
{~gate = true; MIDIdef.all[\input_on].func.value(127.rand+1, 128.rand)}.defer(0.2);
393+
// ~player_inst = 272;
394+
// {~gate = true; MIDIdef.all[\input_on].func.value(128.rand+1, 27.rand+60)}.defer(0.2);
382395
)
383396

384397
(
398+
t = nil;
385399
~seq!?(_.stop);
386400
~gate = false;
387401
~duet = true;
388-
~instrument_temp = 0.9;
402+
~allow_end = false;
403+
~instrument_temp = 1;
389404
~pitch_temp = 1;
390405
~rhythm_temp = 1;
391-
~timing_temp = 0.05;
406+
~timing_temp = 0.1;
407+
~velocity_temp = 1;
392408
~clear_pending_events.();
393409
~release_all.(0);
394410
b.sendMsg("/predictor/reset");
395411
~player_inst = 272;
412+
s = 2;
396413
{~seq = Routine{
397414
loop{
398415
MIDIdef.all[\input_on].func.value(100, 51);
399416
MIDIdef.all[\input_on].func.value(100, 36);
400-
0.12.sleep;
417+
(0.12*s).sleep;
401418
MIDIdef.all[\input_off].func.value(0, 51);
402-
0.005.sleep;
419+
(0.005*s).sleep;
403420
MIDIdef.all[\input_on].func.value(100, 51);
404-
0.125.sleep;
421+
(0.125*s).sleep;
405422
MIDIdef.all[\input_on].func.value(100, 46);
406-
0.22.sleep;
423+
(0.22*s).sleep;
407424
MIDIdef.all[\input_off].func.value(0, 36);
408-
0.01.sleep;
425+
(0.01*s).sleep;
409426
MIDIdef.all[\input_off].func.value(0, 46);
410-
0.02.sleep;
427+
(0.02*s).sleep;
411428
}
412429
}.play(SystemClock);
413-
{ ~gate=true }.defer(3);
430+
{ ~gate=true }.defer(4);
414431
}.defer(0.5);
415432
)
416433

examples/notepredictor/linnstrument-display.scd renamed to examples/notochord/linnstrument-display.scd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// in this example, the most likely next pitches (according to NotePredictor)
1+
// in this example, the most likely next pitches (according to Notochord)
22
// are drawn on the Linnstrument as you play
33

44
(
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
Intelligent Instruments Lab 2022
66
"""
77

8-
from notepredictor import NotePredictor
8+
from notochord import Notochord
99

1010
from iipyper import OSC, run
1111

1212
def main(host="127.0.0.1", port=9999, checkpoint=None):
1313
osc = OSC(host, port)
1414

1515
if checkpoint is not None:
16-
predictor = NotePredictor.from_checkpoint(checkpoint)
16+
predictor = Notochord.from_checkpoint(checkpoint)
1717
predictor.eval()
1818
else:
1919
predictor = None
@@ -31,7 +31,7 @@ def _(address, **kw):
3131
if cmd=="load":
3232
# `nonlocal` is needed to assign to closed-over name
3333
nonlocal predictor
34-
predictor = NotePredictor.from_checkpoint(**kw)
34+
predictor = Notochord.from_checkpoint(**kw)
3535
predictor.eval()
3636

3737
elif cmd=="predict":

0 commit comments

Comments
 (0)