solinish
| // 8-voice paraphonic synth, | |
| // _very loosely_ inspired by ARP Solina architecture | |
| Routine { | |
| // first element is an oscillator. | |
| // classically this is a sawtooth, with some nonlinear lowpass filtering | |
| // we'll expand on it with: | |
| // - triangle->saw width control | |
| // - sine and pulse options | |
| // - random width and hz modulation | |
| SynthDef.new(\slsh_osc, { | |
| arg out=0, amp=0.25, | |
| hz=220, cutoff=10000, w=0, z=0, | |
| hz_drift_amt = 0, | |
| hz_drift_rate = 1.0, | |
| width_drift_amt = 0, | |
| width_drift_rate = 1.0; | |
| var f, w0, w1, waves, snd; | |
| f = hz * (1 + LFNoise2.ar(hz_drift_rate, mul:hz_drift_amt)); | |
| f = Lag.ar(f, 0.005); | |
| w0 = w*pi*0.5 * ( 1 + LFNoise2.ar(width_drift_rate, mul:width_drift_amt)); | |
| w1 = ((w*0.49) + 0.5) * (1 + LFNoise2.ar(width_drift_rate, mul:width_drift_amt)); | |
| waves =[ | |
| SinOscFB.ar(f, w0), | |
| VarSaw.ar(f, 0, w1), | |
| Pulse.ar(f, w1) | |
| ]; | |
| snd = Select.ar(z, waves); | |
| Out.ar(out, snd * amp); | |
| }).send(s); | |
| // filter stage | |
| // classically, a smidge of exponential lag. | |
| // we'll expand by adding resonant highpass+lowpass | |
| SynthDef.new(\slsh_filter, { | |
| arg in=0, out=0, | |
| lag_time=0.0001, | |
| hpf_hz=20, hpf_rq = 1, | |
| lpf_hz=10000, lpf_rq = 1; | |
| var snd; | |
| snd = In.ar(in); | |
| snd = Lag.ar(snd, lag_time); | |
| snd = RLPF.ar(snd, lpf_hz, lpf_rq); | |
| snd = RHPF.ar(snd, hpf_hz, hpf_rq); | |
| ReplaceOut.ar(out, snd); | |
| }).send(s); | |
| // the "ensemble" effect is basically just a few choruses in parallel | |
| // each chorus is an interpolated delay line with feedback and time modulation. | |
| // we'll expand on it by adding: | |
| // - random modulation, | |
| // - a resonant filter in the feedback path (TODO) | |
| // - panning per chorus | |
| // other TODOs: | |
| // - some kinda saturation to emulate BBD | |
| // - LFO waveshape options | |
| SynthDef.new(\slsh_chorus, { | |
| arg in=0, out=0, dry=0.5, wet=0.5, pan=0, | |
| time=0.027, mod_rate=0.2, mod_depth=0.001, fb=0.1, | |
| maxdelaytime=1.0; | |
| var snd, t, del; | |
| snd = In.ar(in); | |
| t = time * (1 + SinOsc.ar(mod_rate, mul:mod_depth)); | |
| del = DelayC.ar(snd + LocalIn.ar, maxdelaytime, t); | |
| LocalOut.ar(del * fb); | |
| Out.ar(out, Pan2.ar(Mix.new([dry*snd, wet*del]), pan)); | |
| }).send(s); | |
| s.sync; | |
| // all voices are summed to a mono bus, | |
| // then 3 choruses are applied in stereo | |
| ~voice_mix_bus = Bus.audio(s, 1); | |
| ~chorus_group = Group.new(s, \addToTail); | |
| ~chorus= [ | |
| // chorus 1 | |
| Synth.new(\slsh_chorus, [ | |
| \in, ~voice_mix_bus.index, \out, 0, \dry, 0.5, \wet, 0.5, | |
| \pan, -0.5, \time, 1/6, \mod_rate, 1/5, \mod_depth, 1/8, \fb, -24.dbamp | |
| ], target:~chorus_group), | |
| // chorus 2 | |
| Synth.new(\slsh_chorus, [ | |
| \in, ~voice_mix_bus.index, \out, 0, \dry, 0.5, \wet, 0.5, | |
| \pan, 0, \time, 1/7, \mod_rate, 1/6, \mod_depth, 1/8, \fb, -24.dbamp | |
| ], target:~chorus_group), | |
| // chorus 3 | |
| Synth.new(\slsh_chorus, [ | |
| \in, ~voice_mix_bus.index, \out, 0, \dry, 0.5, \wet, 0.5, | |
| \pan, 0.5, \time, 1/8, \mod_rate, 1/7, \mod_depth, 1/8, \fb, -24.dbamp | |
| ], target:~chorus_group), | |
| ]; | |
| /// each slsh voice will have: | |
| // - a group | |
| // - a mono output bus | |
| // - two oscillator synths | |
| // - one multi-filter synth | |
| // - amplitude envelope synth | |
| // voices are created statically! | |
| // | |
| SynthDef.new(\slsh_amp_env, { | |
| arg in=0, out=0, | |
| gate=0, amp=0.1, | |
| atk=0.1, dec=0.1, sus=1.0, rel=0.1; | |
| var aenv; | |
| aenv = EnvGen.ar(Env.adsr(atk, dec, sus, rel), gate); | |
| Out.ar(out, In.ar(in) * aenv * amp); | |
| }).send(s); | |
| s.sync; | |
| n = 8; | |
| // NB: busses are not stored in the voice object, | |
| // just in case we want to switch to dynamic synth allocation | |
| // (busses would remain static in that case i think) | |
| ~voice_bus = Array.fill(n, {Bus.audio(s, 1)}); | |
| ~voices = Array.newClear(n); | |
| ~voice_group = Group.new(s, \addToHead); | |
| ~make_voice = { arg idx; | |
| var bidx = ~voice_bus[idx].index; | |
| var v; | |
| v = (); | |
| v.gr = Group.new(~voice_group); | |
| // TODO: use control busses for synth params, &c | |
| v.osc1 = Synth.new(\slsh_osc, [\out, bidx, \hz, 100], v.gr, \addToTail); | |
| v.osc2 = Synth.new(\slsh_osc, [\out, bidx, \hz, 150], v.gr, \addToTail); | |
| v.filter = Synth.new(\slsh_filter, [\in, bidx, \out, bidx], v.gr, \addToTail); | |
| v.aenv = Synth.new(\slsh_amp_env, [\in, bidx, \out, ~voice_mix_bus.index], v.gr, \addToTail); | |
| ~voices[idx] = v; | |
| }; | |
| ~free_voice = { arg idx; ~voices[idx].group.free; }; | |
| // create the voices | |
| n.do({ | |
| arg i; | |
| i.postln; | |
| ~make_voice.value(i); | |
| }); | |
| }.play; |
| //------------------ | |
| //-- execute this chunk... | |
| // set hz array | |
| ( | |
| ~v_hz = { arg idx, hz=[110, 165]; | |
| ~voices[idx].osc1.set(\hz, hz[0]); | |
| ~voices[idx].osc2.set(\hz, hz[1]); | |
| }; | |
| ~voices.do({ arg x, i; [i, x].postln; }); | |
| ~voice_bus.do({ arg x, i; [i, x].postln; }); | |
| ~v_on = { arg idx; ~voices[idx].aenv.set(\gate, 1); }; | |
| ~v_off = { arg idx; ~voices[idx].aenv.set(\gate, 0); }; | |
| ) | |
| //----------------------------------- | |
| //------------------------ | |
| //-- ...then these lines | |
| ~v_on.value(0); // turn on | |
| ~v_hz.value(0, [110, 220]); // 2 oscs in octaves | |
| ~voices[0].osc1.set(\z, 1); // low osc to saw | |
| ~voices[0].osc2.set(\z, 2); // high osc to pulse | |
| ~voices[0].filter.set(\lpf_hz, 880 * 2); // close the LPF a bit.. | |
| ~voices[0].filter.set(\lpf_rq, 0.5); // ..and give it a little resonance | |
| // voice 2 | |
| ~v_on.value(1); | |
| ~v_hz.value(1, [220 * 2, 330 * 4]); // 2 oscs in 5th+1oct | |
| ~voices[1].osc1.set(\w, 0.5); // ... add some feedback | |
| ~voices[1].filter.set(\hpf_hz, 570); // close the HPF a bit... | |
| ~voices[1].filter.set(\hpf_rq, 0.25); // ..and give it a little resonance | |
| ~v_off.value(0); | |
| ~v_off.value(1); | |
| /// make the choruses fast and shallow, instead of fat and slow | |
| /// also hardpan, bigger base delay, no feedback | |
| ~chorus[0].set(\mod_depth, 1/256); | |
| ~chorus[0].set(\mod_rate, 4); | |
| ~chorus[0].set(\time, 1/6); | |
| ~chorus[0].set(\fb, 0); | |
| ~chorus[0].set(\pan, -1); | |
| ~chorus[1].set(\mod_depth, 1/256); | |
| ~chorus[1].set(\mod_rate, 5); | |
| ~chorus[1].set(\time, 1/7); | |
| ~chorus[1].set(\fb, 0); | |
| ~chorus[1].set(\pan, 0); | |
| ~chorus[2].set(\mod_depth, 1/256); | |
| ~chorus[2].set(\mod_rate, 6); | |
| ~chorus[2].set(\time, 1/5); | |
| ~chorus[2].set(\fb, 0); | |
| ~chorus[2].set(\pan, 1); | |
| /* | |
| ~voice_bus[1].scope; | |
| s.scope; | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment