This is the full developer documentation for Cabbage 3
# Cabbage 3 documentation
> Documentation for Cabbage 3: the VS Code extension, the built-in widgets, custom interfaces and the Cabbage opcodes.
Cabbage 3 is a framework for audio software development. You write an instrument in Csound 7, give it an interface with the built-in widgets or with your own HTML, CSS and JavaScript, and export it as a plugin from VS Code.
New here? [Install Cabbage 3](/install/) first, then start with [Using Cabbage 3](/docs/using/overview/). Coming from Cabbage 2? Read [what is new in v3](/docs/whats-new/).
[Using Cabbage 3 ](/docs/using/overview/)Run the extension, work with instruments and export plugins.
[Widgets ](/docs/widgets/overview/)The JSON format and reference pages for the built-in widgets.
[Custom interfaces ](/docs/custom-ui/custom-interfaces/)Build your own interface with web technologies and the Cabbage JS API.
[Opcodes ](/docs/opcodes/overview/)The Csound opcodes that read and update widgets from your instrument.
# Custom Interfaces
> Building entirely custom web-based plugin interfaces with any framework
It’s also possible to design a completely custom web-based interface using any framework you like — vanilla HTML/JS, Svelte, React, Vue, and so on. The Cabbage JS API handles all communication with the Csound backend, so your UI code stays framework-agnostic.
Unlike [Custom Widgets](/docs/custom-ui/custom-widgets/), this approach does not use Cabbage’s built-in UI editing tools. Instead, you have full control over the interface and simply use the API to exchange data with Csound.
Note
A `{...}` section is still required in your `.csd` file to define the plugin parameters exposed to the host DAW. The widgets declared there are not rendered visually when a custom interface is active, but they set up the parameter slots that your UI controls map to.
## Getting Started
[Section titled “Getting Started”](#getting-started)
The quickest way to start a custom interface project is the **Create Vanilla VST3 Effect/Synth Project (Custom UI)** command in the Command Palette. This scaffolds a working plugin binary and creates a ready-to-use resource folder alongside it containing:
* A `cabbage/` folder with `cabbage.js` and supporting files
* A starter `index.html` wired up to the Cabbage JS API (can be removed if you use a build tool to generate your own index.html)
* A `cabbage.project.json` configured for local file mode
From inside that resource folder you can initialise your framework project of choice:
```bash
# Svelte / Vite
npm create vite@latest MyPlugin -- --template svelte
cd MyPlugin && npm install
# React / Vite
npm create vite@latest MyPlugin -- --template react
cd MyPlugin && npm install
```
Then update `cabbage.project.json` to point at the dev server while developing, and switch back to the built output for distribution:
```json
// Development — point at the running dev server:
{ "entry": "http://localhost:5173" }
// Production — point at the built output:
// { "entry": "MyPlugin/dist/index.html" }
```
## VS Code Development Workflow
[Section titled “VS Code Development Workflow”](#vs-code-development-workflow)
The Cabbage VS Code extension can load your custom interface directly in the webview panel, replacing the native widget UI. This lets you develop and test your interface alongside the Csound instrument without leaving VS Code.
### Setting up `cabbage.project.json`
[Section titled “Setting up cabbage.project.json”](#setting-up-cabbageprojectjson)
Create a `cabbage.project.json` file in the same folder as your `.csd` file using the **Create Project File (cabbage.project.json)** command from the Command Palette. This tells the extension how to load your UI:
```json
{
// Point to a running dev server (e.g. Vite) for live-reload during development:
"entry": "http://localhost:5173"
// Or point to a built HTML file for plain HTML or production builds:
// "entry": "dist/index.html"
}
```
When you save the `.csd` file, the extension reads this config and loads your interface in the webview panel at the correct size (taken from the `form` widget’s `size` in the `` section).
### Dev Server Mode (Vite / React / Svelte)
[Section titled “Dev Server Mode (Vite / React / Svelte)”](#dev-server-mode-vite--react--svelte)
When `entry` is a `http://` URL, the extension wraps the dev server in an iframe relay. This means:
* Hot module replacement (HMR) works as normal — changes in your framework project appear instantly
* Two-way communication with the Csound backend is fully supported via `window.parent.postMessage`, handled transparently by the Cabbage JS API
* The Vite dev server must already be running before you save the `.csd`
#### Configuring the `cabbage.js` path
[Section titled “Configuring the cabbage.js path”](#configuring-the-cabbagejs-path)
A typical Cabbage project has `cabbage.js` in a `cabbage/` folder alongside the `.csd`, with the framework project in a subdirectory:
```text
MyPlugin/
├── MyPlugin.csd
├── cabbage.project.json
├── cabbage/
│ └── cabbage.js ← the Cabbage JS API
└── MyPlugin/ ← Vite / React / Svelte project
├── vite.config.js
└── src/
```
Because `cabbage.js` lives outside the framework project directory, bundlers can’t resolve it with a plain relative import like `'../cabbage/cabbage.js'` without a little configuration. Add a path alias and allow the dev server to serve files from the parent directory:
**`vite.config.js`:**
```js
import { defineConfig } from 'vite';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
resolve: {
alias: {
// 'cabbage' resolves to cabbage.js in the parent project folder
cabbage: path.resolve(__dirname, '../cabbage/cabbage.js')
}
},
server: {
fs: {
// Allow the dev server to serve files from outside the project root
allow: ['..']
}
}
});
```
With this in place, you can import the API with a clean bare specifier anywhere in your project:
```js
import { Cabbage } from 'cabbage';
```
### File Mode (Plain HTML / Built Output)
[Section titled “File Mode (Plain HTML / Built Output)”](#file-mode-plain-html--built-output)
When `entry` is a relative file path, the extension loads the HTML file directly in the webview. This works for:
* Vanilla HTML/JS projects
* Built output from a framework (e.g., `dist/index.html` after `npm run build`)
## Communication
[Section titled “Communication”](#communication)
Import the Cabbage API from `cabbage.js`:
```js
import { Cabbage } from './cabbage/cabbage.js'; // vanilla HTML
// or, with a bundler alias:
import { Cabbage } from 'cabbage'; // Vite / webpack
```
### Sending Data (UI → Backend)
[Section titled “Sending Data (UI → Backend)”](#sending-data-ui--backend)
Use `Cabbage.sendControlData()` to send parameter values to Csound:
```js
Cabbage.sendControlData({ channel: "frequency", value: 1000, gesture: "complete" }, null);
```
For continuous controls (sliders, XY pads), use the gesture sequence to support DAW automation recording:
```js
slider.addEventListener('pointerdown', () =>
Cabbage.sendControlData({ channel: "gain", value: slider.value, gesture: "begin" }, null));
slider.addEventListener('input', () =>
Cabbage.sendControlData({ channel: "gain", value: slider.value, gesture: "value" }, null));
slider.addEventListener('pointerup', () =>
Cabbage.sendControlData({ channel: "gain", value: slider.value, gesture: "end" }, null));
```
### Receiving Data (Backend → UI)
[Section titled “Receiving Data (Backend → UI)”](#receiving-data-backend--ui)
Use `Cabbage.addMessageListener()` to handle all incoming messages. It works in every environment — VS Code, DAW plugin, dev server iframe — without any environment-specific setup:
```js
Cabbage.addMessageListener((msg) => {
if (msg.command === 'parameterChange') {
// DAW automation or host parameter change.
// paramIdx/value may be top-level or nested in msg.data depending on the host.
const data = msg.data ?? msg;
updateControl(data.paramIdx, data.value);
} else if (msg.command === 'widgetUpdate') {
// Value update from cabbageSetValue opcode — id and value are top-level.
if (msg.value !== undefined) {
document.getElementById(msg.id).value = msg.value;
}
}
});
```
`addMessageListener` returns a cleanup function — useful in frameworks with component lifecycle hooks.
> **Avoid feedback loops**: When handling `parameterChange`, update the visual display only. Never call `sendControlData()` in response — it creates a loop that interferes with DAW automation.
## Examples
[Section titled “Examples”](#examples)
### Vanilla HTML
[Section titled “Vanilla HTML”](#vanilla-html)
A complete two-slider example:
```html
My Plugin
```
### Svelte
[Section titled “Svelte”](#svelte)
```svelte
```
### React
[Section titled “React”](#react)
```jsx
import { useEffect, useState } from 'react';
import { Cabbage } from 'cabbage';
export function MyControl() {
const [value, setValue] = useState(0);
useEffect(() => {
const remove = Cabbage.addMessageListener((msg) => {
if (msg.command === 'widgetUpdate' && msg.id === 'myChannel') {
setValue(msg.value);
}
});
return remove; // React calls this on unmount
}, []);
const onChange = (e) => {
const v = +e.target.value;
setValue(v);
Cabbage.sendControlData({ channel: 'myChannel', value: v, gesture: 'complete' }, null);
};
return ;
}
```
# Custom Widgets
> Creating custom widget classes for use with the Cabbage editor
Creating custom widget classes allows you to develop new controls while retaining the full editing tools provided by the Cabbage VS Code extension — including the property panel and drag-to-position editing. New widgets can be created using the command palette by selecting **Create New Custom Widget**.
If a custom widget folder does not already exist, the extension will prompt for its creation and automatically copy the required files into it. These files are placed inside a `src` directory, which must not be moved.
Custom widget classes must be placed in the `widgets` subdirectory within `src`, i.e. `CustomWidgetFolder/src/widgets`. This ensures that the extension can locate and load them via the property panel. The file name also serves as the class name.
Note
The custom widget folder must contain a top-level `cabbage` directory (i.e. the directory that contains the `widgets` subfolder). Maintaining this structure ensures that all paths are correctly resolved by Cabbage.
By convention, Cabbage classes use *UpperCamelCase*, while widget types use lowerCamelCase. The backend handles all value normalization required by the host DAW, allowing frontends to send and receive values in their full ranges (e.g., 20–20000 Hz for filter frequency, rather than normalized 0.0–1.0 values).
## Required Interface
[Section titled “Required Interface”](#required-interface)
For a widget to be recognized by the Cabbage VS Code extension — particularly by the property panel — it must implement the following:
### `this.props`
[Section titled “this.props”](#thisprops)
A JSON object containing properties accessible through the UI element inspectors in VS Code. Any property defined here can also be queried and modified using the Cabbage get and set opcodes in Csound. Properties can be objects, strings, or numbers. Boolean can be used only for set Cabbage properties such as `visible`, `active`, `automatable`, etc. Use 0/1 if you need to send boolean values to the backend.
At a minimum, the props object must include:
* An `id` property
* A `channels` array
* An `automatable` property (boolean)
* A `bounds` object to define dimensions and placement
* A `vscode` property (assigned to the VS Code API instance when running from VS Code, undefined otherwise)
* A `zIndex` property (integer)
* A `visible` property (boolean)
* An `active` property (boolean)
### Event Listener Methods
[Section titled “Event Listener Methods”](#event-listener-methods)
* **`addVsCodeEventListeners(widgetDiv, vs)`**: Called when Cabbage is running from VS Code. Initialise your VS Code API instance here.
* **`addEventListeners()`**: Called when Cabbage is running as a plugin.
Both methods need to be implemented to ensure compatibility with different environments. The simplest pattern is to put all the event handler code into `addEventListeners()`, and then call it from `addVsCodeEventListeners()`.
### `getInnerHTML()`
[Section titled “getInnerHTML()”](#getinnerhtml)
Returns the inner HTML used to render the UI element. The element’s size is determined by its `bounds` object. You can return an SVG element or a `div`, depending on your design. The `WidgetManager` class calls this method when the widget is first inserted (via `insertWidget()`) and whenever properties change (via `updateWidget()`).
For canvas-based widgets, `getInnerHTML()` is called during insertion to create the initial DOM structure, but subsequent updates use `updateCanvas()` instead to avoid destroying the canvas context.
### `createCanvas()` and `updateCanvas()` *(canvas widgets only)*
[Section titled “createCanvas() and updateCanvas() (canvas widgets only)”](#createcanvas-and-updatecanvas-canvas-widgets-only)
If you need a canvas for drawing, add a `createCanvas()` function that initialises the canvas and an `updateCanvas()` function that updates its contents. These are called by the `WidgetManager` to manage the embedded graphics context.
### Reactive Properties *(optional)*
[Section titled “Reactive Properties (optional)”](#reactive-properties-optional)
If you need to listen for changes to widget properties, wrap `this.props` with `CabbageUtils.createReactiveProps()`. This creates a Proxy that automatically handles common widget behaviours (like toggling pointer-events when `visible` or `active` change) and can notify you of property changes. Call it in your constructor:
```js
this.props = CabbageUtils.createReactiveProps(this, this.props, {
onPropertyChange: (change) => {
// change.key - the property name
// change.value - new value
// change.oldValue - previous value
// change.path - dot-separated path to the property
},
watchKeys: null, // null = watch all, or array of strings/RegExp
mode: 'change', // 'change' = only notify when value differs, 'set' = notify on every set
lazyPath: true // compute path only when notifying (more efficient)
});
```
The `opts` parameter is optional — you can call `CabbageUtils.createReactiveProps(this, this.props)` without options for basic reactive behaviour.
> **Performance Note**: Don’t use `onPropertyChange` to listen for `value` updates from Csound resulting from calls to `cabbageSetValue`. The `WidgetManager` automatically calls `updateCanvas()` for canvas-based widgets when channel values change, bypassing the reactive props system for efficiency. Value updates are sent as lightweight messages without full widget JSON, allowing high-frequency updates (e.g., for k-rate parameter changes) without overhead. Use reactive props for UI-driven property changes (like `visible`, `bounds`, or custom properties), not for high-frequency value streams.
## Sending Data to the Backend
[Section titled “Sending Data to the Backend”](#sending-data-to-the-backend)
To send data from your widget to the Cabbage backend, use `Cabbage.sendControlData()`:
```js
Cabbage.sendControlData({ channel: "myChannel", value: 42, gesture: "complete" }, this.vscode);
```
**Parameters:**
* `channel`: The channel name (string)
* `value`: The value in its natural range (e.g., 20–20000 Hz for filter frequency)
* `gesture`: Optional — `"begin"`, `"value"`, `"end"`, or `"complete"` (default)
* `vscode`: The VS Code API instance (`this.vscode` in your widget; safely ignored as a plugin)
The backend automatically determines whether the channel is automatable and routes accordingly. For non-automatable data you can also call `sendChannelData()` directly:
```js
Cabbage.sendChannelData("myStringChannel", "hello", this.vscode);
Cabbage.sendChannelData("myNumberChannel", 3.14, this.vscode);
```
## Channel Communication
[Section titled “Channel Communication”](#channel-communication)
The `channels` array defines the channels used to send value updates from the front end to the back end. If no `this.props.id` is defined, the first `channel.id` in the array is used as the primary DOM identifier.
For multi-channel widgets (like an EQ controller with separate frequency and gain channels per band), the `WidgetManager` automatically routes incoming parameter updates to the correct channel based on the channel ID. Each channel should have:
* `id`: Unique channel identifier
* `range`: Object with `min`, `max`, `defaultValue`, and optionally `value`
* `event`: Optional event type (e.g., `"valueChanged"`, `"mouseDragX"`, `"mouseDragY"`)
## CabbageUtils Helper Functions
[Section titled “CabbageUtils Helper Functions”](#cabbageutils-helper-functions)
### `CabbageUtils.getWidgetDivId(props)`
[Section titled “CabbageUtils.getWidgetDivId(props)”](#cabbageutilsgetwidgetdividprops)
Returns the DOM ID for the widget’s div element. Prioritises `props.id`, then falls back to `channels[0].id`.
```js
const divId = CabbageUtils.getWidgetDivId(this.props);
```
### `CabbageUtils.getWidgetDiv(channelOrProps)`
[Section titled “CabbageUtils.getWidgetDiv(channelOrProps)”](#cabbageutilsgetwidgetdivchannelorprops)
Returns the actual DOM element for the widget div. Accepts either a props object or a string div ID. Returns `null` if not found.
```js
const widgetDiv = CabbageUtils.getWidgetDiv(this.props);
const widgetDiv = CabbageUtils.getWidgetDiv("myWidgetId");
```
### `CabbageUtils.getChannelId(props, index)`
[Section titled “CabbageUtils.getChannelId(props, index)”](#cabbageutilsgetchannelidprops-index)
Returns the ID string of the nth channel from the `channels` array. Throws an error if the channel or its ID is not set.
```js
const firstChannelId = CabbageUtils.getChannelId(this.props, 0);
const secondChannelId = CabbageUtils.getChannelId(this.props, 1);
```
# Cabbage JS API
> Reference for the Cabbage JavaScript API, which carries values and messages between a web interface and the Cabbage backend.
The Cabbage JavaScript API provides the communication layer between webview UIs and the Cabbage backend. This API enables bidirectional communication for plugin development.
Note
A `{...}` JSON structure is still required to define parameters for use in a host application. Objects declared in this structure are accessible through the API via a custom event handler (see below). These objects are sent at load time and can be updated at runtime using the [`cabbageSet`](/docs/opcodes/cabbageset/) and [`cabbageSetValue`](/docs/opcodes/cabbagesetvalue/) opcodes.
## Architecture Overview
[Section titled “Architecture Overview”](#architecture-overview)
### UI → Backend (Outgoing Communication)
[Section titled “UI → Backend (Outgoing Communication)”](#ui--backend-outgoing-communication)
Three functions cover everything a UI sends to the backend. Use `Cabbage.sendControlData()` to send widget value changes; the backend automatically determines routing based on channel automatable status. Use `Cabbage.sendWidgetUpdate()` to send widget *property* updates (position, visibility, colours, and so on) as extended JSON packets. Use `Cabbage.sendChannelData()` to write a channel directly, bypassing the widget layer.
### Backend → UI (Incoming Communication)
[Section titled “Backend → UI (Incoming Communication)”](#backend--ui-incoming-communication)
Use `Cabbage.addMessageListener(callback)` to receive messages from the backend. This works in all environments — VS Code, native DAW plugins, and custom UI frameworks — without any environment-specific setup.
## API Reference
[Section titled “API Reference”](#api-reference)
### Core Functions
[Section titled “Core Functions”](#core-functions)
#### `Cabbage.addMessageListener(callback)`
[Section titled “Cabbage.addMessageListener(callback)”](#cabbageaddmessagelistenercallback)
Register a listener for incoming messages from the Cabbage backend. This is the recommended way to handle all backend → UI communication. It abstracts the two runtime environments so that the same UI code works everywhere:
* **VS Code (custom UI / iframe relay)**: messages arrive as `window` message events
* **Native plugin (DAW)**: messages arrive via `window.hostMessageCallback`
**Parameters:**
* `callback` (function): Called with each incoming message object
**Returns:** A cleanup function — call it when the UI is torn down to avoid memory leaks.
**Incoming message shapes:**
| `command` | Fields | Sent by |
| ------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
| `parameterChange` | `paramIdx`, `value` (may be top-level or nested in `data`) | DAW automation / backend |
| `widgetUpdate` | `id`, `value` | [`cabbageSetValue`](/docs/opcodes/cabbagesetvalue/) opcode |
| `widgetUpdate` | `id`, `widgetJson` | [`cabbageSet`](/docs/opcodes/cabbageset/) opcode |
| `channelDataUpdate` | `channel`, `value` | Csound channel data |
| `resizeResponse` | `accepted`, `width`, `height` | Response to `requestResize()` |
**Example — Vanilla HTML:**
```js
import { Cabbage } from './cabbage/cabbage.js';
Cabbage.addMessageListener((msg) => {
if (msg.command === 'parameterChange') {
// paramIdx/value may be top-level or nested in msg.data (DAW host dependent)
const data = msg.data ?? msg;
updateSlider(data.paramIdx, data.value);
} else if (msg.command === 'widgetUpdate') {
// id and value are always top-level
if (msg.value !== undefined) {
document.getElementById(msg.id).value = msg.value;
} else if (msg.widgetJson !== undefined) {
const widget = JSON.parse(msg.widgetJson);
// apply full widget update...
}
}
});
```
**Example — Svelte:**
```js
import { Cabbage } from 'cabbage';
import { onMount, onDestroy } from 'svelte';
let removeListener;
onMount(() => {
removeListener = Cabbage.addMessageListener((msg) => {
if (msg.command === 'widgetUpdate' && msg.id === 'myChannel') {
myValue = msg.value;
}
});
});
onDestroy(() => removeListener?.());
```
***
#### `Cabbage.sendControlData(data, vscode)`
[Section titled “Cabbage.sendControlData(data, vscode)”](#cabbagesendcontroldatadata-vscode)
Send widget value changes to the Cabbage backend. This is the primary API for user interactions.
**Parameters:**
* `data` (Object): Control data object
* `channel` (string): The channel name
* `value` (number|string): Value in natural range (e.g., 20-20000 Hz for frequency)
* `gesture` (string, optional): Gesture type - `"begin"`, `"value"`, `"end"`, `"complete"` (default: `"complete"`)
* `vscode` (Object|null): VS Code API instance (pass `null` for plugin mode)
**Behavior:**
* Automatically routes based on channel automatable status
* Thread-safe: Asynchronous, doesn’t block audio thread
* Values sent in natural ranges, backend handles normalization
* **String values**: on a non-automatable channel, a string value is stored as the channel’s `stringValue` (and into the matching Csound string channel). String state is included in session/preset saves and re-pushed in the launch dumps — see [Persisting Custom UI State](/docs/custom-ui/persisting-ui-state/). The channel must be declared with `"type": "string"`, e.g. `"channels": [{ "id": "myState", "type": "string" }]`.
**Examples:**
```js
// Basic usage
Cabbage.sendControlData({ channel: "frequency", value: 1000 }, null);
// String state (persists across UI reloads via session save)
Cabbage.sendControlData({ channel: "myState", value: JSON.stringify(myState) }, null);
// With gesture for DAW automation recording
Cabbage.sendControlData({ channel: "volume", value: 0.8, gesture: "begin" }, null);
Cabbage.sendControlData({ channel: "volume", value: 0.9, gesture: "value" }, null);
Cabbage.sendControlData({ channel: "volume", value: 0.9, gesture: "end" }, null);
```
***
#### `Cabbage.sendChannelData(channel, data, vscode)`
[Section titled “Cabbage.sendChannelData(channel, data, vscode)”](#cabbagesendchanneldatachannel-data-vscode)
Send channel data directly to Csound without DAW automation — bypasses all widget routing and validation.
**Parameters:**
* `channel` (string): Csound channel name
* `data` (number|string): Data to send
* `vscode` (Object|null): VS Code API instance
**Notes:**
* Use `sendControlData()` instead for most cases as it determines the best route automatically
* Bypasses DAW automation system
* Automatically determines string vs numeric data
***
#### `Cabbage.sendWidgetUpdate(widget, vscode)`
[Section titled “Cabbage.sendWidgetUpdate(widget, vscode)”](#cabbagesendwidgetupdatewidget-vscode)
Send extended JSON widget property packets to the Cabbage backend. The packet is merge-patched into the stored widget object, so this is the recommended way for a custom frontend to update widget properties such as `bounds`, `visible`, or colours at runtime. It is the counterpart to `sendControlData()`: values travel through `sendControlData()`, everything else travels through here.
**Parameters:**
* `widget` (Object): Widget packet. Must identify the widget with an `id`, either a top-level `id` or any `channels[].id`, plus the properties to change
* `vscode` (Object|null): VS Code API instance (pass `null` for plugin mode)
**Behavior:**
* Merge-patch semantics: only the supplied properties are merged, so partial packets are fine, but a `null` value **deletes** its key, and **arrays replace wholesale**, so always send complete arrays (e.g. a full `channels` array, never a fragment of one)
* Values cannot travel this way: `value` and `range.value` are stripped before sending, along with binding and runtime keys (`parameterIndex`, `channel`, `samples`, file paths), value changes must go through `sendControlData()` (or `sendChannelData()`). Stripping happens twice, `sendWidgetUpdate()` sanitizes the packet via `CabbageUtils.sanitizeForEditor()`, and the backend strips the same keys again before merging, so the guarantee holds even for hand-built packets. Note stored widgets may already show a backend-stamped top-level `value`: that is live runtime state, and a packet’s `value` never overwrites it
* Fire-and-forget: the backend applies the patch without echoing it back, so apply the change locally in the UI as well; packets with an unknown id fail silently, so double-check the spelling. The silence is deliberate: re-broadcasting full widget packets, which can carry large `samples` arrays, risks echo loops that flood the strings-only message queue, so property updates are applied without notifying other views
**Example:**
```js
// Move a widget and hide another, from a custom frontend
Cabbage.sendWidgetUpdate({ id: "mainGain", bounds: { left: 40, top: 20 } }, null);
Cabbage.sendWidgetUpdate({ id: "debugPanel", visible: 0 }, null);
```
***
#### `Cabbage.sendMidiMessageFromUI(statusByte, dataByte1, dataByte2, vscode)`
[Section titled “Cabbage.sendMidiMessageFromUI(statusByte, dataByte1, dataByte2, vscode)”](#cabbagesendmidimessagefromuistatusbyte-databyte1-databyte2-vscode)
Send MIDI messages from the UI to the backend.
**Parameters:**
* `statusByte` (number): MIDI status byte
* `dataByte1` (number): First data byte
* `dataByte2` (number): Second data byte
* `vscode` (Object|null): VS Code API instance
**Example:**
```js
Cabbage.sendMidiMessageFromUI(144, 60, 100, null); // Note on
```
***
#### `Cabbage.MidiMessageFromHost(statusByte, dataByte1, dataByte2)`
[Section titled “Cabbage.MidiMessageFromHost(statusByte, dataByte1, dataByte2)”](#cabbagemidimessagefromhoststatusbyte-databyte1-databyte2)
Handle incoming MIDI messages from the backend (callback function).
**Parameters:**
* `statusByte` (number): MIDI status byte
* `dataByte1` (number): First data byte
* `dataByte2` (number): Second data byte
***
#### `Cabbage.isReadyToLoad(vscode)`
[Section titled “Cabbage.isReadyToLoad(vscode)”](#cabbageisreadytoloadvscode)
Signal that the UI is ready to load and initialize. This must be called in order to notify the backend that the front has completed its initial load.
**Parameters:**
* `vscode` (Object|null): VS Code API instance
### Utility Functions
[Section titled “Utility Functions”](#utility-functions)
***
#### `Cabbage.triggerFileOpenDialog(vscode, channel, options)`
[Section titled “Cabbage.triggerFileOpenDialog(vscode, channel, options)”](#cabbagetriggerfileopendialogvscode-channel-options)
Trigger a file open dialog for file selection widgets.
**Parameters:**
* `vscode` (Object|null): VS Code API instance
* `channel` (string): Associated channel name
* `options` (Object, optional): Dialog options
* `directory` (string): Starting directory
* `filters` (string): File filters
* `openAtLastKnownLocation` (boolean): Use last known location
**Example:**
```js
Cabbage.triggerFileOpenDialog(null, "audioFile", {
filters: "*.wav;*.aiff",
openAtLastKnownLocation: true
});
```
***
#### `Cabbage.openUrl(vscode, url, file)`
[Section titled “Cabbage.openUrl(vscode, url, file)”](#cabbageopenurlvscode-url-file)
Open a URL or file in the system default application.
**Parameters:**
* `vscode` (Object|null): VS Code API instance
* `url` (string): URL to open
* `file` (string): File path to open
***
#### `Cabbage.consumeKeypresses(consume)` *(Windows plugin mode only)*
[Section titled “Cabbage.consumeKeypresses(consume) (Windows plugin mode only)”](#cabbageconsumekeypressesconsume-windows-plugin-mode-only)
Control whether keyboard events are captured by the webview or forwarded to the host DAW.
By default (`false`), all key events are passed through to the DAW so keyboard shortcuts keep working while the plugin UI has focus. This is handled automatically by Cabbage’s internal key listener.
**Parameters:**
* `consume` (boolean): `true` to capture keys in the webview, `false` to pass them through to the DAW (default)
**Notes:**
* Native `` and `