Hello Mike,
excellent initiative, I agree that more docs on this would be very helpful. I’m posting a chapter of my synthesis course on demand ugens. It’s not very polished and with German text. I’ve no time to translate atm, sorry, but feel free to take ideas and examples from there. Some examples are focussing on the analogies between Patterns and drate UGens, some are producing even an an identically sounding output.
I find the application case TGrains
(bottom of file) is a good argument for their usage. miSCellaneous_lib’s Buffer Granulation tutorial also contains examples that use drate UGens (1a, 1b, 1c, 1d, 1e, 1f, 3d), though some are a bit more complicated.
best
Daniel
////////////
DEMAND UGENS
////////////
Demand Ugens sind Objekte, die Sequenzierung innerhalb des SC-Servers erlauben,
analog zu Patterns/Streams in der SC-Language.
Sinnvoll sind demand-rate Ugens vor allem dann, wenn die Sequenzierung
innerhalb sehr kurzer Zeitintervalle stattfinden soll,
da keine OSC-Messages verschickt werden muessen.
Generell sind Setups mit demand-rate Ugens beim Debugging schwieriger
als Setups mit Patterns (Debugging-Daten muessen an die Sprache
zurueckgeschickt werden). Komplizierte Verschachtelungen von
demand-rate Ugens sind problematisch, da schnell unuebersichtlich.
s.boot;
Einfaches Beispiel:
(
SynthDef(\test, { |out = 0, freq = 440, amp = 0.1|
Out.ar(out, SinOsc.ar(freq, 0, amp)!2)
}).add
)
Wh: bei Pmono wird nur ein synth am Server gestartet,
Pmono (bzw. eigentlich der EventStreamPlayer) veranlasst dann
die Setzung der entsprechenden Parameter (hier midinote/freq)
ueber OSC-Messages.
(
x = Pmono(\test,
\dur, 0.2,
\midinote, Pwhite(60.0, 90) // was bewirkt hier 60.0 statt 60 ?
).play
)
Wh: da die Werte sprunghaft veraendert werden, hoert man Klicks.
Das kann durch Lag ("Verschmierung" oder "Abrundung" des Signals)
verhindert werden.
(
SynthDef(\test_2, { |out = 0, freq = 440, amp = 0.1|
Out.ar(out, SinOsc.ar(freq.lag(0.03), 0, amp)!2)
}).add
)
(
x = Pmono(\test_2,
\dur, 0.2,
\midinote, Pwhite(60, 90)
).play
)
Umschreibung desselben Beispiels mit demand-rate Ugen:
Die wesentlichen Bestandteile des obigen Pmono / EventStreamPlayers
sind erstens die Sequenz von Dauern und zweitens die Sequenz,
von der zu jedem durch die erste Sequenz definierten Einsatzzeitpunkt
ein Wert der Frequenz "gezogen" oder "gefordert" wird (fordern = demand).
Im Server ist das Schema nun folgendes: es gibt einen UGen, der
fuer die zeitlich definierte "Forderung" von Daten verantwortlich ist
(Demand) und einen fuer die Sequenz der geforderten Frequenzdaten
(in diesem Fall Dwhite).
Die Definition der Zeiten innerhalb von Demand wird von einem
Trigger-Ugen (hier Impulse) uebernommen.
Die Umrechnung der Tonhoehendaten vom MIDI-Raster in Hz
wird separat vorgenommen und ist unabhaengig von demand-rate UGens.
// Das zweite Argument von Demand ist ein Reset-Trigger,
// er wird hier nicht benoetigt (= 0)
// da hier ein Impulse mit geringer Frequenz verwendet wird,
// reicht Demand.kr und Impulse.kr
(
SynthDef(\test_2b, { |out = 0, amp = 0.1|
var freq = Demand.kr(Impulse.kr(5), 0, Dwhite(60, 90)).midicps;
Out.ar(out, SinOsc.ar(freq.lag(0.03), 0, amp)!2)
}).add
)
// Nun Sequenzierung im synth selbst !
x = Synth(\test_2b);
x.free;
Mit demand-rate Ugens koennen die Dauern sehr kurz sein, bis zu Samplelaenge
(Achtung: Impulse und Demand sind nun ar).
1/1000 sec ware bei Steuerung mit Pmono (1000 Osc-Messages pro Sekunde)
schon problematisch.
(
SynthDef(\test_2c, { |out = 0, dur = 0.2, lag = 0.001, lo = 60, hi = 90, amp = 0.1|
var freq = Demand.ar(Impulse.ar(1/dur), 0, Dwhite(lo, hi)).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp)!2)
}).add
)
s.scope
x = Synth(\test_2c)
x.set(\dur, 1/100)
// geht immer mehr Richtung Rauschen
x.set(\dur, 1/1000)
x.set(\lo, 40)
x.set(\hi, 90)
// lag hat auch Einfluss (laengeres lag: hoehere Frequenzen werden gefiltert)
s.freqscope
x.set(\lag, 0.0001)
x.free
///////////////////////////////////////////
demand-rate Ugens, die in aehnlicher Weise operieren wie
gleichnamige Patterns:
Dseq, Dser, Dshuf, Drand, Dxrand, Dwrand
Dswitch, Dswitch1, Dwhite, Dbrown, Ddup
Die Unterscheidung zwischen Dwhite und Diwhite (fuer Integer-Werte)
bzw Dbrown und Dibrown existiert, weil der Server an sich im Gegensatz
zur Sprache keine Integers kennt (Integers sind ja gerade
in der SC-Sprache definiert), daher braucht es eigene Objekte,
die innerhalb des Servers Integers generieren (der Server ist in C++
programmiert und diese Sprache hat natuerlich Integer-Objekte).
// Was passiert hier ?
(
SynthDef(\test_3, { |out = 0, dur = 0.2, lag = 0.001, amp = 0.1|
var freq = Demand.ar(
Impulse.ar(1/dur),
0,
Dwrand([0, 12], [0.8, 0.2], inf) + Dseq([60, 65, 67], inf)
).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp)!2)
}).add
)
x = Synth(\test_3)
x.set(\dur, 1/40)
x.free
///////////////////////////////////////////
Setzung eines Arrays waehrend demand-rate Ugen laeuft.
Wh.: Array Argumente !
Operationen von demand-rate Ugens sind in aehnlicher
Weise wie bei Patterns moeglich (+, -, * ... )
(
SynthDef(\test_3b, { |out = 0, dur = 0.2, lag = 0.001,
midi = #[60, 65, 67], amp = 0.1|
var freq = Demand.ar(
Impulse.ar(1/dur),
0,
Dwrand([0, 12], [0.8, 0.2], inf) + Dseq(midi, inf)
).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp)!2)
}).add
)
x = Synth(\test_3b)
x.set(\midi, [61, 62, 68])
x.free
///////////////////////////////////////////
Ebenso wie bei Patterns sind auch Schachtelungen
im Prinzip in beliebiger Tiefe moeglich
(
SynthDef(\test_3c, { |out = 0, dur = 0.2, lag = 0.001,
midi1 = #[60, 65, 67], midi2 = #[73, 76, 80],
repeats1 = 3, repeats2 = 3, amp = 0.1|
var freq = Demand.ar(
Impulse.ar(1/dur),
0,
Dseq([Dxrand(midi1, repeats1), Dxrand(midi2, repeats2)], inf)
).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp)!2)
}).add
)
x = Synth(\test_3c)
x.set(\midi2, [81, 82, 95])
x.set(\repeats2, 1)
x.set(\lag, 0)
Bei diesem Tempo geht die "melodische Linie"
in eine Klangfarbe mit starker rauschartiger Charakteristik ueber.
x.set(\dur, 1/500)
Wie viele Wellenlaengen gehen sich bei dieser Dauer aus ?
1/500 / (1/[81, 82, 95].midicps)
s.makeGui
// Vergleich von Recordings
x.set(\dur, 1/2000)
Diese Klangcharakteristik (das Spektrum) kann durch die Parameter der Sequenz
beeinflusst werden
x.set(\repeats1, 4)
x.set(\repeats2, 3)
x.set(\repeats1, 12)
x.set(\repeats2, 27)
x.set(\midi1, [53, 59, 81])
x.set(\midi2, [45, 55, 90])
Das geht im Resultat in Richtung der "stochastischen Synthese" (Iannis Xenakis),
bei der verschiedene elementare Wellenformen zusammengesetzt werden (hier nur Sinus).
Das Ergebnis ist ein im Allgemeinen sehr energiereiches Signal.
//////////////////////////////////////////////////
Mit einem Demand-Objekt koennen auch von mehreren demand-rate Ugens
parallel Werte gezogen werden:
Ein Trigger, zwei demand rate Ugens
(
SynthDef(\test_3d, { |out = 0, dur = 0.2, lag = 0.001,
midi1 = #[60, 65, 67], midi2 = #[73, 76, 80], amp = 0.1|
var freq = Demand.ar(
Impulse.ar(1/dur),
0,
[Dxrand(midi1, inf), Dxrand(midi2, inf)]
).midicps;
// ohne dup bereits stereo wegen multichannel expansion !
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp))
}).add
)
x = Synth(\test_3d)
x.free;
//////////////////////////////////////////////////
Zwei Trigger, ein demand rate Ugen
Achtung, dies ist eine haeufige Fehlerursache !
Demand-rate Ugens sind viel eher mit Streams als mit Patterns
zu vergleichen: wenn mehrfach von ihnen Werte gezogen werden,
aendert sich jedesmal ihr Status.
In diesem Beispiel soll ein Effekt erzielt werden,
der beim Klavier als "nachschlagende" Oktaven bezeichnet wird:
ein Ton wird mit Verzoegerung und Oktavversetzung wiederholt.
Dazu bekommt der zweite Impulse einen Phasenoffset
und der Dseq wird in einem Ddup verpackt, damit die Werte
zweimal ausgegeben werden.
(
SynthDef(\test_3e, { |out = 0, dur = 1, lag = 0.001,
midi1 = #[60, 65, 67], amp = 0.1|
var freq = Demand.ar(
// wie koennte man diese Zeile eleganter schreiben ?
[Impulse.ar(1/dur), Impulse.ar(1/dur, 0.5)],
0,
Ddup(2, Dseq(midi1, inf)) + Dseq([0, 12], inf)
).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp))
}).add
)
x = Synth(\test_3e)
x.set(\dur, 1/20)
// Stereo-Effekt
x.set(\dur, 1/100)
x.set(\dur, 1/2000)
x.free;
Dieses Beispiel funktioniert nicht so wie gedacht
jeder Impulse zieht von jedem Dseq!
(
SynthDef(\test_3f, { |out = 0, dur = 1, lag = 0.001,
midi1 = #[60, 65, 67], amp = 0.1|
var freq = Demand.ar(
[Impulse.ar(1/dur), Impulse.ar(1/dur, 0.5)],
0,
[Dseq(midi1, inf), Dseq(midi1, inf) + 12]
).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp))
}).add
)
x = Synth(\test_3f)
Umweg, zwei Demands mit Funktion definieren:
(
SynthDef(\test_3g, { |out = 0, dur = 1, lag = 0.001,
midi1 = #[60, 65, 67], amp = 0.1|
var freq;
freq = ({ |i|
Demand.ar(
Impulse.ar(1/dur, i/2),
0,
Dseq(midi1, inf) + (i * 12)
) }!2
).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp))
}).add
)
x = Synth(\test_3g)
//////////////////////////////////////////////////
Definition von Rhythmen mit demand rate ugens: Duty
(
SynthDef(\test_3h, { |out = 0, durs = #[3, 3, 2], durFactor = 0.1,
lag = 0.003, midi = #[60, 65, 67], amp = 0.1|
var freq = Duty.ar(
Dseq(durs, inf) * durFactor,
0,
Dwrand([0, 12], [0.8, 0.2], inf) + Dseq(midi, inf)
).midicps;
Out.ar(out, SinOsc.ar(freq.lag(lag), 0, amp)!2)
}).add
)
x = Synth(\test_3h)
x.set(\durFactor, 0.07)
x.set(\durs, #[2, 1, 1])
x.set(\midi, #[50, 70, 90])
x.set(\midi, #[70, 71, 72])
x.set(\durFactor, 0.01)
x.set(\durFactor, 0.003)
x.free
//////////////////////////////////////////////////
TDuty ist eine Variante von Duty, bei der ein Trigger ausgegeben wird
//////////////////////////////////////////////////
Weitere Anwendung von demand rate Ugens: Granulation (TGrains)
s.boot;
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
Demand Ugens als Input f. TGrains-Argumente: 'rate' und 'pan'
(
x = {
var trate, dur, rate;
trate = 10;
dur = 0.5 / trate;
rate = Dseq([1.5, 1, 1], inf);
TGrains.ar(2, Impulse.ar(trate), b, rate, MouseX.kr(0, BufDur.kr(b) * 0.1 + 0.5), dur, Dseq([-1, 1], inf), 0.1, 2);
}.scope;
)
x.free
(Poly-)Rhythmus mit TDuty als Trigger
(
x = {
var trig, dur, rate;
trig = TDuty.ar(Dseq([2, 1, 1, 1] / 50, inf));
dur = 0.05;
rate = Dseq([1.5, 1, 1], inf);
TGrains.ar(2, trig, b, rate, MouseX.kr(0, BufDur.kr(b) * 0.1 + 0.5), dur, Dseq([-1, 1], inf), 0.1, 2);
}.scope;
)
x.free