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.
At a glance
Process an input WAV through a chain of effect plugins and write the result:
import minihost
with (
minihost.Plugin("/path/to/delay.vst3", sample_rate=48000) as delay,
minihost.Plugin("/path/to/reverb.vst3", sample_rate=48000) as reverb,
minihost.PluginChain([delay, reverb]) as chain,
):
minihost.process_audio_to_file(
chain, "in.wav", "out.wav",
tail_seconds=4.0, # capture reverb tail
)
process_audio_to_file handles block iteration, latency compensation, sample-rate matching, channel layout, and tail rendering. See the Python API section for lower-level control.
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)
-
AudioBuffer-- the canonical audio container. Planar float32, JUCE-backed, stdlib-only. Numpy-style 2-axis indexing (buf[ch, frame_slice]), JUCE DSP ops (clear,apply_gain,magnitude,copy), zero-copy channel-range views (buf.channel_view(start, count)), DLPack export so it's accepted directly byPlugin.process/numpy.asarray/ PyTorch / etc. -
numpy is optional.
pip install minihostinstalls no Python runtime dependencies; the AudioBuffer API works without numpy.pip install minihost[numpy]enables numpy-typed APIs (AudioBuffer.as_ndarray(),read_audio(as_=numpy.ndarray), accepting numpy arrays as inputs). -
High-level offline processing --
process_audio_to_file(plugin_or_chain, "in.wav", "out.wav")collapses block iteration, latency compensation, sample-rate matching, and tail rendering into one call. -
Audio file I/O via miniaudio + tflac -- read WAV/FLAC/MP3/Vorbis, write WAV (16/24/32-bit) and FLAC (16/24-bit), optional Broadcast Wave (
bext) metadata on WAV output (write_audio(..., bwf={...})) -
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)
-
Control surface mapping --
minihost.MidiMappertranslates incoming MIDI CCs from a USB control surface (Launch Control / MIDIMix / nanoKONTROL / X-Touch / etc.) onto plugin parameters with optional value-range and curve (linear/exp/log); CLI:minihost play --map "channel:cc:param[:lo:hi[:curve]]"(repeatable) or--map-file PATHfor saved JSON mappings. -
14-bit MIDI CC --
map_cc14pairs controller n (0-31) with n+32 for 16384 steps instead of 128, which matters on anything a 7-bit CC makes audibly stepped; CLI:minihost play --map14 "channel:msb_cc:param". Overlap with a plain CC is rejected at map time, because a stray mapping on the LSB otherwise shadows it silently. -
OSC in and out --
OscServer/OscClientover UDP, built on JUCE'sjuce_osc(no new dependency).AudioDevice.connect_osc(port)parses/mh/param/<index>in C and drives parameters without taking a lock or the GIL;OscMapperadds name addressing, curves and ranges;OscFeedbacksends values back so a surface tracks preset loads instead of lying. CLI:minihost play --osc-port 9000 --osc-feedback HOST:PORT. -
Sample-accurate live parameter writes --
AudioDevice.send_param()queues a change on a lock-free ring the audio thread drains at the next block boundary, rather than writing the parameter underneath a runningprocessBlockasset_paramdoes. Coalesces per parameter per block, so a fader drag costs one write. -
Host playhead for realtime --
AudioDevice.set_transport_enabled(True)plustransport_play()/transport_set_bpm()/ loop points gives live plugins a tempo and a position. Tempo-synced delays, arpeggiators and LFOs previously saw no transport at all underminihost play. -
Generated touch surfaces --
minihost touch synth.vst3turns a plugin's parameters into a TouchOSC layout and a matching MIDI map, rendered from one table so the two cannot disagree. Widget choice follows the plugin's metadata (boolean -> button, stepped -> radio, else fader). Generation needs no dependency; compiling to.toscuses the optionalminihost[touch]extra (py2tosc). -
Looped sources for live tweaking --
minihost play --loop-midi PATHloops a MIDI file through the plugin (with All Notes Off between iterations);--loop-audio PATHloops an audio file into the plugin's input ring buffer at real time. Useful for parameter exploration against a repeating pattern. -
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
-
Preset morphing --
minihost.morphinterpolates between two parameter snapshots (A/B blend) for sound-design sweeps (capture/lerp/apply/morph) -
Thread-safe by design -- construction, destruction, and thread-affine control operations are marshaled onto a dedicated native plugin thread, so a plugin can be built on one thread and used or closed from another (only the real-time
process*path is single-thread/lock-free) -
Change notifications (latency, parameter info, program, non-parameter state) with deferred dispatch via
poll_callbacks() -
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.Futurethat resolves to a ready-to-usePlugin, loaded off the calling thread (safe to use/close from any thread thanks to the dedicated plugin thread) -
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
Library structure
minihost ships as two separate static libraries with a one-way dependency: libminihost_audio builds on libminihost, never the reverse.
-
libminihost-- the plugin host core (projects/libminihost/). Loads and runs VST3/AU/LV2 plugins and processes audio blocks you hand it: MIDI in/out, parameters, state save/restore, sample-accurate automation, sidechain, transport, bus layouts, and the routing abstractions (PluginChain,PluginBus,PluginGraph). Depends only on JUCE. C ABI prefix:mh_*(e.g.mh_open,mh_process,mh_chain_*,mh_bus_*,mh_graph_*). Header:minihost.h. Link this alone to load a plugin and feed it your own buffers -- the offline and embedded path, with no device or codec dependencies. -
libminihost_audio-- the I/O layer around the core (projects/libminihost_audio/). It has no plugin-format knowledge; it gets audio and MIDI in and out of the machine and drives a plugin or chain through its real-time audio callback. Provides live audio device playback/capture (miniaudio), audio file read/write (read WAV/FLAC/MP3/Vorbis; write WAV/FLAC via miniaudio + tflac), MIDI ports (libremidi), and the lock-free ring buffers. Depends onlibminihostplus the vendored miniaudio, tflac, and libremidi. C ABI prefix:mh_audio_*. Headers:minihost_audio.h,minihost_audiofile.h,minihost_midi.h. Link this in addition tolibminihostwhen you want real-time devices, file I/O, or MIDI hardware.
In short: libminihost runs the plugin; libminihost_audio connects it to speakers, files, and MIDI hardware. The Python wheel links both.
Desktop application
minihost_desktop (projects/minihost_desktop/) is a developer-facing GUI host built on the same libraries. It loads VST3/AU/LV2 plugins, wires them into a node graph on a canvas, opens native plugin editor windows, renders the graph to disk offline, and drives a realtime audio device with live MIDI input and a transport (BPM / loop region). Project files are JSON, schema-versioned, and round-trip with the Python loader (minihost.load_project / render_project), so a graph built in the app renders identically from the command line.
Each plugin window carries the host-side controls a plugin's own editor usually leaves out: a factory-program selector, a bypass toggle (backed by the plugin's own bypass parameter where it publishes one), and .vstpreset load/save for interchange with other hosts. Offline renders put every plugin into non-realtime mode for the duration, so a bounce uses the plugin's offline code path rather than its realtime one.
Status: functional and pre-release. Both the offline renderer and the realtime engine are built and tested. Not yet done: packaging (code signing, notarization, installers). See docs/dev/desktop_app.md for the design and docs/dev/desktop_app_todo.md for per-feature status.
Plugins run in-process, the same trust model a DAW uses: a misbehaving plugin can crash the whole app and lose unsaved canvas edits. Two mitigations bound the harm. Plugin scanning is out-of-process, so a plugin that crashes while being catalogued takes down only a disposable child. And the working project is autosaved to a sidecar every few seconds; after an unclean exit the app offers to recover it on the next launch, so a crash costs at most a few seconds of unsaved editing. Save often regardless.
The app is opt-in and off by default (it requires a non-headless build, so it is excluded from the headless library, CLI, and Python wheel builds):
# Build the desktop app into its own build-desktop/ dir (keeps the
# headless library / CLI / wheel build in build/ untouched)
make desktop
# Or configure it by hand:
cmake -B build-desktop -DMINIHOST_BUILD_DESKTOP=ON
cmake --build build-desktop --config Release --target minihost_desktop
# Build (if needed) and launch it (macOS)
make run-desktop
Headless entry points for scripting and CI: minihost_desktop --render-project=<project.json> renders a project with no window, and minihost_desktop --save-roundtrip=<project.json> parses and re-saves a project (used as a build smoke test).
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
# Also build the non-headless libminihost_gui.a (GUI support: plugin
# editor windows). The headless libminihost.a is still built alongside it.
cmake -B build -DMINIHOST_BUILD_GUI_LIB=ON
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:
python scripts/download_juce.py
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
Map a control surface to plugin parameters
--map wires incoming MIDI CCs from a USB control surface (Launch Control, MIDIMix, nanoKONTROL, X-Touch, etc.) onto plugin parameters. When set, MIDI is routed through Python via a MidiMapper; mapped CCs become parameter writes and unmapped events (notes, unmapped CCs) are forwarded to the plugin so notes still play. Format: channel:cc:param[:lo:hi[:curve]]. Curves: linear (default), exp (more resolution at low end), log (more resolution at high end).
# One mapping per --map flag, repeatable
minihost play /path/to/synth.vst3 --midi 0 \
--map 0:7:Volume \
--map 0:10:Pan:-1:1 \
--map 0:74:Cutoff:0:1:exp
For a permanent setup, save the mappings to a JSON file once and load it with --map-file:
{
"mappings": [
{"channel": 0, "cc": 7, "param": "Volume"},
{"channel": 0, "cc": 10, "param": "Pan", "value_range": [-1.0, 1.0]},
{"channel": 0, "cc": 74, "param": "Cutoff", "curve": "exp"}
]
}
minihost play /path/to/synth.vst3 --midi 0 \
--map-file ~/.config/minihost/launch_control.json
--map and --map-file are combinable -- the file loads first, CLI args append. Required JSON fields per entry: channel, cc, param. Optional: value_range (default [0.0, 1.0]), curve (default "linear").
Loop a MIDI or audio file as the source
--loop-midi loops a MIDI file into a synth (or any plugin that accepts MIDI), useful for live-tweaking parameters against a repeating pattern. A Python thread schedules events at wall-clock-correct times; All Notes Off is sent on every channel between iterations to silence sustained notes.
# Loop a MIDI pattern through a synth while live-tweaking knobs
minihost play /path/to/synth.vst3 \
--midi 0 \
--map 0:74:Cutoff:0:1:exp \
--loop-midi tests/_wav/test_pattern.mid
--loop-audio loops an audio file as the plugin's input, useful for testing effects against a known source without needing live audio. The ring buffer is auto-enabled; the file is resampled to the device rate if needed. Mutually exclusive with --input.
# Loop a guitar take into a reverb while turning the mix knob
minihost play /path/to/reverb.vst3 \
--midi 0 \
--map 0:7:Mix \
--loop-audio guitar_dry.wav
Both loop flags can run alongside live MIDI input (the file's events and your live notes are merged into the plugin).
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) |
Native CLI binaries
Alongside the Python minihost command, the project ships two native binaries -- minihost_c (pure C) and minihost_cpp (C++) -- built into build/projects/ and published in the cli release archive. They are independent implementations over the same C API and are meant to be interchangeable; a conformance test runs them against each other and fails if they diverge. Beyond the single-plugin commands they add the routing ones:
Plugins are named by path, or by name once they have been scanned -- matching ignores case and takes the whole name:
minihost_c scan # index this platform's plugin locations
minihost_c probe dexed # by name (whole name, any case)
minihost_c --fuzzy probe "pro-q 3" # --fuzzy to match part of a name
minihost_c --format au probe "FabFilter Pro-Q 4" # pin a format
A plugin installed in both AU and VST3 resolves to the VST3 unless --format says otherwise. Substring matching is opt-in because it is rarely decisive on a large collection: with 343 plugins installed here, reverb matches 5 and filter 31.
scan takes an optional directory to scan instead of the defaults. It probes each plugin, so a first pass over a large collection takes minutes; results are cached (shared with the Python CLI's cache), written as the scan proceeds, and only changed plugins are re-probed. Each plugin is probed in a child process the scan is willing to lose, so one that hangs or crashes on load costs one cache entry (timeout / crash) instead of the scan -- --in-process opts out. See the CLI reference.
# one plugin: audio in, or a MIDI file through an instrument
minihost_c process Plugin.vst3 -i input.wav -o output.wav --tail 3
minihost_c process Synth.vst3 -m song.mid -o output.wav --tail 2
# plugins in series; MIDI effects come first and drive what follows
minihost_c chain EQ.vst3 Reverb.vst3 -i input.wav -o output.wav --mix 1:0.5 --tail 3
minihost_c chain Arpeggiator.component Synth.vst3 -m song.mid -o output.wav
# branches in parallel, summed -- one MIDI part layered across instruments.
# A branch may itself be a chain: commas run plugins in series.
minihost_c bus SynthA.vst3 SynthB.vst3 -m song.mid -o output.wav
minihost_c bus Synth.vst3 "Chorder.component,Synth.vst3" -m song.mid -o out.wav --gain 1:0.7
See the CLI reference for the full option list and MIDI Routing for why MIDI effects must precede the instrument they drive.
Python API
Install:
pip install minihost # AudioBuffer-only API; no numpy required
pip install minihost[numpy] # adds numpy-typed return values + numpy input acceptance
The default audio container is minihost.AudioBuffer (planar float32, JUCE-backed, stdlib-only). It supports DLPack so any C extension that takes a 2D float32 c-contiguous buffer (including all of minihost's process methods) accepts it directly. Numpy is fully supported when installed -- pass as_=numpy.ndarray to receive numpy arrays from read_audio / render_midi, or call .as_ndarray() on any AudioBuffer for a zero-copy numpy view.
Quick start: process a WAV file through a chain
import minihost
with (
minihost.Plugin("/path/to/delay.vst3", sample_rate=48000) as delay,
minihost.Plugin("/path/to/reverb.vst3", sample_rate=48000) as reverb,
minihost.PluginChain([delay, reverb]) as chain,
):
minihost.process_audio_to_file(
chain, "in.wav", "out.wav",
tail_seconds=4.0, # capture reverb tail
)
process_audio_to_file handles block iteration, latency compensation, sample-rate matching (input is auto-resampled to the plugin's rate), mono-to-stereo channel duplication, and tail rendering. For in-memory data use process_audio(plugin_or_chain, audio, tail_seconds=...), which returns an AudioBuffer.
Lower-level processing
import minihost
plugin = minihost.Plugin("/path/to/plugin.vst3", sample_rate=48000)
# AudioBuffer is the default container. process accepts it directly via DLPack.
input_audio = minihost.AudioBuffer(2, 512)
output_audio = minihost.AudioBuffer(2, 512)
plugin.process(input_audio, output_audio)
# Numpy users can mix and match -- both accepted as inputs:
import numpy as np # requires minihost[numpy]
input_np = np.zeros((2, 512), dtype=np.float32)
plugin.process(input_np, output_audio) # numpy in -> AudioBuffer out
output_np = output_audio.as_ndarray() # zero-copy numpy view
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)
Preset Morphing
Interpolate between two parameter snapshots (an A/B blend), useful for sound-design sweeps. Morphing operates on normalized per-parameter values (not opaque VST/AU state blobs).
import minihost
plugin = minihost.Plugin("/path/to/synth.vst3", sample_rate=48000)
# Capture two states (e.g. after loading two presets)
a = minihost.capture_params(plugin)
# ... dial in a different sound ...
b = minihost.capture_params(plugin)
# Blend 30% of the way from A to B and apply to the plugin
minihost.morph_params(plugin, a, b, 0.3)
# Or compute a blend without applying; t can be a per-parameter sequence
blended = minihost.lerp_params(a, b, 0.5)
minihost.apply_params(plugin, blended)
Async Plugin Loading
import minihost
# Load a heavy plugin in the background (off the calling thread)
future = minihost.open_async("/path/to/heavy_sampler.vst3", sample_rate=48000)
# Do other work while plugin loads...
# Block until ready -- returns a normal Plugin
plugin = future.result()
print(f"Loaded: {plugin.num_params} params")
# The plugin is built on a loader thread but is safe to use and close from
# any thread: minihost marshals thread-affine operations onto a dedicated
# native plugin thread. Loads are serialized on that thread, so this is
# non-blocking (not parallel) loading.
plugin.close()
Shared session for multi-plugin loading
mh_open and its Python equivalent register the JUCE plugin formats on every call. A Session builds that format manager once and reuses it across loads, probes and scans, which is the difference between loading one plugin and loading a chain of them.
import minihost
session = minihost.Session()
eq = session.open("/path/to/EQ.vst3", sample_rate=48000)
reverb = session.open("/path/to/Reverb.vst3", sample_rate=48000)
# AudioUnits are identified by an id rather than a path, so they load from a
# serialized PluginDescription -- through the session like anything else.
delay = session.open_desc(
'<PLUGIN name="AUDelay" format="AudioUnit" file="AudioUnit:Effects/aufx,dely,appl"/>'
)
session.close() # the plugins keep working; they do not depend on it
The native chain and bus commands load this way, which is where the saving shows: a four-plugin chain built four format managers before.
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 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).
# AudioBuffer slicing returns a new AudioBuffer; write_input accepts it
# directly via DLPack -- no numpy required.
audio = minihost.AudioDevice(plugin)
audio.enable_input() # ~0.5s ring buffer by default
audio.start()
data, sr = minihost.read_audio("guitar.wav")
block_size = 512
for i in range(0, data.frames, 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).
# Default container is AudioBuffer (planar float32, no numpy required).
data, sample_rate = minihost.read_audio("input.wav")
# data is an AudioBuffer of shape (channels, samples)
# Pass as_=numpy.ndarray to get a numpy array instead (requires minihost[numpy]).
import numpy as np
data_np, sample_rate = minihost.read_audio("input.wav", as_=np.ndarray)
# write_audio accepts AudioBuffer, numpy ndarray, or any DLPack/buffer-protocol producer.
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)
# Broadcast Wave (bext) metadata for film/broadcast workflows (WAV only)
minihost.write_audio("take.wav", data, sample_rate, bit_depth=24, bwf={
"description": "Scene 12 take 3",
"originator": "minihost",
"originator_reference": "REF-0012",
"origination_date": "2026-07-07", # yyyy-mm-dd
"origination_time": "12:34:56", # hh:mm:ss
"time_reference": 48000 * 3600, # samples since midnight (timecode anchor)
})
# 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
# Works on AudioBuffer (default), numpy ndarray, or any 2D float32
# c-contig buffer-protocol producer. Return type matches the input type
# (AudioBuffer in -> AudioBuffer out; numpy in -> numpy out).
data, sr = minihost.read_audio("input_44100.wav") # AudioBuffer @ 44.1kHz
resampled = minihost.resample(data, 44100, 48000) # -> 48kHz AudioBuffer
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. Returns AudioBuffer by default; pass as_=numpy.ndarray for numpy:
import minihost
plugin = minihost.Plugin("/path/to/synth.vst3", sample_rate=48000)
# Render to AudioBuffer (default)
audio = minihost.render_midi(plugin, "song.mid")
print(f"Rendered {audio.frames / 48000:.2f} seconds of audio")
# Numpy variant
import numpy as np
audio_np = minihost.render_midi(plugin, "song.mid", as_=np.ndarray)
# Render directly to WAV file (returns frame count)
samples = minihost.render_midi_to_file(plugin, "song.mid", "output.wav", bit_depth=24)
# Stream blocks for large files or real-time processing.
# Each yielded block is an AudioBuffer; pass as_=numpy.ndarray to yield numpy instead.
for block in minihost.render_midi_stream(plugin, "song.mid", block_size=512):
# block.shape == (channels, n) where n <= 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() # returns AudioBuffer or None
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 -- AudioBuffer is the default container
input_audio = minihost.AudioBuffer(2, 512)
output_audio = minihost.AudioBuffer(2, 512)
chain.process(input_audio, output_audio)
# Process with MIDI (enters the first plugin that accepts it, then
# carried on by any plugin that produces MIDI -- e.g. arpeggiator -> synth)
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") # -> AudioBuffer
minihost.render_midi_to_file(chain, "song.mid", "output.wav")
# File-to-file processing through the chain (handles tail, latency, resample)
minihost.process_audio_to_file(chain, "input.wav", "output.wav", tail_seconds=4.0)
# 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")
Parallel routing (PluginBus)
PluginChain is series; PluginBus is parallel. A bus fans the same input to N branches (each a PluginChain) and sums their outputs with a per-branch gain -- parallel compression, dry-bus + reverb-send, multi-band processing. With process_midi, the same MIDI is delivered to every branch, which is the idiomatic way to layer one part across several instruments:
import minihost
# Three synths layered under one MIDI part, summed to stereo.
a = minihost.PluginChain([minihost.Plugin("/path/to/saw.vst3", sample_rate=48000)])
b = minihost.PluginChain([minihost.Plugin("/path/to/sub.vst3", sample_rate=48000)])
c = minihost.PluginChain([minihost.Plugin("/path/to/pad.vst3", sample_rate=48000)])
bus = minihost.PluginBus(2, 2, max_block_size=512, sample_rate=48000.0)
bus.add_branch(a, gain=1.0)
bus.add_branch(b, gain=0.7)
bus.add_branch(c, gain=0.5)
silence = minihost.AudioBuffer(2, 512) # synths ignore audio input
out = minihost.AudioBuffer(2, 512)
note_on = [(0, 0x90, 60, 100)] # C4 reaches ALL three synths
bus.process_midi(silence, out, note_on)
A complete, runnable version (block loop, chord, WAV output) is in examples/parallel_bus.py.
For arbitrary node-to-node topologies (multiple inputs/outputs, MIDI processors, channel pick/merge), use PluginGraph -- the general DAG executor that also backs project files (minihost.load_project). Branch MIDI output is not collected by the bus; reach for PluginGraph if you need that.
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 (carried onward by plugins that produce MIDI)
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);
MIDI File Rendering
Read a standard MIDI file into the event form mh_process* consumes. Tracks are merged and the file's tempo map is applied; sample_offset is absolute, so rebase it per block:
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(synth, inputs, outputs, block, block_midi, n);
}
mh_midi_file_free(events);
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
minihost runs a dedicated native plugin thread and marshals every thread-affine plugin operation onto it -- construction, destruction, and control-plane queries (state, parameter text, program names, reset, set_sample_rate, processing precision). This makes plugins safe to build on one thread and use or close from another (which is what makes open_async work), and hardens the whole library against cross-thread use.
-
process*functions (process,process_midi,process_auto,process_double,process_sidechain): the real-time path -- lock-free, call from a single thread (typically the audio thread). Not marshaled. -
All other (control) functions: safe to call from any thread.
-
Reconfiguring calls (
set_sample_rate,set_state,set_processing_precision,set_non_realtime,reset) must not overlap aprocess*call -- they reconfigure the audio pipeline whileprocessruns unprotected. Stop processing before calling them. -
Set the environment variable
MINIHOST_MESSAGE_THREAD=0to disable the plugin thread (operations then run inline on the caller's thread; cross- thread plugin use, includingopen_async, becomes unsafe).
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
Release files for minihost 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
Total release size: 34.2 MB
Release files / minihost-0.9.0-cp314-cp314-win_amd64.whl
| Download URL | minihost-0.9.0-cp314-cp314-win_amd64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.14 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
6778dbb8a8610eaa1aaef00ccf246a4e15e26511ddfbcde668275f1266e1cad5
|
|
BLAKE2b-256 checksum How to use checksums |
074f058ded40e811d769969e0af0aa1c4cf91374b3734f57117102c4cbf5443c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | minihost-0.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | CPython 3.14 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
4935fbfc77259c46a0f6709c5d235d429ca5a12d4424274f9fe9c2fbbf4320f6
|
|
BLAKE2b-256 checksum How to use checksums |
dc80d4999dde401e85681cf04eef796f85e730220573b5f6bb925221e3545a4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp314-cp314-macosx_11_0_x86_64.whl
| Download URL | minihost-0.9.0-cp314-cp314-macosx_11_0_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.14 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
309c16e695ac39ed776dea3c7e98e69e8d59e91559d8176036f0a2c6bbbe761d
|
|
BLAKE2b-256 checksum How to use checksums |
46d73d2ef19061f4061a11c7a1c60eb5081237aa72a0872badbe596c09b685a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp314-cp314-macosx_11_0_arm64.whl
| Download URL | minihost-0.9.0-cp314-cp314-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.14 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
016316d4d677c2f1b29a67fe64f140456d85f0bf795e12b3e0c1fece155059e7
|
|
BLAKE2b-256 checksum How to use checksums |
9583b351a5822290fed7b2b9e976191f6db04cd9de5e6cd736b0564a4fae63aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp313-cp313-win_amd64.whl
| Download URL | minihost-0.9.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
25c0f147362c089bfa335cf8ae48baa4d2d0dae65094432aa8ca49eb714f4ee4
|
|
BLAKE2b-256 checksum How to use checksums |
3dfea2f6d801d0c438483845a1efd845e16252543212d08e6eefe3c9f00e89fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | minihost-0.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | CPython 3.13 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
160bbe1cd5f6d1d4fac8ded0fd1b71d9f4ffbec587e6d53e116b9122a50367de
|
|
BLAKE2b-256 checksum How to use checksums |
35ce2cecb97a51eabd01990429167c176bbe760b6975ba9063c1d642ac0aac9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp313-cp313-macosx_11_0_x86_64.whl
| Download URL | minihost-0.9.0-cp313-cp313-macosx_11_0_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.13 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
b7ad3b33240040ae2a8e33316886ef6171c2867a3f9af8da236189f3baee998e
|
|
BLAKE2b-256 checksum How to use checksums |
b5c9754f3a6dd24e562be8b83e0f7a67ead1917c287c9f8d844e8dd34f83122e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | minihost-0.9.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
b2efdf148a43fed3642cd6b9f787881effc57459f6ef6835a415fec9d3603562
|
|
BLAKE2b-256 checksum How to use checksums |
a1a9bd6a8d8922cf1ff983065e880ca0ef3bdda78fc46da65fabdb0a2ad3bbc1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp312-cp312-win_amd64.whl
| Download URL | minihost-0.9.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
fa1a17e4d40a207a97785622d583a5d7d2505c9ebc81e162a76bcba233a579cb
|
|
BLAKE2b-256 checksum How to use checksums |
2b47d7d115fd98b561b17deff92afebbb1d4f30cbc8dc95c3ed92806a583920a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | minihost-0.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
8cd69667e7cd3754f47be55d359e446b0c22198cc1ed39af9041274ca8bd89b0
|
|
BLAKE2b-256 checksum How to use checksums |
fd64d9b4c097460325e04108fd1c89cc5259deb03701ab5593df52324469a577
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp312-cp312-macosx_11_0_x86_64.whl
| Download URL | minihost-0.9.0-cp312-cp312-macosx_11_0_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.12 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
935783b136cc2e7dc5893a816d596bd974cd147485ab7f6eaf63ccf2623a9cc9
|
|
BLAKE2b-256 checksum How to use checksums |
40700a887ec7cfe227abec1e5bcbe28e2118fa0786dfe4bca592ded41156e226
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | minihost-0.9.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
200f40664080af2717c6bd44b9a4b1d4d6e6ab8e6830bbc9c2b0bfd74d83f398
|
|
BLAKE2b-256 checksum How to use checksums |
c86e972fcd2c2dd8a887b9803cc7984a14bc41c93a03137e533729d0401ec696
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp311-cp311-win_amd64.whl
| Download URL | minihost-0.9.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
51057ed46a19ac9ed424563dafa0950d68331c0f7018ad3c5a3a79db14fa9dca
|
|
BLAKE2b-256 checksum How to use checksums |
c9eea1258cf98b80b0c89d44d74a3b00a2252bcdf418c7ea7de0b3e5b2a43427
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | minihost-0.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | CPython 3.11 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
2aa88efb37a71ae38b3e338ebf75d6563769bebf1c378e9bcc0f41380b9bacf5
|
|
BLAKE2b-256 checksum How to use checksums |
099fec36bf7d29e50ad7bedf19bb50785b9afa91b54e835bfb62de5726f51d05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp311-cp311-macosx_11_0_x86_64.whl
| Download URL | minihost-0.9.0-cp311-cp311-macosx_11_0_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.11 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
30c4291d9cfaa47a521362e25c3a50725f7a42178a2f2cb24649c6b9a6ff5ae4
|
|
BLAKE2b-256 checksum How to use checksums |
4069db12286126637620187827f90785fb1e3f337d3e49a22a5334232a8eecaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | minihost-0.9.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
dfc7765c53a4f8527400232c369b227f1bd537cfc32812e7a8ca69691e6c5ca3
|
|
BLAKE2b-256 checksum How to use checksums |
1e572eb507120761d4ca46ce23d7a61ee711e682343459dd78b7619505ea7161
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp310-cp310-win_amd64.whl
| Download URL | minihost-0.9.0-cp310-cp310-win_amd64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.10 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
bab57a6cf56884e17e3a409d69bcd248b593a4ce64a847ce72e31e8e1e458e63
|
|
BLAKE2b-256 checksum How to use checksums |
86427db6d156b5a53a2f303d14bdbbfc8baa0687f93d6e6ada1c36b92c5147ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | minihost-0.9.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | CPython 3.10 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
801fbfb9469f799a731b02c473a29956ac244f9c37bf4bf4a935899812914f89
|
|
BLAKE2b-256 checksum How to use checksums |
e647abb3da5c2267017419d27e1b89eaad3f2906ed4f15a66c2273f1fb588ffe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp310-cp310-macosx_11_0_x86_64.whl
| Download URL | minihost-0.9.0-cp310-cp310-macosx_11_0_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.10 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
65126511cad9290b9130f5c912cfb4aeed80fad1e4ef5d8bb44f0fe0aac84d4e
|
|
BLAKE2b-256 checksum How to use checksums |
a52c236883f55af99069a6fcb58e139a57f3b87e23a72aec90421a0dba451d5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / minihost-0.9.0-cp310-cp310-macosx_11_0_arm64.whl
| Download URL | minihost-0.9.0-cp310-cp310-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.5 MB |
| Tags | CPython 3.10 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
20e79e987d332029b4099e8217d0d0dcda20b65c86eca00b9f883a738c6f0357
|
|
BLAKE2b-256 checksum How to use checksums |
7468807b7aa9a1ce3214ba0f66b31a918ff6be14757d607954e26283a7b750b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|