I created this mixer UI for SuperDirt. My primary goal in developing this mixer was to eliminate the need for using a DAW like Ableton during my live coding sessions. Previously, I had been using Ableton solely for mixing in a live setup, so I wanted to streamline the process.
This mixer makes tonal depth, maintaining clean frequency separation, and creating a rich stereo panorama possible, all of which are easily accessible within SuperDirt. In essence, this mixer replaces the default values of the orbits defaultParentEvents. As a result, it empowers users to adjust parameters like gain, allowing values to range from 0 to 1.5 instead of being limited to 1.0. These modified defaultParentEvent values persist until they are overwritten on the TidalCycles side.
Originally I only planned to make the presets changeable with OSC, but this feature is quite similar to make the values for gain, pan and reverb adjustable as well. It wasn't a big deal, so with the latest version these parts of the ui are mappable now.
(
// Currently the ose listener only listens to the SuperDirt default port 57120
var superDirtOSC = NetAddr.new("127.0.0.1", 57120);
// You can change the preset an
superDirtOSC.sendMsg("/SuperDirtMixer/loadPreset", "Default.csv");
// You can change the gain, pan and reverb values with OSC
// The first value is the orbit number and the second value is the new value that will be set in the orbit defaultParentEvent
superDirtOSC.sendMsg("/SuperDirtMixer/gain", 0, 1.4);
superDirtOSC.sendMsg("/SuperDirtMixer/pan", 0, 0.7);
superDirtOSC.sendMsg("/SuperDirtMixer/reverb", 0, 0.4);
)
And to set mute and solo you can do it the standard way (something like this):
I installed it using Quarks.install("https://github.com/thgrund/SuperDirtMixer").
I was going to try it before putting it in the startup.scd, by running this code (had to increase memory because otherwise it would not start the server):
(
s.options.numBuffers = 16 * s.options.numBuffers;
s.options.memSize = 32 * s.options.memSize;
s.options.maxNodes = 32 * s.options.maxNodes;
s.waitForBoot {
~dirt = SuperDirt(2, s);
~dirt.start(57120, 0 ! 14);
// More SuperDirt ...
// Initialize the SuperDirtMixer
~mixer = SuperDirtMixer(~dirt, 6010);
// You can adjust parameters before you use the ui
~mixer.orbitLabels = ["d1 - Lead", "d2 - Bass", "d3 - Key", "d4 - Pad"];
~mixer.enableMasterPeakRMS(0)
}
)
When running:
~mixer.gui
I get the following error:
ERROR: Message 'synth' not understood.
Perhaps you misspelled 'switch', or meant to call 'synth' on another receiver?
RECEIVER:
Instance of GlobalDirtEffect { (0x55f58d7e24a8, gc=18, fmt=00, flg=00, set=03)
instance variables [7]
name : Symbol 'dirt_global_eq'
paramNames : instance of Array (0x55f58cb582f8, size=1, set=2)
numChannels : Integer 2
state : instance of Event (0x55f58d7e3478, size=5, set=3)
alwaysRun : false
synth : instance of Synth (0x55f58a5c9a78, size=6, set=3)
defName : nil
}
ARGS:
CALL STACK:
DoesNotUnderstandError:reportError
arg this = <instance of DoesNotUnderstandError>
Nil:handleError
arg this = nil
arg error = <instance of DoesNotUnderstandError>
Thread:handleError
arg this = <instance of Thread>
arg error = <instance of DoesNotUnderstandError>
Object:throw
arg this = <instance of DoesNotUnderstandError>
Object:doesNotUnderstand
arg this = <instance of GlobalDirtEffect>
arg selector = 'synth'
arg args = [*0]
SuperDirtMixer:gui
arg this = <instance of SuperDirtMixer>
var window = nil
var v = nil
var composite = <instance of CompositeView>
var freqScope = <instance of FreqScopeView>
var orbitUIElements = nil
var masterFunc = nil
var meterResp = nil
var masterOutResp = nil
var equiView = nil
var setEQuiValues = nil
var activeOrbit = <instance of DirtOrbit>
var presetFile = 'Default.csv'
var orbitLevelIndicators = [*0]
var panKnobs = [*0]
var panNumBoxs = [*0]
var gainSliders = [*0]
var gainNumBoxs = [*0]
var reverbKnobs = [*0]
var eqButtons = <instance of List>
var orbitMixerViews = [*0]
var setOrbitEQValues = nil
var leftMasterIndicator = <instance of LevelIndicator>
var rightMasterIndicator = <instance of LevelIndicator>
Interpreter:interpretPrintCmdLine
arg this = <instance of Interpreter>
var res = nil
var func = <instance of Function>
var code = "~mixer.gui"
var doc = <instance of ScelDocument>
var ideClass = nil
Process:interpretPrintCmdLine
arg this = <instance of Main>
^^ ERROR: Message 'synth' not understood.
Perhaps you misspelled 'switch', or meant to call 'synth' on another receiver?
RECEIVER: GlobalDirtEffect('dirt_global_eq', [ 'reverbWet' ])
Hey @loopier! Thanks for trying out the SuperDirtMixer!
When everything works fine, then you should receive these three messages:
---- initialize SuperDirtMixer ----
loading synthdefs in /Users/mrreason/Development/SuperCollider/SuperDirtMixer/classes/../synths/synth.scd
SuperDirtMixer was successfully initialized
I assume you do not see the loading synthdefs line in your console?
And what happens if you try to execute the SynthDef definition in the synths folder manually?
Or so to say, are you able to run these line of codes?
(
var numChannels = ~dirt.numChannels;
SynthDef("dirt_global_eq2", { |out, dryBus, gate = 1, reverbWet|
var sound;
var in = In.ar(dryBus, ~dirt.numChannels);
//ReplaceOut.ar(dryBus, in * (1 - gobalCutoffDry));
// Dry/Wet
// TODO: disable if no value was received
sound = in.equi;
DirtPause.ar(sound, graceTime:4);
ReplaceOut.ar(dryBus, (in * (1 - reverbWet)) + (sound * reverbWet ));
}, [\ir]).add;
)
ERROR: Message 'synth' not understood.
Perhaps you misspelled 'switch', or meant to call 'synth' on another receiver?
RECEIVER:
Instance of GlobalDirtEffect { (0x55b223135458, gc=08, fmt=00, flg=00, set=03)
instance variables [7]
name : Symbol 'dirt_global_eq'
paramNames : instance of Array (0x55b2257c0218, size=1, set=2)
numChannels : Integer 2
state : instance of Event (0x55b224cb7218, size=5, set=3)
alwaysRun : false
synth : instance of Synth (0x55b22313e608, size=6, set=3)
defName : nil
}
ARGS:
CALL STACK:
DoesNotUnderstandError:reportError
arg this = <instance of DoesNotUnderstandError>
Nil:handleError
arg this = nil
arg error = <instance of DoesNotUnderstandError>
Thread:handleError
arg this = <instance of Thread>
arg error = <instance of DoesNotUnderstandError>
Object:throw
arg this = <instance of DoesNotUnderstandError>
Object:doesNotUnderstand
arg this = <instance of GlobalDirtEffect>
arg selector = 'synth'
arg args = [*0]
Interpreter:interpretPrintCmdLine
arg this = <instance of Interpreter>
var res = nil
var func = <instance of Function>
var code = "~dirt.orbits[0].globalEffect..."
var doc = <instance of ScelDocument>
var ideClass = nil
Process:interpretPrintCmdLine
arg this = <instance of Main>
^^ ERROR: Message 'synth' not understood.
Perhaps you misspelled 'switch', or meant to call 'synth' on another receiver?
RECEIVER: GlobalDirtEffect('dirt_global_eq', [ 'reverbWet' ])
the EQui is in the dependency list, but I think it isn't automatically installed – you may give it a try by replacing it with the full URL (I've never tried it, but could work).
you are iterating over all orbits to set the default environment – there is a convenient method to do this: ~dirt.set(name, value, name, value ...). Or, if you have a list of key value pairs: ~dirt.set(*pairs).
And thanks for the comments! I was not aware of the JSONlib quark and it looks really promising. This would make it a lot easier to create presets and make them scalable. And it would help me to fix the defaultParentEvent related DRY pattern issues that I have. I mean in the end I could just save the complete defaultParentEvent as a preset in JSON for each orbit. And the decoding of this would be really simple and straight forward.
For EQui it is not in my dependency list:
But I will give it a try with the git url. This is provided by the SuperCollider documentation so I assume this should work (as you mentioned): Using Quarks | SuperCollider 3.12.2 Help
And I love the set shorthand! Something that I will definitely checkout.
You may run into troubles because there are functions and Buses in the defaultParentEvent that can't be written to json. One could separate these out using a dictionary with a parent (with all "private" stuff in the parent).