#audio #vst-plugin #midi #user-interface #web #processor #standalone #vst

audio-processor-standalone-midi

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

13 releases (2 stable)

new 1.2.0 Sep 29, 2022
1.1.0 Sep 22, 2022
1.0.0-alpha.10 May 25, 2022
1.0.0-alpha.8 Apr 7, 2022
0.1.0 Jul 21, 2021

#152 in Audio

Download history 48/week @ 2022-06-08 11/week @ 2022-06-15 16/week @ 2022-06-22 6/week @ 2022-06-29 26/week @ 2022-07-06 26/week @ 2022-07-13 31/week @ 2022-07-20 14/week @ 2022-07-27 22/week @ 2022-08-03 18/week @ 2022-08-10 12/week @ 2022-08-17 21/week @ 2022-08-24 25/week @ 2022-08-31 65/week @ 2022-09-07 22/week @ 2022-09-14 36/week @ 2022-09-21

148 downloads per month
Used in 7 crates (via audio-processor-standalone)

MIT license

295KB
1.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:

  • [basedrop::Collector] You'll need to set-up basedrop or audio-garbage-collector
  • [host::MidiHost] should be created
    • This will connect to inputs & push messages to a queue
  • [audio_thread::MidiAudioThreadHandler] On your audio thread you should pop from the queue
    • This is enough to add MIDI to a standalone [audio_processor_traits::MidiEventHandler]
  • [vst::MidiVSTConverter] If you're implementing a host, you'll have to convert messages onto the VST API
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

~6–40MB
~699K SLoC