Cabbage Logo
Back to Cabbage Site

Creating custom UIs with Cabbage 3

amazing! congrats on the alpha release. I’m very much looking forward to playing around with this. hopefully will find some time over the next couple weeks.

Btw the time you start playing with it I should have a few more of the teething issue sorted :wink:

1 Like

I’m trying to create custom widgets now!

Do you have examples of widgets using an audio buffer as an input? I’m trying to understand how to do it using the keyboard.js as a template but instead of using midi events I’ll use audio events.

Thanks!

Edit: Actually the gentable example might be a good starting point, i’ll try with this one

Yes, gentable. We’ll need to work on a more robust approach to sending samples. But the gentable has a samples property, which get populated with audio from a Csound function table. I will add an array version of cabbageSetValue so that you can grab audio samples more easily.

I will try to get that done later today if I can :+1:

Ok, new build underway that should provide more options for drawing things. I’ve added array variants of cabbageSet and updated the placeholder docs. My advice for you would be to clone the most recent gentable widget. rename both the class and the filename, but leave it in the same directory as gentable.js. Then hack the drawing routine as you see fit. You can send an array of samples to it using the cabbageSet opcode. Here’s me doing it with a small array.

The current gentable widget sucks at smaller tables. I’l fix this when I get a chance.

I was wondering if there’s any way to do live updates between cabbage and a React app during development?

Good question - right now I don’t think so. What I usually do is develop my interface using vscode’s live server, and then when ready test it with audio. The nice thing about this is I use vscode for the developing the interface too, so all my dev work happens in one place.

Alright, I’ll test more tomorrow.

One thing I noticed that would be useful in terms of development experience would be some JSDoc in the cabbage.js to describe the parameter types.

Like this:

/**
 * Function that combines name and age into one string
 * @param {string} name 
 * @param {number} age 
 * @returns string
 */
const CombineNameAndAge = (name, age) => {
    return `${name} (${age})`
}

will show up like this in the IDE:

Yes, good call. Just pushed some now :+1:

1 Like

The plugin resources folder, is this a custom folder you bundled in the form?

Are you referring to something like the bundle() identifier? No, the plugin resources gets bundled automatically whenever you export a plugin.

This is how I interpreted the original post, but I can’t find this folder on windows?

They get placed into the following locations:

  • MacOS /Library/CabbageAudio/PLUGIN_NAME/
  • Windows C:/ProgramData/CabbageAudio/PLUGIN_NAME/

Unless you have ‘Bundle resources’ enabled - which is a very new feature…

Alright, found it.

Do I remove the all the files except the .csd file, and then move the react build into this folder?

You might as well hold onto the Cabbage folder, but reallt cabbage.js is the only thing you need. Oh, and you might as well leave the widget files there too. This way you can define parameter in your csd file, for example rotarySlider .... Even though it won’t display, it will still create aparameter in the host that you can interact with via cabbage.js, and Csound.

Nice, seems like this works great!

If you control the channel-value outside of the UI (like automation), it will not update the slider in the UI. Is there any way to listen to external changes to the channel-value so we can sync this to the state?

add something like this:

window.addEventListener('message', async event => {
   console.log("Cabbage: onMsessage", event.data); // check input
   switch (message.command) {
        case 'widgetUpdate':
            //see note below

The message will contain a command, and either a data object, or value property. When it’s in this form:

{
   "command": "widgetUpdate",
   "channel": "channelName",
   "value": 0
}

It typically comes from a parameter change in the host. When its in this form:

{
   "command": "widgetUpdate",
   "channel": "channelName",
   "data": {...}
}

it’s usually a result of a called to cabbageSet. You can build your own widgets, and update them dynamically using cabbageSet in this way. It’s a pretty powerful system, albeit it incredibly simple. Just check the incoming data to make sure I have the format of it correct :wink:

Nice one, this works perfectly. Is there a way we can get the initial value?

Think I found something here.

Changing the 2nd slider (channel: “gainR”) will also trigger an event for “gainL”.

<Cabbage>[

{"type":"form","caption":"Effect","size":{"width":580,"height":300},"pluginId":"def1"},

{"type":"verticalSlider","channel":"gainL","bounds":{"left":20,"top":100,"width":80,"height":180}, "text":"Gain", "range":{"min":0,"max":1,"defaultValue":0.5,"skew":1,"increment":0.02}},

{"type":"verticalSlider","channel":"gainR","bounds":{"left":100,"top":100,"width":80,"height":180}, "text":"Gain", "range":{"min":0,"max":1,"defaultValue":0.5,"skew":1,"increment":0.02}}

]</Cabbage>

<CsoundSynthesizer>

<CsOptions>

-n -d

</CsOptions>

<CsInstruments>

; Initialize the global variables.

ksmps = 32

nchnls = 2

0dbfs = 1

instr 1

    a1 inch 1

    kGainL cabbageGetValue "gainL"

    kGainR cabbageGetValue "gainR"

    outs a1*kGainL, a1*kGainR

endin

</CsInstruments>

<CsScore>

;causes Csound to run for about 7000 years...

i1 0 z

</CsScore>

</CsoundSynthesizer>