2 // (Tim Walters) (CC 2006)
4 // Features recursively generated phase modulation trees
6 s.serverRunning.not.if({ s.boot });
12 |name, out = 0, flatness = 6, maxOperators = 32, flavor = 0, attack = 4.0, release = 4.0,
13 sustain = 1.0, trace = false|
14 var freqFuncs, freqModFunc, depthFunc, minNumModulatorsFunc;
15 var maxNumModulatorsFunc, levelSpec, pmtree;
17 // This does timbral shifts by modulating volume of individual operators
18 depthFunc = { LinExp.kr(LFNoise1.kr(exprand(0.1, 2.0)), -1, 1, 0.1, 1.0) };
22 { exprand(0.125, 4000) },
23 { exprand(40, 4000) },
27 // Destabilize oscillators a bit.
28 // Would need a LinExp for proper tuning, but here
30 freqModFunc = { BrownNoise.kr.range(0.998, 0.998.reciprocal) };
32 // This could be something more interesting
33 minNumModulatorsFunc = {
38 // Tree thins out slowly with increasing depth
39 maxNumModulatorsFunc = {
41 (level.reciprocal.sqrt * flatness).asInteger + 1;
44 // Generate tree of given size. If trace is true, dumps representation to post window.
46 |level, size, trace = false|
47 var numModulators, numSubOperators, modGroup, thisUnitDepth, freq, freqMod;
50 // Keep root oscillator in normal note range
51 freq = (level == 1).if(
53 { freqFuncs.wrapAt(flavor).value }
56 (level - 1).do({ "-".post });
59 freqMod = freqModFunc.value;
60 numSubOperators = size - 1;
62 // Keep root oscillator at appropriate volume (don't modulate)
63 thisUnitDepth = (level == 1).if(
64 { -18.dbamp * AmpComp.kr(freq) },
68 // Choose number of tree branches, based on current level
70 minNumModulatorsFunc.value(level)..maxNumModulatorsFunc.value(level)
71 ).choose.min(numSubOperators);
73 // Recursively generate subtrees, dividing up size among them.
77 (numModulators == 0).if({
80 newSizes = 0 ! numModulators;
83 index = rrand(0, numModulators - 1);
84 newSizes[index] = newSizes[index] + 1;
88 pmtree.value(level + 1, newSizes[i], trace)
92 // Return sine oscillator with subtrees summed as phase modulator
93 output = SinOsc.ar(freq * freqMod, modGroup, thisUnitDepth);
98 // Generate synthdef from tree. Use audio rate panning for stereo,
99 // with modulated width.
100 SynthDef.new(name ? \PMTree, {
101 |attack = 4.0, release = 4.0, sustain = 1.0, gate = 1.0|
103 env = EnvGen.kr(Env.asr(attack, sustain, release, curve: [4, -4]), gate, doneAction: 2);
105 pmtree.value(1, maxOperators, trace),
106 SinOsc.ar(freqFuncs.wrapAt(flavor).value, 0, depthFunc.value, rrand(-0.5, 0.5)),
114 ~effectBus = Bus.audio(s, 2);
120 env = EnvGen.ar(Env.asr(0, 1.0, 16.0), gate, doneAction: 2);
122 // Tame those pesky upper mids.
123 BPeakEQ.ar(In.ar(~effectBus.index, 2), 4000.0, 0.5, -6),
125 earlyreflevel: -9.dbamp,
126 taillevel: -12.dbamp,
131 // Player generates and plays synths of random size and flavor.
133 var synthName, polyphony;
138 synthName = ("PMTree" ++ (i % 100).asString).asSymbol;
139 targetNumSynths = rrand(1, polyphony - 1);
140 {~synths.size > targetNumSynths}.while({
141 ~synths.remove(~synths.choose.release);
144 pmSynthDefFactory.value(
147 exprand(2.0, 9.0).asInteger,
148 exprand(4.0, 33.0).asInteger,
149 [0, 0, 0, 0, 1, 2].choose,
155 exprand(12.0, 36.0).wait;
163 // Execute to finish.
165 ~synths.do({ |synth| ~synths.remove(synth.release) });
170 // Execute after sound has completely died away.