Cabbage Logo
Back to Cabbage Site

Internal MIDI Mapping & Widget Integration

Not sure if I should post this under Noobs or if it’s preferable here, also not sure if this is old news for most people, but I share it here just in case it’s useful :slight_smile:

I’m designing my first synth with Cabbage and I wanted it to:

  • Have a basic GUI with standard knob controls for starters (ADSR, etc.).
  • Also have those same parameters respond to incoming MIDI CC data.

In the process of setting it up, I learned a couple of things that I’d like to share:

CC & WIDGET INTEGRATION

It might be a little tricky to integrate the two (MIDI + widget), as either you get the data from incoming MIDI ––or–– from a widget, but not both in one connection, so you have to create a loop of sorts. First, let’s get the order of instructions:

  1. Get the MIDI data
  2. Get the widget data
  3. Check if the widget data changed
  4. Check if the MIDI data changed
  5. If the widget changed, kMIDI = widget state
  6. If MIDI changed, change widget according to the incoming CC

Parameter input will ultimately come from the widget…

kmatt   ctrl7   1, 19, 0, 1
iatt    cabbageGetValue "att"
ktkatt  changed iatt
ktmatt  changed kmatt
if      ktkatt == 1 then
        kmatt = iwatt
endif
cabbageSetValue "att", kmatt, ktmatt

I found this recipe to work OK so far (still need to test it some more), after struggling with the knob state changing every time I played my MIDI controller (somehow, MIDI controller data is remembered by Csound and it ‘updates’ the widget even though I may have previously set it to some other value manually by dragging with the mouse: I want to be able to change the parameters either directly on the GUI or via MIDI but then for the parameters to stay wherever I leave them, if that makes sense.

Also, I realize most DAWs have a ‘MIDI learn’ feature, but I prefer my instruments to incorporate any MIDI mapping directly –– when I’m making music, I’m either too lazy to set everything up or I find that it messes with the creative flow; or in the case of Ableton Live, for example, the mappings are session-dependent as opposed to instrument-dependent.

NOTE: I also found some weird widget behavior using the code above and not sure if this is something to do with it or the widgets: if I set the widget range to start from absolute zero, and I drag the value on the GUI to minimum –– after I had changed the value via MIDI –– it seems that the ‘widget changed’ instruction doesn’t actually register a change and when I play again some MIDI notes on the keyboard, the patch updates with the last MIDI setting and the knob jumps to that value (when it’s not meant to do so). Perhaps @rorywalsh can take a look at this (whenever)? In the meantime, my solution is to give the widget an approximate range:

rslider bounds (275, 180, 60, 60), channel(“att”), range(0.001, 1, 0, 1, 0.001), text(“A”)

ZIPPER NOISE

Parameter mapping, in some cases, is not as straight-forward as just connecting a widget (say to a volume control): you’ll get zipper noise ‘out of the box’. But that’s the nature of the signals themselves (widgets sending data at k-rate, therefore changing the state of the audio and ‘skipping’ from one value to the next, as opposed to a smooth change, as it would happen naturally in the analog world with voltage control). This is well documented in the Csound manual, but I’ll post here the link (under “Smoothing 7-bit Quantisation in MIDI Controllers”) and an example code for convenience:

http://write.flossmanuals.net/csound/c-working-with-controllers/

; Smoothing
kvpt    linseg  0,0.001,0.01
kvol    portk   kvol, kvpt
avol    interp  kvol * kvol

There are three stages to the smoothing –– to the Csound veterans here, please correct me if I’m wrong! Happy to edit the post for future reference:

  1. The linseg opcode will create a ramp from one value to the next.
  2. The portk opcode applies a ‘portamento’ to the signal (a kind of secondary ramp IMO?).
  3. The interp opcode interpolates the segmented values at audio rate.

Hope that helps!

1 Like

I’m afraid I don’t have access to a MIDI controller to test these things right now, but regarding getting rid of the zipper noise, you can also just lowpass filter the k-rate signal"

kVol = tonek(kVol, 10)

if kVol is the signal you want to smooth. I use this trick for most of the widgets to stop that zipper noise.

1 Like

Cool trick, thanks!!

I notice that you read the value of “att” into the iatt variable, this will work when you initialize the instr. If you do not change the value of iatt anywhere else, the ktkatt trigger will not work. It can go like this:
ktkatt, kiatt cabbageGetValue “att”

Reading it as k-rate doesn’t make sense, as all of Csound’s envelop opcodes take attack times as init-rate?

you are absolutely right. i forgot the terms i-rate, k-rate :slight_smile: the division of variables in csound is very unusual… every music programming language I’ve tried has broken the usual patterns :slight_smile:
personally, i try to split i-rate logic from k-rate into two different instruments whenever possible

by the way, about division of logic. do you know, that form autoUpdate does not track changes in #include files? AutoUpdate is a handy thing, but it also prevents you from splitting your project into multiple files. It is sad.

I wasn’t aware that it does not track changes to include files. It seems odd that this would be the case when all it does is recompile Csound. I will take a look when I get a chance. I don’t really advertise that feature because it is quite restricted in terms of adding new parameters.