class library: quit internal server on class library compilation
[supercollider.git] / HelpSource / Tutorials / A-Practical-Guide / PG_Cookbook03_External_Control.schelp
blobf2b44e15a0f8ea8a07d78d2ea8c3099c64edb900
1 title:: PG_Cookbook03_External_Control
2 summary:: Pattern control by external device
3 related:: Tutorials/A-Practical-Guide/PG_Cookbook02_Manipulating_Patterns, Tutorials/A-Practical-Guide/PG_Cookbook04_Sending_MIDI
4 categories:: Streams-Patterns-Events>A-Practical-Guide
6 section::Pattern control by external device
8 subsection::Control of parameters by MIDI or HID
10 To get pattern values from a HID device, use the link::Classes/PhidKey:: or link::Classes/PhidSlot:: pattern.
12 For MIDI, the best approach is to save an incoming value into a variable, and then use link::Classes/Pfunc:: to access the variable for each event.
14 code::
16 ~legato = 1;
17 c = MIDIFunc.cc({ |value, num, chan, src|
18         ~legato = value.linlin(0, 127, 0.1, 2.5)
19 }, 1);  // 1 means modwheel
23 p = Pbind(
24         \degree, Pwhite(-7, 12, inf),
25         \dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
26         \legato, Pfunc { ~legato }      // retrieves value set by MIDI control
27 ).play;
30 p.stop;
31 c.free;
34 If Pfunc code::{  }:: is bothersome in the Pbind, a link::Classes/PatternProxy:: or link::Classes/Pdefn:: could also serve the purpose.
36 code::
38 ~legato = PatternProxy(1);
39 c = MIDIFunc.cc({ |value, num, chan, src|
40         ~legato.source = value.linlin(0, 127, 0.1, 2.5)
41 }, 1);
45 p = Pbind(
46         \degree, Pwhite(-7, 12, inf),
47         \dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
48         \legato, ~legato
49 ).play;
52 p.stop;
53 c.remove;
56 subsection::Triggering patterns by external control
58 Issuing code::play:: to a pattern can occur in an action function for many different kinds of objects: GUI, MIDI, OSCFunc, HID actions. This allows triggering patterns from a variety of interfaces.
60 It's very unlikely that an action function would be triggered exactly in sync with a clock. If the pattern being played needs to run in time with other patterns, use the code::quant:: argument to control its starting time (see link::Classes/Quant::).
62 subsection::Triggering a pattern by a GUI
64 code::
66 var     pattern = Pbind(
67                 \degree, Pseries(7, Pwhite(1, 3, inf) * Prand(#[-1, 1], inf), inf).fold(0, 14)
68                         + Prand(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], inf),
69                 \dur, Pwrand(#[1, 0.5], #[0.8, 0.2], inf)
70         ),
71         player, window;
73 window = Window.new("Pattern trigger", Rect(5, 100, 150, 100))
74                 // onClose is fairly important
75                 // without it, closing the window could leave the pattern playing
76         .onClose_({ player.stop });
77 Button.new(window, Rect(5, 5, 140, 90))
78         .states_([["Pattern GO"], ["Pattern STOP"]])
79         .font_(Font.new("Helvetica", 18))
80         .action_({ |button|
81                 if(button.value == 1 and: { player.isNil or: { player.isPlaying.not } }) {
82                         player = pattern.play;
83                 } {
84                         player.stop;
85                         button.value = 0;
86                 };
87         });
88 window.front;
91 p.stop;
94 subsection::Triggering a pattern by MIDI
96 code::
98 var     pattern = Pbind(
99                 \degree, Pseries(7, Pwhite(1, 3, inf) * Prand(#[-1, 1], inf), inf).fold(0, 14)
100                         + Prand(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], inf),
101                 \dur, Pwrand(#[1, 0.5], #[0.8, 0.2], inf)
102         ),
103         player;
105 ~noteOnFunc = MIDIFunc.noteOn({
106         if(player.isNil or: { player.isPlaying.not }) {
107                 player = pattern.play;
108         } {
109                 player.stop;
110         };
111 // 60 limits this MIDIFunc to listen to middle-C only
112 // but it will pick up that note from any port, any channel
113 }, 60);
116 // when done
117 ~noteOnFunc.free;
120 subsection::Triggering a pattern by signal amplitude
122 Triggering a pattern based on audio amplitude is a bit trickier -- not because it's harder to play the pattern, but because identifying when the trigger should happen is more involved. The most straightforward way in SuperCollider is to use the link::Classes/Amplitude:: UGen to get the volume of the input signal and compare it to a threshold. Volume can fluctuate rapidly, so the code::releaseTime:: argument of Amplitude is set to a high value. This makes the measured amplitude fall more slowly toward the baseline, preventing triggers from being sent too close together.
124 The actual threshold depends on the incoming signal, so the example pops up a quick and dirty window to see the measured amplitude and set the threshold and decay accordingly. The synth listens by default to the first hardware input bus, but you can change it the following in the code to use a different input bus:
126 code::
127         inbus: s.options.numOutputBusChannels
130 In this configuration, the first trigger starts the pattern and the second trigger stops it. You might want the pattern to play while the input signal is above the threshold, and stop when the signal drops to a quieter level. The comparison code::amp >= thresh:: can send a trigger only when the signal goes from softer to lower, so if we want the pattern to stop when the signal becomes quiet, we need to send a trigger when crossing the threshold in both directions.
132 code::
133         var     amp = Amplitude.kr(In.ar(inbus, 1), attackTime: 0.01, releaseTime: decay),
134                 trig = HPZ1.kr(amp >= thresh);
135         SendTrig.kr(trig.abs, 1, trig);
138 link::Classes/HPZ1:: is positive if its input rises and negative if it falls. Triggering based on the absolute value, then, sends the trigger on any change. The client responding to the trigger might need to know the direction of change, so we send HPZ1's value back and the client can decide which action to take based on the sign of this value.
140 For this example, triggers are measured only when the signal rises above the threshold.
142 code::
144 var     pattern = Pbind(
145                 \degree, Pseries(7, Pwhite(1, 3, inf) * Prand(#[-1, 1], inf), inf).fold(0, 14)
146                         + Prand(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], inf),
147                 \dur, Pwrand(#[1, 0.5], #[0.8, 0.2], inf)
148         ),
149         player;
151 // Quicky GUI to tune threshold and decay times
152 ~w = Window("threshold setting", Rect(15, 100, 300, 100))
153         .onClose_({
154                 ~ampSynth.free;
155                 ~ampUpdater.free;
156                 ~oscTrigResp.free;
157                 player.stop;
158         });
159 ~w.view.decorator = FlowLayout(~w.view.bounds, 2@2, 2@2);
160 ~ampView = EZSlider(~w, 295@20, "amplitude", \amp, labelWidth: 80, numberWidth: 60);
161 ~ampView.sliderView.canFocus_(false).enabled_(false);
162 ~ampView.numberView.canFocus_(false).enabled_(false);
163 StaticText(~w, 295@5).background_(Color.gray);
164 ~threshView = EZSlider(~w, 295@30, "threshold", \amp, action: { |ez|
165         ~ampSynth.set(\thresh, ez.value);
166 }, initVal: 0.4, labelWidth: 80, numberWidth: 60);
167 ~decayView = EZSlider(~w, 295@30, "decay", #[0.1, 100, \exp], action: { |ez|
168         ~ampSynth.set(\decay, ez.value);
169 }, initVal: 80.0, labelWidth: 80, numberWidth: 60);
171 ~w.front;
173 ~ampSynth = SynthDef(\ampSynth, { |inbus, thresh = 0.8, decay = 1|
174         var     amp = Amplitude.kr(In.ar(inbus, 1), attackTime: 0.01, releaseTime: decay);
175                 // this trigger (id==0) is to update the gui only
176         SendReply.kr(Impulse.kr(10), '/amp', amp);
177                 // this trigger gets sent only when amplitude crosses threshold
178         SendTrig.kr(amp >= thresh, '/amptrig');
179 }).play(args: [inbus: s.options.numOutputBusChannels, thresh: ~threshView.value, decay: ~decayView.value]);
181 ~ampUpdater = OSCFunc({ |msg|
182         defer { ~ampView.value = msg[3] }
183 }, '/amp', s.addr);
185 ~oscTrigResp = OSCFunc({ |msg|
186         if(player.isNil or: { player.isPlaying.not }) {
187                 player = pattern.play;
188         } {
189                 player.stop;
190         };
191 }, '/amptrig', s.addr);
195 Previous:       link::Tutorials/A-Practical-Guide/PG_Cookbook02_Manipulating_Patterns::
197 Next:           link::Tutorials/A-Practical-Guide/PG_Cookbook04_Sending_MIDI::