Last active
January 21, 2020 04:48
-
-
Save catfact/2e47684e028b5b1b8a50e75dd6253089 to your computer and use it in GitHub Desktop.
solinish
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //------------------ | |
| //-- 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