A cross-platform MIDI library for V, supporting both MIDI 1.0 and MIDI 2.0 (UMP).
Backends are loaded at runtime — no compile-time C headers or static linking required.
| Platform | Backends (tried in order) |
|---|---|
| Linux | JACK, ALSA |
| macOS | CoreMIDI |
| Windows | Windows MIDI Services (MIDI 2.0), WinMM |
- MIDI 1.0 send/receive with callback or polling
- MIDI 2.0 / UMP send/receive
- Hot-plug port observation (device added/removed callbacks)
- Virtual port creation
- Automatic backend discovery — falls back gracefully if a backend is unavailable
From the V package manager:
v install https://github.com/krotki/midviManually:
git clone https://github.com/krotki/midvi ~/.vmodules/midviThen import in your code:
import midvi
import midvi.clientsimport midvi
import midvi.clients
fn main() {
mut out := midvi.MidiOut.new(clients.discover_midi_out('my-app') or {
eprintln(err)
exit(1)
})
defer { out.close() }
// List available ports
for i, name in out.get_ports() {
println('[${i}] ${name}')
}
out.open_port(0, 'out') or { panic(err) }
// Send a middle-C note on channel 1
out.send_message(midvi.note_on(0, 60, 100))
// ... wait ...
out.send_message(midvi.note_off(0, 60, 0))
}import midvi
import midvi.clients
import time
fn main() {
mut inp := midvi.MidiIn.new(clients.discover_midi_in('my-app') or {
eprintln(err)
exit(1)
})
defer { inp.close() }
inp.open_port(0, 'in') or { panic(err) }
// MIDI 1.0 events
inp.set_callback(fn (ev midvi.MidiEvent) {
ch := ev.channel() + 1
match ev.msg_type() {
.note_on { println('Note On ch=${ch} note=${ev.data1} vel=${ev.data2}') }
.note_off { println('Note Off ch=${ch} note=${ev.data1}') }
.control_change { println('CC ch=${ch} ctrl=${ev.data1} val=${ev.data2}') }
else { println('MIDI status=0x${ev.status:02x}') }
}
})
// MIDI 2.0 / UMP events (if the backend supports it)
inp.set_ump_callback(fn (ev midvi.UmpEvent) {
println('UMP type=${ev.ump.msg_type()} group=${ev.ump.group()}')
})
// Block until Ctrl+C
for { time.sleep(10 * time.millisecond) }
}for {
if ev := inp.get_message() {
println('received: status=0x${ev.status:02x}')
}
time.sleep(1 * time.millisecond)
}import midvi
import midvi.clients
import time
fn main() {
mut obs := midvi.MidiPortObserver.new(clients.discover_midi_observer('my-app') or {
eprintln(err)
exit(1)
})
defer { obs.close() }
obs.set_input_callback(fn (ev midvi.PortEvent) {
match ev.event {
.added { println('input added: ${ev.name}') }
.removed { println('input removed: ${ev.name}') }
}
})
obs.set_output_callback(fn (ev midvi.PortEvent) {
match ev.event {
.added { println('output added: ${ev.name}') }
.removed { println('output removed: ${ev.name}') }
}
})
for { time.sleep(10 * time.millisecond) }
}midvi.note_on(channel, note, velocity) // -> MidiEvent
midvi.note_off(channel, note, velocity) // -> MidiEvent
midvi.control_change(channel, controller, val) // -> MidiEvent
midvi.program_change(channel, program) // -> MidiEvent
midvi.pitch_bend(channel, value) // -> MidiEvent (value: -8192..8191)The capi/ directory contains everything needed:
| File | Purpose |
|---|---|
capi/capi.v |
V module that exports the full C API via @[export] |
capi/midvi.h |
C/C++ header — the only file consumers need to include |
Pre-built binaries for all platforms and architectures are attached to each GitHub release. To build from source:
# Linux
v -shared -o libmidvi.so capi/
# macOS
v -shared -o libmidvi.dylib capi/
# Windows (PowerShell)
v -shared -o midvi.dll capi/The shared library loads backends dynamically at startup — no static linking is needed. The relevant runtime libraries must be present on the system:
| Platform | Required at runtime |
|---|---|
| Linux | libasound.so.2 (ALSA) and/or libjack.so.0 (JACK) |
| macOS | CoreMIDI.framework (always available) |
| Windows | winmm.dll (always available); Windows.Devices.Midi2 for MIDI 2.0 (Windows 11+) |
MIT