diff --git a/thesynth.scd b/thesynth.scd new file mode 100644 index 0000000..83dc658 --- /dev/null +++ b/thesynth.scd @@ -0,0 +1,228 @@ +( +s.meter; +s.freqscope; +s.plotTree; +) + +( +var g0 = Group.new(s); // g0 = main synth group +var g1 = Group.after(g0); // g1 = effect group + +var b0 = Bus.audio(s); +var b1 = Bus.audio(s); + +var makeSlider, makeChoice, makeButton; +var busses = Dictionary.new(0); + +var notes = Array.newClear(128); + +var reverb = nil; +var reverbEnabled = false; + +Window.closeAll; +w = Window("gui", Rect(750, 50, 1200, 800)).front; +w.view.decorator_(FlowLayout(w.bounds, 15@15, 7@7)); + +makeButton = { + var button = Button(w, 50@50) + .states_([ + ["OFF", Color.black, Color.gray], + ["ON", Color.black, Color.green] + ]) + .action_({ |obj| + reverbEnabled = (obj.value == 1); + obj.value.postln; + }); +}; + +makeButton.value(); + +makeChoice = { + arg name, items, initValue = 0; + var view, menu, bus; + + bus = Bus.control(s); + busses.put(name, bus); + + view = CompositeView(w, 200@100); + + menu = PopUpMenu(view, 200@50) + .items_(items) + .action_({ |obj| + obj.value.postln; + bus.set(obj.value); + }); + + StaticText(view, Rect(0, 60, 80, 20)).string_(name); + + menu.valueAction = initValue; +}; + +makeChoice.value("osc1_type", items: [\sin, \tri, \saw, \pulse]); +makeChoice.value("osc1_octave", items: [-2, -1, 0, 1, 2], initValue: 2); +makeChoice.value("osc2_type", items: [\sin, \tri, \saw, \pulse]); +makeChoice.value("osc2_octave", items: [-2, -1, 0, 1, 2], initValue: 2); +makeChoice.value("osc3_type", items: [\sin, \tri, \saw, \pulse]); +makeChoice.value("osc3_octave", items: [-2, -1, 0, 1, 2], initValue: 2); + +makeSlider = { + arg name, min = 0, max = 1, initValue = 0, exponential = false; + var view, slider, numberBox, value, onUpdate, valToSlider, sliderToVal, bus; + + bus = Bus.control(s); + busses.put(name, bus); + bus.set(initValue); + + onUpdate = { |val| + val.postln; + bus.set(val); + }; + + // Transform a number from the range (min, max) to the range (0, 1) + valToSlider = { |val| + if(exponential, val.explin(min, max, 0, 1), val.linlin(min, max, 0, 1)); + }; + + // Transform a number in the range (0, 1) to the range (min, max) + sliderToVal = { |val| + if(exponential, val.linexp(0, 1, min, max), val.linlin(0, 1, min, max)); + }; + + view = CompositeView(w, 120@200); + slider = Slider(view, Rect(0, 0, 30, 120)); + numberBox = NumberBox(view, Rect(0, 125, 80, 25)).clipLo_(min).clipHi_(max).decimals_(4); + + slider.action = { |obj| + var val = sliderToVal.value(obj.value); + numberBox.value_(val); + onUpdate.value(val); + }; + + numberBox.action = { |obj| + var sliderVal = valToSlider.value(obj.value); + slider.value_(sliderVal); + onUpdate.value(obj.value); + }; + + StaticText(view, Rect(0, 150, 80, 20)).string_(name); + + numberBox.valueAction_(initValue); +}; + +makeSlider.value("lfo1_freq", 0, 10, 1); +makeSlider.value("osc1_amp", 0, 1, 0.1); +makeSlider.value("osc2_amp", 0, 1, 0.1); +makeSlider.value("osc3_amp", 0, 1, 0.1); +makeSlider.value("osc1_detune", 0.9, 1.1, 1); +makeSlider.value("osc2_detune", 0.9, 1.1, 1); +makeSlider.value("osc3_detune", 0.9, 1.1, 1); +makeSlider.value("osc1_pw", 0, 1, 0.7); +makeSlider.value("osc2_pw", 0, 1, 0.34); +makeSlider.value("osc3_pw", 0, 1, 0.49); +makeSlider.value("attack", 0, 5, 0.1); +makeSlider.value("decay", 0, 1, 0); +makeSlider.value("sustain", 0, 1, 1); +makeSlider.value("release", 0, 5, 1); +makeSlider.value("filter_cutoff", 220, 15000, 220, exponential: true); +makeSlider.value("filter_res", 0.05, 0.95, 0.5); +makeSlider.value("filter_attack", 0, 5, 0); +makeSlider.value("filter_decay", 0, 1, 0); +makeSlider.value("filter_sustain", 0, 1, 1); +makeSlider.value("filter_release", 0, 5, 5); +makeSlider.value("gain", 0, 1, 0.2); + + +SynthDef.new(\synth, { + arg freq = 440, outbus = 0, gate = 1, amp = 0.5, ffreqBus = 0, rqBus = 0; + var osc1, osc2, osc3, lfo1, sig, env, ffreq, rq, pw1, pw2, pw3, fenv, getOsc, gain; + + lfo1 = SinOsc.kr(In.kr(busses["lfo1_freq"])).range(0.9, 1.1); + + getOsc = { |name| + var detune = In.kr(busses[name ++ "_detune"]); + var amp = In.kr(busses[name ++ "_amp"]); + var pulseWidth = In.kr(busses[name ++ "_pw"]); + var octave = pow(2, In.kr(busses[name ++ "_octave"]) - 2); + + Select.ar(which: In.kr(busses[name ++ "_type"]), array: [ + SinOsc.ar(freq * detune * octave, mul: amp)!2, + LFTri.ar(freq * detune * octave, mul: amp)!2, + VarSaw.ar(freq * detune * octave, width: pulseWidth * lfo1, mul: amp)!2, + Pulse.ar(freq * detune * octave, width: pulseWidth * lfo1, mul: amp)!2 + ]); + }; + + osc1 = getOsc.value("osc1"); + osc2 = getOsc.value("osc2"); + osc3 = getOsc.value("osc3"); + + env = EnvGen.kr(Env.adsr(In.kr(busses["attack"]), In.kr(busses["decay"]), In.kr(busses["sustain"]), In.kr(busses["release"])), gate, doneAction: 2); + + sig = Mix.ar([osc1, osc2, osc3]); + gain = In.kr(busses["gain"]); + sig = sig * env * amp * gain; + + ffreq = In.kr(busses["filter_cutoff"]); + fenv = EnvGen.kr(Env.adsr(In.kr(busses["filter_attack"]), In.kr(busses["filter_decay"]), In.kr(busses["filter_sustain"]), In.kr(busses["filter_release"])), gate, doneAction: 2); + ffreq = ffreq * fenv; + rq = In.kr(busses["filter_res"]); + sig = RLPF.ar(sig, ffreq, rq); + + Out.ar(outbus, sig); + +}).add; + +SynthDef.new(\reverb, { + arg inbus, outbus; + Out.ar(outbus, JPverb.ar(in: In.ar(inbus))); +}).add; + +MIDIdef.noteOn(\noteOn, { + arg vel, nn, chan, src; + + var outbus = if(reverbEnabled, { b0 }, { 0 }); + + [vel, nn, chan, src].postln; + notes[nn] = Synth.new(\synth, [ + \freq, nn.midicps, + \gate, 1, + \outbus, outbus, + ], target: g0); + + if(reverbEnabled && (reverb == nil), { + reverb = Synth.new(\reverb, [ + \inbus, b0, + \outbus, 0 + ], target: g1); + }); +}); + +MIDIdef.noteOff(\noteOff, { + arg vel, nn; + [vel, nn].postln; + notes[nn].set(\gate, 0); + notes[nn] = nil; +}); + +) + +( +MIDIClient.init; +MIDIIn.connectAll; +) + +( +~notes = [30, 34, 37, 42, 46, 49]; + +p = Pdef(\pattern, + Pbind( + \instrument, \synth, + \midinote, Prand(~notes.collect({ |n| n + 12 }), inf), + \dur, 0.25 + ) +).play; + + +) + +p.stop; \ No newline at end of file