Cross-platform realtime MIDI processing in Rust.

Overview

midir crates.io Build Status

Cross-platform, realtime MIDI processing in Rust.

Features

midir is inspired by RtMidi and supports the same features*, including virtual ports (except on Windows) and full SysEx support – but with a rust-y API!

* With the exception of message queues, but these can be implemented on top of callbacks using e.g. Rust's channels.

midir currently supports the following platforms/backends:

  • ALSA (Linux)
  • WinMM (Windows)
  • CoreMIDI (macOS, iOS (untested))
  • WinRT (Windows 8+), enable the winrt feature
  • Jack (Linux, macOS), enable the jack feature
  • Web MIDI (Chrome, Opera, perhaps others browsers)

A higher-level API for parsing and assembling MIDI messages might be added in the future.

Documentation & Example

API docs can be found at docs.rs. You can find some examples in the examples directory. Or simply run cargo run --example test_play after cloning this repository.

Comments
  • Web MIDI support.

    Web MIDI support.

    Concerns

    Web MIDI is all kinds of asyncronous. Devices won't be available if you try to block request them immediately. It's possible that sending a MIDI device input immediately after opening it, if that input device requires system exclusive access, may not work. It'd perhaps be possible to buffer data until the promise for opening the device resolves...

    Testing

    C:\local\midir\examples\browser> wasm-pack build --target=no-modules --dev
    ...
    C:\local\midir\examples\browser> "%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files --auto-open-devtools-for-tabs file:///C:\local\midir\examples\browser\index.html
    

    Note that existing chrome instances may interfere with using command line flags - you may prefer to use the msjsdiag.debugger-for-chrome extension instead. Snippets from my local .vscode config:

    .vscode\tasks.json

    { "label": "examples/browser: wasm-pack build --target=no-modules --dev",       "options": { "cwd": "${workspaceFolder}/examples/browser" }, "windows": { "command": "wasm-pack build --target=no-modules --dev",       }, },
    { "label": "examples/browser: wasm-pack build --target=no-modules --profiling", "options": { "cwd": "${workspaceFolder}/examples/browser" }, "windows": { "command": "wasm-pack build --target=no-modules --profiling", }, },
    { "label": "examples/browser: wasm-pack build --target=no-modules --release",   "options": { "cwd": "${workspaceFolder}/examples/browser" }, "windows": { "command": "wasm-pack build --target=no-modules --release",   }, },
    

    .vscode\launch.json

            // msjsdiag.debugger-for-chrome
            // "runtimeArgs": See https://peter.sh/experiments/chromium-command-line-switches/
            {
                "name": "Chrome (--dev)", "type": "chrome", "request": "launch",
                "preLaunchTask": "examples/browser: wasm-pack build --target=no-modules --dev",
                "url": "${workspaceFolder}/examples/browser/index.html",
                "runtimeArgs": ["--allow-file-access-from-files", "--auto-open-devtools-for-tabs"],
                "internalConsoleOptions": "openOnSessionStart",
            },
            {
                "name": "Chrome (--profiling)", "type": "chrome", "request": "launch",
                "preLaunchTask": "examples/browser: wasm-pack build --target=no-modules --profiling",
                "url": "${workspaceFolder}/examples/browser/index.html",
                "runtimeArgs": ["--allow-file-access-from-files", "--auto-open-devtools-for-tabs"],
                "internalConsoleOptions": "openOnSessionStart",
            },
            {
                "name": "Chrome (--release)", "type": "chrome", "request": "launch",
                "preLaunchTask": "examples/browser: wasm-pack build --target=no-modules --release",
                "url": "${workspaceFolder}/examples/browser/index.html",
                "runtimeArgs": ["--allow-file-access-from-files", "--auto-open-devtools-for-tabs"],
                "internalConsoleOptions": "openOnSessionStart",
            },
    

    Screenshots

    image

    image

    opened by MaulingMonkey 16
  • Use unique port identifiers

    Use unique port identifiers

    Ports should not be identified by index (for calls to port_name and connect), but rather by some backend-specific identifier that does not change when the order of devices changes.

    These identifiers do not need to be persistent across sessions (e.g. to write them to a configuration file), because that would be quite a bit harder and can be added later.

    The following identifiers could work for each platform:

    | Backend | Port Identifier | | ------------- | ------------- | | ALSA | snd_seq_addr_t (numeric client + port IDs) | | CoreMIDI | MIDIEndpointRef, or maybe MIDIObjectFindByUniqueID | | JACK | Port name (string) | | WinMM | Maybe a combination of port name (as returned by GetDevCaps) and interface name (another string, see https://docs.microsoft.com/de-de/windows-hardware/drivers/audio/obtaining-a-device-interface-name). This requires a linear search when calling connect(), but I think that's not a problem. | | WinRT | DeviceInformation::Id (string) |

    The platform-specific representation could be wrapped in a PortId struct.

    enhancement 
    opened by Boddlnagg 12
  • Update windows crate to 0.37, remove memalloc dependency

    Update windows crate to 0.37, remove memalloc dependency

    PSTR has moved to core, that's it.

    Yes, the windows crate is churning a bit much, work is underway to make that better but for now, it's a quickly moving upgrade train...

    opened by hrydgard 9
  • How to create a virtual midi port on linux?

    How to create a virtual midi port on linux?

    How to create a virtual midi port on linux?

    Btw, what's the purpose of the strings passed to MidiOutput::new(), MidiInput::new(), and both's connect() methods. It works on windows if I pass "" for all, will it also work on linux and mac?

    opened by Boscop 9
  • Missing timestamp breaks IAC driver send into Ableton Live

    Missing timestamp breaks IAC driver send into Ableton Live

    Hey 👋 , this is somewhat related to #45.

    I've found that when sending MIDI into Ableton Live through IAC Driver on macOS, messages were not picked-up, despite the MIDI indicator flashing.

    After investigation, I could fix the issue by changing the timestamp of the PacketBuffer instances.

    // midir/src/backend/coremidi/mod.rs
    // MidiOutputConnection::send
    
    let packets = PacketBuffer::new(0, message); // <- this apparently confuses Live
    
    // and then:
    let host_time = unsafe { external::AudioGetCurrentHostTime() };
    let packets = PacketBuffer::new(host_time, message); // <- this works
    

    I've found posts on the internet of others hitting this issue. For example: https://forum.ableton.com/viewtopic.php?t=181224

    I understand this is a problem specific to Ableton, but it'd be nice to be able to send MIDI onto it using midir.

    The patch above seems to fix the issue, though there might be a requirement to further offset the 'current host time' by a certain arbitrary small delta. Using mach2::mach_time::mach_absolute_time() instead of AudioGetCurrentHostTime() also fixes the issue.

    All the best; I can raise a PR if you think this change makes sense.

    opened by yamadapc 6
  • Please, help with borow checker

    Please, help with borow checker

    I'm sorry for bothering here: I'm newbie in rust, but for the day I cannot figure out how to handle midi_input in my code:

    I try to use it inside iced application cycle, but, any attempts to get data from an opened connection ends with no possibility to get reference to the data, or pass self to the callback (which I can understand).

    extern crate iced;
    extern crate midir;
    
    use std::error::Error;
    use std::io::{stdin, stdout, Write};
    use std::option::Option::Some;
    
    use iced::{executor, Application, Column, Command, Element, Settings, Text};
    use midir::{Ignore, MidiInput, MidiInputPort};
    
    pub fn main() -> iced::Result {
        BigNote::run(Settings::default())
    }
    
    struct BigNote {
        note: i32,
        notes: Vec<u8>,
        need_init: bool,
    
        midi_input: MidiInput,
    }
    
    #[derive(Debug, Clone, Copy)]
    pub enum Message {
        MidiFired,
        Init,
    }
    
    impl Application for BigNote {
        type Executor = executor::Default;
        type Message = Message;
        type Flags = ();
    
        fn new(_flags: ()) -> (BigNote, Command<self::Message>) {
            (
                BigNote {
                    note: -1,
                    need_init: true,
                    notes: Vec::new(),
                    midi_input: MidiInput::new(&String::from("my_big_note")).unwrap(),
                },
                Command::none(),
            )
        }
    
        fn title(&self) -> String {
            String::from("MyBigNote by Levitanus")
        }
    
        fn view(&mut self) -> Element<Message> {
            Column::new()
                .push(Text::new(self.note.to_string()).size(50))
                .into()
        }
    
        fn update(&mut self, message: Message) -> Command<Message> {
            if self.need_init == true {
                self.need_init = false;
                let port = &self.midi_input.ports()[1];
                let conn_in = self
                    .midi_input
                    .connect(
                        port,
                        "midir-test",
                        |stamp, message, log: &mut Vec<i32>| {
                            // The last of the three callback parameters is the object that we pass in as last parameter of `connect`.
                            println!(
                                "{}: {:?} (len = {}) Data len = {}",
                                stamp,
                                message,
                                message.len(),
                                log.len()
                            );
                        },
                        Vec::new(),
                    )
                    .unwrap();
            }
            Command::none()
        }
    }
    
    
      --> src/main.rs:75:27
       |
    75 |               let conn_in = self
       |  ___________________________^
    76 | |                 .midi_input
       | |___________________________^ move occurs because `self.midi_input` has type `MidiInput`, which does not implement the `Copy` trait```
    opened by Levitanus 6
  • Move `os::unix` module to `virtual`

    Move `os::unix` module to `virtual`

    Currently the platforms that support virtual ports are exactly the Unix platforms, but there is no reason that this must be the case, so the module name should reflect that.

    opened by Boddlnagg 6
  • Several fixes / improvements

    Several fixes / improvements

    Several fixes / improvements:

    • On windows, midiInOpen() only succeeds when callback is extern "system", so I changed it to that. Now it works.
    • Improved error handling when getting port name: Under some conditions (such as when MidiHub is installed but not running), getting the portname can fail, but only for the MidiHub port! Before it crashed because of assert, now it handles this case gracefully. The reason this is necessary is because often you want to list all input or output port names, allowing the user to choose one. This should just ignore the port names that cannot be queried, for example:
    		let midi_out = try!(MidiOutput::new(""));
    		let port_out = try!((|| {
    			for i in 0..midi_out.port_count() {
    				if let Ok(name) = midi_out.port_name(i) {
    					if name == cfg.port_name {
    						return Some(i);
    					}
    				}
    			}
    			return None;
    		})().ok_or(r#"midi output port not found"#));
    
    • A similar issue is: port names should be reported as they appear in the OS, without any added number! The UI can add a number if that's wanted. The reason, seen above, is that the port can be given by name in a config file that the app stores. Port numbers change when devices get added or removed, port names stay the same. So this method of finding the port name by name rather than number between app sessions is more robust, but it requires that the api returns the name without added number!
    • Some small things like sleep(Duration::from_millis(1)); instead of sleep_ms and extend_from_slice() instead of push_all().
    • Most of the time when you want to send midi messages, they are MIDI Short Messages, being expressible in 3 bytes. It is wasteful to allocate a vector and iterate through it and deallocate it for each short message, especially when you are sending a lot of MIDI messages, so I added a function to send MIDI Short Messages expressed in a u32, that can send them directly without any conversion necessary.
    pub fn send_short_message(&mut self, message: u32) -> Result<(), SendError>
    

    Most likely in your app you will have a type for MIDI Short Messages anyway. It will have a method to_u32():

    #[derive(Clone, Copy, Debug, PartialEq)]
    pub struct ShortMsg {
    	pub data0: u8,
    	pub data1: u8,
    	pub data2: u8,
    }
    impl ShortMsg {
    	pub fn status(&self) -> Status { // Status and masks are from the rimd crate
    		Status::from_u8(self.data0 & STATUS_MASK).unwrap()
    	}
    	pub fn channel(&self) -> u8 {
    		self.data0 & CHANNEL_MASK
    	}
    	pub fn to_u32(&self) -> u32 {
    		((((self.data2 as u32) << 16) & 0xFF0000) |
    		  (((self.data1 as u32) << 8) & 0xFF00) |
    		  ((self.data0 as u32) & 0xFF)) as u32
    	}
    	...
    	// different constructors here for the different types of short msgs
    }
    

    Since I'm on windows, I only implemented this for windows, but I'm sure it's easy to add it for other OSes, too.

    Btw, this crate goes well together with the rimd crate :) Maybe it would make sense to unify them into one project in the future..

    opened by Boscop 6
  • bug: future-incompatibility warning with midir 0.7.0

    bug: future-incompatibility warning with midir 0.7.0

    With the latest cargo nightly as of today (2022/04/24), compiling any app that depends on midir displays a rather serious warning message.

    the output is too large so i'm putting a link to it instead: https://termbin.com/2jvy

    The same report can be generated by using the command below:
    cargo report future-incompatibilities --id 7 --package midir:0.7.0

    I'm putting the first warning message here for convenience (there are more).

       [...]     
    The package `midir v0.7.0` currently triggers the following future incompatibility lints:
    > warning: reference to packed field is unaligned
    >    --> D:\home\.cargo\registry\src\github.com-1ecc6299db9ec823\midir-0.7.0\src\backend\winmm\mod.rs:100:38
    >     |
    > 100 |         let pname: &[u16] = unsafe { &device_caps.szPname }; // requires unsafe because of packed alignment ...
    >     |                                      ^^^^^^^^^^^^^^^^^^^^
    >     |
    >     = note: `#[allow(unaligned_references)]` on by default
    >     = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    >     = note: for more information, see issue #82523 <https://github.com/rust-lang/rust/issues/82523>
    >     = note: fields of packed structs are not properly aligned, and creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
    >     = help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
    [...]
    
    opened by insomnimus 5
  • How to connect to *all* ports?

    How to connect to *all* ports?

    I know there is an issue asking for multi-port support which won't be implement, so I'll need to create a new MidiInput client for each port. But on the other hand, I already need a MidiInput client to be able to enumerate all available ports. So, what's the ~~least ugly~~ best way to listen to all ports at once?

    opened by piegamesde 5
  • Report error codes

    Report error codes

    When connecting, if an error occurs a ConnectError::Other(s) is generated:

    https://github.com/Boddlnagg/midir/blob/253ae732e94b40f0d4c30be0819f93a5e4107638/src/backend/winmm/mod.rs#L205-L207

    This swallows the Windows error code, which could be any of the following:

    Value | Description -- | -- MMSYSERR_ALLOCATED | The specified resource is already allocated. MMSYSERR_BADDEVICEID | The specified device identifier is out of range. MMSYSERR_INVALFLAG | The flags specified by dwFlags are invalid. MMSYSERR_INVALPARAM | The specified pointer or structure is invalid. MMSYSERR_NOMEM | The system is unable to allocate or lock memory.

    It would be nice if the reason for the failure were reported, rather than "could not create Windows MM MIDI input port"

    opened by xobs 5
  • drop() no longer calls midiOutReset

    drop() no longer calls midiOutReset

    Fixes #122

    On Windows using the winmm backend, midiOutReset was being called before midiOutClose, presumably to cancel any pending output. This caused extra MIDI commands to be sent. Instead, we now call midiOutClose repeatedly until it's no longer blocked by pending output.

    Tested it in a very simple application, monitoring MIDI activity using "BC Manager", which is also how I verified the original problem. No extraneous MIDI is being sent now.

    opened by dwmuller 2
  • MidiOutputConnection.drop() produces unwanted output on Windows?

    MidiOutputConnection.drop() produces unwanted output on Windows?

    When a MIDI output connection is closed on Windows, this results in a call to midiOutReset. This sends "all notes off" and "reset all controllers" MIDI commands to every MIDI channel, 32 commands in all.

    For the application I'm working on, this is at least unnecessary, and possibly harmful, depending on my hardware setup. The app targets a controller, only, and this activity could have undesirable side-effects on other devices. It also seems somewhat out of keeping with midir's low-level target functionality.

    However, the documentation for midiOutReset also specifies that it stops processing of pending output buffers. midiOutClose will return an error if output is still pending (MIDIERR_STILLPLAYING). I wonder if that's why the call to midiOutReset exists. It might be better to retry midiOutClose until that error no longer occurs (similar to what happens on a call to midiOutUnprepareHeader on a send).

    If there's interest in such a change I could try it out, and produce a pull request.

    https://learn.microsoft.com/en-us/windows/win32/api/mmeapi/nf-mmeapi-midioutreset

    opened by dwmuller 1
  • Why closures, why threads?

    Why closures, why threads?

    I've used midir to my satisfaction for a while now. However, in a synth or sequencer application you'd need to do intricate timing related code. A closure to handle the incoming MIDI messages just won't cut it. Sure you can use a ring buffer and send the data to your main processing thread, but the problem arises when you reconnect. Is there any reason you chose to use a closure for this purpose? I'd much rather have asynchronous way of working where I just poll for MIDI data every iteration. This is much easier to integrate in a real-time application, which is largely based on state machines. Alsa-rs gets these things right. Cpal and Midir seem to favor closures and even delegating streaming to threads, which just complicates things. It may look cleaner and safer, but it doesn't scale up to professional usage. Unless I'm missing something.

    opened by pietervandermeer 2
  • [alsa] Fix port listing

    [alsa] Fix port listing

    Searching for either WRITE or SUB_WRITE capabilities causes filling of the ports list with a bunch of ports that are impossible to connect to and always return an error if you try to. I believe that the correct way is to search for both.

    Alsa utils do the same: Search for readable: https://github.com/alsa-project/alsa-utils/blob/a566f8a0ed6d7ca5fd6ae2d224f3f28654a2f3be/seq/aplaymidi/arecordmidi.c#L685-L689 Search for writable: https://github.com/alsa-project/alsa-utils/blob/a566f8a0ed6d7ca5fd6ae2d224f3f28654a2f3be/seq/aplaymidi/aplaymidi.c#L832-L836

    opened by PolyMeilex 0
  • Long port names on Linux

    Long port names on Linux

    When using Linux, the returned port name is much longer than with Windows/macOS. These long names force me to reserve more space in the GUI than needed just because of Linux users, which are a minority.

    Example:

    • Windows/macOS: Arturia KeyStep 32
    • Linux: Arturia KeyStep 32:Arturia KeyStep 32 MIDI 1 20:0

    It is obvious that the second part "Arturia KeyStep 32 MIDI 1" including the port info needs to be returned, but the first part seems to be redundant. When using amidi --list-devices the result is also "Arturia KeyStep 32 MIDI 1", the so called "client name" is omitted there.

    I already thought about manually splitting the parts using the colon as delimiter, but I think the colon is a valid character inside the port name, so this method is not 100% safe, even if I'm not aware of any device that's named in such manner.

    opened by sourcebox 2
Owner
Patrick Reisert
Patrick Reisert
Small music theory library with MIDI capabilities written in Rust

mumuse Small music theory library with MIDI capabilities written in Rust (wip). Examples Creating notes and transpositions // Declare Note from &str l

Alexis LOUIS 4 Jul 27, 2022
Midnote is a terminal application that reads a MIDI file and displays you its notes bar-by-bar, while playing it.

MIDNOTE Midnote is a terminal application that reads a MIDI file and displays you its notes bar-by-bar, while playing it. Goals As a blind musician my

null 4 Oct 30, 2022
MIDI-controlled stereo-preserving granular-synthesizer LV2 plugin

Stereog "Stereog" rhymes with "hairy dog." Stereog is a MIDI-controlled stereo-preserving granular synthesizer LV2 plugin. It is experimental software

Ed Cashin 6 Jun 3, 2022
Tools for working with MIDI files - written by All the Music LLC for musicians.

Tools for Generating and Working with MIDI Files NOTICE: This repo is under construction and is not ready for use. Once it is ready, and the other rep

null 9 Nov 17, 2022
Map the Teenage Engineering OP-1 MIDI output to keyboard commands

OP1NPUT Maps the Teenage Engineering OP-1's MIDI output to keyboard keypresses so it may be used as a game controller. This exists because many of the

Glen Murphy 4 Nov 7, 2022
Encrypt and decrypt files by playing melodies on your MIDI keyboard.

midicrypt Encrypt and decrypt files by playing melodies on your MIDI keyboard. Written in Rust. ❯ ./midicrypt -h midicrypt 0.1.0 NINNiT Encrypts and D

null 2 Jun 24, 2022
🎹 Simple MIDI note repeater plugin (VST3/CLAP).

⏱️ Clockwork A simple MIDI note repeater plugin, written in Rust. ?? Showcase: (turn on audio) clockwork-showcase.mp4 ?? Manual: The user manual can b

Alexander Weichart 13 Nov 30, 2022
Drumsthesia is a simple software that helps you to learn how to play the drums (or midi controllers).

Drumsthesia A shameless copy of Neothesia adapted for e-Drums. Youtube Video Binaries for MacOS, Linux (untested) and Windows (untested). Download Scr

Rodrigo Watanabe 4 Mar 20, 2023
A small program that converts midi files to RPE (Re: Phigros Edit) Charts

Midi to RPE(Re: Phigros Edit) chart converter $ mid2json --help Usage: mid2json [OPTIONS] <MIDI_PATH> Arguments: <MIDI_PATH> Name of the input fil

液氦 4 Jun 8, 2023
Polyrhythmically-inclinded Midi Drum generator

Polyrhythmix Polyrhythmix (Poly) is a command-line assistant designed to generate MIDI files from the description of drum parts. It provides a conveni

Denis Redozubov 234 Jul 3, 2023
Build custom songs for Pokémon Mystery Dungeon: Explorers of Sky from Soundfonts and MIDI files

skysongbuilder A tool to build custom songs for Pokémon Mystery Dungeon: Explorers of Sky from Soundfonts and MIDI files Features: Optimizations down

Adakite 3 Sep 23, 2023
Minimalist multi-track audio recorder which may be controlled via OSC or MIDI.

smrec Minimalist multi-track audio recorder which may be controlled via OSC or MIDI. I did this because I needed a simple multi-track audio recorder w

Ali Somay 18 Oct 22, 2023
Cross-platform audio I/O library in pure Rust

CPAL - Cross-Platform Audio Library Low-level library for audio input and output in pure Rust. This library currently supports the following: Enumerat

null 1.8k Jan 8, 2023
Auritia is a DAW coded in Rust and Vue in hopes of having cross platform compatability, while also providing enough features for anyone to use professionally

Steps Install WebView if you're not on Windows 11 Install Node deps npm i To run the dev server do npm run tauri dev Compiling Linux You will need to

Auritia 20 Aug 27, 2022
Cross-platform audio for Rust

quad-snd High-level, light-weight, and opinionated audio library. Web: WebAudio Android: OpenSLES Linux: Alsa macOS: CoreAudio Windows: Wasapi iOS: Co

Fedor Logachev 86 Nov 7, 2022
TinyAudio is a cross-platform audio output library

TinyAudio TinyAudio is a cross-platform audio output library. Its main goal to provide unified access to a default sound output device of your operati

Dmitry Stepanov 39 Apr 4, 2023
A collection of filters for real-time audio processing

Audio Filters A collection of filters for real-time audio processing Feature Progress #![no_std] (via libm) f32 & f64 capable (via num-traits) SIMD Do

null 42 Nov 5, 2022
Psst - Fast and multi-platform Spotify client with native GUI

Psst Fast Spotify client with native GUI, without Electron, built in Rust. Very early in development, lacking in features, stability, and general user

Jan Pochyla 7.2k Jan 2, 2023
Spotify for the terminal written in Rust 🚀

Spotify TUI A Spotify client for the terminal written in Rust. The terminal in the demo above is using the Rigel theme. Spotify TUI Installation Homeb

Alexander Keliris 14.1k Jan 1, 2023