dynamically loading and unloading plugins from tidal in the same way superdirt effects get applied is not something i would recommend under any circumstances. there's an enormous overhead to instantiating a VST plugin instance compared to instantiating a predefined synth in SC. simply put, it takes a while for a plugin to load, let alone load any kind of preset/state, and in the context of music even a few dozens of milliseconds are simply too long. the more sensible option would be to predefine a signal path via SynthDefs that contain "slots" for VST plugins in the form of VSTPlugin.ar
ugens, and then load up a few plugins we like.
regarding sending MIDI and changing parameters from tidalcycles, you can check out this startup file - it's a stripped down version of what i'm currently using to control VST plugins with tidal. it's also possible to layer the sounds up with superdirt, but it's not a full superdirt implementation of VST plugins, so you can't apply djf
or squiz
to your vst instruments in this way. however, other people like @mrreason have done work on how to achieve that, so it's definetly possible!
(
s.reboot { // server options are only updated on reboot
// configure the sound server: here you could add hardware specific options
// see http://doc.sccode.org/Classes/ServerOptions.html
s.options.numBuffers = 1024 * 256; // increase this if you need to load more samples
s.options.memSize = 8192 * 32; // increase this if you get "alloc failed" messages
s.options.numWireBufs = 64; // increase this if you get "exceeded number of interconnect buffers" messages
s.options.maxNodes = 1024*32; // increase this if you are getting drop outs and the message "too many nodes"
s.options.numOutputBusChannels = 2; // set this to your hardware output channel size, if necessary
s.options.numInputBusChannels = 10; // set this to your hardware output channel size, if necessary
s.options.maxLogins = 8; // optional, required to enable feedforward VU meter
// boot the server and start SuperDirt
s.waitForBoot {
~dirt = SuperDirt(2, s); // two output channels, increase if you want to pan across more channels
~dirt.doNotReadYet = true; // load samples on demand, memory saving
//~dirt.loadSoundFiles; // load samples (path containing a wildcard can be passed in)
// for example: ~dirt.loadSoundFiles("/Users/myUserName/Dirt/samples/*");
// s.sync; // optionally: wait for samples to be read
~dirt.start(57120, 0 ! 12); // start listening on port 57120, create two busses each sending audio to channel 0
// optional, enable feedforward vu meter
~dirt.startSendRMS;
// optional, needed for convenient access from sclang:
(
~d1 = ~dirt.orbits[0]; ~d2 = ~dirt.orbits[1]; ~d3 = ~dirt.orbits[2];
~d4 = ~dirt.orbits[3]; ~d5 = ~dirt.orbits[4]; ~d6 = ~dirt.orbits[5];
~d7 = ~dirt.orbits[6]; ~d8 = ~dirt.orbits[7]; ~d9 = ~dirt.orbits[8];
~d10 = ~dirt.orbits[9]; ~d11 = ~dirt.orbits[10]; ~d12 = ~dirt.orbits[11];
);
// midi
(
MIDIClient.init;
~midithroughport0 = MIDIOut(0);
~midithroughport0.latency = 0.0;
~dirt.soundLibrary.addMIDI(\midi0, ~midithroughport0);
~midithroughport1 = MIDIOut(1);
~midithroughport1.latency = 0.0;
~dirt.soundLibrary.addMIDI(\midi1, ~midithroughport1);
);
(
// VSTPlugin.search(s,['/home/ribon/.wine/drive_c/Program Files/Steinberg/VstPlugins']);
// VSTPlugin.search(s,['/home/ribon/.wine/drive_c/Program Files/Native Instruments/VSTPlugins 64 bit/']);
VSTPlugin.search;
);
(
//INITIALIZE BUSES HERE
~reverbBus = Bus.audio(s,2);
~masterBus = Bus.audio(s,2);
//OPTIONAL: redirect superdirt orbits, would require instantiating buses, adding an InBus.ar to the "voice" synthdef and passing the bus names as arguments.
//~dirt.orbits[0].outBus = ~kickBus;
//~dirt.orbits[1].outBus = ~snareBus;
//~dirt.orbits[2].outBus = ~hatBus;
//~dirt.orbits[3].outBus = ~percBus;
//~dirt.orbits[4].outBus = ~bassBus;
);
(
s.sync;
SynthDef("MasterBus",{
var sound;
sound = InBus.ar(~masterBus,2);
sound = VSTPlugin.ar(sound,2,id:\masterchain);
sound = sound.tanh; // TANH ON THE MASTER YEEEEAAAAAHHH
Out.ar(0,sound);
}).add;
SynthDef("ReverbBus",{
var sound;
sound = InBus.ar(~reverbBus,2);
sound = VSTPlugin.ar(sound,2,id:\reverb);
Out.ar(~masterBus,sound);
}).add;
);
(
SynthDef("voice",{ | out, level = 1, sendAmount = 0 |
var sound, chain;
sound = SoundIn.ar(0).dup;
sound = sound + VSTPlugin.ar(nil,2,id:\generator);
sound = VSTPlugin.ar(sound,2,id:\channelstrip);
Out.ar(~masterBus,sound * level);
Out.ar(~reverbBus, sound * sendAmount);
}).add;
);
//Now comes the ugly part - in order to stop SuperDirt from flooding the post window with "sound x not found" messages,
//we need to define "dummy" synthdefs with names matching all instances of the "voice" synthDef we want to use
//alternatively, we could reate a separate synthDef for each VST plugin instance we want to use
//also note that any synthdefs and sample folders loaded by superdirt sharing the same name will get played together
//my solution is to not load any default superdirt samples, alternatively you can use different names for your VSTs
(
//this synthdef basically does nothing except immediately freeing itself. CPU impact should be almost zero.
SynthDef("kick",{ | out |
var u;
u = Impulse.ar(1);
FreeSelf.kr(u);
Out.ar(0, DC.ar(0));
}).add;
SynthDef("snare",{ | out |
var u;
u = Impulse.ar(1);
FreeSelf.kr(u);
Out.ar(0, DC.ar(0));
}).add;
SynthDef("hat",{ | out |
var u;
u = Impulse.ar(1);
FreeSelf.kr(u);
Out.ar(0, DC.ar(0));
}).add;
SynthDef("perc",{ | out |
var u;
u = Impulse.ar(1);
FreeSelf.kr(u);
Out.ar(0, DC.ar(0));
}).add;
SynthDef("bass",{ | out |
var u;
u = Impulse.ar(1);
FreeSelf.kr(u);
Out.ar(0, DC.ar(0));
}).add;
SynthDef("keys",{ | out |
var u;
u = Impulse.ar(1);
FreeSelf.kr(u);
Out.ar(0, DC.ar(0));
}).add;
);
(
s.sync;
~synths = Dictionary [
\masterBus -> Synth("MasterBus"),
\kick -> Synth("voice",[out:0]),
\snare -> Synth("voice",[out:0]),
\hat -> Synth("voice",[out:0]),
\perc -> Synth("voice",[out:0]),
\bass -> Synth("voice",[out:0]),
\keys -> Synth("voice",[out:0]),
\reverb -> Synth("ReverbBus"),
];
~instruments = Dictionary [
\kick -> VSTPluginController.collect(~synths.at(\kick)),
\snare -> VSTPluginController.collect(~synths.at(\snare)),
\hat -> VSTPluginController.collect(~synths.at(\hat)),
\perc -> VSTPluginController.collect(~synths.at(\perc)),
\bass -> VSTPluginController.collect(~synths.at(\bass)),
\keys -> VSTPluginController.collect(~synths.at(\keys)),
\masterBus -> VSTPluginController.collect(~synths.at(\masterBus)),
\reverb -> VSTPluginController.collect(~synths.at(\reverb)),
];
);
(
s.sync;
//3.wait;
~oscfunc = OSCFunc({ |msg, time, tidalAddr|
var dict = (), timeDelta; // Create dictionary
dict.putPairs(msg[1..]);
if (dict.at(\n).isNil, {dict[\n] = 0});
timeDelta = time - Main.elapsedTime;
//it took me a while of playing around to find this value, it might be different for you.
timeDelta = timeDelta - 0.3; //server latency + dict[\shift];
/// adding a midi play function
if ( ~instruments[dict[\s]].isNil == false ,{
thisThread.clock.sched(timeDelta, {(\type: \vst_midi,\vst: ~instruments[dict[\s]].generator,
\midicmd: \noteOn,
\chan: 0,
\amp: dict[\amp],
\midinote: dict[\n] + 60,
\dur: dict[\delta],
).play;
});
//param automation - needs stacks in tidal for multiple params since its 1 per "voice"/msg/event bundle
if ( dict.at(\param).notNil == true && dict.at(\paramval).notNil == true && dict.at(\paramtarget).notNil == true ,{
thisThread.clock.sched(timeDelta, {~instruments[dict[\paramtarget]].generator.set(dict[\param].asInteger,dict[\paramval]);
})
});
});
}, '/dirt/play', NetAddr("127.0.0.1"), recvPort: 57120 /*3337*/);
);
(
//INIT PLUGINS
s.sync;
~instruments[\kick].generator.open("Vital.vst3", editor: true, verbose: false, multiThreading: true);
~instruments[\snare].generator.open("Vital.vst3", editor: true, verbose: false, multiThreading: true);
~instruments[\hat].generator.open("Vital.vst3", editor: true, verbose: false, multiThreading: true);
~instruments[\perc].generator.open("Vital.vst3", editor: true, verbose: false, multiThreading: true);
~instruments[\bass].generator.open("Vital.vst3", editor: true, verbose: false, multiThreading: true);
~instruments[\keys].generator.open("Vital.vst3", editor: true, verbose: false, multiThreading: true);
~instruments[\reverb].reverb.open("VCV Rack 2.vst3", editor: true, verbose: false, multiThreading: true);
//loading presets - but this will crash unless you have already saved presets with these names!!
/*
~instruments[\kick].generator.loadPreset("kick");
~instruments[\snare].generator.loadPreset("snare");
~instruments.at(\hat).generator.loadPreset("hat");
~instruments.at(\perc).generator.loadPreset("perc");
~instruments.at(\bass).generator.loadPreset("bass");
~instruments.at(\keys).generator.loadPreset("keys");
~instruments[\reverb].reverb.loadPreset("sendfx");
"PRESETS LOADED!!!".postln;
*/
);
};
s.latency = 0.333; // increase this if you get "late" messages
};
);
https://gist.github.com/ribon3000/4769de8019f5c4d292ab2a04112f9725
for my personal use, i have defined a "voice" as being 2 VST plugins in series, with the second processing the output of the first before passing it on to 2 outputs, one of which goes into a separate reverb Synth i also defined, and finally a "master" track that can process everything together.
after creating these synth definitions, i then instantiate a couple of named instances of these synths. some of them contain multiple VST plugin slots, others just one.
in any case, i can use VSTPluginController.collect
to create an object that allows me to access all VSTPlugin.ar
instances that live within a single synth, and address these separately by their ID.
after that is done, the script then loads the actual plugins and a few preset files i've previously saved. note that the second VSTPlugin.ar
in each "voice" synth actually doesn't load a plugin yet - this is no problem, as VSTPlugin.ar
will simply pass any audio through if there's no plugin loaded.
at any point after the server started and the script finished, i can use SClang to easily load and unload plugins from any of the Synths, or load different presets. However, i am leaving the audio routing and all spawned Synth instances in place.
it would be possible to do the loading, unloading, bypassing and preset management via Tidal, but personally i don't think that these are actions that need to be patterned.
personally, i'm currently wrapping supercollider and tidal in an enclosing nodeJS program that can send messages to both separately, allowing me to do things like trigger tidal code snippets or load/unload VST plugins via MIDI/OSC controls. in this way i can leave tidal to do what it does best, which is generating patterns of control data, and don't need to worry about making it do extra things.
[/details]