AUDIO
Source snapshot · 6 November 2026

Plug-in export layer

Editors

Product::with_editor(Editor { html, width, height, resizable }) gives a product a web editor: one HTML page of up to 1 MiB, between 64 and 4096 pixels in each direction. Resizable editors may grow to four times their size.

The editor is built only with the ravet-plugin feature editor (umbrella plugin-editor), which adds ravet-webview:

ravet-plugin = { version = "…", features = ["clap", "native", "editor"] }

Without it, clap and native build no web view: the CLAP export offers no GUI extension and the native table reports a 0 × 0 editor, so adapters show none. Products without an editor leave the feature off and link no WebKit or Cocoa bindings. The delay and gain examples turn it on.

The editor opens in every format that has an embedded editor on the platform (decision 0293):

Platform Web view CLAP VST3 LV2 AAX AUv2
macOS Cocoa/WebKit cocoa NSView CocoaUI NSView Cocoa view
Windows Win32/WebView2 win32 HWND WindowsUI HWND —
Linux GTK 3/WebKitGTK 4.1 on X11 or XWayland x11 X11EmbedWindowID X11UI — —

Windows needs the WebView2 Runtime installed (Windows 11 ships it); editor sizes there are the host's physical pixels, and WebView2 scales page content by the monitor's DPI, so above 100 % display scaling a fixed-size page has fewer CSS pixels than its declared size. Keep layouts responsive; content-scale negotiation is open work. On Linux the parent is an X11 window, which need not be an XEmbed socket: the editor's GtkPlug is mapped inside plain host windows such as REAPER's. GTK work is pumped by editor_idle (VST3 IRunLoop timer, LV2 idle interface) or a CLAP host timer; hosts already running GTK on Wayland, or built on GTK 2/GTK 4, are refused. Keyboard input in Windows and Linux editors is not validated yet. Evidence and hosts are listed in the support matrix and work status.

Editor assets

Images, fonts, scripts and styles need not be inlined into the 1 MiB page or sent through 64 KiB messages. Declare them as EditorAssets and refer to them by relative URL:

```rust,ignore use ravet_plugin::{Editor, EditorAsset};

const ASSETS: &[EditorAsset] = &[ EditorAsset::new("app.js", include_bytes!("../editor/app.js")), EditorAsset::new("img/knob.png", include_bytes!("../editor/img/knob.png")), // An explicit content type when the extension does not give one. EditorAsset { path: "data/curve.bin", mime: "application/octet-stream", data: include_bytes!("../editor/curve.bin") }, ]; // Product::new(…).with_editor(Editor { … }).with_editor_assets(ASSETS) ```

The page then refers to each asset by its relative path, exactly as declared: a stylesheet link to style.css, a script element for app.js and an image whose source is img/knob.png.

With assets, the page itself is served as index.html from a local asset origin that also serves them: ravet://editor/ on macOS, ravet-<id>://editor/ on Linux and https://editor.ravet.invalid/ on Windows. Requests are answered from the binary's memory (GET only, 404 for unknown paths); nothing is read from disk and nothing reaches the network. Products without assets keep the inline page. Limits, checked by Product::resolve (ProductError::InvalidEditorAsset): at most 4,096 assets, 64 MiB each and 256 MiB in total; paths of at most 512 bytes from ASCII letters, digits and ._-/, with no ., .. or empty segment; index.html is reserved. An empty mime infers the type from the extension (HTML, CSS, JavaScript, JSON, SVG, PNG, JPEG, GIF, WebP, ICO, WOFF/WOFF2, TTF/OTF, WASM, WAV, MP3, text), else application/octet-stream. Each request copies the asset once. The Gain example ships asset.css this way.

RAVET injects its editor client into the page's <head>. The client speaks the editor protocol and exposes window.ravetPlugin:

Member Purpose
info Product and parameter metadata, or null until it arrives
value(key), normalized(key) Current physical or 0–1 value
on("info" \| "values" \| "product", listener) Metadata, value updates ({values, changed}) and product messages
set(key, value), setNormalized(key, n) One complete edit
begin(key), update(key, value), updateNormalized(key, n), end(key) A continuous gesture
send(data) A product-channel message; PluginExport::editor_message may reply

On macOS, focusing a text field in the page takes keyboard focus from hosts that keep it for their own shortcuts, such as Pro Tools, and gives it back when the field loses focus; see keyboard focus. The editor protocol is unchanged.

examples/gain-plugin/editor/index.html is a minimal example; examples/delay-plugin/editor/index.html draws knobs and a division selector.

Editor edits update the parameter cache immediately, then travel through a bounded lock-free queue (256 edits) to whichever thread delivers parameter events. That thread sends the host gesture-begin, value and gesture-end events, and gives the processor the values at the start of the block; host automation later in the block still wins. A complete edit is queued all at once or not at all. If the queue is full, the edit is dropped and the page keeps showing the host's value. Closing the editor ends every gesture the page left open.

The web view asks the plug-in for its next message 20 times a second. Queued messages (info, product replies) go first, then a snapshot, which is rebuilt only when a value changes. Messages are limited to 64 KiB. A product with so many parameters that info exceeds this receives no info. Static data belongs in editor assets; larger dynamic data still has to be split by the product.