Last active
February 8, 2026 21:55
-
-
Save caseyanderson/1c1aea83762b0801830834fc2ef88eec to your computer and use it in GitHub Desktop.
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
| /* Deterministic Synth Control | |
| Synth.new with Busses | |
| */ | |
| ( | |
| SynthDef(\sin, { |amp = 0.5, end = 2, freq = 0.5, len = 5, max = 0.7, maxdelay = 1, min = 0.2, out = 0, start = 1, trig = 1| | |
| var freqHz = freq.linlin(0,1, 50.0, 500.0); | |
| var ampScaled = if(amp < 0.1, 0.0, amp.lincurve(0,1, 0.0, 1.0, -4)); | |
| var env = EnvGen.kr(Env.perc(len * 0.25, len * 0.75), trig, doneAction:2); | |
| var line = Line.kr(start, end, len, doneAction:2); | |
| var sig = SinOsc.ar(freqHz * DelayN.kr(line, maxdelay, Rand(min, max)), 0, ampScaled) * env; | |
| Out.ar(out, Pan2.ar(sig * 0.25)); | |
| }).add; | |
| SynthDef(\formant, { |amp = 0.5, end = 2, freq = 0.5, len = 5, max = 0.7, maxdelay = 1, min = 0.2, out = 0, start = 1, trig = 1| | |
| var freqHz = freq.linlin(0,1, 50.0, 500.0); | |
| var ampScaled = if(amp < 0.1, 0.0, amp.lincurve(0,1, 0.0, 1.0, -4)); | |
| var env = EnvGen.kr(Env.perc(len * 0.25, len * 0.75), trig, doneAction:2); | |
| var line = XLine.kr(start, end, len, doneAction:2); | |
| var sig = Formant.ar( | |
| freqHz * DelayN.kr(line, maxdelay, Rand(min, max)), | |
| freqHz * DelayN.kr(line, maxdelay, Rand(min, max)), | |
| freqHz * DelayN.kr(line, maxdelay, Rand(min, max)), | |
| ampScaled | |
| ) * env; | |
| var clean = HPF.ar(sig, 50); | |
| Out.ar(out, Pan2.ar(clean * 0.25)); | |
| }).add; | |
| ) | |
| // OSCFunc | |
| ( | |
| OSCFunc({ |msg| | |
| var idx = msg[1]; | |
| var val = msg[2]; | |
| if(val == 1) { | |
| switch(idx, | |
| 0, { | |
| var currentFreq = ~twisterBusses[4].getSynchronous; | |
| var currentAmp = ~twisterBusses[0].getSynchronous; | |
| var synth; | |
| synth = Synth(\sin, [ | |
| \out, 0, | |
| \start, rrand(2,13), | |
| \end, rrand(2,13), | |
| \len, rrand(20,50), | |
| \trig, 1, | |
| \freq, currentFreq, | |
| \amp, currentAmp | |
| ]); | |
| synth.map(\freq, ~twisterBusses[4]); | |
| synth.map(\amp, ~twisterBusses[0]); | |
| }, | |
| 1, { | |
| var currentFreq = ~twisterBusses[5].getSynchronous; | |
| var currentAmp = ~twisterBusses[1].getSynchronous; | |
| var synth; | |
| synth = Synth(\formant, [ | |
| \out, 2, | |
| \start, rrand(2,17), | |
| \end, rrand(2,17), | |
| \len, rrand(20,50), | |
| \trig, 1, | |
| \freq, currentFreq, | |
| \amp, currentAmp | |
| ]); | |
| synth.map(\freq, ~twisterBusses[5]); | |
| synth.map(\amp, ~twisterBusses[1]); | |
| }, | |
| 2, { | |
| var currentFreq = ~twisterBusses[6].getSynchronous; | |
| var currentAmp = ~twisterBusses[2].getSynchronous; | |
| var synth; | |
| synth = Synth(\formant, [ | |
| \out, 4, | |
| \start, rrand(2,17), | |
| \end, rrand(2,17), | |
| \len, rrand(20,50), | |
| \trig, 1, | |
| \freq, currentFreq, | |
| \amp, currentAmp | |
| ]); | |
| synth.map(\freq, ~twisterBusses[6]); | |
| synth.map(\amp, ~twisterBusses[2]); | |
| } | |
| ); | |
| }; | |
| }, '/buttonControl').add; | |
| ) |
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
| /* SUS SOUNDS | |
| */ | |
| ( | |
| ~reaper = NetAddr( "127.0.0.1", 8000 ); | |
| // ~synthControl = NetAddr( "127.0.0.1", NetAddr.langPort ); // generic OSC responder for internal Synths | |
| ) | |
| // low wind | |
| // ready for tuning | |
| ( | |
| SynthDef( \staticWind, { | amp = 0.0, mFreq = 0.1, fFreq = 1348, fRq = 0.5, trig = 0| | |
| var env, src, mod; | |
| env = EnvGen.kr( Env.adsr( 4.0, 1.0, 0.95, 4.0), trig ); | |
| mod = Mix.fill( 6, { LFNoise2.ar( mFreq ).range( 0.0, 1.0 )}); | |
| src = PinkNoise.ar( mod.lagud( 1, 4 ) ); | |
| src = RLPF.ar( src, fFreq.lag( 1 ), fRq ) * env * amp; | |
| Out.ar(0, Pan2.ar( src ) ); | |
| }).add; | |
| // swirly wind, ready for tuning | |
| SynthDef( \swirlyWind, { | amp = 0, maxFreq = 2e4, maxRQ = 1.5, minFreq = 20, minRQ = 0.01, trig = 0 | | |
| var env, freq, gen, in, mul, noises, rq, sig; | |
| env = EnvGen.kr( Env.adsr( 4.0, 2.0, 0.95, 4.0 ), trig ); | |
| gen = { rrand( 1,10 ).reciprocal }; // 1, 10 | |
| noises = [ BrownNoise.ar, WhiteNoise.ar, PinkNoise.ar ]; | |
| in = SelectX.ar( Array.fill(4, { LFNoise2.kr( gen.value ) } ).exprange( 0.02, 2 ), noises).mean; | |
| freq = Array.fill(8, { LFNoise2.ar( gen.value ) }).exprange( minFreq, maxFreq ); | |
| rq = Array.fill(8, { LFNoise2.ar( gen.value ) }).exprange(minRQ, maxRQ); // 0.1, 2.0 | |
| mul = Array.fill(8, { LFNoise2.ar( gen.value ) }).range(0.1, 0.95); | |
| sig = BPF.ar(in, freq, rq, mul).clump(2).mean * amp; | |
| Out.ar(2, sig * env); | |
| }).add; | |
| ) | |
| ///////// With OSC messages | |
| ( | |
| // run the synths | |
| ~staticWind = Synth( \staticWind, [ \amp, 0.0, \fFreq, 100, \fRq, 0.5, \mFreq, 0.1, \trig, 0 ] ); | |
| ~swirlyWind = Synth( \swirlyWind, [ \amp, 0.0, \maxFreq, 2e4, \maxRQ, 1.5, \minRQ, 0.01, \trig, 0 ] ); | |
| ) | |
| ///// | |
| ( | |
| OSCFunc( { | msg | | |
| //msg.postln; | |
| switch( msg[1], | |
| // staticWind controls | |
| 0, { ~staticWind.set( \amp, msg[2] ) }, | |
| 4, { ~staticWind.set( \mFreq, msg[2].linlin( 0.0, 1.0, 0.1, 100.0 ) ) }, | |
| 8, { ~staticWind.set( \fFreq, msg[2].linlin( 0.0, 1.0, 50, 2000 ) ) }, | |
| 12,{ ~staticWind.set( \fRq, msg[2].linlin( 0.0, 1.0, 0.1, 1.0 ) ) }, | |
| // swirlyWind controls | |
| 1, { ~swirlyWind.set( \amp, msg[2] ) }, | |
| 5, { ~swirlyWind.set( \maxFreq, msg[2].linlin( 0.0, 1.0, 1000, 1e4 ) ) }, | |
| 9, { ~swirlyWind.set( \maxRQ, msg[2].linlin( 0.0, 1.0, 0.55, 1.5 ) ) }, | |
| 13,{ ~swirlyWind.set( \minRQ, msg[2].linlin( 0.0, 1.0, 0.01, 0.45 ) ) } | |
| ); | |
| }, '/knobControl'); | |
| OSCFunc( { | msg | | |
| //msg.postln; | |
| switch( msg[1], | |
| 0, { ~staticWind.set( \trig, msg[2] ) }, | |
| 1, { ~swirlyWind.set( \trig, msg[2] ) } | |
| ) | |
| }, '/buttonControl'); | |
| ) | |
| //////////////////////////////////////////////////// | |
| x = Synth( \staticWind, [ \mFreq, 0.1, \fFreq, 100, \fRq, 0.5 ] ); | |
| x.set( \trig, 1, \amp, 0.2 ); | |
| x.set( \trig, 0 ); | |
| x.set( \fFreq, 1000 ); | |
| x.set( \fRq, 0.5 ); | |
| x.set( \amp, 0.3 ); | |
| y = Synth( \swirlyWind, [ \trig, 1, \amp, 0.5 ] ); | |
| y.set( \trig, 0 ); | |
| y.set( \amp, 0.4 ); | |
| y.set( \maxFreq, 500 ); | |
| y.set( \minRQ, 0.1 ); |
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
| /* | |
| MIDI Fighter Twister + Spectra Control Panel | |
| Casey Anderson | |
| Twister: CC 0-15, Channel 1 (chan=0) | |
| Spectra: CC 36-51, Channel 4 (chan=3) | |
| TODO: | |
| Test Control Bus mapping in other files (should allow for patterns) | |
| */ | |
| ( | |
| Server.killAll; | |
| s = Server.default; | |
| s.options.device = "ASIO : Voicemeeter Virtual ASIO"; | |
| s.options.numOutputBusChannels = 8; | |
| s.options.sampleRate = 48000; | |
| s.options.blockSize = 256; | |
| s.options.hardwareBufferSize = 256; | |
| s.options.memSize = 65536; | |
| s.boot; | |
| s.waitForBoot({ | |
| // ┌─────────────────────────────────────────────────────────────────────────────┐ | |
| // │ CONSTANTS & STATE │ | |
| // └─────────────────────────────────────────────────────────────────────────────┘ | |
| ~synthControl = NetAddr("127.0.0.1", NetAddr.langPort); | |
| ~knobs = (0..15); | |
| ~buttons = (0..15); | |
| ~ccToButton = Dictionary[ | |
| 48 -> 0, 49 -> 1, 50 -> 2, 51 -> 3, | |
| 44 -> 4, 45 -> 5, 46 -> 6, 47 -> 7, | |
| 40 -> 8, 41 -> 9, 42 -> 10, 43 -> 11, | |
| 36 -> 12, 37 -> 13, 38 -> 14, 39 -> 15 | |
| ]; | |
| ~twisterLastValue = Array.fill(16, -1); | |
| ~twisterLastBusValue = Array.fill(16, 0.0); | |
| ~twisterLastSetTime = Array.fill(16, -999999.0); | |
| ~spectraLastValue = Array.fill(16, -1); | |
| // Declare data dictionaries early so they exist when helpers use them | |
| ~twisterData = Dictionary.new; | |
| ~spectraData = Dictionary.new; | |
| // ┌─────────────────────────────────────────────────────────────────────────────┐ | |
| // │ CONTROL BUSSES │ | |
| // └─────────────────────────────────────────────────────────────────────────────┘ | |
| ~twisterBusses = ~knobs.collect { Bus.control(s, 1).set(0.0) }; | |
| ~spectraBusses = ~buttons.collect { Bus.control(s, 1).set(0.0) }; | |
| // ┌─────────────────────────────────────────────────────────────────────────────┐ | |
| // │ GUI LAYOUT HELPERS │ | |
| // └─────────────────────────────────────────────────────────────────────────────┘ | |
| ~twisterLayout = { |knobNum| | |
| var statusButton = Button() | |
| .minSize_(80@30) | |
| .states_([ | |
| ["KNOB " ++ knobNum ++ " Off", Color.black, Color.gray], | |
| ["KNOB " ++ knobNum ++ " On", Color.white, Color.red] | |
| ]) | |
| .value_(0); | |
| var knob = Knob() | |
| .minSize_(80@80) | |
| .value_(0.0); | |
| var knobValDisplay = NumberBox() | |
| .minSize_(80@30) | |
| .align_(\center) | |
| .value_(0.0) | |
| .clipLo_(0.0) | |
| .clipHi_(1.0) | |
| .decimals_(3); | |
| var view = View() | |
| .background_(Color.gray(0.9)) | |
| .layout_( | |
| VLayout( | |
| statusButton, | |
| knob, | |
| knobValDisplay | |
| ).spacing_(5).margins_([10,5,10,5]) | |
| ); | |
| IdentityDictionary[ | |
| \layout -> view, | |
| \button -> statusButton, | |
| \knob -> knob, | |
| \number2 -> knobValDisplay | |
| ]; | |
| }; | |
| ~spectraLayout = { |buttonNum| | |
| var statusButton = Button() | |
| .minSize_(80@30) | |
| .states_([ | |
| ["BUTTON " ++ buttonNum ++ " Off", Color.black, Color.gray], | |
| ["BUTTON " ++ buttonNum ++ " On", Color.white, Color.red] | |
| ]) | |
| .value_(0); | |
| var button = Button() | |
| .minSize_(80@80) | |
| .states_([ | |
| ["OFF", Color.black, Color.gray], | |
| ["ON", Color.white, Color.red] | |
| ]) | |
| .value_(0); | |
| var view = View() | |
| .background_(Color.gray(0.9)) | |
| .layout_( | |
| VLayout( | |
| statusButton, | |
| button | |
| ).spacing_(5).margins_([10,5,10,5]) | |
| ); | |
| IdentityDictionary[ | |
| \layout -> view, | |
| \button1 -> statusButton, | |
| \button2 -> button | |
| ]; | |
| }; | |
| // ┌─────────────────────────────────────────────────────────────────────────────┐ | |
| // │ GUI CREATION & LAYOUT │ | |
| // └─────────────────────────────────────────────────────────────────────────────┘ | |
| ~makeCombinedPanel = { | |
| var twisterStrips, spectraStrips; | |
| twisterStrips = ~knobs.collect(~twisterLayout.(_)); | |
| spectraStrips = ~buttons.collect(~spectraLayout.(_)); | |
| if(~combinedPanel.notNil, { ~combinedPanel.close }); | |
| ~twisterData[\twisterStrips] = twisterStrips; | |
| ~spectraData[\spectraStrips] = spectraStrips; | |
| ~combinedPanel = Window.new("Twister + Spectra Control Panel", Rect(210.0, 65.0, 892.0, 692.0)) | |
| .alwaysOnTop_(true) | |
| .layout_( | |
| HLayout( | |
| GridLayout.rows( | |
| *twisterStrips.clump(4).collect { |row| | |
| row.collect { |strip| strip[\layout] } | |
| } | |
| ).hSpacing_(10).vSpacing_(10), | |
| GridLayout.rows( | |
| *spectraStrips.clump(4).collect { |row| | |
| row.collect { |strip| strip[\layout] } | |
| } | |
| ).hSpacing_(10).vSpacing_(10) | |
| ).spacing_(10) | |
| ) | |
| .front; | |
| }; | |
| ~makeCombinedPanel.value; | |
| // ┌─────────────────────────────────────────────────────────────────────────────┐ | |
| // │ KNOB ACTION LOGIC │ | |
| // └─────────────────────────────────────────────────────────────────────────────┘ | |
| ~knobs.do { |num| | |
| var strip = ~twisterData[\twisterStrips][num]; | |
| if(strip.notNil && strip[\knob].notNil, { | |
| strip[\knob].action_({ |obj| | |
| strip[\number2].value_(obj.value); | |
| if(strip[\button].value == 1, { | |
| var val = obj.value; | |
| var now = thisThread.seconds; | |
| var lastVal = ~twisterLastBusValue[num] ? 0.0; | |
| var delta = (val - lastVal).abs; | |
| var lastTime = ~twisterLastSetTime[num] ? -999999.0; | |
| if(delta > 0.015 && (now - lastTime > 0.02), { | |
| var finalVal = val; | |
| ~twisterBusses[num].set(finalVal); | |
| ~twisterLastBusValue[num] = finalVal; | |
| ~twisterLastSetTime[num] = now; | |
| }); | |
| if(val < 0.04, { | |
| ~twisterBusses[num].set(0.0); | |
| ~twisterLastBusValue[num] = 0.0; | |
| ~twisterLastSetTime[num] = now; | |
| }); | |
| }); | |
| }); | |
| }); | |
| }; | |
| // ┌─────────────────────────────────────────────────────────────────────────────┐ | |
| // │ MIDI CLIENT & RESPONDERS │ | |
| // └─────────────────────────────────────────────────────────────────────────────┘ | |
| MIDIClient.init; | |
| MIDIIn.connectAll; | |
| ~twisterUID = MIDIClient.sources.detect { |src| src.name.contains("Twister") }.tryPerform(\uid); | |
| ~spectraUID = MIDIClient.sources.detect { |src| src.name.contains("Spectra") }.tryPerform(\uid); | |
| if(~twisterUID.notNil, { | |
| ~knobs.do { |i| | |
| MIDIFunc.cc({ |val, cc, chan, src| | |
| if(chan == 0 && cc == i, { | |
| var mappedVal = val.linlin(0,127,0.0,1.0); | |
| if(~twisterLastValue[i].isNil || mappedVal != ~twisterLastValue[i], { | |
| ~twisterLastValue[i] = mappedVal; | |
| { | |
| var strip = ~twisterData[\twisterStrips][i]; | |
| if(strip.notNil && strip[\button].value == 1, { | |
| strip[\knob].valueAction_(mappedVal); | |
| ~synthControl.sendMsg('/knobControl', i, mappedVal); | |
| }); | |
| }.defer; | |
| }); | |
| }); | |
| }, i, 0, ~twisterUID); | |
| }; | |
| }); | |
| if(~spectraUID.notNil, { | |
| MIDIFunc.cc({ |val, cc, chan, src| | |
| var idx = ~ccToButton[cc]; | |
| if(idx.notNil && chan == 3, { | |
| var mappedVal = if(val > 0, 1, 0); | |
| if(~spectraLastValue[idx].isNil || mappedVal != ~spectraLastValue[idx], { | |
| ~spectraLastValue[idx] = mappedVal; | |
| AppClock.sched(0, { | |
| var strip = ~spectraData[\spectraStrips][idx]; | |
| if(strip.notNil && strip[\button1].value == 1, { | |
| strip[\button2].valueAction_(mappedVal); | |
| ~synthControl.sendMsg('/buttonControl', idx, mappedVal); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }, nil, 3, ~spectraUID); | |
| }); | |
| // ┌─────────────────────────────────────────────────────────────────────────────┐ | |
| // │ CLEANUP │ | |
| // └─────────────────────────────────────────────────────────────────────────────┘ | |
| ~combinedPanel.onClose_({ | |
| ~twisterBusses.do(_.free); | |
| ~spectraBusses.do(_.free); | |
| MIDIIn.disconnectAll; | |
| }); | |
| }); | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment