A minimal juce-based plugin host using nanobind
Project description
minihost
Minihost is a headless, JUCE-based audio plugin host that supports VST3, AudioUnit, and LV2 plugins. It provides a C/C++ API for integration and a Python API powered by nanobind.
Features
- Load VST3 plugins (macOS, Windows, Linux)
- Load AudioUnit plugins (macOS only)
- Load LV2 plugins (macOS, Windows, Linux)
- Headless mode (default) - no GUI dependencies, uses JUCE's
juce_audio_processors_headlessmodule - Plugin chaining - connect multiple plugins in series (synth -> reverb -> limiter)
- Audio file I/O via miniaudio + tflac -- read WAV/FLAC/MP3/Vorbis, write WAV (16/24/32-bit) and FLAC (16/24-bit)
- Sample rate conversion via miniaudio resampler --
minihost.resample()API andminihost resampleCLI subcommand - Real-time audio playback via miniaudio (cross-platform), with duplex capture mode for effect processing
- Audio device selection -- enumerate and target specific playback/capture devices (
minihost devicesCLI,audio_get_playback_devices()/audio_get_capture_devices()API,--playback-device/--capture-deviceonminihost play) - Real-time audio input -- lock-free ring buffer API (
write_input()) and duplex capture (capture=True) for routing system audio through effects - Real-time MIDI I/O via libremidi (cross-platform)
- Virtual MIDI ports - create named ports that DAWs can connect to (macOS, Linux)
- Standalone MIDI input - monitor raw MIDI messages without a plugin (
MidiInclass) - Batch processing -- glob patterns and directory output for processing multiple files (
minihost process -i "*.wav" -o output/) - Auto-tail detection --
tail_seconds="auto"monitors output amplitude and stops rendering when reverb/delay tails decay below threshold - Process audio with sample-accurate parameter automation
- Single and double precision processing
- MIDI input/output support
- Transport info for tempo-synced plugins
- State save/restore for presets and per-program state
- Thread-safe parameter access
- Change notifications (latency, parameter info, program, non-parameter state)
- Parameter gestures for automation bracketing
- Bus layout validation and sidechain support
- Track name/color metadata forwarding to plugins
- Latency and tail time reporting
- Parameter access by name --
plugin.find_param("Cutoff"),plugin.get_param_by_name("Cutoff"),plugin.set_param_by_name("Cutoff", 0.5)with case-insensitive lookup - Async plugin loading --
minihost.open_async()returns aconcurrent.futures.Futurefor background loading of large sample-library plugins - VST3 preset I/O -- read and write
.vstpresetfiles from C (minihost_vstpreset.h), C++, and Python (minihost.vstpreset);minihost presetsCLI subcommand exports the current plugin state, optionally after loading a program, state blob, or another.vstpreset
Requirements
- CMake 3.20+
- C++17 compiler
- JUCE framework (automatically downloaded if not present)
- Vendored C libraries: miniaudio, tflac, libremidi, midifile (see docs/vendored.md)
Platform-specific
-
macOS: Xcode command line tools
-
Windows: Visual Studio 2019+ or MinGW
-
Linux: Install the following development libraries:
sudo apt install libasound2-dev libfreetype-dev libfontconfig1-dev \ libwebkit2gtk-4.1-dev libgtk-3-dev libgl-dev libcurl4-openssl-dev
Building
macOS / Linux
# Clone the repository
git clone https://github.com/shakfu/minihost.git
cd minihost
# Build (JUCE will be downloaded automatically)
make
# Or with a custom JUCE path
cmake -B build -DJUCE_PATH=/path/to/JUCE
cmake --build build
# Disable headless mode (enables GUI support)
cmake -B build -DMINIHOST_HEADLESS=OFF
cmake --build build
Windows
# Clone the repository
git clone https://github.com/shakfu/minihost.git
cd minihost
# Download JUCE
python scripts/download_juce.py
# Configure and build
cmake -B build
cmake --build build --config Release
JUCE Setup
JUCE is downloaded automatically by make (macOS/Linux). You can also download it manually:
# Cross-platform (recommended) - works on Windows, macOS, Linux
python scripts/download_juce.py
# Unix only (bash)
./scripts/download_juce.sh
To use a different version or existing installation:
# Download specific version (macOS/Linux)
JUCE_VERSION=8.0.6 python scripts/download_juce.py
# Download specific version (Windows PowerShell)
$env:JUCE_VERSION="8.0.6"; python scripts/download_juce.py
# Or point to existing JUCE
cmake -B build -DJUCE_PATH=/path/to/your/JUCE
Command Line Interface
The minihost command provides a CLI for common plugin operations:
# Install (from source)
uv sync
# Available commands
minihost --help
usage: minihost [-h] [-r SAMPLE_RATE] [-b BLOCK_SIZE]
{scan,info,params,midi,devices,presets,play,process,resample} ...
Audio plugin hosting CLI
positional arguments:
{scan,info,params,midi,devices,presets,play,process,resample}
Commands
scan Scan directory for plugins
info Show plugin info
params List plugin parameters
midi List or monitor MIDI ports
devices List audio playback/capture devices
presets List factory presets or export .vstpreset files
play Play plugin with real-time audio/MIDI
process Process audio through plugin (offline)
resample Resample audio file to a different sample rate
options:
-h, --help show this help message and exit
-r, --sample-rate SAMPLE_RATE
Sample rate in Hz (default: 48000)
-b, --block-size BLOCK_SIZE
Block size in samples (default: 512)
Commands
minihost info - Show plugin info
minihost info /path/to/plugin.vst3 # full info (loads plugin)
minihost info /path/to/plugin.vst3 --probe # lightweight metadata only
minihost info /path/to/plugin.vst3 --json # JSON output
By default shows full runtime details (sample rate, channels, latency, buses, presets). Use --probe for fast metadata-only mode without fully loading the plugin.
minihost scan - Scan directory for plugins
minihost scan /Library/Audio/Plug-Ins/VST3/
minihost scan ~/Music/Plugins --json
minihost params - List plugin parameters
minihost params /path/to/plugin.vst3
minihost params /path/to/plugin.vst3 --json
minihost devices - List audio devices
minihost devices # list playback and capture devices
minihost devices --json # JSON output
Use an index or case-insensitive device-name substring with minihost play --playback-device / --capture-device.
minihost presets - List or export factory presets
# List factory presets
minihost presets /path/to/synth.vst3
minihost presets /path/to/synth.vst3 --json
# Export factory preset N as a .vstpreset
minihost presets /path/to/synth.vst3 --program 5 --save preset5.vstpreset
# Round-trip: load a .vstpreset and re-save (preserves class_id)
minihost presets /path/to/synth.vst3 --load-vstpreset in.vstpreset --save out.vstpreset
# Convert a raw state blob to .vstpreset
minihost presets /path/to/synth.vst3 --state state.bin --save out.vstpreset
minihost midi - List or monitor MIDI ports
minihost midi # list all MIDI ports
minihost midi --json # list as JSON
minihost midi -m 0 # monitor MIDI input port 0
minihost midi --virtual-midi "Monitor" # create virtual port and monitor
minihost play - Play plugin with real-time audio/MIDI
# Connect to MIDI input port 0
minihost play /path/to/synth.vst3 --midi 0
# Create a virtual MIDI port (macOS/Linux)
minihost play /path/to/synth.vst3 --virtual-midi "My Synth"
# Enable audio input for effect processing (duplex mode)
minihost play /path/to/reverb.vst3 --input
minihost play /path/to/amp-sim.vst3 --input --midi 0 # with MIDI too
# Select specific audio devices (index from `minihost devices` or name substring)
minihost play /path/to/synth.vst3 --playback-device "BlackHole"
minihost play /path/to/effect.vst3 --input --playback-device 0 --capture-device 1
minihost process - Process audio/MIDI offline
# Process audio through effect
minihost process /path/to/effect.vst3 -i input.wav -o output.wav
# With parameter control
minihost process /path/to/effect.vst3 -i input.wav -o output.wav --param "Mix:0.5"
# Render MIDI through synth
minihost process /path/to/synth.vst3 -m song.mid -o output.wav --tail 3.0
# With preset and bit depth
minihost process /path/to/synth.vst3 -m song.mid -o output.wav --preset 5 --bit-depth 16
# Sidechain processing (second -i is sidechain)
minihost process /path/to/compressor.vst3 -i main.wav -i sidechain.wav -o output.wav
# Batch processing (glob input, directory output)
minihost process /path/to/reverb.vst3 -i "drums/*.wav" -o processed/
minihost process /path/to/effect.vst3 -i "*.wav" -o output/ -y # overwrite existing
# Mixed sample rates are automatically resampled (use --no-resample to error instead)
minihost process /path/to/effect.vst3 -i 44100hz.wav -i 48000hz_sidechain.wav -o out.wav
minihost resample - Resample audio files
minihost resample input.wav -o output.wav -r 48000
minihost resample input.wav -o output.wav -r 44100 --bit-depth 16
minihost resample input.wav -o output.wav -r 96000 -y # overwrite
Global Options
| Option | Description |
|---|---|
-r, --sample-rate |
Sample rate in Hz (default: 48000) |
-b, --block-size |
Block size in samples (default: 512) |
Python API
uv sync
import numpy as np
import minihost
plugin = minihost.Plugin("/path/to/plugin.vst3", sample_rate=48000)
input_audio = np.zeros((2, 512), dtype=np.float32)
output_audio = np.zeros((2, 512), dtype=np.float32)
plugin.process(input_audio, output_audio)
Parameter Access by Name
import minihost
plugin = minihost.Plugin("/path/to/synth.vst3", sample_rate=48000)
# Find parameter index by name (case-insensitive)
idx = plugin.find_param("Cutoff")
# Get/set by name directly
value = plugin.get_param_by_name("Cutoff")
plugin.set_param_by_name("Cutoff", 0.7)
plugin.set_param_by_name("resonance", 0.4) # case-insensitive
# Index-based API remains available for hot paths
plugin.set_param(idx, 0.5)
Async Plugin Loading
import minihost
# Load a heavy plugin in the background
future = minihost.open_async("/path/to/heavy_sampler.vst3", sample_rate=48000)
# Do other work while plugin loads...
# Block until ready
plugin = future.result()
print(f"Loaded: {plugin.num_params} params")
Audio Device Enumeration and Selection
import minihost
# List available audio devices
for dev in minihost.audio_get_playback_devices():
print(f"[{dev['index']}] {dev['name']}{' *' if dev['is_default'] else ''}")
for dev in minihost.audio_get_capture_devices():
print(f"[{dev['index']}] {dev['name']}{' *' if dev['is_default'] else ''}")
# Target a specific playback device (e.g., for routing to a loopback driver)
plugin = minihost.Plugin("/path/to/synth.vst3", sample_rate=48000)
with minihost.AudioDevice(plugin, playback_device_index=2) as audio:
audio.send_midi(0x90, 60, 100)
# Duplex mode with explicit capture + playback devices
with minihost.AudioDevice(plugin, capture=True,
capture_device_index=1,
playback_device_index=0) as audio:
pass
Pass -1 (the default) to use the system default device.
Real-time Audio Playback
import minihost
import time
plugin = minihost.Plugin("/path/to/synth.vst3", sample_rate=48000)
# Use as context manager for automatic start/stop
with minihost.AudioDevice(plugin) as audio:
# Plugin is now producing audio through speakers
# Send MIDI programmatically
audio.send_midi(0x90, 60, 100) # Note on: C4, velocity 100
time.sleep(1)
audio.send_midi(0x80, 60, 0) # Note off
time.sleep(0.5)
# Or manual control
audio = minihost.AudioDevice(plugin)
audio.start()
audio.send_midi(0x90, 64, 80) # E4 note on
time.sleep(0.5)
audio.send_midi(0x80, 64, 0) # E4 note off
audio.stop()
Real-time Audio Input (Effect Processing)
Route system audio through an effect plugin using duplex mode or the ring buffer API:
import minihost
import numpy as np
import time
plugin = minihost.Plugin("/path/to/reverb.vst3", sample_rate=48000)
# Option 1: Duplex mode (system audio capture -> plugin -> speakers)
with minihost.AudioDevice(plugin, capture=True) as audio:
print("Processing system audio through effect... Ctrl+C to stop")
time.sleep(10)
# Option 2: Ring buffer (push audio from Python)
audio = minihost.AudioDevice(plugin)
audio.enable_input() # ~0.5s ring buffer by default
audio.start()
# Write audio into the ring buffer (e.g., from a file or generator)
data, sr = minihost.read_audio("guitar.wav")
block_size = 512
for i in range(0, data.shape[1], block_size):
chunk = data[:, i:i+block_size]
audio.write_input(chunk)
time.sleep(block_size / sr * 0.9) # pace to real time
audio.stop()
audio.disable_input()
Real-time MIDI I/O
import minihost
# Enumerate available MIDI ports
inputs = minihost.midi_get_input_ports()
outputs = minihost.midi_get_output_ports()
print(f"MIDI Inputs: {inputs}")
print(f"MIDI Outputs: {outputs}")
# Connect MIDI when creating AudioDevice
with minihost.AudioDevice(plugin, midi_input_port=0) as audio:
# MIDI from port 0 is now routed to the plugin
pass
# Or connect dynamically
audio = minihost.AudioDevice(plugin)
audio.connect_midi_input(0)
audio.start()
# ...
audio.disconnect_midi_input()
audio.stop()
# Create virtual MIDI ports (appear in system MIDI, DAWs can connect)
audio = minihost.AudioDevice(plugin)
audio.create_virtual_midi_input("minihost Input")
audio.create_virtual_midi_output("minihost Output")
audio.start()
# Other apps can now send MIDI to "minihost Input"
# and receive MIDI from "minihost Output"
Standalone MIDI Input
Monitor MIDI messages without loading a plugin:
import minihost
def on_midi(data: bytes):
status = data[0]
if status & 0xF0 == 0x90 and data[2] > 0:
print(f"Note On: {data[1]} vel={data[2]}")
# Open hardware MIDI port
with minihost.MidiIn.open(0, on_midi) as midi_in:
input("Press Enter to stop...\n")
# Or create a virtual MIDI port
with minihost.MidiIn.open_virtual("My Monitor", on_midi) as midi_in:
input("Press Enter to stop...\n")
Audio File I/O
import minihost
# Read audio files (WAV, FLAC, MP3, Vorbis)
data, sample_rate = minihost.read_audio("input.wav")
# data shape: (channels, samples), dtype: float32
# Write audio files
minihost.write_audio("output.wav", data, sample_rate, bit_depth=24) # WAV (16/24/32-bit)
minihost.write_audio("output.flac", data, sample_rate, bit_depth=24) # FLAC (16/24-bit)
# Get file info without decoding
info = minihost.get_audio_info("song.wav")
print(f"{info['channels']}ch, {info['sample_rate']}Hz, {info['duration']:.2f}s")
Sample Rate Conversion
import minihost
# Resample a numpy array
data, sr = minihost.read_audio("input_44100.wav") # 44.1kHz
resampled = minihost.resample(data, 44100, 48000) # -> 48kHz
minihost.write_audio("output_48000.wav", resampled, 48000)
MIDI File Read/Write
import minihost
# Create a new MIDI file
mf = minihost.MidiFile()
mf.ticks_per_quarter = 480
# Add events
mf.add_tempo(0, 0, 120.0) # 120 BPM at tick 0
mf.add_note_on(0, 0, 0, 60, 100) # C4 note on at tick 0
mf.add_note_off(0, 480, 0, 60, 0) # C4 note off at tick 480
# Save to file
mf.save("output.mid")
# Load existing MIDI file
mf2 = minihost.MidiFile()
mf2.load("input.mid")
# Read events
events = mf2.get_events(0) # Get events from track 0
for event in events:
if event['type'] == 'note_on':
print(f"Note {event['pitch']} vel {event['velocity']} at {event['seconds']:.2f}s")
MIDI File Rendering
Render MIDI files through plugins to produce audio output:
import minihost
plugin = minihost.Plugin("/path/to/synth.vst3", sample_rate=48000)
# Render to numpy array
audio = minihost.render_midi(plugin, "song.mid")
print(f"Rendered {audio.shape[1] / 48000:.2f} seconds of audio")
# Render directly to WAV file
samples = minihost.render_midi_to_file(plugin, "song.mid", "output.wav", bit_depth=24)
# Stream blocks for large files or real-time processing
for block in minihost.render_midi_stream(plugin, "song.mid", block_size=512):
# Process each block (shape: channels, block_size)
pass
# Auto-detect reverb/delay tail (stops when output decays below -80 dB)
audio = minihost.render_midi(plugin, "song.mid", tail_seconds="auto")
# Custom threshold (-40 dB) and max tail (10s safety cap)
audio = minihost.render_midi(plugin, "song.mid",
tail_seconds="auto", tail_threshold=1e-2, max_tail_seconds=10)
# Fine-grained control with MidiRenderer class
renderer = minihost.MidiRenderer(plugin, "song.mid")
print(f"Duration: {renderer.duration_seconds:.2f}s")
while not renderer.is_finished:
block = renderer.render_block()
print(f"Progress: {renderer.progress:.1%}")
Plugin Chaining
Chain multiple plugins together for serial processing:
import minihost
import time
# Load plugins (all must have same sample rate)
synth = minihost.Plugin("/path/to/synth.vst3", sample_rate=48000)
reverb = minihost.Plugin("/path/to/reverb.vst3", sample_rate=48000)
limiter = minihost.Plugin("/path/to/limiter.vst3", sample_rate=48000)
# Create chain
chain = minihost.PluginChain([synth, reverb, limiter])
print(f"Total latency: {chain.latency_samples} samples")
print(f"Tail length: {chain.tail_seconds:.2f} seconds")
# Real-time playback through chain
with minihost.AudioDevice(chain) as audio:
audio.send_midi(0x90, 60, 100) # Note on to synth
time.sleep(2)
audio.send_midi(0x80, 60, 0) # Note off
time.sleep(1) # Let reverb tail fade
# Offline processing
import numpy as np
input_audio = np.zeros((2, 512), dtype=np.float32)
output_audio = np.zeros((2, 512), dtype=np.float32)
chain.process(input_audio, output_audio)
# Process with MIDI (MIDI goes to first plugin)
midi_events = [(0, 0x90, 60, 100)]
chain.process_midi(input_audio, output_audio, midi_events)
# Sample-accurate automation across chain
# param_changes: (sample_offset, plugin_index, param_index, value)
param_changes = [
(0, 1, 0, 0.3), # Set reverb param 0 at sample 0
(256, 1, 0, 0.6), # Change reverb param 0 at sample 256
(0, 2, 0, 0.8), # Set limiter param 0 at sample 0
]
chain.process_auto(input_audio, output_audio, midi_events, param_changes)
# Render MIDI file through chain
audio = minihost.render_midi(chain, "song.mid")
minihost.render_midi_to_file(chain, "song.mid", "output.wav")
# Access individual plugins in chain
for i in range(chain.num_plugins):
plugin = chain.get_plugin(i)
print(f"Plugin {i}: {plugin.num_params} params")
VST3 Presets
Read, load, and write Steinberg .vstpreset files:
import minihost
from minihost import vstpreset
plugin = minihost.Plugin("/path/to/synth.vst3")
# Read a .vstpreset into raw chunks
preset = vstpreset.read_vstpreset("patch.vstpreset")
print(preset.class_id, len(preset.component_state or b""))
# Load into a plugin (calls plugin.set_state under the hood)
vstpreset.load_vstpreset("patch.vstpreset", plugin)
# Save the plugin's current state to a .vstpreset.
# class_id defaults to the FUID auto-detected from the plugin bundle's
# moduleinfo.json (requires VST3 SDK 3.7.5+, which all modern plugins ship).
vstpreset.save_vstpreset("out.vstpreset", plugin)
# Or pass class_id explicitly (e.g., for legacy plugins without moduleinfo.json):
vstpreset.save_vstpreset("out.vstpreset", plugin,
class_id="ABCDEF0123456789ABCDEF0123456789")
# Read just the class ID from a bundle without instantiating the plugin
fuid = vstpreset.read_class_id_from_bundle("/path/to/synth.vst3")
print(fuid) # e.g., "ABCDEF0123456789ABCDEF0123456789"
# Or write raw chunks you already have
vstpreset.write_vstpreset("out.vstpreset",
class_id=fuid,
component_state=plugin.get_state())
C API Usage
#include "minihost.h"
// Load a plugin
char err[256];
MH_Plugin* plugin = mh_open("/path/to/plugin.vst3",
48000.0, // sample rate
512, // max block size
2, 2, // in/out channels
err, sizeof(err));
// Process audio
float* inputs[2] = { in_left, in_right };
float* outputs[2] = { out_left, out_right };
mh_process(plugin, inputs, outputs, 512);
// Process with MIDI
MH_MidiEvent midi[] = {
{ 0, 0x90, 60, 100 }, // Note on at sample 0
{ 256, 0x80, 60, 0 } // Note off at sample 256
};
mh_process_midi(plugin, inputs, outputs, 512, midi, 2);
// Parameter control
int num_params = mh_get_num_params(plugin);
float value = mh_get_param(plugin, 0);
mh_set_param(plugin, 0, 0.5f);
// State save/restore
int size = mh_get_state_size(plugin);
void* state = malloc(size);
mh_get_state(plugin, state, size);
mh_set_state(plugin, state, size);
// Cleanup
mh_close(plugin);
Real-time Audio Playback
#include "minihost_audio.h"
// Enumerate and select a playback device (optional)
MH_AudioDeviceInfo devices[32];
int n = mh_audio_enumerate_playback_devices(devices, 32);
for (int i = 0; i < n; i++) {
printf("[%d]%s %s\n", i, devices[i].is_default ? "*" : " ", devices[i].name);
}
// Open audio device for real-time playback
MH_AudioConfig config = {
.sample_rate = 48000,
.buffer_frames = 512,
.playback_device_index = -1, // -1 = system default
.capture_device_index = -1,
};
MH_AudioDevice* audio = mh_audio_open(plugin, &config, err, sizeof(err));
// Start playback
mh_audio_start(audio);
// Plugin is now producing audio through speakers
// Send MIDI, adjust parameters, etc.
// Stop and cleanup
mh_audio_stop(audio);
mh_audio_close(audio);
mh_close(plugin);
Real-time MIDI I/O
#include "minihost_midi.h"
// Enumerate available MIDI ports
int num_inputs = mh_midi_get_num_inputs();
int num_outputs = mh_midi_get_num_outputs();
for (int i = 0; i < num_inputs; i++) {
char name[256];
mh_midi_get_input_name(i, name, sizeof(name));
printf("MIDI Input %d: %s\n", i, name);
}
// Connect MIDI to audio device
MH_AudioConfig config = {
.sample_rate = 48000,
.midi_input_port = 0, // Connect to first MIDI input
.midi_output_port = -1 // No MIDI output
};
MH_AudioDevice* audio = mh_audio_open(plugin, &config, err, sizeof(err));
// Or connect/disconnect dynamically
mh_audio_connect_midi_input(audio, 1);
mh_audio_disconnect_midi_input(audio);
// Create virtual MIDI ports (appear in system MIDI, DAWs can connect)
mh_audio_create_virtual_midi_input(audio, "minihost Input");
mh_audio_create_virtual_midi_output(audio, "minihost Output");
Plugin Chaining
Chain multiple plugins together for processing (e.g., synth -> reverb -> limiter):
#include "minihost_chain.h"
// Load plugins
MH_Plugin* synth = mh_open("/path/to/synth.vst3", 48000, 512, 0, 2, err, sizeof(err));
MH_Plugin* reverb = mh_open("/path/to/reverb.vst3", 48000, 512, 2, 2, err, sizeof(err));
MH_Plugin* limiter = mh_open("/path/to/limiter.vst3", 48000, 512, 2, 2, err, sizeof(err));
// Create chain (all plugins must have same sample rate)
MH_Plugin* plugins[] = { synth, reverb, limiter };
MH_PluginChain* chain = mh_chain_create(plugins, 3, err, sizeof(err));
// Get combined latency
int latency = mh_chain_get_latency_samples(chain);
// Process audio through chain
float* inputs[2] = { in_left, in_right };
float* outputs[2] = { out_left, out_right };
mh_chain_process(chain, inputs, outputs, 512);
// Process with MIDI (MIDI goes to first plugin only)
MH_MidiEvent midi[] = { { 0, 0x90, 60, 100 } };
mh_chain_process_midi_io(chain, inputs, outputs, 512, midi, 1, NULL, 0, NULL);
// Sample-accurate automation across chain
MH_ChainParamChange changes[] = {
{ .sample_offset = 0, .plugin_index = 1, .param_index = 0, .value = 0.3f },
{ .sample_offset = 256, .plugin_index = 1, .param_index = 0, .value = 0.6f },
};
mh_chain_process_auto(chain, inputs, outputs, 512,
NULL, 0, NULL, 0, NULL, changes, 2);
// Real-time playback through chain
MH_AudioConfig config = { .sample_rate = 48000, .buffer_frames = 512 };
MH_AudioDevice* audio = mh_audio_open_chain(chain, &config, err, sizeof(err));
mh_audio_start(audio);
// ...
mh_audio_stop(audio);
mh_audio_close(audio);
// Cleanup
mh_chain_close(chain); // Does not close individual plugins
mh_close(synth);
mh_close(reverb);
mh_close(limiter);
Audio File I/O
Read and write audio files without external dependencies:
#include "minihost_audiofile.h"
// Read any supported format (WAV, FLAC, MP3, Vorbis)
char err[1024];
MH_AudioData* audio = mh_audio_read("input.flac", err, sizeof(err));
if (audio) {
printf("Channels: %u, Frames: %u, Rate: %u\n",
audio->channels, audio->frames, audio->sample_rate);
// audio->data is interleaved float32
mh_audio_data_free(audio);
}
// Write audio file (format selected by extension)
mh_audio_write("output.wav", interleaved_data,
2, num_frames, 48000, 24, err, sizeof(err)); // WAV
mh_audio_write("output.flac", interleaved_data,
2, num_frames, 48000, 24, err, sizeof(err)); // FLAC
// Get file info without decoding
MH_AudioFileInfo info;
mh_audio_get_file_info("song.wav", &info, err, sizeof(err));
printf("Duration: %.2f seconds\n", info.duration);
// Resample audio (e.g., 44.1kHz -> 48kHz)
MH_AudioData* resampled = mh_audio_resample(
audio->data, audio->channels, audio->frames,
44100, 48000, err, sizeof(err));
if (resampled) {
printf("Resampled: %u frames at %u Hz\n", resampled->frames, resampled->sample_rate);
mh_audio_data_free(resampled);
}
VST3 Preset I/O
Portable .vstpreset reader/writer with no external dependencies:
#include "minihost_vstpreset.h"
char err[256];
// Read a .vstpreset
MH_VstPreset preset;
if (mh_vstpreset_read("in.vstpreset", &preset, err, sizeof(err))) {
// Apply the processor chunk to a plugin
mh_set_state(plugin, preset.component_state, preset.component_size);
mh_vstpreset_free(&preset);
}
// Auto-detect the processor FUID from the plugin bundle's moduleinfo.json
// (requires VST3 SDK 3.7.5+, which all modern plugins ship).
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, "Cannot determine class_id: %s\n", err);
// For legacy plugins without moduleinfo.json, supply class_id another way
// (e.g., copy it from an existing .vstpreset).
}
// Write current plugin state to a .vstpreset
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, // optional controller state
err, sizeof(err));
free(state);
Thread Safety
mh_process,mh_process_midi,mh_process_midi_io,mh_process_auto: Call from audio thread only (no locking)- All other functions are thread-safe with internal locking
- Do not call
mh_closewhile another thread is using the plugin
API Reference
Detailed API documentation:
- C API Reference --
minihost.h,minihost_audio.h,minihost_audiofile.h,minihost_chain.h,minihost_midi.h,minihost_vstpreset.h - Python API Reference --
Plugin,PluginChain,AudioDevice,MidiFile,MidiIn, audio I/O, MIDI rendering, automation, VST3 presets - Hosting Guide -- practical guide with extended examples
License
GPL3
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file minihost-0.1.5-cp314-cp314-win_amd64.whl.
File metadata
- Download URL: minihost-0.1.5-cp314-cp314-win_amd64.whl
- Upload date:
- Size: 941.1 kB
- Tags: CPython 3.14, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c87f8c0ed8ac2a3d98b04f86d74858c5fb00dddf7a839774e67eb90289c3196
|
|
| MD5 |
bf3946804ee9d1ea7e40e62f2fc43337
|
|
| BLAKE2b-256 |
7a43121834ae6f47d0ab925157bc0157361ee6a258ab71d45d39e183efade871
|
File details
Details for the file minihost-0.1.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.4 MB
- Tags: CPython 3.14, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8c51b6eed05e9dbbf51afa91d76c29ae3925df28cab3a081f2e038328a6bb55
|
|
| MD5 |
b278334cfd7fd63804add71a1cff430d
|
|
| BLAKE2b-256 |
d71c844db3a865fcedc2b529561b6cf393303e75b4b9b6c6f6460450d92f9b8d
|
File details
Details for the file minihost-0.1.5-cp314-cp314-macosx_11_0_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp314-cp314-macosx_11_0_x86_64.whl
- Upload date:
- Size: 1.9 MB
- Tags: CPython 3.14, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98b8e279f4696264fe99825319af10754dd3d120e19a19b40e8829510cf3be5a
|
|
| MD5 |
f85cfac8b1af5f989775003e18d47d63
|
|
| BLAKE2b-256 |
ec827dfc415db1ab515993bd7cd738c074790c3ebebc306ba8b45116deb569cc
|
File details
Details for the file minihost-0.1.5-cp314-cp314-macosx_11_0_arm64.whl.
File metadata
- Download URL: minihost-0.1.5-cp314-cp314-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.14, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2ab25cda3d0ea54bcb0e37013f4e06f6f212728a7021ff57738801cc75d722c
|
|
| MD5 |
552942884629fc8163647a348d3b239c
|
|
| BLAKE2b-256 |
053b625282324b75368f1c94929c1fe059b94bf543f7238a550d078a3046f7dd
|
File details
Details for the file minihost-0.1.5-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: minihost-0.1.5-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 913.4 kB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f6470cbeb061c675654ae1b8082b51b758404cab1a75fd02614266c04f82810
|
|
| MD5 |
e7d43793036e9a66c3e72e9976b4e3b6
|
|
| BLAKE2b-256 |
c47174271b58782d701d21679c6683553e370e5faadcd66be49b52d44c96904d
|
File details
Details for the file minihost-0.1.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.4 MB
- Tags: CPython 3.13, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7e693eb7bf91da4c9ad8f472274ac7ea3db374d0ea820c97937969e8b184e40
|
|
| MD5 |
fb1211fec77316a89e65fe5dba394a78
|
|
| BLAKE2b-256 |
625ad5e1bc315d882ff6342eb5101dee686942dce8e5a4a90d7852efaefdeb62
|
File details
Details for the file minihost-0.1.5-cp313-cp313-macosx_11_0_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp313-cp313-macosx_11_0_x86_64.whl
- Upload date:
- Size: 1.9 MB
- Tags: CPython 3.13, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abba199cf45530b9e9c8f1f2a31b15322566b07858ea685a3b008f94eeb98615
|
|
| MD5 |
79ef438aa51a64238fbe6075f08ba297
|
|
| BLAKE2b-256 |
375107d5370671bd4ed9ae7f3458b331ab832f8885f2b99f7dedcd9e85105101
|
File details
Details for the file minihost-0.1.5-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: minihost-0.1.5-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2f5bbb3bb899dd4c18ffac69e6ea2370495d2c4b5406823b5f70a4457db0e90
|
|
| MD5 |
306234bdade8e81fa4780089f22cf82a
|
|
| BLAKE2b-256 |
8edd62cf6f18f8583c31e74a23315beec96ebb523584c53827377969d3c7ebc8
|
File details
Details for the file minihost-0.1.5-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: minihost-0.1.5-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 913.6 kB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb0a4e1f1ee58281bcc44424af1ba7da709f768f3f610ddd3804a895dbf0a701
|
|
| MD5 |
586119b23cc550b141cebaf25ddf06c5
|
|
| BLAKE2b-256 |
5fb8c645725f377cab53c7ede15f059e76475dd129914c4ba16b8ef0dbc79605
|
File details
Details for the file minihost-0.1.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.4 MB
- Tags: CPython 3.12, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5469f84f94a30ca5edbd6b3ff7689b1047e68e6f1dc52b90d6763338af132c9a
|
|
| MD5 |
c66577ede1a30049af43b3401b24502b
|
|
| BLAKE2b-256 |
74c5dc44dc10b861dffe3297d413152fd2f26d29b4ccf81e58a1bdcc33a2ad68
|
File details
Details for the file minihost-0.1.5-cp312-cp312-macosx_11_0_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp312-cp312-macosx_11_0_x86_64.whl
- Upload date:
- Size: 1.9 MB
- Tags: CPython 3.12, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e17a1c56a6db0abae5d7d4a7a14a21acf2f11944fa7db646a171328fc15a0d43
|
|
| MD5 |
d2a65ec0c8f850d080e87ec3e43e15b2
|
|
| BLAKE2b-256 |
86b8ecf7399d29f1aab2b72ca176eea2ebcb51e94efd0695a8af461287195997
|
File details
Details for the file minihost-0.1.5-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: minihost-0.1.5-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
190a2df00a75d27d1e4befec1ea6da89d9959bdff3726f3ed77b19436b92148e
|
|
| MD5 |
517e0c325184bfcb3849d97e3ee9d3b5
|
|
| BLAKE2b-256 |
9724ce68267720d4d9facc3672332af7670e93428cee4feaaec2a1445bb71203
|
File details
Details for the file minihost-0.1.5-cp311-cp311-win_amd64.whl.
File metadata
- Download URL: minihost-0.1.5-cp311-cp311-win_amd64.whl
- Upload date:
- Size: 913.9 kB
- Tags: CPython 3.11, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba66cd3b791dacba6e70fef4758df2a2f0209d3000ac01382b913267d7c9d155
|
|
| MD5 |
00b8a322b09f2147ae0c92cbb1784a4e
|
|
| BLAKE2b-256 |
ef586810fc7cdf79768a3462ea3c8eebd76f13ef747610f31a8546f1af8873d3
|
File details
Details for the file minihost-0.1.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.4 MB
- Tags: CPython 3.11, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13e33a14ac30f1779b68bb7fdf6040cfe3d2372383798658e199f9b6948d2a5d
|
|
| MD5 |
bbbcbfb64a2e836875b5630104b04fa4
|
|
| BLAKE2b-256 |
17cae43eb172ea738a3c84bdcb76a3e459976091fafc0a4de70112f4a3cc841a
|
File details
Details for the file minihost-0.1.5-cp311-cp311-macosx_11_0_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp311-cp311-macosx_11_0_x86_64.whl
- Upload date:
- Size: 1.9 MB
- Tags: CPython 3.11, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06244812a2d518f29dab66ff0ae87f0934ea589153766c354327537e29553cad
|
|
| MD5 |
939ef91b702c5a4a7c28315beab4704a
|
|
| BLAKE2b-256 |
2fc5d53d536ba1b4b570892d326d58c670562ccedf7434445fe018bf10f0c734
|
File details
Details for the file minihost-0.1.5-cp311-cp311-macosx_11_0_arm64.whl.
File metadata
- Download URL: minihost-0.1.5-cp311-cp311-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.11, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c238ced4ac74d19cdbbe2ae7c5c71d188f45db717e80b454b9260514ba8c53d
|
|
| MD5 |
430c1f77c497d77aa70e562995ce13c6
|
|
| BLAKE2b-256 |
516f3644182e9c90bc9337f8e23bea02d97e4da632ee785ed09010361d44c0ee
|
File details
Details for the file minihost-0.1.5-cp310-cp310-win_amd64.whl.
File metadata
- Download URL: minihost-0.1.5-cp310-cp310-win_amd64.whl
- Upload date:
- Size: 914.1 kB
- Tags: CPython 3.10, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f8b5090883cf90711524ec5f735387e7b6d962c9f41e30fef5f59d032b2a971
|
|
| MD5 |
3f876d93dc11aca5c9d93cdc93f5dab8
|
|
| BLAKE2b-256 |
3760b160c54100a6c3465b8e730ee79e443721fa2a7440d6b012d8e2a5f082f7
|
File details
Details for the file minihost-0.1.5-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.4 MB
- Tags: CPython 3.10, manylinux: glibc 2.27+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e684c37feb8791f07c31b8a601dea7f8146fcd226b369461233424342b52bcf1
|
|
| MD5 |
49ab7fe5e80663e05e3e6e0ea46d679e
|
|
| BLAKE2b-256 |
60efacf610aee1f786907670ae2a0b0bc60a52b30107313ebd261f9ff3d4fd4e
|
File details
Details for the file minihost-0.1.5-cp310-cp310-macosx_11_0_x86_64.whl.
File metadata
- Download URL: minihost-0.1.5-cp310-cp310-macosx_11_0_x86_64.whl
- Upload date:
- Size: 1.9 MB
- Tags: CPython 3.10, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4799415c2ab3de63fd0f585b0abc87d538d97ac024f41e9d250cd6f24bc01435
|
|
| MD5 |
846eaf382bdcb04287de9d5467ce1050
|
|
| BLAKE2b-256 |
0079ebf4b2cc900975fc1dd911c60f269e1642d1fc59eb36776104a7e4c3e582
|
File details
Details for the file minihost-0.1.5-cp310-cp310-macosx_11_0_arm64.whl.
File metadata
- Download URL: minihost-0.1.5-cp310-cp310-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.10, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efb2571bd4cddc5b156dc968ad8a29307c1f15a64647aa78e51e7789bf2bbd49
|
|
| MD5 |
ce3d15c6f22f124429bf372fb28b9210
|
|
| BLAKE2b-256 |
1403bd3ee522ea7929d321fae82bd1e774925ebd82f44e799df44fedb3e9bfb4
|