├── 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 [](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::