class library: quit internal server on class library compilation
[supercollider.git] / HelpSource / Tutorials / A-Practical-Guide / PG_Cookbook05_Using_Samples.schelp
blob0a21db1a74c9fffd5db5793b4ea5bb87892c890d
1 title:: PG_Cookbook05_Using_Samples
2 summary:: Using samples
3 related:: Tutorials/A-Practical-Guide/PG_Cookbook04_Sending_MIDI, Tutorials/A-Practical-Guide/PG_Cookbook06_Phrase_Network
4 categories:: Streams-Patterns-Events>A-Practical-Guide
6 section::Using samples
8 subsection::Playing a pattern in time with a sampled loop
10 A deceptively complex requirement... here, we will loop the a11wlk01.wav sample between 0.404561 and 3.185917 seconds (chosen for its surprisingly accurate four-beat groove), and overlay synthesized bells emphasizing the meter.
12 It might be tempting to loop a link::Classes/PlayBuf:: so that the loop runs automatically on the server, but it can easily drift out of sync with the client (because of slight deviations in the actual sample rate). Instead, the example defines a SynthDef that plays exactly one repetition of the loop, and repeatedly triggers it once per bar.
14 The primary bell pattern accents the downbeat and follows with a randomly generated rhythm. The catch is that we have no assurance that the link::Classes/Pwrand:: code::\dur:: pattern will add up to exactly 4 beats. The link::Classes/Pfindur:: ("finite duration") pattern cuts off the inner Pbind after 4 beats. This would stop the pattern, except link::Classes/Pn:: repeats the Pfindur infinitely, placing the accent in the right place every time.
16 The loop actually starts with a half-beat anacrusis, so link::Classes/Ptpar:: delays the bell patterns by 0.5 beats.
18 code::
20 b = Buffer.read(s, Help.dir +/+ "sounds/a11wlk01.wav");
22 // one loop segment
23 SynthDef(\oneLoop, { |out, bufnum, start, time, amp|
24         var     sig = PlayBuf.ar(1, bufnum, startPos: start, loop: 0),
25                 env = EnvGen.kr(Env.linen(0.01, time, 0.05, level: amp), doneAction: 2);
26         Out.ar(out, (sig * env) ! 2)
27 }).add;
29 SynthDef(\bell, { |out, accent = 0, amp = 0.1, decayScale = 1|
30         var     exc = PinkNoise.ar(amp)
31                         * Decay2.kr(Impulse.kr(0), 0.01, 0.05),
32                 sig = Klank.ar(`[
33                         { ExpRand(400, 1600) } ! 4,
34                         1 ! 4,
35                         { ExpRand(0.1, 0.4) } ! 4
36                 ], exc, freqscale: accent + 1, decayscale: decayScale);
37         DetectSilence.ar(sig, doneAction: 2);
38         Out.ar(out, sig ! 2)
39 }).add;
43 TempoClock.default.tempo = 0.35953685899971 * 4;
45 p = Ptpar([
46         0, Pbind(
47                 \instrument, \oneLoop,
48                 \bufnum, b,
49                 \amp, 0.4,
50                 \start, 17841,
51                 \time, 0.35953685899971.reciprocal,
52                 \dur, 4
53         ),
54         0.5, Pn(
55                 Pfindur(4,
56                         Pbind(
57                                 \instrument, \bell,
58                                 \accent, Pseq([2, Pn(0, inf)], 1),
59                                 \amp, Pseq([0.3, Pn(0.1, inf)], 1),
60                                 \decayScale, Pseq([6, Pn(1, inf)], 1),
61                                 \dur, Pwrand(#[0.25, 0.5, 0.75, 1], #[2, 3, 1, 1].normalizeSum, inf)
62                         )
63                 ),
64         inf),
65         0.5, Pbind(
66                 \instrument, \bell,
67                 \accent, -0.6,
68                 \amp, 0.2,
69                 \decayScale, 0.1,
70                 \dur, 1
71         )
72 ], 1).play;
75 p.stop;
78 The use of Ptpar above means that you could stop or start only the whole ball of wax at once, with no control over the three layers. It's no more difficult to play the layers in the independent event stream players, using the quant argument to ensure the proper synchronization. See the link::Classes/Quant:: help file for details on specifying the onset time of a pattern.
80 code::
82 TempoClock.default.tempo = 0.35953685899971 * 4;
84 p = Pbind(
85         \instrument, \oneLoop,
86         \bufnum, b,
87         \amp, 0.4,
88         \start, 17841,
89         \time, 0.35953685899971.reciprocal,
90         \dur, 4
91 ).play(quant: [4, 3.5]);
93 q = Pn(
94         Pfindur(4,
95                 Pbind(
96                         \instrument, \bell,
97                         \accent, Pseq([2, Pn(0, inf)], 1),
98                         \amp, Pseq([0.3, Pn(0.1, inf)], 1),
99                         \decayScale, Pseq([6, Pn(1, inf)], 1),
100                         \dur, Pwrand(#[0.25, 0.5, 0.75, 1], #[2, 3, 1, 1].normalizeSum, inf)
101                 )
102         ),
103 inf).play(quant: [4, 4]);
105 r = Pbind(
106         \instrument, \bell,
107         \accent, -0.6,
108         \amp, 0.2,
109         \decayScale, 0.1,
110         \dur, 1
111 ).play(quant: [4, 4]);
114 [p, q, r].do(_.stop);
116 b.free;
119 subsection::Using audio samples to play pitched material
121 To use an instrument sample in a pattern, you need a SynthDef that plays the sample at a given rate. Here we will use link::Classes/PlayBuf::, which doesn't allow looping over a specific region. For that, link::Classes/Phasor:: and link::Classes/BufRd:: are probably the best choice. ( strong::Third-party extension alert:: : LoopBuf by Lance Putnam is an alternative - find it in the strong::sc3-plugins:: package.)
123 Frequency is controlled by the rate parameter. The sample plays at a given frequency at normal rate, so to play a specific frequency, code::frequency / baseFrequency:: gives you the required rate.
125 The first example makes a custom protoEvent that calculates rate, as code::\freq::, based on the base frequency. It uses one sample, so it would be best for patterns that will play in a narrow range. Since there isn't an instrument sample in the SuperCollider distribution, we will record a frequency-modulation sample into a buffer before running the pattern.
127 code::
128 // make a sound sample
130 var     recorder;
131 fork {
132         b = Buffer.alloc(s, 44100 * 2, 1);
133         s.sync;
134         recorder = { |freq = 440|
135                 var     initPulse = Impulse.kr(0),
136                         mod = SinOsc.ar(freq) * Decay2.kr(initPulse, 0.01, 3) * 5,
137                         car = SinOsc.ar(freq + (mod*freq)) * Decay2.kr(initPulse, 0.01, 2.0);
138                 RecordBuf.ar(car, b, loop: 0, doneAction: 2);
139                 car ! 2
140         }.play;
141         o = OSCFunc({ |msg|
142                 if(msg[1] == recorder.nodeID, {
143                         "done recording".postln;
144                         o.free;
145                 });
146         }, '/n_end', s.addr);
148 SynthDef(\sampler, { |out, bufnum, freq = 1, amp = 1|
149         var     sig = PlayBuf.ar(1, bufnum, rate: freq, doneAction: 2) * amp;
150         Out.ar(out, sig ! 2)
151 }).add;
155 // WAIT for "done recording" message before doing this
156 var     samplerEvent = Event.default.put(\freq, { ~midinote.midicps / ~sampleBaseFreq });
158 TempoClock.default.tempo = 1;
159 p = Pbind(
160         \degree, Pwhite(0, 12, inf),
161         \dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
162         \amp, Pexprand(0.1, 0.5, inf),
163         \sampleBaseFreq, 440,
164         \instrument, \sampler,
165         \bufnum, b
166 ).play(protoEvent: samplerEvent);
169 p.stop;
170 b.free;
173 subsection::Multi-sampled instruments
175 To extend the sampler's range using multiple samples and ensure smooth transitions between frequency ranges, the SynthDef should crossfade between adjacent buffers. A hybrid approach is used here, where Pbind calculates the lower buffer number to use and the SynthDef calculates the crossfade strength. (The calculations could be structured differently, either putting more of them into the SynthDef for convenience in the pattern, or loading them into the pattern and keeping the SynthDef as lean as possible.)
177 MIDI note numbers are used for these calculations because it's a linear frequency scale and linear interpolation is easier than the exponential interpolation that would be required when using Hz. Assuming a sorted array, indexInBetween gives the fractional index using linear interpolation. If you need to use frequency in Hz, use this function in place of indexInBetween.
179 code::
180         f = { |val, array|
181                 var a, b, div;
182                 var i = array.indexOfGreaterThan(val);
183                 if(i.isNil) { array.size - 1 } {
184                         if(i == 0) { i } {
185                                 a = array[i-1]; b = array[i];
186                                 div = b / a;
187                                 if(div == 1) { i } {
188                                                 // log() / log() == log(val/a) at base (b/a)
189                                                 // which is the inverse of exponential interpolation
190                                         log(val / a) / log(div) + i - 1
191                                 }
192                         }
193                 };
194         };
197 But that function isn't needed for this example:
199 code::
201 var     bufCount;
202 ~midinotes = (39, 46 .. 88);
203 bufCount = ~midinotes.size;
205 fork {
206                 // record the samples at different frequencies
207         b = Buffer.allocConsecutive(~midinotes.size, s, 44100 * 2, 1);
208         SynthDef(\sampleSource, { |freq = 440, bufnum|
209                 var     initPulse = Impulse.kr(0),
210                         mod = SinOsc.ar(freq) * Decay2.kr(initPulse, 0.01, 3) * 5,
211                         car = SinOsc.ar(freq + (mod*freq)) * Decay2.kr(initPulse, 0.01, 2.0);
212                 RecordBuf.ar(car, bufnum, loop: 0, doneAction: 2);
213         }).send(s);
214         s.sync;
215                 // record all 8 buffers concurrently
216         b.do({ |buf, i|
217                 Synth(\sampleSource, [freq: ~midinotes[i].midicps, bufnum: buf]);
218         });
220 o = OSCFunc({ |msg|
221         bufCount = bufCount - 1;
222         if(bufCount == 0) {
223                 "done recording".postln;
224                 o.free;
225         };
226 }, '/n_end', s.addr);
228 SynthDef(\multiSampler, { |out, bufnum, bufBase, baseFreqBuf, freq = 440, amp = 1|
229         var     buf1 = bufnum.floor,
230                 buf2 = buf1 + 1,
231                 xfade = (bufnum - buf1).madd(2, -1),
232                 basefreqs = Index.kr(baseFreqBuf, [buf1, buf2]),
233                 playbufs = PlayBuf.ar(1, bufBase + [buf1, buf2], freq / basefreqs, loop: 0, doneAction: 2),
234                 sig = XFade2.ar(playbufs[0], playbufs[1], xfade, amp);
235         Out.ar(out, sig ! 2)
236 }).add;
238 ~baseBuf = Buffer.alloc(s, ~midinotes.size, 1, { |buf| buf.setnMsg(0, ~midinotes.midicps) });
242 TempoClock.default.tempo = 1;
243 p = Pbind(
244         \instrument, \multiSampler,
245         \bufBase, b.first,
246         \baseFreqBuf, ~baseBuf,
247         \degree, Pseries(0, Prand(#[-2, -1, 1, 2], inf), inf).fold(-11, 11),
248         \dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
249         \amp, Pexprand(0.1, 0.5, inf),
250                 // some important conversions
251                 // identify the buffer numbers to read
252         \freq, Pfunc { |ev| ev.use(ev[\freq]) },
253         \bufnum, Pfunc({ |ev| ~midinotes.indexInBetween(ev[\freq].cpsmidi) })
254                 .clip(0, ~midinotes.size - 1.001)
255 ).play;
258 p.stop;
259 b.do(_.free); ~baseBuf.free;
262 Previous:       link::Tutorials/A-Practical-Guide/PG_Cookbook04_Sending_MIDI::
264 Next:           link::Tutorials/A-Practical-Guide/PG_Cookbook06_Phrase_Network::