├── src ├── backend │ ├── dummy │ │ └── mod.rs │ ├── mod.rs │ ├── winmm │ │ ├── handler.rs │ │ └── mod.rs │ ├── jack │ │ ├── wrappers.rs │ │ └── mod.rs │ ├── webmidi │ │ └── mod.rs │ ├── winrt │ │ └── mod.rs │ ├── coremidi │ │ └── mod.rs │ ├── android │ │ └── mod.rs │ └── alsa │ │ └── mod.rs ├── os │ ├── mod.rs │ └── unix.rs ├── lib.rs ├── errors.rs └── common.rs ├── .gitignore ├── examples ├── browser │ ├── Cargo.toml │ ├── index.html │ └── src │ │ └── lib.rs ├── test_list_ports.rs ├── test_read_input.rs ├── test_forward.rs ├── test_play.rs ├── test_reuse.rs └── test_sysex.rs ├── LICENSE ├── README.md ├── Cargo.toml ├── CHANGELOG.md ├── tests └── virtual.rs └── .github └── workflows └── ci.yml /src/backend/dummy/mod.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/os/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | pub mod unix; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .vscode 4 | .cargo 5 | *.iml 6 | .idea -------------------------------------------------------------------------------- /examples/browser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "midir_browser_example" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | midir = { "path" = "../.." } 12 | 13 | console_error_panic_hook = "0.1.6" 14 | web-sys = { version = "0.3", features = ["console", "Window"] } 15 | js-sys = "0.3" 16 | wasm-bindgen = "0.2" 17 | -------------------------------------------------------------------------------- /examples/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | midir – examples/browser 6 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Patrick Reisert and the RtMidi contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/os/unix.rs: -------------------------------------------------------------------------------- 1 | use crate::{ConnectError, MidiInputConnection, MidiOutputConnection}; 2 | 3 | // TODO: maybe move to module `virtual` instead of `os::unix`? 4 | 5 | /// Trait that is implemented by `MidiInput` on platforms that 6 | /// support virtual ports (currently every platform but Windows). 7 | pub trait VirtualInput 8 | where 9 | Self: Sized, 10 | { 11 | /// Creates a virtual input port. Once it has been created, 12 | /// other applications can connect to this port and send MIDI 13 | /// messages which will be received by this port. 14 | fn create_virtual( 15 | self, 16 | port_name: &str, 17 | callback: F, 18 | data: T, 19 | ) -> Result, ConnectError> 20 | where 21 | F: FnMut(u64, &[u8], &mut T) + Send + 'static; 22 | } 23 | 24 | /// Trait that is implemented by `MidiOutput` on platforms that 25 | /// support virtual ports (currently every platform but Windows). 26 | pub trait VirtualOutput 27 | where 28 | Self: Sized, 29 | { 30 | /// Creates a virtual output port. Once it has been created, 31 | /// other applications can connect to this port and will 32 | /// receive MIDI messages that are sent to this port. 33 | fn create_virtual(self, port_name: &str) -> Result>; 34 | } 35 | -------------------------------------------------------------------------------- /examples/test_list_ports.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io::{stdin, stdout, Write}; 3 | 4 | use midir::{Ignore, MidiInput, MidiOutput}; 5 | 6 | fn main() { 7 | env_logger::init(); 8 | match run() { 9 | Ok(_) => (), 10 | Err(err) => println!("Error: {}", err), 11 | } 12 | } 13 | 14 | fn run() -> Result<(), Box> { 15 | let mut midi_in = MidiInput::new("midir test input")?; 16 | midi_in.ignore(Ignore::None); 17 | let midi_out = MidiOutput::new("midir test output")?; 18 | 19 | let mut input = String::new(); 20 | 21 | loop { 22 | println!("Available input ports:"); 23 | for (i, p) in midi_in.ports().iter().enumerate() { 24 | println!("{}: {} (ID: \"{}\")", i, midi_in.port_name(p)?, p.id()); 25 | } 26 | 27 | println!("\nAvailable output ports:"); 28 | for (i, p) in midi_out.ports().iter().enumerate() { 29 | println!("{}: {} (ID: \"{}\")", i, midi_out.port_name(p)?, p.id()); 30 | } 31 | 32 | // run in endless loop if "--loop" parameter is specified 33 | match ::std::env::args().nth(1) { 34 | Some(ref arg) if arg == "--loop" => {} 35 | _ => break, 36 | } 37 | print!("\nPress to retry ..."); 38 | stdout().flush()?; 39 | input.clear(); 40 | stdin().read_line(&mut input)?; 41 | println!("\n"); 42 | } 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | // This module is not public 2 | 3 | // TODO: improve feature selection (make sure that there is always exactly one implementation, or enable dynamic backend selection) 4 | // TODO: allow to disable build dependency on ALSA 5 | 6 | #[cfg(all(target_os = "windows", not(feature = "winrt")))] 7 | mod winmm; 8 | #[cfg(all(target_os = "windows", not(feature = "winrt")))] 9 | pub use self::winmm::*; 10 | 11 | #[cfg(all(target_os = "windows", feature = "winrt"))] 12 | mod winrt; 13 | #[cfg(all(target_os = "windows", feature = "winrt"))] 14 | pub use self::winrt::*; 15 | 16 | #[cfg(all(target_os = "macos", not(feature = "jack")))] 17 | mod coremidi; 18 | #[cfg(all(target_os = "macos", not(feature = "jack")))] 19 | pub use self::coremidi::*; 20 | 21 | #[cfg(all(target_os = "ios", not(feature = "jack")))] 22 | mod coremidi; 23 | #[cfg(all(target_os = "ios", not(feature = "jack")))] 24 | pub use self::coremidi::*; 25 | 26 | #[cfg(all(target_os = "linux", not(feature = "jack")))] 27 | mod alsa; 28 | #[cfg(all(target_os = "linux", not(feature = "jack")))] 29 | pub use self::alsa::*; 30 | 31 | #[cfg(all(feature = "jack", not(target_os = "windows")))] 32 | mod jack; 33 | #[cfg(all(feature = "jack", not(target_os = "windows")))] 34 | pub use self::jack::*; 35 | 36 | #[cfg(target_arch = "wasm32")] 37 | mod webmidi; 38 | #[cfg(target_arch = "wasm32")] 39 | pub use self::webmidi::*; 40 | 41 | #[cfg(target_os = "android")] 42 | mod android; 43 | #[cfg(target_os = "android")] 44 | pub use self::android::*; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # midir [![crates.io](https://img.shields.io/crates/v/midir.svg)](https://crates.io/crates/midir) 2 | 3 | Cross-platform, realtime MIDI processing in Rust. 4 | 5 | ## Features 6 | **midir** is inspired by [RtMidi](https://github.com/thestk/rtmidi) and supports the same features*, including virtual ports (except on Windows) and full SysEx support – but with a rust-y API! 7 | 8 | * With the exception of message queues, but these can be implemented on top of callbacks using e.g. Rust's channels. 9 | 10 | **midir** currently supports the following platforms/backends: 11 | - [x] ALSA (Linux) 12 | - [x] WinMM (Windows) 13 | - [x] CoreMIDI (macOS, iOS) 14 | - [x] WinRT (Windows 8+), enable the `winrt` feature 15 | - [x] Jack (Linux, macOS), enable the `jack` feature 16 | - [x] Web MIDI (Chrome, Opera, perhaps others browsers) 17 | - [x] Android (API 29+, NDK AMidi + JNI) 18 | 19 | A higher-level API for parsing and assembling MIDI messages might be added in the future. 20 | 21 | ## Documentation & Example 22 | API docs can be found at [docs.rs](https://docs.rs/crate/midir/). You can find some examples in the [`examples`](examples/) directory. Or simply run `cargo run --example test_play` after cloning this repository. 23 | 24 | ### Android 25 | - Requires Android API 29+ and the Android NDK (r20b+). 26 | - Build (example, to remove before merging): 27 | - Install: `cargo install cargo-ndk` 28 | - Targets: `rustup target add aarch64-linux-android` 29 | - Build: `cargo ndk -t arm64-v8a -o ./app/src/main/jniLibs build --release` 30 | - Permissions/features: 31 | - Manifest should declare `` (not needed for USB/BLE MIDI). 32 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(future_incompatible)] 2 | #![warn(rust_2018_idioms)] 3 | #![warn(rust_2021_compatibility)] 4 | 5 | #[cfg(feature = "jack")] 6 | #[macro_use] 7 | extern crate bitflags; 8 | 9 | #[repr(u8)] 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 11 | /// An enum that is used to specify what kind of MIDI messages should 12 | /// be ignored when receiving messages. 13 | pub enum Ignore { 14 | None = 0x00, 15 | Sysex = 0x01, 16 | Time = 0x02, 17 | SysexAndTime = 0x03, 18 | ActiveSense = 0x04, 19 | SysexAndActiveSense = 0x05, 20 | TimeAndActiveSense = 0x06, 21 | All = 0x07, 22 | } 23 | 24 | impl std::ops::BitOr for Ignore { 25 | type Output = Ignore; 26 | #[inline(always)] 27 | fn bitor(self, rhs: Self) -> Self::Output { 28 | // this is safe because all combinations also exist as variants 29 | unsafe { std::mem::transmute(self as u8 | rhs as u8) } 30 | } 31 | } 32 | 33 | impl Ignore { 34 | #[inline(always)] 35 | pub fn contains(self, other: Ignore) -> bool { 36 | self as u8 & other as u8 != 0 37 | } 38 | } 39 | 40 | /// A MIDI structure used internally by some backends to store incoming 41 | /// messages. Each message represents one and only one MIDI message. 42 | /// The timestamp is represented as the elapsed microseconds since 43 | /// a point in time that is arbitrary, but does not change for the 44 | /// lifetime of a given MidiInputConnection. 45 | #[derive(Debug, Clone)] 46 | struct MidiMessage { 47 | bytes: Vec, 48 | timestamp: u64, 49 | } 50 | 51 | impl MidiMessage { 52 | fn new() -> MidiMessage { 53 | MidiMessage { 54 | bytes: vec![], 55 | timestamp: 0, 56 | } 57 | } 58 | } 59 | 60 | pub mod os; // include platform-specific behaviour 61 | 62 | mod errors; 63 | pub use errors::*; 64 | 65 | mod common; 66 | pub use common::*; 67 | 68 | mod backend; 69 | -------------------------------------------------------------------------------- /examples/test_read_input.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io::{stdin, stdout, Write}; 3 | 4 | use midir::{Ignore, MidiInput}; 5 | 6 | fn main() { 7 | env_logger::init(); 8 | match run() { 9 | Ok(_) => (), 10 | Err(err) => println!("Error: {}", err), 11 | } 12 | } 13 | 14 | fn run() -> Result<(), Box> { 15 | let mut input = String::new(); 16 | 17 | let mut midi_in = MidiInput::new("midir reading input")?; 18 | midi_in.ignore(Ignore::None); 19 | 20 | // Get an input port (read from console if multiple are available) 21 | let in_ports = midi_in.ports(); 22 | let in_port = match in_ports.len() { 23 | 0 => return Err("no input port found".into()), 24 | 1 => { 25 | println!( 26 | "Choosing the only available input port: {}", 27 | midi_in.port_name(&in_ports[0]).unwrap() 28 | ); 29 | &in_ports[0] 30 | } 31 | _ => { 32 | println!("\nAvailable input ports:"); 33 | for (i, p) in in_ports.iter().enumerate() { 34 | println!("{}: {}", i, midi_in.port_name(p).unwrap()); 35 | } 36 | print!("Please select input port: "); 37 | stdout().flush()?; 38 | let mut input = String::new(); 39 | stdin().read_line(&mut input)?; 40 | in_ports 41 | .get(input.trim().parse::()?) 42 | .ok_or("invalid input port selected")? 43 | } 44 | }; 45 | 46 | println!("\nOpening connection"); 47 | let in_port_name = midi_in.port_name(in_port)?; 48 | 49 | // _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope 50 | let _conn_in = midi_in.connect( 51 | in_port, 52 | "midir-read-input", 53 | move |stamp, message, _| { 54 | println!("{}: {:?} (len = {})", stamp, message, message.len()); 55 | }, 56 | (), 57 | )?; 58 | 59 | println!( 60 | "Connection open, reading input from '{}' (press enter to exit) ...", 61 | in_port_name 62 | ); 63 | 64 | input.clear(); 65 | stdin().read_line(&mut input)?; // wait for next enter key press 66 | 67 | println!("Closing connection"); 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [".", "examples/browser"] 3 | 4 | [package] 5 | name = "midir" 6 | version = "0.11.0-pre" 7 | authors = ["Patrick Reisert"] 8 | description = "A cross-platform, realtime MIDI processing library, inspired by RtMidi." 9 | repository = "https://github.com/Boddlnagg/midir" 10 | readme = "README.md" 11 | keywords = ["midi", "audio", "music", "sound"] 12 | categories = ["multimedia::audio", "api-bindings"] 13 | license = "MIT" 14 | edition = "2021" 15 | 16 | [features] 17 | default = [] 18 | avoid_timestamping = [] 19 | coremidi_send_timestamped = [] 20 | jack = ["jack-sys", "libc", "bitflags"] 21 | winrt = [ 22 | "windows/Foundation", 23 | "windows/Foundation_Collections", 24 | "windows/Devices_Midi", 25 | "windows/Devices_Enumeration", 26 | "windows/Storage_Streams", 27 | "windows/Win32_System_WinRT", 28 | ] 29 | 30 | [dependencies] 31 | log = "0.4" 32 | bitflags = { version = "2", optional = true } 33 | jack-sys = { version = "0.5", optional = true } 34 | libc = { version = "0.2.21", optional = true } 35 | 36 | [target.'cfg(target_os = "linux")'.dependencies] 37 | alsa = ">=0.9.0, <0.11" # the changes between 0.9 and 0.10 do not bother us 38 | libc = "0.2.21" 39 | 40 | [target.'cfg(target_os = "ios")'.dependencies] 41 | coremidi = "0.8.0" 42 | 43 | [target.'cfg(target_os = "macos")'.dependencies] 44 | coremidi = "0.8.0" 45 | 46 | [target.'cfg(target_os = "windows")'.dependencies] 47 | windows = { version = ">=0.59.0,<=0.62", features = [ 48 | "Win32_Foundation", 49 | "Win32_Media", 50 | "Win32_Media_Multimedia", 51 | "Win32_Media_Audio", 52 | ] } 53 | parking_lot = { version = "0.12.1" } 54 | 55 | [target.'cfg(target_arch = "wasm32")'.dependencies] 56 | js-sys = "0.3" 57 | wasm-bindgen = "0.2" 58 | web-sys = { version = "0.3", features = [ 59 | "Event", 60 | "Navigator", 61 | "Window", 62 | "MidiAccess", 63 | "MidiInput", 64 | "MidiInputMap", 65 | "MidiMessageEvent", 66 | "MidiOptions", 67 | "MidiOutput", 68 | "MidiOutputMap", 69 | "MidiPort", 70 | "MidiPortType", 71 | ] } 72 | 73 | [dev-dependencies] 74 | env_logger = "0.11" # only for examples 75 | 76 | [target.'cfg(target_arch = "wasm32")'.dev-dependencies] 77 | wasm-bindgen-test = "0.2" 78 | 79 | # Android parts. 80 | [target.'cfg(target_os = "android")'.dependencies] 81 | jni = { version = "0.21", default-features = false } 82 | ndk-context = "0.1" 83 | jni-min-helper = "0.3" 84 | 85 | # is already provided by the NDK. (will show a link error below API 29) 86 | [target.'cfg(target_os = "android")'.build-dependencies] 87 | cc = "1.0" 88 | -------------------------------------------------------------------------------- /examples/test_forward.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io::{stdin, stdout, Write}; 3 | 4 | use midir::{Ignore, MidiIO, MidiInput, MidiOutput}; 5 | 6 | fn main() { 7 | env_logger::init(); 8 | match run() { 9 | Ok(_) => (), 10 | Err(err) => println!("Error: {}", err), 11 | } 12 | } 13 | 14 | #[cfg(not(target_arch = "wasm32"))] // conn_out is not `Send` in Web MIDI, which means it cannot be passed to connect 15 | fn run() -> Result<(), Box> { 16 | let mut midi_in = MidiInput::new("midir forwarding input")?; 17 | midi_in.ignore(Ignore::None); 18 | let midi_out = MidiOutput::new("midir forwarding output")?; 19 | 20 | let in_port = select_port(&midi_in, "input")?; 21 | println!(); 22 | let out_port = select_port(&midi_out, "output")?; 23 | 24 | println!("\nOpening connections"); 25 | let in_port_name = midi_in.port_name(&in_port)?; 26 | let out_port_name = midi_out.port_name(&out_port)?; 27 | 28 | let mut conn_out = midi_out.connect(&out_port, "midir-forward")?; 29 | 30 | // _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope 31 | let _conn_in = midi_in.connect( 32 | &in_port, 33 | "midir-forward", 34 | move |stamp, message, _| { 35 | conn_out 36 | .send(message) 37 | .unwrap_or_else(|_| println!("Error when forwarding message ...")); 38 | println!("{}: {:?} (len = {})", stamp, message, message.len()); 39 | }, 40 | (), 41 | )?; 42 | 43 | println!( 44 | "Connections open, forwarding from '{}' to '{}' (press enter to exit) ...", 45 | in_port_name, out_port_name 46 | ); 47 | 48 | let mut input = String::new(); 49 | stdin().read_line(&mut input)?; // wait for next enter key press 50 | 51 | println!("Closing connections"); 52 | Ok(()) 53 | } 54 | 55 | fn select_port(midi_io: &T, descr: &str) -> Result> { 56 | println!("Available {} ports:", descr); 57 | let midi_ports = midi_io.ports(); 58 | for (i, p) in midi_ports.iter().enumerate() { 59 | println!("{}: {}", i, midi_io.port_name(p)?); 60 | } 61 | print!("Please select {} port: ", descr); 62 | stdout().flush()?; 63 | let mut input = String::new(); 64 | stdin().read_line(&mut input)?; 65 | let port = midi_ports 66 | .get(input.trim().parse::()?) 67 | .ok_or("Invalid port number")?; 68 | Ok(port.clone()) 69 | } 70 | 71 | #[cfg(target_arch = "wasm32")] 72 | fn run() -> Result<(), Box> { 73 | println!("test_forward cannot run on Web MIDI"); 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All major changes to this project will be documented in this file. 4 | 5 | ## [0.10.3] 2025-10-27 6 | - Fix UB in WinMM backend ([#137](https://github.com/Boddlnagg/midir/issues/137) - thanks @barafael) 7 | - Fix file descriptor leak in ALSA backend ([#175](https://github.com/Boddlnagg/midir/pull/175) - thanks @abique) 8 | 9 | ## [0.10.2] 2025-08-07 10 | - Support iOS by disabling timestamping there ([#170](https://github.com/Boddlnagg/midir/pull/170) - thanks @joe-noel-dev) 11 | 12 | ## [0.10.1] 2024-11-20 13 | - Add `id()` and `find_port_by_id()` to `MidiInputPort` and `MidiOutputPort` ([#157](https://github.com/Boddlnagg/midir/pull/157) - thanks @oscartbeaumont) 14 | 15 | ## [0.10.0] 2024-04-21 16 | - Upgrade to 2021 edition 17 | - Upgrade `alsa`, `coremidi` and `windows` dependencies 18 | - [winmm] Fix hanging when closing MIDI input ([#151](https://github.com/Boddlnagg/midir/pull/151) - thanks, @j-n-f) 19 | 20 | ## [0.9.1] - 2023-01-27 21 | - Fix Jack build on ARM and bump jack-sys version ([#127](https://github.com/Boddlnagg/midir/pull/127)) 22 | 23 | ## [0.9.0] - 2023-01-08 24 | 25 | - Upgrade `alsa` and `windows` dependencies (the latter now requires `rustc 1.64`) 26 | - [winrt] Fix received data buffer size ([#116](https://github.com/Boddlnagg/midir/pull/116)) 27 | - [alsa] Fix port listing ([#117](https://github.com/Boddlnagg/midir/pull/117)) 28 | - [alsa] Send messages without buffering ([#125](https://github.com/Boddlnagg/midir/pull/125)) 29 | 30 | ## [0.8.0] - 2022-05-13 31 | 32 | - Migrate Windows backends to Microsoft's `windows` crate 33 | - Upgrade `coremidi` and `alsa` dependencies 34 | - Implement `PartialEq` for ports 35 | 36 | ## [0.7.0] - 2020-09-05 37 | 38 | - Update some Linux dependencies (`alsa`, `nix`) 39 | - Improve error handling for `MMSYSERR_ALLOCATED` (Windows) 40 | 41 | ## [0.6.2] - 2020-07-21 42 | 43 | - Remove deprecated usage of `mem::uninitialized` 44 | - Switch from `winrt-rust` to `winrt-rs` for WinRT backend 45 | 46 | ## [0.6.1] - 2020-06-04 47 | 48 | - Implement `Clone` for port structures 49 | - Add trait that abstracts over input and output 50 | 51 | ## [0.6.0] - 2020-05-11 52 | 53 | - Upgrade to winapi 0.3 54 | - Add WinRT backend 55 | - Add WebMIDI backend 56 | - Use platform-specific representation of port identifiers instead of indices 57 | 58 | ## [0.5.0] - 2017-12-09 59 | 60 | - Switch to absolute μs timestamps 61 | 62 | ## [0.4.0] - 2017-09-27 63 | 64 | - Add CoreMIDI backend 65 | - Use `usize` for port numbers and counts 66 | 67 | ## [0.3.2] - 2017-04-06 68 | 69 | - Use `alsa-rs` instead of homegrown wrapper 70 | 71 | ## [0.3.1] - 2017-03-21 72 | 73 | - Fix crates.io badges 74 | 75 | ## [0.3.0] - 2017-03-21 76 | 77 | - Fix compilation on ARM platforms -------------------------------------------------------------------------------- /examples/test_play.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io::{stdin, stdout, Write}; 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | 6 | use midir::{MidiOutput, MidiOutputPort}; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | match run() { 11 | Ok(_) => (), 12 | Err(err) => println!("Error: {}", err), 13 | } 14 | } 15 | 16 | fn run() -> Result<(), Box> { 17 | let midi_out = MidiOutput::new("My Test Output")?; 18 | 19 | // Get an output port (read from console if multiple are available) 20 | let out_ports = midi_out.ports(); 21 | let out_port: &MidiOutputPort = match out_ports.len() { 22 | 0 => return Err("no output port found".into()), 23 | 1 => { 24 | println!( 25 | "Choosing the only available output port: {}", 26 | midi_out.port_name(&out_ports[0]).unwrap() 27 | ); 28 | &out_ports[0] 29 | } 30 | _ => { 31 | println!("\nAvailable output ports:"); 32 | for (i, p) in out_ports.iter().enumerate() { 33 | println!("{}: {}", i, midi_out.port_name(p).unwrap()); 34 | } 35 | print!("Please select output port: "); 36 | stdout().flush()?; 37 | let mut input = String::new(); 38 | stdin().read_line(&mut input)?; 39 | out_ports 40 | .get(input.trim().parse::()?) 41 | .ok_or("invalid output port selected")? 42 | } 43 | }; 44 | 45 | println!("\nOpening connection"); 46 | let mut conn_out = midi_out.connect(out_port, "midir-test")?; 47 | println!("Connection open. Listen!"); 48 | { 49 | // Define a new scope in which the closure `play_note` borrows conn_out, so it can be called easily 50 | let mut play_note = |note: u8, duration: u64| { 51 | const NOTE_ON_MSG: u8 = 0x90; 52 | const NOTE_OFF_MSG: u8 = 0x80; 53 | const VELOCITY: u8 = 0x64; 54 | // We're ignoring errors in here 55 | let _ = conn_out.send(&[NOTE_ON_MSG, note, VELOCITY]); 56 | sleep(Duration::from_millis(duration * 150)); 57 | let _ = conn_out.send(&[NOTE_OFF_MSG, note, VELOCITY]); 58 | }; 59 | 60 | sleep(Duration::from_millis(4 * 150)); 61 | 62 | play_note(66, 4); 63 | play_note(65, 3); 64 | play_note(63, 1); 65 | play_note(61, 6); 66 | play_note(59, 2); 67 | play_note(58, 4); 68 | play_note(56, 4); 69 | play_note(54, 4); 70 | } 71 | sleep(Duration::from_millis(150)); 72 | println!("\nClosing connection"); 73 | // This is optional, the connection would automatically be closed as soon as it goes out of scope 74 | conn_out.close(); 75 | println!("Connection closed"); 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /examples/test_reuse.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io::{stdin, stdout, Write}; 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | 6 | use midir::{Ignore, MidiInput, MidiOutput}; 7 | 8 | fn main() { 9 | env_logger::init(); 10 | match run() { 11 | Ok(_) => (), 12 | Err(err) => println!("Error: {}", err), 13 | } 14 | } 15 | 16 | fn run() -> Result<(), Box> { 17 | let mut input = String::new(); 18 | 19 | let mut midi_in = MidiInput::new("My Test Input")?; 20 | midi_in.ignore(Ignore::None); 21 | let mut midi_out = MidiOutput::new("My Test Output")?; 22 | 23 | println!("Available input ports:"); 24 | let midi_in_ports = midi_in.ports(); 25 | for (i, p) in midi_in_ports.iter().enumerate() { 26 | println!("{}: {}", i, midi_in.port_name(p)?); 27 | } 28 | print!("Please select input port: "); 29 | stdout().flush()?; 30 | stdin().read_line(&mut input)?; 31 | let in_port = midi_in_ports 32 | .get(input.trim().parse::()?) 33 | .ok_or("Invalid port number")?; 34 | 35 | println!("\nAvailable output ports:"); 36 | let midi_out_ports = midi_out.ports(); 37 | for (i, p) in midi_out_ports.iter().enumerate() { 38 | println!("{}: {}", i, midi_out.port_name(p)?); 39 | } 40 | print!("Please select output port: "); 41 | stdout().flush()?; 42 | input.clear(); 43 | stdin().read_line(&mut input)?; 44 | let out_port = midi_out_ports 45 | .get(input.trim().parse::()?) 46 | .ok_or("Invalid port number")?; 47 | 48 | // This shows how to reuse input and output objects: 49 | // Open/close the connections twice using the same MidiInput/MidiOutput objects 50 | for _ in 0..2 { 51 | println!("\nOpening connections"); 52 | let log_all_bytes = Vec::new(); // We use this as an example custom data to pass into the callback 53 | let conn_in = midi_in.connect( 54 | in_port, 55 | "midir-test", 56 | |stamp, message, log| { 57 | // The last of the three callback parameters is the object that we pass in as last parameter of `connect`. 58 | println!("{}: {:?} (len = {})", stamp, message, message.len()); 59 | log.extend_from_slice(message); 60 | }, 61 | log_all_bytes, 62 | )?; 63 | 64 | // One could get the log back here out of the error 65 | let mut conn_out = midi_out.connect(out_port, "midir-test")?; 66 | 67 | println!("Connections open, enter `q` to exit ..."); 68 | 69 | loop { 70 | input.clear(); 71 | stdin().read_line(&mut input)?; 72 | if input.trim() == "q" { 73 | break; 74 | } else { 75 | conn_out.send(&[144, 60, 1])?; 76 | sleep(Duration::from_millis(200)); 77 | conn_out.send(&[144, 60, 0])?; 78 | } 79 | } 80 | println!("Closing connections"); 81 | let (midi_in_, log_all_bytes) = conn_in.close(); 82 | midi_in = midi_in_; 83 | midi_out = conn_out.close(); 84 | println!("Connections closed"); 85 | println!("Received bytes: {:?}", log_all_bytes); 86 | } 87 | Ok(()) 88 | } 89 | -------------------------------------------------------------------------------- /examples/test_sysex.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | env_logger::init(); 3 | match example::run() { 4 | Ok(_) => (), 5 | Err(err) => println!("Error: {}", err), 6 | } 7 | } 8 | 9 | #[cfg(not(any(windows, target_arch = "wasm32")))] // virtual ports are not supported on Windows nor on Web MIDI 10 | mod example { 11 | 12 | use std::error::Error; 13 | use std::thread::sleep; 14 | use std::time::Duration; 15 | 16 | use midir::os::unix::VirtualInput; 17 | use midir::{Ignore, MidiInput, MidiOutput}; 18 | 19 | const LARGE_SYSEX_SIZE: usize = 5572; // This is the maximum that worked for me 20 | 21 | pub fn run() -> Result<(), Box> { 22 | let mut midi_in = MidiInput::new("My Test Input")?; 23 | midi_in.ignore(Ignore::None); 24 | let midi_out = MidiOutput::new("My Test Output")?; 25 | 26 | let previous_count = midi_out.port_count(); 27 | 28 | println!("Creating virtual input port ..."); 29 | let conn_in = midi_in.create_virtual( 30 | "midir-test", 31 | |stamp, message, _| { 32 | println!("{}: {:?} (len = {})", stamp, message, message.len()); 33 | }, 34 | (), 35 | )?; 36 | 37 | assert_eq!(midi_out.port_count(), previous_count + 1); 38 | 39 | let out_ports = midi_out.ports(); 40 | let new_port = out_ports.last().unwrap(); 41 | println!( 42 | "Connecting to port '{}' ...", 43 | midi_out.port_name(&new_port).unwrap() 44 | ); 45 | let mut conn_out = midi_out.connect(&new_port, "midir-test")?; 46 | println!("Starting to send messages ..."); 47 | //sleep(Duration::from_millis(2000)); 48 | println!("Sending NoteOn message"); 49 | conn_out.send(&[144, 60, 1])?; 50 | sleep(Duration::from_millis(200)); 51 | println!("Sending small SysEx message ..."); 52 | conn_out.send(&[0xF0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF7])?; 53 | sleep(Duration::from_millis(200)); 54 | println!("Sending large SysEx message ..."); 55 | let mut v = Vec::with_capacity(LARGE_SYSEX_SIZE); 56 | v.push(0xF0u8); 57 | for _ in 1..LARGE_SYSEX_SIZE - 1 { 58 | v.push(0u8); 59 | } 60 | v.push(0xF7u8); 61 | assert_eq!(v.len(), LARGE_SYSEX_SIZE); 62 | conn_out.send(&v)?; 63 | sleep(Duration::from_millis(200)); 64 | // FIXME: the following doesn't seem to work with ALSA 65 | println!("Sending large SysEx message (chunked)..."); 66 | for ch in v.chunks(4) { 67 | conn_out.send(ch)?; 68 | } 69 | sleep(Duration::from_millis(200)); 70 | println!("Sending small SysEx message ..."); 71 | conn_out.send(&[0xF0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF7])?; 72 | sleep(Duration::from_millis(200)); 73 | println!("Closing output ..."); 74 | conn_out.close(); 75 | println!("Closing virtual input ..."); 76 | conn_in.close().0; 77 | Ok(()) 78 | } 79 | } 80 | 81 | // needed to compile successfully 82 | #[cfg(any(windows, target_arch = "wasm32"))] 83 | mod example { 84 | use std::error::Error; 85 | pub fn run() -> Result<(), Box> { 86 | Ok(()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/browser/src/lib.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Array; 2 | use wasm_bindgen::prelude::*; 3 | use wasm_bindgen::JsCast; 4 | use web_sys::console; 5 | 6 | use std::error::Error; 7 | use std::sync::{Arc, Mutex}; 8 | 9 | use midir::{Ignore, MidiInput}; 10 | 11 | pub fn log(s: String) { 12 | console::log(&Array::of1(&s.into())); 13 | } 14 | 15 | macro_rules! println { 16 | () => (log("".to_owned())); 17 | ($($arg:tt)*) => (log(format!($($arg)*))); 18 | } 19 | 20 | #[wasm_bindgen(start)] 21 | pub fn start() { 22 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 23 | 24 | let token_outer = Arc::new(Mutex::new(None)); 25 | let token = token_outer.clone(); 26 | let closure: Closure = Closure::wrap(Box::new(move || { 27 | if run().unwrap() == true { 28 | if let Some(token) = *token.lock().unwrap() { 29 | web_sys::window().unwrap().clear_interval_with_handle(token); 30 | } 31 | } 32 | })); 33 | *token_outer.lock().unwrap() = web_sys::window() 34 | .unwrap() 35 | .set_interval_with_callback_and_timeout_and_arguments_0( 36 | closure.as_ref().unchecked_ref(), 37 | 200, 38 | ) 39 | .ok(); 40 | closure.forget(); 41 | } 42 | 43 | fn run() -> Result> { 44 | let window = web_sys::window().expect("no global `window` exists"); 45 | 46 | let mut midi_in = MidiInput::new("midir reading input")?; 47 | midi_in.ignore(Ignore::None); 48 | 49 | // Get an input port 50 | let ports = midi_in.ports(); 51 | let in_port = match &ports[..] { 52 | [] => { 53 | println!("No ports available yet, will try again"); 54 | return Ok(false); 55 | } 56 | [ref port] => { 57 | println!( 58 | "Choosing the only available input port: {}", 59 | midi_in.port_name(port).unwrap() 60 | ); 61 | port 62 | } 63 | _ => { 64 | let mut msg = "Choose an available input port:\n".to_string(); 65 | for (i, port) in ports.iter().enumerate() { 66 | msg.push_str(format!("{}: {}\n", i, midi_in.port_name(port).unwrap()).as_str()); 67 | } 68 | loop { 69 | if let Ok(Some(port_str)) = window.prompt_with_message_and_default(&msg, "0") { 70 | if let Ok(port_int) = port_str.parse::() { 71 | if let Some(port) = ports.get(port_int) { 72 | break port; 73 | } 74 | } 75 | } 76 | } 77 | } 78 | }; 79 | 80 | println!("Opening connection"); 81 | let in_port_name = midi_in.port_name(in_port)?; 82 | 83 | // _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope 84 | let _conn_in = midi_in.connect( 85 | in_port, 86 | "midir-read-input", 87 | move |stamp, message, _| { 88 | println!("{}: {:?} (len = {})", stamp, message, message.len()); 89 | }, 90 | (), 91 | )?; 92 | 93 | println!("Connection open, reading input from '{}'", in_port_name); 94 | Box::leak(Box::new(_conn_in)); 95 | Ok(true) 96 | } 97 | -------------------------------------------------------------------------------- /tests/virtual.rs: -------------------------------------------------------------------------------- 1 | //! This file contains automated tests, but they require virtual ports and therefore can't work on Windows or Web MIDI ... 2 | #![cfg(not(any(windows, target_arch = "wasm32")))] 3 | 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | use midir::os::unix::{VirtualInput, VirtualOutput}; 8 | use midir::{Ignore, MidiInput, MidiOutput, MidiOutputPort}; 9 | 10 | #[test] 11 | fn end_to_end() { 12 | let mut midi_in = MidiInput::new("My Test Input").unwrap(); 13 | midi_in.ignore(Ignore::None); 14 | let midi_out = MidiOutput::new("My Test Output").unwrap(); 15 | 16 | let previous_count = midi_out.port_count(); 17 | 18 | println!("Creating virtual input port ..."); 19 | let conn_in = midi_in 20 | .create_virtual( 21 | "midir-test", 22 | |stamp, message, _| { 23 | println!("{}: {:?} (len = {})", stamp, message, message.len()); 24 | }, 25 | (), 26 | ) 27 | .unwrap(); 28 | 29 | assert_eq!(midi_out.port_count(), previous_count + 1); 30 | 31 | let new_port: MidiOutputPort = midi_out.ports().into_iter().rev().next().unwrap(); 32 | 33 | println!( 34 | "Connecting to port '{}' ...", 35 | midi_out.port_name(&new_port).unwrap() 36 | ); 37 | let mut conn_out = midi_out.connect(&new_port, "midir-test").unwrap(); 38 | println!("Starting to send messages ..."); 39 | conn_out.send(&[144, 60, 1]).unwrap(); 40 | sleep(Duration::from_millis(200)); 41 | conn_out.send(&[144, 60, 0]).unwrap(); 42 | sleep(Duration::from_millis(50)); 43 | conn_out.send(&[144, 60, 1]).unwrap(); 44 | sleep(Duration::from_millis(200)); 45 | conn_out.send(&[144, 60, 0]).unwrap(); 46 | sleep(Duration::from_millis(50)); 47 | println!("Closing output ..."); 48 | let midi_out = conn_out.close(); 49 | println!("Closing virtual input ..."); 50 | let midi_in = conn_in.close().0; 51 | assert_eq!(midi_out.port_count(), previous_count); 52 | 53 | let previous_count = midi_in.port_count(); 54 | 55 | // reuse midi_in and midi_out, but swap roles 56 | println!("\nCreating virtual output port ..."); 57 | let mut conn_out = midi_out.create_virtual("midir-test").unwrap(); 58 | assert_eq!(midi_in.port_count(), previous_count + 1); 59 | 60 | let new_port = midi_in.ports().into_iter().rev().next().unwrap(); 61 | 62 | println!( 63 | "Connecting to port '{}' ...", 64 | midi_in.port_name(&new_port).unwrap() 65 | ); 66 | let conn_in = midi_in 67 | .connect( 68 | &new_port, 69 | "midir-test", 70 | |stamp, message, _| { 71 | println!("{}: {:?} (len = {})", stamp, message, message.len()); 72 | }, 73 | (), 74 | ) 75 | .unwrap(); 76 | println!("Starting to send messages ..."); 77 | conn_out.send(&[144, 60, 1]).unwrap(); 78 | sleep(Duration::from_millis(200)); 79 | conn_out.send(&[144, 60, 0]).unwrap(); 80 | sleep(Duration::from_millis(50)); 81 | conn_out.send(&[144, 60, 1]).unwrap(); 82 | sleep(Duration::from_millis(200)); 83 | conn_out.send(&[144, 60, 0]).unwrap(); 84 | sleep(Duration::from_millis(50)); 85 | println!("Closing input ..."); 86 | let midi_in = conn_in.close().0; 87 | println!("Closing virtual output ..."); 88 | conn_out.close(); 89 | assert_eq!(midi_in.port_count(), previous_count); 90 | } 91 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | const INVALID_PORT_MSG: &str = "invalid port"; 5 | const PORT_OUT_OF_RANGE_MSG: &str = "provided port number was out of range"; 6 | const CANNOT_RETRIEVE_PORT_NAME_MSG: &str = "unknown error when trying to retrieve the port name"; 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | /// An error that can occur during initialization (i.e., while 10 | /// creating a `MidiInput` or `MidiOutput` object). 11 | pub struct InitError; 12 | 13 | impl Error for InitError {} 14 | 15 | impl fmt::Display for InitError { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | "MIDI support could not be initialized".fmt(f) 18 | } 19 | } 20 | 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 22 | /// An error that can occur when retrieving information about 23 | /// available ports. 24 | pub enum PortInfoError { 25 | PortNumberOutOfRange, // TODO: don't expose this 26 | InvalidPort, 27 | CannotRetrievePortName, 28 | } 29 | 30 | impl Error for PortInfoError {} 31 | 32 | impl fmt::Display for PortInfoError { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | match *self { 35 | PortInfoError::PortNumberOutOfRange => PORT_OUT_OF_RANGE_MSG.fmt(f), 36 | PortInfoError::InvalidPort => INVALID_PORT_MSG.fmt(f), 37 | PortInfoError::CannotRetrievePortName => CANNOT_RETRIEVE_PORT_NAME_MSG.fmt(f), 38 | } 39 | } 40 | } 41 | 42 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 43 | /// The kind of error for a `ConnectError`. 44 | pub enum ConnectErrorKind { 45 | InvalidPort, 46 | Other(&'static str), 47 | } 48 | 49 | impl ConnectErrorKind {} 50 | 51 | impl fmt::Display for ConnectErrorKind { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | match *self { 54 | ConnectErrorKind::InvalidPort => INVALID_PORT_MSG.fmt(f), 55 | ConnectErrorKind::Other(msg) => msg.fmt(f), 56 | } 57 | } 58 | } 59 | 60 | /// An error that can occur when trying to connect to a port. 61 | pub struct ConnectError { 62 | kind: ConnectErrorKind, 63 | inner: T, 64 | } 65 | 66 | impl ConnectError { 67 | pub fn new(kind: ConnectErrorKind, inner: T) -> ConnectError { 68 | ConnectError { kind, inner } 69 | } 70 | 71 | /// Helper method to create ConnectErrorKind::Other. 72 | pub fn other(msg: &'static str, inner: T) -> ConnectError { 73 | Self::new(ConnectErrorKind::Other(msg), inner) 74 | } 75 | 76 | pub fn kind(&self) -> ConnectErrorKind { 77 | self.kind 78 | } 79 | 80 | pub fn into_inner(self) -> T { 81 | self.inner 82 | } 83 | } 84 | 85 | impl fmt::Debug for ConnectError { 86 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 87 | self.kind.fmt(f) 88 | } 89 | } 90 | 91 | impl fmt::Display for ConnectError { 92 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 93 | self.kind.fmt(f) 94 | } 95 | } 96 | 97 | impl Error for ConnectError {} 98 | 99 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 100 | /// An error that can occur when sending MIDI messages. 101 | pub enum SendError { 102 | InvalidData(&'static str), 103 | Other(&'static str), 104 | } 105 | 106 | impl Error for SendError {} 107 | 108 | impl fmt::Display for SendError { 109 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 110 | match *self { 111 | SendError::InvalidData(msg) | SendError::Other(msg) => msg.fmt(f), 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/backend/winmm/handler.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, slice}; 2 | 3 | use log::error; 4 | 5 | use windows::Win32::Media::Audio::{midiInAddBuffer, HMIDIIN, MIDIHDR}; 6 | use windows::Win32::Media::{MMSYSERR_NOERROR, MM_MIM_DATA, MM_MIM_LONGDATA, MM_MIM_LONGERROR}; 7 | 8 | use crate::Ignore; 9 | 10 | use super::{DWORD, DWORD_PTR, UINT}; 11 | 12 | use super::HandlerData; 13 | 14 | pub extern "system" fn handle_input( 15 | _: HMIDIIN, 16 | input_status: UINT, 17 | instance_ptr: DWORD_PTR, 18 | midi_message: DWORD_PTR, 19 | timestamp: DWORD, 20 | ) { 21 | if input_status != MM_MIM_DATA 22 | && input_status != MM_MIM_LONGDATA 23 | && input_status != MM_MIM_LONGERROR 24 | { 25 | return; 26 | } 27 | 28 | let data: &mut HandlerData = unsafe { &mut *(instance_ptr as *mut HandlerData) }; 29 | 30 | // Calculate time stamp. 31 | data.message.timestamp = timestamp as u64 * 1000; // milliseconds -> microseconds 32 | 33 | if input_status == MM_MIM_DATA { 34 | // Channel or system message 35 | // Make sure the first byte is a status byte. 36 | let status: u8 = (midi_message & 0x000000FF) as u8; 37 | if status & 0x80 == 0 { 38 | return; 39 | } 40 | 41 | // Determine the number of bytes in the MIDI message. 42 | let nbytes: u16 = if status < 0xC0 { 43 | 3 44 | } else if status < 0xE0 { 45 | 2 46 | } else if status < 0xF0 { 47 | 3 48 | } else if status == 0xF1 { 49 | if data.ignore_flags.contains(Ignore::Time) { 50 | return; 51 | } else { 52 | 2 53 | } 54 | } else if status == 0xF2 { 55 | 3 56 | } else if status == 0xF3 { 57 | 2 58 | } else if status == 0xF8 && (data.ignore_flags.contains(Ignore::Time)) { 59 | // A MIDI timing tick message and we're ignoring it. 60 | return; 61 | } else if status == 0xFE && (data.ignore_flags.contains(Ignore::ActiveSense)) { 62 | // A MIDI active sensing message and we're ignoring it. 63 | return; 64 | } else { 65 | 1 66 | }; 67 | 68 | // Copy bytes to our MIDI message. 69 | let ptr = (&midi_message) as *const DWORD_PTR as *const u8; 70 | let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr, nbytes as usize) }; 71 | data.message.bytes.extend_from_slice(bytes); 72 | } else { 73 | // Sysex message (MIM_LONGDATA or MIM_LONGERROR) 74 | let sysex = unsafe { &*(midi_message as *const MIDIHDR) }; 75 | if !data.ignore_flags.contains(Ignore::Sysex) && input_status != MM_MIM_LONGERROR { 76 | // Sysex message and we're not ignoring it 77 | let bytes: &[u8] = 78 | unsafe { slice::from_raw_parts(sysex.lpData.0, sysex.dwBytesRecorded as usize) }; 79 | data.message.bytes.extend_from_slice(bytes); 80 | // TODO: If sysex messages are longer than MIDIR_SYSEX_BUFFER_SIZE, they 81 | // are split in chunks. We could reassemble a single message. 82 | } 83 | 84 | // The WinMM API requires that the sysex buffer be requeued after 85 | // input of each sysex message. Even if we are ignoring sysex 86 | // messages, we still need to requeue the buffer in case the user 87 | // decides to not ignore sysex messages in the future. However, 88 | // it seems that WinMM calls this function with an empty sysex 89 | // buffer when an application closes and in this case, we should 90 | // avoid requeueing it, else the computer suddenly reboots after 91 | // one or two minutes. 92 | if (unsafe { *data.sysex_buffer.0[sysex.dwUser] }).dwBytesRecorded > 0 { 93 | //if ( sysex->dwBytesRecorded > 0 ) { 94 | let in_handle = data.in_handle.as_ref().unwrap().0.lock(); 95 | let result = unsafe { 96 | midiInAddBuffer( 97 | *in_handle, 98 | data.sysex_buffer.0[sysex.dwUser], 99 | mem::size_of::() as u32, 100 | ) 101 | }; 102 | drop(in_handle); 103 | if result != MMSYSERR_NOERROR { 104 | error!("Error in handle_input: Requeuing WinMM input sysex buffer failed."); 105 | } 106 | 107 | if data.ignore_flags.contains(Ignore::Sysex) { 108 | return; 109 | } 110 | } else { 111 | return; 112 | } 113 | } 114 | 115 | (data.callback)( 116 | data.message.timestamp, 117 | &data.message.bytes, 118 | data.user_data.as_mut().unwrap(), 119 | ); 120 | 121 | // Clear the vector for the next input message. 122 | data.message.bytes.clear(); 123 | } 124 | -------------------------------------------------------------------------------- /src/backend/jack/wrappers.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals, dead_code)] 2 | 3 | use std::ffi::{CStr, CString}; 4 | use std::ops::Index; 5 | use std::os::raw::c_char; 6 | use std::{ptr, slice, str}; 7 | 8 | use libc::{c_void, size_t}; 9 | 10 | use jack_sys::{ 11 | jack_activate, jack_client_close, jack_client_open, jack_client_t, jack_connect, 12 | jack_deactivate, jack_free, jack_get_ports, jack_get_time, jack_midi_clear_buffer, 13 | jack_midi_data_t, jack_midi_event_get, jack_midi_event_reserve, jack_midi_event_t, 14 | jack_midi_get_event_count, jack_nframes_t, jack_port_get_buffer, jack_port_name, 15 | jack_port_register, jack_port_t, jack_port_unregister, jack_ringbuffer_create, 16 | jack_ringbuffer_free, jack_ringbuffer_read, jack_ringbuffer_read_space, jack_ringbuffer_t, 17 | jack_ringbuffer_write, jack_set_process_callback, 18 | }; 19 | 20 | pub const JACK_DEFAULT_MIDI_TYPE: &[u8] = b"8 bit raw midi\0"; 21 | 22 | bitflags! { 23 | #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] 24 | pub struct JackOpenOptions: u32 { 25 | const NoStartServer = 1; 26 | const UseExactName = 2; 27 | const ServerName = 4; 28 | const SessionID = 32; 29 | } 30 | } 31 | 32 | bitflags! { 33 | #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] 34 | pub struct PortFlags: u32 { 35 | const PortIsInput = 1; 36 | const PortIsOutput = 2; 37 | const PortIsPhysical = 4; 38 | const PortCanMonitor = 8; 39 | const PortIsTerminal = 16; 40 | } 41 | } 42 | 43 | // TODO: hide this type 44 | pub type ProcessCallback = extern "C" fn(nframes: jack_nframes_t, arg: *mut c_void) -> i32; 45 | 46 | pub struct Client { 47 | p: *mut jack_client_t, 48 | } 49 | 50 | unsafe impl Send for Client {} 51 | 52 | impl Client { 53 | pub fn get_time() -> u64 { 54 | unsafe { jack_get_time() } 55 | } 56 | 57 | pub fn open(name: &str, options: JackOpenOptions) -> Result { 58 | let c_name = CString::new(name).expect("client name must not contain null bytes"); 59 | let result = unsafe { jack_client_open(c_name.as_ptr(), options.bits(), ptr::null_mut()) }; 60 | if result.is_null() { 61 | Err(()) 62 | } else { 63 | Ok(Client { p: result }) 64 | } 65 | } 66 | 67 | pub fn get_midi_ports(&self, flags: PortFlags) -> PortInfos<'_> { 68 | let raw: *mut *const c_char = unsafe { 69 | jack_get_ports( 70 | self.p, 71 | ptr::null_mut(), 72 | JACK_DEFAULT_MIDI_TYPE.as_ptr() as *const c_char, 73 | flags.bits() as _, 74 | ) 75 | }; 76 | let slice: &[*const c_char] = if raw.is_null() { 77 | &[] 78 | } else { 79 | unsafe { 80 | let mut n = 0usize; 81 | while !(*raw.add(n)).is_null() { 82 | n += 1; 83 | } 84 | slice::from_raw_parts(raw as *const *const c_char, n) 85 | } 86 | }; 87 | PortInfos { raw, p: slice } 88 | } 89 | 90 | pub fn register_midi_port(&mut self, name: &str, flags: PortFlags) -> Result { 91 | let c_name = CString::new(name).expect("port name must not contain null bytes"); 92 | let result = unsafe { 93 | jack_port_register( 94 | self.p, 95 | c_name.as_ptr(), 96 | JACK_DEFAULT_MIDI_TYPE.as_ptr() as *const _, 97 | flags.bits() as _, 98 | 0, 99 | ) 100 | }; 101 | if result.is_null() { 102 | Err(()) 103 | } else { 104 | Ok(MidiPort { p: result }) 105 | } 106 | } 107 | 108 | /// This can not be implemented in Drop, because it needs a reference 109 | /// to the client. But it consumes the MidiPort. 110 | pub fn unregister_midi_port(&mut self, client: MidiPort) { 111 | unsafe { jack_port_unregister(self.p, client.p) }; 112 | } 113 | 114 | pub fn activate(&mut self) { 115 | unsafe { jack_activate(self.p) }; 116 | } 117 | 118 | pub fn deactivate(&mut self) { 119 | unsafe { jack_deactivate(self.p) }; 120 | } 121 | 122 | /// The code in the supplied function must be suitable for real-time 123 | /// execution. That means that it cannot call functions that might block 124 | /// for a long time. This includes all I/O functions (disk, TTY, network), 125 | /// malloc, free, printf, pthread_mutex_lock, sleep, wait, poll, select, 126 | /// pthread_join, pthread_cond_wait, etc, etc. 127 | pub fn set_process_callback(&mut self, callback: ProcessCallback, data: *mut c_void) { 128 | unsafe { jack_set_process_callback(self.p, Some(callback), data) }; 129 | } 130 | 131 | pub fn connect(&mut self, source_port: &CStr, destination_port: &CStr) -> Result<(), ()> { 132 | let rc = unsafe { jack_connect(self.p, source_port.as_ptr(), destination_port.as_ptr()) }; 133 | if rc == 0 { 134 | Ok(()) 135 | } else { 136 | Err(()) // TODO: maybe handle EEXIST explicitly 137 | } 138 | } 139 | } 140 | 141 | impl Drop for Client { 142 | fn drop(&mut self) { 143 | unsafe { jack_client_close(self.p) }; 144 | } 145 | } 146 | 147 | pub struct PortInfos<'a> { 148 | // Returned by jack_get_ports (NULL-terminated array), needs to be freed with jack_free 149 | raw: *mut *const c_char, 150 | p: &'a [*const c_char], 151 | } 152 | unsafe impl<'a> Send for PortInfos<'a> {} 153 | 154 | impl<'a> PortInfos<'a> { 155 | pub fn count(&self) -> usize { 156 | self.p.len() 157 | } 158 | 159 | pub fn get_c_name(&self, index: usize) -> &CStr { 160 | let ptr: *const c_char = self.p[index]; 161 | unsafe { CStr::from_ptr(ptr) } 162 | } 163 | } 164 | 165 | impl<'a> Index for PortInfos<'a> { 166 | type Output = str; 167 | 168 | fn index(&self, index: usize) -> &Self::Output { 169 | let slice = self.get_c_name(index).to_bytes(); 170 | str::from_utf8(slice).expect("Error converting port name to UTF8") 171 | } 172 | } 173 | 174 | impl<'a> Drop for PortInfos<'a> { 175 | fn drop(&mut self) { 176 | if !self.raw.is_null() { 177 | unsafe { jack_free(self.raw as *mut _) } 178 | } 179 | } 180 | } 181 | 182 | pub struct MidiPort { 183 | p: *mut jack_port_t, 184 | } 185 | 186 | unsafe impl Send for MidiPort {} 187 | 188 | impl MidiPort { 189 | pub fn get_name(&self) -> &CStr { 190 | unsafe { CStr::from_ptr(jack_port_name(self.p)) } 191 | } 192 | 193 | pub fn get_midi_buffer(&self, nframes: jack_nframes_t) -> MidiBuffer { 194 | let buf = unsafe { jack_port_get_buffer(self.p, nframes) }; 195 | MidiBuffer { p: buf } 196 | } 197 | } 198 | 199 | pub struct MidiBuffer { 200 | p: *mut c_void, 201 | } 202 | 203 | impl MidiBuffer { 204 | pub fn get_event_count(&self) -> u32 { 205 | unsafe { jack_midi_get_event_count(self.p) } 206 | } 207 | 208 | pub unsafe fn get_event(&self, ev: *mut jack_midi_event_t, index: u32) { 209 | jack_midi_event_get(ev, self.p, index); 210 | } 211 | 212 | pub fn clear(&mut self) { 213 | unsafe { jack_midi_clear_buffer(self.p) } 214 | } 215 | 216 | pub fn event_reserve( 217 | &mut self, 218 | time: jack_nframes_t, 219 | data_size: usize, 220 | ) -> *mut jack_midi_data_t { 221 | unsafe { jack_midi_event_reserve(self.p, time, data_size as size_t) } 222 | } 223 | } 224 | 225 | pub struct Ringbuffer { 226 | p: *mut jack_ringbuffer_t, 227 | } 228 | 229 | unsafe impl Send for Ringbuffer {} 230 | 231 | impl Ringbuffer { 232 | pub fn new(size: usize) -> Ringbuffer { 233 | let result = unsafe { jack_ringbuffer_create(size as size_t) }; 234 | Ringbuffer { p: result } 235 | } 236 | 237 | pub fn get_read_space(&self) -> usize { 238 | unsafe { jack_ringbuffer_read_space(self.p) as usize } 239 | } 240 | 241 | pub fn read(&mut self, destination: *mut u8, count: usize) -> usize { 242 | let bytes_read = 243 | unsafe { jack_ringbuffer_read(self.p, destination as *mut _, count as size_t) }; 244 | bytes_read as usize 245 | } 246 | 247 | pub fn write(&mut self, source: &[u8]) -> usize { 248 | unsafe { 249 | jack_ringbuffer_write(self.p, source.as_ptr() as *const _, source.len() as size_t) 250 | as usize 251 | } 252 | } 253 | } 254 | 255 | impl Drop for Ringbuffer { 256 | fn drop(&mut self) { 257 | unsafe { jack_ringbuffer_free(self.p) } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/backend/webmidi/mod.rs: -------------------------------------------------------------------------------- 1 | //! Web MIDI Backend. 2 | //! 3 | //! Reference: 4 | //! * [W3C Editor's Draft](https://webaudio.github.io/web-midi-api/) 5 | //! * [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess) 6 | 7 | use js_sys::{Map, Promise, Uint8Array}; 8 | use wasm_bindgen::prelude::*; 9 | use wasm_bindgen::JsCast; 10 | use web_sys::{MidiAccess, MidiMessageEvent, MidiOptions}; 11 | 12 | use std::cell::RefCell; 13 | use std::sync::{Arc, Mutex}; 14 | 15 | use crate::errors::*; 16 | use crate::Ignore; 17 | 18 | thread_local! { 19 | static STATIC : RefCell = RefCell::new(Static::new()); 20 | } 21 | 22 | struct Static { 23 | pub access: Option, 24 | pub request: Option, 25 | pub ever_requested: bool, 26 | 27 | pub on_ok: Closure, 28 | pub on_err: Closure, 29 | } 30 | 31 | impl Static { 32 | pub fn new() -> Self { 33 | let mut s = Self { 34 | access: None, 35 | request: None, 36 | ever_requested: false, 37 | 38 | on_ok: Closure::wrap(Box::new(|access| { 39 | STATIC.with(|s| { 40 | let mut s = s.borrow_mut(); 41 | let access: MidiAccess = access.dyn_into().unwrap(); 42 | s.request = None; 43 | s.access = Some(access); 44 | }); 45 | })), 46 | on_err: Closure::wrap(Box::new(|_error| { 47 | STATIC.with(|s| { 48 | let mut s = s.borrow_mut(); 49 | s.request = None; 50 | }); 51 | })), 52 | }; 53 | // Some notes on sysex behavior: 54 | // 1) Some devices (but not all!) may work without sysex 55 | // 2) Chrome will only prompt the end user to grant permission if they requested sysex permissions for now... 56 | // but that's changing soon for "security reasons" (reduced fingerprinting? poorly tested drivers?): 57 | // https://www.chromestatus.com/feature/5138066234671104 58 | // 59 | // I've chosen to hardcode sysex=true here, since that'll be compatible with more devices, *and* should change 60 | // less behavior when Chrome's changes land. 61 | s.request_midi_access(true); 62 | s 63 | } 64 | 65 | fn request_midi_access(&mut self, sysex: bool) { 66 | self.ever_requested = true; 67 | if self.access.is_some() { 68 | return; 69 | } // Already have access 70 | if self.request.is_some() { 71 | return; 72 | } // Mid-request already 73 | let window = if let Some(w) = web_sys::window() { 74 | w 75 | } else { 76 | return; 77 | }; 78 | 79 | let _request = match window 80 | .navigator() 81 | .request_midi_access_with_options(MidiOptions::new().sysex(sysex)) 82 | { 83 | Ok(p) => { 84 | self.request = Some(p.then2(&self.on_ok, &self.on_err)); 85 | } 86 | Err(_) => { 87 | return; 88 | } // node.js? brower doesn't support webmidi? other? 89 | }; 90 | } 91 | } 92 | 93 | #[derive(Clone, PartialEq)] 94 | pub struct MidiInputPort { 95 | input: web_sys::MidiInput, 96 | } 97 | 98 | impl MidiInputPort { 99 | pub fn id(&self) -> String { 100 | self.input.id() 101 | } 102 | } 103 | 104 | pub struct MidiInput { 105 | ignore_flags: Ignore, 106 | } 107 | 108 | impl MidiInput { 109 | pub fn new(_client_name: &str) -> Result { 110 | STATIC.with(|_| {}); 111 | Ok(MidiInput { 112 | ignore_flags: Ignore::None, 113 | }) 114 | } 115 | 116 | pub(crate) fn ports_internal(&self) -> Vec { 117 | STATIC.with(|s| { 118 | let mut v = Vec::new(); 119 | let s = s.borrow(); 120 | if let Some(access) = s.access.as_ref() { 121 | let inputs: Map = access.inputs().unchecked_into(); 122 | inputs.for_each(&mut |value, _| { 123 | v.push(crate::common::MidiInputPort { 124 | imp: MidiInputPort { 125 | input: value.dyn_into().unwrap(), 126 | }, 127 | }); 128 | }); 129 | } 130 | v 131 | }) 132 | } 133 | 134 | pub fn ignore(&mut self, flags: Ignore) { 135 | self.ignore_flags = flags; 136 | } 137 | 138 | pub fn port_count(&self) -> usize { 139 | STATIC.with(|s| { 140 | let s = s.borrow(); 141 | s.access 142 | .as_ref() 143 | .map(|access| access.inputs().unchecked_into::().size() as usize) 144 | .unwrap_or(0) 145 | }) 146 | } 147 | 148 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 149 | Ok(port.input.name().unwrap_or_else(|| port.input.id())) 150 | } 151 | 152 | pub fn connect( 153 | self, 154 | port: &MidiInputPort, 155 | _port_name: &str, 156 | mut callback: F, 157 | data: T, 158 | ) -> Result, ConnectError> 159 | where 160 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 161 | { 162 | let input = port.input.clone(); 163 | let _ = input.open(); // NOTE: asyncronous! 164 | 165 | let ignore_flags = self.ignore_flags; 166 | let user_data = Arc::new(Mutex::new(Some(data))); 167 | 168 | let closure = { 169 | let user_data = user_data.clone(); 170 | 171 | let closure = Closure::wrap(Box::new(move |event: MidiMessageEvent| { 172 | let time = (event.time_stamp() * 1000.0) as u64; // ms -> us 173 | let buffer = event.data().unwrap(); 174 | 175 | let status = buffer[0]; 176 | if !(status == 0xF0 && ignore_flags.contains(Ignore::Sysex) 177 | || status == 0xF1 && ignore_flags.contains(Ignore::Time) 178 | || status == 0xF8 && ignore_flags.contains(Ignore::Time) 179 | || status == 0xFE && ignore_flags.contains(Ignore::ActiveSense)) 180 | { 181 | callback( 182 | time, 183 | &buffer[..], 184 | user_data.lock().unwrap().as_mut().unwrap(), 185 | ); 186 | } 187 | }) as Box); 188 | 189 | input.set_onmidimessage(Some(closure.as_ref().unchecked_ref())); 190 | 191 | closure 192 | }; 193 | 194 | Ok(MidiInputConnection { 195 | ignore_flags, 196 | input, 197 | user_data, 198 | closure, 199 | }) 200 | } 201 | } 202 | 203 | pub struct MidiInputConnection { 204 | ignore_flags: Ignore, 205 | input: web_sys::MidiInput, 206 | user_data: Arc>>, 207 | #[allow(dead_code)] // Must be kept alive until we decide to unregister from input 208 | closure: Closure, 209 | } 210 | 211 | impl MidiInputConnection { 212 | pub fn close(self) -> (MidiInput, T) { 213 | let Self { 214 | ignore_flags, 215 | input, 216 | user_data, 217 | .. 218 | } = self; 219 | 220 | input.set_onmidimessage(None); 221 | let mut user_data = user_data.lock().unwrap(); 222 | 223 | (MidiInput { ignore_flags }, user_data.take().unwrap()) 224 | } 225 | } 226 | 227 | #[derive(Clone, PartialEq)] 228 | pub struct MidiOutputPort { 229 | output: web_sys::MidiOutput, 230 | } 231 | 232 | impl MidiOutputPort { 233 | pub fn id(&self) -> String { 234 | self.output.id() 235 | } 236 | } 237 | 238 | pub struct MidiOutput {} 239 | 240 | impl MidiOutput { 241 | pub fn new(_client_name: &str) -> Result { 242 | STATIC.with(|_| {}); 243 | Ok(MidiOutput {}) 244 | } 245 | 246 | pub(crate) fn ports_internal(&self) -> Vec { 247 | STATIC.with(|s| { 248 | let mut v = Vec::new(); 249 | let s = s.borrow(); 250 | if let Some(access) = s.access.as_ref() { 251 | access 252 | .outputs() 253 | .unchecked_into::() 254 | .for_each(&mut |value, _| { 255 | v.push(crate::common::MidiOutputPort { 256 | imp: MidiOutputPort { 257 | output: value.dyn_into().unwrap(), 258 | }, 259 | }); 260 | }); 261 | } 262 | v 263 | }) 264 | } 265 | 266 | pub fn port_count(&self) -> usize { 267 | STATIC.with(|s| { 268 | let s = s.borrow(); 269 | s.access 270 | .as_ref() 271 | .map(|access| access.outputs().unchecked_into::().size() as usize) 272 | .unwrap_or(0) 273 | }) 274 | } 275 | 276 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 277 | Ok(port.output.name().unwrap_or_else(|| port.output.id())) 278 | } 279 | 280 | pub fn connect( 281 | self, 282 | port: &MidiOutputPort, 283 | _port_name: &str, 284 | ) -> Result> { 285 | let _ = port.output.open(); // NOTE: asyncronous! 286 | Ok(MidiOutputConnection { 287 | output: port.output.clone(), 288 | }) 289 | } 290 | } 291 | 292 | pub struct MidiOutputConnection { 293 | output: web_sys::MidiOutput, 294 | } 295 | 296 | impl MidiOutputConnection { 297 | pub fn close(self) -> MidiOutput { 298 | let _ = self.output.close(); // NOTE: asyncronous! 299 | MidiOutput {} 300 | } 301 | 302 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 303 | self.output 304 | .send(unsafe { Uint8Array::view(message) }.as_ref()) 305 | .map_err(|_| SendError::Other("JavaScript exception")) 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: "**" 7 | 8 | concurrency: 9 | group: ci-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: read 14 | 15 | env: 16 | CARGO_TERM_COLOR: always 17 | 18 | jobs: 19 | build-stable: 20 | name: Build (stable) — ${{ matrix.name }} 21 | runs-on: ${{ matrix.os }} 22 | continue-on-error: ${{ matrix.allow_fail || false }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | include: 27 | - { name: Linux (ALSA), os: ubuntu-latest, target: x86_64-unknown-linux-gnu, features: "", setup: linux-alsa } 28 | - { name: Linux (JACK), os: ubuntu-latest, target: x86_64-unknown-linux-gnu, features: "jack", setup: linux-jack } 29 | - { name: Linux (WASM), os: ubuntu-latest, target: wasm32-unknown-unknown, features: "", setup: wasm } 30 | - { name: Windows (MSVC / WinMM), os: windows-latest, target: x86_64-pc-windows-msvc, features: "", setup: windows-msvc } 31 | - { name: Windows (MSVC / WinRT), os: windows-latest, target: x86_64-pc-windows-msvc, features: "winrt", setup: windows-msvc } 32 | - { name: Windows (GNU i686 / WinMM), os: windows-latest, target: i686-pc-windows-gnu, features: "", setup: windows-gnu } 33 | - { name: Windows (GNU i686 / WinRT), os: windows-latest, target: i686-pc-windows-gnu, features: "winrt", setup: windows-gnu, allow_fail: true } 34 | - { name: macOS (CoreMIDI), os: macos-latest, target: "", features: "", setup: macos } 35 | - { name: macOS (JACK), os: macos-latest, target: "", features: "jack", setup: macos-jack, allow_fail: true } 36 | - { name: iOS (Mac Catalyst aarch64-apple-ios-macabi), os: macos-latest, target: aarch64-apple-ios-macabi, features: "", setup: ios-macabi } 37 | - { name: Linux (aarch64-unknown-linux-gnu), os: ubuntu-latest, target: aarch64-unknown-linux-gnu, features: "", setup: linux-aarch64 } 38 | - { name: Windows (ARM64 MSVC), os: windows-latest, target: aarch64-pc-windows-msvc, features: "", setup: windows-msvc } 39 | - { name: Android (aarch64/armv7/x86_64/i686; API 29), os: ubuntu-latest, target: "", features: "", setup: android } 40 | 41 | steps: 42 | - name: Checkout 43 | uses: actions/checkout@v4 44 | 45 | - name: Rust cache 46 | uses: Swatinem/rust-cache@v2 47 | with: 48 | cache-all-crates: true 49 | 50 | - name: Check format 51 | run: cargo fmt --all -- --check 52 | 53 | - name: Setup Linux dependencies 54 | if: runner.os == 'Linux' && matrix.setup != 'linux-aarch64' && matrix.setup != 'wasm' && matrix.setup != 'android' 55 | run: | 56 | sudo apt-get update && sudo apt-get install -y libasound2-dev pkg-config 57 | if [[ "${{ matrix.setup }}" == "linux-jack" ]]; then 58 | sudo apt-get install -y libjack-jackd2-dev 59 | fi 60 | 61 | - name: Setup WASM 62 | if: matrix.setup == 'wasm' 63 | run: rustup target add wasm32-unknown-unknown 64 | 65 | - name: Install Chrome for WASM tests 66 | if: matrix.setup == 'wasm' 67 | uses: browser-actions/setup-chrome@v2 68 | with: 69 | chrome-version: stable 70 | install-dependencies: true 71 | 72 | - name: Install wasm-pack 73 | if: matrix.setup == 'wasm' 74 | run: curl -sSf https://rustwasm.github.io/wasm-pack/installer/init.sh | sh -s -- -f 75 | 76 | - name: Setup Linux aarch64 (cross-compile) 77 | if: matrix.setup == 'linux-aarch64' 78 | run: | 79 | set -euxo pipefail 80 | rustup target add aarch64-unknown-linux-gnu 81 | 82 | sudo dpkg --add-architecture arm64 83 | cat <<'EOF' | sudo tee /etc/apt/sources.list.d/ubuntu-arm64.sources 84 | Types: deb 85 | URIs: http://ports.ubuntu.com/ubuntu-ports 86 | Suites: noble noble-updates noble-backports noble-security 87 | Components: main restricted universe multiverse 88 | Architectures: arm64 89 | EOF 90 | sudo sed -Ei 's|^Types: deb|Types: deb\nArchitectures: amd64|' \ 91 | /etc/apt/sources.list.d/ubuntu.sources 92 | 93 | sudo apt-get update 94 | sudo apt-get install -y \ 95 | gcc-aarch64-linux-gnu \ 96 | libc6-dev-arm64-cross \ 97 | pkg-config \ 98 | libasound2-dev:arm64 \ 99 | libjack-jackd2-dev:arm64 100 | 101 | echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV 102 | echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV 103 | echo "PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig" >> $GITHUB_ENV 104 | 105 | - name: Setup Windows MSVC toolchain 106 | if: matrix.setup == 'windows-msvc' 107 | run: | 108 | rustup update stable 109 | if [ -n "${{ matrix.target }}" ]; then rustup target add ${{ matrix.target }}; fi 110 | shell: bash 111 | 112 | - name: Setup Windows GNU toolchain 113 | if: matrix.setup == 'windows-gnu' 114 | uses: msys2/setup-msys2@v2 115 | with: 116 | install: >- 117 | base-devel 118 | mingw-w64-i686-toolchain 119 | msystem: MINGW32 120 | update: true 121 | 122 | - name: Setup macOS JACK 123 | if: matrix.setup == 'macos-jack' 124 | run: | 125 | brew install jack 126 | echo "PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig:/usr/local/lib/pkgconfig" >> $GITHUB_ENV 127 | 128 | - name: Start JACK (macOS) 129 | if: matrix.setup == 'macos-jack' 130 | run: | 131 | jackd -d dummy >/tmp/jack.log 2>&1 & disown 132 | sleep 2 133 | 134 | - name: Setup iOS (Mac Catalyst) 135 | if: matrix.setup == 'ios-macabi' 136 | run: rustup target add aarch64-apple-ios-macabi 137 | 138 | - name: Setup Android NDK 139 | if: matrix.setup == 'android' 140 | uses: nttld/setup-ndk@v1 141 | with: 142 | ndk-version: r26d 143 | local-cache: true 144 | 145 | - name: Export ANDROID_NDK_HOME for cargo-ndk 146 | if: matrix.setup == 'android' 147 | run: echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME:-$NDK_HOME}" >> $GITHUB_ENV 148 | 149 | - name: Install cargo-ndk and Android targets 150 | if: matrix.setup == 'android' 151 | run: | 152 | cargo install cargo-ndk --locked 153 | rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android 154 | 155 | - name: Build (standard) 156 | if: matrix.setup != 'windows-gnu' && matrix.setup != 'android' && matrix.setup != 'wasm' 157 | run: | 158 | if [ -n "${{ matrix.target }}" ]; then 159 | cargo build --verbose --target ${{ matrix.target }} ${{ matrix.features && format('--features {0}', matrix.features) }} 160 | else 161 | cargo build --verbose ${{ matrix.features && format('--features {0}', matrix.features) }} 162 | fi 163 | shell: bash 164 | 165 | - name: Build (Windows GNU) 166 | if: matrix.setup == 'windows-gnu' 167 | shell: msys2 {0} 168 | run: | 169 | export PATH="$(cygpath -u "$USERPROFILE")/.cargo/bin:$PATH" 170 | rustup update stable 171 | rustup target add ${{ matrix.target }} 172 | export CARGO_TARGET_I686_PC_WINDOWS_GNU_LINKER=i686-w64-mingw32-gcc 173 | export CARGO_TARGET_I686_PC_WINDOWS_GNU_AR=i686-w64-mingw32-ar 174 | cargo build --verbose --target ${{ matrix.target }} ${{ matrix.features && format('--features {0}', matrix.features) }} 175 | 176 | - name: Build (Android via cargo-ndk, API 29) 177 | if: matrix.setup == 'android' 178 | run: | 179 | cargo ndk \ 180 | --platform 29 \ 181 | --target aarch64-linux-android \ 182 | --target armv7-linux-androideabi \ 183 | --target x86_64-linux-android \ 184 | --target i686-linux-android \ 185 | build --verbose 186 | 187 | - name: Build WASM 188 | if: matrix.setup == 'wasm' 189 | run: cargo build --verbose --target wasm32-unknown-unknown ${{ matrix.features && format('--features {0}', matrix.features) }} 190 | 191 | - name: Build WASM example (browser) 192 | if: matrix.setup == 'wasm' && hashFiles('examples/browser/Cargo.toml') != '' 193 | run: | 194 | cd examples/browser 195 | wasm-pack build --target=no-modules --dev 196 | 197 | - name: Test WASM 198 | if: matrix.setup == 'wasm' 199 | run: wasm-pack test --chrome --headless 200 | 201 | - name: Start JACK (Linux) 202 | if: runner.os == 'Linux' && matrix.setup == 'linux-jack' 203 | run: | 204 | sudo apt-get update 205 | sudo apt-get install -y jackd2 206 | jackd -d dummy >/tmp/jack.log 2>&1 & disown 207 | sleep 2 208 | shell: bash 209 | 210 | - name: Test (Linux JACK) 211 | if: runner.os == 'Linux' && matrix.setup == 'linux-jack' 212 | run: cargo test --verbose ${{ matrix.features && format('--features {0}', matrix.features) }} 213 | shell: bash 214 | 215 | - name: Test (Linux ALSA with gating) 216 | if: runner.os == 'Linux' && matrix.setup == 'linux-alsa' 217 | run: | 218 | FEATURES='${{ matrix.features && format('--features {0}', matrix.features) }}' 219 | if [ -e /dev/snd/seq ]; then 220 | echo "ALSA sequencer present; running full tests" 221 | cargo test --verbose ${FEATURES} 222 | else 223 | echo "ALSA sequencer not available; skipping end_to_end" 224 | cargo test --verbose ${FEATURES} -- --skip end_to_end 225 | fi 226 | shell: bash 227 | 228 | - name: Test (non-Linux hosted targets) 229 | if: > 230 | runner.os != 'Linux' && 231 | (matrix.target == '' || 232 | matrix.target == 'x86_64-pc-windows-msvc' || 233 | startsWith(matrix.name, 'macOS')) && 234 | matrix.setup != 'windows-gnu' 235 | run: cargo test --verbose ${{ matrix.features && format('--features {0}', matrix.features) }} 236 | 237 | - name: Test (Windows GNU) 238 | if: matrix.setup == 'windows-gnu' 239 | shell: msys2 {0} 240 | run: | 241 | export PATH="$(cygpath -u "$USERPROFILE")/.cargo/bin:$PATH" 242 | export CARGO_TARGET_I686_PC_WINDOWS_GNU_LINKER=i686-w64-mingw32-gcc 243 | export CARGO_TARGET_I686_PC_WINDOWS_GNU_AR=i686-w64-mingw32-ar 244 | cargo test --verbose --target ${{ matrix.target }} ${{ matrix.features && format('--features {0}', matrix.features) }} 245 | -------------------------------------------------------------------------------- /src/backend/winrt/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use crate::errors::*; 4 | use crate::Ignore; 5 | 6 | use windows::core::HSTRING; 7 | 8 | use windows::{ 9 | Devices::Enumeration::DeviceInformation, 10 | Devices::Midi::*, 11 | Foundation::TypedEventHandler, 12 | Storage::Streams::{DataReader, DataWriter}, 13 | }; 14 | 15 | #[derive(Clone, PartialEq)] 16 | pub struct MidiInputPort { 17 | id: HSTRING, 18 | } 19 | 20 | impl MidiInputPort { 21 | pub fn id(&self) -> String { 22 | self.id.to_string_lossy() 23 | } 24 | } 25 | 26 | pub struct MidiInput { 27 | selector: HSTRING, 28 | ignore_flags: Ignore, 29 | } 30 | 31 | impl MidiInput { 32 | pub fn new(_client_name: &str) -> Result { 33 | let device_selector = MidiInPort::GetDeviceSelector().map_err(|_| InitError)?; 34 | Ok(MidiInput { 35 | selector: device_selector, 36 | ignore_flags: Ignore::None, 37 | }) 38 | } 39 | 40 | pub fn ignore(&mut self, flags: Ignore) { 41 | self.ignore_flags = flags; 42 | } 43 | 44 | pub(crate) fn ports_internal(&self) -> Vec { 45 | let device_collection = DeviceInformation::FindAllAsyncAqsFilter(&self.selector) 46 | .unwrap() 47 | .join() 48 | .expect("FindAllAsyncAqsFilter failed"); 49 | let count = device_collection.Size().expect("Size failed") as usize; 50 | let mut result = Vec::with_capacity(count); 51 | for device_info in device_collection.into_iter() { 52 | let device_id = device_info.Id().expect("Id failed"); 53 | result.push(crate::common::MidiInputPort { 54 | imp: MidiInputPort { id: device_id }, 55 | }); 56 | } 57 | result 58 | } 59 | 60 | pub fn port_count(&self) -> usize { 61 | let device_collection = DeviceInformation::FindAllAsyncAqsFilter(&self.selector) 62 | .unwrap() 63 | .join() 64 | .expect("FindAllAsyncAqsFilter failed"); 65 | device_collection.Size().expect("Size failed") as usize 66 | } 67 | 68 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 69 | let device_info_async = DeviceInformation::CreateFromIdAsync(&port.id) 70 | .map_err(|_| PortInfoError::InvalidPort)?; 71 | let device_info = device_info_async 72 | .join() 73 | .map_err(|_| PortInfoError::InvalidPort)?; 74 | let device_name = device_info 75 | .Name() 76 | .map_err(|_| PortInfoError::CannotRetrievePortName)?; 77 | Ok(device_name.to_string()) 78 | } 79 | 80 | fn handle_input(args: &MidiMessageReceivedEventArgs, handler_data: &mut HandlerData) { 81 | let ignore = handler_data.ignore_flags; 82 | let data = &mut handler_data.user_data.as_mut().unwrap(); 83 | let message = args.Message().expect("Message failed"); 84 | let timestamp = message.Timestamp().expect("Timestamp failed").Duration as u64 / 10; 85 | let buffer = message.RawData().expect("RawData failed"); 86 | let length = buffer.Length().expect("Length failed") as usize; 87 | let data_reader = DataReader::FromBuffer(&buffer).expect("FromBuffer failed"); 88 | let mut message_bytes = vec![0; length]; 89 | data_reader 90 | .ReadBytes(&mut message_bytes) 91 | .expect("ReadBytes failed"); 92 | 93 | // The first byte in the message is the status 94 | let status = message_bytes[0]; 95 | 96 | if !(status == 0xF0 && ignore.contains(Ignore::Sysex) 97 | || status == 0xF1 && ignore.contains(Ignore::Time) 98 | || status == 0xF8 && ignore.contains(Ignore::Time) 99 | || status == 0xFE && ignore.contains(Ignore::ActiveSense)) 100 | { 101 | (handler_data.callback)(timestamp, &message_bytes, data); 102 | } 103 | } 104 | 105 | pub fn connect( 106 | self, 107 | port: &MidiInputPort, 108 | _port_name: &str, 109 | callback: F, 110 | data: T, 111 | ) -> Result, ConnectError> 112 | where 113 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 114 | { 115 | let in_port = match MidiInPort::FromIdAsync(&port.id) { 116 | Ok(port_async) => match port_async.join() { 117 | Ok(port) => port, 118 | _ => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 119 | }, 120 | Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 121 | }; 122 | 123 | let handler_data = Arc::new(Mutex::new(HandlerData { 124 | ignore_flags: self.ignore_flags, 125 | callback: Box::new(callback), 126 | user_data: Some(data), 127 | })); 128 | let handler_data2 = handler_data.clone(); 129 | 130 | type Handler = TypedEventHandler; 131 | let handler = Handler::new( 132 | move |_sender, args: windows::core::Ref<'_, MidiMessageReceivedEventArgs>| { 133 | MidiInput::handle_input( 134 | args.as_ref() 135 | .expect("MidiMessageReceivedEventArgs were null"), 136 | &mut *handler_data2.lock().unwrap(), 137 | ); 138 | Ok(()) 139 | }, 140 | ); 141 | let event_token = in_port 142 | .MessageReceived(&handler) 143 | .expect("MessageReceived failed"); 144 | 145 | Ok(MidiInputConnection { 146 | port: RtMidiInPort(in_port), 147 | event_token, 148 | handler_data, 149 | }) 150 | } 151 | } 152 | 153 | struct RtMidiInPort(MidiInPort); 154 | unsafe impl Send for RtMidiInPort {} 155 | 156 | pub struct MidiInputConnection { 157 | port: RtMidiInPort, 158 | event_token: i64, 159 | // TODO: get rid of Arc & Mutex? 160 | // synchronization is required because the borrow checker does not 161 | // know that the callback we're in here is never called concurrently 162 | // (always in sequence) 163 | handler_data: Arc>>, 164 | } 165 | 166 | impl MidiInputConnection { 167 | pub fn close(self) -> (MidiInput, T) { 168 | let _ = self.port.0.RemoveMessageReceived(self.event_token); 169 | self.port.0.Close().expect("failed to close MidiInput"); 170 | let device_selector = MidiInPort::GetDeviceSelector().expect("GetDeviceSelector failed"); // probably won't ever fail here, because it worked previously 171 | let mut handler_data_locked = self.handler_data.lock().unwrap(); 172 | ( 173 | MidiInput { 174 | selector: device_selector, 175 | ignore_flags: handler_data_locked.ignore_flags, 176 | }, 177 | handler_data_locked.user_data.take().unwrap(), 178 | ) 179 | } 180 | } 181 | 182 | /// This is all the data that is stored on the heap as long as a connection 183 | /// is opened and passed to the callback handler. 184 | /// 185 | /// It is important that `user_data` is the last field to not influence 186 | /// offsets after monomorphization. 187 | struct HandlerData { 188 | ignore_flags: Ignore, 189 | callback: Box, 190 | user_data: Option, 191 | } 192 | 193 | #[derive(Clone, PartialEq)] 194 | pub struct MidiOutputPort { 195 | id: HSTRING, 196 | } 197 | 198 | impl MidiOutputPort { 199 | pub fn id(&self) -> String { 200 | self.id.to_string_lossy() 201 | } 202 | } 203 | 204 | pub struct MidiOutput { 205 | selector: HSTRING, 206 | } 207 | 208 | impl MidiOutput { 209 | pub fn new(_client_name: &str) -> Result { 210 | let device_selector = MidiOutPort::GetDeviceSelector().map_err(|_| InitError)?; 211 | Ok(MidiOutput { 212 | selector: device_selector, 213 | }) 214 | } 215 | 216 | pub(crate) fn ports_internal(&self) -> Vec { 217 | let device_collection = DeviceInformation::FindAllAsyncAqsFilter(&self.selector) 218 | .unwrap() 219 | .join() 220 | .expect("FindAllAsyncAqsFilter failed"); 221 | let count = device_collection.Size().expect("Size failed") as usize; 222 | let mut result = Vec::with_capacity(count); 223 | for device_info in device_collection.into_iter() { 224 | let device_id = device_info.Id().expect("Id failed"); 225 | result.push(crate::common::MidiOutputPort { 226 | imp: MidiOutputPort { id: device_id }, 227 | }); 228 | } 229 | result 230 | } 231 | 232 | pub fn port_count(&self) -> usize { 233 | let device_collection = DeviceInformation::FindAllAsyncAqsFilter(&self.selector) 234 | .unwrap() 235 | .join() 236 | .expect("FindAllAsyncAqsFilter failed"); 237 | device_collection.Size().expect("Size failed") as usize 238 | } 239 | 240 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 241 | let device_info_async = DeviceInformation::CreateFromIdAsync(&port.id) 242 | .map_err(|_| PortInfoError::InvalidPort)?; 243 | let device_info = device_info_async 244 | .join() 245 | .map_err(|_| PortInfoError::InvalidPort)?; 246 | let device_name = device_info 247 | .Name() 248 | .map_err(|_| PortInfoError::CannotRetrievePortName)?; 249 | Ok(device_name.to_string()) 250 | } 251 | 252 | pub fn connect( 253 | self, 254 | port: &MidiOutputPort, 255 | _port_name: &str, 256 | ) -> Result> { 257 | let out_port = match MidiOutPort::FromIdAsync(&port.id) { 258 | Ok(port_async) => match port_async.join() { 259 | Ok(port) => port, 260 | _ => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 261 | }, 262 | Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 263 | }; 264 | Ok(MidiOutputConnection { port: out_port }) 265 | } 266 | } 267 | 268 | pub struct MidiOutputConnection { 269 | port: IMidiOutPort, 270 | } 271 | 272 | unsafe impl Send for MidiOutputConnection {} 273 | 274 | impl MidiOutputConnection { 275 | pub fn close(self) -> MidiOutput { 276 | self.port.Close().expect("failed to close MidiOutput"); 277 | let device_selector = MidiOutPort::GetDeviceSelector().expect("GetDeviceSelector failed"); // probably won't ever fail here, because it worked previously 278 | MidiOutput { 279 | selector: device_selector, 280 | } 281 | } 282 | 283 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 284 | let data_writer = DataWriter::new().unwrap(); 285 | data_writer 286 | .WriteBytes(message) 287 | .map_err(|_| SendError::Other("WriteBytes failed"))?; 288 | let buffer = data_writer 289 | .DetachBuffer() 290 | .map_err(|_| SendError::Other("DetachBuffer failed"))?; 291 | self.port 292 | .SendBuffer(&buffer) 293 | .map_err(|_| SendError::Other("SendBuffer failed"))?; 294 | Ok(()) 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | use backend::{ 4 | MidiInput as MidiInputImpl, MidiInputConnection as MidiInputConnectionImpl, 5 | MidiInputPort as MidiInputPortImpl, MidiOutput as MidiOutputImpl, 6 | MidiOutputConnection as MidiOutputConnectionImpl, MidiOutputPort as MidiOutputPortImpl, 7 | }; 8 | use errors::*; 9 | 10 | use crate::{backend, errors, Ignore, InitError}; 11 | 12 | /// Trait that abstracts over input and output ports. 13 | pub trait MidiIO { 14 | /// Type of an input or output port structure. 15 | type Port: Clone; 16 | 17 | /// Get a collection of all MIDI input or output ports. 18 | /// The resulting vector contains one object per port, which you can use to 19 | /// query metadata about the port or connect to it. 20 | fn ports(&self) -> Vec; 21 | 22 | /// Get the number of available MIDI input or output ports. 23 | fn port_count(&self) -> usize; 24 | 25 | /// Get the name of a specified MIDI input or output port. 26 | /// 27 | /// An error will be returned when the port is no longer valid 28 | /// (e.g. the respective device has been disconnected). 29 | fn port_name(&self, port: &Self::Port) -> Result; 30 | } 31 | 32 | /// An object representing a single input port. 33 | /// How the port is identified internally is backend-dependent. 34 | /// If the backend allows it, port objects remain valid when 35 | /// other ports in the system change (i.e. it is not just an index). 36 | /// 37 | /// Use the `ports` method of a `MidiInput` instance to obtain 38 | /// available ports. 39 | #[derive(Clone, PartialEq)] 40 | pub struct MidiInputPort { 41 | pub(crate) imp: MidiInputPortImpl, 42 | } 43 | 44 | impl MidiInputPort { 45 | /// Get a unique stable identifier for this port. 46 | /// This identifier must be treated as an opaque string. 47 | pub fn id(&self) -> String { 48 | self.imp.id() 49 | } 50 | } 51 | 52 | /// A collection of input ports. 53 | pub type MidiInputPorts = Vec; 54 | 55 | /// An instance of `MidiInput` is required for anything related to MIDI input. 56 | /// Create one with `MidiInput::new`. 57 | pub struct MidiInput { 58 | //ignore_flags: Ignore 59 | imp: MidiInputImpl, 60 | } 61 | 62 | impl MidiInput { 63 | /// Creates a new `MidiInput` object that is required for any MIDI input functionality. 64 | pub fn new(client_name: &str) -> Result { 65 | MidiInputImpl::new(client_name).map(|imp| MidiInput { imp }) 66 | } 67 | 68 | /// Set flags to decide what kind of messages should be ignored (i.e., filtered out) 69 | /// by this `MidiInput`. By default, no messages are ignored. 70 | pub fn ignore(&mut self, flags: Ignore) { 71 | self.imp.ignore(flags); 72 | } 73 | 74 | /// Get a collection of all MIDI input ports that *midir* can connect to. 75 | /// The resulting vector contains one object per port, which you can use to 76 | /// query metadata about the port or connect to it in order to receive 77 | /// MIDI messages. 78 | pub fn ports(&self) -> MidiInputPorts { 79 | self.imp.ports_internal() 80 | } 81 | 82 | /// Get the number of available MIDI input ports that *midir* can connect to. 83 | pub fn port_count(&self) -> usize { 84 | self.imp.port_count() 85 | } 86 | 87 | /// Get the name of a specified MIDI input port. 88 | /// 89 | /// An error will be returned when the port is no longer valid 90 | /// (e.g. the respective device has been disconnected). 91 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 92 | self.imp.port_name(&port.imp) 93 | } 94 | 95 | /// Get a MIDI input port by its unique identifier. 96 | pub fn find_port_by_id(&self, id: &str) -> Option { 97 | self.ports().into_iter().find(|port| port.id() == id) 98 | } 99 | 100 | /// Connect to a specified MIDI input port in order to receive messages. 101 | /// For each incoming MIDI message, the provided `callback` function will 102 | /// be called. The first parameter of the callback function is a timestamp 103 | /// (in microseconds) designating the time since some unspecified point in 104 | /// the past (which will not change during the lifetime of a 105 | /// `MidiInputConnection`). The second parameter contains the actual bytes 106 | /// of the MIDI message. 107 | /// 108 | /// Additional data that should be passed whenever the callback is 109 | /// invoked can be specified by `data`. Use the empty tuple `()` if 110 | /// you do not want to pass any additional data. 111 | /// 112 | /// The connection will be kept open as long as the returned 113 | /// `MidiInputConnection` is kept alive. 114 | /// 115 | /// The `port_name` is an additional name that will be assigned to the 116 | /// connection. It is only used by some backends. 117 | /// 118 | /// An error will be returned when the port is no longer valid 119 | /// (e.g. the respective device has been disconnected). 120 | pub fn connect( 121 | self, 122 | port: &MidiInputPort, 123 | port_name: &str, 124 | callback: F, 125 | data: T, 126 | ) -> Result, ConnectError> 127 | where 128 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 129 | { 130 | match self.imp.connect(&port.imp, port_name, callback, data) { 131 | Ok(imp) => Ok(MidiInputConnection { imp }), 132 | Err(imp) => { 133 | let kind = imp.kind(); 134 | Err(ConnectError::new( 135 | kind, 136 | MidiInput { 137 | imp: imp.into_inner(), 138 | }, 139 | )) 140 | } 141 | } 142 | } 143 | } 144 | 145 | impl MidiIO for MidiInput { 146 | type Port = MidiInputPort; 147 | 148 | fn ports(&self) -> MidiInputPorts { 149 | self.imp.ports_internal() 150 | } 151 | 152 | fn port_count(&self) -> usize { 153 | self.imp.port_count() 154 | } 155 | 156 | fn port_name(&self, port: &MidiInputPort) -> Result { 157 | self.imp.port_name(&port.imp) 158 | } 159 | } 160 | 161 | #[cfg(unix)] 162 | impl crate::os::unix::VirtualInput for MidiInput { 163 | fn create_virtual( 164 | self, 165 | port_name: &str, 166 | callback: F, 167 | data: T, 168 | ) -> Result, ConnectError> 169 | where 170 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 171 | { 172 | match self.imp.create_virtual(port_name, callback, data) { 173 | Ok(imp) => Ok(MidiInputConnection { imp }), 174 | Err(imp) => { 175 | let kind = imp.kind(); 176 | Err(ConnectError::new( 177 | kind, 178 | MidiInput { 179 | imp: imp.into_inner(), 180 | }, 181 | )) 182 | } 183 | } 184 | } 185 | } 186 | 187 | /// Represents an open connection to a MIDI input port. 188 | pub struct MidiInputConnection { 189 | imp: MidiInputConnectionImpl, 190 | } 191 | 192 | impl MidiInputConnection { 193 | /// Closes the connection. The returned values allow you to 194 | /// inspect the additional data passed to the callback (the `data` 195 | /// parameter of `connect`), or to reuse the `MidiInput` object, 196 | /// but they can be safely ignored. 197 | pub fn close(self) -> (MidiInput, T) { 198 | let (imp, data) = self.imp.close(); 199 | (MidiInput { imp }, data) 200 | } 201 | } 202 | 203 | /// An object representing a single output port. 204 | /// How the port is identified internally is backend-dependent. 205 | /// If the backend allows it, port objects remain valid when 206 | /// other ports in the system change (i.e. it is not just an index). 207 | /// 208 | /// Use the `ports` method of a `MidiOutput` instance to obtain 209 | /// available ports. 210 | #[derive(Clone, PartialEq)] 211 | pub struct MidiOutputPort { 212 | pub(crate) imp: MidiOutputPortImpl, 213 | } 214 | 215 | impl MidiOutputPort { 216 | /// Get a unique stable identifier for this port. 217 | /// This identifier must be treated as an opaque string. 218 | pub fn id(&self) -> String { 219 | self.imp.id() 220 | } 221 | } 222 | 223 | /// A collection of output ports. 224 | pub type MidiOutputPorts = Vec; 225 | 226 | /// An instance of `MidiOutput` is required for anything related to MIDI output. 227 | /// Create one with `MidiOutput::new`. 228 | pub struct MidiOutput { 229 | imp: MidiOutputImpl, 230 | } 231 | 232 | impl MidiOutput { 233 | /// Creates a new `MidiOutput` object that is required for any MIDI output functionality. 234 | pub fn new(client_name: &str) -> Result { 235 | MidiOutputImpl::new(client_name).map(|imp| MidiOutput { imp }) 236 | } 237 | 238 | /// Get a collection of all MIDI output ports that *midir* can connect to. 239 | /// The resulting vector contains one object per port, which you can use to 240 | /// query metadata about the port or connect to it in order to send 241 | /// MIDI messages. 242 | pub fn ports(&self) -> MidiOutputPorts { 243 | self.imp.ports_internal() 244 | } 245 | 246 | /// Get the number of available MIDI output ports that *midir* can connect to. 247 | pub fn port_count(&self) -> usize { 248 | self.imp.port_count() 249 | } 250 | 251 | /// Get the name of a specified MIDI output port. 252 | /// 253 | /// An error will be returned when the port is no longer valid 254 | /// (e.g. the respective device has been disconnected). 255 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 256 | self.imp.port_name(&port.imp) 257 | } 258 | 259 | /// Get a MIDI output port by its unique identifier. 260 | pub fn find_port_by_id(&self, id: &str) -> Option { 261 | self.ports().into_iter().find(|port| port.id() == id) 262 | } 263 | 264 | /// Connect to a specified MIDI output port in order to send messages. 265 | /// The connection will be kept open as long as the returned 266 | /// `MidiOutputConnection` is kept alive. 267 | /// 268 | /// The `port_name` is an additional name that will be assigned to the 269 | /// connection. It is only used by some backends. 270 | /// 271 | /// An error will be returned when the port is no longer valid 272 | /// (e.g. the respective device has been disconnected). 273 | pub fn connect( 274 | self, 275 | port: &MidiOutputPort, 276 | port_name: &str, 277 | ) -> Result> { 278 | match self.imp.connect(&port.imp, port_name) { 279 | Ok(imp) => Ok(MidiOutputConnection { imp }), 280 | Err(imp) => { 281 | let kind = imp.kind(); 282 | Err(ConnectError::new( 283 | kind, 284 | MidiOutput { 285 | imp: imp.into_inner(), 286 | }, 287 | )) 288 | } 289 | } 290 | } 291 | } 292 | 293 | impl MidiIO for MidiOutput { 294 | type Port = MidiOutputPort; 295 | 296 | fn ports(&self) -> MidiOutputPorts { 297 | self.imp.ports_internal() 298 | } 299 | 300 | fn port_count(&self) -> usize { 301 | self.imp.port_count() 302 | } 303 | 304 | fn port_name(&self, port: &MidiOutputPort) -> Result { 305 | self.imp.port_name(&port.imp) 306 | } 307 | } 308 | 309 | #[cfg(unix)] 310 | impl crate::os::unix::VirtualOutput for MidiOutput { 311 | fn create_virtual( 312 | self, 313 | port_name: &str, 314 | ) -> Result> { 315 | match self.imp.create_virtual(port_name) { 316 | Ok(imp) => Ok(MidiOutputConnection { imp }), 317 | Err(imp) => { 318 | let kind = imp.kind(); 319 | Err(ConnectError::new( 320 | kind, 321 | MidiOutput { 322 | imp: imp.into_inner(), 323 | }, 324 | )) 325 | } 326 | } 327 | } 328 | } 329 | 330 | /// Represents an open connection to a MIDI output port. 331 | pub struct MidiOutputConnection { 332 | imp: MidiOutputConnectionImpl, 333 | } 334 | 335 | impl MidiOutputConnection { 336 | /// Closes the connection. The returned value allows you to 337 | /// reuse the `MidiOutput` object, but it can be safely ignored. 338 | pub fn close(self) -> MidiOutput { 339 | MidiOutput { 340 | imp: self.imp.close(), 341 | } 342 | } 343 | 344 | /// Send a message to the port that this output connection is connected to. 345 | /// The message must be a valid MIDI message (see https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message). 346 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 347 | self.imp.send(message) 348 | } 349 | } 350 | 351 | #[cfg(test)] 352 | mod tests { 353 | use super::*; 354 | 355 | #[test] 356 | fn test_trait_impls() { 357 | // make sure that all the structs implement `Send` 358 | fn is_send() {} 359 | is_send::(); 360 | is_send::(); 361 | #[cfg(not(target_arch = "wasm32"))] 362 | { 363 | // The story around threading and `Send` on WASM is not clear yet 364 | // Tracking issue: https://github.com/Boddlnagg/midir/issues/49 365 | // Prev. discussion: https://github.com/Boddlnagg/midir/pull/47 366 | is_send::(); 367 | is_send::>(); 368 | is_send::(); 369 | is_send::(); 370 | } 371 | 372 | // make sure that Midi port structs implement `PartialEq` 373 | fn is_partial_eq() {} 374 | is_partial_eq::(); 375 | is_partial_eq::(); 376 | 377 | is_partial_eq::(); 378 | is_partial_eq::(); 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/backend/jack/mod.rs: -------------------------------------------------------------------------------- 1 | use jack_sys::jack_nframes_t; 2 | use libc::c_void; 3 | 4 | use std::ffi::CString; 5 | use std::{mem, slice}; 6 | 7 | mod wrappers; 8 | use self::wrappers::*; 9 | 10 | use crate::errors::*; 11 | use crate::{Ignore, MidiMessage}; 12 | 13 | const OUTPUT_RINGBUFFER_SIZE: usize = 16384; 14 | 15 | type CallbackFn = dyn FnMut(u64, &[u8], &mut T) + Send; 16 | 17 | struct InputHandlerData { 18 | port: Option, 19 | ignore_flags: Ignore, 20 | callback: Box>, 21 | user_data: Option, 22 | } 23 | 24 | pub struct MidiInput { 25 | ignore_flags: Ignore, 26 | client: Option, 27 | } 28 | 29 | #[derive(Clone, PartialEq)] 30 | pub struct MidiInputPort { 31 | name: CString, 32 | } 33 | 34 | impl MidiInputPort { 35 | pub fn id(&self) -> String { 36 | self.name.to_string_lossy().to_string() 37 | } 38 | } 39 | 40 | pub struct MidiInputConnection { 41 | handler_data: Box>, 42 | client: Option, 43 | } 44 | 45 | impl MidiInput { 46 | pub fn new(client_name: &str) -> Result { 47 | let client = match Client::open(client_name, JackOpenOptions::NoStartServer) { 48 | Ok(c) => c, 49 | Err(_) => { 50 | return Err(InitError); 51 | } // TODO: maybe add message that Jack server might not be running 52 | }; 53 | 54 | Ok(MidiInput { 55 | ignore_flags: Ignore::None, 56 | client: Some(client), 57 | }) 58 | } 59 | 60 | pub fn ignore(&mut self, flags: Ignore) { 61 | self.ignore_flags = flags; 62 | } 63 | 64 | pub(crate) fn ports_internal(&self) -> Vec { 65 | let ports = self 66 | .client 67 | .as_ref() 68 | .unwrap() 69 | .get_midi_ports(PortFlags::PortIsOutput); 70 | let mut result = Vec::with_capacity(ports.count()); 71 | for i in 0..ports.count() { 72 | result.push(crate::common::MidiInputPort { 73 | imp: MidiInputPort { 74 | name: ports.get_c_name(i).into(), 75 | }, 76 | }) 77 | } 78 | result 79 | } 80 | 81 | pub fn port_count(&self) -> usize { 82 | self.client 83 | .as_ref() 84 | .unwrap() 85 | .get_midi_ports(PortFlags::PortIsOutput) 86 | .count() 87 | } 88 | 89 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 90 | Ok(port.name.to_string_lossy().into()) 91 | } 92 | 93 | fn activate_callback(&mut self, callback: F, data: T) -> Box> 94 | where 95 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 96 | { 97 | let handler_data = Box::new(InputHandlerData { 98 | port: None, 99 | ignore_flags: self.ignore_flags, 100 | callback: Box::new(callback), 101 | user_data: Some(data), 102 | }); 103 | 104 | let data_ptr = unsafe { mem::transmute_copy::<_, *mut InputHandlerData>(&handler_data) }; 105 | 106 | self.client 107 | .as_mut() 108 | .unwrap() 109 | .set_process_callback(handle_input::, data_ptr as *mut c_void); 110 | self.client.as_mut().unwrap().activate(); 111 | handler_data 112 | } 113 | 114 | pub fn connect( 115 | mut self, 116 | port: &MidiInputPort, 117 | port_name: &str, 118 | callback: F, 119 | data: T, 120 | ) -> Result, ConnectError> 121 | where 122 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 123 | { 124 | let mut handler_data = self.activate_callback(callback, data); 125 | 126 | // Create port ... 127 | let dest_port = match self 128 | .client 129 | .as_mut() 130 | .unwrap() 131 | .register_midi_port(port_name, PortFlags::PortIsInput) 132 | { 133 | Ok(p) => p, 134 | Err(()) => { 135 | return Err(ConnectError::other("could not register JACK port", self)); 136 | } 137 | }; 138 | 139 | // ... and connect it to the output 140 | if self 141 | .client 142 | .as_mut() 143 | .unwrap() 144 | .connect(&port.name, dest_port.get_name()) 145 | .is_err() 146 | { 147 | return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)); 148 | } 149 | 150 | handler_data.port = Some(dest_port); 151 | 152 | Ok(MidiInputConnection { 153 | handler_data, 154 | client: self.client.take(), 155 | }) 156 | } 157 | 158 | pub fn create_virtual( 159 | mut self, 160 | port_name: &str, 161 | callback: F, 162 | data: T, 163 | ) -> Result, ConnectError> 164 | where 165 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 166 | { 167 | let mut handler_data = self.activate_callback(callback, data); 168 | 169 | // Create port 170 | let port = match self 171 | .client 172 | .as_mut() 173 | .unwrap() 174 | .register_midi_port(port_name, PortFlags::PortIsInput) 175 | { 176 | Ok(p) => p, 177 | Err(()) => { 178 | return Err(ConnectError::other("could not register JACK port", self)); 179 | } 180 | }; 181 | 182 | handler_data.port = Some(port); 183 | 184 | Ok(MidiInputConnection { 185 | handler_data, 186 | client: self.client.take(), 187 | }) 188 | } 189 | } 190 | 191 | impl MidiInputConnection { 192 | pub fn close(mut self) -> (MidiInput, T) { 193 | self.close_internal(); 194 | 195 | ( 196 | MidiInput { 197 | client: self.client.take(), 198 | ignore_flags: self.handler_data.ignore_flags, 199 | }, 200 | self.handler_data.user_data.take().unwrap(), 201 | ) 202 | } 203 | 204 | fn close_internal(&mut self) { 205 | let port = self.handler_data.port.take().unwrap(); 206 | self.client.as_mut().unwrap().unregister_midi_port(port); 207 | self.client.as_mut().unwrap().deactivate(); 208 | } 209 | } 210 | 211 | impl Drop for MidiInputConnection { 212 | fn drop(&mut self) { 213 | if self.client.is_some() { 214 | self.close_internal(); 215 | } 216 | } 217 | } 218 | 219 | extern "C" fn handle_input(nframes: jack_nframes_t, arg: *mut c_void) -> i32 { 220 | let data: &mut InputHandlerData = unsafe { &mut *(arg as *mut InputHandlerData) }; 221 | 222 | // Is port created? 223 | if let Some(ref port) = data.port { 224 | let buff = port.get_midi_buffer(nframes); 225 | 226 | let mut message = MidiMessage::new(); // TODO: create MidiMessage once and reuse its buffer for every handle_input call 227 | 228 | // We have midi events in buffer 229 | let evcount = buff.get_event_count(); 230 | let mut event = mem::MaybeUninit::uninit(); 231 | 232 | for j in 0..evcount { 233 | message.bytes.clear(); 234 | unsafe { buff.get_event(event.as_mut_ptr(), j) }; 235 | let event = unsafe { event.assume_init() }; 236 | 237 | for i in 0..event.size { 238 | message.bytes.push(unsafe { *event.buffer.add(i) }); 239 | } 240 | 241 | message.timestamp = Client::get_time(); // this is in microseconds 242 | (data.callback)( 243 | message.timestamp, 244 | &message.bytes, 245 | data.user_data.as_mut().unwrap(), 246 | ); 247 | } 248 | } 249 | 250 | 0 251 | } 252 | 253 | struct OutputHandlerData { 254 | port: Option, 255 | buff_size: Ringbuffer, 256 | buff_message: Ringbuffer, 257 | } 258 | 259 | pub struct MidiOutput { 260 | client: Option, 261 | } 262 | 263 | #[derive(Clone, PartialEq)] 264 | pub struct MidiOutputPort { 265 | name: CString, 266 | } 267 | 268 | impl MidiOutputPort { 269 | pub fn id(&self) -> String { 270 | self.name.to_string_lossy().to_string() 271 | } 272 | } 273 | 274 | pub struct MidiOutputConnection { 275 | handler_data: Box, 276 | client: Option, 277 | } 278 | 279 | impl MidiOutput { 280 | pub fn new(client_name: &str) -> Result { 281 | let client = match Client::open(client_name, JackOpenOptions::NoStartServer) { 282 | Ok(c) => c, 283 | Err(_) => { 284 | return Err(InitError); 285 | } // TODO: maybe add message that Jack server might not be running 286 | }; 287 | 288 | Ok(MidiOutput { 289 | client: Some(client), 290 | }) 291 | } 292 | 293 | pub(crate) fn ports_internal(&self) -> Vec { 294 | let ports = self 295 | .client 296 | .as_ref() 297 | .unwrap() 298 | .get_midi_ports(PortFlags::PortIsInput); 299 | let mut result = Vec::with_capacity(ports.count()); 300 | for i in 0..ports.count() { 301 | result.push(crate::common::MidiOutputPort { 302 | imp: MidiOutputPort { 303 | name: ports.get_c_name(i).into(), 304 | }, 305 | }) 306 | } 307 | result 308 | } 309 | 310 | pub fn port_count(&self) -> usize { 311 | self.client 312 | .as_ref() 313 | .unwrap() 314 | .get_midi_ports(PortFlags::PortIsInput) 315 | .count() 316 | } 317 | 318 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 319 | Ok(port.name.to_string_lossy().into()) 320 | } 321 | 322 | fn activate_callback(&mut self) -> Box { 323 | let handler_data = Box::new(OutputHandlerData { 324 | port: None, 325 | buff_size: Ringbuffer::new(OUTPUT_RINGBUFFER_SIZE), 326 | buff_message: Ringbuffer::new(OUTPUT_RINGBUFFER_SIZE), 327 | }); 328 | 329 | let data_ptr = unsafe { mem::transmute_copy::<_, *mut OutputHandlerData>(&handler_data) }; 330 | 331 | self.client 332 | .as_mut() 333 | .unwrap() 334 | .set_process_callback(handle_output, data_ptr as *mut c_void); 335 | self.client.as_mut().unwrap().activate(); 336 | handler_data 337 | } 338 | 339 | pub fn connect( 340 | mut self, 341 | port: &MidiOutputPort, 342 | port_name: &str, 343 | ) -> Result> { 344 | let mut handler_data = self.activate_callback(); 345 | 346 | // Create port ... 347 | let source_port = match self 348 | .client 349 | .as_mut() 350 | .unwrap() 351 | .register_midi_port(port_name, PortFlags::PortIsOutput) 352 | { 353 | Ok(p) => p, 354 | Err(()) => { 355 | return Err(ConnectError::other("could not register JACK port", self)); 356 | } 357 | }; 358 | 359 | // ... and connect it to the input 360 | if self 361 | .client 362 | .as_mut() 363 | .unwrap() 364 | .connect(source_port.get_name(), &port.name) 365 | .is_err() 366 | { 367 | return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)); 368 | } 369 | 370 | handler_data.port = Some(source_port); 371 | 372 | Ok(MidiOutputConnection { 373 | handler_data, 374 | client: self.client.take(), 375 | }) 376 | } 377 | 378 | pub fn create_virtual( 379 | mut self, 380 | port_name: &str, 381 | ) -> Result> { 382 | let mut handler_data = self.activate_callback(); 383 | 384 | // Create port 385 | let port = match self 386 | .client 387 | .as_mut() 388 | .unwrap() 389 | .register_midi_port(port_name, PortFlags::PortIsOutput) 390 | { 391 | Ok(p) => p, 392 | Err(()) => { 393 | return Err(ConnectError::other("could not register JACK port", self)); 394 | } 395 | }; 396 | 397 | handler_data.port = Some(port); 398 | 399 | Ok(MidiOutputConnection { 400 | handler_data, 401 | client: self.client.take(), 402 | }) 403 | } 404 | } 405 | 406 | impl MidiOutputConnection { 407 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 408 | let nbytes = message.len(); 409 | 410 | // Write full message to buffer 411 | let written = self.handler_data.buff_message.write(message); 412 | debug_assert!( 413 | written == nbytes, 414 | "not enough bytes written to ALSA ringbuffer `message`" 415 | ); 416 | let nbytes_slice = unsafe { 417 | slice::from_raw_parts( 418 | &nbytes as *const usize as *const u8, 419 | mem::size_of_val(&nbytes), 420 | ) 421 | }; 422 | let written = self.handler_data.buff_size.write(nbytes_slice); 423 | debug_assert!( 424 | written == mem::size_of_val(&nbytes), 425 | "not enough bytes written to ALSA ringbuffer `size`" 426 | ); 427 | Ok(()) 428 | } 429 | 430 | pub fn close(mut self) -> MidiOutput { 431 | self.close_internal(); 432 | 433 | MidiOutput { 434 | client: self.client.take(), 435 | } 436 | } 437 | 438 | fn close_internal(&mut self) { 439 | let port = self.handler_data.port.take().unwrap(); 440 | self.client.as_mut().unwrap().unregister_midi_port(port); 441 | self.client.as_mut().unwrap().deactivate(); 442 | } 443 | } 444 | 445 | impl Drop for MidiOutputConnection { 446 | fn drop(&mut self) { 447 | if self.client.is_some() { 448 | self.close_internal(); 449 | } 450 | } 451 | } 452 | 453 | extern "C" fn handle_output(nframes: jack_nframes_t, arg: *mut c_void) -> i32 { 454 | let data: &mut OutputHandlerData = unsafe { &mut *(arg as *mut OutputHandlerData) }; 455 | 456 | // Is port created? 457 | if let Some(ref port) = data.port { 458 | let mut space: usize = 0; 459 | 460 | let mut buff = port.get_midi_buffer(nframes); 461 | buff.clear(); 462 | 463 | while data.buff_size.get_read_space() > 0 { 464 | let read = data 465 | .buff_size 466 | .read(&mut space as *mut usize as *mut u8, mem::size_of::()); 467 | debug_assert!( 468 | read == mem::size_of::(), 469 | "not enough bytes read from `size` ringbuffer" 470 | ); 471 | let midi_data = buff.event_reserve(0, space); 472 | let read = data.buff_message.read(midi_data, space); 473 | debug_assert!( 474 | read == space, 475 | "not enough bytes read from `message` ringbuffer" 476 | ); 477 | } 478 | } 479 | 480 | 0 481 | } 482 | -------------------------------------------------------------------------------- /src/backend/coremidi/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use crate::errors::*; 4 | use crate::{Ignore, MidiMessage}; 5 | 6 | use coremidi::*; 7 | 8 | mod external { 9 | #[link(name = "CoreAudio", kind = "framework")] 10 | extern "C" { 11 | pub fn AudioConvertHostTimeToNanos(inHostTime: u64) -> u64; 12 | pub fn AudioGetCurrentHostTime() -> u64; 13 | } 14 | } 15 | 16 | pub struct MidiInput { 17 | client: Client, 18 | ignore_flags: Ignore, 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct MidiInputPort { 23 | source: Arc, 24 | } 25 | 26 | impl MidiInputPort { 27 | pub fn id(&self) -> String { 28 | self.source 29 | .unique_id() 30 | // According to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case 31 | .unwrap_or(0) 32 | .to_string() 33 | } 34 | } 35 | 36 | impl PartialEq for MidiInputPort { 37 | fn eq(&self, other: &Self) -> bool { 38 | if let (Some(id1), Some(id2)) = (self.source.unique_id(), other.source.unique_id()) { 39 | id1 == id2 40 | } else { 41 | // According to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case 42 | false 43 | } 44 | } 45 | } 46 | 47 | impl MidiInput { 48 | pub fn new(client_name: &str) -> Result { 49 | match Client::new(client_name) { 50 | Ok(cl) => Ok(MidiInput { 51 | client: cl, 52 | ignore_flags: Ignore::None, 53 | }), 54 | Err(_) => Err(InitError), 55 | } 56 | } 57 | 58 | pub(crate) fn ports_internal(&self) -> Vec { 59 | Sources 60 | .into_iter() 61 | .map(|s| crate::common::MidiInputPort { 62 | imp: MidiInputPort { 63 | source: Arc::new(s), 64 | }, 65 | }) 66 | .collect() 67 | } 68 | 69 | pub fn ignore(&mut self, flags: Ignore) { 70 | self.ignore_flags = flags; 71 | } 72 | 73 | pub fn port_count(&self) -> usize { 74 | Sources::count() 75 | } 76 | 77 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 78 | match port.source.display_name() { 79 | Some(name) => Ok(name), 80 | None => Err(PortInfoError::CannotRetrievePortName), 81 | } 82 | } 83 | 84 | fn handle_input(packets: &PacketList, handler_data: &mut HandlerData) { 85 | let continue_sysex = &mut handler_data.continue_sysex; 86 | let ignore = handler_data.ignore_flags; 87 | let message = &mut handler_data.message; 88 | let data = &mut handler_data.user_data.as_mut().unwrap(); 89 | for p in packets.iter() { 90 | let pdata = p.data(); 91 | if pdata.len() == 0 { 92 | continue; 93 | } 94 | 95 | let mut timestamp = p.timestamp(); 96 | 97 | if cfg!(not(target_os = "ios")) { 98 | if timestamp == 0 { 99 | // this might happen for asnychronous sysex messages (?) 100 | timestamp = unsafe { external::AudioGetCurrentHostTime() }; 101 | } 102 | 103 | if !*continue_sysex { 104 | message.timestamp = 105 | unsafe { external::AudioConvertHostTimeToNanos(timestamp) } as u64 / 1000; 106 | } 107 | } 108 | 109 | let mut cur_byte = 0; 110 | if *continue_sysex { 111 | // We have a continuing, segmented sysex message. 112 | if !ignore.contains(Ignore::Sysex) { 113 | // If we're not ignoring sysex messages, copy the entire packet. 114 | message.bytes.extend_from_slice(pdata); 115 | } 116 | *continue_sysex = pdata[pdata.len() - 1] != 0xF7; 117 | 118 | if !ignore.contains(Ignore::Sysex) && !*continue_sysex { 119 | // If we reached the end of the sysex, invoke the user callback 120 | (handler_data.callback)(message.timestamp, &message.bytes, data); 121 | message.bytes.clear(); 122 | } 123 | } else { 124 | while cur_byte < pdata.len() { 125 | // We are expecting that the next byte in the packet is a status byte. 126 | let status = pdata[cur_byte]; 127 | if status & 0x80 == 0 { 128 | break; 129 | } 130 | // Determine the number of bytes in the MIDI message. 131 | let size; 132 | if status < 0xC0 { 133 | size = 3; 134 | } else if status < 0xE0 { 135 | size = 2; 136 | } else if status < 0xF0 { 137 | size = 3; 138 | } else if status == 0xF0 { 139 | // A MIDI sysex 140 | if ignore.contains(Ignore::Sysex) { 141 | size = 0; 142 | cur_byte = pdata.len(); 143 | } else { 144 | size = pdata.len() - cur_byte; 145 | } 146 | *continue_sysex = pdata[pdata.len() - 1] != 0xF7; 147 | } else if status == 0xF1 { 148 | // A MIDI time code message 149 | if ignore.contains(Ignore::Time) { 150 | size = 0; 151 | cur_byte += 2; 152 | } else { 153 | size = 2; 154 | } 155 | } else if status == 0xF2 { 156 | size = 3; 157 | } else if status == 0xF3 { 158 | size = 2; 159 | } else if status == 0xF8 && ignore.contains(Ignore::Time) { 160 | // A MIDI timing tick message and we're ignoring it. 161 | size = 0; 162 | cur_byte += 1; 163 | } else if status == 0xFE && ignore.contains(Ignore::ActiveSense) { 164 | // A MIDI active sensing message and we're ignoring it. 165 | size = 0; 166 | cur_byte += 1; 167 | } else { 168 | size = 1; 169 | } 170 | 171 | // Copy the MIDI data to our vector. 172 | if size > 0 { 173 | let message_bytes = &pdata[cur_byte..(cur_byte + size)]; 174 | if !*continue_sysex { 175 | // This is either a non-sysex message or a non-segmented sysex message 176 | (handler_data.callback)(message.timestamp, message_bytes, data); 177 | message.bytes.clear(); 178 | } else { 179 | // This is the beginning of a segmented sysex message 180 | message.bytes.extend_from_slice(message_bytes); 181 | } 182 | cur_byte += size; 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | pub fn connect( 190 | self, 191 | port: &MidiInputPort, 192 | port_name: &str, 193 | callback: F, 194 | data: T, 195 | ) -> Result, ConnectError> 196 | where 197 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 198 | { 199 | let handler_data = Arc::new(Mutex::new(HandlerData { 200 | message: MidiMessage::new(), 201 | ignore_flags: self.ignore_flags, 202 | continue_sysex: false, 203 | callback: Box::new(callback), 204 | user_data: Some(data), 205 | })); 206 | let handler_data2 = handler_data.clone(); 207 | let iport = match self.client.input_port(port_name, move |packets| { 208 | MidiInput::handle_input(packets, &mut *handler_data2.lock().unwrap()) 209 | }) { 210 | Ok(p) => p, 211 | Err(_) => return Err(ConnectError::other("error creating MIDI input port", self)), 212 | }; 213 | if let Err(_) = iport.connect_source(&port.source) { 214 | return Err(ConnectError::other( 215 | "error connecting MIDI input port", 216 | self, 217 | )); 218 | } 219 | Ok(MidiInputConnection { 220 | client: self.client, 221 | details: InputConnectionDetails::Explicit(iport), 222 | handler_data: handler_data, 223 | }) 224 | } 225 | 226 | pub fn create_virtual( 227 | self, 228 | port_name: &str, 229 | callback: F, 230 | data: T, 231 | ) -> Result, ConnectError> 232 | where 233 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 234 | { 235 | let handler_data = Arc::new(Mutex::new(HandlerData { 236 | message: MidiMessage::new(), 237 | ignore_flags: self.ignore_flags, 238 | continue_sysex: false, 239 | callback: Box::new(callback), 240 | user_data: Some(data), 241 | })); 242 | let handler_data2 = handler_data.clone(); 243 | let vrt = match self.client.virtual_destination(port_name, move |packets| { 244 | MidiInput::handle_input(packets, &mut *handler_data2.lock().unwrap()) 245 | }) { 246 | Ok(p) => p, 247 | Err(_) => return Err(ConnectError::other("error creating MIDI input port", self)), 248 | }; 249 | Ok(MidiInputConnection { 250 | client: self.client, 251 | details: InputConnectionDetails::Virtual(vrt), 252 | handler_data: handler_data, 253 | }) 254 | } 255 | } 256 | 257 | enum InputConnectionDetails { 258 | Explicit(InputPort), 259 | Virtual(VirtualDestination), 260 | } 261 | 262 | pub struct MidiInputConnection { 263 | client: Client, 264 | #[allow(dead_code)] 265 | details: InputConnectionDetails, 266 | // TODO: get rid of Arc & Mutex? 267 | // synchronization is required because the borrow checker does not 268 | // know that the callback we're in here is never called concurrently 269 | // (always in sequence) 270 | handler_data: Arc>>, 271 | } 272 | 273 | impl MidiInputConnection { 274 | pub fn close(self) -> (MidiInput, T) { 275 | let mut handler_data_locked = self.handler_data.lock().unwrap(); 276 | ( 277 | MidiInput { 278 | client: self.client, 279 | ignore_flags: handler_data_locked.ignore_flags, 280 | }, 281 | handler_data_locked.user_data.take().unwrap(), 282 | ) 283 | } 284 | } 285 | 286 | /// This is all the data that is stored on the heap as long as a connection 287 | /// is opened and passed to the callback handler. 288 | /// 289 | /// It is important that `user_data` is the last field to not influence 290 | /// offsets after monomorphization. 291 | struct HandlerData { 292 | message: MidiMessage, 293 | ignore_flags: Ignore, 294 | continue_sysex: bool, 295 | callback: Box, 296 | user_data: Option, 297 | } 298 | 299 | pub struct MidiOutput { 300 | client: Client, 301 | } 302 | 303 | #[derive(Clone)] 304 | pub struct MidiOutputPort { 305 | dest: Arc, 306 | } 307 | 308 | impl MidiOutputPort { 309 | pub fn id(&self) -> String { 310 | self.dest 311 | .unique_id() 312 | // According to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case 313 | .unwrap_or(0) 314 | .to_string() 315 | } 316 | } 317 | 318 | impl PartialEq for MidiOutputPort { 319 | fn eq(&self, other: &Self) -> bool { 320 | if let (Some(id1), Some(id2)) = (self.dest.unique_id(), other.dest.unique_id()) { 321 | id1 == id2 322 | } else { 323 | // Acording to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case 324 | false 325 | } 326 | } 327 | } 328 | 329 | impl MidiOutput { 330 | pub fn new(client_name: &str) -> Result { 331 | match Client::new(client_name) { 332 | Ok(cl) => Ok(MidiOutput { client: cl }), 333 | Err(_) => Err(InitError), 334 | } 335 | } 336 | 337 | pub(crate) fn ports_internal(&self) -> Vec { 338 | Destinations 339 | .into_iter() 340 | .map(|d| crate::common::MidiOutputPort { 341 | imp: MidiOutputPort { dest: Arc::new(d) }, 342 | }) 343 | .collect() 344 | } 345 | 346 | pub fn port_count(&self) -> usize { 347 | Destinations::count() 348 | } 349 | 350 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 351 | match port.dest.display_name() { 352 | Some(name) => Ok(name), 353 | None => Err(PortInfoError::CannotRetrievePortName), 354 | } 355 | } 356 | 357 | pub fn connect( 358 | self, 359 | port: &MidiOutputPort, 360 | port_name: &str, 361 | ) -> Result> { 362 | let oport = match self.client.output_port(port_name) { 363 | Ok(p) => p, 364 | Err(_) => return Err(ConnectError::other("error creating MIDI output port", self)), 365 | }; 366 | Ok(MidiOutputConnection { 367 | client: self.client, 368 | details: OutputConnectionDetails::Explicit(oport, port.dest.clone()), 369 | }) 370 | } 371 | 372 | pub fn create_virtual( 373 | self, 374 | port_name: &str, 375 | ) -> Result> { 376 | let vrt = match self.client.virtual_source(port_name) { 377 | Ok(p) => p, 378 | Err(_) => { 379 | return Err(ConnectError::other( 380 | "error creating virtual MIDI source", 381 | self, 382 | )) 383 | } 384 | }; 385 | Ok(MidiOutputConnection { 386 | client: self.client, 387 | details: OutputConnectionDetails::Virtual(vrt), 388 | }) 389 | } 390 | } 391 | 392 | enum OutputConnectionDetails { 393 | Explicit(OutputPort, Arc), 394 | Virtual(VirtualSource), 395 | } 396 | 397 | pub struct MidiOutputConnection { 398 | client: Client, 399 | details: OutputConnectionDetails, 400 | } 401 | 402 | impl MidiOutputConnection { 403 | pub fn close(self) -> MidiOutput { 404 | MidiOutput { 405 | client: self.client, 406 | } 407 | } 408 | 409 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 410 | let send_time = 411 | if cfg!(feature = "coremidi_send_timestamped") && cfg!(not(target_os = "ios")) { 412 | unsafe { external::AudioGetCurrentHostTime() } 413 | } else { 414 | 0 415 | }; 416 | let packets = PacketBuffer::new(send_time, message); 417 | match self.details { 418 | OutputConnectionDetails::Explicit(ref port, ref dest) => port 419 | .send(&dest, &packets) 420 | .map_err(|_| SendError::Other("error sending MIDI message to port")), 421 | OutputConnectionDetails::Virtual(ref vrt) => vrt 422 | .received(&packets) 423 | .map_err(|_| SendError::Other("error sending MIDI to virtual destinations")), 424 | } 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /src/backend/android/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicBool, Ordering}, 3 | mpsc, Arc, 4 | }; 5 | use std::thread::{self, JoinHandle}; 6 | use std::time::Duration; 7 | 8 | use crate::errors::*; 9 | use crate::Ignore; 10 | 11 | use jni::errors::Error as JniError; 12 | use jni::objects::{GlobalRef, JObject, JObjectArray, JString, JValue}; 13 | use jni::sys::{jint, jobject, JNIEnv as JNISys}; 14 | use jni::JNIEnv; 15 | 16 | use jni_min_helper::{android_context, jni_with_env, JObjectGet, JniProxy}; 17 | 18 | // AMidi (NDK) FFI 19 | 20 | #[allow(non_camel_case_types)] 21 | type media_status_t = i32; 22 | 23 | // Opaque types 24 | #[repr(C)] 25 | struct AMidiDevice; 26 | #[repr(C)] 27 | struct AMidiInputPort; 28 | #[repr(C)] 29 | struct AMidiOutputPort; 30 | 31 | // opcodes for AMidiOutputPort_receive 32 | const AMIDI_OPCODE_DATA: i32 = 1; 33 | const AMIDI_OPCODE_FLUSH: i32 = 2; 34 | 35 | #[cfg(target_os = "android")] 36 | #[link(name = "amidi")] 37 | extern "C" { 38 | fn AMidiDevice_fromJava( 39 | env: *mut JNISys, 40 | midiDeviceObj: jobject, 41 | outDevicePtrPtr: *mut *mut AMidiDevice, 42 | ) -> media_status_t; 43 | fn AMidiDevice_release(device: *mut AMidiDevice) -> media_status_t; 44 | 45 | fn AMidiInputPort_open( 46 | device: *const AMidiDevice, 47 | port_number: i32, 48 | out_input_port: *mut *mut AMidiInputPort, 49 | ) -> media_status_t; 50 | fn AMidiInputPort_close(input_port: *mut AMidiInputPort); 51 | 52 | fn AMidiInputPort_send( 53 | input_port: *mut AMidiInputPort, 54 | buffer: *const u8, 55 | num_bytes: usize, 56 | ) -> isize; 57 | 58 | fn AMidiOutputPort_open( 59 | device: *const AMidiDevice, 60 | port_number: i32, 61 | out_output_port: *mut *mut AMidiOutputPort, 62 | ) -> media_status_t; 63 | fn AMidiOutputPort_close(output_port: *mut AMidiOutputPort); 64 | 65 | fn AMidiOutputPort_receive( 66 | output_port: *mut AMidiOutputPort, 67 | opcode_ptr: *mut i32, 68 | buffer: *mut u8, 69 | max_bytes: usize, 70 | num_bytes_received_ptr: *mut usize, 71 | out_timestamp_ns_ptr: *mut i64, 72 | ) -> isize; 73 | } 74 | 75 | // Helpers (JNI) 76 | 77 | fn get_midi_manager<'a>(env: &mut JNIEnv<'a>) -> Result, InitError> { 78 | // Acquire a local ref for the app Context 79 | let ctx_local = env 80 | .new_local_ref(android_context()) 81 | .map_err(|_| InitError)?; 82 | 83 | let class_ctx = env 84 | .find_class("android/content/Context") 85 | .map_err(|_| InitError)?; 86 | let midi_service_field = env 87 | .get_static_field(&class_ctx, "MIDI_SERVICE", "Ljava/lang/String;") 88 | .map_err(|_| InitError)? 89 | .l() 90 | .map_err(|_| InitError)?; 91 | 92 | let mgr = env 93 | .call_method( 94 | &ctx_local, 95 | "getSystemService", 96 | "(Ljava/lang/String;)Ljava/lang/Object;", 97 | &[JValue::from(&midi_service_field)], 98 | ) 99 | .map_err(|_| InitError)? 100 | .l() 101 | .map_err(|_| InitError)?; 102 | 103 | Ok(mgr) 104 | } 105 | 106 | fn java_string(env: &mut JNIEnv<'_>, s: JString<'_>) -> String { 107 | env.get_string(&s) 108 | .map(|os| os.to_string_lossy().into_owned()) 109 | .unwrap_or_default() 110 | } 111 | 112 | fn get_devices<'a>( 113 | env: &mut JNIEnv<'a>, 114 | midi_manager: &JObject<'a>, 115 | ) -> Result>, InitError> { 116 | let devices_obj = env 117 | .call_method( 118 | midi_manager, 119 | "getDevices", 120 | "()[Landroid/media/midi/MidiDeviceInfo;", 121 | &[], 122 | ) 123 | .map_err(|_| InitError)? 124 | .l() 125 | .map_err(|_| InitError)?; 126 | 127 | let arr: JObjectArray<'_> = devices_obj.into(); 128 | let len = env.get_array_length(&arr).map_err(|_| InitError)? as i32; 129 | let mut out = Vec::with_capacity(len as usize); 130 | for i in 0..len { 131 | let obj = env 132 | .get_object_array_element(&arr, i) 133 | .map_err(|_| InitError)?; 134 | out.push(obj); 135 | } 136 | Ok(out) 137 | } 138 | 139 | fn port_label<'a>(env: &mut JNIEnv<'a>, info: &JObject<'a>, port_info: &JObject<'a>) -> String { 140 | let dev = (|| -> Result { 141 | let info_cls = env.find_class("android/media/midi/MidiDeviceInfo")?; 142 | let props = env 143 | .call_method(info, "getProperties", "()Landroid/os/Bundle;", &[])? 144 | .l()?; 145 | let key = env 146 | .get_static_field(&info_cls, "PROPERTY_NAME", "Ljava/lang/String;")? 147 | .l()?; 148 | let name_obj = env 149 | .call_method( 150 | &props, 151 | "getString", 152 | "(Ljava/lang/String;)Ljava/lang/String;", 153 | &[JValue::from(&key)], 154 | )? 155 | .l()?; 156 | let name: JString<'_> = JString::from(name_obj); 157 | Ok(java_string(env, name)) 158 | })() 159 | .unwrap_or_else(|_| "MIDI Device".to_owned()); 160 | 161 | let port_name = (|| -> Result { 162 | let name_obj = env 163 | .call_method(port_info, "getName", "()Ljava/lang/String;", &[])? 164 | .l()?; 165 | let s: JString<'_> = JString::from(name_obj); 166 | Ok(java_string(env, s)) 167 | })() 168 | .unwrap_or_else(|_| "Port".to_owned()); 169 | 170 | let port_number = env 171 | .call_method(port_info, "getPortNumber", "()I", &[]) 172 | .and_then(|v| v.i()) 173 | .unwrap_or(0); 174 | 175 | format!("{dev} – {port_name} (#{port_number})") 176 | } 177 | 178 | fn open_midi_device_global<'a>( 179 | env: &mut JNIEnv<'a>, 180 | info: &JObject<'a>, 181 | midi_manager: &JObject<'a>, 182 | ) -> Result { 183 | // Prepare a oneshot channel to receive the device object. 184 | let (tx, rx) = mpsc::channel::>(); 185 | 186 | // Build a dynamic proxy for MidiManager.OnDeviceOpenedListener 187 | let listener = JniProxy::build( 188 | env, 189 | None, 190 | ["android/media/midi/MidiManager$OnDeviceOpenedListener"].as_slice(), 191 | move |env, method, args| { 192 | if method.get_method_name(env).unwrap_or_default() == "onDeviceOpened" { 193 | // args[0] is MidiDevice or null 194 | let dev = if args[0].is_null() { 195 | None 196 | } else { 197 | env.new_global_ref(&args[0]).ok() 198 | }; 199 | let _ = tx.send(dev); 200 | } 201 | JniProxy::void(env) 202 | }, 203 | ) 204 | .map_err(|_| InitError)?; 205 | 206 | // Call openDevice(info, listener, null) 207 | let null_obj = JObject::null(); 208 | env.call_method( 209 | midi_manager, 210 | "openDevice", 211 | "(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V", 212 | &[JValue::from(info), JValue::from(&listener), JValue::from(&null_obj)], 213 | ) 214 | .map_err(|_| InitError)?; 215 | 216 | // Wait up to 5 seconds 217 | let dev = rx 218 | .recv_timeout(Duration::from_secs(5)) 219 | .map_err(|_| InitError)?; 220 | dev.ok_or(InitError) 221 | } 222 | 223 | unsafe fn amidi_from_java( 224 | env: &mut JNIEnv<'_>, 225 | device_obj: &GlobalRef, 226 | ) -> Result<*mut AMidiDevice, InitError> { 227 | // To pass to the NDK function 228 | let local = env 229 | .new_local_ref(device_obj.as_obj()) 230 | .map_err(|_| InitError)?; 231 | let mut out: *mut AMidiDevice = std::ptr::null_mut(); 232 | let status = AMidiDevice_fromJava( 233 | env.get_native_interface(), 234 | local.into_raw(), 235 | &mut out as *mut _, 236 | ); 237 | if status != 0 || out.is_null() { 238 | return Err(InitError); 239 | } 240 | Ok(out) 241 | } 242 | 243 | fn close_java_device(env: &mut JNIEnv<'_>, device_obj: &GlobalRef) { 244 | let _ = env.call_method(device_obj.as_obj(), "close", "()V", &[]); 245 | } 246 | 247 | // Public types 248 | 249 | #[derive(Clone, PartialEq)] 250 | pub struct MidiInputPort { 251 | device_id: i32, 252 | port_number: i32, 253 | name: String, 254 | } 255 | 256 | impl MidiInputPort { 257 | pub fn id(&self) -> String { 258 | format!("{}:in:{}", self.device_id, self.port_number) 259 | } 260 | } 261 | 262 | pub struct MidiInput { 263 | ignore_flags: Ignore, 264 | } 265 | 266 | impl MidiInput { 267 | pub fn new(_client_name: &str) -> Result { 268 | // To ensure JNI is available and an Android context exists 269 | let _ = jni_with_env(|_env| Ok(())).map_err(|_| InitError)?; 270 | Ok(MidiInput { 271 | ignore_flags: Ignore::None, 272 | }) 273 | } 274 | 275 | pub fn ignore(&mut self, flags: Ignore) { 276 | self.ignore_flags = flags; 277 | } 278 | 279 | pub(crate) fn ports_internal(&self) -> Vec { 280 | jni_with_env(|env| -> Result<_, JniError> { 281 | let mgr = get_midi_manager(env).map_err(|_| JniError::NullPtr("get_midi_manager"))?; 282 | let devices = get_devices(env, &mgr).map_err(|_| JniError::NullPtr("get_devices"))?; 283 | let pi_class = env.find_class("android/media/midi/MidiDeviceInfo$PortInfo")?; 284 | let type_output = env.get_static_field(&pi_class, "TYPE_OUTPUT", "I")?.i()?; 285 | 286 | let mut result = Vec::new(); 287 | for info in devices { 288 | let id = env.call_method(&info, "getId", "()I", &[])?.i()?; 289 | let ports = env 290 | .call_method( 291 | &info, 292 | "getPorts", 293 | "()[Landroid/media/midi/MidiDeviceInfo$PortInfo;", 294 | &[], 295 | )? 296 | .l()?; 297 | let arr: JObjectArray<'_> = ports.into(); 298 | let len = env.get_array_length(&arr)? as i32; 299 | 300 | for i in 0..len { 301 | let port_info = env.get_object_array_element(&arr, i)?; 302 | let ptype = env.call_method(&port_info, "getType", "()I", &[])?.i()?; 303 | if ptype == type_output { 304 | let pnum = env 305 | .call_method(&port_info, "getPortNumber", "()I", &[])? 306 | .i()?; 307 | let label = port_label(env, &info, &port_info); 308 | result.push(crate::common::MidiInputPort { 309 | imp: MidiInputPort { 310 | device_id: id, 311 | port_number: pnum, 312 | name: label, 313 | }, 314 | }); 315 | } 316 | } 317 | } 318 | Ok(result) 319 | }) 320 | .unwrap_or_default() 321 | } 322 | 323 | pub fn port_count(&self) -> usize { 324 | self.ports_internal().len() 325 | } 326 | 327 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 328 | Ok(port.name.clone()) 329 | } 330 | 331 | pub fn connect( 332 | self, 333 | port: &MidiInputPort, 334 | _port_name: &str, 335 | mut callback: F, 336 | data: T, 337 | ) -> Result, ConnectError> 338 | where 339 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 340 | { 341 | let ignore_flags = self.ignore_flags; 342 | 343 | let open = jni_with_env(|env| -> Result<_, JniError> { 344 | let mgr = get_midi_manager(env).map_err(|_| JniError::NullPtr("get_midi_manager"))?; 345 | let devices = get_devices(env, &mgr).map_err(|_| JniError::NullPtr("get_devices"))?; 346 | let info = devices 347 | .into_iter() 348 | .find(|info| { 349 | env.call_method(info, "getId", "()I", &[]) 350 | .and_then(|v| v.i()) 351 | .map(|id| id == port.device_id) 352 | .unwrap_or(false) 353 | }) 354 | .ok_or(JniError::NullPtr("device not found"))?; 355 | 356 | let dev_global = open_midi_device_global(env, &info, &mgr) 357 | .map_err(|_| JniError::NullPtr("open_device"))?; 358 | let amidi = unsafe { amidi_from_java(env, &dev_global) } 359 | .map_err(|_| JniError::NullPtr("amidi_from_java"))?; 360 | Ok((dev_global, amidi)) 361 | }); 362 | 363 | let (java_device, amidi_device) = match open { 364 | Ok(v) => v, 365 | Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 366 | }; 367 | 368 | // Open device output port for reading 369 | let mut out_port: *mut AMidiOutputPort = std::ptr::null_mut(); 370 | let status = unsafe { 371 | AMidiOutputPort_open( 372 | amidi_device as *const AMidiDevice, 373 | port.port_number, 374 | &mut out_port, 375 | ) 376 | }; 377 | if status != 0 || out_port.is_null() { 378 | unsafe { AMidiDevice_release(amidi_device) }; 379 | let _ = jni_with_env(|env| { 380 | close_java_device(env, &java_device); 381 | Ok(()) 382 | }); 383 | return Err(ConnectError::other( 384 | "could not open Android MIDI output port", 385 | self, 386 | )); 387 | } 388 | 389 | let stop = Arc::new(AtomicBool::new(false)); 390 | let stop_clone = stop.clone(); 391 | let out_port_addr = out_port as usize; 392 | 393 | // Spawn reader thread 394 | let thread: JoinHandle = thread::Builder::new() 395 | .name("midir Android input handler".to_string()) 396 | .spawn(move || { 397 | let out_port_ptr = out_port_addr as *mut AMidiOutputPort; 398 | let mut buf = vec![0u8; 1024]; 399 | let mut opcode: i32 = 0; 400 | let mut nbytes: usize = 0; 401 | let mut ts_ns: i64 = 0; 402 | 403 | let mut user_data = data; 404 | 405 | while !stop_clone.load(Ordering::Relaxed) { 406 | let rc = unsafe { 407 | AMidiOutputPort_receive( 408 | out_port_ptr, 409 | &mut opcode as *mut _, 410 | buf.as_mut_ptr(), 411 | buf.len(), 412 | &mut nbytes as *mut _, 413 | &mut ts_ns as *mut _, 414 | ) 415 | }; 416 | if rc < 0 { 417 | std::thread::sleep(Duration::from_millis(2)); 418 | continue; 419 | } 420 | 421 | if opcode == AMIDI_OPCODE_FLUSH { 422 | continue; 423 | } 424 | 425 | if opcode == AMIDI_OPCODE_DATA && nbytes > 0 { 426 | let message = &buf[..nbytes]; 427 | let status = message[0]; 428 | 429 | // Filter according to Ignore flags 430 | if (status == 0xF0 && ignore_flags.contains(Ignore::Sysex)) 431 | || (status == 0xF1 && ignore_flags.contains(Ignore::Time)) 432 | || (status == 0xF8 && ignore_flags.contains(Ignore::Time)) 433 | || (status == 0xFE && ignore_flags.contains(Ignore::ActiveSense)) 434 | { 435 | continue; 436 | } 437 | 438 | let ts_us = if ts_ns > 0 { (ts_ns as u64) / 1000 } else { 0 }; 439 | callback(ts_us, message, &mut user_data); 440 | } else { 441 | std::thread::sleep(Duration::from_millis(1)); 442 | } 443 | } 444 | 445 | user_data 446 | }) 447 | .map_err(|_| { 448 | ConnectError::other("could not start Android input handler thread", self) 449 | })?; 450 | 451 | Ok(MidiInputConnection { 452 | java_device, 453 | amidi_device, 454 | out_port, 455 | stop, 456 | thread: Some(thread), 457 | ignore_flags, 458 | }) 459 | } 460 | 461 | // Virtual ports are not supported when using AMidi without a MidiDeviceService (Just throw a err) 462 | pub fn create_virtual( 463 | self, 464 | _port_name: &str, 465 | _callback: F, 466 | _data: T, 467 | ) -> Result, ConnectError> 468 | where 469 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 470 | { 471 | Err(ConnectError::other( 472 | "virtual MIDI input ports are not supported on Android", 473 | self, 474 | )) 475 | } 476 | } 477 | 478 | unsafe impl Send for MidiInputConnection {} 479 | unsafe impl Sync for MidiInputConnection {} 480 | 481 | pub struct MidiInputConnection { 482 | java_device: GlobalRef, 483 | amidi_device: *mut AMidiDevice, 484 | out_port: *mut AMidiOutputPort, 485 | stop: Arc, 486 | thread: Option>, 487 | ignore_flags: Ignore, 488 | } 489 | 490 | impl MidiInputConnection { 491 | pub fn close(mut self) -> (MidiInput, T) { 492 | self.stop.store(true, Ordering::Relaxed); 493 | let user_data = self.thread.take().map(|h| h.join().unwrap()).unwrap(); 494 | 495 | unsafe { 496 | AMidiOutputPort_close(self.out_port); 497 | AMidiDevice_release(self.amidi_device); 498 | } 499 | let _ = jni_with_env(|env| { 500 | close_java_device(env, &self.java_device); 501 | Ok(()) 502 | }); 503 | 504 | ( 505 | MidiInput { 506 | ignore_flags: self.ignore_flags, 507 | }, 508 | user_data, 509 | ) 510 | } 511 | } 512 | 513 | #[derive(Clone, PartialEq)] 514 | pub struct MidiOutputPort { 515 | device_id: i32, 516 | port_number: i32, 517 | name: String, 518 | } 519 | 520 | impl MidiOutputPort { 521 | pub fn id(&self) -> String { 522 | format!("{}:out:{}", self.device_id, self.port_number) 523 | } 524 | } 525 | 526 | pub struct MidiOutput { 527 | // no state 528 | } 529 | 530 | impl MidiOutput { 531 | pub fn new(_client_name: &str) -> Result { 532 | let _ = jni_with_env(|_env| Ok(())).map_err(|_| InitError)?; 533 | Ok(MidiOutput {}) 534 | } 535 | 536 | pub(crate) fn ports_internal(&self) -> Vec { 537 | jni_with_env(|env| -> Result<_, JniError> { 538 | let mgr = get_midi_manager(env).map_err(|_| JniError::NullPtr("get_midi_manager"))?; 539 | let devices = get_devices(env, &mgr).map_err(|_| JniError::NullPtr("get_devices"))?; 540 | let pi_class = env.find_class("android/media/midi/MidiDeviceInfo$PortInfo")?; 541 | let type_input = env.get_static_field(&pi_class, "TYPE_INPUT", "I")?.i()?; 542 | 543 | let mut result = Vec::new(); 544 | for info in devices { 545 | let id = env.call_method(&info, "getId", "()I", &[])?.i()?; 546 | let ports = env 547 | .call_method( 548 | &info, 549 | "getPorts", 550 | "()[Landroid/media/midi/MidiDeviceInfo$PortInfo;", 551 | &[], 552 | )? 553 | .l()?; 554 | let arr: JObjectArray<'_> = ports.into(); 555 | let len = env.get_array_length(&arr)? as i32; 556 | 557 | for i in 0..len { 558 | let port_info = env.get_object_array_element(&arr, i)?; 559 | let ptype = env.call_method(&port_info, "getType", "()I", &[])?.i()?; 560 | if ptype == type_input { 561 | let pnum = env 562 | .call_method(&port_info, "getPortNumber", "()I", &[])? 563 | .i()?; 564 | let label = port_label(env, &info, &port_info); 565 | result.push(crate::common::MidiOutputPort { 566 | imp: MidiOutputPort { 567 | device_id: id, 568 | port_number: pnum, 569 | name: label, 570 | }, 571 | }); 572 | } 573 | } 574 | } 575 | Ok(result) 576 | }) 577 | .unwrap_or_default() 578 | } 579 | 580 | pub fn port_count(&self) -> usize { 581 | self.ports_internal().len() 582 | } 583 | 584 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 585 | Ok(port.name.clone()) 586 | } 587 | 588 | pub fn connect( 589 | self, 590 | port: &MidiOutputPort, 591 | _port_name: &str, 592 | ) -> Result> { 593 | let open = jni_with_env(|env| -> Result<_, JniError> { 594 | let mgr = get_midi_manager(env).map_err(|_| JniError::NullPtr("get_midi_manager"))?; 595 | let devices = get_devices(env, &mgr).map_err(|_| JniError::NullPtr("get_devices"))?; 596 | let info = devices 597 | .into_iter() 598 | .find(|info| { 599 | env.call_method(info, "getId", "()I", &[]) 600 | .and_then(|v| v.i()) 601 | .map(|id| id == port.device_id) 602 | .unwrap_or(false) 603 | }) 604 | .ok_or(JniError::NullPtr("device not found"))?; 605 | 606 | let dev_global = open_midi_device_global(env, &info, &mgr) 607 | .map_err(|_| JniError::NullPtr("open_device"))?; 608 | let amidi = unsafe { amidi_from_java(env, &dev_global) } 609 | .map_err(|_| JniError::NullPtr("amidi_from_java"))?; 610 | Ok((dev_global, amidi)) 611 | }); 612 | 613 | let (java_device, amidi_device) = match open { 614 | Ok(v) => v, 615 | Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 616 | }; 617 | 618 | // Open device input port for sending 619 | let mut in_port: *mut AMidiInputPort = std::ptr::null_mut(); 620 | let status = unsafe { 621 | AMidiInputPort_open( 622 | amidi_device as *const AMidiDevice, 623 | port.port_number, 624 | &mut in_port, 625 | ) 626 | }; 627 | if status != 0 || in_port.is_null() { 628 | unsafe { AMidiDevice_release(amidi_device) }; 629 | let _ = jni_with_env(|env| { 630 | close_java_device(env, &java_device); 631 | Ok(()) 632 | }); 633 | return Err(ConnectError::other( 634 | "could not open Android MIDI input port", 635 | self, 636 | )); 637 | } 638 | 639 | Ok(MidiOutputConnection { 640 | java_device, 641 | amidi_device, 642 | in_port, 643 | }) 644 | } 645 | 646 | // Similar 647 | pub fn create_virtual( 648 | self, 649 | _port_name: &str, 650 | ) -> Result> { 651 | Err(ConnectError::other( 652 | "virtual MIDI output ports are not supported on Android", 653 | self, 654 | )) 655 | } 656 | } 657 | 658 | unsafe impl Sync for MidiOutputConnection {} 659 | 660 | pub struct MidiOutputConnection { 661 | java_device: GlobalRef, 662 | amidi_device: *mut AMidiDevice, 663 | in_port: *mut AMidiInputPort, 664 | } 665 | 666 | unsafe impl Send for MidiOutputConnection {} 667 | 668 | impl MidiOutputConnection { 669 | pub fn close(self) -> MidiOutput { 670 | unsafe { 671 | AMidiInputPort_close(self.in_port); 672 | AMidiDevice_release(self.amidi_device); 673 | } 674 | let _ = jni_with_env(|env| { 675 | close_java_device(env, &self.java_device); 676 | Ok(()) 677 | }); 678 | MidiOutput {} 679 | } 680 | 681 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 682 | if message.is_empty() { 683 | return Err(SendError::InvalidData( 684 | "message to be sent must not be empty", 685 | )); 686 | } 687 | 688 | let rc = unsafe { AMidiInputPort_send(self.in_port, message.as_ptr(), message.len()) }; 689 | if rc < 0 { 690 | return Err(SendError::Other("AMidiInputPort_send failed")); 691 | } 692 | 693 | Ok(()) 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /src/backend/winmm/mod.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::ReentrantMutex as Mutex; 2 | use std::alloc::{alloc, dealloc, Layout}; 3 | use std::ffi::{c_void, OsString}; 4 | use std::mem::MaybeUninit; 5 | use std::os::windows::ffi::OsStringExt; 6 | use std::ptr::null_mut; 7 | use std::thread::sleep; 8 | use std::time::Duration; 9 | use std::{mem, ptr, slice}; 10 | 11 | use log::warn; 12 | 13 | use windows::core::PSTR; 14 | use windows::Win32::Media::Audio::{ 15 | midiInAddBuffer, midiInClose, midiInGetDevCapsW, midiInGetNumDevs, midiInMessage, midiInOpen, 16 | midiInPrepareHeader, midiInReset, midiInStart, midiInStop, midiInUnprepareHeader, midiOutClose, 17 | midiOutGetDevCapsW, midiOutGetNumDevs, midiOutLongMsg, midiOutMessage, midiOutOpen, 18 | midiOutPrepareHeader, midiOutReset, midiOutShortMsg, midiOutUnprepareHeader, CALLBACK_FUNCTION, 19 | CALLBACK_NULL, HMIDIIN, HMIDIOUT, MIDIERR_NOTREADY, MIDIERR_STILLPLAYING, MIDIHDR, MIDIINCAPSW, 20 | MIDIOUTCAPSW, 21 | }; 22 | use windows::Win32::Media::Multimedia::{DRV_QUERYDEVICEINTERFACE, DRV_QUERYDEVICEINTERFACESIZE}; 23 | use windows::Win32::Media::{MMSYSERR_ALLOCATED, MMSYSERR_BADDEVICEID, MMSYSERR_NOERROR}; 24 | 25 | #[allow(non_camel_case_types)] 26 | #[allow(clippy::upper_case_acronyms)] 27 | type ULONG = u32; 28 | #[allow(non_camel_case_types)] 29 | #[allow(clippy::upper_case_acronyms)] 30 | type UINT = u32; 31 | #[allow(non_camel_case_types)] 32 | #[allow(clippy::upper_case_acronyms)] 33 | type DWORD = u32; 34 | #[allow(non_camel_case_types)] 35 | #[allow(clippy::upper_case_acronyms)] 36 | type DWORD_PTR = usize; 37 | 38 | use crate::errors::*; 39 | use crate::{Ignore, MidiMessage}; 40 | 41 | mod handler; 42 | 43 | const MIDIR_SYSEX_BUFFER_SIZE: usize = 1024; 44 | const MIDIR_SYSEX_BUFFER_COUNT: usize = 4; 45 | 46 | // helper for string conversion 47 | fn from_wide_ptr(ptr: *const u16, max_len: usize) -> OsString { 48 | unsafe { 49 | assert!(!ptr.is_null()); 50 | let len = (0..max_len as isize) 51 | .position(|i| *ptr.offset(i) == 0) 52 | .unwrap(); 53 | let slice = slice::from_raw_parts(ptr, len); 54 | OsString::from_wide(slice) 55 | } 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct MidiInput { 60 | ignore_flags: Ignore, 61 | } 62 | 63 | #[derive(Clone)] 64 | pub struct MidiInputPort { 65 | name: String, 66 | interface_id: Box<[u16]>, 67 | } 68 | 69 | impl MidiInputPort { 70 | pub fn id(&self) -> String { 71 | String::from_utf16_lossy(&self.interface_id[..self.interface_id.len() - 1]) 72 | } 73 | } 74 | 75 | impl PartialEq for MidiInputPort { 76 | fn eq(&self, other: &Self) -> bool { 77 | self.interface_id == other.interface_id 78 | } 79 | } 80 | 81 | pub struct MidiInputConnection { 82 | handler_data: Box>, 83 | } 84 | 85 | impl MidiInputPort { 86 | fn count() -> UINT { 87 | unsafe { midiInGetNumDevs() } 88 | } 89 | 90 | /// Queries the interface ID. The result contains a terminating zero. 91 | fn interface_id(port_number: UINT) -> Result, PortInfoError> { 92 | let mut buffer_size: ULONG = 0; 93 | let result = unsafe { 94 | midiInMessage( 95 | Some(HMIDIIN(port_number as *mut c_void)), 96 | DRV_QUERYDEVICEINTERFACESIZE, 97 | Some(&mut buffer_size as *mut _ as DWORD_PTR), 98 | None, 99 | ) 100 | }; 101 | if result == MMSYSERR_BADDEVICEID { 102 | return Err(PortInfoError::PortNumberOutOfRange); 103 | } else if result != MMSYSERR_NOERROR { 104 | return Err(PortInfoError::CannotRetrievePortName); 105 | } 106 | let mut buffer = Vec::::with_capacity(buffer_size as usize / 2); 107 | unsafe { 108 | let result = midiInMessage( 109 | Some(HMIDIIN(port_number as *mut c_void)), 110 | DRV_QUERYDEVICEINTERFACE, 111 | Some(buffer.as_mut_ptr() as usize), 112 | Some(buffer_size as DWORD_PTR), 113 | ); 114 | if result == MMSYSERR_BADDEVICEID { 115 | return Err(PortInfoError::PortNumberOutOfRange); 116 | } else if result != MMSYSERR_NOERROR { 117 | return Err(PortInfoError::CannotRetrievePortName); 118 | } 119 | buffer.set_len(buffer_size as usize / 2); 120 | } 121 | //println!("{}", from_wide_ptr(buffer.as_ptr(), buffer.len()).to_string_lossy().into_owned()); 122 | assert!(buffer.last().cloned() == Some(0)); 123 | Ok(buffer.into_boxed_slice()) 124 | } 125 | 126 | fn name(port_number: UINT) -> Result { 127 | let mut device_caps: MaybeUninit = MaybeUninit::uninit(); 128 | let result = unsafe { 129 | midiInGetDevCapsW( 130 | port_number as usize, 131 | device_caps.as_mut_ptr(), 132 | mem::size_of::() as u32, 133 | ) 134 | }; 135 | if result == MMSYSERR_BADDEVICEID { 136 | return Err(PortInfoError::PortNumberOutOfRange); 137 | } else if result != MMSYSERR_NOERROR { 138 | return Err(PortInfoError::CannotRetrievePortName); 139 | } 140 | let device_caps = unsafe { device_caps.assume_init() }; 141 | let pname_ptr: *const [u16; 32] = std::ptr::addr_of!(device_caps.szPname); 142 | let output = from_wide_ptr(pname_ptr as *const _, 32) 143 | .to_string_lossy() 144 | .into_owned(); 145 | Ok(output) 146 | } 147 | 148 | fn from_port_number(port_number: UINT) -> Result { 149 | Ok(MidiInputPort { 150 | name: Self::name(port_number)?, 151 | interface_id: Self::interface_id(port_number)?, 152 | }) 153 | } 154 | 155 | fn current_port_number(&self) -> Option { 156 | for i in 0..Self::count() { 157 | if let Ok(name) = Self::name(i) { 158 | if name != self.name { 159 | continue; 160 | } 161 | if let Ok(id) = Self::interface_id(i) { 162 | if id == self.interface_id { 163 | return Some(i); 164 | } 165 | } 166 | } 167 | } 168 | None 169 | } 170 | } 171 | 172 | struct SysexBuffer([*mut MIDIHDR; MIDIR_SYSEX_BUFFER_COUNT]); 173 | unsafe impl Send for SysexBuffer {} 174 | 175 | struct MidiInHandle(Mutex); 176 | unsafe impl Send for MidiInHandle {} 177 | 178 | /// This is all the data that is stored on the heap as long as a connection 179 | /// is opened and passed to the callback handler. 180 | /// 181 | /// It is important that `user_data` is the last field to not influence 182 | /// offsets after monomorphization. 183 | struct HandlerData { 184 | message: MidiMessage, 185 | sysex_buffer: SysexBuffer, 186 | in_handle: Option, 187 | ignore_flags: Ignore, 188 | callback: Box, 189 | user_data: Option, 190 | } 191 | 192 | impl MidiInput { 193 | pub fn new(_client_name: &str) -> Result { 194 | Ok(MidiInput { 195 | ignore_flags: Ignore::None, 196 | }) 197 | } 198 | 199 | pub fn ignore(&mut self, flags: Ignore) { 200 | self.ignore_flags = flags; 201 | } 202 | 203 | pub(crate) fn ports_internal(&self) -> Vec { 204 | let count = MidiInputPort::count(); 205 | let mut result = Vec::with_capacity(count as usize); 206 | for i in 0..count { 207 | let port = match MidiInputPort::from_port_number(i) { 208 | Ok(p) => p, 209 | Err(_) => continue, 210 | }; 211 | result.push(crate::common::MidiInputPort { imp: port }); 212 | } 213 | result 214 | } 215 | 216 | pub fn port_count(&self) -> usize { 217 | MidiInputPort::count() as usize 218 | } 219 | 220 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 221 | Ok(port.name.clone()) 222 | } 223 | 224 | pub fn connect( 225 | self, 226 | port: &MidiInputPort, 227 | _port_name: &str, 228 | callback: F, 229 | data: T, 230 | ) -> Result, ConnectError> 231 | where 232 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 233 | { 234 | let port_number = match port.current_port_number() { 235 | Some(p) => p, 236 | None => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 237 | }; 238 | 239 | let mut handler_data = Box::new(HandlerData { 240 | message: MidiMessage::new(), 241 | sysex_buffer: SysexBuffer([null_mut(); MIDIR_SYSEX_BUFFER_COUNT]), 242 | in_handle: None, 243 | ignore_flags: self.ignore_flags, 244 | callback: Box::new(callback), 245 | user_data: Some(data), 246 | }); 247 | 248 | let mut in_handle: MaybeUninit = MaybeUninit::uninit(); 249 | let handler_data_ptr: *mut HandlerData = &mut *handler_data; 250 | let result = unsafe { 251 | midiInOpen( 252 | in_handle.as_mut_ptr(), 253 | port_number as UINT, 254 | Some(handler::handle_input:: as DWORD_PTR), 255 | Some(handler_data_ptr as DWORD_PTR), 256 | CALLBACK_FUNCTION, 257 | ) 258 | }; 259 | if result == MMSYSERR_ALLOCATED { 260 | return Err(ConnectError::other( 261 | "could not create Windows MM MIDI input port (MMSYSERR_ALLOCATED)", 262 | self, 263 | )); 264 | } else if result != MMSYSERR_NOERROR { 265 | return Err(ConnectError::other( 266 | "could not create Windows MM MIDI input port", 267 | self, 268 | )); 269 | } 270 | let in_handle = unsafe { in_handle.assume_init() }; 271 | 272 | // Allocate and init the sysex buffers. 273 | for i in 0..MIDIR_SYSEX_BUFFER_COUNT { 274 | handler_data.sysex_buffer.0[i] = Box::into_raw(Box::new(MIDIHDR { 275 | lpData: PSTR(unsafe { 276 | alloc(Layout::from_size_align_unchecked( 277 | MIDIR_SYSEX_BUFFER_SIZE, 278 | 1, 279 | )) 280 | }), 281 | dwBufferLength: MIDIR_SYSEX_BUFFER_SIZE as u32, 282 | dwBytesRecorded: 0, 283 | dwUser: i as DWORD_PTR, // We use the dwUser parameter as buffer indicator 284 | dwFlags: 0, 285 | lpNext: ptr::null_mut(), 286 | reserved: 0, 287 | dwOffset: 0, 288 | dwReserved: unsafe { mem::zeroed() }, 289 | })); 290 | 291 | // TODO: are those buffers ever freed if an error occurs here (altough these calls probably only fail with out-of-memory)? 292 | // TODO: close port in case of error? 293 | 294 | let result = unsafe { 295 | midiInPrepareHeader( 296 | in_handle, 297 | handler_data.sysex_buffer.0[i], 298 | mem::size_of::() as u32, 299 | ) 300 | }; 301 | if result != MMSYSERR_NOERROR { 302 | return Err(ConnectError::other( 303 | "could not initialize Windows MM MIDI input port (PrepareHeader)", 304 | self, 305 | )); 306 | } 307 | 308 | // Register the buffer. 309 | let result = unsafe { 310 | midiInAddBuffer( 311 | in_handle, 312 | handler_data.sysex_buffer.0[i], 313 | mem::size_of::() as u32, 314 | ) 315 | }; 316 | if result != MMSYSERR_NOERROR { 317 | return Err(ConnectError::other( 318 | "could not initialize Windows MM MIDI input port (AddBuffer)", 319 | self, 320 | )); 321 | } 322 | } 323 | 324 | handler_data.in_handle = Some(MidiInHandle(Mutex::new(in_handle))); 325 | 326 | // We can safely access (a copy of) `in_handle` here, although 327 | // it has been copied into the Mutex already, because the callback 328 | // has not been called yet. 329 | let result = unsafe { midiInStart(in_handle) }; 330 | if result != MMSYSERR_NOERROR { 331 | unsafe { midiInClose(in_handle) }; 332 | return Err(ConnectError::other( 333 | "could not start Windows MM MIDI input port", 334 | self, 335 | )); 336 | } 337 | 338 | Ok(MidiInputConnection { handler_data }) 339 | } 340 | } 341 | 342 | impl MidiInputConnection { 343 | pub fn close(mut self) -> (MidiInput, T) { 344 | self.close_internal(); 345 | 346 | ( 347 | MidiInput { 348 | ignore_flags: self.handler_data.ignore_flags, 349 | }, 350 | self.handler_data.user_data.take().unwrap(), 351 | ) 352 | } 353 | 354 | fn close_internal(&mut self) { 355 | // for information about this lock, see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo 356 | let in_handle_lock = self.handler_data.in_handle.as_ref().unwrap().0.lock(); 357 | 358 | // TODO: Call both reset and stop here? The difference seems to be that 359 | // reset "returns all pending input buffers to the callback function" 360 | unsafe { 361 | midiInReset(*in_handle_lock); 362 | midiInStop(*in_handle_lock); 363 | } 364 | 365 | for i in 0..MIDIR_SYSEX_BUFFER_COUNT { 366 | let result; 367 | unsafe { 368 | result = midiInUnprepareHeader( 369 | *in_handle_lock, 370 | self.handler_data.sysex_buffer.0[i], 371 | mem::size_of::() as u32, 372 | ); 373 | dealloc( 374 | (*self.handler_data.sysex_buffer.0[i]).lpData.0 as *mut _, 375 | Layout::from_size_align_unchecked(MIDIR_SYSEX_BUFFER_SIZE, 1), 376 | ); 377 | // recreate the Box so that it will be dropped/deallocated at the end of this scope 378 | let _ = Box::from_raw(self.handler_data.sysex_buffer.0[i]); 379 | } 380 | 381 | if result != MMSYSERR_NOERROR { 382 | warn!("Warning: Ignoring error shutting down Windows MM input port (UnprepareHeader)."); 383 | } 384 | } 385 | 386 | unsafe { midiInClose(*in_handle_lock) }; 387 | } 388 | } 389 | 390 | impl Drop for MidiInputConnection { 391 | fn drop(&mut self) { 392 | // If user_data has been emptied, we know that we already have closed the connection 393 | if self.handler_data.user_data.is_some() { 394 | self.close_internal() 395 | } 396 | } 397 | } 398 | 399 | #[derive(Debug)] 400 | pub struct MidiOutput; 401 | 402 | #[derive(Clone)] 403 | pub struct MidiOutputPort { 404 | name: String, 405 | interface_id: Box<[u16]>, 406 | } 407 | 408 | impl MidiOutputPort { 409 | pub fn id(&self) -> String { 410 | String::from_utf16_lossy(&self.interface_id[..self.interface_id.len() - 1]) 411 | } 412 | } 413 | 414 | impl PartialEq for MidiOutputPort { 415 | fn eq(&self, other: &Self) -> bool { 416 | self.interface_id == other.interface_id 417 | } 418 | } 419 | 420 | pub struct MidiOutputConnection { 421 | out_handle: HMIDIOUT, 422 | } 423 | 424 | unsafe impl Send for MidiOutputConnection {} 425 | 426 | impl MidiOutputPort { 427 | fn count() -> UINT { 428 | unsafe { midiOutGetNumDevs() } 429 | } 430 | 431 | /// Queries the interface ID. The result contains a terminating zero. 432 | fn interface_id(port_number: UINT) -> Result, PortInfoError> { 433 | let mut buffer_size: ULONG = 0; 434 | let result = unsafe { 435 | midiOutMessage( 436 | Some(HMIDIOUT(port_number as *mut c_void)), 437 | DRV_QUERYDEVICEINTERFACESIZE, 438 | Some(&mut buffer_size as *mut _ as DWORD_PTR), 439 | None, 440 | ) 441 | }; 442 | if result == MMSYSERR_BADDEVICEID { 443 | return Err(PortInfoError::PortNumberOutOfRange); 444 | } else if result != MMSYSERR_NOERROR { 445 | return Err(PortInfoError::CannotRetrievePortName); 446 | } 447 | let mut buffer = Vec::::with_capacity(buffer_size as usize / 2); 448 | unsafe { 449 | let result = midiOutMessage( 450 | Some(HMIDIOUT(port_number as *mut c_void)), 451 | DRV_QUERYDEVICEINTERFACE, 452 | Some(buffer.as_mut_ptr() as DWORD_PTR), 453 | Some(buffer_size as DWORD_PTR), 454 | ); 455 | if result == MMSYSERR_BADDEVICEID { 456 | return Err(PortInfoError::PortNumberOutOfRange); 457 | } else if result != MMSYSERR_NOERROR { 458 | return Err(PortInfoError::CannotRetrievePortName); 459 | } 460 | buffer.set_len(buffer_size as usize / 2); 461 | } 462 | //println!("{}", from_wide_ptr(buffer.as_ptr(), buffer.len()).to_string_lossy().into_owned()); 463 | assert!(buffer.last().cloned() == Some(0)); 464 | Ok(buffer.into_boxed_slice()) 465 | } 466 | 467 | fn name(port_number: UINT) -> Result { 468 | let mut device_caps: MaybeUninit = MaybeUninit::uninit(); 469 | let result = unsafe { 470 | midiOutGetDevCapsW( 471 | port_number as usize, 472 | device_caps.as_mut_ptr(), 473 | mem::size_of::() as u32, 474 | ) 475 | }; 476 | if result == MMSYSERR_BADDEVICEID { 477 | return Err(PortInfoError::PortNumberOutOfRange); 478 | } else if result != MMSYSERR_NOERROR { 479 | return Err(PortInfoError::CannotRetrievePortName); 480 | } 481 | let device_caps = unsafe { device_caps.assume_init() }; 482 | let pname_ptr: *const [u16; 32] = std::ptr::addr_of!(device_caps.szPname); 483 | let output = from_wide_ptr(pname_ptr as *const _, 32) 484 | .to_string_lossy() 485 | .into_owned(); 486 | Ok(output) 487 | } 488 | 489 | fn from_port_number(port_number: UINT) -> Result { 490 | Ok(MidiOutputPort { 491 | name: Self::name(port_number)?, 492 | interface_id: Self::interface_id(port_number)?, 493 | }) 494 | } 495 | 496 | fn current_port_number(&self) -> Option { 497 | for i in 0..Self::count() { 498 | if let Ok(name) = Self::name(i) { 499 | if name != self.name { 500 | continue; 501 | } 502 | if let Ok(id) = Self::interface_id(i) { 503 | if id == self.interface_id { 504 | return Some(i); 505 | } 506 | } 507 | } 508 | } 509 | None 510 | } 511 | } 512 | 513 | impl MidiOutput { 514 | pub fn new(_client_name: &str) -> Result { 515 | Ok(MidiOutput) 516 | } 517 | 518 | pub(crate) fn ports_internal(&self) -> Vec { 519 | let count = MidiOutputPort::count(); 520 | let mut result = Vec::with_capacity(count as usize); 521 | for i in 0..count { 522 | let port = match MidiOutputPort::from_port_number(i) { 523 | Ok(p) => p, 524 | Err(_) => continue, 525 | }; 526 | result.push(crate::common::MidiOutputPort { imp: port }); 527 | } 528 | result 529 | } 530 | 531 | pub fn port_count(&self) -> usize { 532 | MidiOutputPort::count() as usize 533 | } 534 | 535 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 536 | Ok(port.name.clone()) 537 | } 538 | 539 | pub fn connect( 540 | self, 541 | port: &MidiOutputPort, 542 | _port_name: &str, 543 | ) -> Result> { 544 | let port_number = match port.current_port_number() { 545 | Some(p) => p, 546 | None => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 547 | }; 548 | let mut out_handle: MaybeUninit = MaybeUninit::uninit(); 549 | let result = unsafe { 550 | midiOutOpen( 551 | out_handle.as_mut_ptr(), 552 | port_number as UINT, 553 | None, 554 | None, 555 | CALLBACK_NULL, 556 | ) 557 | }; 558 | if result == MMSYSERR_ALLOCATED { 559 | return Err(ConnectError::other( 560 | "could not create Windows MM MIDI output port (MMSYSERR_ALLOCATED)", 561 | self, 562 | )); 563 | } else if result != MMSYSERR_NOERROR { 564 | return Err(ConnectError::other( 565 | "could not create Windows MM MIDI output port", 566 | self, 567 | )); 568 | } 569 | Ok(MidiOutputConnection { 570 | out_handle: unsafe { out_handle.assume_init() }, 571 | }) 572 | } 573 | } 574 | 575 | impl MidiOutputConnection { 576 | pub fn close(self) -> MidiOutput { 577 | // The actual closing is done by the implementation of Drop 578 | MidiOutput // In this API this is a noop 579 | } 580 | 581 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 582 | let nbytes = message.len(); 583 | if nbytes == 0 { 584 | return Err(SendError::InvalidData( 585 | "message to be sent must not be empty", 586 | )); 587 | } 588 | 589 | if message[0] == 0xF0 { 590 | // Sysex message 591 | // Allocate buffer for sysex data and copy message 592 | let mut buffer = message.to_vec(); 593 | 594 | // Create and prepare MIDIHDR structure. 595 | let mut sysex = MIDIHDR { 596 | lpData: PSTR(buffer.as_mut_ptr()), 597 | dwBufferLength: nbytes as u32, 598 | dwBytesRecorded: 0, 599 | dwUser: 0, 600 | dwFlags: 0, 601 | lpNext: ptr::null_mut(), 602 | reserved: 0, 603 | dwOffset: 0, 604 | dwReserved: unsafe { mem::zeroed() }, 605 | }; 606 | 607 | let result = unsafe { 608 | midiOutPrepareHeader( 609 | self.out_handle, 610 | &mut sysex, 611 | mem::size_of::() as u32, 612 | ) 613 | }; 614 | 615 | if result != MMSYSERR_NOERROR { 616 | return Err(SendError::Other( 617 | "preparation for sending sysex message failed (OutPrepareHeader)", 618 | )); 619 | } 620 | 621 | // Send the message. 622 | loop { 623 | let result = unsafe { 624 | midiOutLongMsg(self.out_handle, &sysex, mem::size_of::() as u32) 625 | }; 626 | if result == MIDIERR_NOTREADY { 627 | sleep(Duration::from_millis(1)); 628 | continue; 629 | } else { 630 | if result != MMSYSERR_NOERROR { 631 | return Err(SendError::Other("sending sysex message failed")); 632 | } 633 | break; 634 | } 635 | } 636 | 637 | loop { 638 | let result = unsafe { 639 | midiOutUnprepareHeader( 640 | self.out_handle, 641 | &mut sysex, 642 | mem::size_of::() as u32, 643 | ) 644 | }; 645 | if result == MIDIERR_STILLPLAYING { 646 | sleep(Duration::from_millis(1)); 647 | continue; 648 | } else { 649 | break; 650 | } 651 | } 652 | } else { 653 | // Channel or system message. 654 | // Make sure the message size isn't too big. 655 | if nbytes > 3 { 656 | return Err(SendError::InvalidData( 657 | "non-sysex message must not be longer than 3 bytes", 658 | )); 659 | } 660 | 661 | // Pack MIDI bytes into double word. 662 | let mut packet: u32 = 0; 663 | let ptr = std::ptr::addr_of_mut!(packet).cast::(); 664 | for (i, item) in message.iter().enumerate().take(nbytes) { 665 | unsafe { *ptr.add(i) = *item }; 666 | } 667 | 668 | // Send the message immediately. 669 | loop { 670 | let result = unsafe { midiOutShortMsg(self.out_handle, packet) }; 671 | if result == MIDIERR_NOTREADY { 672 | sleep(Duration::from_millis(1)); 673 | continue; 674 | } else { 675 | if result != MMSYSERR_NOERROR { 676 | return Err(SendError::Other("sending non-sysex message failed")); 677 | } 678 | break; 679 | } 680 | } 681 | } 682 | 683 | Ok(()) 684 | } 685 | } 686 | 687 | impl Drop for MidiOutputConnection { 688 | fn drop(&mut self) { 689 | unsafe { 690 | midiOutReset(self.out_handle); 691 | midiOutClose(self.out_handle); 692 | } 693 | } 694 | } 695 | -------------------------------------------------------------------------------- /src/backend/alsa/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | use std::mem; 3 | use std::thread::{Builder, JoinHandle}; 4 | 5 | use crate::{errors, Ignore, MidiMessage}; 6 | 7 | use alsa::seq::{Addr, EventType, PortCap, PortInfo, PortSubscribe, PortType, QueueTempo}; 8 | use alsa::{Direction, Seq}; 9 | 10 | use log::{debug, error, log_enabled, Level}; 11 | 12 | use errors::*; 13 | 14 | mod helpers { 15 | use crate::errors::PortInfoError; 16 | use alsa::seq::{Addr, ClientIter, MidiEvent, PortCap, PortInfo, PortIter, PortType, Seq}; 17 | 18 | pub fn poll(fds: &mut [libc::pollfd], timeout: i32) -> i32 { 19 | unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, timeout) } 20 | } 21 | 22 | #[inline] 23 | pub fn get_ports(s: &Seq, capability: PortCap, f: F) -> Vec 24 | where 25 | F: Fn(PortInfo) -> T, 26 | { 27 | ClientIter::new(s) 28 | .flat_map(|c| PortIter::new(s, c.get_client())) 29 | .filter(|p| { 30 | p.get_type() 31 | .intersects(PortType::MIDI_GENERIC | PortType::SYNTH | PortType::APPLICATION) 32 | }) 33 | .filter(|p| p.get_capability().contains(capability)) 34 | .map(f) 35 | .collect() 36 | } 37 | 38 | #[inline] 39 | pub fn get_port_count(s: &Seq, capability: PortCap) -> usize { 40 | ClientIter::new(s) 41 | .flat_map(|c| PortIter::new(s, c.get_client())) 42 | .filter(|p| { 43 | p.get_type() 44 | .intersects(PortType::MIDI_GENERIC | PortType::SYNTH | PortType::APPLICATION) 45 | }) 46 | .filter(|p| p.get_capability().contains(capability)) 47 | .count() 48 | } 49 | 50 | #[inline] 51 | pub fn get_port_name(s: &Seq, addr: Addr) -> Result { 52 | use std::fmt::Write; 53 | 54 | let pinfo = match s.get_any_port_info(addr) { 55 | Ok(p) => p, 56 | Err(_) => return Err(PortInfoError::InvalidPort), 57 | }; 58 | 59 | let cinfo = s 60 | .get_any_client_info(pinfo.get_client()) 61 | .map_err(|_| PortInfoError::CannotRetrievePortName)?; 62 | let mut output = String::new(); 63 | write!( 64 | &mut output, 65 | "{}:{} {}:{}", 66 | cinfo 67 | .get_name() 68 | .map_err(|_| PortInfoError::CannotRetrievePortName)?, 69 | pinfo 70 | .get_name() 71 | .map_err(|_| PortInfoError::CannotRetrievePortName)?, 72 | pinfo.get_client(), // These lines added to make sure devices are listed 73 | pinfo.get_port() // with full portnames added to ensure individual device names 74 | ) 75 | .unwrap(); 76 | Ok(output) 77 | } 78 | 79 | pub struct EventDecoder { 80 | ev: MidiEvent, 81 | } 82 | 83 | impl EventDecoder { 84 | pub fn new(merge_commands: bool) -> EventDecoder { 85 | let coder = MidiEvent::new(0).unwrap(); 86 | coder.enable_running_status(merge_commands); 87 | EventDecoder { ev: coder } 88 | } 89 | 90 | #[inline] 91 | pub fn get_wrapped(&mut self) -> &mut MidiEvent { 92 | &mut self.ev 93 | } 94 | } 95 | 96 | pub struct EventEncoder { 97 | ev: MidiEvent, 98 | buffer_size: u32, 99 | } 100 | 101 | unsafe impl Send for EventEncoder {} 102 | 103 | impl EventEncoder { 104 | #[inline] 105 | pub fn new(buffer_size: u32) -> EventEncoder { 106 | EventEncoder { 107 | ev: MidiEvent::new(buffer_size).unwrap(), 108 | buffer_size, 109 | } 110 | } 111 | 112 | #[inline] 113 | pub fn get_buffer_size(&self) -> u32 { 114 | self.buffer_size 115 | } 116 | 117 | #[inline] 118 | pub fn resize_buffer(&mut self, bufsize: u32) -> Result<(), ()> { 119 | match self.ev.resize_buffer(bufsize) { 120 | Ok(_) => { 121 | self.buffer_size = bufsize; 122 | Ok(()) 123 | } 124 | Err(_) => Err(()), 125 | } 126 | } 127 | 128 | #[inline] 129 | pub fn get_wrapped(&mut self) -> &mut MidiEvent { 130 | &mut self.ev 131 | } 132 | } 133 | } 134 | 135 | const INITIAL_CODER_BUFFER_SIZE: usize = 32; 136 | 137 | pub struct MidiInput { 138 | ignore_flags: Ignore, 139 | seq: Option, 140 | } 141 | 142 | #[derive(Clone, PartialEq)] 143 | pub struct MidiInputPort { 144 | addr: Addr, 145 | } 146 | 147 | impl MidiInputPort { 148 | pub fn id(&self) -> String { 149 | format!("{}:{}", self.addr.client, self.addr.port) 150 | } 151 | } 152 | 153 | pub struct MidiInputConnection { 154 | subscription: Option, 155 | thread: Option, T)>>, 156 | vport: i32, // TODO: probably port numbers are only u8, therefore could use Option 157 | trigger_send_fd: Option, 158 | } 159 | 160 | type CallbackFn = dyn FnMut(u64, &[u8], &mut T) + Send; 161 | 162 | struct HandlerData { 163 | ignore_flags: Ignore, 164 | seq: Seq, 165 | trigger_rcv_fd: Option, 166 | callback: Box>, 167 | queue_id: i32, // an input queue is needed to get timestamped events 168 | } 169 | 170 | #[repr(transparent)] 171 | struct PipeFd(i32); 172 | 173 | impl PipeFd { 174 | fn get(&self) -> i32 { 175 | self.0 176 | } 177 | } 178 | 179 | impl Drop for PipeFd { 180 | fn drop(&mut self) { 181 | unsafe { 182 | libc::close(self.0); 183 | } 184 | } 185 | } 186 | 187 | impl MidiInput { 188 | pub fn new(client_name: &str) -> Result { 189 | let seq = match Seq::open(None, None, true) { 190 | Ok(s) => s, 191 | Err(_) => { 192 | return Err(InitError); 193 | } 194 | }; 195 | 196 | let c_client_name = CString::new(client_name).map_err(|_| InitError)?; 197 | seq.set_client_name(&c_client_name).map_err(|_| InitError)?; 198 | 199 | Ok(MidiInput { 200 | ignore_flags: Ignore::None, 201 | seq: Some(seq), 202 | }) 203 | } 204 | 205 | pub fn ignore(&mut self, flags: Ignore) { 206 | self.ignore_flags = flags; 207 | } 208 | 209 | pub(crate) fn ports_internal(&self) -> Vec { 210 | helpers::get_ports( 211 | self.seq.as_ref().unwrap(), 212 | PortCap::READ | PortCap::SUBS_READ, 213 | |p| crate::common::MidiInputPort { 214 | imp: MidiInputPort { addr: p.addr() }, 215 | }, 216 | ) 217 | } 218 | 219 | pub fn port_count(&self) -> usize { 220 | helpers::get_port_count( 221 | self.seq.as_ref().unwrap(), 222 | PortCap::READ | PortCap::SUBS_READ, 223 | ) 224 | } 225 | 226 | pub fn port_name(&self, port: &MidiInputPort) -> Result { 227 | helpers::get_port_name(self.seq.as_ref().unwrap(), port.addr) 228 | } 229 | 230 | fn init_queue(&mut self) -> i32 { 231 | let seq = self.seq.as_mut().unwrap(); 232 | let mut queue_id = 0; 233 | // Create the input queue 234 | if !cfg!(feature = "avoid_timestamping") { 235 | queue_id = seq.alloc_named_queue(c"midir queue").unwrap(); 236 | // Set arbitrary tempo (mm=100) and resolution (240) 237 | let qtempo = QueueTempo::empty().unwrap(); 238 | qtempo.set_tempo(600_000); 239 | qtempo.set_ppq(240); 240 | seq.set_queue_tempo(queue_id, &qtempo).unwrap(); 241 | let _ = seq.drain_output(); 242 | } 243 | 244 | queue_id 245 | } 246 | 247 | fn init_trigger(&mut self) -> Result<(PipeFd, PipeFd), ()> { 248 | let mut trigger_fds = [-1, -1]; 249 | 250 | if unsafe { libc::pipe(trigger_fds.as_mut_ptr()) } == -1 { 251 | Err(()) 252 | } else { 253 | // first element: receiver, second element: sender 254 | Ok((PipeFd(trigger_fds[0]), PipeFd(trigger_fds[1]))) 255 | } 256 | } 257 | 258 | fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result { 259 | let mut pinfo = PortInfo::empty().unwrap(); 260 | // these functions are private, and the values are zeroed already by `empty()` 261 | //pinfo.set_client(0); 262 | //pinfo.set_port(0); 263 | pinfo.set_capability(PortCap::WRITE | PortCap::SUBS_WRITE); 264 | pinfo.set_type(PortType::MIDI_GENERIC | PortType::APPLICATION); 265 | pinfo.set_midi_channels(16); 266 | 267 | if !cfg!(feature = "avoid_timestamping") { 268 | pinfo.set_timestamping(true); 269 | pinfo.set_timestamp_real(true); 270 | pinfo.set_timestamp_queue(queue_id); 271 | } 272 | 273 | pinfo.set_name(port_name); 274 | match self.seq.as_mut().unwrap().create_port(&pinfo) { 275 | Ok(_) => Ok(pinfo.get_port()), 276 | Err(_) => Err(()), 277 | } 278 | } 279 | 280 | fn start_input_queue(&mut self, queue_id: i32) { 281 | if !cfg!(feature = "avoid_timestamping") { 282 | let seq = self.seq.as_mut().unwrap(); 283 | let _ = seq.control_queue(queue_id, EventType::Start, 0, None); 284 | let _ = seq.drain_output(); 285 | } 286 | } 287 | 288 | pub fn connect( 289 | mut self, 290 | port: &MidiInputPort, 291 | port_name: &str, 292 | callback: F, 293 | data: T, 294 | ) -> Result, ConnectError> 295 | where 296 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 297 | { 298 | let trigger_fds = match self.init_trigger() { 299 | Ok(fds) => fds, 300 | Err(()) => { 301 | return Err(ConnectError::other( 302 | "could not create communication pipe for ALSA handler", 303 | self, 304 | )); 305 | } 306 | }; 307 | 308 | let queue_id = self.init_queue(); 309 | 310 | let src_pinfo = match self.seq.as_ref().unwrap().get_any_port_info(port.addr) { 311 | Ok(p) => p, 312 | Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 313 | }; 314 | 315 | let c_port_name = match CString::new(port_name) { 316 | Ok(c_port_name) => c_port_name, 317 | Err(_) => { 318 | return Err(ConnectError::other( 319 | "port_name must not contain null bytes", 320 | self, 321 | )) 322 | } 323 | }; 324 | 325 | let vport = match self.create_port(&c_port_name, queue_id) { 326 | Ok(vp) => vp, 327 | Err(_) => { 328 | return Err(ConnectError::other( 329 | "could not create ALSA input port", 330 | self, 331 | )); 332 | } 333 | }; 334 | 335 | // Make subscription 336 | let sub = PortSubscribe::empty().unwrap(); 337 | sub.set_sender(src_pinfo.addr()); 338 | sub.set_dest(Addr { 339 | client: self.seq.as_ref().unwrap().client_id().unwrap(), 340 | port: vport, 341 | }); 342 | if self.seq.as_ref().unwrap().subscribe_port(&sub).is_err() { 343 | return Err(ConnectError::other( 344 | "could not create ALSA input subscription", 345 | self, 346 | )); 347 | } 348 | let subscription = sub; 349 | 350 | // Start the input queue 351 | self.start_input_queue(queue_id); 352 | 353 | // Start our MIDI input thread. 354 | let handler_data = HandlerData { 355 | ignore_flags: self.ignore_flags, 356 | seq: self.seq.take().unwrap(), 357 | trigger_rcv_fd: Some(trigger_fds.0), 358 | callback: Box::new(callback), 359 | queue_id, 360 | }; 361 | 362 | let threadbuilder = Builder::new(); 363 | let name = format!("midir ALSA input handler (port '{}')", port_name); 364 | let threadbuilder = threadbuilder.name(name); 365 | let thread = match threadbuilder.spawn(move || { 366 | let mut d = data; 367 | let h = handle_input(handler_data, &mut d); 368 | (h, d) // return both the handler data and the user data 369 | }) { 370 | Ok(handle) => handle, 371 | Err(_) => { 372 | //unsafe { snd_seq_unsubscribe_port(self.seq.as_mut_ptr(), sub.as_ptr()) }; 373 | return Err(ConnectError::other( 374 | "could not start ALSA input handler thread", 375 | self, 376 | )); 377 | } 378 | }; 379 | 380 | Ok(MidiInputConnection { 381 | subscription: Some(subscription), 382 | thread: Some(thread), 383 | vport, 384 | trigger_send_fd: Some(trigger_fds.1), 385 | }) 386 | } 387 | 388 | pub fn create_virtual( 389 | mut self, 390 | port_name: &str, 391 | callback: F, 392 | data: T, 393 | ) -> Result, ConnectError> 394 | where 395 | F: FnMut(u64, &[u8], &mut T) + Send + 'static, 396 | { 397 | let trigger_fds = match self.init_trigger() { 398 | Ok(fds) => fds, 399 | Err(()) => { 400 | return Err(ConnectError::other( 401 | "could not create communication pipe for ALSA handler", 402 | self, 403 | )); 404 | } 405 | }; 406 | 407 | let queue_id = self.init_queue(); 408 | 409 | let c_port_name = match CString::new(port_name) { 410 | Ok(c_port_name) => c_port_name, 411 | Err(_) => { 412 | return Err(ConnectError::other( 413 | "port_name must not contain null bytes", 414 | self, 415 | )) 416 | } 417 | }; 418 | 419 | let vport = match self.create_port(&c_port_name, queue_id) { 420 | Ok(vp) => vp, 421 | Err(_) => { 422 | return Err(ConnectError::other( 423 | "could not create ALSA input port", 424 | self, 425 | )); 426 | } 427 | }; 428 | 429 | // Start the input queue 430 | self.start_input_queue(queue_id); 431 | 432 | // Start our MIDI input thread. 433 | let handler_data = HandlerData { 434 | ignore_flags: self.ignore_flags, 435 | seq: self.seq.take().unwrap(), 436 | trigger_rcv_fd: Some(trigger_fds.0), 437 | callback: Box::new(callback), 438 | queue_id, 439 | }; 440 | 441 | let threadbuilder = Builder::new(); 442 | let thread = match threadbuilder.spawn(move || { 443 | let mut d = data; 444 | let h = handle_input(handler_data, &mut d); 445 | (h, d) // return both the handler data and the user data 446 | }) { 447 | Ok(handle) => handle, 448 | Err(_) => { 449 | //unsafe { snd_seq_unsubscribe_port(self.seq.as_mut_ptr(), sub.as_ptr()) }; 450 | return Err(ConnectError::other( 451 | "could not start ALSA input handler thread", 452 | self, 453 | )); 454 | } 455 | }; 456 | 457 | Ok(MidiInputConnection { 458 | subscription: None, 459 | thread: Some(thread), 460 | vport, 461 | trigger_send_fd: Some(trigger_fds.1), 462 | }) 463 | } 464 | } 465 | 466 | impl MidiInputConnection { 467 | pub fn close(mut self) -> (MidiInput, T) { 468 | let (handler_data, user_data) = self.close_internal(); 469 | 470 | ( 471 | MidiInput { 472 | ignore_flags: handler_data.ignore_flags, 473 | seq: Some(handler_data.seq), 474 | }, 475 | user_data, 476 | ) 477 | } 478 | 479 | /// This must only be called if the handler thread has not yet been shut down 480 | fn close_internal(&mut self) -> (HandlerData, T) { 481 | // Request the thread to stop. 482 | let _res = unsafe { 483 | libc::write( 484 | self.trigger_send_fd 485 | .as_ref() 486 | .expect("send_fd already taken") 487 | .get(), 488 | &false as *const bool as *const _, 489 | mem::size_of::() as libc::size_t, 490 | ) 491 | }; 492 | 493 | let thread = self.thread.take().unwrap(); 494 | // Join the thread to get the handler_data back 495 | let (mut handler_data, user_data) = match thread.join() { 496 | Ok(data) => data, 497 | // TODO: handle this more gracefully? 498 | Err(e) => { 499 | if let Some(e) = e.downcast_ref::<&'static str>() { 500 | panic!("Error when joining ALSA thread: {}", e); 501 | } else { 502 | panic!("Unknown error when joining ALSA thread: {:?}", e); 503 | } 504 | } 505 | }; 506 | 507 | // TODO: find out why snd_seq_unsubscribe_port takes a long time if there was not yet any input message 508 | if let Some(ref subscription) = self.subscription { 509 | let _ = handler_data 510 | .seq 511 | .unsubscribe_port(subscription.get_sender(), subscription.get_dest()); 512 | } 513 | 514 | // Close the trigger fds 515 | handler_data.trigger_rcv_fd = None; 516 | self.trigger_send_fd = None; 517 | 518 | // Stop and free the input queue 519 | if !cfg!(feature = "avoid_timestamping") { 520 | let _ = handler_data 521 | .seq 522 | .control_queue(handler_data.queue_id, EventType::Stop, 0, None); 523 | let _ = handler_data.seq.drain_output(); 524 | let _ = handler_data.seq.free_queue(handler_data.queue_id); 525 | } 526 | 527 | // Delete the port 528 | let _ = handler_data.seq.delete_port(self.vport); 529 | 530 | (handler_data, user_data) 531 | } 532 | } 533 | 534 | impl Drop for MidiInputConnection { 535 | fn drop(&mut self) { 536 | // Use `self.thread` as a flag whether the connection has already been dropped 537 | if self.thread.is_some() { 538 | self.close_internal(); 539 | } 540 | } 541 | } 542 | 543 | pub struct MidiOutput { 544 | seq: Option, // TODO: if `Seq` is marked as non-zero, this should just be pointer-sized 545 | } 546 | 547 | #[derive(Clone, PartialEq)] 548 | pub struct MidiOutputPort { 549 | addr: Addr, 550 | } 551 | 552 | impl MidiOutputPort { 553 | pub fn id(&self) -> String { 554 | format!("{}:{}", self.addr.client, self.addr.port) 555 | } 556 | } 557 | 558 | pub struct MidiOutputConnection { 559 | seq: Option, 560 | vport: i32, 561 | coder: helpers::EventEncoder, 562 | subscription: Option, 563 | } 564 | 565 | impl MidiOutput { 566 | pub fn new(client_name: &str) -> Result { 567 | let seq = match Seq::open(None, Some(Direction::Playback), true) { 568 | Ok(s) => s, 569 | Err(_) => { 570 | return Err(InitError); 571 | } 572 | }; 573 | 574 | let c_client_name = CString::new(client_name).map_err(|_| InitError)?; 575 | seq.set_client_name(&c_client_name).map_err(|_| InitError)?; 576 | 577 | Ok(MidiOutput { seq: Some(seq) }) 578 | } 579 | 580 | pub(crate) fn ports_internal(&self) -> Vec { 581 | helpers::get_ports( 582 | self.seq.as_ref().unwrap(), 583 | PortCap::WRITE | PortCap::SUBS_WRITE, 584 | |p| crate::common::MidiOutputPort { 585 | imp: MidiOutputPort { addr: p.addr() }, 586 | }, 587 | ) 588 | } 589 | 590 | pub fn port_count(&self) -> usize { 591 | helpers::get_port_count( 592 | self.seq.as_ref().unwrap(), 593 | PortCap::WRITE | PortCap::SUBS_WRITE, 594 | ) 595 | } 596 | 597 | pub fn port_name(&self, port: &MidiOutputPort) -> Result { 598 | helpers::get_port_name(self.seq.as_ref().unwrap(), port.addr) 599 | } 600 | 601 | pub fn connect( 602 | mut self, 603 | port: &MidiOutputPort, 604 | port_name: &str, 605 | ) -> Result> { 606 | let pinfo = match self.seq.as_ref().unwrap().get_any_port_info(port.addr) { 607 | Ok(p) => p, 608 | Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)), 609 | }; 610 | 611 | let c_port_name = match CString::new(port_name) { 612 | Ok(c_port_name) => c_port_name, 613 | Err(_) => { 614 | return Err(ConnectError::other( 615 | "port_name must not contain null bytes", 616 | self, 617 | )) 618 | } 619 | }; 620 | 621 | let vport = match self.seq.as_ref().unwrap().create_simple_port( 622 | &c_port_name, 623 | PortCap::READ | PortCap::SUBS_READ, 624 | PortType::MIDI_GENERIC | PortType::APPLICATION, 625 | ) { 626 | Ok(vport) => vport, 627 | Err(_) => { 628 | return Err(ConnectError::other( 629 | "could not create ALSA output port", 630 | self, 631 | )) 632 | } 633 | }; 634 | 635 | // Make subscription 636 | let sub = PortSubscribe::empty().unwrap(); 637 | sub.set_sender(Addr { 638 | client: self.seq.as_ref().unwrap().client_id().unwrap(), 639 | port: vport, 640 | }); 641 | sub.set_dest(pinfo.addr()); 642 | sub.set_time_update(true); 643 | sub.set_time_real(true); 644 | if self.seq.as_ref().unwrap().subscribe_port(&sub).is_err() { 645 | return Err(ConnectError::other( 646 | "could not create ALSA output subscription", 647 | self, 648 | )); 649 | } 650 | 651 | Ok(MidiOutputConnection { 652 | seq: self.seq.take(), 653 | vport, 654 | coder: helpers::EventEncoder::new(INITIAL_CODER_BUFFER_SIZE as u32), 655 | subscription: Some(sub), 656 | }) 657 | } 658 | 659 | pub fn create_virtual( 660 | mut self, 661 | port_name: &str, 662 | ) -> Result> { 663 | let c_port_name = match CString::new(port_name) { 664 | Ok(c_port_name) => c_port_name, 665 | Err(_) => { 666 | return Err(ConnectError::other( 667 | "port_name must not contain null bytes", 668 | self, 669 | )) 670 | } 671 | }; 672 | 673 | let vport = match self.seq.as_ref().unwrap().create_simple_port( 674 | &c_port_name, 675 | PortCap::READ | PortCap::SUBS_READ, 676 | PortType::MIDI_GENERIC | PortType::APPLICATION, 677 | ) { 678 | Ok(vport) => vport, 679 | Err(_) => { 680 | return Err(ConnectError::other( 681 | "could not create ALSA output port", 682 | self, 683 | )) 684 | } 685 | }; 686 | 687 | Ok(MidiOutputConnection { 688 | seq: self.seq.take(), 689 | vport, 690 | coder: helpers::EventEncoder::new(INITIAL_CODER_BUFFER_SIZE as u32), 691 | subscription: None, 692 | }) 693 | } 694 | } 695 | 696 | impl MidiOutputConnection { 697 | pub fn close(mut self) -> MidiOutput { 698 | self.close_internal(); 699 | 700 | MidiOutput { 701 | seq: self.seq.take(), 702 | } 703 | } 704 | 705 | pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> { 706 | let nbytes = message.len(); 707 | assert!(nbytes <= u32::MAX as usize); 708 | 709 | if nbytes > self.coder.get_buffer_size() as usize 710 | && self.coder.resize_buffer(nbytes as u32).is_err() 711 | { 712 | return Err(SendError::Other("could not resize ALSA encoding buffer")); 713 | } 714 | 715 | let mut ev = match self.coder.get_wrapped().encode(message) { 716 | Ok((_, Some(ev))) => ev, 717 | _ => return Err(SendError::InvalidData("ALSA encoder reported invalid data")), 718 | }; 719 | 720 | ev.set_source(self.vport); 721 | ev.set_subs(); 722 | ev.set_direct(); 723 | 724 | // Send the event. 725 | if self 726 | .seq 727 | .as_ref() 728 | .unwrap() 729 | .event_output_direct(&mut ev) 730 | .is_err() 731 | { 732 | return Err(SendError::Other("could not send encoded ALSA message")); 733 | } 734 | 735 | let _ = self.seq.as_mut().unwrap().drain_output(); 736 | Ok(()) 737 | } 738 | 739 | fn close_internal(&mut self) { 740 | let seq = self.seq.as_mut().unwrap(); 741 | if let Some(ref subscription) = self.subscription { 742 | let _ = seq.unsubscribe_port(subscription.get_sender(), subscription.get_dest()); 743 | } 744 | let _ = seq.delete_port(self.vport); 745 | } 746 | } 747 | 748 | impl Drop for MidiOutputConnection { 749 | fn drop(&mut self) { 750 | if self.seq.is_some() { 751 | self.close_internal(); 752 | } 753 | } 754 | } 755 | 756 | fn handle_input(mut data: HandlerData, user_data: &mut T) -> HandlerData { 757 | use alsa::seq::Connect; 758 | use alsa::PollDescriptors; 759 | use libc::pollfd; 760 | 761 | const INVALID_POLLFD: pollfd = pollfd { 762 | fd: -1, 763 | events: 0, 764 | revents: 0, 765 | }; 766 | 767 | let mut continue_sysex: bool = false; 768 | 769 | // ALSA documentation says: 770 | // The required buffer size for a sequencer event it as most 12 bytes, except for System Exclusive events (which we handle separately) 771 | let mut buffer = [0; 12]; 772 | 773 | let mut coder = helpers::EventDecoder::new(false); 774 | 775 | let poll_desc_info = (&data.seq, Some(Direction::Capture)); 776 | let mut poll_fds = vec![INVALID_POLLFD; poll_desc_info.count() + 1]; 777 | poll_fds[0] = pollfd { 778 | fd: data.trigger_rcv_fd.as_ref().unwrap().get(), 779 | events: libc::POLLIN, 780 | revents: 0, 781 | }; 782 | 783 | poll_desc_info.fill(&mut poll_fds[1..]).unwrap(); 784 | 785 | let mut message = MidiMessage::new(); 786 | 787 | { 788 | // open scope where we can borrow data.seq 789 | let mut seq_input = data.seq.input(); 790 | 791 | let mut do_input = true; 792 | while do_input { 793 | if let Ok(0) = seq_input.event_input_pending(true) { 794 | // No data pending 795 | if helpers::poll(&mut poll_fds, -1) >= 0 { 796 | // Read from our "channel" whether we should stop the thread 797 | if poll_fds[0].revents & libc::POLLIN != 0 { 798 | let _res = unsafe { 799 | libc::read( 800 | poll_fds[0].fd, 801 | std::ptr::addr_of_mut!(do_input) as *mut libc::c_void, 802 | mem::size_of::() as libc::size_t, 803 | ) 804 | }; 805 | } 806 | } 807 | continue; 808 | } 809 | 810 | // This is a bit weird, but we now have to decode an ALSA MIDI 811 | // event (back) into MIDI bytes. We'll ignore non-MIDI types. 812 | 813 | // The ALSA sequencer has a maximum buffer size for MIDI sysex 814 | // events of 256 bytes. If a device sends sysex messages larger 815 | // than this, they are segmented into 256 byte chunks. So, 816 | // we'll watch for this and concatenate sysex chunks into a 817 | // single sysex message if necessary. 818 | // 819 | // TODO: Figure out if this is still true (seems to not be the case) 820 | // If not (i.e., each event represents a complete message), we can 821 | // call the user callback with the byte buffer directly, without the 822 | // copying to `message.bytes` first. 823 | if !continue_sysex { 824 | message.bytes.clear() 825 | } 826 | 827 | let ignore_flags = data.ignore_flags; 828 | 829 | // If here, there should be data. 830 | let mut ev = match seq_input.event_input() { 831 | Ok(ev) => ev, 832 | Err(ref e) if e.errno() == libc::ENOSPC => { 833 | error!("Error in handle_input: ALSA MIDI input buffer overrun!"); 834 | continue; 835 | } 836 | Err(ref e) if e.errno() == libc::EAGAIN => { 837 | error!("Error in handle_input: no input event from ALSA MIDI input buffer!"); 838 | continue; 839 | } 840 | Err(ref e) => { 841 | error!( 842 | "Error in handle_input: unknown ALSA MIDI input error ({})!", 843 | e 844 | ); 845 | continue; 846 | } 847 | }; 848 | 849 | let do_decode = match ev.get_type() { 850 | EventType::PortSubscribed => { 851 | debug!("Notice from handle_input: ALSA port connection made!"); 852 | false 853 | } 854 | EventType::PortUnsubscribed => { 855 | if log_enabled!(Level::Debug) { 856 | debug!("Notice from handle_input: ALSA port connection has closed!"); 857 | let connect = ev.get_data::().unwrap(); 858 | debug!( 859 | "sender = {}:{}, dest = {}:{}", 860 | connect.sender.client, 861 | connect.sender.port, 862 | connect.dest.client, 863 | connect.dest.port 864 | ); 865 | } 866 | false 867 | } 868 | EventType::Qframe => { 869 | // MIDI time code 870 | !ignore_flags.contains(Ignore::Time) 871 | } 872 | EventType::Tick => { 873 | // 0xF9 ... MIDI timing tick 874 | !ignore_flags.contains(Ignore::Time) 875 | } 876 | EventType::Clock => { 877 | // 0xF8 ... MIDI timing (clock) tick 878 | !ignore_flags.contains(Ignore::Time) 879 | } 880 | EventType::Sensing => { 881 | // Active sensing 882 | !ignore_flags.contains(Ignore::ActiveSense) 883 | } 884 | EventType::Sysex => { 885 | if !ignore_flags.contains(Ignore::Sysex) { 886 | // Directly copy the data from the external buffer to our message 887 | message.bytes.extend_from_slice(ev.get_ext().unwrap()); 888 | continue_sysex = *message.bytes.last().unwrap() != 0xF7; 889 | } 890 | false // don't ever decode sysex messages (it would unnecessarily copy the message content to another buffer) 891 | } 892 | _ => true, 893 | }; 894 | 895 | // NOTE: SysEx messages have already been "decoded" at this point! 896 | if do_decode { 897 | if let Ok(nbytes) = coder.get_wrapped().decode(&mut buffer, &mut ev) { 898 | if nbytes > 0 { 899 | message.bytes.extend_from_slice(&buffer[0..nbytes]); 900 | } 901 | } 902 | } 903 | 904 | if message.bytes.is_empty() || continue_sysex { 905 | continue; 906 | } 907 | 908 | // Calculate the time stamp: 909 | // Use the ALSA sequencer event time data. 910 | // (thanks to Pedro Lopez-Cabanillas!). 911 | let alsa_time = ev.get_time().unwrap(); 912 | let secs = alsa_time.as_secs(); 913 | let nsecs = alsa_time.subsec_nanos(); 914 | 915 | message.timestamp = (secs as u64 * 1_000_000) + (nsecs as u64 / 1_000); 916 | (data.callback)(message.timestamp, &message.bytes, user_data); 917 | } 918 | } // close scope where data.seq is borrowed 919 | data // return data back to thread owner 920 | } 921 | --------------------------------------------------------------------------------