#hosting #midi #lock-free-queue #vst #standalone #real-time

audio-processor-standalone-midi

Stand-alone MIDI hosting for a VST host or an audio-processor-traits implementor

23 releases (12 stable)

1.13.0 Aug 26, 2024
1.11.0 Jan 17, 2024
1.10.0 Dec 7, 2023
1.9.0 May 22, 2023
0.1.0 Jul 21, 2021

#1154 in Audio

40 downloads per month
Used in audio-processor-standalon…

MIT license

330KB
2.5K SLoC

audio-processor-standalone-midi

crates.io docs.rs


This crate provides conversion into the VST types, which is to allow a VST host to use it. This is provided by the MidiVSTConverter.

Wraps midir to provide MIDI input handling. The host may be started with MidiHost.

When MIDI messages are received, they're pushed onto a lock-free atomic_queue::Queue. The messages are picked-up in the audio-thread by MidiAudioThreadHandler.

It provides easy stand-alone MIDI integration with:

  • audio-processor-traits - MidiEventHandler trait
  • rust-vst - PluginInstance

Currently, MIDI messages over 3 bytes are dropped by this host. In addition, the queue is bounded & a size must be provided. Default implementations will use a MIDI queue capacity of 100. This is a stand-alone MIDI host with helpers for doing real-time handling of MIDI messages in the audio-thread and to integrate with VST plugins.

This is part of https://github.com/yamadapc/augmented-audio.

Memory Safety

In order to integrate with the VST C API, this crate does manual memory allocation and handling. This is due to VST event types being unrepresentable as safe Rust constructs (and due to real-time safety being required as well).

Real-time Safety

This crate provides the host side, which is the MIDI host. This host allocates when it receives messages from midir.

The events are forwarded onto a lock-free queue (atomic_queue).

On the audio_thread and vst modules, past construction methods that should be called on the audio-thread will not (de)-allocate. This is tested using the assert_no_alloc crate.

In addition, basedrop / audio_garbage_collector are used to prevent de-allocation from happening on the audio-thread.

Test coverage

The crate has unit-tests (though it should be considered experimental as all of this repository).

Test coverage is at 80%.

Actix & Actors

The MidiHost exposes an actix API, to be used with the actix actor system. This is to ease communicating with the midi handler from multiple threads.

Usage

To use this crate:

fn example() {
    use audio_processor_standalone_midi::audio_thread::MidiAudioThreadHandler;
    use audio_processor_standalone_midi::host::MidiHost;
    use audio_processor_standalone_midi::vst::MidiVSTConverter;
    use basedrop::Collector;

    // GC ======================================================================================
    // See `audio-garbage-collector` for an easier to use wrapper on top of this.
    //
    // `Collector` will let us use reference counted values on the audio-thread, which will be
    // pushed to a queue for de-allocation. You must set-up a background thread that forces it
    // to actually collect garbage from the queue.
    let gc = Collector::new();
    let handle = gc.handle();

    // Host ====================================================================================
    // A host may be created with `new` or `default_with_handle`.
    let mut host = MidiHost::default_with_handle(&handle);
    // It'll connect to all MIDI input ports when started
    // host.start().expect("Failed to connect");
    // The host will push messages onto a lock-free queue. This is a reference counted value.
    let midi_messages_queue = host.messages().clone();

    // Audio-thread ============================================================================
    // ...
    // Within your audio-thread
    // ...
    // You'll want to share a `MidiAudioThreadHandler` between process calls as it pre-allocates
    // buffers.
    let mut midi_audio_thread_handler = MidiAudioThreadHandler::default();

    // On each tick you'll call collect
    midi_audio_thread_handler.collect_midi_messages(&midi_messages_queue);
    // You'll get the MIDI message buffer
    let midi_messages = midi_audio_thread_handler.buffer();
    // ^ This is a `&Vec<MidiMessageEntry>`. If you're using `audio-processor-traits`, you can
    //   pass this into any `MidiEventHandler` implementor.

    // VST interop =============================================================================
    // If you want to interface with a VST plugin, you'll need to convert messages into the
    // C-api.
    // ...
    // You'll want to share this between process calls as it pre-allocates buffers.
    let mut midi_vst_converter = MidiVSTConverter::default();
    midi_vst_converter.accept(&midi_messages);
    // This can be passed into VST plugins.
    let events: &vst::api::Events = midi_vst_converter.events();
}

License: MIT

Dependencies

~2–31MB
~477K SLoC