File: PG_Cookbook03_External_Control.schelp

package info (click to toggle)
supercollider 1%3A3.13.0%2Brepack-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 80,292 kB
  • sloc: cpp: 476,363; lisp: 84,680; ansic: 77,685; sh: 25,509; python: 7,909; makefile: 3,440; perl: 1,964; javascript: 974; xml: 826; java: 677; yacc: 314; lex: 175; objc: 152; ruby: 136
file content (195 lines) | stat: -rw-r--r-- 7,150 bytes parent folder | download | duplicates (5)
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
title:: Pattern Guide Cookbook 03: External Control
summary:: Pattern control by external device
related:: Tutorials/A-Practical-Guide/PG_Cookbook02_Manipulating_Patterns, Tutorials/A-Practical-Guide/PG_Cookbook04_Sending_MIDI
categories:: Streams-Patterns-Events>A-Practical-Guide

section::Pattern control by external device

subsection::Control of parameters by MIDI or HID

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.

code::
(
~legato = 1;
c = MIDIFunc.cc({ |value, num, chan, src|
	~legato = value.linlin(0, 127, 0.1, 2.5)
}, 1);	// 1 means modwheel
)

(
p = Pbind(
	\degree, Pwhite(-7, 12, inf),
	\dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
	\legato, Pfunc { ~legato }	// retrieves value set by MIDI control
).play;
)

p.stop;
c.free;
::

If Pfunc code::{  }:: is bothersome in the Pbind, a link::Classes/PatternProxy:: or link::Classes/Pdefn:: could also serve the purpose.

code::
(
~legato = PatternProxy(1);
c = MIDIFunc.cc({ |value, num, chan, src|
	~legato.source = value.linlin(0, 127, 0.1, 2.5)
}, 1);
)

(
p = Pbind(
	\degree, Pwhite(-7, 12, inf),
	\dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
	\legato, ~legato
).play;
)

p.stop;
c.remove;
::

subsection::Triggering patterns by external control

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.

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::).

subsection::Triggering a pattern by a GUI

code::
(
var	pattern = Pbind(
		\degree, Pseries(7, Pwhite(1, 3, inf) * Prand(#[-1, 1], inf), inf).fold(0, 14)
			+ Prand(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], inf),
		\dur, Pwrand(#[1, 0.5], #[0.8, 0.2], inf)
	),
	player, window;

window = Window.new("Pattern trigger", Rect(5, 100, 150, 100))
		// onClose is fairly important
		// without it, closing the window could leave the pattern playing
	.onClose_({ player.stop });
Button.new(window, Rect(5, 5, 140, 90))
	.states_([["Pattern GO"], ["Pattern STOP"]])
	.font_(Font.new("Helvetica", 18))
	.action_({ |button|
		if(button.value == 1 and: { player.isNil or: { player.isPlaying.not } }) {
			player = pattern.play;
		} {
			player.stop;
			button.value = 0;
		};
	});
window.front;
)

p.stop;
::

subsection::Triggering a pattern by MIDI

code::
(
var	pattern = Pbind(
		\degree, Pseries(7, Pwhite(1, 3, inf) * Prand(#[-1, 1], inf), inf).fold(0, 14)
			+ Prand(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], inf),
		\dur, Pwrand(#[1, 0.5], #[0.8, 0.2], inf)
	),
	player;

~noteOnFunc = MIDIFunc.noteOn({
	if(player.isNil or: { player.isPlaying.not }) {
		player = pattern.play;
	} {
		player.stop;
	};
// 60 limits this MIDIFunc to listen to middle-C only
// but it will pick up that note from any port, any channel
}, 60);
)

// when done
~noteOnFunc.free;
::

subsection::Triggering a pattern by signal amplitude

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.

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:

code::
	inbus: s.options.numOutputBusChannels
::

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.

code::
	var	amp = Amplitude.kr(In.ar(inbus, 1), attackTime: 0.01, releaseTime: decay),
		trig = HPZ1.kr(amp >= thresh);
	SendTrig.kr(trig.abs, 1, trig);
::

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.

For this example, triggers are measured only when the signal rises above the threshold.

code::
(
var	pattern = Pbind(
		\degree, Pseries(7, Pwhite(1, 3, inf) * Prand(#[-1, 1], inf), inf).fold(0, 14)
			+ Prand(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], inf),
		\dur, Pwrand(#[1, 0.5], #[0.8, 0.2], inf)
	),
	player;

// Quicky GUI to tune threshold and decay times
~w = Window("threshold setting", Rect(15, 100, 300, 100))
	.onClose_({
		~ampSynth.free;
		~ampUpdater.free;
		~oscTrigResp.free;
		player.stop;
	});
~w.view.decorator = FlowLayout(~w.view.bounds, 2@2, 2@2);
~ampView = EZSlider(~w, 295@20, "amplitude", \amp, labelWidth: 80, numberWidth: 60);
~ampView.sliderView.canFocus_(false).enabled_(false);
~ampView.numberView.canFocus_(false).enabled_(false);
StaticText(~w, 295@5).background_(Color.gray);
~threshView = EZSlider(~w, 295@30, "threshold", \amp, action: { |ez|
	~ampSynth.set(\thresh, ez.value);
}, initVal: 0.4, labelWidth: 80, numberWidth: 60);
~decayView = EZSlider(~w, 295@30, "decay", #[0.1, 100, \exp], action: { |ez|
	~ampSynth.set(\decay, ez.value);
}, initVal: 80.0, labelWidth: 80, numberWidth: 60);

~w.front;

~ampSynth = SynthDef(\ampSynth, { |inbus, thresh = 0.8, decay = 1|
	var	amp = Amplitude.kr(In.ar(inbus, 1), attackTime: 0.01, releaseTime: decay);
		// this trigger (id==0) is to update the gui only
	SendReply.kr(Impulse.kr(10), '/amp', amp);
		// this trigger gets sent only when amplitude crosses threshold
	SendReply.kr(amp >= thresh, '/amptrig');
}).play(args: [inbus: s.options.numOutputBusChannels, thresh: ~threshView.value, decay: ~decayView.value]);

~ampUpdater = OSCFunc({ |msg|
	defer { ~ampView.value = msg[3] }
}, '/amp', s.addr);

~oscTrigResp = OSCFunc({ |msg|
	if(player.isNil or: { player.isPlaying.not }) {
		player = pattern.play;
	} {
		player.stop;
	};
}, '/amptrig', s.addr);
)
::

Previous:	link::Tutorials/A-Practical-Guide/PG_Cookbook02_Manipulating_Patterns::

Next:		link::Tutorials/A-Practical-Guide/PG_Cookbook04_Sending_MIDI::