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

Commit bb0d8e3

Browse files
authored
Merge pull request #2 from Intelligent-Instruments-Lab/master
Merge upstream
2 parents 1601aaf + 7e141b5 commit bb0d8e3

File tree

14 files changed

+1150
-307
lines changed

14 files changed

+1150
-307
lines changed

Readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- scripts: helper scripts for training, data preprocessing etc
77
- examples:
88
- iipyper: basic usage for iipyper
9-
- notepredictor: interactive MIDI apps with notepredictor
9+
- notepredictor: interactive MIDI apps with notepredictor and SuperCollider
1010
<!-- - clients: templates for SuperCollider, Bela (C++), Pure Data, ... -->
1111

1212
# Setup
@@ -30,7 +30,7 @@ python scripts/train_notes.py --data_dir /path/to/data/storage --log_dir /path/f
3030
```
3131
python examples/notepredictor/server.py --checkpoint /path/to/my/model.ckpt
3232
```
33-
step through `examples/notepredictor/midi-duet.scd` in SuperCollider IDE
33+
step through `examples/notepredictor/generate.scd` in SuperCollider IDE
3434

3535
# Develop
3636

examples/iipyper/iipyper-tutorial.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
from iipyper import OSC, MIDI, repeat, run
44

5-
# TODO: MIDI
6-
# TODO: loops are broken, why?
7-
85
def main(osc_host='127.0.0.1', osc_port=9999, loop_time=1, loop_msg='hello'):
96
# loop API:
107
# use the @repeat decorator to define functions which run every n seconds
@@ -63,7 +60,7 @@ def keyword_example(address, arg1=0, arg2=1, arg3=99):
6360
print(address, arg1, arg2, arg3)
6461
# no return value: does not send OSC back
6562

66-
# can also give the route explictly to the decorator,
63+
# can also give the route explicitly to the decorator,
6764
# supporting wildcards
6865
@osc.args('/math/*')
6966
def _(address, a, b):
@@ -74,7 +71,7 @@ def _(address, a, b):
7471
if op=='mul':
7572
return address, a * b
7673

77-
# OSC clients can be created explcitly and given names:
74+
# OSC clients can be created explicitly and given names:
7875
osc.create_client('supercollider', port=57120) # uses same host as server
7976
# but clients will also be created automatically when possible
8077

@@ -91,9 +88,8 @@ def _(address, a, b):
9188
osc('supercollider2', '/other_send_test', 3)
9289

9390
# it may be possible to have async/threaded option for both OSC and MIDI?
94-
# MIDI is threaded and OSC is async
95-
# but MIDI could be enqueued and handled in the loop,
96-
# OSC could launch the threading server?
91+
# MIDI handlers are threaded in mido and OSC handlers as async in pythonosc.
92+
# currently MIDI is enqueued and handled async.
9793

9894
if __name__=='__main__':
9995
run(main)
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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

Comments
 (0)