Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / Common / Collections / Event.sc
blob9567a4b2e79563cadb6c632f34c9593c41c7b187
1 Event : Environment {
2         classvar defaultParentEvent;
3         classvar <parentEvents;
4         classvar <partialEvents;
6         *new { arg n=8, proto, parent, know=true;
7                 ^super.new(n, proto, parent, know)
8         }
10         *default {
11                 ^Event.new(8, nil, defaultParentEvent, true);
12         }
13         *silent { |dur(1.0), inEvent|
14                 if(inEvent.isNil) { inEvent = Event.new }
15                         { inEvent = inEvent.copy };
16                 inEvent.put(\isRest, true).put(\dur, dur).put(\parent, defaultParentEvent)
17                         .put(\delta, dur * (inEvent[\stretch] ? 1));
18                 ^inEvent
19         }
20         *addEventType { arg type, func;
21                 var types = partialEvents.playerEvent.eventTypes;
22                 types.put(type, func)
23         }
25         next { arg inval; ^composeEvents(inval, this) }
27         delta {
28                 _Event_Delta
29                 ^this.primitiveFailed;
30                 /*
31                 // implemented by primitive for speed
32                 var delta;
33                 delta = this.at('delta');
34                 if (delta.notNil, { ^delta },{ ^this.at('dur') * this.at('stretch') });
35                 */
36         }
37         play {
38                 if (parent.isNil) {
39                         parent = defaultParentEvent;
40                 };
41                 this.use {
42                         this[\play].value;
43                 };
44 //              ^this.delta
45         }
47         // this[\isRest] may be nil
48         isRest {
49                 ^this[\isRest] == true
50                 or: { this[\type] == \rest
51                         or: {
52                                 this.use {
53                                         parent ?? { parent = defaultParentEvent };
54                                         ~detunedFreq.value.isRest
55                                 }
56                         }
57                 }
58         }
60         // node watcher interface
62         isPlaying_ { arg val;
63                 this.put(\isPlaying, val);
64         }
66         isRunning_ { arg val;
67                 this.put(\isRunning, val);
68         }
70         // this method is called by EventStreamPlayer so it can schedule Routines as well
71         playAndDelta { | cleanup, mute |
72                 if (mute) { this.put(\type, \rest) };
73                 cleanup.update(this);
74                 this.play;
75                 ^this.delta;
76         }
78         // A Quant specifies the quant and phase at which a TempoClock starts an EventStreamPlayer
79         // Its offset specifies how far ahead of time events are actually computed by the EventStream.
80         // offset allows ~strum to be negative, so strummed chords complete on the beat
81         // it also makes it possible for one pattern to run a little ahead of another to set values
82         // This method keeps ~timingOffset and Quant.offset the same.
84         synchWithQuant { | quant |
85                 if(quant.timingOffset.notNil) {
86                         ^this.copy.put(\timingOffset, quant.timingOffset)
87                 } {
88                         quant.timingOffset = this[\timingOffset];
89                         ^this
90                 };
91         }
93         // This enables events to represent the server resources they created in an Event
94         // So, ~bufnum = (type: \sine1, amps: 1/(1..10)) is possible
95         asControlInput {
96                 ^this[ EventTypesWithCleanup.ugenInputTypes[this[\type] ] ] ;
97         }
98         asUGenInput { ^this.asControlInput }
100         printOn { arg stream, itemsPerLine = 5;
101                 var max, itemsPerLinem1, i=0;
102                 itemsPerLinem1 = itemsPerLine - 1;
103                 max = this.size;
104                 stream << "( ";
105                 this.keysValuesDo({ arg key, val;
106                         stream <<< key << ": " << val;
107                         if ((i=i+1) < max, { stream.comma.space;
108                                 if (i % itemsPerLine == itemsPerLinem1, { stream.nl.space.space });
109                         });
110                 });
111                 stream << " )";
112         }
114         storeOn { arg stream, itemsPerLine = 5;
115                 var max, itemsPerLinem1, i=0;
116                 itemsPerLinem1 = itemsPerLine - 1;
117                 max = this.size;
118                 stream << "( ";
119                 this.keysValuesDo({ arg key, val;
120                         stream <<< key << ": " <<< val;
121                         if ((i=i+1) < max, { stream.comma.space;
122                                 if (i % itemsPerLine == itemsPerLinem1, { stream.nl.space.space });
123                         });
124                 });
125                 stream << " )";
126         }
128         *initClass {
129                 Class.initClassTree(Server);
130                 Class.initClassTree(TempoClock);
131                 this.makeParentEvents;
133                 StartUp.add {
134                         Event.makeDefaultSynthDef;
135                 };
136         }
138         *makeDefaultSynthDef {
139                 SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1;
140                                 var z;
141                                 z = LPF.ar(
142                                                 Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3, 0.3)),
143                                                 XLine.kr(Rand(4000,5000), Rand(2500,3200), 1)
144                                         ) * Linen.kr(gate, 0.01, 0.7, 0.3, 2);
145                                 OffsetOut.ar(out, Pan2.ar(z, pan, amp));
146                         }, [\ir]).add;
147         }
149         *makeParentEvents {
150                 // define useful event subsets.
151                 partialEvents = (
152                         pitchEvent: (
153                                 mtranspose: 0,
154                                 gtranspose: 0.0,
155                                 ctranspose: 0.0,
157                                 octave: 5.0,
158                                 root: 0.0,                                      // root of the scale
159                                 degree: 0,
160                                 scale: #[0, 2, 4, 5, 7, 9, 11],         // diatonic major scale
161                                 stepsPerOctave: 12.0,
162                                 detune: 0.0,                                    // detune in Hertz
163                                 harmonic: 1.0,                          // harmonic ratio
164                                 octaveRatio: 2.0,
166                                 note: #{
167                                         (~degree + ~mtranspose).degreeToKey(
168                                                 ~scale,
169                                                 ~scale.respondsTo(\stepsPerOctave).if(
170                                                         { ~scale.stepsPerOctave },
171                                                         ~stepsPerOctave
172                                                 )
173                                         );
174                                 },
175                                 midinote: #{
176                                         (((~note.value + ~gtranspose + ~root) /
177                                                 ~scale.respondsTo(\stepsPerOctave).if(
178                                                         { ~scale.stepsPerOctave },
179                                                         ~stepsPerOctave) + ~octave - 5.0) *
180                                                 (12.0 * ~scale.respondsTo(\octaveRatio).if
181                                                 ({ ~scale.octaveRatio }, ~octaveRatio).log2) + 60.0);
182                                 },
183                                 detunedFreq: #{
184                                         ~freq.value + ~detune
185                                 },
186                                 freq: #{
187                                         (~midinote.value + ~ctranspose).midicps * ~harmonic;
188                                 },
189                                 freqToNote: #{ arg self, freq; // conversion from frequency to note value
190                                         self.use {
191                                                 var midinote;
192                                                 var steps = ~scale.respondsTo(\stepsPerOctave).if(
193                                                         { ~scale.stepsPerOctave }, ~stepsPerOctave
194                                                 );
195                                                 midinote = cpsmidi((freq / ~harmonic) - ~ctranspose);
196                                                 midinote / 12.0 - ~octave * steps - ~root - ~gtranspose
197                                         }
198                                 },
199                                 freqToScale: #{ arg self, freq;
200                                         // conversion from frequency to scale value.
201                                         self.use {
202                                                 var steps = ~scale.respondsTo(\stepsPerOctave).if(
203                                                         { ~scale.stepsPerOctave }, ~stepsPerOctave
204                                                 );
205                                                 var degree = self.freqToNote(freq).keyToDegree(~scale, steps)
206                                                                                 - ~mtranspose;
207                                                 degree.asArray.collect {|x, i|
208                                                         x = x.round(0.01);
209                                                         if(x.floor != x) {
210                                                                 "could not translate: %\n".postf(freq[i]);
211                                                                 nil
212                                                         } { x }
213                                                 }.unbubble;
214                                         }
215                                 }
216                         ),
218                         durEvent: (
219                                 tempo: nil,
220                                 dur: 1.0,
221                                 stretch: 1.0,
222                                 legato: 0.8,
223                                 sustain: #{ ~dur * ~legato * ~stretch },
224                                 lag: 0.0,
225                                 strum: 0.0,
226                                 strumEndsTogether: false
227                         ),
229                         ampEvent: (
230                                 amp: #{ ~db.dbamp },
231                                 db: -20.0,
232                                 velocity: 64,           // MIDI units 0-127
233                                 pan: 0.0,                       // pan center
234                                 trig: 0.5
235                         ),
237                         serverEvent: (
238                                 server: nil,
240                                 synthLib: nil,
242                                 group: 1,
243                                 out: 0,
244                                 addAction: 0,
246                                 instrument: \default,
247                                 variant: nil,
249                                         // this function should return a msgFunc: a Function that
250                                         // assembles a synth control list from event values
252                                 getMsgFunc: { |instrument|
253                                         var     synthLib, synthDesc, desc;
254                                                 // if user specifies a msgFunc, prefer user's choice
255                                         if(~msgFunc.isNil) {
256                                                 instrument = ~instrument = instrument.asSymbol;
257                                                 synthLib = ~synthLib ?? { SynthDescLib.global };
258                                                 synthDesc = desc = synthLib.at(instrument);
259                                                 if (desc.notNil) {
260                                                         ~hasGate = desc.hasGate;
261                                                         ~msgFunc = desc.msgFunc;
262                                                 }{
263                                                         ~msgFunc = ~defaultMsgFunc;
264                                                 };
265                                         } { ~msgFunc };
266                                 },
267                                 synthDefName: { |instrument, variant, synthDesc|
268                                                 // allow `nil to cancel a variant in a pattern
269                                         variant = variant.dereference;
270                                         if(variant.notNil and: { synthDesc.notNil and: { synthDesc.hasVariants } })
271                                                 { (instrument ++ "." ++ variant).asSymbol }
272                                                 { instrument.asSymbol };
273                                 },
275                                 getBundleArgs: { |instrument|
276                                         ~getMsgFunc.valueEnvir(instrument).valueEnvir;
277                                 }.flop,
279                                 hasGate: true,          // assume SynthDef has gate
280                                 sendGate: nil,          // false => event does not specify note release
282                                 defaultMsgFunc: #{|freq = 440, amp = 0.1, pan = 0, out = 0|
283                                         [\freq, freq, \amp, amp, \pan, pan, \out, out] },
285                                 // for \type \set
286                                 args: #[\freq, \amp, \pan, \trig],
288                                 timingOffset: 0,
290                                 // it is more efficient to directly use schedBundleArrayOnClock
291                                 // we keep these for compatibility.
293                                 schedBundle: #{ |lag, offset, server ... bundle |
294                                         schedBundleArrayOnClock(offset, thisThread.clock, bundle, lag, server);
295                                 },
297                                 schedBundleArray: #{ | lag, offset, server, bundleArray, latency |
298                                         schedBundleArrayOnClock(offset, thisThread.clock, bundleArray, lag, server, latency);
299                                 },
301                                 schedStrummedNote: {| lag, strumTime, sustain, server, msg, sendGate |
302                                                 var dur, schedBundle = ~schedBundle;
303                                                 schedBundle.value(lag, strumTime + ~timingOffset, server, msg);
304                                                 if(sendGate) {
305                                                         if (~strumEndsTogether) {
306                                                                 dur = sustain ;
307                                                         } {
308                                                                 dur = sustain + strumTime
309                                                         };
310                                                         schedBundle.value(lag, dur + ~timingOffset, server,
311                                                                         [15 /* \n_set */, msg[2], \gate, 0])
312                                                 }
313                                 }
315                         ),
317                         bufferEvent: (
318                                 bufnum: 0,
319                                 filename: "",
320                                 frame: 0,
321                                 numframes: 0,
322                                 numchannels: 1,
323                                 gencmd: \sine1,
324                                 genflags: 7,
325                                 genarray: [1],
326                                 bufpos: 0,
327                                 leaveOpen: 0
328                         ),
330                         midiEvent: (
331                                 midiEventFunctions: (
332                                         noteOn:  #{ arg chan=0, midinote=60, amp=0.1;
333                                                         [chan, midinote, asInteger((amp * 127).clip(0, 127)) ] },
334                                         noteOff: #{ arg chan=0, midinote=60, amp=0.1;
335                                                         [ chan, midinote, asInteger((amp * 127).clip(0, 127)) ] },
336                                         polyTouch: #{ arg chan=0, midinote=60, polyTouch=125;
337                                                                                         [ chan, midinote, polyTouch ] },
338                                         control: #{ arg chan=0, ctlNum, control=125;
339                                                                                         [chan, ctlNum, control ] },
340                                         program:  #{ arg chan=0, progNum=1; [ chan, progNum ] },
341                                         touch:  #{ arg chan=0, val=125; [ chan, val ] },
342                                         bend:  #{ arg chan=0, val=125; [ chan, val ] },
343                                         allNotesOff: #{ arg chan=0; [chan] },
344                                         smpte:  #{ arg frames=0, seconds=0, minutes=0, hours=0, frameRate=25;
345                                                                                         [frames, seconds, minutes, hours, frameRate] },
346                                         songPtr: #{ arg songPtr; [songPtr] },
347                                         sysex: #{ arg uid, array; [array] } // Int8Array
348                                 ),
349                                 midicmd: \noteOn
350                         ),
352                         nodeEvent: (
353                                 delta: 0,
355                                 addAction: 0,
356                                 group: 1,
357                                 latency: 0.2,
358                                 instrument: \default,
359                                 hasGate: true,
361                                 stopServerNode: #{
362                                         if (~hasGate == true)
363                                                 {currentEnvironment.set(\gate, 0) }
364                                                 {currentEnvironment.sendOSC([11, ~id]) };
365                                         ~isPlaying = false;
366                                 },
368                                 freeServerNode:         #{
369                                                 currentEnvironment.sendOSC([11, ~id]);
370                                                 ~isPlaying = false;
371                                 },
372                                 releaseServerNode:      #{
373                                                 currentEnvironment.set(\gate, 0);
374                                                 ~isPlaying = false;
375                                 },
376                                 pauseServerNode:        #{ currentEnvironment.sendOSC([12, ~id, false]); },
377                                 resumeServerNode:       #{ currentEnvironment.sendOSC([12, ~id, true]);  },
379                                 // perhaps these should be changed into mapServerNode etc.
381                                 map:    #{ | ev, key, busIndex | ev.sendOSC([14, ev[\id], key, busIndex]) },
384                                 before:         #{ | ev,target |
385                                         ev.sendOSC([18, ev[\id], target]);
386                                         ev[\group] = target; ev[\addAction] = 2;
387                                 },
388                                 after:  #{  | ev, target |
389                                         ev.sendOSC([19, ev[\id], target]);
390                                         ev[\group] = target; ev[\addAction] = 3;
391                                 },
392                                 headOf:         #{ | ev, group |
393                                         ev.sendOSC([22, group, ev[\id]]);
394                                         ev[\group] = group; ev[\addAction] = 0;
395                                 },
396                                 tailOf:         #{ |ev,  group |
397                                         ev.sendOSC([23, group, ev[\id]]);
398                                         ev[\group] = group; ev[\addAction] = 1;
399                                 },
400                                 isPlaying: #{ |ev| ^(ev[\isPlaying] == true) },
401                                 isPlaying_: #{ | ev, flag | ev[\isPlaying] = flag; },
402                                 nodeID: #{ |ev| ^ev[\id].asArray.last },
405                                 asEventStreamPlayer: #{|ev| ev }
406                         ),
408                         playerEvent: (
410                                 type: \note,
412                                 play: #{
413                                         var tempo, server;
415                                         ~finish.value;
417                                         server = ~server ?? { Server.default };
419                                         tempo = ~tempo;
420                                         if (tempo.notNil) {
421                                                 thisThread.clock.tempo = tempo;
422                                         };
423                                         // ~isRest may be nil - force Boolean behavior
424                                         if(~isRest != true) { ~eventTypes[~type].value(server) };
425                                 },
427                                 // synth / node interface
428                                 // this may be better moved into the cleanup events, but for now
429                                 // it avoids confusion.
430                                 // this is a preliminary implementation and does not recalculate dependent
431                                 // values like degree, octave etc.
433                                 freeServerNode: #{
434                                         if(~id.notNil) {
435                                                 ~server.sendMsg("/n_free", *~id);
436                                                 ~isPlaying = false;
437                                         };
438                                 },
440                                 // for some yet unknown reason, this function is uncommented,
441                                 // it breaks the play method for gatelesss synths
443                                 releaseServerNode: #{ |releaseTime|
444                                         var sendGate, msg;
445                                         if(~id.notNil) {
447                                         releaseTime = if(releaseTime.isNil) { 0.0 } { -1.0 - releaseTime };
448                                         sendGate = ~sendGate ? ~hasGate;
450                                         if(sendGate) {
451                                                 ~server.sendBundle(~server.latency,
452                                                                 *["/n_set", ~id, "gate", releaseTime].flop);
453                                         } {
454                                                 ~server.sendBundle(~server.latency, ["/n_free"] ++ ~id);
455                                         };
456                                         ~isPlaying = false;
457                                         };
458                                 },
461                                 // the event types
463                                 eventTypes: (
465                                         rest: #{},
467                                         note: #{|server|
468                                                 var freqs, lag, strum, sustain;
469                                                 var bndl, addAction, sendGate, ids;
470                                                 var msgFunc, instrumentName, offset, strumOffset;
472                                                 // var schedBundleArray;
474                                                 freqs = ~detunedFreq.value;
475                                                 if (freqs.isRest.not) {
477                                                         // msgFunc gets the synth's control values from the Event
478                                                         msgFunc = ~getMsgFunc.valueEnvir;
479                                                         instrumentName = ~synthDefName.valueEnvir;
481                                                         // determine how to send those commands
482                                                         // sendGate == false turns off releases
484                                                         sendGate = ~sendGate ? ~hasGate;
486                                                         // update values in the Event that may be determined by functions
488                                                         ~freq = freqs;
489                                                         ~amp = ~amp.value;
490                                                         ~sustain = sustain = ~sustain.value;
491                                                         lag = ~lag;
492                                                         offset = ~timingOffset;
493                                                         strum = ~strum;
494                                                         ~server = server;
495                                                         ~isPlaying = true;
496                                                         addAction = Node.actionNumberFor(~addAction);
498                                                         // compute the control values and generate OSC commands
499                                                         bndl = msgFunc.valueEnvir;
500                                                         bndl = [9 /* \s_new */, instrumentName, ids, addAction, ~group] ++ bndl;
503                                                         if(strum == 0 and: { (sendGate and: { sustain.isArray })
504                                                                 or: { offset.isArray } or: { lag.isArray } }) {
505                                                                         bndl = flopTogether(
506                                                                                                 bndl,
507                                                                                                 [sustain, lag, offset]
508                                                                         );
509                                                                         #sustain, lag, offset = bndl[1].flop;
510                                                                         bndl = bndl[0];
511                                                         } {
512                                                                         bndl = bndl.flop
513                                                         };
515                                                         // produce a node id for each synth
517                                                         ~id = ids = Array.fill(bndl.size, { server.nextNodeID });
518                                                         bndl = bndl.collect { | msg, i |
519                                                                         msg[2] = ids[i];
520                                                                         msg.asOSCArgArray
521                                                         };
523                                                         // schedule when the bundles are sent
525                                                         if (strum == 0) {
526                                                                 ~schedBundleArray.(lag, offset, server, bndl, ~latency);
527                                                                 if (sendGate) {
528                                                                         ~schedBundleArray.(
529                                                                                 lag,
530                                                                                 sustain + offset,
531                                                                                 server,
532                                                                                 [15 /* \n_set */, ids, \gate, 0].flop,
533                                                                                 ~latency
534                                                                         );
535                                                                 }
536                                                         } {
538                                                                 if (strum < 0) { bndl = bndl.reverse };
539                                                                 strumOffset = offset + Array.series(bndl.size, 0, strum.abs);
540                                                                 ~schedBundleArray.(
541                                                                         lag, strumOffset, server, bndl, ~latency
542                                                                 );
543                                                                 if (sendGate) {
544                                                                         if (~strumEndsTogether) {
545                                                                                         strumOffset = sustain + offset
546                                                                         } {
547                                                                                         strumOffset = sustain + strumOffset
548                                                                         };
549                                                                         ~schedBundleArray.(
550                                                                                 lag, strumOffset, server,
551                                                                                 [15 /* \n_set */, ids, \gate, 0].flop,
552                                                                                 ~latency
553                                                                         );
554                                                                 }
555                                                         }
556                                                 }
557                                         },
559                                         // optimized version of type \note, about double as efficient.
560                                         // Synth must have no gate and free itself after sustain.
561                                         // Event supports no strum, no conversion of argument objects to controls
564                                         grain: #{|server|
566                                                 var freqs, lag, instr;
567                                                 var bndl, addAction, sendGate, ids;
568                                                 var msgFunc, instrumentName, offset;
570                                                 freqs = ~detunedFreq.value;
572                                                 if (freqs.isRest.not) {
574                                                         // msgFunc gets the synth's control values from the Event
575                                                         instr = ( ~synthLib ?? { SynthDescLib.global } ).at(~instrument);
576                                                         if(instr.isNil) {
577                                                                 "Event: instrument % not found in SynthDescLib"
578                                                                                 .format(~instrument).warn;
579                                                                 ^this
580                                                         };
581                                                         msgFunc = instr.msgFunc;
582                                                         instrumentName = ~synthDefName.valueEnvir;
584                                                         // update values in the Event that may be determined by functions
586                                                         ~freq = freqs;
587                                                         ~amp = ~amp.value;
588                                                         ~sustain = ~sustain.value;
590                                                         addAction = Node.actionNumberFor(~addAction);
592                                                         // compute the control values and generate OSC commands
594                                                         bndl = msgFunc.valueEnvir;
595                                                         bndl = [9 /* \s_new */, instrumentName, -1, addAction, ~group] ++ bndl;
597                                                         ~schedBundleArray.(
598                                                                 ~lag,
599                                                                 ~timingOffset,
600                                                                 server,
601                                                                 bndl.flop,
602                                                                 ~latency
603                                                         );
604                                                 }
605                                         },
607                                         on: #{|server|
608                                                 var freqs;
609                                                 var bndl, sendGate, ids;
610                                                 var msgFunc, desc, synthLib, bundle, instrumentName;
612                                                 freqs = ~detunedFreq.value;
614                                                 if (freqs.isRest.not) {
615                                                         ~freq = freqs;
616                                                         ~amp = ~amp.value;
617                                                         msgFunc = ~getMsgFunc.valueEnvir;
618                                                         instrumentName = ~synthDefName.valueEnvir;
619                                                         bndl = msgFunc.valueEnvir;
620                                                         bndl = [9 /* \s_new */, instrumentName, ~id,
621                                                                          Node.actionNumberFor(~addAction), ~group] ++ bndl;
622                                                         bndl = bndl.flop;
623                                                         if ( (ids = ~id).isNil ) {
624                                                                 ids = Array.fill(bndl.size, {server.nextNodeID });
625                                                                 bndl = bndl.collect { | msg, i |
626                                                                         msg[2] = ids[i];
627                                                                         msg.asOSCArgArray
628                                                                 };
629                                                         } {
630                                                                 bndl = bndl.asOSCArgBundle;
632                                                         };
633                                                         ~schedBundleArray.value(~lag, ~timingOffset, server, bndl);
634                                                 };
636                                                 ~server = server;
637                                                 ~id = ids;
638                                                 ~callback.value(currentEnvironment)
639                                         },
641                                         set: #{|server|
642                                                 var freqs, lag, dur, strum, bndl, msgFunc;
643                                                 freqs = ~freq = ~detunedFreq.value;
645                                                 if (freqs.isRest.not) {
646                                                         ~server = server;
647                                                         freqs = ~freq;
648                                                         ~amp = ~amp.value;
650                                                         if(~args.size == 0) {
651                                                                 msgFunc = ~getMsgFunc.valueEnvir;
652                                                                 bndl = msgFunc.valueEnvir;
653                                                         } {
654                                                                 bndl = ~args.envirPairs;
655                                                         };
657                                                         bndl = ([15 /* \n_set */, ~id] ++  bndl).flop.asOSCArgBundle;
658                                                         ~schedBundleArray.value(~lag, ~timingOffset, server, bndl);
659                                                 };
660                                         },
662                                         off: #{|server|
663                                                 var gate;
664                                                 if (~hasGate) {
665                                                         gate = min(0.0, ~gate ? 0.0); // accept release times
666                                                         ~schedBundleArray.value(~lag, ~timingOffset, server,
667                                                                 [15 /* \n_set */, ~id.asControlInput, \gate, gate].flop)
668                                                 } {
669                                                         ~schedBundleArray.value(~lag, ~timingOffset, server,
670                                                                 [\n_free, ~id.asControlInput].flop)
671                                                 }
672                                         },
674                                         kill: #{|server|
675                                                 ~schedBundleArray.value(~lag, ~timingOffset, server,
676                                                                 [\n_free, ~id.asControlInput].flop)
677                                         },
679                                         group: #{|server|
680                                                 var bundle, cmd;
681                                                 if (~id.isNil) { ~id = server.nextNodeID };
682                                                 bundle = [21 /* \g_new */, ~id.asArray, Node.actionNumberFor(~addAction),
683                                                                  ~group.asControlInput].flop;
684                                                 ~schedBundleArray.value(~lag, ~timingOffset, server, bundle);
685                                         },
687                                         parGroup: #{|server|
688                                                 var bundle, cmd;
689                                                 if (~id.isNil) { ~id = server.nextNodeID };
690                                                 bundle = [63 /* \p_new */, ~id.asArray, Node.actionNumberFor(~addAction),
691                                                                  ~group.asControlInput].flop;
692                                                 ~schedBundleArray.value(~lag, ~timingOffset, server, bundle);
693                                         },
696                                         bus: #{|server|
697                                                 var array;
698                                                 array = ~array.asArray;
699                                                 ~schedBundle.value(~lag, ~timingOffset, server,
700                                                         [\c_setn, ~out.asControlInput, array.size] ++ array);
701                                         },
703                                         gen: #{|server|
704                                                 ~schedBundle.value(~lag, ~timingOffset, server,
705                                                         [\b_gen, ~bufnum.asControlInput, ~gencmd, ~genflags] ++ ~genarray);
706                                         },
708                                         load: #{|server|
709                                                 ~schedBundle.value(~lag, ~timingOffset, server,
710                                                         [\b_allocRead, ~bufnum.asControlInput, ~filename,
711                                                                 ~frame, ~numframes]);
712                                         },
713                                         read: #{|server|
714                                                 ~schedBundle.value(~lag, ~timingOffset, server,
715                                                         [\b_read, ~bufnum.asControlInput, ~filename,
716                                                                 ~frame, ~numframes, ~bufpos, ~leaveOpen]);
717                                         },
718                                         alloc: #{|server|
719                                                 ~schedBundle.value(~lag, ~timingOffset, server,
720                                                         [\b_alloc, ~bufnum.asControlInput, ~numframes, ~numchannels]);
721                                         },
722                                         free: #{|server|
723                                                 ~schedBundle.value(~lag, ~timingOffset, server,
724                                                         [\b_free, ~bufnum.asControlInput]);
725                                         },
727                                         midi: #{|server|
728                                                 var freqs, lag, dur, sustain, strum;
729                                                 var bndl, midiout, hasGate, midicmd;
731                                                 freqs = ~freq = ~detunedFreq.value;
733                                                 if (freqs.isRest.not) {
734                                                         ~amp = ~amp.value;
735                                                         ~midinote = (freqs.cpsmidi).round(1).asInteger;
736                                                         strum = ~strum;
737                                                         lag = ~lag;
738                                                         sustain = ~sustain = ~sustain.value;
739                                                         midiout = ~midiout.value;
740                                                         ~uid ?? { ~uid = midiout.uid };  // mainly for sysex cmd
741                                                         hasGate = ~hasGate ? true;
742                                                         midicmd = ~midicmd;
743                                                         bndl = ~midiEventFunctions[midicmd].valueEnvir.asCollection;
745                                                         bndl = bndl.asControlInput.flop;
747                                                         bndl.do {|msgArgs, i|
748                                                                         var latency;
750                                                                         latency = i * strum + lag;
752                                                                         if(latency == 0.0) {
753                                                                                 midiout.performList(midicmd, msgArgs)
754                                                                         } {
755                                                                                 thisThread.clock.sched(latency, {
756                                                                                         midiout.performList(midicmd, msgArgs);
757                                                                                 })
758                                                                         };
759                                                                         if(hasGate and: { midicmd === \noteOn }) {
760                                                                                 thisThread.clock.sched(sustain + latency, {
761                                                                                         midiout.noteOff(*msgArgs)
762                                                                                 });
763                                                                         };
764                                                         };
765                                                 }
766                                         },
767                                         setProperties:  {
768                                                 var receiver = ~receiver,
769                                                         go = {
770                                                                 ~args.do { |each|
771                                                                                 var selector, value = each.envirGet;
772                                                                                 if(value.notNil) {
773                                                                                         selector = each.asSetter;
774                                                                                         if(~doTrace == true) {
775                                                                                                 postf("%.%_(%)\n",receiver,selector,value)
776                                                                                         };
777                                                                                         receiver.perform(selector.asSetter, value)
778                                                                                  };
779                                                                 }
780                                                         };
781                                                 if(~defer ? true) {
782                                                                 // inEnvir is needed
783                                                                 // because we'll no longer be in this Event
784                                                                 // when defer wakes up
785                                                         go.inEnvir.defer
786                                                 } {
787                                                         go.value
788                                                 };
789                                         },
790                                         monoOff:  #{|server|
792                                                 if(~hasGate == false) {
793                                                         ~schedBundle.value(~lag, ~timingOffset, server,
794                                                                 [\n_free] ++ ~id.asControlInput);
795                                                 } {
796                                                         ~schedBundle.value(~lag, ~timingOffset, server,
797                                                                 *([15 /* \n_set */, ~id.asControlInput, \gate, 0].flop) );
798                                                 };
800                                         },
802                                         monoSet: #{|server|
803                                                 var freqs, lag, bndl;
805                                                 freqs = ~freq = ~detunedFreq.value;
807                                                 if (freqs.isRest.not) {
808                                                         ~amp = ~amp.value;
809                                                         ~sustain = ~sustain.value;
811                                                         bndl = ([15 /* \n_set */, ~id.asControlInput] ++ ~msgFunc.valueEnvir).flop;
812                                                         bndl = bndl.collect(_.asOSCArgArray);
813                                                         ~schedBundle.value(~lag, ~timingOffset, server, *bndl);
814                                                 };
816                                         },
818                                         monoNote:       #{ |server|
819                                                 var bndl, id, ids, addAction, f;
820                                                 addAction = ~addAction;
821                                                 ~freq = ~detunedFreq.value;
822                                                 f = ~freq;
823                                                 ~amp = ~amp.value;
825                                                 bndl = ( [9 /* \s_new */, ~instrument, ids, addAction, ~group.asControlInput]
826                                                         ++ ~msgFunc.valueEnvir).flop;
827                                                 bndl.do { | b |
828                                                         id = server.nextNodeID;
829                                                         ids = ids.add(id);
830                                                         b[2] = id;
831                                                 };
833                                                 if ((addAction == 0) || (addAction == 3)) {
834                                                         bndl = bndl.reverse;
835                                                 };
836                                                 bndl = bndl.collect(_.asOSCArgArray);
837                                                 ~schedBundle.value(~lag, ~timingOffset, server, *bndl);
838                                                 ~updatePmono.value(ids, server);
839                                         },
841                                         Synth: #{ |server|
842                                                 var instrumentName, desc, msgFunc, sustain;
843                                                 var bndl, synthLib, addAction, group, latency, ids, id, groupControls;
844                                                 ~server = server;
845                                                 addAction = Node.actionNumberFor(~addAction);
846                                                 group = ~group.asControlInput;
847                                                 ~freq = ~detunedFreq.value;
848                                                 ~amp = ~amp.value;
849                                                 ~sustain = sustain = ~sustain.value;
850                                                 ids = ~id;
851                                                 msgFunc = ~getMsgFunc.valueEnvir;
852                                                 instrumentName = ~synthDefName.valueEnvir;
854                                                 bndl = [9 /* \s_new */, instrumentName, ids, addAction, group]
855                                                                                         ++ msgFunc.valueEnvir;
856                                                 if ((addAction == 0) || (addAction == 3)) {
857                                                         bndl = bndl.reverse;
858                                                 };
860                                                 bndl = bndl.collect(_.asOSCArgArray);
862                                                 server.sendBundle(server.latency, *bndl);
863                                                 ~id = ids;
864                                                 ~isPlaying = true;
865                                                 NodeWatcher.register(currentEnvironment);
867                                         },
869                                         Group: #{|server|
870                                                 var ids, group, addAction, bundle;
871                                                 ids = ~id = (~id ?? { server.nextNodeID }).asArray;
872                                                 addAction = Node.actionNumberFor(~addAction);
873                                                 group = ~group.asControlInput;
874                                                 ~server = server;
875                                                 if ((addAction == 0) || (addAction == 3) ) {
876                                                         ids = ids.reverse;
877                                                 };
878                                                 bundle = ids.collect {|id, i|
879                                                         [21 /* \g_new */, id, addAction, group];
880                                                 };
881                                                 server.sendBundle(server.latency, *bundle);
882                                                 ~isPlaying = true;
883                                                 NodeWatcher.register(currentEnvironment);
886                                         },
888                                         tree: #{ |server|
889                                                 var doTree = { |tree, currentNode, addAction=1|
890                                                         if(tree.isKindOf(Association)) {
891                                                                 ~bundle = ~bundle.add([21 /* \g_new */,
892                                                                         tree.key.asControlInput, Node.actionNumberFor(addAction),
893                                                                         currentNode.asControlInput]);
894                                                                 currentNode = tree.key;
895                                                                 tree = tree.value;
896                                                         };
897                                                         if(tree.isSequenceableCollection) {
898                                                                 tree.do { |x, i|
899                                                                         x ?? { tree[i] = x = server.nextNodeID };
900                                                                         doTree.(x, currentNode)
901                                                                 };
902                                                         } {
903                                                                 ~bundle = ~bundle.add([21 /* \g_new */,
904                                                                         tree.asControlInput, Node.actionNumberFor(addAction),
905                                                                         currentNode.asControlInput]);
906                                                         };
908                                                 };
909                                                 ~bundle = nil;
910                                                 ~treeGroups = ~treeGroups ?? { ~tree.deepCopy };
911                                                 ~treeGroups !? {
912                                                         doTree.(~treeGroups, ~group, ~addAction);
913                                                         CmdPeriod.doOnce { ~treeGroups = nil };
914                                                 };
915                                                 ~bundle !? {
916                                                         server.sendBundle(server.latency, *~bundle);
917                                                 };
918                                                 ~bundle = nil;
919                                         }
920                                 )
921                         )
922                 );
924                 parentEvents = (
925                         default: ().putAll(
926                                 partialEvents.pitchEvent,
927                                 partialEvents.ampEvent,
928                                 partialEvents.durEvent,
929                                 partialEvents.bufferEvent,
930                                 partialEvents.serverEvent,
931                                 partialEvents.playerEvent,
932                                 partialEvents.midiEvent
933                         ),
935                         groupEvent:     (
936                                 lag: 0,
937                                 play: #{
938                                         var server, group, addAction, ids, bundle;
939                                         ~finish.value;
940                                         group = ~group.asControlInput;
941                                         addAction = Node.actionNumberFor(~addAction);
942                                         ~server = server= ~server ?? {Server.default};
943                                         ids = Event.checkIDs(~id, server);
944                                         if (ids.isNil) { ids = ~id = server.nextNodeID };
945                                         if ((addAction == 0) || (addAction == 3) ) {
946                                                 ids = ids.asArray.reverse;
947                                         };
948                                         bundle = ids.collect {|id, i|
949                                                 [21 /* \g_new */, id, addAction, group];
950                                         };
951                                         server.sendBundle(server.latency, *bundle);
952                                         ~isPlaying = true;
953                                         ~isRunning = true;
954                                         NodeWatcher.register(currentEnvironment);
955                                 }).putAll(partialEvents.nodeEvent),
957                         synthEvent:     (
958                                 lag: 0,
959                                 play: #{
961                                 var server, latency, group, addAction;
962                                 var instrumentName, synthLib, desc, msgFunc;
963                                 var msgs, cvs;
964                                 var bndl, ids;
965                                 ~finish.value;
966                                 ~server = server = ~server ?? { Server.default };
967                                 ~sustain = ~sustain.value;
968                                 group = ~group.asControlInput;
969                                 addAction = Node.actionNumberFor(~addAction);
970                                 synthLib = ~synthLib ?? { SynthDescLib.global };
971                                 instrumentName = ~instrument.asSymbol;
972                                 desc = synthLib.synthDescs[instrumentName];
973                                 if (desc.notNil) {
974                                         msgFunc = desc.msgFunc;
975                                         ~hasGate = desc.hasGate;
976                                 }{
977                                         msgFunc = ~defaultMsgFunc;
978                                 };
980                                 msgs = msgFunc.valueEnvir.flop;
981                                 ids = Event.checkIDs(~id, server);
982                                 if (ids.isNil) { ids = msgs.collect { server.nextNodeID } };
983                                 bndl = ids.collect { |id, i|
984                                         [9 /* \s_new */, instrumentName, id, addAction, group]
985                                          ++ msgs[i]
986                                 };
988                                 if ((addAction == 0) || (addAction == 3)) {
989                                         bndl = bndl.reverse;
990                                 };
991                                 bndl = bndl.asOSCArgBundle;
992                                 if (~lag !=0) {
993                                         server.sendBundle(server.latency ? 0 + ~lag, *bndl);
994                                 } {
995                                         server.sendBundle(server.latency, *bndl);
997                                 };
998                                 ~id = ids;
999                                 ~isPlaying = true;
1000                                 ~isRunning = true;
1001                                 NodeWatcher.register(currentEnvironment);
1002                                 },
1003                                 defaultMsgFunc: #{|freq = 440, amp = 0.1, pan = 0, out = 0|
1004                                         [\freq, freq, \amp, amp, \pan, pan, \out, out] }
1005                         ).putAll(partialEvents.nodeEvent)
1007                 );
1009                 defaultParentEvent = parentEvents.default;
1010         }