This repository has been archived by the owner on Sep 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 51
/
Engine_PolySub.sc
196 lines (156 loc) · 5.01 KB
/
Engine_PolySub.sc
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
196
// a subtractive polysynth engine
Engine_PolySub : CroneEngine {
classvar <polyDef;
classvar <paramDefaults;
classvar <maxNumVoices;
var <ctlBus; // collection of control busses
var <mixBus; // audio bus for mixing synth voices
var <gr; // parent group for voice nodes
var <voices; // collection of voice nodes
*initClass {
maxNumVoices = 16;
StartUp.add {
// a decently versatile subtractive synth voice
polyDef = SynthDef.new(\polySub, {
arg out, gate=1, hz, level=0.2, // the basics
shape=0.0, // base waveshape selection
timbre=0.5, // modulation of waveshape
sub=0.4, // sub-octave sine level
noise = 0.0, // pink noise level (before filter)
cut=8.0, // RLPF cutoff frequency as ratio of fundamental
// amplitude envelope params
ampAtk=0.05, ampDec=0.1, ampSus=1.0, ampRel=1.0, ampCurve= -1.0,
// filter envelope params
cutAtk=0.0, cutDec=0.0, cutSus=1.0, cutRel=1.0,
cutCurve = -1.0, cutEnvAmt=0.0,
fgain=0.0, // filter gain (moogFF model)
detune=0, // linear frequency detuning between channels
width=0.5,// stereo width
hzLag = 0.1;
var osc1, osc2, snd, freq, del, aenv, fenv, deltime;
// TODO: could add control over these lag times if you wanna get crazy
detune = Lag.kr(detune);
shape = Lag.kr(shape);
timbre = Lag.kr(timbre);
fgain = Lag.kr(fgain.min(4.0));
cut = Lag.kr(cut);
width = Lag.kr(width);
detune = detune / 2;
hz = Lag.kr(hz, hzLag);
freq = [hz + detune, hz - detune];
osc1 = VarSaw.ar(freq:freq, width:timbre);
osc2 = Pulse.ar(freq:freq, width:timbre);
// TODO: could add more oscillator types
// FIXME: probably a better way to do this channel selection
snd = [SelectX.ar(shape, [osc1[0], osc2[0]]), SelectX.ar(shape, [osc1[1], osc2[1]])];
snd = snd + ((SinOsc.ar(hz / 2) * sub).dup);
aenv = EnvGen.ar(
Env.adsr(ampAtk, ampDec, ampSus, ampRel, 1.0, ampCurve),
gate, doneAction:2);
fenv = EnvGen.ar(Env.adsr(cutAtk, cutDec, cutSus, cutRel), gate);
cut = SelectX.kr(cutEnvAmt, [cut, cut * fenv]);
cut = cut * hz.min(SampleRate.ir * 0.5);
snd = SelectX.ar(noise, [snd, [PinkNoise.ar, PinkNoise.ar]]);
snd = MoogFF.ar(snd, cut, fgain) * aenv;
Out.ar(out, level * SelectX.ar(width, [Mix.new(snd).dup, snd]));
});
CroneDefs.add(polyDef);
//// FIXME: probably a better way... ehh.
paramDefaults = Dictionary.with(
\level -> -12.dbamp,
\shape -> 0.0,
\timbre -> 0.5,
\noise -> 0.0,
\cut -> 8.0,
\ampAtk -> 0.05, \ampDec -> 0.1, \ampSus -> 1.0, \ampRel -> 1.0, \ampCurve -> -1.0,
\cutAtk -> 0.0, \cutDec -> 0.0, \cutSus -> 1.0, \cutRel -> 1.0,
\cutCurve -> -1.0, \cutEnvAmt -> 0.0,
\fgain -> 0.0,
\detune -> 0,
\width -> 0.5,
\hzLag -> 0.1
);
} // Startup
} // initClass
*new { arg context, callback;
^super.new(context, callback);
}
alloc {
gr = ParGroup.new(context.xg);
voices = Dictionary.new;
ctlBus = Dictionary.new;
polyDef.allControlNames.do({ arg ctl;
var name = ctl.name;
postln("control name: " ++ name);
if((name != \gate) && (name != \hz) && (name != \out), {
ctlBus.add(name -> Bus.control(context.server));
ctlBus[name].set(paramDefaults[name]);
});
});
ctlBus.postln;
ctlBus[\level].setSynchronous( 0.2 );
//--------------
//--- voice control, all are indexed by arbitarry ID number
// (voice allocation should be performed by caller)
// start a new voice
this.addCommand(\start, "if", { arg msg;
this.addVoice(msg[1], msg[2], true);
});
// same as start, but don't map control busses, just copy their current values
this.addCommand(\solo, "i", { arg msg;
this.addVoice(msg[1], msg[2], false);
});
// stop a voice
this.addCommand(\stop, "i", { arg msg;
this.removeVoice(msg[1]);
});
// free all synths
this.addCommand(\stopAll, "", {
gr.set(\gate, 0);
voices.clear;
});
// generate commands to set each control bus
ctlBus.keys.do({ arg name;
this.addCommand(name, "f", { arg msg; ctlBus[name].setSynchronous(msg[1]); });
});
postln("polysub: performing init callback");
}
addVoice { arg id, hz, map=true;
var params = List.with(\out, context.out_b.index, \hz, hz);
var numVoices = voices.size;
//postln("num voices: " ++ numVoices);
if(voices[id].notNil, {
voices[id].set(\gate, 1);
voices[id].set(\hz, hz);
}, {
if(numVoices < maxNumVoices, {
// shouldn't need this
// this.removeVoice(id);
ctlBus.keys.do({ arg name;
params.add(name);
params.add(ctlBus[name].getSynchronous);
});
voices.add(id -> Synth.new(\polySub, params, gr));
NodeWatcher.register(voices[id]);
voices[id].onFree({
voices.removeAt(id);
});
if(map, {
ctlBus.keys.do({ arg name;
voices[id].map(name, ctlBus[name]);
});
});
});
});
}
removeVoice { arg id;
if(true, { //voices[id].notNil, {
voices[id].set(\gate, 0);
//voices.removeAt(id);
});
}
free {
gr.free;
ctlBus.do({ arg bus, i; bus.free; });
}
} // class