C API Reference¶
Plugin Functions (minihost.h)¶
| Function | Description |
|---|---|
mh_open |
Load a plugin |
mh_open_ex |
Load a plugin with sidechain channel configuration |
mh_open_async |
Load a plugin in a background thread |
mh_close |
Unload a plugin |
mh_get_path |
Get the plugin file path passed to mh_open / mh_open_ex |
mh_get_info |
Get plugin info (channels, params, latency, MIDI capabilities) |
mh_probe |
Get plugin metadata without full instantiation |
mh_scan_directory |
Recursively scan directory for plugins |
Audio Processing¶
| Function | Description |
|---|---|
mh_process |
Process audio (non-interleaved float32 buffers) |
mh_process_midi |
Process audio with MIDI input |
mh_process_midi_io |
Process audio with MIDI input and output |
mh_process_auto |
Process with sample-accurate parameter automation and MIDI |
mh_process_sidechain |
Process audio with sidechain input |
mh_process_double |
Process audio with 64-bit double precision |
mh_supports_double |
Check if plugin supports native double precision |
Parameters¶
| Function | Description |
|---|---|
mh_get_num_params |
Get parameter count |
mh_get_param |
Get parameter value (normalized 0.0-1.0) |
mh_set_param |
Set parameter value (normalized 0.0-1.0) |
mh_get_param_info |
Get parameter metadata (name, label, default, steps, ID, category) |
mh_param_to_text |
Convert normalized value to display string (e.g., "2500 Hz") |
mh_param_from_text |
Convert display string to normalized value |
mh_begin_param_gesture |
Signal start of parameter change gesture |
mh_end_param_gesture |
Signal end of parameter change gesture |
Parameter Morphing¶
A/B interpolation over the normalized parameter values (a "snapshot" is one value per parameter). Operates on parameters, not opaque state blobs, since only parameters interpolate. Only continuous parameters glide; stepped/boolean parameters are quantized by the plugin.
| Function | Description |
|---|---|
mh_morph_capture |
Capture every parameter's current normalized value into an array (returns the count) |
mh_morph_apply |
Set every parameter from a snapshot array (values clamped to 0..1) |
mh_morph_lerp |
Interpolate two snapshots with one blend amount t: out = clamp01(a + (b - a) * t) (pure math) |
mh_morph_lerp_per_param |
Interpolate two snapshots with a per-parameter t array |
mh_morph |
Interpolate two snapshots at scalar t and apply to the plugin in one call |
int n = mh_get_num_params(p);
float a[512], b[512]; // n <= 512 for this example
mh_set_program(p, 0); mh_morph_capture(p, a, n); // snapshot A
mh_set_program(p, 1); mh_morph_capture(p, b, n); // snapshot B
mh_morph(p, a, b, n, 0.5f); // apply the 50% blend
State Management¶
| Function | Description |
|---|---|
mh_get_state_size |
Get full state size in bytes |
mh_get_state |
Save full plugin state |
mh_set_state |
Restore full plugin state |
mh_get_program_state_size |
Get current program state size |
mh_get_program_state |
Save current program state |
mh_set_program_state |
Restore current program state |
Factory Presets¶
| Function | Description |
|---|---|
mh_get_num_programs |
Get number of factory presets |
mh_get_program_name |
Get preset name by index |
mh_get_program |
Get current preset index |
mh_set_program |
Load preset by index |
Transport and Playback¶
| Function | Description |
|---|---|
mh_set_transport |
Set transport info (BPM, time signature, position, play state) |
mh_get_bypass |
Get bypass state |
mh_set_bypass |
Set bypass state |
mh_reset |
Reset internal state (clears delay lines, filter states) |
mh_set_non_realtime |
Enable higher-quality algorithms for offline processing |
Configuration¶
| Function | Description |
|---|---|
mh_get_sample_rate |
Get current sample rate |
mh_set_sample_rate |
Change sample rate (preserves parameter state) |
mh_get_latency_samples |
Get plugin latency in samples |
mh_get_tail_seconds |
Get reverb/delay tail length in seconds |
mh_get_processing_precision |
Get processing precision (single/double) |
mh_set_processing_precision |
Set processing precision (single/double) |
mh_set_track_properties |
Set track name and/or color metadata |
Bus Layout¶
| Function | Description |
|---|---|
mh_get_num_buses |
Get number of input or output buses |
mh_get_bus_info |
Get bus info (name, channels, is_main, is_enabled) |
mh_check_buses_layout |
Check if a bus layout is supported |
mh_get_sidechain_channels |
Get configured sidechain channel count |
MH_Info.num_input_ch is the main input bus only -- the width of the
inputs array the mh_process* functions expect. Sidechain channels are
reported by mh_get_sidechain_channels and supplied separately to
mh_process_sidechain. Before C ABI 2.4.0 num_input_ch summed every input
bus, which made callers over-provision the main buffer and pushed the
sidechain past the channels the plugin reads.
Change Notifications¶
| Function | Description |
|---|---|
mh_set_change_callback |
Register callback for processor-level changes (latency, param info, program, non-param state) |
mh_set_param_value_callback |
Register callback for plugin-initiated parameter value changes |
mh_set_param_gesture_callback |
Register callback for parameter gesture begin/end |
Constants: MH_CHANGE_LATENCY, MH_CHANGE_PARAM_INFO, MH_CHANGE_PROGRAM, MH_CHANGE_NON_PARAM_STATE
Audio Device Functions (minihost_audio.h)¶
| Function | Description |
|---|---|
mh_audio_open |
Open audio device for real-time plugin playback |
mh_audio_open_chain |
Open audio device for real-time chain playback |
mh_audio_close |
Close audio device |
mh_audio_start |
Start audio playback |
mh_audio_stop |
Stop audio playback |
mh_audio_is_playing |
Check if audio is currently playing |
mh_audio_set_input_callback |
Set input audio callback for effect plugins |
mh_audio_get_sample_rate |
Get actual device sample rate |
mh_audio_get_buffer_frames |
Get actual buffer size in frames |
mh_audio_get_channels |
Get number of output channels |
Set MH_AudioConfig.capture = 1 to open the device in duplex mode (system audio input routed through the plugin). The audio callback de-interleaves captured audio directly into the plugin's input buffers.
Set MH_AudioConfig.playback_device_index / capture_device_index to a 0-based device index to target a specific audio device. Use -1 (the default) to use the system default.
Device Enumeration¶
| Function | Description |
|---|---|
mh_audio_enumerate_playback_devices |
Fill an array of MH_AudioDeviceInfo with available playback devices. Returns count |
mh_audio_enumerate_capture_devices |
Fill an array of MH_AudioDeviceInfo with available capture devices. Returns count |
MH_AudioDeviceInfo struct:
-
char name[256]-- device name (null-terminated) -
int is_default--1if this is the system default, else0
Typical usage:
MH_AudioDeviceInfo devices[32];
int count = mh_audio_enumerate_playback_devices(devices, 32);
for (int i = 0; i < count; i++) {
printf("[%d]%s %s\n", i, devices[i].is_default ? "*" : " ", devices[i].name);
}
Audio Input (Ring Buffer)¶
| Function | Description |
|---|---|
mh_audio_enable_input |
Enable ring buffer audio input with given capacity |
mh_audio_disable_input |
Disable ring buffer input (revert to silence) |
mh_audio_write_input |
Write interleaved float32 frames into input ring buffer (thread-safe) |
mh_audio_input_available |
Get number of frames available for reading |
MIDI Connections¶
| Function | Description |
|---|---|
mh_audio_connect_midi_input |
Connect MIDI input port to device |
mh_audio_connect_midi_output |
Connect MIDI output port to device |
mh_audio_disconnect_midi_input |
Disconnect MIDI input |
mh_audio_disconnect_midi_output |
Disconnect MIDI output |
mh_audio_get_midi_input_port |
Get connected MIDI input port index (-1 if none) |
mh_audio_get_midi_output_port |
Get connected MIDI output port index (-1 if none) |
mh_audio_create_virtual_midi_input |
Create virtual MIDI input port |
mh_audio_create_virtual_midi_output |
Create virtual MIDI output port |
mh_audio_is_midi_input_virtual |
Check if MIDI input is a virtual port |
mh_audio_is_midi_output_virtual |
Check if MIDI output is a virtual port |
mh_audio_send_midi |
Send MIDI event programmatically to plugin |
Parameter Changes¶
| Function | Description |
|---|---|
mh_audio_send_param |
Queue a parameter change from application code |
mh_audio_send_param_control |
Queue one from a control-surface thread (its own ring) |
Both take (dev, plugin_index, param_index, value), where plugin_index
selects the chain slot on a device opened with mh_audio_open_chain and is 0
otherwise. value is normalized 0..1 and clamped when applied.
Prefer these to mh_set_param while a device is running. mh_set_param takes
the plugin's state mutex and writes the parameter underneath a running
processBlock, which by contract takes no lock -- so nothing orders the two
and the change lands at an undefined point in the block. These queue the change
lock-free; the audio thread drains at the top of the next block and applies it
through the _auto process entry point.
Call each from a single thread. There are two rings for the same single-producer reason the MIDI pair has: sharing one between two producers corrupts its indices. The drain coalesces per parameter per block, so a fader drag emitting hundreds of values costs one parameter write.
Host Playhead¶
| Function | Description |
|---|---|
mh_audio_set_transport_enabled |
Enable the host playhead (off by default) |
mh_audio_get_transport_enabled |
Is it enabled? |
mh_audio_transport_play / _stop |
Start / stop the playhead |
mh_audio_transport_set_bpm |
Set tempo |
mh_audio_transport_set_time_sig |
Set time signature |
mh_audio_transport_set_position |
Move the playhead, in samples |
mh_audio_transport_set_loop |
Set loop points, in samples |
mh_audio_transport_set_recording |
Set the recording flag passed to the plugin |
mh_audio_get_transport |
Read the current playhead into an MH_TransportInfo |
Off by default, in which case the plugin is told there is no transport. The audio thread owns the transport and is its only writer; setters post to a lock-free command ring it drains each block, so a setter never blocks and its effect is visible one block later. Call the setters from a single thread.
A device opened on a chain hands the playhead to every plugin in it, resolved once at open.
mh_audio_get_transport copies the most recently published snapshot. Rotating
buffers make a torn read require the reader to be descheduled across several
audio callbacks mid-copy; that is a bound, not a proof of atomicity.
OSC Input¶
| Function | Description |
|---|---|
mh_audio_connect_osc |
Listen for OSC on a UDP port and drive parameters directly |
mh_audio_disconnect_osc |
Stop listening |
mh_audio_get_osc_port |
Bound port, or -1 |
mh_audio_set_slot_name |
Give a chain slot a stable name for addressing |
mh_audio_get_slot_name |
The name given to a slot, or NULL |
Recognised addresses, each taking one float in 0..1 unless noted:
/mh/param/<index>, /mh/<slot>/param/<index>, /mh/<name>/param/<index>,
/mh/transport/play, /stop, /bpm, /position (beats), /loop,
/record. Anything else is ignored.
Parameters are addressed only by index: resolving a parameter name means a table of every parameter and the plugin's own lock, and the socket thread must have neither. The value goes straight onto the control parameter ring, so the socket thread never blocks and never takes a lock.
Chain slots are the exception, and are worth the small table.
/mh/<slot>/param/<index> addresses a slot by position, which is stable only
while the chain is built the same way -- and a generated layout outlives the
code that builds the chain. mh_audio_set_slot_name attaches a name to the
plugin rather than to a position, so the address survives the chain being
rebuilt in a different order. Names are alphanumeric, start with a letter (which
keeps them distinct from the numeric form), must be unique, and must be set
before mh_audio_connect_osc -- the table is read by the socket thread and is
never written while that thread exists.
OSC Functions (minihost_osc.h)¶
OSC input and output, built on JUCE's juce_osc module rather than a vendored
OSC library -- JUCE is already a dependency, juce_osc needs only
juce_events, and it carries the same licence as every other JUCE module
minihost links.
| Function | Description |
|---|---|
mh_osc_server_open |
Bind a UDP port and start listening. Port 0 lets the OS choose |
mh_osc_server_close |
Stop listening. Blocks until the socket thread has stopped |
mh_osc_server_get_port |
The port actually bound |
mh_osc_client_open |
Open a sender aimed at host:port |
mh_osc_client_close |
Close the sender |
mh_osc_send_float / _int / _string |
Send a message with one argument |
mh_osc_send_bang |
Send a message with no arguments (a trigger) |
mh_osc_send_floats |
Send a message with an array of floats |
mh_osc_address_matches |
Does a pattern match a concrete address? |
mh_osc_is_valid_address |
Is this a valid OSC address pattern? |
typedef void (*MH_OscCallback)(const char* address, const float* args,
int num_args, void* user_data);
The callback runs on the OSC socket thread, not the audio thread and not a JUCE message thread. It must not block: that thread is the only reader, so a slow callback costs incoming messages. The intended shape is a push onto a lock-free ring that some other thread drains.
Argument types: float32 arrives as itself and int32 is converted; any other
type (string, blob, time tag) is reported as 0.0f rather than skipped, so
argument positions stay aligned with what the sender wrote.
Scope, stated rather than discovered later:
- UDP only. No TCP, no SLIP. This is what tablet surfaces use; a serial OSC device is not served.
- Bundle time tags are parsed but not scheduled. Bundle contents are delivered immediately, which is right for control and wrong for sequencing.
Audio File I/O Functions (minihost_audiofile.h)¶
| Function | Description |
|---|---|
mh_audio_read |
Read audio file to interleaved float32 buffer |
mh_audio_data_free |
Free decoded audio data returned by mh_audio_read |
mh_audio_write |
Write interleaved float32 data to WAV or FLAC file |
mh_audio_get_file_info |
Get audio file metadata without decoding |
mh_audio_resample |
Resample interleaved float32 audio between any two sample rates |
Supported Formats¶
| Format | Read | Write |
|---|---|---|
| WAV | Yes | Yes (16/24/32-bit) |
| FLAC | Yes | Yes (16/24-bit) |
| MP3 | Yes | No |
| Vorbis | Yes | No |
Structs¶
MH_AudioData -- returned by mh_audio_read():
-
float* data-- interleaved float32 samples -
unsigned int channels -
unsigned int frames -
unsigned int sample_rate
MH_AudioFileInfo -- populated by mh_audio_get_file_info():
-
unsigned int channels -
unsigned int sample_rate -
unsigned long long frames -
double duration
Plugin Chain Functions (minihost_chain.h)¶
| Function | Description |
|---|---|
mh_chain_create |
Create chain from array of plugins (all must share sample rate) |
mh_chain_close |
Close chain (does not close individual plugins) |
mh_chain_process |
Process audio through chain |
mh_chain_process_midi_io |
Process with MIDI I/O. MIDI enters the first plugin that accepts it and is carried onward by any plugin reporting produces_midi, so a MIDI effect can drive an instrument behind it; midi_out is what leaves the last plugin |
mh_chain_process_auto |
Process with sample-accurate parameter automation and MIDI |
mh_chain_get_latency_samples |
Get total chain latency (sum of all plugins) |
mh_chain_get_num_plugins |
Get number of plugins in chain |
mh_chain_get_plugin |
Get plugin by index |
mh_chain_get_num_input_channels |
Get input channel count (from first plugin) |
mh_chain_get_num_output_channels |
Get output channel count (from last plugin) |
mh_chain_get_sample_rate |
Get sample rate (shared by all plugins) |
mh_chain_get_max_block_size |
Get maximum block size |
mh_chain_reset |
Reset all plugins in chain |
mh_chain_set_non_realtime |
Set non-realtime mode for all plugins |
mh_chain_get_tail_seconds |
Get maximum tail length (max of all plugins) |
Plugin Bus Functions (minihost_graph.h)¶
Parallel-branches-summed routing: fan one input to N MH_PluginChain branches and sum their outputs with per-branch gain. Python: PluginBus. (File name retained from before the 0.2.0 rename; symbols are mh_bus_*.)
| Function | Description |
|---|---|
mh_bus_create |
Create a bus for a fixed I/O channel layout, block size, and sample rate |
mh_bus_close |
Close the bus (does not close the branches) |
mh_bus_add_branch |
Add a MH_PluginChain branch with a linear gain; returns branch index. Rejects channel/sample-rate mismatch |
mh_bus_set_branch_gain / mh_bus_get_branch_gain |
Set/get a branch's summing gain (0.0 mutes; muted branches skip processing) |
mh_bus_get_num_branches |
Number of branches |
mh_bus_process |
Fan input to every branch, sum (per-branch gain) into outputs |
mh_bus_process_midi |
As mh_bus_process, but also fan the same MIDI to every branch (to each branch's first plugin). Branch MIDI output is not collected |
mh_bus_get_num_input_channels / mh_bus_get_num_output_channels |
Configured channel counts |
mh_bus_get_sample_rate / mh_bus_get_max_block_size |
Configured sample rate / block size |
mh_bus_get_latency_samples / mh_bus_get_tail_seconds |
Maximum latency / tail across branches |
The general DAG executor (Python PluginGraph) lives in minihost_graph_v2.h as the mh_graph_* / MH_PluginGraph family, with a C++ RAII wrapper minihost::PluginGraph in minihost_graph_v2.hpp. Its surface is large (node add/connect, MIDI routing, automation, mh_graph_compile, mh_graph_render_block); see the header for the full API.
Plugin Discovery and the Scan Cache (minihost.h)¶
| Function | Description |
|---|---|
mh_get_default_plugin_dir |
Canonical plugin directory for this platform, by index |
mh_plugin_cache_path |
Absolute path of the shared scan cache |
mh_plugin_cache_scan |
Scan directories (or the canonical ones) and write the cache |
mh_plugin_cache_scan_supervised |
The same, probing each plugin in a disposable child process |
mh_plugin_scan_worker_main |
Answer the worker flag; call first thing in main |
mh_plugin_cache_lookup |
Resolve a plugin name to a path, ignoring case |
mh_plugin_cache_match |
Nth match for a name, for reporting an ambiguity |
Both take a format preference ("vst3", "au"/"audiounit", or NULL) and an
allow_substring flag.
mh_get_default_plugin_dir enumerates until it returns 0, and skips
directories that do not exist, so a caller sees what is actually installed:
char dir[1024];
for (int i = 0; mh_get_default_plugin_dir(i, dir, sizeof(dir)); i++)
printf("%s\n", dir);
| Platform | Directories |
|---|---|
| macOS | /Library/Audio/Plug-Ins/{VST3,Components} and the same two under ~/Library |
| Windows | C:\Program Files\Common Files\VST3, and the x86 equivalent |
| Linux | /usr/lib/vst3, /usr/local/lib/vst3, ~/.vst3 |
Scanning probes each plugin, which means loading it, so a first full scan of
a large collection takes minutes. The result is cached and keyed by path with
an mtime + size fingerprint, so a repeat scan re-probes only what changed;
pass refresh = 1 to force. A plugin that fails to probe is remembered as an
error rather than retried on every scan. The cache is written as the scan
proceeds, so a scan that is interrupted keeps what it had and a re-run resumes.
Probing is dispatched to the message thread, since instantiating an AudioUnit is thread-affine on macOS; callers need do nothing.
mh_plugin_cache_scan probes in the calling process, so a plugin that hangs
or crashes on load ends the scan. mh_plugin_cache_scan_supervised gives each
plugin its own child process and a deadline instead:
char err[512] = {0};
int n = mh_plugin_cache_scan_supervised(NULL, 0, /*refresh=*/0,
NULL, 0, /* worker: this binary */
/*timeout_ms=*/0, /* 0 -> 60000 */
NULL, NULL, err, sizeof(err));
Passing NULL for the worker spawns this process's own executable, which
works when the program answers the worker flag first thing in main:
int main(int argc, char** argv) {
if (mh_plugin_scan_worker_main(argc, argv)) return 0; /* was a worker */
...
}
An embedder whose executable is not ours -- a DAW, or Python -- passes its own
worker command instead ({"python3", "-m", "yourpkg.worker"}), implementing
the protocol documented in the header: one JSON object on stdout between
MH_SCAN_WORKER_BEGIN and MH_SCAN_WORKER_END. The markers exist because
plugins print to stdout while loading, so the answer has to be findable inside
that noise. MINIHOST_SCAN_WORKER overrides the command, and
MINIHOST_SCAN_TIMEOUT_MS the deadline.
Cache entries gain two outcomes only the supervised path can report --
timeout and crash -- both fingerprinted like any other entry, so a re-scan
skips those plugins rather than paying for them again.
char err[512] = {0};
int cached = mh_plugin_cache_scan(NULL, 0, /*refresh=*/0, NULL, NULL,
err, sizeof(err)); /* NULL: canonical dirs */
if (cached < 0) fprintf(stderr, "%s\n", err);
Name lookup ignores case and matches the whole name; pass allow_substring = 1
to match part of one, which on a real collection is usually ambiguous. Entries
that failed to probe are never offered. When every match is the same name in
more than one format -- a common way to install a plugin -- one is chosen
instead of reporting an ambiguity: the requested format if given, else VST3.
The return value distinguishes the three outcomes:
char path[1024];
int n = mh_plugin_cache_lookup("dexed", NULL, /*allow_substring=*/0,
path, sizeof(path));
if (n == 1) {
/* unique match: path is filled in */
} else if (n == 0) {
/* nothing matched -- scan first, or use a path */
} else {
/* ambiguous: n candidates, path holds the first */
char one[1024];
for (int i = 0; mh_plugin_cache_match("dexed", NULL, 0, i,
one, sizeof(one)); i++)
fprintf(stderr, " %s\n", one);
}
The cache file and its JSON schema are shared with the Python CLI
(minihost.plugincache), so a scan from either front-end serves the other.
Location, honouring MINIHOST_CACHE_DIR:
| Platform | Cache file |
|---|---|
| macOS | ~/Library/Caches/minihost/plugins.json |
| Windows | %LOCALAPPDATA%/minihost/Cache/plugins.json |
| other | $XDG_CACHE_HOME/minihost/plugins.json (or ~/.cache/...) |
Added in C ABI 2.7.0.
MIDI File Reading (minihost.h)¶
| Function | Description |
|---|---|
mh_midi_file_load |
Read a standard MIDI file into one time-ordered MH_MidiEvent array |
mh_midi_file_free |
Release an array returned by mh_midi_file_load |
Tracks are merged, the file's tempo map is applied, and meta events are
dropped (they have no MH_MidiEvent form). sample_offset is absolute --
measured in samples from the start of the file at the rate you pass in --
so rebase it per block before handing events to a plugin:
MH_MidiEvent* events = NULL;
int count = 0;
double duration = 0.0;
char err[512] = {0};
if (!mh_midi_file_load("song.mid", 48000.0, &events, &count, &duration,
err, sizeof(err))) {
fprintf(stderr, "%s\n", err);
return 1;
}
int cursor = 0;
for (int start = 0; start < total_frames; start += block) {
int end = start + block;
MH_MidiEvent block_midi[256];
int n = 0;
while (cursor < count && events[cursor].sample_offset < end && n < 256) {
block_midi[n] = events[cursor];
block_midi[n].sample_offset -= start; /* rebase to this block */
n++;
cursor++;
}
mh_process_midi(p, inputs, outputs, block, block_midi, n);
}
mh_midi_file_free(events);
An empty file succeeds with count == 0 and a NULL array. Added in C ABI
2.6.0; before that, reading a MIDI file was possible only from Python.
MIDI Functions (minihost_midi.h)¶
Port Enumeration¶
| Function | Description |
|---|---|
mh_midi_get_num_inputs |
Get number of available MIDI input ports |
mh_midi_get_num_outputs |
Get number of available MIDI output ports |
mh_midi_get_input_name |
Get MIDI input port name by index |
mh_midi_get_output_name |
Get MIDI output port name by index |
mh_midi_enumerate_inputs |
Enumerate input ports via callback |
mh_midi_enumerate_outputs |
Enumerate output ports via callback |
Standalone MIDI I/O¶
| Function | Description |
|---|---|
mh_midi_in_open |
Open MIDI input port with message callback |
mh_midi_in_open_virtual |
Create virtual MIDI input port with callback |
mh_midi_in_close |
Close MIDI input |
mh_midi_out_open |
Open MIDI output port |
mh_midi_out_open_virtual |
Create virtual MIDI output port |
mh_midi_out_close |
Close MIDI output |
mh_midi_out_send |
Send raw MIDI message on output port |
VST3 Preset Functions (minihost_vstpreset.h)¶
Portable Steinberg .vstpreset reader/writer with no external dependencies (little-endian packing built in). Callable from both C and C++.
| Function | Description |
|---|---|
mh_vstpreset_read |
Read a .vstpreset file into an MH_VstPreset struct |
mh_vstpreset_write |
Write class_id + component/controller state blobs to a .vstpreset file |
mh_vstpreset_free |
Free heap-allocated state blobs inside an MH_VstPreset populated by mh_vstpreset_read |
mh_vstpreset_read_class_id_from_bundle |
Read the processor FUID from a VST3 bundle's Contents/Resources/moduleinfo.json |
Structs¶
MH_VstPreset -- populated by mh_vstpreset_read():
-
char class_id[33]-- 32-char FUID plus null terminator (MH_VSTPRESET_CLASS_ID_LEN= 32) -
void* component_state-- processor (Comp) chunk, heap-allocated -
int component_size -
void* controller_state-- controller (Cont) chunk, heap-allocated (may beNULL) -
int controller_size
Typical usage:
#include "minihost_vstpreset.h"
// Read
MH_VstPreset preset;
char err[256];
if (mh_vstpreset_read("in.vstpreset", &preset, err, sizeof(err))) {
// apply preset.component_state to plugin via mh_set_state(), etc.
mh_vstpreset_free(&preset);
}
// Auto-detect the processor FUID from the plugin bundle (no plugin
// instantiation required). Works for any VST3 plugin built against
// SDK 3.7.5 or newer (which ships moduleinfo.json in the bundle).
char class_id[MH_VSTPRESET_CLASS_ID_LEN + 1];
if (!mh_vstpreset_read_class_id_from_bundle(
"/path/to/synth.vst3", class_id, err, sizeof(err))) {
fprintf(stderr, "%s\n", err);
// Legacy plugin: caller must supply class_id another way
// (e.g., copy from an existing .vstpreset).
}
// Write
int state_size = mh_get_state_size(plugin);
void* state = malloc(state_size);
mh_get_state(plugin, state, state_size);
mh_vstpreset_write("out.vstpreset",
class_id,
state, state_size,
NULL, 0, // no separate controller state
err, sizeof(err));
free(state);
Notes on mh_vstpreset_read_class_id_from_bundle¶
-
Returns 1 on success and writes a 32-character uppercase hex FUID into
out_class_id(which must be at leastMH_VSTPRESET_CLASS_ID_LEN + 1bytes). -
Selects the first entry in the bundle's
Classesarray whoseCategoryis"Audio Module Class"(the processor component, not the controller). -
Tolerates JSON5-style trailing commas, which appear in real-world bundles (e.g., Dexed, Strokes).
-
Returns 0 with a descriptive error in
err_buffor: missingmoduleinfo.json(plugin predates VST3 SDK 3.7.5), file unreadable, file > 1 MB, JSON malformed, no Audio Module Class entry, or CID not exactly 32 hex characters. -
VST3 only -- AudioUnit and LV2 plugins do not have
.vstpresetfiles or moduleinfo.json.