Skip to main content

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_headless module
  • 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 and minihost resample CLI 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 devices CLI, audio_get_playback_devices() / audio_get_capture_devices() API, --playback-device / --capture-device on minihost 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 (MidiIn class)
  • 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 a concurrent.futures.Future for background loading of large sample-library plugins
  • VST3 preset I/O -- read and write .vstpreset files from C (minihost_vstpreset.h), C++, and Python (minihost.vstpreset); minihost presets CLI 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_close while 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

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

minihost-0.1.5-cp314-cp314-win_amd64.whl (941.1 kB view details)

Uploaded CPython 3.14Windows x86-64

minihost-0.1.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (2.4 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

minihost-0.1.5-cp314-cp314-macosx_11_0_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.14macOS 11.0+ x86-64

minihost-0.1.5-cp314-cp314-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

minihost-0.1.5-cp313-cp313-win_amd64.whl (913.4 kB view details)

Uploaded CPython 3.13Windows x86-64

minihost-0.1.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (2.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

minihost-0.1.5-cp313-cp313-macosx_11_0_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.13macOS 11.0+ x86-64

minihost-0.1.5-cp313-cp313-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

minihost-0.1.5-cp312-cp312-win_amd64.whl (913.6 kB view details)

Uploaded CPython 3.12Windows x86-64

minihost-0.1.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (2.4 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

minihost-0.1.5-cp312-cp312-macosx_11_0_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.12macOS 11.0+ x86-64

minihost-0.1.5-cp312-cp312-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

minihost-0.1.5-cp311-cp311-win_amd64.whl (913.9 kB view details)

Uploaded CPython 3.11Windows x86-64

minihost-0.1.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (2.4 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

minihost-0.1.5-cp311-cp311-macosx_11_0_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.11macOS 11.0+ x86-64

minihost-0.1.5-cp311-cp311-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

minihost-0.1.5-cp310-cp310-win_amd64.whl (914.1 kB view details)

Uploaded CPython 3.10Windows x86-64

minihost-0.1.5-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (2.4 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

minihost-0.1.5-cp310-cp310-macosx_11_0_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.10macOS 11.0+ x86-64

minihost-0.1.5-cp310-cp310-macosx_11_0_arm64.whl (1.7 MB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

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

Hashes for minihost-0.1.5-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 9c87f8c0ed8ac2a3d98b04f86d74858c5fb00dddf7a839774e67eb90289c3196
MD5 bf3946804ee9d1ea7e40e62f2fc43337
BLAKE2b-256 7a43121834ae6f47d0ab925157bc0157361ee6a258ab71d45d39e183efade871

See more details on using hashes here.

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

File hashes

Hashes for minihost-0.1.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a8c51b6eed05e9dbbf51afa91d76c29ae3925df28cab3a081f2e038328a6bb55
MD5 b278334cfd7fd63804add71a1cff430d
BLAKE2b-256 d71c844db3a865fcedc2b529561b6cf393303e75b4b9b6c6f6460450d92f9b8d

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp314-cp314-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp314-cp314-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 98b8e279f4696264fe99825319af10754dd3d120e19a19b40e8829510cf3be5a
MD5 f85cfac8b1af5f989775003e18d47d63
BLAKE2b-256 ec827dfc415db1ab515993bd7cd738c074790c3ebebc306ba8b45116deb569cc

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f2ab25cda3d0ea54bcb0e37013f4e06f6f212728a7021ff57738801cc75d722c
MD5 552942884629fc8163647a348d3b239c
BLAKE2b-256 053b625282324b75368f1c94929c1fe059b94bf543f7238a550d078a3046f7dd

See more details on using hashes here.

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

Hashes for minihost-0.1.5-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 1f6470cbeb061c675654ae1b8082b51b758404cab1a75fd02614266c04f82810
MD5 e7d43793036e9a66c3e72e9976b4e3b6
BLAKE2b-256 c47174271b58782d701d21679c6683553e370e5faadcd66be49b52d44c96904d

See more details on using hashes here.

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

File hashes

Hashes for minihost-0.1.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b7e693eb7bf91da4c9ad8f472274ac7ea3db374d0ea820c97937969e8b184e40
MD5 fb1211fec77316a89e65fe5dba394a78
BLAKE2b-256 625ad5e1bc315d882ff6342eb5101dee686942dce8e5a4a90d7852efaefdeb62

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp313-cp313-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp313-cp313-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 abba199cf45530b9e9c8f1f2a31b15322566b07858ea685a3b008f94eeb98615
MD5 79ef438aa51a64238fbe6075f08ba297
BLAKE2b-256 375107d5370671bd4ed9ae7f3458b331ab832f8885f2b99f7dedcd9e85105101

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b2f5bbb3bb899dd4c18ffac69e6ea2370495d2c4b5406823b5f70a4457db0e90
MD5 306234bdade8e81fa4780089f22cf82a
BLAKE2b-256 8edd62cf6f18f8583c31e74a23315beec96ebb523584c53827377969d3c7ebc8

See more details on using hashes here.

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

Hashes for minihost-0.1.5-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 fb0a4e1f1ee58281bcc44424af1ba7da709f768f3f610ddd3804a895dbf0a701
MD5 586119b23cc550b141cebaf25ddf06c5
BLAKE2b-256 5fb8c645725f377cab53c7ede15f059e76475dd129914c4ba16b8ef0dbc79605

See more details on using hashes here.

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

File hashes

Hashes for minihost-0.1.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5469f84f94a30ca5edbd6b3ff7689b1047e68e6f1dc52b90d6763338af132c9a
MD5 c66577ede1a30049af43b3401b24502b
BLAKE2b-256 74c5dc44dc10b861dffe3297d413152fd2f26d29b4ccf81e58a1bdcc33a2ad68

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp312-cp312-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp312-cp312-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 e17a1c56a6db0abae5d7d4a7a14a21acf2f11944fa7db646a171328fc15a0d43
MD5 d2a65ec0c8f850d080e87ec3e43e15b2
BLAKE2b-256 86b8ecf7399d29f1aab2b72ca176eea2ebcb51e94efd0695a8af461287195997

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 190a2df00a75d27d1e4befec1ea6da89d9959bdff3726f3ed77b19436b92148e
MD5 517e0c325184bfcb3849d97e3ee9d3b5
BLAKE2b-256 9724ce68267720d4d9facc3672332af7670e93428cee4feaaec2a1445bb71203

See more details on using hashes here.

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

Hashes for minihost-0.1.5-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 ba66cd3b791dacba6e70fef4758df2a2f0209d3000ac01382b913267d7c9d155
MD5 00b8a322b09f2147ae0c92cbb1784a4e
BLAKE2b-256 ef586810fc7cdf79768a3462ea3c8eebd76f13ef747610f31a8546f1af8873d3

See more details on using hashes here.

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

File hashes

Hashes for minihost-0.1.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 13e33a14ac30f1779b68bb7fdf6040cfe3d2372383798658e199f9b6948d2a5d
MD5 bbbcbfb64a2e836875b5630104b04fa4
BLAKE2b-256 17cae43eb172ea738a3c84bdcb76a3e459976091fafc0a4de70112f4a3cc841a

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp311-cp311-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp311-cp311-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 06244812a2d518f29dab66ff0ae87f0934ea589153766c354327537e29553cad
MD5 939ef91b702c5a4a7c28315beab4704a
BLAKE2b-256 2fc5d53d536ba1b4b570892d326d58c670562ccedf7434445fe018bf10f0c734

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9c238ced4ac74d19cdbbe2ae7c5c71d188f45db717e80b454b9260514ba8c53d
MD5 430c1f77c497d77aa70e562995ce13c6
BLAKE2b-256 516f3644182e9c90bc9337f8e23bea02d97e4da632ee785ed09010361d44c0ee

See more details on using hashes here.

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

Hashes for minihost-0.1.5-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 2f8b5090883cf90711524ec5f735387e7b6d962c9f41e30fef5f59d032b2a971
MD5 3f876d93dc11aca5c9d93cdc93f5dab8
BLAKE2b-256 3760b160c54100a6c3465b8e730ee79e443721fa2a7440d6b012d8e2a5f082f7

See more details on using hashes here.

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

File hashes

Hashes for minihost-0.1.5-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e684c37feb8791f07c31b8a601dea7f8146fcd226b369461233424342b52bcf1
MD5 49ab7fe5e80663e05e3e6e0ea46d679e
BLAKE2b-256 60efacf610aee1f786907670ae2a0b0bc60a52b30107313ebd261f9ff3d4fd4e

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp310-cp310-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp310-cp310-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 4799415c2ab3de63fd0f585b0abc87d538d97ac024f41e9d250cd6f24bc01435
MD5 846eaf382bdcb04287de9d5467ce1050
BLAKE2b-256 0079ebf4b2cc900975fc1dd911c60f269e1642d1fc59eb36776104a7e4c3e582

See more details on using hashes here.

File details

Details for the file minihost-0.1.5-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for minihost-0.1.5-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 efb2571bd4cddc5b156dc968ad8a29307c1f15a64647aa78e51e7789bf2bbd49
MD5 ce3d15c6f22f124429bf372fb28b9210
BLAKE2b-256 1403bd3ee522ea7929d321fae82bd1e774925ebd82f44e799df44fedb3e9bfb4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page