-
Notifications
You must be signed in to change notification settings - Fork 1
/
10. Controllers part 2.scd
569 lines (468 loc) · 15.7 KB
/
10. Controllers part 2.scd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
//OSC
//Arduino
//WebGUI
//------------------------------------------------------------
// -------------------- OpenSoundControl -----------------
//------------------------------------------------------------
//network-based
//there are many mobile apps to send OSC
// Control (iOS/Android), QuickOSC (Android); TouchOSC (iOS/Android - paid app)
//networking background - see slides 18-21
"http://wiki.dxarts.washington.edu/groups/general/wiki/8722b/attachments/80b10/10.%20Network%20art.pdf".openOS;
//weneed to be on the same network (usually campus wifi is fine, but not recommended for a performance)
//we need to know our IP addres and port SC is listening on
NetAddr.myIP;
NetAddr.langPort;
//use that to put into TouchOSC
//use any template... I choose LiveControl iPad
//you can also build own templates
//now see what we're getting
OSCFunc.trace(true, true);
OSCFunc.trace(false);
s.quit;//no server status messages for a moment
//in TouchOSC, go to mixer tab, change the first slider
(
~sliderResp = OSCdef(\sliderResp, {arg msg;
var val;
// msg.postln;
val = msg[1];
val.postln;
}, '/2/fader1')
);
~sliderResp.free;
//---------------
// gui with OSC control
//--------------
//TouchOSC: Mixer tab in LiveControl iPad layout
//reset buttons: /2/push1-8
//sliders: /2/fader1-8
(
var window, allBuffers, relativeSearchPath, fileNames, synth, allButtons, mappingDictionary, vLayout;
var allOSCdefs;
allOSCdefs = Array.newClear;
relativeSearchPath = "../02/audio/*.wav"; //configuration
s.waitForBoot({
allBuffers = relativeSearchPath.resolveRelative.pathMatch.collect({|thisPath|
thisPath.postln; //so we can see what's happening
CtkBuffer.playbuf(thisPath).load(sync: true);
});
//prepare synth
synth = CtkSynthDef(\stereoPlayerWithPan, {|buffer = 0, amp = 1, pan = 0|
var sig;
sig = PlayBuf.ar(2, buffer);
Out.ar(0, Balance2.ar(sig[0], sig[1], pan.lag(0.1), amp.lag(0.1))); //LAGs here
});
//create a layout which will space our buttons
vLayout = VLayout.new;
//we also need a window
window = Window.new("simple player", 600@400/*syntactical shorcut to create a Point*/);
allButtons = allBuffers.collect({|thisBuffer, inc|
var note, filename, thisButton, thisSlider, thisKnob;
filename = thisBuffer.path.basename;
filename.postln; //just the filename, without full path, use for display
//before adding more to the layout, let's create the button and ezgui objects first
thisButton = Button(window)
.states_([[filename], [filename ++ " playing"]])
.action_({|buttonObject|
buttonObject.value.postln;
if(buttonObject.value.asBoolean, {
note = synth.note.buffer_(thisBuffer).play;
// {Synth.basicNew(note.synthdefname, s,note.node).autogui;}.defer(0.1);
}, {
note.free;
});
});
thisSlider = EZSlider(window, label: "amp", controlSpec: ControlSpec(0.ampdb, 2.ampdb, \db, units: " dB"), initVal: 0, layout: \horz, unitWidth: 30)
.action_({|slider|
if(note.notNil, {
note.amp_(slider.value.dbamp); //note dbamp here, since we're using decibel scale above (ControlSpec)
});
});
thisKnob = EZKnob(window, controlSpec: \pan, label: "pan", layout: \vert)
.action_({|knob|
if(note.notNil, {
note.pan_(knob.value);
});
});
//OSC here!
//buttons
allOSCdefs = allOSCdefs.add(
OSCdef(\buttonResp ++ inc.asSymbol, {arg msg; //remember about naming your OSCdefs!
var val;
val = msg[1];
{
if(val == 0, {
//toggle button value
if(thisButton.value.asBoolean, {//true when value >0
thisButton.valueAction = 0;
}, {
thisButton.valueAction = 1;
});
});
}.defer; //important: things in the responder can't trigger GUI events directly... need to be deferred (rescheduled using AppClock)
}, '/2/push' ++ (inc + 1).asSymbol) //this will create OSC responder for paths '/2/push1', '/2/push2', '/2/push3' etc
);
//sliders for amps
allOSCdefs = allOSCdefs.add(
OSCdef(\sliderResp ++ inc.asSymbol, {arg msg;
var val;
val = msg[1];
{
thisSlider.valueAction_(thisSlider.controlSpec.map(val)); //no linlin, since we're getting 0-1 values already
}.defer; //important
}, '/2/fader' ++ (inc + 1).asSymbol) //this will create responder for faders, see buttons above
);
//continue with layout stuff
vLayout.add( //add to the general layout
HLayout(
VLayout(
thisButton, //button here
HLayout(
thisSlider.labelView,
thisSlider.sliderView,
thisSlider.numberView.maxWidth_(40), //number box seems the only thing that doesn't scale reasonably by default, so we limit its width here
thisSlider.unitView //since we've set the unitWidth > 0 above, in slider declaration
) //use HLayout to set 4 elements of the ezslider
),
VLayout(
thisKnob.labelView,
thisKnob.knobView,
thisKnob.numberView.maxWidth_(40) //as above
//no unitview here since we are not "enabling" it in the declaration
) //as above
);
); //add to layout parenthesis
thisButton; //we need this to add button to our allButtons variable!
});
//add layout to the window
window.layout_(vLayout);
//bring the window to front
window.front;
//free buffers on close
window.onClose_({
"stop all notes - through buttons".postln;
allButtons.do(_.valueAction_(0));
"freeing buffers".postln;
allBuffers.do(_.free);
allOSCdefs.do(_.free); //free responders
});
//see "Char".openHelpFile;
//also "Dictionary".openHelpFile;
window.view.keyUpAction_({|thisView, char, modifiers, unicode, keycode, key|
var thisIndex, thisButton;
[char, modifiers, unicode, keycode, key].postln;
// char.class.postln;
thisIndex = key - 49;
"thisIndex: ".post; thisIndex.postln;
// thisIndex = thisIndex.clip(0, allButtons.size - 1);
// thisIndex = thisIndex % allButtons.size;
if((thisIndex >= 0) && (thisIndex < allButtons.size), {
thisButton = allButtons[thisIndex];
// "thisButton.value.asBoolean: "a.post; thisButton.value.asBoolean.postln;
if(thisButton.value.asBoolean, {
thisButton.valueAction = 0;
}, {
thisButton.valueAction = 1;
});
});
});
});
)
//---------------
// simple ambisonics panner
//--------------
//IN PRINCIPLE SHOULD WORK!!!
//we need polar coordinates for that...
a = Point.new(0, 0);
a.theta.raddeg;
a = Point.new(1, 0);
a.theta.raddeg;
a = Point.new(0, 1);
a.theta.raddeg;
//TouchOSC: XY Pad tab in LiveControl iPad layout
//XY pad 1 address: '/5/xy1', provides 2 values
(
var synth, note, resp;
s.waitForBoot({
synth = CtkSynthDef(\bfNoise, {arg out = 2, amp = 0.5, azimuth = 0;
Out.ar(out, PanB.ar(PinkNoise.ar(amp), azimuth));
});
s.sync; //so the synth is ready before we call it
note = synth.note.play;
resp = OSCdef(\panner, {arg msg;
var vals, point;
// msg.postln;
vals = msg[1..2];
vals = vals - 0.5; //make them from -0.5 to 0.5, i.e. with 0,0 coordinates in the center
point = Point.new(vals[0], vals[1]);
// optionally rotate it as needed
point.rotate(-90.degrad);
"azimuth: ".post; point.theta.raddeg.postln;
note.azimuth_(point.theta);
}, '/5/xy1')
});
CmdPeriod.doOnce({
resp.free;
});
);
//------------------------------------------------------------
// -------------------- Arduino --------------------
//------------------------------------------------------------
//communicates over serial ports
//it will send whatever we tell it to...
//Simple approach - Firmata
//needs SCPyduino quark
Quarks.gui; //update quarks first! the install SCPyduino
//load firmata onto Arduino
"SCPyduino".openHelpFile
//we need to open serial port
SerialPort.listDevices //see what's your port name - the one with USB in the name
//choose appropriate index. You can also cut and paste the path name directly in the port argument
SerialPort.devices[2];
f = SCPyduino.new("/dev/tty.usbmodem1421", 57600); //you need to specify baudrate of 57600! SCPyduino has it set by default to something else...
// e = SCPyduino.new(SerialPort.devices[2], 57600);
f.close; //later
// e = SCPyduino.new(SerialPort.devices[2], 57600); //you can do this, but port order is not guaranteed
//wait ~3s before running subsequent functions...
//firmata looks at arduino as havin 2 8-pin ports (0-7 and 8-13)
//we need to activate port we'd like to use
//important - do it first
f.digitalPorts[0].active_(1); //first 8
f.digitalPorts[1].active_(1); //second 8
//now we can set the pin mode
f.digital[13].mode_(f.digital_output); //works
f.digital[13].write(1); //LED
f.digital[13].write(0);
//PWM output
f.digital[4].mode_(f.digital_pwm); //throws an error since it's not a pwm pin
f.digital[5].mode_(f.digital_pwm); // works
f.digital[5].write(0.5);
f.digital[5].write(0);
//digital input
f.digital[2].mode_(f.digital_input);
f.iterate;//read value from Arduino - this should happen in a loop?
// f.digital[2].value //get it - this doesn't see to work...
//analog input
f.analog[0].active_(1);
f.analog[0].value;
//post value of analog pin 0
(
a = fork{ //just a Routine
loop{
f.iterate;
f.analog[0].value.postln;
// f.digital[2].value.postln;
};
};
)
a.stop;
f.close;
/*
circuit:
5V -> photocell one terminal
photocell another terminal to analog in
the same terminal to a resistor to ground
*/
// ----------
// photocell sound example
// ---------
(
var arduino, synth, note, routine;
arduino = SCPyduino.new("/dev/tty.usbmodem1421", 57600); //you need to specify baudrate of 57600! SCPyduino has it set by default to something else...
s.waitForBoot({
synth = CtkSynthDef(\simpleSine, {|out = 0, amp = 0.5, freq = 573, pan = 0|
Out.ar(out, Pan2.ar(SinOsc.ar(freq.lag(0.1), 0, amp), pan))
});
s.sync;
note = synth.note(2).play;
//analog input
"waiting for firmata".postln;
4.wait; //wait works since we are inside a Routine (waitForBoot)
arduino.analog[0].active_(1);
//post value of analog pin 0
routine = Routine.run({ //just a Routine
loop{
arduino.iterate;
// note.freq_(arduino.analog[0].value + 300);
note.freq_(arduino.analog[0].value.linlin(910, 970, 100, 1200));
}
});
});
CmdPeriod.doOnce({
routine.stop;
note.free;
arduino.close; //important!
});
)
// ----------
// alternative/custom communication mode
// ---------
//arduino code
/*
//vars
int hi = 255;
int lo = 0;
int analogPins[] = {
A0, A1, A2, A3, A4, A5, A0, A1, A2, A3, A4, A5};each sensor read twice, unnecessarily
int analogPinCount;
int digitalPins[] = {
2, 3, 4, 5};
int digitalPinCount;
int lowBattPin = 5;
//use Serial1 for xBee and Serial for USB
#define serialPort Serial
void setup()
{
// Open serial
serialPort.begin(115200);
analogPinCount = sizeof(analogPins) * 0.5; //0.5 since each member of an array is 2-byte long
digitalPinCount = sizeof(digitalPins) * 0.5; //0.5 since each member of an array is 2-byte long
//set inputs
// for(int thisInput = 0; thisInput < digitalPinCount; thisInput++) {
// pinMode(digitalPins[thisInput], INPUT_PULLUP);
// }
// pinMode(lowBattPin, INPUT); //or INPUT_PULLUP?
}
void loop() // run
{
sendBeginning();
sendAnalogIns();
sendDigitalIns();
sendEnding();
// for debugging
// serialPort.println(analogPinCount);
// serialPort.println(digitalPinCount);
// delay(1000);
// delay(1);
// serialPort.println(millis());
}
void sendBeginning()//begin word
{
serialPort.write(hi);
serialPort.write(hi);
serialPort.write(lo);
}
void sendEnding()//end word
{
serialPort.write(hi);
serialPort.write(lo);
}
void sendAnalogIns()
{
for(int thisInput = 0; thisInput < analogPinCount; thisInput++) {
int thisSensorVal;
thisSensorVal = analogRead(analogPins[thisInput]);
serialPort.write(highByte(thisSensorVal));
serialPort.write(lowByte(thisSensorVal));
}
}
void sendDigitalIns()
{
byte buttons = B00000000;//storing button bits
// for(int thisInput = 0; thisInput < digitalPinCount; thisInput++) {
// bitWrite(buttons, thisInput, bitRead(digitalRead(digitalPins[thisInput]), 0));
// };
// //flip all buttons - maybe need to flip the lowBattPin, to be checked
// buttons = ~buttons;
// bitWrite(buttons, 7, digitalRead(lowBattPin));
serialPort.write(buttons);
}
*/
(
var serial, synth, note, routine;
s.waitForBoot({
synth = CtkSynthDef(\simpleSineNoLag, {|out = 0, amp = 0.5, freq = 573, pan = 0|
Out.ar(out, Pan2.ar(SinOsc.ar(freq, 0, amp), pan))
});
s.sync;
note = synth.note.play;
serial = SerialPort("/dev/tty.usbmodem1411", baudrate: 115200); //open port
routine = Routine.run({
var inByte, inArr, inArrClumped, thisCoordArrayVals, cartesianPoints, oldData, isFirstRun, numSensors, sensorRawValues, serialArray, serialArraySize;
"Starting serial".postln;
//data format
//buttons byte - from individual bits lowbatt, 0, 0, 0, rl, ll, rh, lh (sensors stard from LSB)
//hi hi lo sensor0MSB sensor0LSB sensor1MSB sensor1LSB ... sensor11MSB sensor11LSB buttons hi lo
numSensors = 12;
serialArraySize = 6 + (numSensors * 2); // 3 word begin, 12 sensors *2, 1 byte buttons, 2 word end
serialArray = Array.newClear(serialArraySize);
inArr = Array.newClear(numSensors);
inf.do({
inByte = serial.read; //read data here -------<<<<<<<<<<
// inByte.postln;
serialArray = serialArray.shift(-1); //shift existing values left
serialArray[serialArraySize - 1] = inByte; //put new value to the rightmost slot
// serialArray.postln;
if((serialArray[0]==255 && serialArray[1]==255 && serialArray[2]==0 && serialArray[serialArraySize - 2]==255 && serialArray[serialArraySize - 1]==0), { //recognize the word
sensorRawValues = serialArray.shift(-3).clipExtend(numSensors * 2); //get raw 2-byte values in th earray
//get processed data
numSensors.do({arg i;
inArr[i] = (sensorRawValues[2*i] << 8) + sensorRawValues[(2*i) + 1];
});
// inArr.postln;
note.freq_(inArr[0] + 300); //get data from analog pin 0
});
});
});
CmdPeriod.doOnce({
serial.dump;
routine.stop;
note.free;
serial.close; //important!
});
});
)
// ----------
// work in progress - a web interface
// ---------
//this requires an extension - WsGUI; not published yet, but I've put it on the website (files -> wsGUI.zip). To install, decompress and copy the whole folder to:
(Platform.userAppSupportDir ++ "/Extensions").openOS;
//see help file
WsWindow.openHelpFile;
//how to use it
a = WsWindow.new("new title", true, 8000); //specify port for using internal static web server; nil when serving www folder using another web server
//now point a web browser to http://localhost:8000 (or http://yourIP:8000)
"http://localhost:8000".openOS;
NetAddr.myIP; //from NetLib; useful for connecting external clients
//background and title
a.background_(Color.blue);
a.background_(Color.white);
a.title_("different title");
//this has some basic widgets: button, text, slider
//text, labels
c = WsStaticText.new(a, Rect(0.5, 0.5, 0.2, 0.2));
c.string_("My text with newline \nhere but also html <br> newline and and and and and and it also wraps long lines");
c.font_(Font.new(\Courier).size_(16));
c.background_(Color.yellow); //change if you don't want the default transparent; setting transparency currently doesn't work
c.background_(Color.white);
c.stringColor_(Color.red);
c.align_(\justify);
c.remove;
//button - add to window immediately
i = WsButton.new(a, Rect(0, 0, 0.2, 0.2)); //WsButton imitates SC style multistate button
(
i.states_([
["zero", Color.black, Color.rand],
["one", Color.black, Color.rand],
["two", Color.black, Color.rand]
]);
)
i.value_(0);
i.value_(1);
i.item;
i.item_("two"); // not implemented
i.states[i.value][0];
i.font_(Font.new(\Georgia).size_(24));
i.action_({|but| "got state: ".post; but.value.postln; "item: ".post; but.item.postln;}); //passed button object now
i.remove;
//slider
k = WsSlider.new(a, Rect(0.1, 0.1, 0.1, 0.4));
k.value_(0);
k.value_(0.7);
k.value;
k.action_({|sl| postf("slider value: %\n", sl.value)})
k.valueAction_(0.3);
k.remove;
//finishing
a.clear //remove all elements, don't close server (can add new elements)
a.free //close ws server