Spacechild1 / vstplugin

VST plugin support for Pd and SuperCollider (mirror of https://git.iem.at/pd/vstplugin). If possible, use the issue tracker at https://git.iem.at/pd/vstplugin/-/issues.
Other
87 stars 6 forks source link

SC NRT, using effects together with instruments. #4

Closed Catsvilles closed 4 years ago

Catsvilles commented 4 years ago

Hi, I'm following the help file with an example of rendering score in NRT thread, but unfortunately the example shows using instruments only and I would like to use both SC effects and VST effects in one Synthdef.

I don't know if this is the right approach but I'm trying to achieve something like "channels" in traditional DAW where we would have multiple effects and instrument per channel. So, I have multiple Synthdefs that look something like that:

`SynthDef(\first, { |out = 0| var snd;

snd = VSTPlugin.ar(nil, 2, id: \Ins); snd = VSTPlugin.ar(snd, 2, id: \Filter); snd = VSTPlugin.ar(snd, 2, id: \Reverb); snd = VSTPlugin.ar(snd, 2, id: \Compressor); snd = JPverb.ar(snd, 2, 0.7, 4, modDepth:1); snd = Limiter.ar(snd, level: 0.5, dur: 0.1); Out.ar(out, snd); }).store;`

then I'm trying to create the synth: ~fx1 = VSTPluginController(Synth.basicNew(\first));

and this is where problems start, normally, in realtime mode I would reuse ~fx1 variable to "attach" the effect, something like:

~filter = VSTPluginController.new(~fx1, \Filter);

but this doesn't work in NRT mode. I'm sure there is an easy solution to solving this, I just cannot find it due to the lack of experience. Also, not clear how to manage VST effects since we should probably create only one synth per Synthdef? ~score.add([0.0, ~fx1.synth.newMsg]);

Thanks a lot! Would be really cool to see more examples using NRT, I cannot seem even to find/grasp the appropriate way of composing a song in NRT using rendering per beats, seems a bit messy to call every instrument per beat, but ofc, it's just me not fully understanding the things, for sure it will get better with time/experience. :)

Spacechild1 commented 4 years ago

normally, in realtime mode I would reuse ~fx1 variable to "attach" the effect, something like:

I don't think this works even in RT mode. The first argument of VSTPluginController.new must be a Synth, not a VSTPluginController*). So generally have to do the following:

~synth = Synth.basicNew(\first);
~ins= VSTPluginController(~synth, \Ins);
~filter = VSTPluginController(~synth, \Filter);
// etc.

BUT I think you've actually discovered a NRT bug. The above doesn't work if I use SynthDef.store (for some reason, the VSTPlugin id becomes nil), but it works with SynthDef.add. I have to investigate why...

In the meantime, I've updated the NRT example in the develop branch. It was pretty outdated. You don't need to delay the openMsg anymore and there's now a built-in vst_midi Event type. Here's the new example: nrt_example.zip

Would be really cool to see more examples using NRT

Unfortunately, NRT mode in SuperCollider is pretty awkward to work with. You have to think completely different. The problem is the asynchronous nature of a Client Server app. In a synchronous app like Pd, you can mostly use the exact same patch both for RT and NRT rendering.

On the other hand, you can simply "record" patterns (as my example shows), which makes composing NRT scores less awkward.

*) Actually, this would be a nice feature to pass VSTPluginController for easier chaining. I'm putting this on my list: https://git.iem.at/pd/vstplugin/-/issues/75

Spacechild1 commented 4 years ago

Actually, turns out that the problem with .store also occurs in RT synthesis! I just happened to always use SynthDef.add. It is strange: it can find the SynthDef and get the UGens list, but members like id are always nil, although they are definitely set in the SynthDef function...

Catsvilles commented 4 years ago

@Spacechild1

I don't think this works even in RT mode. The first argument of VSTPluginController.new must be a Synth

Yes, this is exactly how I do in RT, sorry to confuse, just wanted to show the examples of NRT which do not work... Anyway, good to know I'm not going crazy and the issue is with SC, because I really tried for hours to solve this and most of the the ways were perfectly working in RT but not NRT :D

You don't need to delay the openMsg anymore and there's now a built-in vst_midi Event type.

Wow, cool! That probably means we don't have to rely on custom Event and now easily can play chords? Because before that, I had to hack into the custom Even function in the NRT example to make chords work.

Catsvilles commented 4 years ago

Another thing I would like to ask now but didn't want to create issue for is if there is a way to to switch between banks of midi programs? Say, the VST plugin allows only 127 midi programs to switch between, but what we could do is make 127 folder and fill them with the according number of presets. Switching to folder is easy with ~fx.program_(1); but after that I have no idea how to select the program within.

From what I understand the first thing to do is actually send midi command to switch to the bank and then we should be able to switch between programs within the bank. Is there a way to do this with VSTplugin?

Spacechild1 commented 4 years ago

That probably means we don't have to rely on custom Event and now easily can play chords?

Yes! Also, vst_midi schedules MIDI messages as OSC bundles, like all other Server Event types), which guarantees proper timing. For example, VSTis now sound exactly at the same time as other Synths scheduled for the same logical time. I think I have introduced this in v0.3.

Anyway, good to know I'm not going crazy and the issue is with SC

I now understand what's going on... SynthDesc.store reconstructs itself from the file it has written, losing all extra information stored in the UGens. I think I have to either work with metadata, or tell people to only use SynthDef.add :-)

Spacechild1 commented 4 years ago

From what I understand the first thing to do is actually send midi command to switch to the bank and then we should be able to switch between programs within the bank. Is there a way to do this with VSTplugin?

Yes, just send the appropiate MIDI CC messages (read about "MIDI Bank Select"). Given that your plugin actually supports MIDI program and bank changes in the first place!

Say, the VST plugin allows only 127 midi programs to switch between, but what we could do is make 127 folder and fill them with the according number of presets. Switching to folder is easy with ~fx.program_(1); but after that I have no idea how to select the program within.

Generally, the behavior of program highly depends on the plugin. Some (VST2) plugins allow to save preset banks (see readBank/writeBank) and you can use program to switch between individual programs in the given bank. Many plugins just use program for (read-only) factory presets. Some plugins don't use program at all, instead they only use VST preset data/files.

Of course, you can always simply use loadPreset/savePreset and organize the presets in any way you want for a given project. For example, a "bank" could simply be an Array of preset names and then you can have an Array of "banks". Assuming that there's a good reason for accessing programs numerically in the first place.

That being said, program and MIDI program/bank changes have the advantage that they work synchronously (in the RT thread), while the preset methods are asynchronous. On the other hand, you can fake synchronous "presets" by saving/sending the parameter changes you're actually interested in.

Catsvilles commented 4 years ago

@Spacechild1 Unfortunately, the new example of using VST effects and instruments in NRT doesn't seem to work + I receive errors: FAILURE IN SERVER /u_cmd failed

I will keep digging but logically the code should be working, have no idea what's wrong...

The errors seem to come from VST effects somehow.

Spacechild1 commented 4 years ago

can you post a full code example and the exact error messages?

Catsvilles commented 4 years ago

Basically the code is the same as in the example you provided. I tried with less VST effects too, so I don't think the issues is using too many of them :)

`// SynthDef for playing the VSTi SynthDef.new(\helm, { arg out; var sig = VSTPlugin.ar(nil, 2, id: \vsti); sig = VSTPlugin.ar(sig, 2, id: \fx); Out.ar(out, sig); }).add; // don't use .store!

// 1) create the VST Synths in the language

// make two Synths

~synth1 = Synth.basicNew(\helm); ~synth2 = Synth.basicNew(\helm);

// get handles to the plugin controllers ~vsti1 = VSTPluginController(~synth1, \vsti); ~fx1 = VSTPluginController(~synth1, \fx); ~low = VSTPluginController(~synth1, \fx); ~hi = VSTPluginController(~synth1, \fx);

~vsti2 = VSTPluginController(~synth2, \vsti); ~fx2 = VSTPluginController(~synth2, \fx);

// 2) create the score:

~score = Score.new;

~score.add([0.0, VSTPlugin.searchMsg(verbose: true)]); // search for plugins in default search paths // ~score.add([0.0, VSTPlugin.searchMsg(["/my/plugin/directory"], useDefault: false, verbose: true)]); // use custom search path

// create the synths: ~score.add([0.0, ~synth1.newMsg]); ~score.add([0.0, ~synth2.newMsg]);

// load VSTi plugins ~score.add([0.0, ~vsti1.openMsg("Helm")]); ~score.add([0.0, ~vsti2.openMsg("Helm")]);

// choose programs ~score.add([0.0, ~vsti1.programMsg(1)]); ~score.add([0.0, ~vsti2.programMsg(2)]);

//load FX ~score.add([0.0, ~fx1.openMsg("GLFO")]); ~score.add([0.0, ~fx2.openMsg("GChorus")]); ~score.add([0.01, ~low.openMsg("GLow")]); ~score.add([0.01, ~low.programMsg(rrand(0, 6))]); ~score.add([0.01, ~hi.openMsg("GHi")]); ~score.add([0.01, ~hi.programMsg(rrand(0, 2))]);

// set some FX params ~score.add([0.0, ~fx1.programMsg(0)]); ~score.add([0.0, ~fx2.setMsg(0, 0.9)]); // chorus depth // make a random melody ~melody = Pbind( \type, \vst_midi, \dur, Pxrand(#[8, 16, 24, 32], inf), \legato, 1, \amp, Pexprand(0.5, 1.0, inf), \midinote, Pxrand(Scale.major.degrees + 48, inf) );

// helper function for recording a Pbind to a Score ~render = { arg score, vsti, pbind, start, dur; var list = pbind.asScore(dur, start, (vst: vsti)).score; score.score = score.score.addAll(list[1..(list.size-1)]); // we have to remove first and last bundle! };

~render.(~score, ~vsti1, ~melody, 1, 16); // render 8 beats of the first voice, starting at beat 1 ~render.(~score, ~vsti2, ~melody, 16, 8); // render 7 beats of the second voice, starting at beat 4

// end ~score.sort; // important! ~score.add([~score.endTime + 4, [0]]);

// 3) render stereo aiff file

~score.recordNRT("~/nrt_test".standardizePath, "~/nrttest.aiff".standardizePath, options: ServerOptions.new.numOutputBusChannels(2));

// 4) save the Score to a file

~score.saveToFile("~/nrt_test.txt".standardizePath); )`

Full console output:

-> a Score Faust: supercollider.cpp: sc_api_version = 3 Faust: FaustGreyholeRaw numControls=7 Faust: supercollider.cpp: sc_api_version = 3 Faust: FaustJPverbRaw numControls=11 Found 0 LADSPA plugins VSTPlugin 0.3.3start time 0 nextOSCPacket 0 nextOSCPacket 0 vst_search: bufnum -1 out of range nextOSCPacket 0 nextOSCPacket 0 nextOSCPacket 0 nextOSCPacket 0 nextOSCPacket 0 nextOSCPacket 0 nextOSCPacket 0 FAILURE IN SERVER /u_cmd failed nextOSCPacket 0 FAILURE IN SERVER /u_cmd failed nextOSCPacket 0 FAILURE IN SERVER /u_cmd failed nextOSCPacket 0 FAILURE IN SERVER /u_cmd failed nextOSCPacket 0.01 FAILURE IN SERVER /u_cmd failed nextOSCPacket 0.01 FAILURE IN SERVER /u_cmd failed nextOSCPacket 0.01 FAILURE IN SERVER /u_cmd failed nextOSCPacket 0.01 FAILURE IN SERVER /u_cmd failed nextOSCPacket 1 nextOSCPacket 16 nextOSCPacket 17 nextOSCPacket 17 nextOSCPacket 24 nextOSCPacket 24 nextOSCPacket 28 nextOSCPacket 28 X11: terminated UI thread

OS Ubuntu 18, using latest SC compiled from source and latest release version of VSTplugin.

Catsvilles commented 4 years ago

Very curios, if I want to add SC effects too, should I create a different Synthdef for every VSTi routed to it's effects or it's somehow possible with just one Synthdef? Actually doesn't seem possible, because what if we want some VSTi to have JPverb and some not? ¯_(ツ)_/¯

Spacechild1 commented 4 years ago
~fx1 = VSTPluginController(~synth1, \fx);
~low = VSTPluginController(~synth1, \fx);
~hi = VSTPluginController(~synth1, \fx);

you're getting three handles to the same VSTPlugin instance. this can't work.

~score.add([0.0, ~fx1.openMsg("GLFO")]);
~score.add([0.0, ~fx2.openMsg("GChorus")]);
~score.add([0.01, ~low.openMsg("GLow")]);
~score.add([0.01, ~low.programMsg(rrand(0, 6))]);
~score.add([0.01, ~hi.openMsg("GHi")]);
~score.add([0.01, ~hi.programMsg(rrand(0, 2))]);

~fx1, ~low and ~how point to the same VSTPlugin UGen instance... also, the 0.01 delay shouldn't be necessary anymore.

Very curios, if I want to add SC effects too, should I create a different Synthdef for every VSTi routed to it's effects or it's somehow possible with just one Synthdef?

You can of course add any kinds of SC effects within the Synthdef. Sometimes it's ok to have a static FX chain in a single SynthDef, but often it's better to have a dedicated Synth for each effect (SC or VST), so you can route them flexibly. This is especially true for send effects (like a reverb), like in a DAW, where you usually don't put one reverb plugin on each track, but rather send the tracks to an FX bus which contains the reverb plugin. Unless, of course, you want a different room acoustic on each track. Same thing for master FX.

Spacechild1 commented 4 years ago

Actually doesn't seem possible, because what if we want some VSTi to have JPverb and some not?

That's exactly the point. Only put a VSTi and an FX into the same SynthDef if they really form a conceptual unit.

Spacechild1 commented 4 years ago

Personally, I always make a very general SynthDef like

SynthDef(\insert_2ch, { arg bus;
    ReplaceOut(bus, VSTPlugin.ar(In.ar(bus, 2), 2));
}).add;

and then create a seperate Synth for each plugin, so I can connect them freely. See also the "Serial FX chains" section in VSTPluginController.schelp.

Catsvilles commented 4 years ago

Wow, this is getting out of hand, I'm literally using the code from the example, without changing anything and still getting bunch of

FAILURE IN SERVER /u_cmd Node 1 not found

Yes, first thing it doesn't like from the code is the name \my_instrument which I change to \helm, I guess this is some SC specific stuff.

Also, following your comments I tried to assign every effect to it's unique id, when using many of them but as I said when trying to use even VST effect the errors still occur. Without any everything works fine. I'm sorry, this is taking way to much for such (you would think) an easy thing. Have no idea, if this is my OS, SC problem, will reboot to start again now.

Spacechild1 commented 4 years ago

I think I found the problem! SynthDef.add doesn't store the SynthDef on disk, but then the scsynth subprocess can't find the SynthDef, so we need to use SynthDef.store (that's why I used in the first place, I just forgot :-). I think you were getting all those weird errors because the scsynth process was loading an outdated SynthDef file, while the language was seeing the new SynthDef.

The solution is simple: use SynthDef.store and pass the SynthDef to VSTPluginController.open. See the updated example: nrt_example.zip

This this work now?

Catsvilles commented 4 years ago

@Spacechild1 Yes, it works! No errors this time, thank you very much! For now it seems that only 1 effect is working and even if adding more effects with unique IDs, the sound is not changing and responding. Can only hear the first effect. But with the examples you provided and told me to look through (like "Serial FX chains" ) I will surely figure it out on my own, need to go deeper into SC and learn more, anyway :) Thank you very much for your time and valuable advises and efforts! I hope more people will find all these examples here helpful in future. Cheers!

Spacechild1 commented 4 years ago

For now it seems that only 1 effect is working and even if adding more effects with unique IDs, the sound is not changing and responding.

For me it certainly is :-). Anyway, thanks a lot for the report! I will now update the example.

Catsvilles commented 4 years ago

@Spacechild1 Hey, sorry to bother you again but I just wanted to ask if it's possible somehow to specify attack and/or release times when playing a note using \type, \vst_midi with Pbind? I tried to search the whole project and help files but couldn't find anything on how to approach this.

From what I understand this should be possible via midi CC messages. But it's possible I misunderstood something... Anyway, will really appreciate if there is an easier way to do it and yo can point me to it! Thanks, again :)

Spacechild1 commented 4 years ago

From what I understand this should be possible via midi CC messages

This has nothing to do with MIDI messages.

Almost every synthesizer has some kind of envelope-generator which can be controlled with VST parameters, but it's usually the same for all voices. If you want to play polyphonically and need different envelopes for each voice, you can create several VSTis and do your own voice allocation in Supercollider.

Spacechild1 commented 4 years ago

Note that you can always create custom envelopes in Supercollider with EnvGen, but obviously this only works if you play the synthesizer monophonically, because the envelope affects the total output of the synthesizer.

Catsvilles commented 4 years ago

@Spacechild1 Thanks again! I think I will try to use EnvGen for now.