Cabbage Logo
Back to Cabbage Site

MIDI feedback from plugin to MIDI controller - Can plugin inform a mapped MIDI controller about the state of variables when they change internally without using a mouse?

Hi,

I’m new to designing plugins (using Cabbage/Csound) and I hope you can help me find a solution to my problem.
Perhaps my problem cannot be resolved with Cabbage/Csound due to any inherent limitations or perhaps the limitations may be due to Audio Unit and VST principles.
In any case your feedback or suggestions for alternative approaches would be very precious.

I’d like my plugins to send MIDI feedback (for the mapped messages) to MIDI controllers (with endless encoders) not only when I tweak plugin parameters/variables with a mouse but also when they are changed internally within the plugin. For example, pushing a button may multiply the frequency of an oscillator and update the encoder widget used to control that frequency. Such a change should inform a MIDI controller that the frequency was changed even though it was not changed with a mouse. This would set the controller at the correct new position and prevent jumping from the new (changed only within plugin but not updated in controller) to the old position when the controller is changed.

I’d particularly like to have this functioning so that I could save/load presets on the fly. I’m aware of the “snapshot” implementation to save presets, but I don’t want to load presets with a mouse. Instead, I’d like to load/change them very quickly by pushing buttons on a MIDI controller. I have a fine working preset handling based on saving/loading plugin parameters to/from function tables. I have a range of buttons (say 1-8) to write all parameters to any of the “slots”, i.e. tables 1-8, by a long button press. Then I can load all parameters (different presents) by a short press on any of the buttons. All tables, i.e. presets, can be saved/loaded using ftsave/ftload. Plugin parameters and widget states get updated correctly without a problem. The only thing that I’m missing in this scenario is the ability for plugin to send feedback to MIDI controller when changes to plugin parameters occur within the plugin, e.g. after they have been loaded from function tables and the widget states have been updated. If I change plugin parameters with a mouse, plugin sends feedback to MIDI controller as expected, but this is not the case when plugin parameters and widget states are updated from within the plugin. An exception to this is when plugin parameters are changed by using e.g. VST program or bank change, but this to my knowledge does not provide a solution to what I’m trying to achieve.

I couldn’t find the info I am looking for on the Cabbage forum.
The most related discussion I found was: Bug with presets and plugin parameter state (in VST)

To illustrate the issue, I’m attaching a short video clip (Cabbage_MIDIfeedback.mp4.zip (2.6 MB) ) and posting the code for this simple demo.
In the video, there are 6 buttons/endless encoders of the MIDI controller (Midi Fighter Twister) visible.
Only the bottom row is mapped to the test plugin:

  • the endless encoder on the left is mapped to the plugins’ encoder, i.e. oscillator frequency
  • the middle button is mapped to the upper (red) button on the plugin, i.e. used to multiply frequency by factor two
  • the right button is mapped to the lower (green) button on the plugin, i.e. used to divide frequency by factor two

You can see that changing the plugin controls with the mouse sends feedback to the MIDI controller.
The MIDI controller affects the plugin as expected, but when buttons on the controller are pushed to multiply/divide frequency, the plugin doesn’t send MIDI feedback to the frequency knob on the controller after the plugin frequency and the associated widget have changed. It seems that MIDI feedback is only sent if the plugin controls are changed with a mouse and not when they are changed internally within the plugin.

Cabbage: 2.3.0
Computer: MacBook Pro (Retina, 15-inch, Mid 2015), MacOS Sierra (10.12.6)

Code:

<Cabbage>
form caption("Untitled") size(200, 200), colour(58, 110, 182), pluginid("tmf1")

rslider bounds(2, 22, 120, 120) range(0.5, 10000, 220, 0.25, 0.001) channel("OscFrq") identchannel("OscFrqID") popuptext("0") colour(0, 255, 0, 255) trackercolour(255, 0, 0, 255)
label   bounds(6, 154, 114, 22), text("Osc-Frq") 

button bounds(126, 20, 65, 65) latched(0) channel("OscFrqMultiplier2")  text("","") value(0)  corners(3) colour:0(255, 0, 0, 255) colour:1(255, 255, 0, 255) fontcolour:0(0, 0, 0, 255) fontcolour:1(160, 0, 0, 255)
button bounds(126, 88, 65, 65) latched(0) channel("OscFrqMultiplier05") text("","")  value(0)  corners(3) colour:0(0, 255, 0, 255) colour:1(255, 255, 0, 255) fontcolour:0(0, 0, 0, 255) fontcolour:1(160, 0, 0, 255)


</Cabbage>
<CsoundSynthesizer>
<CsOptions>
; just some options I tested
;-n -d -+rtmidi=NULL -M0 -m0d 
;-+rtmidi=NULL -Ma -Q1 
;-odac -n -d -M0 -Q0 -b 4096
;-dm0 -odac -Q2 
;-dm0 -n -d -M0 -Q0

-d -n  -m0d -M1 -Q0  -+rtmidi=NULL 

</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1

kOscFrq chnget "OscFrq"

if (trigger(chnget:k("OscFrqMultiplier2"), .5, 0) == 1) then;
    kOscFrq *= 2
    chnset  kOscFrq, "OscFrq" 
endif
    
if (trigger(chnget:k("OscFrqMultiplier05"), .5, 0) == 1) then;
   kOscFrq *= 0.5
   chnset  kOscFrq, "OscFrq" 
endif   


aSig  poscil 0.8, kOscFrq
outs aSig,aSig

endin

</CsInstruments>
<CsScore>
f0 z
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

Hi @Samo, how is the hardware getting information from the plugin? In the video, you move some sliders and the hardware updates, yet in your Csound code you are not sending any MIDI data out. Did you have to configure something in Live for this to work?

p.s. I edited your post to add code formatting. If you don’t wrap code in formatting tags much of it will not be visible to readers…

Thanks for checking this and adding code formatting.
I’ll format my code next time.

The plugin is MIDI mapped within DAW. There was nothing unconventional about that and all plugins send MIDI feedback out to an external controller. I can see that with the MIDI monitor application on OSX and of course it is obvious with my controller (Midi Fighter Twister), which visually shows the status and also updates the state of its endless encoders.

According to Ableton it looks like this has to be configured manually? But I guess in your cases this IO mapping is done automatically with the MIDI Fighter Twister?

Either way, it looks like you have two ways to do this. On the one hand it seems that you can set up Live to output MIDI based on plugin parameters. In this way you don’t have to use -Q or have Csound output any MIDI messages at all. The other way is to send the data directly from Csound, in which case you should probably disable the auto mapping from Live as otherwise you’ll have two system trying to send MIDI data? With this slightly modified instrument, does the MIDI hardware LED change? Sorry, I don’t have such a nice MIDI controller to debug this with, so we might have to do it remotely. Btw, what version of Cabbage are you using?

<Cabbage>
form caption("Untitled") size(200, 200), colour(58, 110, 182), pluginid("tmf1")

rslider bounds(2, 22, 120, 120) range(0.5, 10000, 220, 0.25, 0.001) channel("OscFrq") identchannel("OscFrqID") popuptext("0") colour(0, 255, 0, 255) trackercolour(255, 0, 0, 255)
label   bounds(6, 154, 114, 22), text("Osc-Frq") 

button bounds(126, 20, 65, 65) latched(0) channel("OscFrqMultiplier2")  text("","") value(0)  corners(3) colour:0(255, 0, 0, 255) colour:1(255, 255, 0, 255) fontcolour:0(0, 0, 0, 255) fontcolour:1(160, 0, 0, 255)
button bounds(126, 88, 65, 65) latched(0) channel("OscFrqMultiplier05") text("","")  value(0)  corners(3) colour:0(0, 255, 0, 255) colour:1(255, 255, 0, 255) fontcolour:0(0, 0, 0, 255) fontcolour:1(160, 0, 0, 255)


</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-d -n  -+rtmidi=NULL 
</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1    
kSlider chnget "OscFrq"
kOscFrq  = abs(oscili:k(20000, 1))
chnset  kOscFrq, "OscFrq" 
endin

</CsInstruments>
<CsScore>
f0 z
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

I’m setting MIDI ports in remote mode (input and output). This means that the selected ports are MIDI mappable to any plugin parameters and should send MIDI feedback to external controllers.

What do you mean by this?
“On the one hand it seems that you can set up Live to output MIDI based on plugin parameters”

I have tried a similar code as yours before but I tested your code to be sure.
The plugin doesn’t send MIDI feedback (no changing light on my controller). However, if I grab the knob with a mouse (overriding internal parameter variation), then it does send MIDI to controller, just as in my example.

I have been trying to send MIDI feedback directly from Csound without success. I suppose this would be useful particularly for the standalone application. I’m not sure how this would function for plugins if one wants to have other controllers connected to DAW as well. But since different ports can be selected, it might work just fine.

However, when it comes to using -Q, I have a few questions, which may belong to a different topic. But since it came up here, I’ll just list them:

  • Cabbage is crashing (always on 2nd build of instrument after restart) whenever I try to send MIDI (when -+rtmidi=NULL is not used), e.g. if I use -d -n -M0 Q1.
  • Is it possible to select the device (MIDI controller/port) by name and not only by number (which depends on what I have connected - multiple controllers)?
    Example of listing:
    0: IAC Driver Bus 1 (hostbased)
    1: IAC Driver IAC Bus 2 (hostbased)
    2: IAC Driver IAC Bus 3 (hostbased)
    3: IAC Driver IAC Bus 4 (hostbased)
    4: Network Session 1 (hostbased)
  • it would be great if midi mapping would be possible in future versions of the very useful (new) “export as standalone” feature.

I’ve been thinking of alternative solutions, in particular testing snapshots (snaps). Again, this might be another topic?
Snapshots do send MIDI feedback when changed my a mouse in DAW. So, my general question here is: Can different snapshots be loaded with a MIDI controller?
Ideally, I’d like to use different buttons to load/save different snapshot items, so using snapshots is not ideal for my scope.
It seems that I cannot change an item in combobox when I use populate("*.snaps") in the same way as when I use e.g. items(“item 0”, “item 1”, “item 2”). If this would be possible, I could maybe use buttons to load snapshots, which could be MIDI mapped in DAW and thus mouse handling could be avoided. But again, this wouldn’t be as flexible as I envisioned (see my original post).

I am using Cabbage 2.3.0

This is being done then by the host and not Cabbage.

That’s to be expected as once you remove -+rtmidi=NULL you are asking Csound to open MIDI devices that Cabbage is also trying to access. Cabbage provides its own MIDI IO so you must st rtmidi to NULL. This doesn’t mean you are disabling real time midi, it just means that Cabbage takes control of it rather than portmidi which is what Csound uses.

No. I’m afraid not. :frowning_face:

Agreed, it might be a lot of work for the main IDE, but could be easier with the standalone exports. I’ll take a look.

You’re talking about Cabbage snapshots? So if you select a preset in your plugin, as soon as Cabbage updates your sliders this data is sent to your MIDI device? If so, I think it must be your host doing it. The question I have is why the host does it for this and not when chnset it called? And as I write this I think of something that might help. (memo to me: I need to make certain that notify host is being called when I updates channels with a chnset)

This should be possible, but now I see you’re only using 2.3.0. The current beta build is a 38 iterations newer. The best would be to test the latest beta first. Hopefully some of these issues with go away. You can find it here. Just click the artifacts button and download the installers.

Thanks for the explanations.

Yes, I am talking about Cabbage snapshots. And yes, it is the host sending MIDI to controller. Exactly that was my question - thanks for elucidating it. It seems the host is not notified.

I still don’t fully understand the Csound flags. Sorry for my ignorance and thanks for your patience.
I am not sure how would I implement a MIDI feedback in Cabbage/Csould, so that a controller would be able to understand it. I have to play around with it, but do I just need to send MIDI to the correct device/port (which might be difficult or practically not feasible if I can’t select the port by name)?

I installed Cabbage 2.3.38. The lack of host MIDI feedback persists. I really hope you can find a solution to this.

I also noticed another strange behaviour. Maybe this is expected? All is fine when I use
-d -n -+rtmidi=NULL -M1 -Q0,
but Ableton Live crashes when I use
-d -n -M1 -Q0 -+rtmidi=NULL
This happens only when I export my plugin (see code from my original post) as an au-synth and not when it’s an au-effect. I haven’t tested VST.

I still (Cabbage 2.3.38) can’t select a snapshot with a button. I hope you can check what I am doing wrong in my code:

<Cabbage>
form caption("test snaps") size(320, 300), colour(58, 110, 182), pluginid("sps2")

rslider bounds(2, 22, 120, 120) range(0.5, 10000, 220, 0.25, 0.001) channel("OscFrq1") identchannel("OscFrq1ID") popuptext("0") colour(0, 255, 0, 255) trackercolour(255, 0, 0, 255)
label   bounds(6, 154, 114, 22), text("Osc-Frq1") 
rslider bounds(196, 22, 120, 120) range(0.5, 10000, 220, 0.25, 0.001) channel("OscFrq2") identchannel("OscFrq2ID") popuptext("0") colour(0, 255, 0, 255) trackercolour(255, 0, 0, 255)
label   bounds(200, 154, 114, 22), text("Osc-Frq2") 
button bounds(6, 224, 120, 65) latched(0) channel("select1")  text("","") value(0) corners(3) text("select snaps & item 1") colour:0(0, 0, 0, 255) colour:1(255, 255, 0, 255)
filebutton bounds(4, 188, 60, 25) channel("but1") text("Save"), mode("snapshot")
combobox bounds(66, 188, 100, 25) channel("snaps_list") identchannel("snaps_listID") populate("*.snaps") ;value(2)
combobox bounds(182, 230, 98, 30) channel("combo_box_list") identchannel("combo_box_listID")  value(3) align("left") items( "item1", "item2", "item3")

</Cabbage>

<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d
</CsOptions>
<CsInstruments>

ksmps = 32
nchnls = 2
0dbfs = 1

instr 1

kOscFrq1 chnget "OscFrq1"
kOscFrq2 chnget "OscFrq2"

; select item 1 from snaps_list and from combo_box_list
if (trigger(chnget:k("select1"), .5, 0) == 1) then; 0->1
    
;Ssnaps sprintf "value(%.2f)", 0 ; this doesn't help
;chnset Ssnaps, "snaps_listID"
;printks Ssnaps,0
    
ktest = 1
chnset ktest, "snaps_list"
chnset ktest, "combo_box_list"
endif

endin

</CsInstruments>
<CsScore>
f0 z
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

No, in the case of Cabbage you just use something like:

-d -n -+rtmidi=NULL -M1 -Q0

Which will send data out on the first channel. Then in your host you route that wherever you like. Unless you specifically send MIDI data out of Csound using specific MIDI out opcodes, Csound is not generating or sending any MIDI. So the MIDI you see being sent out from your Cabbage plugins is not from Csound as you are not outputting any MIDI from Csound.

I assume this one crashes because Csound tries to open a MIDI output port that Live is accessing. In the first instance Csound knows not to open any MIDI ports directly.

My apologies, this is not actually permitted. Sorry, I probably cost you some time there. :frowning_face: I had a look through the source code and I can see now why this isn’t permitting. Only the host and the preset combo can control preset.

I also took a look at the code that is called whenever a chnset is used to update a widget channel and it seems it does not notify the host. So let me see if I can fix that. That might just solve all your problems. It will most likely be Monday before I can get around to it. Things are pretty crazy here right now, as I imagine they are everywhere.

I just did some quick tests and I can recreate the problem here. Thanks for picking up on this. It’s a complete oversight on my part. I just hope there is a quick and simple fix :confused:

I couldn’t wait till Monday :wink:

I just pushed some changes there that I think should address your problem. As you can see from the following screen, the host now updates too/. I hope this enough to sort your issue.

You can find the latest build here. Once it’s finished building you should see an artefact button. Please let me know if this helps at all.

Thank you Rory!
I am grateful for your quick and educational responses, which I find very inspiring!
Your fix made me happy :relieved:

It works for VST synths and VST effects now, but it still doesn’t seem to work for AUs. I hope you could fix also those? :worried:

Yes, crazy times… Luckily (at least for now) some of us can still keep busy in a creative way and even do some Cabbage in spare time. Let’s hope the virus situation doesn’t get much worse.

Best wishes,
Samo

I’ll take a look on Monday and see what’s happening. Stay safe till then!

I just tried this now with Logic Pro and everything works as expected. Calling chnset updates the on screen slider, and the attached logic slider:

What DAW did you try with?

I tried it with Ableton Live. Now I tried using your code above (from 23 Mar) and that works, but something strange is going on… I can brake MIDI feedback on AUs (fx and synth) if I map to a controller and then change a knob a lot. It seems like it gets stuck at some point and stops working. But not if I am “gentle” with it and not for VSTs. Also not if I use mouse, but this might be because I am not able to shake it brutally enough :grin:. Restarting Live makes it work again.

Previously I tested a more complex plugin, and there it stopped working immediately (I guess), only for AUs though. It might have been due to its internal “shaking” (changing lots of parameters). Maybe you have a clue what could be going on and I could try out some more systematic tests these days. Sorry for not being very elaborate now - it’s been a long day.

Are you trying to update manually, while chnset is also trying to update the widgets? Or has this no longer anything to do with chnset?

Yes, exactly. And it doesn’t get stuck on VSTs… I’ll need to get some time to debug my other plugin and try to isolate the issue. Let me know if you have any idea.

Ha, I’m no less confused, as I asked two questions and got a ‘yes exactly’. :laughing: I guess one might expect problems if you are trying to update widgets, while Cabbage is also trying to do the same. But I’ve a feeling this is not the case, and instead you are simply trying to move the sliders remotely via MIDI, and this is causing the crash?

Sorry for confusion! It must have to do with chnset since that is in action in your code (from 23 Mar), right? Or do you mean something else? Yes, I am trying to update manually via a mapped MIDI controller. As I mentioned, I also tried to update manually using a mouse, which doesn’t “break” MIDI feedback. I imagine it could be a problem if the user and Cabbage are both trying to do the same thing, but strange that I observed a problem only with AUs, while VSTs seem more robust to that kind of conflict. And just to be clear, it did not “crash” DAW, the MIDI feedback simply stopped working. Maybe Csound crashed? But in that case it must be running in independent instances for different plugins, because I had 2 of them (effect and synth) and I could “break” either one of them while the other still functioned properly.

Please excuse my unconventional nomenclature - I don’t have a programming background.

No need to apologise. You’re making perfect sense.

If that happened you would know all about it. The plugin would be completely unresponsive and the DAW would report that it had caused some kind of issue. So in this case, I don’t think that’s the reason.

Not all that strange when you consider they are both very different systems. Both systems present problems.

I think the bottom line is, if you are constantly updating from Csound using a chnset, AND your are also trying to simultaneously update with a mouse or via MIDI, you can probablt expect some issues. You could try slowing down the updates from chnset by wrapping it in a timer:

if metro(1) == 1 then
   chnset kVal, "sliderChannel"
endif

Perhaps that might help a little? 1 is obviously quite slow for a metro in this instance, but it might be bet to start there and try to reduce over time.

Thanks for explanations. I just did some more simple tests (two buttons, one knob as in my first example) and it didn’t work when I had the flag -m0d, but it did without it (-n -d -m0d -+rtmidi=NULL). Could this mean something to you? Then I tried out my real plugin. It worked. Went back to the simple example and it also worked. So I am very confused. Maybe it has to do with DAW and its inner life and past traumas :confused: I’ll have to live with it (DAW and plugin) for a while and get to know the beasts. I’ll report back when I have a more clear idea. Cheers