Permalink
Newer
100644
189 lines (151 sloc)
4.81 KB
1
// a subtractive polysynth engine
2
3
Engine_PolySub : CroneEngine {
4
5
classvar <polyDef;
6
classvar <paramDefaults;
7
classvar <maxNumVoices;
8
9
var <ctlBus; // collection of control busses
10
var <mixBus; // audio bus for mixing synth voices
11
var <gr; // parent group for voice nodes
12
var <voices; // collection of voice nodes
13
14
*initClass {
15
maxNumVoices = 16;
16
StartUp.add {
17
// a decently versatile subtractive synth voice
18
polyDef = SynthDef.new(\polySub, {
19
arg out, gate=1, hz, level=0.2, // the basics
20
shape=0.0, // base waveshape selection
21
timbre=0.5, // modulation of waveshape
22
sub=0.4, // sub-octave sine level
23
noise = 0.0, // pink noise level (before filter)
24
cut=8.0, // RLPF cutoff frequency as ratio of fundamental
25
// amplitude envelope params
26
ampAtk=0.05, ampDec=0.1, ampSus=1.0, ampRel=1.0, ampCurve= -1.0,
27
// filter envelope params
28
cutAtk=0.0, cutDec=0.0, cutSus=1.0, cutRel=1.0,
29
cutCurve = -1.0, cutEnvAmt=0.0,
30
fgain=0.0, // filter gain (moogFF model)
31
detune=0, // linear frequency detuning between channels
32
width=0.5,// stereo width
33
hzLag = 0.1;
34
36
37
// TODO: could add control over these lag times if you wanna get crazy
38
detune = Lag.kr(detune);
39
shape = Lag.kr(shape);
40
timbre = Lag.kr(timbre);
41
fgain = Lag.kr(fgain.min(4.0));
42
cut = Lag.kr(cut);
43
width = Lag.kr(width);
44
45
detune = detune / 2;
46
hz = Lag.kr(hz, hzLag);
47
freq = [hz + detune, hz - detune];
48
osc1 = VarSaw.ar(freq:freq, width:timbre);
49
osc2 = Pulse.ar(freq:freq, width:timbre);
50
// TODO: could add more oscillator types
51
52
53
// FIXME: probably a better way to do this channel selection
54
snd = [SelectX.ar(shape, [osc1[0], osc2[0]]), SelectX.ar(shape, [osc1[1], osc2[1]])];
55
snd = snd + ((SinOsc.ar(hz / 2) * sub).dup);
56
aenv = EnvGen.ar(
57
Env.adsr(ampAtk, ampDec, ampSus, ampRel, 1.0, ampCurve),
58
gate, doneAction:2);
59
60
fenv = EnvGen.ar(Env.adsr(cutAtk, cutDec, cutSus, cutRel), gate);
61
62
cut = SelectX.kr(cutEnvAmt, [cut, cut * fenv]);
64
65
snd = SelectX.ar(noise, [snd, [PinkNoise.ar, PinkNoise.ar]]);
66
snd = MoogFF.ar(snd, cut, fgain) * aenv;
67
68
Out.ar(out, level * SelectX.ar(width, [Mix.new(snd).dup, snd]));
69
});
70
71
CroneDefs.add(polyDef);
73
paramDefaults = Dictionary.with(
74
\level -> -12.dbamp,
75
\shape -> 0.0,
76
\timbre -> 0.5,
77
\noise -> 0.0,
78
\cut -> 8.0,
79
\ampAtk -> 0.05, \ampDec -> 0.1, \ampSus -> 1.0, \ampRel -> 1.0, \ampCurve -> -1.0,
80
\cutAtk -> 0.0, \cutDec -> 0.0, \cutSus -> 1.0, \cutRel -> 1.0,
81
\cutCurve -> -1.0, \cutEnvAmt -> 0.0,
82
\fgain -> 0.0,
83
\detune -> 0,
84
\width -> 0.5,
85
\hzLag -> 0.1
86
);
87
88
} // Startup
89
} // initClass
90
91
*new { arg context, callback;
92
^super.new(context, callback);
93
}
94
95
alloc {
96
gr = ParGroup.new(context.xg);
97
98
voices = Dictionary.new;
99
ctlBus = Dictionary.new;
100
polyDef.allControlNames.do({ arg ctl;
101
var name = ctl.name;
102
postln("control name: " ++ name);
103
if((name != \gate) && (name != \hz) && (name != \out), {
104
ctlBus.add(name -> Bus.control(context.server));
105
ctlBus[name].set(paramDefaults[name]);
106
});
107
});
108
109
ctlBus.postln;
110
111
ctlBus[\level].setSynchronous( 0.2 );
112
113
114
//--------------
115
//--- voice control, all are indexed by arbitarry ID number
116
// (voice allocation should be performed by caller)
117
118
// start a new voice
119
this.addCommand(\start, "if", { arg msg;
120
this.addVoice(msg[1], msg[2], true);
121
});
122
123
124
// same as start, but don't map control busses, just copy their current values
126
this.addVoice(msg[1], msg[2], false);
127
});
128
129
130
// stop a voice
131
this.addCommand(\stop, "i", { arg msg;
132
this.removeVoice(msg[1]);
133
});
134
135
// free all synths
136
this.addCommand(\stopAll, "", {
137
gr.set(\gate, 0);
138
voices.clear;
139
});
140
141
// generate commands to set each control bus
142
ctlBus.keys.do({ arg name;
143
this.addCommand(name, "f", { arg msg; ctlBus[name].setSynchronous(msg[1]); });
144
});
145
146
postln("polysub: performing init callback");
147
}
148
149
addVoice { arg id, hz, map=true;
150
var params = List.with(\out, context.out_b.index, \hz, hz);
151
var numVoices = voices.size;
152
153
if(voices[id].notNil, {
154
voices[id].set(\gate, 1);
155
voices[id].set(\hz, hz);
156
}, {
157
if(numVoices < maxNumVoices, {
158
ctlBus.keys.do({ arg name;
159
params.add(name);
160
params.add(ctlBus[name].getSynchronous);
161
});
163
voices.add(id -> Synth.new(\polySub, params, gr));
164
NodeWatcher.register(voices[id]);
165
voices[id].onFree({
166
voices.removeAt(id);
167
});
168
169
if(map, {
170
ctlBus.keys.do({ arg name;
171
voices[id].map(name, ctlBus[name]);
172
});
173
});
174
});
175
});
176
}
177
178
removeVoice { arg id;