summaryrefslogtreecommitdiffstats
path: root/third_party/rust/midir
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/rust/midir
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/midir')
-rw-r--r--third_party/rust/midir/.cargo-checksum.json1
-rw-r--r--third_party/rust/midir/CHANGELOG.md50
-rw-r--r--third_party/rust/midir/Cargo.toml97
-rw-r--r--third_party/rust/midir/LICENSE21
-rw-r--r--third_party/rust/midir/README.md23
-rw-r--r--third_party/rust/midir/azure-pipelines-template.yml106
-rw-r--r--third_party/rust/midir/azure-pipelines.yml26
-rw-r--r--third_party/rust/midir/examples/test_forward.rs65
-rw-r--r--third_party/rust/midir/examples/test_list_ports.rs46
-rw-r--r--third_party/rust/midir/examples/test_play.rs74
-rw-r--r--third_party/rust/midir/examples/test_read_input.rs58
-rw-r--r--third_party/rust/midir/examples/test_reuse.rs82
-rw-r--r--third_party/rust/midir/examples/test_sysex.rs79
-rwxr-xr-xthird_party/rust/midir/src/backend/alsa/mod.rs733
-rw-r--r--third_party/rust/midir/src/backend/coremidi/mod.rs351
-rw-r--r--third_party/rust/midir/src/backend/jack/mod.rs364
-rw-r--r--third_party/rust/midir/src/backend/jack/wrappers.rs250
-rw-r--r--third_party/rust/midir/src/backend/mod.rs22
-rw-r--r--third_party/rust/midir/src/backend/webmidi/mod.rs250
-rw-r--r--third_party/rust/midir/src/backend/winmm/handler.rs85
-rw-r--r--third_party/rust/midir/src/backend/winmm/mod.rs536
-rw-r--r--third_party/rust/midir/src/backend/winrt/mod.rs299
-rw-r--r--third_party/rust/midir/src/common.rs315
-rw-r--r--third_party/rust/midir/src/errors.rs114
-rw-r--r--third_party/rust/midir/src/lib.rs65
-rw-r--r--third_party/rust/midir/src/os/mod.rs1
-rw-r--r--third_party/rust/midir/src/os/unix.rs27
-rw-r--r--third_party/rust/midir/tests/virtual.rs72
28 files changed, 4212 insertions, 0 deletions
diff --git a/third_party/rust/midir/.cargo-checksum.json b/third_party/rust/midir/.cargo-checksum.json
new file mode 100644
index 0000000000..ef3648bcbb
--- /dev/null
+++ b/third_party/rust/midir/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"10db6f8dbb1c5566e75f2eeda6b2ee8bb44fe4a76f57e0bfb98c62f7f8c04f89","Cargo.toml":"bf775ce409ea369d81cf5c4a02ddcf96a24f819e60bafbfb0f107a2f84b10a15","LICENSE":"6fe6f623b1fa80e90679aee2f917d8978a184988ebb995ebc254cc9633903cac","README.md":"4131b953217e77a4463fde307ba3262b4df11732c1ff209668df12dff3c73ffc","azure-pipelines-template.yml":"c787791a94e654226a299aaa875fcc48f6eedf4dae631855cb5a7067891dbe3a","azure-pipelines.yml":"1b4fab0afacc66732a385cb6e5b213c170fc9717219a03ccda9c5db78cd461dd","examples/test_forward.rs":"6cb060aba7e8c39eaf53ea95a72d4c7939ffb4bebc82c291135fdc35495078ce","examples/test_list_ports.rs":"41ba21ab1e56d76206abc8b291d27050cb1a788372f00f6761c78f03fb5981ff","examples/test_play.rs":"22630e46af9628d8193ad8e19ff095ad02542b7ab697be4e513da78210ad5c0c","examples/test_read_input.rs":"4901f18435c3f8021750ccd4687abe92194ab38f1e7721896a6a31f6650d524c","examples/test_reuse.rs":"fdb3b430aec42c7c648fbecf22e6c726ef8a20638936a1a70fb373dff94c0632","examples/test_sysex.rs":"ea06427a644c3639f1c49271be5d16c9d3890d3741eb6ebf2ff64d2f7fd36e96","src/backend/alsa/mod.rs":"3dfbd12d82988d5c749ec7fba87f7a4c659761cfdf50a2a2dc7afbae9a254bed","src/backend/coremidi/mod.rs":"f827cbc5db7086ea58c5927213a2c3e0246244d5939c2ba0ff787caae7089511","src/backend/jack/mod.rs":"8f2eace3e9046ec6de8c7fc37d3502d2b971a73fe2a96e5c2a423d51445f1505","src/backend/jack/wrappers.rs":"f18718f234e41c91bb5463546fbbe61be64e9581a4fae6ef2de20cafae487298","src/backend/mod.rs":"1a8106889ecd053af27b3a72515bfb286da1b08bb90909fa6d4e7b816b50c447","src/backend/webmidi/mod.rs":"4af5b288833ee99f047a638b368eca293f89356f1e82147c9a9c1633d950955d","src/backend/winmm/handler.rs":"45b36067fd280a38943f385d3d7f6885d7448153f53e9c8f66b58b484535ad1c","src/backend/winmm/mod.rs":"d3d089e7d505dd66f596d7175ba6df2be4260e38f011009e5cbd5ace1de188d1","src/backend/winrt/mod.rs":"ca7ac4ac310e7f6a6c28dd6374bfe97b38ed8656c7ca343494264cce45f93ae6","src/common.rs":"2cab2e987428522ca601544b516b64b858859730fbd1be0e53c828e82025319d","src/errors.rs":"495ba80f9dcfeefd343b460b74549b12cb1825c3e1b315848f859d0b4d66ddbe","src/lib.rs":"ecde030ca02a90a99577cd71446857a2c00aee8ff1bc7890c54a5d0d22d2be2c","src/os/mod.rs":"507dfa95e57805c489a883dcf9efddcb718d5178267f296294f72b3c397c12c7","src/os/unix.rs":"a1977659d270fcf31111d4446b949d2760d76e2077639e6008d634800861b77b","tests/virtual.rs":"b47501eeb313f3e255d2d1888c333ff994d958865272929fe7bf116be45b6805"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/midir/CHANGELOG.md b/third_party/rust/midir/CHANGELOG.md
new file mode 100644
index 0000000000..83a0987a7b
--- /dev/null
+++ b/third_party/rust/midir/CHANGELOG.md
@@ -0,0 +1,50 @@
+# Changelog
+
+All major changes to this project will be documented in this file.
+
+## [Unreleased]
+
+- ...
+
+## [0.7.0] - 2020-09-05
+
+- Update some Linux dependencies (`alsa`, `nix`)
+- Improve error handling for `MMSYSERR_ALLOCATED` (Windows)
+
+## [0.6.2] - 2020-07-21
+
+- Remove deprecated usage of `mem::uninitialized`
+- Switch from `winrt-rust` to `winrt-rs` for WinRT backend
+
+## [0.6.1] - 2020-06-04
+
+- Implement `Clone` for port structures
+- Add trait that abstracts over input and output
+
+## [0.6.0] - 2020-05-11
+
+- Upgrade to winapi 0.3
+- Add WinRT backend
+- Add WebMIDI backend
+- Use platform-specific representation of port identifiers instead of indices
+
+## [0.5.0] - 2017-12-09
+
+- Switch to absolute μs timestamps
+
+## [0.4.0] - 2017-09-27
+
+- Add CoreMIDI backend
+- Use `usize` for port numbers and counts
+
+## [0.3.2] - 2017-04-06
+
+- Use `alsa-rs` instead of homegrown wrapper
+
+## [0.3.1] - 2017-03-21
+
+- Fix crates.io badges
+
+## [0.3.0] - 2017-03-21
+
+- Fix compilation on ARM platforms \ No newline at end of file
diff --git a/third_party/rust/midir/Cargo.toml b/third_party/rust/midir/Cargo.toml
new file mode 100644
index 0000000000..f84a0a79cb
--- /dev/null
+++ b/third_party/rust/midir/Cargo.toml
@@ -0,0 +1,97 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+name = "midir"
+version = "0.7.0"
+authors = ["Patrick Reisert"]
+description = "A cross-platform, realtime MIDI processing library, inspired by RtMidi."
+readme = "README.md"
+keywords = [
+ "midi",
+ "audio",
+ "music",
+ "sound",
+]
+categories = [
+ "multimedia::audio",
+ "api-bindings",
+]
+license = "MIT"
+repository = "https://github.com/Boddlnagg/midir"
+
+[dependencies]
+bitflags = "1.2"
+memalloc = "0.1.0"
+
+[dependencies.jack-sys]
+version = "0.1.0"
+optional = true
+
+[dependencies.libc]
+version = "0.2.21"
+optional = true
+
+[dependencies.winrt]
+version = "0.7.0"
+optional = true
+
+[features]
+avoid_timestamping = []
+default = []
+jack = [
+ "jack-sys",
+ "libc",
+]
+
+[target."cfg(target_arch = \"wasm32\")".dependencies]
+js-sys = "0.3"
+wasm-bindgen = "0.2"
+
+[target."cfg(target_arch = \"wasm32\")".dependencies.web-sys]
+version = "0.3"
+features = [
+ "Event",
+ "Navigator",
+ "Window",
+ "MidiAccess",
+ "MidiInput",
+ "MidiInputMap",
+ "MidiMessageEvent",
+ "MidiOptions",
+ "MidiOutput",
+ "MidiOutputMap",
+ "MidiPort",
+ "MidiPortType",
+]
+
+[target."cfg(target_arch = \"wasm32\")".dev-dependencies]
+wasm-bindgen-test = "0.2"
+
+[target."cfg(target_os = \"linux\")".dependencies]
+alsa = "0.7"
+libc = "0.2.21"
+
+[target."cfg(target_os = \"macos\")".dependencies]
+coremidi = "0.6.0"
+
+[target."cfg(windows)".dependencies.winapi]
+version = "0.3"
+features = [
+ "mmsystem",
+ "mmeapi",
+]
+
+[workspace]
+members = [
+ ".",
+ "examples/browser",
+]
diff --git a/third_party/rust/midir/LICENSE b/third_party/rust/midir/LICENSE
new file mode 100644
index 0000000000..0d67973af2
--- /dev/null
+++ b/third_party/rust/midir/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Patrick Reisert and the RtMidi contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/rust/midir/README.md b/third_party/rust/midir/README.md
new file mode 100644
index 0000000000..42e1012411
--- /dev/null
+++ b/third_party/rust/midir/README.md
@@ -0,0 +1,23 @@
+# midir [![crates.io](https://img.shields.io/crates/v/midir.svg)](https://crates.io/crates/midir) [![Build Status](https://dev.azure.com/Boddlnagg/midir/_apis/build/status/Boddlnagg.midir?branchName=master)](https://dev.azure.com/Boddlnagg/midir/_build/latest?definitionId=1)
+
+Cross-platform, realtime MIDI processing in Rust. This is a friendly fork with
+small changes required for vendoring the crate in Firefox. It will go away as
+soon as we will be able to vendor the upstream crate.
+
+## Features
+**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!
+
+<sup>* With the exception of message queues, but these can be implemented on top of callbacks using e.g. Rust's channels.</sup>
+
+**midir** currently supports the following platforms/backends:
+- [x] ALSA (Linux)
+- [x] WinMM (Windows)
+- [x] CoreMIDI (macOS, iOS (untested))
+- [x] WinRT (Windows 8+), enable the `winrt` feature
+- [x] Jack (Linux, macOS), enable the `jack` feature
+- [x] Web MIDI (Chrome, Opera, perhaps others browsers)
+
+A higher-level API for parsing and assembling MIDI messages might be added in the future.
+
+## Documentation & Example
+API docs can be found at [docs.rs](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.
diff --git a/third_party/rust/midir/azure-pipelines-template.yml b/third_party/rust/midir/azure-pipelines-template.yml
new file mode 100644
index 0000000000..a76af85ba0
--- /dev/null
+++ b/third_party/rust/midir/azure-pipelines-template.yml
@@ -0,0 +1,106 @@
+jobs:
+- job: ${{ parameters.name }}
+ pool:
+ vmImage: ${{ parameters.vmImage }}
+ strategy:
+ matrix:
+ stable:
+ rustup_toolchain: stable-${{ parameters.target }}
+ features: ""
+ ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}:
+ beta:
+ rustup_toolchain: beta-${{ parameters.target }}
+ features: ""
+ nightly:
+ rustup_toolchain: nightly-${{ parameters.target }}
+ features: ""
+ ${{ if startsWith(parameters.name, 'Windows') }}:
+ stable-winrt:
+ rustup_toolchain: stable-${{ parameters.target }}
+ features: "winrt"
+ ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}:
+ beta-winrt:
+ rustup_toolchain: beta-${{ parameters.target }}
+ features: "winrt"
+ nightly-winrt:
+ rustup_toolchain: nightly-${{ parameters.target }}
+ features: "winrt"
+ ${{ if and(not(startsWith(parameters.name, 'Windows')), not(endsWith(parameters.name, 'WASM'))) }}:
+ stable-jack:
+ rustup_toolchain: stable-${{ parameters.target }}
+ features: "jack"
+ ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}:
+ beta-jack:
+ rustup_toolchain: beta-${{ parameters.target }}
+ features: "jack"
+ nightly-jack:
+ rustup_toolchain: nightly-${{ parameters.target }}
+ features: "jack"
+ steps:
+ - ${{ if not(startsWith(parameters.name, 'Windows')) }}:
+ # Linux and macOS
+ - script: |
+ curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN
+ export PATH="$HOME/.cargo/bin:$PATH"
+ echo "##vso[task.setvariable variable=PATH;]$PATH"
+ displayName: Install Rust
+ - ${{ if and(startsWith(parameters.name, 'Linux'), not(endsWith(parameters.name, 'WASM'))) }}:
+ # Linux only
+ - script: |
+ sudo apt-get update && sudo apt-get install -y libasound2-dev libjack-jackd2-dev
+ displayName: Install ALSA and Jack dependencies
+ - ${{ if startsWith(parameters.name, 'macOS') }}:
+ # macOS only
+ - script: |
+ if [ "$FEATURES" = "jack" ]; then
+ curl -LOS https://github.com/jackaudio/jackaudio.github.com/releases/download/1.9.11/JackOSX.0.92_b3.zip && unzip JackOSX.0.92_b3.zip && sudo installer -pkg JackOSX.0.92_b3.pkg -target /
+ fi
+ displayName: Install Jack dependencies
+ - ${{ if endsWith(parameters.name, 'WASM') }}:
+ # WASM only
+ - script: |
+ rustup target add wasm32-unknown-unknown
+ displayName: Add wasm32-unknown-unknown target
+ - ${{ if startsWith(parameters.name, 'Windows') }}:
+ # Windows
+ - script: |
+ curl -sSf -o rustup-init.exe https://win.rustup.rs
+ rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN%
+ set PATH=%PATH%;%USERPROFILE%\.cargo\bin
+ echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
+ displayName: Install Rust (Windows)
+ # All platforms
+ - script: |
+ rustc -Vv
+ cargo -V
+ displayName: Query installed versions
+ - ${{ if not(endsWith(parameters.name, 'WASM')) }}:
+ # Use bash for cross-platform env variable syntax
+ - bash: cargo build --verbose --features "$FEATURES"
+ displayName: Build
+ - bash: cargo build --features "$FEATURES" --example test_list_ports
+ displayName: Build example program
+ - ${{ if not(startsWith(parameters.name, 'Linux')) }}:
+ # Tests cannot run on Linux (missing ALSA driver) or with Jack (Jack not running)
+ - bash: |
+ if [[ "$FEATURES" != *"jack"* ]]; then
+ cargo test --verbose --features "$FEATURES"
+ fi
+ displayName: Run unit tests
+ - bash: |
+ if [[ "$FEATURES" != *"jack"* ]]; then
+ cargo run --features "$FEATURES" --example test_list_ports
+ fi
+ displayName: Run example program
+ - ${{ if endsWith(parameters.name, 'WASM') }}:
+ # WebAssembly
+ - script: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
+ displayName: Install wasm-pack
+ #- bash: cargo build --verbose --target wasm32-unknown-unknown --features "$FEATURES"
+ # displayName: Build
+ - bash: |
+ cd examples/browser
+ wasm-pack build --target=no-modules --dev
+ displayName: Build WASM example program
+ - bash: wasm-pack test --chrome
+ displayName: Run WASM tests
diff --git a/third_party/rust/midir/azure-pipelines.yml b/third_party/rust/midir/azure-pipelines.yml
new file mode 100644
index 0000000000..de8076ce34
--- /dev/null
+++ b/third_party/rust/midir/azure-pipelines.yml
@@ -0,0 +1,26 @@
+jobs:
+- template: azure-pipelines-template.yml
+ parameters:
+ name: macOS
+ vmImage: macOS-latest
+ target: x86_64-apple-darwin
+- template: azure-pipelines-template.yml
+ parameters:
+ name: Linux
+ vmImage: ubuntu-latest
+ target: x86_64-unknown-linux-gnu
+- template: azure-pipelines-template.yml
+ parameters:
+ name: Windows
+ vmImage: vs2017-win2016
+ target: x86_64-pc-windows-msvc
+- template: azure-pipelines-template.yml
+ parameters:
+ name: Windows_GNU
+ vmImage: vs2017-win2016
+ target: i686-pc-windows-gnu
+- template: azure-pipelines-template.yml
+ parameters:
+ name: Linux_WASM
+ vmImage: ubuntu-latest
+ target: x86_64-unknown-linux-gnu
diff --git a/third_party/rust/midir/examples/test_forward.rs b/third_party/rust/midir/examples/test_forward.rs
new file mode 100644
index 0000000000..1ecb142c2e
--- /dev/null
+++ b/third_party/rust/midir/examples/test_forward.rs
@@ -0,0 +1,65 @@
+extern crate midir;
+
+use std::io::{stdin, stdout, Write};
+use std::error::Error;
+
+use midir::{MidiInput, MidiOutput, MidiIO, Ignore};
+
+fn main() {
+ match run() {
+ Ok(_) => (),
+ Err(err) => println!("Error: {}", err)
+ }
+}
+
+#[cfg(not(target_arch = "wasm32"))] // conn_out is not `Send` in Web MIDI, which means it cannot be passed to connect
+fn run() -> Result<(), Box<dyn Error>> {
+ let mut midi_in = MidiInput::new("midir forwarding input")?;
+ midi_in.ignore(Ignore::None);
+ let midi_out = MidiOutput::new("midir forwarding output")?;
+
+ let in_port = select_port(&midi_in, "input")?;
+ println!();
+ let out_port = select_port(&midi_out, "output")?;
+
+ println!("\nOpening connections");
+ let in_port_name = midi_in.port_name(&in_port)?;
+ let out_port_name = midi_out.port_name(&out_port)?;
+
+ let mut conn_out = midi_out.connect(&out_port, "midir-forward")?;
+
+ // _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope
+ let _conn_in = midi_in.connect(&in_port, "midir-forward", move |stamp, message, _| {
+ conn_out.send(message).unwrap_or_else(|_| println!("Error when forwarding message ..."));
+ println!("{}: {:?} (len = {})", stamp, message, message.len());
+ }, ())?;
+
+ println!("Connections open, forwarding from '{}' to '{}' (press enter to exit) ...", in_port_name, out_port_name);
+
+ let mut input = String::new();
+ stdin().read_line(&mut input)?; // wait for next enter key press
+
+ println!("Closing connections");
+ Ok(())
+}
+
+fn select_port<T: MidiIO>(midi_io: &T, descr: &str) -> Result<T::Port, Box<dyn Error>> {
+ println!("Available {} ports:", descr);
+ let midi_ports = midi_io.ports();
+ for (i, p) in midi_ports.iter().enumerate() {
+ println!("{}: {}", i, midi_io.port_name(p)?);
+ }
+ print!("Please select {} port: ", descr);
+ stdout().flush()?;
+ let mut input = String::new();
+ stdin().read_line(&mut input)?;
+ let port = midi_ports.get(input.trim().parse::<usize>()?)
+ .ok_or("Invalid port number")?;
+ Ok(port.clone())
+}
+
+#[cfg(target_arch = "wasm32")]
+fn run() -> Result<(), Box<dyn Error>> {
+ println!("test_forward cannot run on Web MIDI");
+ Ok(())
+}
diff --git a/third_party/rust/midir/examples/test_list_ports.rs b/third_party/rust/midir/examples/test_list_ports.rs
new file mode 100644
index 0000000000..4bc4c6b948
--- /dev/null
+++ b/third_party/rust/midir/examples/test_list_ports.rs
@@ -0,0 +1,46 @@
+extern crate midir;
+
+use std::io::{stdin, stdout, Write};
+use std::error::Error;
+
+use midir::{MidiInput, MidiOutput, Ignore};
+
+fn main() {
+ match run() {
+ Ok(_) => (),
+ Err(err) => println!("Error: {}", err)
+ }
+}
+
+fn run() -> Result<(), Box<dyn Error>> {
+ let mut midi_in = MidiInput::new("midir test input")?;
+ midi_in.ignore(Ignore::None);
+ let midi_out = MidiOutput::new("midir test output")?;
+
+ let mut input = String::new();
+
+ loop {
+ println!("Available input ports:");
+ for (i, p) in midi_in.ports().iter().enumerate() {
+ println!("{}: {}", i, midi_in.port_name(p)?);
+ }
+
+ println!("\nAvailable output ports:");
+ for (i, p) in midi_out.ports().iter().enumerate() {
+ println!("{}: {}", i, midi_out.port_name(p)?);
+ }
+
+ // run in endless loop if "--loop" parameter is specified
+ match ::std::env::args().nth(1) {
+ Some(ref arg) if arg == "--loop" => {}
+ _ => break
+ }
+ print!("\nPress <enter> to retry ...");
+ stdout().flush()?;
+ input.clear();
+ stdin().read_line(&mut input)?;
+ println!("\n");
+ }
+
+ Ok(())
+}
diff --git a/third_party/rust/midir/examples/test_play.rs b/third_party/rust/midir/examples/test_play.rs
new file mode 100644
index 0000000000..d34e87f8aa
--- /dev/null
+++ b/third_party/rust/midir/examples/test_play.rs
@@ -0,0 +1,74 @@
+extern crate midir;
+
+use std::thread::sleep;
+use std::time::Duration;
+use std::io::{stdin, stdout, Write};
+use std::error::Error;
+
+use midir::{MidiOutput, MidiOutputPort};
+
+fn main() {
+ match run() {
+ Ok(_) => (),
+ Err(err) => println!("Error: {}", err)
+ }
+}
+
+fn run() -> Result<(), Box<dyn Error>> {
+ let midi_out = MidiOutput::new("My Test Output")?;
+
+ // Get an output port (read from console if multiple are available)
+ let out_ports = midi_out.ports();
+ let out_port: &MidiOutputPort = match out_ports.len() {
+ 0 => return Err("no output port found".into()),
+ 1 => {
+ println!("Choosing the only available output port: {}", midi_out.port_name(&out_ports[0]).unwrap());
+ &out_ports[0]
+ },
+ _ => {
+ println!("\nAvailable output ports:");
+ for (i, p) in out_ports.iter().enumerate() {
+ println!("{}: {}", i, midi_out.port_name(p).unwrap());
+ }
+ print!("Please select output port: ");
+ stdout().flush()?;
+ let mut input = String::new();
+ stdin().read_line(&mut input)?;
+ out_ports.get(input.trim().parse::<usize>()?)
+ .ok_or("invalid output port selected")?
+ }
+ };
+
+ println!("\nOpening connection");
+ let mut conn_out = midi_out.connect(out_port, "midir-test")?;
+ println!("Connection open. Listen!");
+ {
+ // Define a new scope in which the closure `play_note` borrows conn_out, so it can be called easily
+ let mut play_note = |note: u8, duration: u64| {
+ const NOTE_ON_MSG: u8 = 0x90;
+ const NOTE_OFF_MSG: u8 = 0x80;
+ const VELOCITY: u8 = 0x64;
+ // We're ignoring errors in here
+ let _ = conn_out.send(&[NOTE_ON_MSG, note, VELOCITY]);
+ sleep(Duration::from_millis(duration * 150));
+ let _ = conn_out.send(&[NOTE_OFF_MSG, note, VELOCITY]);
+ };
+
+ sleep(Duration::from_millis(4 * 150));
+
+ play_note(66, 4);
+ play_note(65, 3);
+ play_note(63, 1);
+ play_note(61, 6);
+ play_note(59, 2);
+ play_note(58, 4);
+ play_note(56, 4);
+ play_note(54, 4);
+ }
+ sleep(Duration::from_millis(150));
+ println!("\nClosing connection");
+ // This is optional, the connection would automatically be closed as soon as it goes out of scope
+ conn_out.close();
+ println!("Connection closed");
+ Ok(())
+}
diff --git a/third_party/rust/midir/examples/test_read_input.rs b/third_party/rust/midir/examples/test_read_input.rs
new file mode 100644
index 0000000000..321ccc7d15
--- /dev/null
+++ b/third_party/rust/midir/examples/test_read_input.rs
@@ -0,0 +1,58 @@
+extern crate midir;
+
+use std::io::{stdin, stdout, Write};
+use std::error::Error;
+
+use midir::{MidiInput, Ignore};
+
+fn main() {
+ match run() {
+ Ok(_) => (),
+ Err(err) => println!("Error: {}", err)
+ }
+}
+
+fn run() -> Result<(), Box<dyn Error>> {
+ let mut input = String::new();
+
+ let mut midi_in = MidiInput::new("midir reading input")?;
+ midi_in.ignore(Ignore::None);
+
+ // Get an input port (read from console if multiple are available)
+ let in_ports = midi_in.ports();
+ let in_port = match in_ports.len() {
+ 0 => return Err("no input port found".into()),
+ 1 => {
+ println!("Choosing the only available input port: {}", midi_in.port_name(&in_ports[0]).unwrap());
+ &in_ports[0]
+ },
+ _ => {
+ println!("\nAvailable input ports:");
+ for (i, p) in in_ports.iter().enumerate() {
+ println!("{}: {}", i, midi_in.port_name(p).unwrap());
+ }
+ print!("Please select input port: ");
+ stdout().flush()?;
+ let mut input = String::new();
+ stdin().read_line(&mut input)?;
+ in_ports.get(input.trim().parse::<usize>()?)
+ .ok_or("invalid input port selected")?
+ }
+ };
+
+ println!("\nOpening connection");
+ let in_port_name = midi_in.port_name(in_port)?;
+
+ // _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope
+ let _conn_in = midi_in.connect(in_port, "midir-read-input", move |stamp, message, _| {
+ println!("{}: {:?} (len = {})", stamp, message, message.len());
+ }, ())?;
+
+ println!("Connection open, reading input from '{}' (press enter to exit) ...", in_port_name);
+
+ input.clear();
+ stdin().read_line(&mut input)?; // wait for next enter key press
+
+ println!("Closing connection");
+ Ok(())
+}
diff --git a/third_party/rust/midir/examples/test_reuse.rs b/third_party/rust/midir/examples/test_reuse.rs
new file mode 100644
index 0000000000..79eeaf9b37
--- /dev/null
+++ b/third_party/rust/midir/examples/test_reuse.rs
@@ -0,0 +1,82 @@
+extern crate midir;
+
+use std::thread::sleep;
+use std::time::Duration;
+use std::io::{stdin, stdout, Write};
+use std::error::Error;
+
+use midir::{MidiInput, MidiOutput, Ignore};
+
+fn main() {
+ match run() {
+ Ok(_) => (),
+ Err(err) => println!("Error: {}", err)
+ }
+}
+
+fn run() -> Result<(), Box<dyn Error>> {
+ let mut input = String::new();
+
+ let mut midi_in = MidiInput::new("My Test Input")?;
+ midi_in.ignore(Ignore::None);
+ let mut midi_out = MidiOutput::new("My Test Output")?;
+
+ println!("Available input ports:");
+ let midi_in_ports = midi_in.ports();
+ for (i, p) in midi_in_ports.iter().enumerate() {
+ println!("{}: {}", i, midi_in.port_name(p)?);
+ }
+ print!("Please select input port: ");
+ stdout().flush()?;
+ stdin().read_line(&mut input)?;
+ let in_port = midi_in_ports.get(input.trim().parse::<usize>()?)
+ .ok_or("Invalid port number")?;
+
+ println!("\nAvailable output ports:");
+ let midi_out_ports = midi_out.ports();
+ for (i, p) in midi_out_ports.iter().enumerate() {
+ println!("{}: {}", i, midi_out.port_name(p)?);
+ }
+ print!("Please select output port: ");
+ stdout().flush()?;
+ input.clear();
+ stdin().read_line(&mut input)?;
+ let out_port = midi_out_ports.get(input.trim().parse::<usize>()?)
+ .ok_or("Invalid port number")?;
+
+ // This shows how to reuse input and output objects:
+ // Open/close the connections twice using the same MidiInput/MidiOutput objects
+ for _ in 0..2 {
+ println!("\nOpening connections");
+ let log_all_bytes = Vec::new(); // We use this as an example custom data to pass into the callback
+ let conn_in = midi_in.connect(in_port, "midir-test", |stamp, message, log| {
+ // The last of the three callback parameters is the object that we pass in as last parameter of `connect`.
+ println!("{}: {:?} (len = {})", stamp, message, message.len());
+ log.extend_from_slice(message);
+ }, log_all_bytes)?;
+
+ // One could get the log back here out of the error
+ let mut conn_out = midi_out.connect(out_port, "midir-test")?;
+
+ println!("Connections open, enter `q` to exit ...");
+
+ loop {
+ input.clear();
+ stdin().read_line(&mut input)?;
+ if input.trim() == "q" {
+ break;
+ } else {
+ conn_out.send(&[144, 60, 1])?;
+ sleep(Duration::from_millis(200));
+ conn_out.send(&[144, 60, 0])?;
+ }
+ }
+ println!("Closing connections");
+ let (midi_in_, log_all_bytes) = conn_in.close();
+ midi_in = midi_in_;
+ midi_out = conn_out.close();
+ println!("Connections closed");
+ println!("Received bytes: {:?}", log_all_bytes);
+ }
+ Ok(())
+}
diff --git a/third_party/rust/midir/examples/test_sysex.rs b/third_party/rust/midir/examples/test_sysex.rs
new file mode 100644
index 0000000000..79149b3c39
--- /dev/null
+++ b/third_party/rust/midir/examples/test_sysex.rs
@@ -0,0 +1,79 @@
+extern crate midir;
+
+fn main() {
+ match example::run() {
+ Ok(_) => (),
+ Err(err) => println!("Error: {}", err)
+ }
+}
+
+#[cfg(not(any(windows, target_arch = "wasm32")))] // virtual ports are not supported on Windows nor on Web MIDI
+mod example {
+
+use std::thread::sleep;
+use std::time::Duration;
+use std::error::Error;
+
+use midir::{MidiInput, MidiOutput, Ignore};
+use midir::os::unix::VirtualInput;
+
+const LARGE_SYSEX_SIZE: usize = 5572; // This is the maximum that worked for me
+
+pub fn run() -> Result<(), Box<dyn Error>> {
+ let mut midi_in = MidiInput::new("My Test Input")?;
+ midi_in.ignore(Ignore::None);
+ let midi_out = MidiOutput::new("My Test Output")?;
+
+ let previous_count = midi_out.port_count();
+
+ println!("Creating virtual input port ...");
+ let conn_in = midi_in.create_virtual("midir-test", |stamp, message, _| {
+ println!("{}: {:?} (len = {})", stamp, message, message.len());
+ }, ())?;
+
+ assert_eq!(midi_out.port_count(), previous_count + 1);
+
+ let out_ports = midi_out.ports();
+ let new_port = out_ports.last().unwrap();
+ println!("Connecting to port '{}' ...", midi_out.port_name(&new_port).unwrap());
+ let mut conn_out = midi_out.connect(&new_port, "midir-test")?;
+ println!("Starting to send messages ...");
+ //sleep(Duration::from_millis(2000));
+ println!("Sending NoteOn message");
+ conn_out.send(&[144, 60, 1])?;
+ sleep(Duration::from_millis(200));
+ println!("Sending small SysEx message ...");
+ conn_out.send(&[0xF0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF7])?;
+ sleep(Duration::from_millis(200));
+ println!("Sending large SysEx message ...");
+ let mut v = Vec::with_capacity(LARGE_SYSEX_SIZE);
+ v.push(0xF0u8);
+ for _ in 1..LARGE_SYSEX_SIZE-1 {
+ v.push(0u8);
+ }
+ v.push(0xF7u8);
+ assert_eq!(v.len(), LARGE_SYSEX_SIZE);
+ conn_out.send(&v)?;
+ sleep(Duration::from_millis(200));
+ // FIXME: the following doesn't seem to work with ALSA
+ println!("Sending large SysEx message (chunked)...");
+ for ch in v.chunks(4) {
+ conn_out.send(ch)?;
+ }
+ sleep(Duration::from_millis(200));
+ println!("Sending small SysEx message ...");
+ conn_out.send(&[0xF0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF7])?;
+ sleep(Duration::from_millis(200));
+ println!("Closing output ...");
+ conn_out.close();
+ println!("Closing virtual input ...");
+ conn_in.close().0;
+ Ok(())
+}
+}
+
+ // needed to compile successfully
+#[cfg(any(windows, target_arch = "wasm32"))] mod example {
+ use std::error::Error;
+ pub fn run() -> Result<(), Box<dyn Error>> { Ok(()) }
+}
diff --git a/third_party/rust/midir/src/backend/alsa/mod.rs b/third_party/rust/midir/src/backend/alsa/mod.rs
new file mode 100755
index 0000000000..f57ffb798e
--- /dev/null
+++ b/third_party/rust/midir/src/backend/alsa/mod.rs
@@ -0,0 +1,733 @@
+extern crate libc;
+extern crate alsa;
+
+use std::mem;
+use std::thread::{Builder, JoinHandle};
+use std::io::{stderr, Write};
+use std::ffi::{CString, CStr};
+
+use self::alsa::{Seq, Direction};
+use self::alsa::seq::{PortInfo, PortSubscribe, Addr, QueueTempo, EventType, PortCap, PortType};
+
+use ::{MidiMessage, Ignore};
+use ::errors::*;
+
+mod helpers {
+ use super::alsa::seq::{Seq, Addr, ClientIter, PortIter, PortInfo, PortCap, MidiEvent, PortType};
+ use ::errors::PortInfoError;
+
+ pub fn poll(fds: &mut [super::libc::pollfd], timeout: i32) -> i32 {
+ unsafe { super::libc::poll(fds.as_mut_ptr(), fds.len() as super::libc::nfds_t, timeout) }
+ }
+
+ #[inline]
+ pub fn get_ports<F, T>(s: &Seq, capability: PortCap, f: F) -> Vec<T> where F: Fn(PortInfo) -> T {
+ ClientIter::new(s).flat_map(|c| PortIter::new(s, c.get_client()))
+ .filter(|p| p.get_type().intersects(PortType::MIDI_GENERIC | PortType::SYNTH | PortType::APPLICATION))
+ .filter(|p| p.get_capability().intersects(capability))
+ .map(f)
+ .collect()
+ }
+
+ #[inline]
+ pub fn get_port_count(s: &Seq, capability: PortCap) -> usize {
+ ClientIter::new(s).flat_map(|c| PortIter::new(s, c.get_client()))
+ .filter(|p| p.get_type().intersects(PortType::MIDI_GENERIC | PortType::SYNTH | PortType::APPLICATION))
+ .filter(|p| p.get_capability().intersects(capability))
+ .count()
+ }
+
+ #[inline]
+ pub fn get_port_name(s: &Seq, addr: Addr) -> Result<String, PortInfoError> {
+ use std::fmt::Write;
+
+ let pinfo = match s.get_any_port_info(addr) {
+ Ok(p) => p,
+ Err(_) => return Err(PortInfoError::InvalidPort)
+ };
+
+ let cinfo = s.get_any_client_info(pinfo.get_client()).map_err(|_| PortInfoError::CannotRetrievePortName)?;
+ let mut output = String::new();
+ write!(&mut output, "{}:{} {}:{}",
+ cinfo.get_name().map_err(|_| PortInfoError::CannotRetrievePortName)?,
+ pinfo.get_name().map_err(|_| PortInfoError::CannotRetrievePortName)?,
+ pinfo.get_client(), // These lines added to make sure devices are listed
+ pinfo.get_port() // with full portnames added to ensure individual device names
+ ).unwrap();
+ Ok(output)
+ }
+
+ pub struct EventDecoder {
+ ev: MidiEvent
+ }
+
+ impl EventDecoder {
+ pub fn new(merge_commands: bool) -> EventDecoder {
+ let coder = MidiEvent::new(0).unwrap();
+ coder.enable_running_status(merge_commands);
+ EventDecoder { ev: coder }
+ }
+
+ #[inline]
+ pub fn get_wrapped(&mut self) -> &mut MidiEvent {
+ &mut self.ev
+ }
+ }
+
+ pub struct EventEncoder {
+ ev: MidiEvent,
+ buffer_size: u32
+ }
+
+ unsafe impl Send for EventEncoder {}
+
+ impl EventEncoder {
+ #[inline]
+ pub fn new(buffer_size: u32) -> EventEncoder {
+ EventEncoder {
+ ev: MidiEvent::new(buffer_size).unwrap(),
+ buffer_size: buffer_size
+ }
+ }
+
+ #[inline]
+ pub fn get_buffer_size(&self) -> u32 {
+ self.buffer_size
+ }
+
+ #[inline]
+ pub fn resize_buffer(&mut self, bufsize: u32) -> Result<(), ()> {
+ match self.ev.resize_buffer(bufsize) {
+ Ok(_) => {
+ self.buffer_size = bufsize;
+ Ok(())
+ },
+ Err(_) => Err(())
+ }
+ }
+
+ #[inline]
+ pub fn get_wrapped(&mut self) -> &mut MidiEvent {
+ &mut self.ev
+ }
+ }
+}
+
+const INITIAL_CODER_BUFFER_SIZE: usize = 32;
+
+pub struct MidiInput {
+ ignore_flags: Ignore,
+ seq: Option<Seq>,
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MidiInputPort {
+ addr: Addr
+}
+
+pub struct MidiInputConnection<T: 'static> {
+ subscription: Option<PortSubscribe>,
+ thread: Option<JoinHandle<(HandlerData<T>, T)>>,
+ vport: i32, // TODO: probably port numbers are only u8, therefore could use Option<u8>
+ trigger_send_fd: i32,
+}
+
+struct HandlerData<T: 'static> {
+ ignore_flags: Ignore,
+ seq: Seq,
+ trigger_rcv_fd: i32,
+ callback: Box<dyn FnMut(u64, &[u8], &mut T) + Send>,
+ queue_id: i32, // an input queue is needed to get timestamped events
+}
+
+impl MidiInput {
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ let seq = match Seq::open(None, None, true) {
+ Ok(s) => s,
+ Err(_) => { return Err(InitError); }
+ };
+
+ let c_client_name = CString::new(client_name).map_err(|_| InitError)?;
+ seq.set_client_name(&c_client_name).map_err(|_| InitError)?;
+
+ Ok(MidiInput {
+ ignore_flags: Ignore::None,
+ seq: Some(seq),
+ })
+ }
+
+ pub fn ignore(&mut self, flags: Ignore) {
+ self.ignore_flags = flags;
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiInputPort> {
+ helpers::get_ports(self.seq.as_ref().unwrap(), PortCap::READ | PortCap::SUBS_READ, |p| ::common::MidiInputPort {
+ imp: MidiInputPort {
+ addr: p.addr()
+ }
+ })
+ }
+
+ pub fn port_count(&self) -> usize {
+ helpers::get_port_count(self.seq.as_ref().unwrap(), PortCap::READ | PortCap::SUBS_READ)
+ }
+
+ pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ helpers::get_port_name(self.seq.as_ref().unwrap(), port.addr)
+ }
+
+ fn init_queue(&mut self) -> i32 {
+ let seq = self.seq.as_mut().unwrap();
+ let mut queue_id = 0;
+ // Create the input queue
+ if !cfg!(feature = "avoid_timestamping") {
+ queue_id = seq.alloc_named_queue(unsafe { CStr::from_bytes_with_nul_unchecked(b"midir queue\0") }).unwrap();
+ // Set arbitrary tempo (mm=100) and resolution (240)
+ let qtempo = QueueTempo::empty().unwrap();
+ qtempo.set_tempo(600_000);
+ qtempo.set_ppq(240);
+ seq.set_queue_tempo(queue_id, &qtempo).unwrap();
+ let _ = seq.drain_output();
+ }
+
+ queue_id
+ }
+
+ fn init_trigger(&mut self) -> Result<[i32; 2], ()> {
+ let mut trigger_fds = [-1, -1];
+
+ if unsafe { self::libc::pipe(trigger_fds.as_mut_ptr()) } == -1 {
+ Err(())
+ } else {
+ Ok(trigger_fds)
+ }
+ }
+
+ fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result<i32, ()> {
+ let mut pinfo = PortInfo::empty().unwrap();
+ // these functions are private, and the values are zeroed already by `empty()`
+ //pinfo.set_client(0);
+ //pinfo.set_port(0);
+ pinfo.set_capability(PortCap::WRITE | PortCap::SUBS_WRITE);
+ pinfo.set_type(PortType::MIDI_GENERIC | PortType::APPLICATION);
+ pinfo.set_midi_channels(16);
+
+ if !cfg!(feature = "avoid_timestamping") {
+ pinfo.set_timestamping(true);
+ pinfo.set_timestamp_real(true);
+ pinfo.set_timestamp_queue(queue_id);
+ }
+
+ pinfo.set_name(port_name);
+ match self.seq.as_mut().unwrap().create_port(&mut pinfo) {
+ Ok(_) => Ok(pinfo.get_port()),
+ Err(_) => Err(())
+ }
+ }
+
+ fn start_input_queue(&mut self, queue_id: i32) {
+ if !cfg!(feature = "avoid_timestamping") {
+ let seq = self.seq.as_mut().unwrap();
+ let _ = seq.control_queue(queue_id, EventType::Start, 0, None);
+ let _ = seq.drain_output();
+ }
+ }
+
+ pub fn connect<F, T: Send>(
+ mut self, port: &MidiInputPort, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<Self>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+
+ let trigger_fds = match self.init_trigger() {
+ Ok(fds) => fds,
+ Err(()) => { return Err(ConnectError::other("could not create communication pipe for ALSA handler", self)); }
+ };
+
+ let queue_id = self.init_queue();
+
+ let src_pinfo = match self.seq.as_ref().unwrap().get_any_port_info(port.addr) {
+ Ok(p) => p,
+ Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ };
+
+ let c_port_name = match CString::new(port_name) {
+ Ok(c_port_name) => c_port_name,
+ Err(_) => return Err(ConnectError::other("port_name must not contain null bytes", self))
+ };
+
+ let vport = match self.create_port(&c_port_name, queue_id) {
+ Ok(vp) => vp,
+ Err(_) => {
+ return Err(ConnectError::other("could not create ALSA input port", self));
+ }
+ };
+
+ // Make subscription
+ let sub = PortSubscribe::empty().unwrap();
+ sub.set_sender(src_pinfo.addr());
+ sub.set_dest(Addr { client: self.seq.as_ref().unwrap().client_id().unwrap(), port: vport});
+ if self.seq.as_ref().unwrap().subscribe_port(&sub).is_err() {
+ return Err(ConnectError::other("could not create ALSA input subscription", self));
+ }
+ let subscription = sub;
+
+ // Start the input queue
+ self.start_input_queue(queue_id);
+
+ // Start our MIDI input thread.
+ let handler_data = HandlerData {
+ ignore_flags: self.ignore_flags,
+ seq: self.seq.take().unwrap(),
+ trigger_rcv_fd: trigger_fds[0],
+ callback: Box::new(callback),
+ queue_id: queue_id
+ };
+
+ let threadbuilder = Builder::new();
+ let name = format!("midir ALSA input handler (port '{}')", port_name);
+ let threadbuilder = threadbuilder.name(name);
+ let thread = match threadbuilder.spawn(move || {
+ let mut d = data;
+ let h = handle_input(handler_data, &mut d);
+ (h, d) // return both the handler data and the user data
+ }) {
+ Ok(handle) => handle,
+ Err(_) => {
+ //unsafe { snd_seq_unsubscribe_port(self.seq.as_mut_ptr(), sub.as_ptr()) };
+ return Err(ConnectError::other("could not start ALSA input handler thread", self));
+ }
+ };
+
+ Ok(MidiInputConnection {
+ subscription: Some(subscription),
+ thread: Some(thread),
+ vport: vport,
+ trigger_send_fd: trigger_fds[1]
+ })
+ }
+
+ pub fn create_virtual<F, T: Send>(
+ mut self, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<Self>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+ let trigger_fds = match self.init_trigger() {
+ Ok(fds) => fds,
+ Err(()) => { return Err(ConnectError::other("could not create communication pipe for ALSA handler", self)); }
+ };
+
+ let queue_id = self.init_queue();
+
+ let c_port_name = match CString::new(port_name) {
+ Ok(c_port_name) => c_port_name,
+ Err(_) => return Err(ConnectError::other("port_name must not contain null bytes", self))
+ };
+
+ let vport = match self.create_port(&c_port_name, queue_id) {
+ Ok(vp) => vp,
+ Err(_) => {
+ return Err(ConnectError::other("could not create ALSA input port", self));
+ }
+ };
+
+ // Start the input queue
+ self.start_input_queue(queue_id);
+
+ // Start our MIDI input thread.
+ let handler_data = HandlerData {
+ ignore_flags: self.ignore_flags,
+ seq: self.seq.take().unwrap(),
+ trigger_rcv_fd: trigger_fds[0],
+ callback: Box::new(callback),
+ queue_id: queue_id
+ };
+
+ let threadbuilder = Builder::new();
+ let thread = match threadbuilder.spawn(move || {
+ let mut d = data;
+ let h = handle_input(handler_data, &mut d);
+ (h, d) // return both the handler data and the user data
+ }) {
+ Ok(handle) => handle,
+ Err(_) => {
+ //unsafe { snd_seq_unsubscribe_port(self.seq.as_mut_ptr(), sub.as_ptr()) };
+ return Err(ConnectError::other("could not start ALSA input handler thread", self));
+ }
+ };
+
+ Ok(MidiInputConnection {
+ subscription: None,
+ thread: Some(thread),
+ vport: vport,
+ trigger_send_fd: trigger_fds[1]
+ })
+ }
+}
+
+impl<T> MidiInputConnection<T> {
+ pub fn close(mut self) -> (MidiInput, T) {
+ let (handler_data, user_data) = self.close_internal();
+
+ (MidiInput {
+ ignore_flags: handler_data.ignore_flags,
+ seq: Some(handler_data.seq),
+ }, user_data)
+ }
+
+ /// This must only be called if the handler thread has not yet been shut down
+ fn close_internal(&mut self) -> (HandlerData<T>, T) {
+ // Request the thread to stop.
+ let _res = unsafe { self::libc::write(self.trigger_send_fd, &false as *const bool as *const _, mem::size_of::<bool>() as self::libc::size_t) };
+
+ let thread = self.thread.take().unwrap();
+ // Join the thread to get the handler_data back
+ let (handler_data, user_data) = match thread.join() {
+ Ok(data) => data,
+ // TODO: handle this more gracefully?
+ Err(e) => {
+ if let Some(e) = e.downcast_ref::<&'static str>() {
+ panic!("Error when joining ALSA thread: {}", e);
+ } else {
+ panic!("Unknown error when joining ALSA thread: {:?}", e);
+ }
+ }
+ };
+
+ // TODO: find out why snd_seq_unsubscribe_port takes a long time if there was not yet any input message
+ if let Some(ref subscription) = self.subscription {
+ let _ = handler_data.seq.unsubscribe_port(subscription.get_sender(), subscription.get_dest());
+ }
+
+ // Close the trigger fds (TODO: make sure that these are closed even in the presence of panic in thread)
+ unsafe {
+ self::libc::close(handler_data.trigger_rcv_fd);
+ self::libc::close(self.trigger_send_fd);
+ }
+
+ // Stop and free the input queue
+ if !cfg!(feature = "avoid_timestamping") {
+ let _ = handler_data.seq.control_queue(handler_data.queue_id, EventType::Stop, 0, None);
+ let _ = handler_data.seq.drain_output();
+ let _ = handler_data.seq.free_queue(handler_data.queue_id);
+ }
+
+ // Delete the port
+ let _ = handler_data.seq.delete_port(self.vport);
+
+ (handler_data, user_data)
+ }
+}
+
+
+impl<T> Drop for MidiInputConnection<T> {
+ fn drop(&mut self) {
+ // Use `self.thread` as a flag whether the connection has already been dropped
+ if self.thread.is_some() {
+ self.close_internal();
+ }
+ }
+}
+
+pub struct MidiOutput {
+ seq: Option<Seq>, // TODO: if `Seq` is marked as non-zero, this should just be pointer-sized
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MidiOutputPort {
+ addr: Addr
+}
+
+pub struct MidiOutputConnection {
+ seq: Option<Seq>,
+ vport: i32,
+ coder: helpers::EventEncoder,
+ subscription: Option<PortSubscribe>
+}
+
+impl MidiOutput {
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ let seq = match Seq::open(None, Some(Direction::Playback), true) {
+ Ok(s) => s,
+ Err(_) => { return Err(InitError); }
+ };
+
+ let c_client_name = CString::new(client_name).map_err(|_| InitError)?;
+ seq.set_client_name(&c_client_name).map_err(|_| InitError)?;
+
+ Ok(MidiOutput {
+ seq: Some(seq),
+ })
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiOutputPort> {
+ helpers::get_ports(self.seq.as_ref().unwrap(), PortCap::WRITE | PortCap::SUBS_WRITE, |p| ::common::MidiOutputPort {
+ imp: MidiOutputPort {
+ addr: p.addr()
+ }
+ })
+ }
+
+ pub fn port_count(&self) -> usize {
+ helpers::get_port_count(self.seq.as_ref().unwrap(), PortCap::WRITE | PortCap::SUBS_WRITE)
+ }
+
+ pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ helpers::get_port_name(self.seq.as_ref().unwrap(), port.addr)
+ }
+
+ pub fn connect(mut self, port: &MidiOutputPort, port_name: &str) -> Result<MidiOutputConnection, ConnectError<Self>> {
+ let pinfo = match self.seq.as_ref().unwrap().get_any_port_info(port.addr) {
+ Ok(p) => p,
+ Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ };
+
+ let c_port_name = match CString::new(port_name) {
+ Ok(c_port_name) => c_port_name,
+ Err(_) => return Err(ConnectError::other("port_name must not contain null bytes", self))
+ };
+
+ let vport = match self.seq.as_ref().unwrap().create_simple_port(&c_port_name, PortCap::READ | PortCap::SUBS_READ, PortType::MIDI_GENERIC | PortType::APPLICATION) {
+ Ok(vport) => vport,
+ Err(_) => return Err(ConnectError::other("could not create ALSA output port", self))
+ };
+
+ // Make subscription
+ let sub = PortSubscribe::empty().unwrap();
+ sub.set_sender(Addr { client: self.seq.as_ref().unwrap().client_id().unwrap(), port: vport });
+ sub.set_dest(pinfo.addr());
+ sub.set_time_update(true);
+ sub.set_time_real(true);
+ if self.seq.as_ref().unwrap().subscribe_port(&sub).is_err() {
+ return Err(ConnectError::other("could not create ALSA output subscription", self));
+ }
+
+ Ok(MidiOutputConnection {
+ seq: self.seq.take(),
+ vport: vport,
+ coder: helpers::EventEncoder::new(INITIAL_CODER_BUFFER_SIZE as u32),
+ subscription: Some(sub)
+ })
+ }
+
+ pub fn create_virtual(
+ mut self, port_name: &str
+ ) -> Result<MidiOutputConnection, ConnectError<Self>> {
+ let c_port_name = match CString::new(port_name) {
+ Ok(c_port_name) => c_port_name,
+ Err(_) => return Err(ConnectError::other("port_name must not contain null bytes", self))
+ };
+
+ let vport = match self.seq.as_ref().unwrap().create_simple_port(&c_port_name, PortCap::READ | PortCap::SUBS_READ, PortType::MIDI_GENERIC | PortType::APPLICATION) {
+ Ok(vport) => vport,
+ Err(_) => return Err(ConnectError::other("could not create ALSA output port", self))
+ };
+
+ Ok(MidiOutputConnection {
+ seq: self.seq.take(),
+ vport: vport,
+ coder: helpers::EventEncoder::new(INITIAL_CODER_BUFFER_SIZE as u32),
+ subscription: None
+ })
+ }
+}
+
+
+impl MidiOutputConnection {
+ pub fn close(mut self) -> MidiOutput {
+ self.close_internal();
+
+ MidiOutput {
+ seq: self.seq.take(),
+ }
+ }
+
+ pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
+ let nbytes = message.len();
+ assert!(nbytes <= u32::max_value() as usize);
+
+ if nbytes > self.coder.get_buffer_size() as usize {
+ if self.coder.resize_buffer(nbytes as u32).is_err() {
+ return Err(SendError::Other("could not resize ALSA encoding buffer"));
+ }
+ }
+
+ let mut ev = match self.coder.get_wrapped().encode(message) {
+ Ok((_, Some(ev))) => ev,
+ _ => return Err(SendError::InvalidData("ALSA encoder reported invalid data"))
+ };
+
+ ev.set_source(self.vport);
+ ev.set_subs();
+ ev.set_direct();
+
+ // Send the event.
+ if self.seq.as_ref().unwrap().event_output(&mut ev).is_err() {
+ return Err(SendError::Other("could not send encoded ALSA message"));
+ }
+
+ let _ = self.seq.as_mut().unwrap().drain_output();
+ Ok(())
+ }
+
+ fn close_internal(&mut self) {
+ let seq = self.seq.as_mut().unwrap();
+ if let Some(ref subscription) = self.subscription {
+ let _ = seq.unsubscribe_port(subscription.get_sender(), subscription.get_dest());
+ }
+ let _ = seq.delete_port(self.vport);
+ }
+}
+
+impl Drop for MidiOutputConnection {
+ fn drop(&mut self) {
+ if self.seq.is_some() {
+ self.close_internal();
+ }
+ }
+}
+
+fn handle_input<T>(mut data: HandlerData<T>, user_data: &mut T) -> HandlerData<T> {
+ use self::alsa::PollDescriptors;
+ use self::alsa::seq::Connect;
+
+ let mut continue_sysex: bool = false;
+
+ // ALSA documentation says:
+ // The required buffer size for a sequencer event it as most 12 bytes, except for System Exclusive events (which we handle separately)
+ let mut buffer = [0; 12];
+
+ let mut coder = helpers::EventDecoder::new(false);
+
+ let mut poll_fds: Box<[self::libc::pollfd]>;
+ {
+ let poll_desc_info = (&data.seq, Some(Direction::Capture));
+ let poll_fd_count = poll_desc_info.count() + 1;
+ let mut vec = Vec::with_capacity(poll_fd_count);
+ unsafe {
+ vec.set_len(poll_fd_count);
+ poll_fds = vec.into_boxed_slice();
+ }
+ poll_desc_info.fill(&mut poll_fds[1..]).unwrap();
+ }
+ poll_fds[0].fd = data.trigger_rcv_fd;
+ poll_fds[0].events = self::libc::POLLIN;
+
+
+ let mut message = MidiMessage::new();
+
+ { // open scope where we can borrow data.seq
+ let mut seq_input = data.seq.input();
+
+ let mut do_input = true;
+ while do_input {
+ if let Ok(0) = seq_input.event_input_pending(true) {
+ // No data pending
+ if helpers::poll(&mut poll_fds, -1) >= 0 {
+ // Read from our "channel" whether we should stop the thread
+ if poll_fds[0].revents & self::libc::POLLIN != 0 {
+ let _res = unsafe { self::libc::read(poll_fds[0].fd, mem::transmute(&mut do_input), mem::size_of::<bool>() as self::libc::size_t) };
+ }
+ }
+ continue;
+ }
+
+ // This is a bit weird, but we now have to decode an ALSA MIDI
+ // event (back) into MIDI bytes. We'll ignore non-MIDI types.
+
+ // The ALSA sequencer has a maximum buffer size for MIDI sysex
+ // events of 256 bytes. If a device sends sysex messages larger
+ // than this, they are segmented into 256 byte chunks. So,
+ // we'll watch for this and concatenate sysex chunks into a
+ // single sysex message if necessary.
+ //
+ // TODO: Figure out if this is still true (seems to not be the case)
+ // If not (i.e., each event represents a complete message), we can
+ // call the user callback with the byte buffer directly, without the
+ // copying to `message.bytes` first.
+ if !continue_sysex { message.bytes.clear() }
+
+ let ignore_flags = data.ignore_flags;
+
+ // If here, there should be data.
+ let mut ev = match seq_input.event_input() {
+ Ok(ev) => ev,
+ Err(ref e) if e.errno() == alsa::nix::errno::Errno::ENOSPC => {
+ let _ = writeln!(stderr(), "\nError in handle_input: ALSA MIDI input buffer overrun!\n");
+ continue;
+ },
+ Err(ref e) if e.errno() == alsa::nix::errno::Errno::EAGAIN => {
+ let _ = writeln!(stderr(), "\nError in handle_input: no input event from ALSA MIDI input buffer!\n");
+ continue;
+ },
+ Err(ref e) => {
+ let _ = writeln!(stderr(), "\nError in handle_input: unknown ALSA MIDI input error ({})!\n", e);
+ //perror("System reports");
+ continue;
+ }
+ };
+
+ let do_decode = match ev.get_type() {
+ EventType::PortSubscribed => {
+ if cfg!(debug) { println!("Notice from handle_input: ALSA port connection made!") };
+ false
+ },
+ EventType::PortUnsubscribed => {
+ if cfg!(debug) {
+ let _ = writeln!(stderr(), "Notice from handle_input: ALSA port connection has closed!");
+ let connect = ev.get_data::<Connect>().unwrap();
+ let _ = writeln!(stderr(), "sender = {}:{}, dest = {}:{}",
+ connect.sender.client,
+ connect.sender.port,
+ connect.dest.client,
+ connect.dest.port
+ );
+ }
+ false
+ },
+ EventType::Qframe => { // MIDI time code
+ !ignore_flags.contains(Ignore::Time)
+ },
+ EventType::Tick => { // 0xF9 ... MIDI timing tick
+ !ignore_flags.contains(Ignore::Time)
+ },
+ EventType::Clock => { // 0xF8 ... MIDI timing (clock) tick
+ !ignore_flags.contains(Ignore::Time)
+ },
+ EventType::Sensing => { // Active sensing
+ !ignore_flags.contains(Ignore::ActiveSense)
+ },
+ EventType::Sysex => {
+ if !ignore_flags.contains(Ignore::Sysex) {
+ // Directly copy the data from the external buffer to our message
+ message.bytes.extend_from_slice(ev.get_ext().unwrap());
+ continue_sysex = *message.bytes.last().unwrap() != 0xF7;
+ }
+ false // don't ever decode sysex messages (it would unnecessarily copy the message content to another buffer)
+ },
+ _ => true
+ };
+
+ // NOTE: SysEx messages have already been "decoded" at this point!
+ if do_decode {
+ if let Ok(nbytes) = coder.get_wrapped().decode(&mut buffer, &mut ev) {
+ if nbytes > 0 {
+ message.bytes.extend_from_slice(&buffer[0..nbytes]);
+ }
+ }
+ }
+
+ if message.bytes.len() == 0 || continue_sysex { continue; }
+
+ // Calculate the time stamp:
+ // Use the ALSA sequencer event time data.
+ // (thanks to Pedro Lopez-Cabanillas!).
+ let alsa_time = ev.get_time().unwrap();
+ let secs = alsa_time.as_secs();
+ let nsecs = alsa_time.subsec_nanos();
+
+ message.timestamp = ( secs as u64 * 1_000_000 ) + ( nsecs as u64 / 1_000 );
+ (data.callback)(message.timestamp, &message.bytes, user_data);
+ }
+
+ } // close scope where data.seq is borrowed
+ data // return data back to thread owner
+}
diff --git a/third_party/rust/midir/src/backend/coremidi/mod.rs b/third_party/rust/midir/src/backend/coremidi/mod.rs
new file mode 100644
index 0000000000..31fd905778
--- /dev/null
+++ b/third_party/rust/midir/src/backend/coremidi/mod.rs
@@ -0,0 +1,351 @@
+extern crate coremidi;
+
+use std::sync::{Arc, Mutex};
+
+use ::errors::*;
+use ::Ignore;
+use ::MidiMessage;
+
+use self::coremidi::*;
+
+mod external {
+ #[link(name = "CoreAudio", kind = "framework")]
+ extern "C" {
+ pub fn AudioConvertHostTimeToNanos(inHostTime: u64) -> u64;
+ pub fn AudioGetCurrentHostTime() -> u64;
+ }
+}
+
+pub struct MidiInput {
+ client: Client,
+ ignore_flags: Ignore
+}
+
+#[derive(Clone)]
+pub struct MidiInputPort {
+ source: Arc<Source>
+}
+
+impl PartialEq for MidiInputPort {
+ fn eq(&self, other: &Self) -> bool {
+ if let (Some(id1), Some(id2)) = (self.source.unique_id(), other.source.unique_id()) {
+ id1 == id2
+ } else {
+ // Acording to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case
+ false
+ }
+ }
+}
+
+impl MidiInput {
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ match Client::new(client_name) {
+ Ok(cl) => Ok(MidiInput { client: cl, ignore_flags: Ignore::None }),
+ Err(_) => Err(InitError)
+ }
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiInputPort> {
+ Sources.into_iter().map(|s| ::common::MidiInputPort {
+ imp: MidiInputPort { source: Arc::new(s) }
+ }).collect()
+ }
+
+ pub fn ignore(&mut self, flags: Ignore) {
+ self.ignore_flags = flags;
+ }
+
+ pub fn port_count(&self) -> usize {
+ Sources::count()
+ }
+
+ pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ match port.source.display_name() {
+ Some(name) => Ok(name),
+ None => Err(PortInfoError::CannotRetrievePortName)
+ }
+ }
+
+ fn handle_input<T>(packets: &PacketList, handler_data: &mut HandlerData<T>) {
+ let continue_sysex = &mut handler_data.continue_sysex;
+ let ignore = handler_data.ignore_flags;
+ let message = &mut handler_data.message;
+ let data = &mut handler_data.user_data.as_mut().unwrap();
+ for p in packets.iter() {
+ let pdata = p.data();
+ if pdata.len() == 0 { continue; }
+
+ let mut timestamp = p.timestamp();
+ if timestamp == 0 { // this might happen for asnychronous sysex messages (?)
+ timestamp = unsafe { external::AudioGetCurrentHostTime() };
+ }
+
+ if !*continue_sysex {
+ message.timestamp = unsafe { external::AudioConvertHostTimeToNanos(timestamp) } as u64 / 1000;
+ }
+
+ let mut cur_byte = 0;
+ if *continue_sysex {
+ // We have a continuing, segmented sysex message.
+ if !ignore.contains(Ignore::Sysex) {
+ // If we're not ignoring sysex messages, copy the entire packet.
+ message.bytes.extend_from_slice(pdata);
+ }
+ *continue_sysex = pdata[pdata.len() - 1] != 0xF7;
+
+ if !ignore.contains(Ignore::Sysex) && !*continue_sysex {
+ // If we reached the end of the sysex, invoke the user callback
+ (handler_data.callback)(message.timestamp, &message.bytes, data);
+ message.bytes.clear();
+ }
+ } else {
+ while cur_byte < pdata.len() {
+ // We are expecting that the next byte in the packet is a status byte.
+ let status = pdata[cur_byte];
+ if status & 0x80 == 0 { break; }
+ // Determine the number of bytes in the MIDI message.
+ let size;
+ if status < 0xC0 { size = 3; }
+ else if status < 0xE0 { size = 2; }
+ else if status < 0xF0 { size = 3; }
+ else if status == 0xF0 {
+ // A MIDI sysex
+ if ignore.contains(Ignore::Sysex) {
+ size = 0;
+ cur_byte = pdata.len();
+ } else {
+ size = pdata.len() - cur_byte;
+ }
+ *continue_sysex = pdata[pdata.len() - 1] != 0xF7;
+ }
+ else if status == 0xF1 {
+ // A MIDI time code message
+ if ignore.contains(Ignore::Time) {
+ size = 0;
+ cur_byte += 2;
+ } else {
+ size = 2;
+ }
+ }
+ else if status == 0xF2 { size = 3; }
+ else if status == 0xF3 { size = 2; }
+ else if status == 0xF8 && ignore.contains(Ignore::Time) {
+ // A MIDI timing tick message and we're ignoring it.
+ size = 0;
+ cur_byte += 1;
+ }
+ else if status == 0xFE && ignore.contains(Ignore::ActiveSense) {
+ // A MIDI active sensing message and we're ignoring it.
+ size = 0;
+ cur_byte += 1;
+ }
+ else { size = 1; }
+
+ // Copy the MIDI data to our vector.
+ if size > 0 {
+ let message_bytes = &pdata[cur_byte..(cur_byte + size)];
+ if !*continue_sysex {
+ // This is either a non-sysex message or a non-segmented sysex message
+ (handler_data.callback)(message.timestamp, message_bytes, data);
+ message.bytes.clear();
+ } else {
+ // This is the beginning of a segmented sysex message
+ message.bytes.extend_from_slice(message_bytes);
+ }
+ cur_byte += size;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn connect<F, T: Send + 'static>(
+ self, port: &MidiInputPort, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+ let handler_data = Arc::new(Mutex::new(HandlerData {
+ message: MidiMessage::new(),
+ ignore_flags: self.ignore_flags,
+ continue_sysex: false,
+ callback: Box::new(callback),
+ user_data: Some(data)
+ }));
+ let handler_data2 = handler_data.clone();
+ let iport = match self.client.input_port(port_name, move |packets| {
+ MidiInput::handle_input(packets, &mut *handler_data2.lock().unwrap())
+ }) {
+ Ok(p) => p,
+ Err(_) => return Err(ConnectError::other("error creating MIDI input port", self))
+ };
+ if let Err(_) = iport.connect_source(&port.source) {
+ return Err(ConnectError::other("error connecting MIDI input port", self));
+ }
+ Ok(MidiInputConnection {
+ client: self.client,
+ details: InputConnectionDetails::Explicit(iport),
+ handler_data: handler_data
+ })
+ }
+
+ pub fn create_virtual<F, T: Send + 'static>(
+ self, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+
+ let handler_data = Arc::new(Mutex::new(HandlerData {
+ message: MidiMessage::new(),
+ ignore_flags: self.ignore_flags,
+ continue_sysex: false,
+ callback: Box::new(callback),
+ user_data: Some(data)
+ }));
+ let handler_data2 = handler_data.clone();
+ let vrt = match self.client.virtual_destination(port_name, move |packets| {
+ MidiInput::handle_input(packets, &mut *handler_data2.lock().unwrap())
+ }) {
+ Ok(p) => p,
+ Err(_) => return Err(ConnectError::other("error creating MIDI input port", self))
+ };
+ Ok(MidiInputConnection {
+ client: self.client,
+ details: InputConnectionDetails::Virtual(vrt),
+ handler_data: handler_data
+ })
+ }
+}
+
+enum InputConnectionDetails {
+ Explicit(InputPort),
+ Virtual(VirtualDestination)
+}
+
+pub struct MidiInputConnection<T> {
+ client: Client,
+ #[allow(dead_code)]
+ details: InputConnectionDetails,
+ // TODO: get rid of Arc & Mutex?
+ // synchronization is required because the borrow checker does not
+ // know that the callback we're in here is never called concurrently
+ // (always in sequence)
+ handler_data: Arc<Mutex<HandlerData<T>>>
+}
+
+impl<T> MidiInputConnection<T> {
+ pub fn close(self) -> (MidiInput, T) {
+ let mut handler_data_locked = self.handler_data.lock().unwrap();
+ (MidiInput {
+ client: self.client,
+ ignore_flags: handler_data_locked.ignore_flags
+ }, handler_data_locked.user_data.take().unwrap())
+ }
+}
+
+/// This is all the data that is stored on the heap as long as a connection
+/// is opened and passed to the callback handler.
+///
+/// It is important that `user_data` is the last field to not influence
+/// offsets after monomorphization.
+struct HandlerData<T> {
+ message: MidiMessage,
+ ignore_flags: Ignore,
+ continue_sysex: bool,
+ callback: Box<dyn FnMut(u64, &[u8], &mut T) + Send>,
+ user_data: Option<T>
+}
+
+pub struct MidiOutput {
+ client: Client
+}
+
+#[derive(Clone)]
+pub struct MidiOutputPort {
+ dest: Arc<Destination>
+}
+
+impl PartialEq for MidiOutputPort {
+ fn eq(&self, other: &Self) -> bool {
+ if let (Some(id1), Some(id2)) = (self.dest.unique_id(), other.dest.unique_id()) {
+ id1 == id2
+ } else {
+ // Acording to macos docs "The system assigns unique IDs to all objects.", so I think we can ignore this case
+ false
+ }
+ }
+}
+
+impl MidiOutput {
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ match Client::new(client_name) {
+ Ok(cl) => Ok(MidiOutput { client: cl }),
+ Err(_) => Err(InitError)
+ }
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiOutputPort> {
+ Destinations.into_iter().map(|d| ::common::MidiOutputPort {
+ imp: MidiOutputPort { dest: Arc::new(d) }
+ }).collect()
+ }
+
+ pub fn port_count(&self) -> usize {
+ Destinations::count()
+ }
+
+ pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ match port.dest.display_name() {
+ Some(name) => Ok(name),
+ None => Err(PortInfoError::CannotRetrievePortName)
+ }
+ }
+
+ pub fn connect(self, port: &MidiOutputPort, port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ let oport = match self.client.output_port(port_name) {
+ Ok(p) => p,
+ Err(_) => return Err(ConnectError::other("error creating MIDI output port", self))
+ };
+ Ok(MidiOutputConnection {
+ client: self.client,
+ details: OutputConnectionDetails::Explicit(oport, port.dest.clone())
+ })
+ }
+
+ pub fn create_virtual(self, port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ let vrt = match self.client.virtual_source(port_name) {
+ Ok(p) => p,
+ Err(_) => return Err(ConnectError::other("error creating virtual MIDI source", self))
+ };
+ Ok(MidiOutputConnection {
+ client: self.client,
+ details: OutputConnectionDetails::Virtual(vrt)
+ })
+ }
+}
+
+enum OutputConnectionDetails {
+ Explicit(OutputPort, Arc<Destination>),
+ Virtual(VirtualSource)
+}
+
+pub struct MidiOutputConnection {
+ client: Client,
+ details: OutputConnectionDetails
+}
+
+impl MidiOutputConnection {
+ pub fn close(self) -> MidiOutput {
+ MidiOutput { client: self.client }
+ }
+
+ pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
+ let packets = PacketBuffer::new(0, message);
+ match self.details {
+ OutputConnectionDetails::Explicit(ref port, ref dest) => {
+ port.send(&dest, &packets).map_err(|_| SendError::Other("error sending MIDI message to port"))
+ },
+ OutputConnectionDetails::Virtual(ref vrt) => {
+ vrt.received(&packets).map_err(|_| SendError::Other("error sending MIDI to virtual destinations"))
+ }
+ }
+
+ }
+}
diff --git a/third_party/rust/midir/src/backend/jack/mod.rs b/third_party/rust/midir/src/backend/jack/mod.rs
new file mode 100644
index 0000000000..92cc81a295
--- /dev/null
+++ b/third_party/rust/midir/src/backend/jack/mod.rs
@@ -0,0 +1,364 @@
+extern crate jack_sys;
+extern crate libc;
+
+use self::jack_sys::jack_nframes_t;
+use self::libc::c_void;
+
+use std::{mem, slice};
+use std::ffi::CString;
+
+mod wrappers;
+use self::wrappers::*;
+
+use ::{Ignore, MidiMessage};
+use ::errors::*;
+
+const OUTPUT_RINGBUFFER_SIZE: usize = 16384;
+
+struct InputHandlerData<T> {
+ port: Option<MidiPort>,
+ ignore_flags: Ignore,
+ callback: Box<dyn FnMut(u64, &[u8], &mut T) + Send>,
+ user_data: Option<T>
+}
+
+pub struct MidiInput {
+ ignore_flags: Ignore,
+ client: Option<Client>,
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MidiInputPort {
+ name: CString
+}
+
+pub struct MidiInputConnection<T> {
+ handler_data: Box<InputHandlerData<T>>,
+ client: Option<Client>
+}
+
+impl MidiInput {
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ let client = match Client::open(client_name, JackOpenOptions::NoStartServer) {
+ Ok(c) => c,
+ Err(_) => { return Err(InitError); } // TODO: maybe add message that Jack server might not be running
+ };
+
+ Ok(MidiInput {
+ ignore_flags: Ignore::None,
+ client: Some(client),
+ })
+ }
+
+ pub fn ignore(&mut self, flags: Ignore) {
+ self.ignore_flags = flags;
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiInputPort> {
+ let ports = self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsOutput);
+ let mut result = Vec::with_capacity(ports.count());
+ for i in 0..ports.count() {
+ result.push(::common::MidiInputPort {
+ imp: MidiInputPort {
+ name: ports.get_c_name(i).into()
+ }
+ })
+ }
+ result
+ }
+
+ pub fn port_count(&self) -> usize {
+ self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsOutput).count()
+ }
+
+ pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ Ok(port.name.to_string_lossy().into())
+ }
+
+ fn activate_callback<F, T: Send>(&mut self, callback: F, data: T)
+ -> Box<InputHandlerData<T>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static
+ {
+ let handler_data = Box::new(InputHandlerData {
+ port: None,
+ ignore_flags: self.ignore_flags,
+ callback: Box::new(callback),
+ user_data: Some(data)
+ });
+
+ let data_ptr = unsafe { mem::transmute_copy::<_, *mut InputHandlerData<T>>(&handler_data) };
+
+ self.client.as_mut().unwrap().set_process_callback(handle_input::<T>, data_ptr as *mut c_void);
+ self.client.as_mut().unwrap().activate();
+ handler_data
+ }
+
+ pub fn connect<F, T: Send>(
+ mut self, port: &MidiInputPort, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+
+ let mut handler_data = self.activate_callback(callback, data);
+
+ // Create port ...
+ let dest_port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsInput) {
+ Ok(p) => p,
+ Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); }
+ };
+
+ // ... and connect it to the output
+ if let Err(_) = self.client.as_mut().unwrap().connect(&port.name, dest_port.get_name()) {
+ return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self));
+ }
+
+ handler_data.port = Some(dest_port);
+
+ Ok(MidiInputConnection {
+ handler_data: handler_data,
+ client: self.client.take()
+ })
+ }
+
+ pub fn create_virtual<F, T: Send>(
+ mut self, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<Self>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+
+ let mut handler_data = self.activate_callback(callback, data);
+
+ // Create port
+ let port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsInput) {
+ Ok(p) => p,
+ Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); }
+ };
+
+ handler_data.port = Some(port);
+
+ Ok(MidiInputConnection {
+ handler_data: handler_data,
+ client: self.client.take()
+ })
+ }
+}
+
+impl<T> MidiInputConnection<T> {
+ pub fn close(mut self) -> (MidiInput, T) {
+ self.close_internal();
+
+ (MidiInput {
+ client: self.client.take(),
+ ignore_flags: self.handler_data.ignore_flags,
+ }, self.handler_data.user_data.take().unwrap())
+ }
+
+ fn close_internal(&mut self) {
+ let port = self.handler_data.port.take().unwrap();
+ self.client.as_mut().unwrap().unregister_midi_port(port);
+ self.client.as_mut().unwrap().deactivate();
+ }
+}
+
+impl<T> Drop for MidiInputConnection<T> {
+ fn drop(&mut self) {
+ if self.client.is_some() {
+ self.close_internal();
+ }
+ }
+}
+
+extern "C" fn handle_input<T>(nframes: jack_nframes_t, arg: *mut c_void) -> i32 {
+ let data: &mut InputHandlerData<T> = unsafe { &mut *(arg as *mut InputHandlerData<T>) };
+
+ // Is port created?
+ if let Some(ref port) = data.port {
+ let buff = port.get_midi_buffer(nframes);
+
+ let mut message = MidiMessage::new(); // TODO: create MidiMessage once and reuse its buffer for every handle_input call
+
+ // We have midi events in buffer
+ let evcount = buff.get_event_count();
+ let mut event = mem::MaybeUninit::uninit();
+
+ for j in 0..evcount {
+ message.bytes.clear();
+ unsafe { buff.get_event(event.as_mut_ptr(), j) };
+ let event = unsafe { event.assume_init() };
+
+ for i in 0..event.size {
+ message.bytes.push(unsafe { *event.buffer.offset(i as isize) });
+ }
+
+ message.timestamp = Client::get_time(); // this is in microseconds
+ (data.callback)(message.timestamp, &message.bytes, data.user_data.as_mut().unwrap());
+ }
+ }
+
+ return 0;
+}
+
+struct OutputHandlerData {
+ port: Option<MidiPort>,
+ buff_size: Ringbuffer,
+ buff_message: Ringbuffer,
+}
+
+pub struct MidiOutput {
+ client: Option<Client>,
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MidiOutputPort {
+ name: CString
+}
+
+pub struct MidiOutputConnection {
+ handler_data: Box<OutputHandlerData>,
+ client: Option<Client>
+}
+
+impl MidiOutput {
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ let client = match Client::open(client_name, JackOpenOptions::NoStartServer) {
+ Ok(c) => c,
+ Err(_) => { return Err(InitError); } // TODO: maybe add message that Jack server might not be running
+ };
+
+ Ok(MidiOutput {
+ client: Some(client),
+ })
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiOutputPort> {
+ let ports = self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsInput);
+ let mut result = Vec::with_capacity(ports.count());
+ for i in 0..ports.count() {
+ result.push(::common::MidiOutputPort {
+ imp: MidiOutputPort {
+ name: ports.get_c_name(i).into()
+ }
+ })
+ }
+ result
+ }
+
+ pub fn port_count(&self) -> usize {
+ self.client.as_ref().unwrap().get_midi_ports(PortFlags::PortIsInput).count()
+ }
+
+ pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ Ok(port.name.to_string_lossy().into())
+ }
+
+ fn activate_callback(&mut self) -> Box<OutputHandlerData> {
+ let handler_data = Box::new(OutputHandlerData {
+ port: None,
+ buff_size: Ringbuffer::new(OUTPUT_RINGBUFFER_SIZE),
+ buff_message: Ringbuffer::new(OUTPUT_RINGBUFFER_SIZE)
+ });
+
+ let data_ptr = unsafe { mem::transmute_copy::<_, *mut OutputHandlerData>(&handler_data) };
+
+ self.client.as_mut().unwrap().set_process_callback(handle_output, data_ptr as *mut c_void);
+ self.client.as_mut().unwrap().activate();
+ handler_data
+ }
+
+ pub fn connect(mut self, port: &MidiOutputPort, port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ let mut handler_data = self.activate_callback();
+
+ // Create port ...
+ let source_port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsOutput) {
+ Ok(p) => p,
+ Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); }
+ };
+
+ // ... and connect it to the input
+ if let Err(_) = self.client.as_mut().unwrap().connect(source_port.get_name(), &port.name) {
+ return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self));
+ }
+
+ handler_data.port = Some(source_port);
+
+ Ok(MidiOutputConnection {
+ handler_data: handler_data,
+ client: self.client.take()
+ })
+ }
+
+ pub fn create_virtual(
+ mut self, port_name: &str
+ ) -> Result<MidiOutputConnection, ConnectError<Self>> {
+ let mut handler_data = self.activate_callback();
+
+ // Create port
+ let port = match self.client.as_mut().unwrap().register_midi_port(port_name, PortFlags::PortIsOutput) {
+ Ok(p) => p,
+ Err(()) => { return Err(ConnectError::other("could not register JACK port", self)); }
+ };
+
+ handler_data.port = Some(port);
+
+ Ok(MidiOutputConnection {
+ handler_data: handler_data,
+ client: self.client.take()
+ })
+ }
+}
+
+impl MidiOutputConnection {
+ pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
+ let nbytes = message.len();
+
+ // Write full message to buffer
+ let written = self.handler_data.buff_message.write(message);
+ debug_assert!(written == nbytes, "not enough bytes written to ALSA ringbuffer `message`");
+ let nbytes_slice = unsafe { slice::from_raw_parts(&nbytes as *const usize as *const u8, mem::size_of_val(&nbytes)) };
+ let written = self.handler_data.buff_size.write(nbytes_slice);
+ debug_assert!(written == mem::size_of_val(&nbytes), "not enough bytes written to ALSA ringbuffer `size`");
+ Ok(())
+ }
+
+ pub fn close(mut self) -> MidiOutput {
+ self.close_internal();
+
+ MidiOutput {
+ client: self.client.take(),
+ }
+ }
+
+ fn close_internal(&mut self) {
+ let port = self.handler_data.port.take().unwrap();
+ self.client.as_mut().unwrap().unregister_midi_port(port);
+ self.client.as_mut().unwrap().deactivate();
+ }
+}
+
+impl Drop for MidiOutputConnection {
+ fn drop(&mut self) {
+ if self.client.is_some() {
+ self.close_internal();
+ }
+ }
+}
+
+extern "C" fn handle_output(nframes: jack_nframes_t, arg: *mut c_void) -> i32 {
+ let data: &mut OutputHandlerData = unsafe { mem::transmute(arg) };
+
+ // Is port created?
+ if let Some(ref port) = data.port {
+ let mut space: usize = 0;
+
+ let mut buff = port.get_midi_buffer(nframes);
+ buff.clear();
+
+ while data.buff_size.get_read_space() > 0 {
+ let read = data.buff_size.read(&mut space as *mut usize as *mut u8, mem::size_of::<usize>());
+ debug_assert!(read == mem::size_of::<usize>(), "not enough bytes read from `size` ringbuffer");
+ let midi_data = buff.event_reserve(0, space);
+ let read = data.buff_message.read(midi_data, space);
+ debug_assert!(read == space, "not enough bytes read from `message` ringbuffer");
+ }
+ }
+
+ return 0;
+}
diff --git a/third_party/rust/midir/src/backend/jack/wrappers.rs b/third_party/rust/midir/src/backend/jack/wrappers.rs
new file mode 100644
index 0000000000..ef00e2d73f
--- /dev/null
+++ b/third_party/rust/midir/src/backend/jack/wrappers.rs
@@ -0,0 +1,250 @@
+#![allow(non_upper_case_globals, dead_code)]
+
+use std::{ptr, slice, str};
+use std::ffi::{CStr, CString};
+use std::ops::Index;
+
+use super::libc::{c_void, size_t};
+
+use super::jack_sys::{
+ jack_get_time,
+ jack_client_t,
+ jack_client_open,
+ jack_client_close,
+ jack_get_ports,
+ jack_port_t,
+ jack_port_register,
+ jack_port_unregister,
+ jack_port_name,
+ jack_free,
+ jack_activate,
+ jack_deactivate,
+ jack_nframes_t,
+ jack_set_process_callback,
+ jack_connect,
+ jack_port_get_buffer,
+ jack_midi_data_t,
+ jack_midi_get_event_count,
+ jack_midi_event_get,
+ jack_midi_event_t,
+ jack_midi_clear_buffer,
+ jack_midi_event_reserve,
+ jack_ringbuffer_t,
+ jack_ringbuffer_create,
+ jack_ringbuffer_read_space,
+ jack_ringbuffer_read,
+ jack_ringbuffer_free,
+ jack_ringbuffer_write,
+};
+
+pub const JACK_DEFAULT_MIDI_TYPE: &[u8] = b"8 bit raw midi\0";
+
+bitflags! {
+ pub struct JackOpenOptions: u32 {
+ const NoStartServer = 1;
+ const UseExactName = 2;
+ const ServerName = 4;
+ const SessionID = 32;
+ }
+}
+
+bitflags! {
+ pub struct PortFlags: u32 {
+ const PortIsInput = 1;
+ const PortIsOutput = 2;
+ const PortIsPhysical = 4;
+ const PortCanMonitor = 8;
+ const PortIsTerminal = 16;
+ }
+}
+
+// TODO: hide this type
+pub type ProcessCallback = extern "C" fn(nframes: jack_nframes_t, arg: *mut c_void) -> i32;
+
+pub struct Client {
+ p: *mut jack_client_t
+}
+
+unsafe impl Send for Client {}
+
+impl Client {
+ pub fn get_time() -> u64 {
+ unsafe { jack_get_time() }
+ }
+
+ pub fn open(name: &str, options: JackOpenOptions) -> Result<Client, ()> {
+ let c_name = CString::new(name).ok().expect("client name must not contain null bytes");
+ let result = unsafe { jack_client_open(c_name.as_ptr(), options.bits(), ptr::null_mut()) };
+ if result.is_null() {
+ Err(())
+ } else {
+ Ok(Client { p: result })
+ }
+ }
+
+ pub fn get_midi_ports(&self, flags: PortFlags) -> PortInfos {
+ let ports_ptr = unsafe { jack_get_ports(self.p, ptr::null_mut(), JACK_DEFAULT_MIDI_TYPE.as_ptr() as *const i8, flags.bits() as u64) };
+ let slice = if ports_ptr.is_null() {
+ &[]
+ } else {
+ unsafe {
+ let count = (0isize..).find(|i| (*ports_ptr.offset(*i)).is_null()).unwrap() as usize;
+ slice::from_raw_parts(ports_ptr, count)
+ }
+ };
+ PortInfos { p: slice }
+ }
+
+ pub fn register_midi_port(&mut self, name: &str, flags: PortFlags) -> Result<MidiPort, ()> {
+ let c_name = CString::new(name).ok().expect("port name must not contain null bytes");
+ let result = unsafe { jack_port_register(self.p, c_name.as_ptr(), JACK_DEFAULT_MIDI_TYPE.as_ptr() as *const i8, flags.bits() as u64, 0) };
+ if result.is_null() {
+ Err(())
+ } else {
+ Ok(MidiPort { p: result })
+ }
+ }
+
+ /// This can not be implemented in Drop, because it needs a reference
+ /// to the client. But it consumes the MidiPort.
+ pub fn unregister_midi_port(&mut self, client: MidiPort) {
+ unsafe { jack_port_unregister(self.p, client.p) };
+ }
+
+ pub fn activate(&mut self) {
+ unsafe { jack_activate(self.p) };
+ }
+
+ pub fn deactivate(&mut self) {
+ unsafe { jack_deactivate(self.p) };
+ }
+
+ /// The code in the supplied function must be suitable for real-time
+ /// execution. That means that it cannot call functions that might block
+ /// for a long time. This includes all I/O functions (disk, TTY, network),
+ /// malloc, free, printf, pthread_mutex_lock, sleep, wait, poll, select,
+ /// pthread_join, pthread_cond_wait, etc, etc.
+ pub fn set_process_callback(&mut self, callback: ProcessCallback, data: *mut c_void) {
+ unsafe { jack_set_process_callback(self.p, Some(callback), data) };
+ }
+
+ pub fn connect(&mut self, source_port: &CStr, destination_port: &CStr) -> Result<(), ()> {
+ let rc = unsafe { jack_connect(self.p, source_port.as_ptr(), destination_port.as_ptr()) };
+ if rc == 0 {
+ Ok(())
+ } else {
+ Err(()) // TODO: maybe handle EEXIST explicitly
+ }
+ }
+}
+
+impl Drop for Client {
+ fn drop(&mut self) {
+ unsafe { jack_client_close(self.p) };
+ }
+}
+
+pub struct PortInfos<'a> {
+ p: &'a[*const i8],
+}
+
+unsafe impl<'a> Send for PortInfos<'a> {}
+
+impl<'a> PortInfos<'a> {
+ pub fn count(&self) -> usize {
+ self.p.len()
+ }
+
+ pub fn get_c_name(&self, index: usize) -> &CStr {
+ let ptr = self.p[index];
+ unsafe { CStr::from_ptr(ptr) }
+ }
+}
+
+impl<'a> Index<usize> for PortInfos<'a> {
+ type Output = str;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ let slice = self.get_c_name(index).to_bytes();
+ str::from_utf8(slice).ok().expect("Error converting port name to UTF8")
+ }
+}
+
+impl<'a> Drop for PortInfos<'a> {
+ fn drop(&mut self) {
+ if self.p.len() > 0 {
+ unsafe { jack_free(self.p.as_ptr() as *mut _) }
+ }
+ }
+}
+
+pub struct MidiPort {
+ p: *mut jack_port_t
+}
+
+unsafe impl Send for MidiPort {}
+
+impl MidiPort {
+ pub fn get_name(&self) -> &CStr {
+ unsafe { CStr::from_ptr(jack_port_name(self.p)) }
+ }
+
+ pub fn get_midi_buffer(&self, nframes: jack_nframes_t) -> MidiBuffer {
+ let buf = unsafe { jack_port_get_buffer(self.p, nframes) };
+ MidiBuffer { p: buf }
+ }
+}
+
+pub struct MidiBuffer {
+ p: *mut c_void
+}
+
+impl MidiBuffer {
+ pub fn get_event_count(&self) -> u32 {
+ unsafe { jack_midi_get_event_count(self.p) }
+ }
+
+ pub unsafe fn get_event(&self, ev: *mut jack_midi_event_t, index: u32) {
+ jack_midi_event_get(ev, self.p, index);
+ }
+
+ pub fn clear(&mut self) {
+ unsafe { jack_midi_clear_buffer(self.p) }
+ }
+
+ pub fn event_reserve(&mut self, time: jack_nframes_t, data_size: usize) -> *mut jack_midi_data_t {
+ unsafe { jack_midi_event_reserve(self.p, time, data_size as size_t) }
+ }
+}
+
+pub struct Ringbuffer {
+ p: *mut jack_ringbuffer_t
+}
+
+unsafe impl Send for Ringbuffer {}
+
+impl Ringbuffer {
+ pub fn new(size: usize) -> Ringbuffer {
+ let result = unsafe { jack_ringbuffer_create(size as size_t) };
+ Ringbuffer { p: result }
+ }
+
+ pub fn get_read_space(&self) -> usize {
+ unsafe { jack_ringbuffer_read_space(self.p) as usize }
+ }
+
+ pub fn read(&mut self, destination: *mut u8, count: usize) -> usize {
+ let bytes_read = unsafe { jack_ringbuffer_read(self.p, destination as *mut i8, count as size_t) };
+ bytes_read as usize
+ }
+
+ pub fn write(&mut self, source: &[u8]) -> usize {
+ unsafe { jack_ringbuffer_write(self.p, source.as_ptr() as *const i8, source.len() as size_t) as usize }
+ }
+}
+
+impl Drop for Ringbuffer{
+ fn drop(&mut self) {
+ unsafe { jack_ringbuffer_free(self.p) }
+ }
+} \ No newline at end of file
diff --git a/third_party/rust/midir/src/backend/mod.rs b/third_party/rust/midir/src/backend/mod.rs
new file mode 100644
index 0000000000..2940daeeff
--- /dev/null
+++ b/third_party/rust/midir/src/backend/mod.rs
@@ -0,0 +1,22 @@
+// This module is not public
+
+// TODO: improve feature selection (make sure that there is always exactly one implementation, or enable dynamic backend selection)
+// TODO: allow to disable build dependency on ALSA
+
+#[cfg(all(target_os="windows", not(feature = "winrt")))] mod winmm;
+#[cfg(all(target_os="windows", not(feature = "winrt")))] pub use self::winmm::*;
+
+#[cfg(all(target_os="windows", feature = "winrt"))] mod winrt;
+#[cfg(all(target_os="windows", feature = "winrt"))] pub use self::winrt::*;
+
+#[cfg(all(target_os="macos", not(feature = "jack")))] mod coremidi;
+#[cfg(all(target_os="macos", not(feature = "jack")))] pub use self::coremidi::*;
+
+#[cfg(all(target_os="linux", not(feature = "jack")))] mod alsa;
+#[cfg(all(target_os="linux", not(feature = "jack")))] pub use self::alsa::*;
+
+#[cfg(all(feature = "jack", not(target_os="windows")))] mod jack;
+#[cfg(all(feature = "jack", not(target_os="windows")))] pub use self::jack::*;
+
+#[cfg(target_arch="wasm32")] mod webmidi;
+#[cfg(target_arch="wasm32")] pub use self::webmidi::*;
diff --git a/third_party/rust/midir/src/backend/webmidi/mod.rs b/third_party/rust/midir/src/backend/webmidi/mod.rs
new file mode 100644
index 0000000000..b76f0b3617
--- /dev/null
+++ b/third_party/rust/midir/src/backend/webmidi/mod.rs
@@ -0,0 +1,250 @@
+//! Web MIDI Backend.
+//!
+//! Reference:
+//! * [W3C Editor's Draft](https://webaudio.github.io/web-midi-api/)
+//! * [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess)
+
+extern crate js_sys;
+extern crate wasm_bindgen;
+extern crate web_sys;
+
+use self::js_sys::{Map, Promise, Uint8Array};
+use self::wasm_bindgen::prelude::*;
+use self::wasm_bindgen::JsCast;
+use self::web_sys::{MidiAccess, MidiOptions, MidiMessageEvent};
+
+use std::cell::RefCell;
+use std::sync::{Arc, Mutex};
+
+use ::errors::*;
+use ::Ignore;
+
+
+
+thread_local! {
+ static STATIC : RefCell<Static> = RefCell::new(Static::new());
+}
+
+struct Static {
+ pub access: Option<MidiAccess>,
+ pub request: Option<Promise>,
+ pub ever_requested: bool,
+
+ pub on_ok: Closure<dyn FnMut(JsValue)>,
+ pub on_err: Closure<dyn FnMut(JsValue)>,
+}
+
+impl Static {
+ pub fn new() -> Self {
+ let mut s = Self {
+ access: None,
+ request: None,
+ ever_requested: false,
+
+ on_ok: Closure::wrap(Box::new(|access| {
+ STATIC.with(|s|{
+ let mut s = s.borrow_mut();
+ let access : MidiAccess = access.dyn_into().unwrap();
+ s.request = None;
+ s.access = Some(access);
+ });
+ })),
+ on_err: Closure::wrap(Box::new(|_error| {
+ STATIC.with(|s|{
+ let mut s = s.borrow_mut();
+ s.request = None;
+ });
+ })),
+ };
+ // Some notes on sysex behavior:
+ // 1) Some devices (but not all!) may work without sysex
+ // 2) Chrome will only prompt the end user to grant permission if they requested sysex permissions for now...
+ // but that's changing soon for "security reasons" (reduced fingerprinting? poorly tested drivers?):
+ // https://www.chromestatus.com/feature/5138066234671104
+ //
+ // I've chosen to hardcode sysex=true here, since that'll be compatible with more devices, *and* should change
+ // less behavior when Chrome's changes land.
+ s.request_midi_access(true);
+ s
+ }
+
+ fn request_midi_access(&mut self, sysex: bool) {
+ self.ever_requested = true;
+ if self.access.is_some() { return; } // Already have access
+ if self.request.is_some() { return; } // Mid-request already
+ let window = if let Some(w) = web_sys::window() { w } else { return; };
+
+ let _request = match window.navigator().request_midi_access_with_options(MidiOptions::new().sysex(sysex)) {
+ Ok(p) => { self.request = Some(p.then2(&self.on_ok, &self.on_err)); },
+ Err(_) => { return; } // node.js? brower doesn't support webmidi? other?
+ };
+ }
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MidiInputPort {
+ input: web_sys::MidiInput,
+}
+
+pub struct MidiInput {
+ ignore_flags: Ignore
+}
+
+impl MidiInput {
+ pub fn new(_client_name: &str) -> Result<Self, InitError> {
+ STATIC.with(|_|{});
+ Ok(MidiInput { ignore_flags: Ignore::None })
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiInputPort> {
+ STATIC.with(|s|{
+ let mut v = Vec::new();
+ let s = s.borrow();
+ if let Some(access) = s.access.as_ref() {
+ let inputs : Map = access.inputs().unchecked_into();
+ inputs.for_each(&mut |value, _|{
+ v.push(::common::MidiInputPort {
+ imp: MidiInputPort { input: value.dyn_into().unwrap() }
+ });
+ });
+ }
+ v
+ })
+ }
+
+ pub fn ignore(&mut self, flags: Ignore) {
+ self.ignore_flags = flags;
+ }
+
+ pub fn port_count(&self) -> usize {
+ STATIC.with(|s| {
+ let s = s.borrow();
+ s.access.as_ref().map(|access| access.inputs().unchecked_into::<Map>().size() as usize).unwrap_or(0)
+ })
+ }
+
+ pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ Ok(port.input.name().unwrap_or_else(|| port.input.id()))
+ }
+
+ pub fn connect<F, T: Send + 'static>(
+ self, port: &MidiInputPort, _port_name: &str, mut callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static
+ {
+ let input = port.input.clone();
+ let _ = input.open(); // NOTE: asyncronous!
+
+ let ignore_flags = self.ignore_flags;
+ let user_data = Arc::new(Mutex::new(Some(data)));
+
+ let closure = {
+ let user_data = user_data.clone();
+
+ let closure = Closure::wrap(Box::new(move |event: MidiMessageEvent| {
+ let time = (event.time_stamp() * 1000.0) as u64; // ms -> us
+ let buffer = event.data().unwrap();
+
+ let status = buffer[0];
+ if !(status == 0xF0 && ignore_flags.contains(Ignore::Sysex) ||
+ status == 0xF1 && ignore_flags.contains(Ignore::Time) ||
+ status == 0xF8 && ignore_flags.contains(Ignore::Time) ||
+ status == 0xFE && ignore_flags.contains(Ignore::ActiveSense))
+ {
+ callback(time, &buffer[..], user_data.lock().unwrap().as_mut().unwrap());
+ }
+ }) as Box<dyn FnMut(MidiMessageEvent)>);
+
+ input.set_onmidimessage(Some(closure.as_ref().unchecked_ref()));
+
+ closure
+ };
+
+ Ok(MidiInputConnection { ignore_flags, input, user_data, closure })
+ }
+}
+
+pub struct MidiInputConnection<T> {
+ ignore_flags: Ignore,
+ input: web_sys::MidiInput,
+ user_data: Arc<Mutex<Option<T>>>,
+ #[allow(dead_code)] // Must be kept alive until we decide to unregister from input
+ closure: Closure<dyn FnMut(MidiMessageEvent)>,
+}
+
+impl<T> MidiInputConnection<T> {
+ pub fn close(self) -> (MidiInput, T) {
+ let Self { ignore_flags, input, user_data, .. } = self;
+
+ input.set_onmidimessage(None);
+ let mut user_data = user_data.lock().unwrap();
+
+ (
+ MidiInput { ignore_flags },
+ user_data.take().unwrap()
+ )
+ }
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MidiOutputPort {
+ output: web_sys::MidiOutput,
+}
+
+pub struct MidiOutput {
+}
+
+impl MidiOutput {
+ pub fn new(_client_name: &str) -> Result<Self, InitError> {
+ STATIC.with(|_|{});
+ Ok(MidiOutput {})
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiOutputPort> {
+ STATIC.with(|s|{
+ let mut v = Vec::new();
+ let s = s.borrow();
+ if let Some(access) = s.access.as_ref() {
+ access.outputs().unchecked_into::<Map>().for_each(&mut |value, _|{
+ v.push(::common::MidiOutputPort {
+ imp: MidiOutputPort { output: value.dyn_into().unwrap() }
+ });
+ });
+ }
+ v
+ })
+ }
+
+ pub fn port_count(&self) -> usize {
+ STATIC.with(|s|{
+ let s = s.borrow();
+ s.access.as_ref().map(|access| access.outputs().unchecked_into::<Map>().size() as usize).unwrap_or(0)
+ })
+ }
+
+ pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ Ok(port.output.name().unwrap_or_else(|| port.output.id()))
+ }
+
+ pub fn connect(self, port: &MidiOutputPort, _port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ let _ = port.output.open(); // NOTE: asyncronous!
+ Ok(MidiOutputConnection{
+ output: port.output.clone()
+ })
+ }
+}
+
+pub struct MidiOutputConnection {
+ output: web_sys::MidiOutput,
+}
+
+impl MidiOutputConnection {
+ pub fn close(self) -> MidiOutput {
+ let _ = self.output.close(); // NOTE: asyncronous!
+ MidiOutput {}
+ }
+
+ pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
+ self.output.send(unsafe { Uint8Array::view(message) }.as_ref()).map_err(|_| SendError::Other("JavaScript exception"))
+ }
+}
diff --git a/third_party/rust/midir/src/backend/winmm/handler.rs b/third_party/rust/midir/src/backend/winmm/handler.rs
new file mode 100644
index 0000000000..b04d2ee908
--- /dev/null
+++ b/third_party/rust/midir/src/backend/winmm/handler.rs
@@ -0,0 +1,85 @@
+use std::{mem, slice};
+use std::io::{Write, stderr};
+
+use super::winapi::shared::basetsd::DWORD_PTR;
+use super::winapi::shared::minwindef::{DWORD, UINT};
+use super::winapi::um::mmeapi::midiInAddBuffer;
+use super::winapi::um::mmsystem::{HMIDIIN, MIDIHDR, MMSYSERR_NOERROR, MM_MIM_DATA,
+ MM_MIM_LONGDATA, MM_MIM_LONGERROR};
+use super::HandlerData;
+use Ignore;
+
+pub extern "system" fn handle_input<T>(_: HMIDIIN,
+ input_status: UINT,
+ instance_ptr: DWORD_PTR,
+ midi_message: DWORD_PTR,
+ timestamp: DWORD) {
+ if input_status != MM_MIM_DATA && input_status != MM_MIM_LONGDATA && input_status != MM_MIM_LONGERROR { return; }
+
+ let data: &mut HandlerData<T> = unsafe { &mut *(instance_ptr as *mut HandlerData<T>) };
+
+ // Calculate time stamp.
+ data.message.timestamp = timestamp as u64 * 1000; // milliseconds -> microseconds
+
+ if input_status == MM_MIM_DATA { // Channel or system message
+ // Make sure the first byte is a status byte.
+ let status: u8 = (midi_message & 0x000000FF) as u8;
+ if !(status & 0x80 != 0) { return; }
+
+ // Determine the number of bytes in the MIDI message.
+ let nbytes: u16 = if status < 0xC0 { 3 }
+ else if status < 0xE0 { 2 }
+ else if status < 0xF0 { 3 }
+ else if status == 0xF1 {
+ if data.ignore_flags.contains(Ignore::Time) { return; }
+ else { 2 }
+ } else if status == 0xF2 { 3 }
+ else if status == 0xF3 { 2 }
+ else if status == 0xF8 && (data.ignore_flags.contains(Ignore::Time)) {
+ // A MIDI timing tick message and we're ignoring it.
+ return;
+ } else if status == 0xFE && (data.ignore_flags.contains(Ignore::ActiveSense)) {
+ // A MIDI active sensing message and we're ignoring it.
+ return;
+ } else { 1 };
+
+ // Copy bytes to our MIDI message.
+ let ptr = (&midi_message) as *const DWORD_PTR as *const u8;
+ let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr, nbytes as usize) };
+ data.message.bytes.extend_from_slice(bytes);
+ } else { // Sysex message (MIM_LONGDATA or MIM_LONGERROR)
+ let sysex = unsafe { &*(midi_message as *const MIDIHDR) };
+ if !data.ignore_flags.contains(Ignore::Sysex) && input_status != MM_MIM_LONGERROR {
+ // Sysex message and we're not ignoring it
+ let bytes: &[u8] = unsafe { slice::from_raw_parts(sysex.lpData as *const u8, sysex.dwBytesRecorded as usize) };
+ data.message.bytes.extend_from_slice(bytes);
+ // TODO: If sysex messages are longer than RT_SYSEX_BUFFER_SIZE, they
+ // are split in chunks. We could reassemble a single message.
+ }
+
+ // The WinMM API requires that the sysex buffer be requeued after
+ // input of each sysex message. Even if we are ignoring sysex
+ // messages, we still need to requeue the buffer in case the user
+ // decides to not ignore sysex messages in the future. However,
+ // it seems that WinMM calls this function with an empty sysex
+ // buffer when an application closes and in this case, we should
+ // avoid requeueing it, else the computer suddenly reboots after
+ // one or two minutes.
+ if (unsafe {*data.sysex_buffer.0[sysex.dwUser as usize]}).dwBytesRecorded > 0 {
+ //if ( sysex->dwBytesRecorded > 0 ) {
+ let in_handle = data.in_handle.as_ref().unwrap().0.lock().unwrap();
+ let result = unsafe { midiInAddBuffer(*in_handle, data.sysex_buffer.0[sysex.dwUser as usize], mem::size_of::<MIDIHDR>() as u32) };
+ drop(in_handle);
+ if result != MMSYSERR_NOERROR {
+ let _ = writeln!(stderr(), "\nError in handle_input: Requeuing WinMM input sysex buffer failed.\n");
+ }
+
+ if data.ignore_flags.contains(Ignore::Sysex) { return; }
+ } else { return; }
+ }
+
+ (data.callback)(data.message.timestamp, &data.message.bytes, data.user_data.as_mut().unwrap());
+
+ // Clear the vector for the next input message.
+ data.message.bytes.clear();
+} \ No newline at end of file
diff --git a/third_party/rust/midir/src/backend/winmm/mod.rs b/third_party/rust/midir/src/backend/winmm/mod.rs
new file mode 100644
index 0000000000..2d20004ece
--- /dev/null
+++ b/third_party/rust/midir/src/backend/winmm/mod.rs
@@ -0,0 +1,536 @@
+extern crate winapi;
+
+use std::{mem, ptr, slice};
+use std::ffi::OsString;
+use std::os::windows::ffi::OsStringExt;
+use std::sync::Mutex;
+use std::io::{Write, stderr};
+use std::thread::sleep;
+use std::time::Duration;
+use memalloc::{allocate, deallocate};
+use std::mem::MaybeUninit;
+use std::ptr::null_mut;
+
+use self::winapi::shared::basetsd::{DWORD_PTR, UINT_PTR};
+use self::winapi::shared::minwindef::{DWORD, UINT};
+
+use self::winapi::um::mmeapi::{midiInAddBuffer, midiInClose, midiInGetDevCapsW, midiInGetNumDevs,
+ midiInOpen, midiInPrepareHeader, midiInReset, midiInStart,
+ midiInStop, midiInUnprepareHeader, midiOutClose,
+ midiOutGetDevCapsW, midiOutGetNumDevs, midiOutLongMsg, midiOutOpen,
+ midiOutPrepareHeader, midiOutReset, midiOutShortMsg,
+ midiOutUnprepareHeader};
+
+use self::winapi::um::mmsystem::{CALLBACK_FUNCTION, CALLBACK_NULL, HMIDIIN, HMIDIOUT, LPMIDIHDR,
+ MIDIERR_NOTREADY, MIDIERR_STILLPLAYING, MIDIHDR, MIDIINCAPSW,
+ MIDIOUTCAPSW, MMSYSERR_BADDEVICEID, MMSYSERR_NOERROR, MMSYSERR_ALLOCATED};
+
+use {Ignore, MidiMessage};
+use errors::*;
+
+mod handler;
+
+const DRV_QUERYDEVICEINTERFACE: UINT = 0x80c;
+const DRV_QUERYDEVICEINTERFACESIZE: UINT = 0x80d;
+
+const RT_SYSEX_BUFFER_SIZE: usize = 1024;
+const RT_SYSEX_BUFFER_COUNT: usize = 4;
+
+// helper for string conversion
+fn from_wide_ptr(ptr: *const u16, max_len: usize) -> OsString {
+ unsafe {
+ assert!(!ptr.is_null());
+ let len = (0..max_len as isize).position(|i| *ptr.offset(i) == 0).unwrap();
+ let slice = slice::from_raw_parts(ptr, len);
+ OsString::from_wide(slice)
+ }
+}
+
+#[derive(Debug)]
+pub struct MidiInput {
+ ignore_flags: Ignore
+}
+
+#[derive(Clone)]
+pub struct MidiInputPort {
+ name: String,
+ interface_id: Box<[u16]>
+}
+
+impl PartialEq for MidiInputPort {
+ fn eq(&self, other: &Self) -> bool {
+ self.interface_id == other.interface_id
+ }
+}
+
+pub struct MidiInputConnection<T> {
+ handler_data: Box<HandlerData<T>>,
+}
+
+impl MidiInputPort {
+ pub fn count() -> UINT {
+ unsafe { midiInGetNumDevs() }
+ }
+
+ fn interface_id(port_number: UINT) -> Result<Box<[u16]>, PortInfoError> {
+ let mut buffer_size: winapi::shared::minwindef::ULONG = 0;
+ let result = unsafe { winapi::um::mmeapi::midiInMessage(port_number as HMIDIIN, DRV_QUERYDEVICEINTERFACESIZE, &mut buffer_size as *mut _ as DWORD_PTR, 0) };
+ if result == MMSYSERR_BADDEVICEID {
+ return Err(PortInfoError::PortNumberOutOfRange)
+ } else if result != MMSYSERR_NOERROR {
+ return Err(PortInfoError::CannotRetrievePortName)
+ }
+ let mut buffer = Vec::<u16>::with_capacity(buffer_size as usize / 2);
+ unsafe {
+ let result = winapi::um::mmeapi::midiInMessage(port_number as HMIDIIN, DRV_QUERYDEVICEINTERFACE, buffer.as_mut_ptr() as DWORD_PTR, buffer_size as DWORD_PTR);
+ if result == MMSYSERR_BADDEVICEID {
+ return Err(PortInfoError::PortNumberOutOfRange)
+ } else if result != MMSYSERR_NOERROR {
+ return Err(PortInfoError::CannotRetrievePortName)
+ }
+ buffer.set_len(buffer_size as usize / 2);
+ }
+ //println!("{}", from_wide_ptr(buffer.as_ptr(), buffer.len()).to_string_lossy().into_owned());
+ Ok(buffer.into_boxed_slice())
+ }
+
+ fn name(port_number: UINT) -> Result<String, PortInfoError> {
+ let mut device_caps: MaybeUninit<MIDIINCAPSW> = MaybeUninit::uninit();
+ let result = unsafe { midiInGetDevCapsW(port_number as UINT_PTR, device_caps.as_mut_ptr(), mem::size_of::<MIDIINCAPSW>() as u32) };
+ if result == MMSYSERR_BADDEVICEID {
+ return Err(PortInfoError::PortNumberOutOfRange)
+ } else if result != MMSYSERR_NOERROR {
+ return Err(PortInfoError::CannotRetrievePortName)
+ }
+ let device_caps = unsafe { device_caps.assume_init() };
+ let pname = device_caps.szPname;
+ let output = from_wide_ptr(pname.as_ptr(), pname.len()).to_string_lossy().into_owned();
+ Ok(output)
+ }
+
+ fn from_port_number(port_number: UINT) -> Result<Self, PortInfoError> {
+ Ok(MidiInputPort {
+ name: Self::name(port_number)?,
+ interface_id: Self::interface_id(port_number)?
+ })
+ }
+
+ fn current_port_number(&self) -> Option<UINT> {
+ for i in 0..Self::count() {
+ if let Ok(name) = Self::name(i) {
+ if name != self.name { continue; }
+ if let Ok(id) = Self::interface_id(i) {
+ if id == self.interface_id {
+ return Some(i);
+ }
+ }
+ }
+ }
+ None
+ }
+}
+
+struct SysexBuffer([LPMIDIHDR; RT_SYSEX_BUFFER_COUNT]);
+unsafe impl Send for SysexBuffer {}
+
+struct MidiInHandle(Mutex<HMIDIIN>);
+unsafe impl Send for MidiInHandle {}
+
+/// This is all the data that is stored on the heap as long as a connection
+/// is opened and passed to the callback handler.
+///
+/// It is important that `user_data` is the last field to not influence
+/// offsets after monomorphization.
+struct HandlerData<T> {
+ message: MidiMessage,
+ sysex_buffer: SysexBuffer,
+ in_handle: Option<MidiInHandle>,
+ ignore_flags: Ignore,
+ callback: Box<dyn FnMut(u64, &[u8], &mut T) + Send + 'static>,
+ user_data: Option<T>
+}
+
+impl MidiInput {
+ pub fn new(_client_name: &str) -> Result<Self, InitError> {
+ Ok(MidiInput { ignore_flags: Ignore::None })
+ }
+
+ pub fn ignore(&mut self, flags: Ignore) {
+ self.ignore_flags = flags;
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiInputPort> {
+ let count = MidiInputPort::count();
+ let mut result = Vec::with_capacity(count as usize);
+ for i in 0..count {
+ let port = match MidiInputPort::from_port_number(i) {
+ Ok(p) => p,
+ Err(_) => continue
+ };
+ result.push(::common::MidiInputPort {
+ imp: port
+ });
+ }
+ result
+ }
+
+ pub fn port_count(&self) -> usize {
+ MidiInputPort::count() as usize
+ }
+
+ pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ Ok(port.name.clone())
+ }
+
+ pub fn connect<F, T: Send>(
+ self, port: &MidiInputPort, _port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+
+ let port_number = match port.current_port_number() {
+ Some(p) => p,
+ None => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ };
+
+ let mut handler_data = Box::new(HandlerData {
+ message: MidiMessage::new(),
+ sysex_buffer: SysexBuffer([null_mut(); RT_SYSEX_BUFFER_COUNT]),
+ in_handle: None,
+ ignore_flags: self.ignore_flags,
+ callback: Box::new(callback),
+ user_data: Some(data)
+ });
+
+ let mut in_handle: MaybeUninit<HMIDIIN> = MaybeUninit::uninit();
+ let handler_data_ptr: *mut HandlerData<T> = &mut *handler_data;
+ let result = unsafe { midiInOpen(in_handle.as_mut_ptr(),
+ port_number as UINT,
+ handler::handle_input::<T> as DWORD_PTR,
+ handler_data_ptr as DWORD_PTR,
+ CALLBACK_FUNCTION) };
+ if result == MMSYSERR_ALLOCATED {
+ return Err(ConnectError::other("could not create Windows MM MIDI input port (MMSYSERR_ALLOCATED)", self));
+ } else if result != MMSYSERR_NOERROR {
+ return Err(ConnectError::other("could not create Windows MM MIDI input port", self));
+ }
+ let in_handle = unsafe { in_handle.assume_init() };
+
+ // Allocate and init the sysex buffers.
+ for i in 0..RT_SYSEX_BUFFER_COUNT {
+ handler_data.sysex_buffer.0[i] = Box::into_raw(Box::new(MIDIHDR {
+ lpData: unsafe { allocate(RT_SYSEX_BUFFER_SIZE/*, mem::align_of::<u8>()*/) } as *mut i8,
+ dwBufferLength: RT_SYSEX_BUFFER_SIZE as u32,
+ dwBytesRecorded: 0,
+ dwUser: i as DWORD_PTR, // We use the dwUser parameter as buffer indicator
+ dwFlags: 0,
+ lpNext: ptr::null_mut(),
+ reserved: 0,
+ dwOffset: 0,
+ dwReserved: unsafe { mem::zeroed() },
+ }));
+
+ // TODO: are those buffers ever freed if an error occurs here (altough these calls probably only fail with out-of-memory)?
+ // TODO: close port in case of error?
+
+ let result = unsafe { midiInPrepareHeader(in_handle, handler_data.sysex_buffer.0[i], mem::size_of::<MIDIHDR>() as u32) };
+ if result != MMSYSERR_NOERROR {
+ return Err(ConnectError::other("could not initialize Windows MM MIDI input port (PrepareHeader)", self));
+ }
+
+ // Register the buffer.
+ let result = unsafe { midiInAddBuffer(in_handle, handler_data.sysex_buffer.0[i], mem::size_of::<MIDIHDR>() as u32) };
+ if result != MMSYSERR_NOERROR {
+ return Err(ConnectError::other("could not initialize Windows MM MIDI input port (AddBuffer)", self));
+ }
+ }
+
+ handler_data.in_handle = Some(MidiInHandle(Mutex::new(in_handle)));
+
+ // We can safely access (a copy of) `in_handle` here, although
+ // it has been copied into the Mutex already, because the callback
+ // has not been called yet.
+ let result = unsafe { midiInStart(in_handle) };
+ if result != MMSYSERR_NOERROR {
+ unsafe { midiInClose(in_handle) };
+ return Err(ConnectError::other("could not start Windows MM MIDI input port", self));
+ }
+
+ Ok(MidiInputConnection {
+ handler_data: handler_data
+ })
+ }
+}
+
+impl<T> MidiInputConnection<T> {
+ pub fn close(mut self) -> (MidiInput, T) {
+ self.close_internal();
+
+ (MidiInput {
+ ignore_flags: self.handler_data.ignore_flags,
+ }, self.handler_data.user_data.take().unwrap())
+ }
+
+ fn close_internal(&mut self) {
+ // for information about his lock, see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
+ let in_handle_lock = self.handler_data.in_handle.as_ref().unwrap().0.lock().unwrap();
+
+ // TODO: Call both reset and stop here? The difference seems to be that
+ // reset "returns all pending input buffers to the callback function"
+ unsafe {
+ midiInReset(*in_handle_lock);
+ midiInStop(*in_handle_lock);
+ }
+
+ for i in 0..RT_SYSEX_BUFFER_COUNT {
+ let result;
+ unsafe {
+ result = midiInUnprepareHeader(*in_handle_lock, self.handler_data.sysex_buffer.0[i], mem::size_of::<MIDIHDR>() as u32);
+ deallocate((*self.handler_data.sysex_buffer.0[i]).lpData as *mut u8, RT_SYSEX_BUFFER_SIZE/*, mem::align_of::<u8>()*/);
+ // recreate the Box so that it will be dropped/deallocated at the end of this scope
+ let _ = Box::from_raw(self.handler_data.sysex_buffer.0[i]);
+ }
+
+ if result != MMSYSERR_NOERROR {
+ let _ = writeln!(stderr(), "Warning: Ignoring error shutting down Windows MM input port (UnprepareHeader).");
+ }
+ }
+
+ unsafe { midiInClose(*in_handle_lock) };
+ }
+}
+
+impl<T> Drop for MidiInputConnection<T> {
+ fn drop(&mut self) {
+ // If user_data has been emptied, we know that we already have closed the connection
+ if self.handler_data.user_data.is_some() {
+ self.close_internal()
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct MidiOutput;
+
+#[derive(Clone)]
+pub struct MidiOutputPort {
+ name: String,
+ interface_id: Box<[u16]>
+}
+
+impl PartialEq for MidiOutputPort {
+ fn eq(&self, other: &Self) -> bool {
+ self.interface_id == other.interface_id
+ }
+}
+
+pub struct MidiOutputConnection {
+ out_handle: HMIDIOUT,
+}
+
+unsafe impl Send for MidiOutputConnection {}
+
+impl MidiOutputPort {
+ pub fn count() -> UINT {
+ unsafe { midiOutGetNumDevs() }
+ }
+
+ fn interface_id(port_number: UINT) -> Result<Box<[u16]>, PortInfoError> {
+ let mut buffer_size: winapi::shared::minwindef::ULONG = 0;
+ let result = unsafe { winapi::um::mmeapi::midiOutMessage(port_number as HMIDIOUT, DRV_QUERYDEVICEINTERFACESIZE, &mut buffer_size as *mut _ as DWORD_PTR, 0) };
+ if result == MMSYSERR_BADDEVICEID {
+ return Err(PortInfoError::PortNumberOutOfRange)
+ } else if result != MMSYSERR_NOERROR {
+ return Err(PortInfoError::CannotRetrievePortName)
+ }
+ let mut buffer = Vec::<u16>::with_capacity(buffer_size as usize / 2);
+ unsafe {
+ let result = winapi::um::mmeapi::midiOutMessage(port_number as HMIDIOUT, DRV_QUERYDEVICEINTERFACE, buffer.as_mut_ptr() as DWORD_PTR, buffer_size as DWORD_PTR);
+ if result == MMSYSERR_BADDEVICEID {
+ return Err(PortInfoError::PortNumberOutOfRange)
+ } else if result != MMSYSERR_NOERROR {
+ return Err(PortInfoError::CannotRetrievePortName)
+ }
+ buffer.set_len(buffer_size as usize / 2);
+ }
+ //println!("{}", from_wide_ptr(buffer.as_ptr(), buffer.len()).to_string_lossy().into_owned());
+ Ok(buffer.into_boxed_slice())
+ }
+
+ fn name(port_number: UINT) -> Result<String, PortInfoError> {
+ let mut device_caps: MaybeUninit<MIDIOUTCAPSW> = MaybeUninit::uninit();
+ let result = unsafe { midiOutGetDevCapsW(port_number as UINT_PTR, device_caps.as_mut_ptr(), mem::size_of::<MIDIOUTCAPSW>() as u32) };
+ if result == MMSYSERR_BADDEVICEID {
+ return Err(PortInfoError::PortNumberOutOfRange)
+ } else if result != MMSYSERR_NOERROR {
+ return Err(PortInfoError::CannotRetrievePortName)
+ }
+ let device_caps = unsafe { device_caps.assume_init() };
+ let pname = device_caps.szPname;
+ let output = from_wide_ptr(pname.as_ptr(), pname.len()).to_string_lossy().into_owned();
+ Ok(output)
+ }
+
+ fn from_port_number(port_number: UINT) -> Result<Self, PortInfoError> {
+ Ok(MidiOutputPort {
+ name: Self::name(port_number)?,
+ interface_id: Self::interface_id(port_number)?
+ })
+ }
+
+ fn current_port_number(&self) -> Option<UINT> {
+ for i in 0..Self::count() {
+ if let Ok(name) = Self::name(i) {
+ if name != self.name { continue; }
+ if let Ok(id) = Self::interface_id(i) {
+ if id == self.interface_id {
+ return Some(i);
+ }
+ }
+ }
+ }
+ None
+ }
+}
+
+impl MidiOutput {
+ pub fn new(_client_name: &str) -> Result<Self, InitError> {
+ Ok(MidiOutput)
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiOutputPort> {
+ let count = MidiOutputPort::count();
+ let mut result = Vec::with_capacity(count as usize);
+ for i in 0..count {
+ let port = match MidiOutputPort::from_port_number(i) {
+ Ok(p) => p,
+ Err(_) => continue
+ };
+ result.push(::common::MidiOutputPort {
+ imp: port
+ });
+ }
+ result
+ }
+
+ pub fn port_count(&self) -> usize {
+ MidiOutputPort::count() as usize
+ }
+
+ pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ Ok(port.name.clone())
+ }
+
+ pub fn connect(self, port: &MidiOutputPort, _port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ let port_number = match port.current_port_number() {
+ Some(p) => p,
+ None => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ };
+ let mut out_handle: MaybeUninit<HMIDIOUT> = MaybeUninit::uninit();
+ let result = unsafe { midiOutOpen(out_handle.as_mut_ptr(), port_number as UINT, 0, 0, CALLBACK_NULL) };
+ if result == MMSYSERR_ALLOCATED {
+ return Err(ConnectError::other("could not create Windows MM MIDI output port (MMSYSERR_ALLOCATED)", self));
+ } else if result != MMSYSERR_NOERROR {
+ return Err(ConnectError::other("could not create Windows MM MIDI output port", self));
+ }
+ Ok(MidiOutputConnection {
+ out_handle: unsafe { out_handle.assume_init() },
+ })
+ }
+}
+
+impl MidiOutputConnection {
+ pub fn close(self) -> MidiOutput {
+ // The actual closing is done by the implementation of Drop
+ MidiOutput // In this API this is a noop
+ }
+
+ pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
+ let nbytes = message.len();
+ if nbytes == 0 {
+ return Err(SendError::InvalidData("message to be sent must not be empty"));
+ }
+
+ if message[0] == 0xF0 { // Sysex message
+ // Allocate buffer for sysex data and copy message
+ let mut buffer = message.to_vec();
+
+ // Create and prepare MIDIHDR structure.
+ let mut sysex = MIDIHDR {
+ lpData: buffer.as_mut_ptr() as *mut i8,
+ dwBufferLength: nbytes as u32,
+ dwBytesRecorded: 0,
+ dwUser: 0,
+ dwFlags: 0,
+ lpNext: ptr::null_mut(),
+ reserved: 0,
+ dwOffset: 0,
+ dwReserved: unsafe { mem::zeroed() },
+ };
+
+ let result = unsafe { midiOutPrepareHeader(self.out_handle, &mut sysex, mem::size_of::<MIDIHDR>() as u32) };
+
+ if result != MMSYSERR_NOERROR {
+ return Err(SendError::Other("preparation for sending sysex message failed (OutPrepareHeader)"));
+ }
+
+ // Send the message.
+ loop {
+ let result = unsafe { midiOutLongMsg(self.out_handle, &mut sysex, mem::size_of::<MIDIHDR>() as u32) };
+ if result == MIDIERR_NOTREADY {
+ sleep(Duration::from_millis(1));
+ continue;
+ } else {
+ if result != MMSYSERR_NOERROR {
+ return Err(SendError::Other("sending sysex message failed"));
+ }
+ break;
+ }
+ }
+
+ loop {
+ let result = unsafe { midiOutUnprepareHeader(self.out_handle, &mut sysex, mem::size_of::<MIDIHDR>() as u32) };
+ if result == MIDIERR_STILLPLAYING {
+ sleep(Duration::from_millis(1));
+ continue;
+ } else { break; }
+ }
+ } else { // Channel or system message.
+ // Make sure the message size isn't too big.
+ if nbytes > 3 {
+ return Err(SendError::InvalidData("non-sysex message must not be longer than 3 bytes"));
+ }
+
+ // Pack MIDI bytes into double word.
+ let packet: DWORD = 0;
+ let ptr = &packet as *const u32 as *mut u8;
+ for i in 0..nbytes {
+ unsafe { *ptr.offset(i as isize) = message[i] };
+ }
+
+ // Send the message immediately.
+ loop {
+ let result = unsafe { midiOutShortMsg(self.out_handle, packet) };
+ if result == MIDIERR_NOTREADY {
+ sleep(Duration::from_millis(1));
+ continue;
+ } else {
+ if result != MMSYSERR_NOERROR {
+ return Err(SendError::Other("sending non-sysex message failed"));
+ }
+ break;
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl Drop for MidiOutputConnection {
+ fn drop(&mut self) {
+ unsafe {
+ midiOutReset(self.out_handle);
+ midiOutClose(self.out_handle);
+ }
+ }
+}
diff --git a/third_party/rust/midir/src/backend/winrt/mod.rs b/third_party/rust/midir/src/backend/winrt/mod.rs
new file mode 100644
index 0000000000..a8204ec742
--- /dev/null
+++ b/third_party/rust/midir/src/backend/winrt/mod.rs
@@ -0,0 +1,299 @@
+extern crate winrt;
+
+use std::sync::{Arc, Mutex};
+
+use ::errors::*;
+use ::Ignore;
+
+use self::winrt::{AbiTransferable, HString, TryInto};
+
+winrt::import!(
+ dependencies
+ os
+ types
+ windows::foundation::*
+ windows::devices::midi::*
+ windows::devices::enumeration::DeviceInformation
+ windows::storage::streams::{Buffer, DataWriter}
+);
+
+use self::windows::foundation::*;
+use self::windows::devices::midi::*;
+use self::windows::devices::enumeration::DeviceInformation;
+use self::windows::storage::streams::{Buffer, DataWriter};
+
+#[derive(Clone, PartialEq)]
+pub struct MidiInputPort {
+ id: HString
+}
+
+unsafe impl Send for MidiInputPort {} // because HString doesn't ...
+
+pub struct MidiInput {
+ selector: HString,
+ ignore_flags: Ignore
+}
+
+#[repr(C)]
+pub struct abi_IMemoryBufferByteAccess {
+ __base: [usize; 3],
+ get_buffer: extern "system" fn(
+ winrt::NonNullRawComPtr<IMemoryBufferByteAccess>,
+ value: *mut *mut u8,
+ capacity: *mut u32,
+ ) -> winrt::ErrorCode,
+}
+
+unsafe impl winrt::ComInterface for IMemoryBufferByteAccess {
+ type VTable = abi_IMemoryBufferByteAccess;
+ fn iid() -> winrt::Guid {
+ winrt::Guid::from_values(0x5b0d3235, 0x4dba, 0x4d44, [0x86, 0x5e, 0x8f, 0x1d, 0x0e, 0x4f, 0xd0, 0x4d])
+ }
+}
+
+unsafe impl AbiTransferable for IMemoryBufferByteAccess {
+ type Abi = winrt::RawComPtr<Self>;
+
+ fn get_abi(&self) -> Self::Abi {
+ self.ptr.get_abi()
+ }
+
+ fn set_abi(&mut self) -> *mut Self::Abi {
+ self.ptr.set_abi()
+ }
+}
+
+#[repr(transparent)]
+#[derive(Default, Clone)]
+pub struct IMemoryBufferByteAccess {
+ ptr: winrt::ComPtr<IMemoryBufferByteAccess>,
+}
+
+impl IMemoryBufferByteAccess {
+ pub unsafe fn get_buffer(&self) -> winrt::Result<&[u8]> {
+ match self.get_abi() {
+ None => panic!("The `this` pointer was null when calling method"),
+ Some(ptr) => {
+ let mut bufptr = std::ptr::null_mut();
+ let mut capacity: u32 = 0;
+ (ptr.vtable().get_buffer)(ptr, &mut bufptr, &mut capacity).ok()?;
+ if capacity == 0 {
+ bufptr = 1 as *mut u8; // null pointer is not allowed
+ }
+ Ok(std::slice::from_raw_parts(bufptr, capacity as usize))
+ }
+ }
+ }
+}
+
+
+unsafe impl Send for MidiInput {} // because HString doesn't ...
+
+impl MidiInput {
+ pub fn new(_client_name: &str) -> Result<Self, InitError> {
+ let device_selector = MidiInPort::get_device_selector().map_err(|_| InitError)?;
+ Ok(MidiInput { selector: device_selector, ignore_flags: Ignore::None })
+ }
+
+ pub fn ignore(&mut self, flags: Ignore) {
+ self.ignore_flags = flags;
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiInputPort> {
+ let device_collection = DeviceInformation::find_all_async_aqs_filter(&self.selector).unwrap().get().expect("find_all_async failed");
+ let count = device_collection.size().expect("get_size failed") as usize;
+ let mut result = Vec::with_capacity(count as usize);
+ for device_info in device_collection.into_iter() {
+ let device_id = device_info.id().expect("get_id failed");
+ result.push(::common::MidiInputPort {
+ imp: MidiInputPort { id: device_id }
+ });
+ }
+ result
+ }
+
+ pub fn port_count(&self) -> usize {
+ let device_collection = DeviceInformation::find_all_async_aqs_filter(&self.selector).unwrap().get().expect("find_all_async failed");
+ device_collection.size().expect("get_size failed") as usize
+ }
+
+ pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ let device_info_async = DeviceInformation::create_from_id_async(&port.id).map_err(|_| PortInfoError::InvalidPort)?;
+ let device_info = device_info_async.get().map_err(|_| PortInfoError::InvalidPort)?;
+ let device_name = device_info.name().map_err(|_| PortInfoError::CannotRetrievePortName)?;
+ Ok(device_name.to_string())
+ }
+
+ fn handle_input<T>(args: &MidiMessageReceivedEventArgs, handler_data: &mut HandlerData<T>) {
+ let ignore = handler_data.ignore_flags;
+ let data = &mut handler_data.user_data.as_mut().unwrap();
+ let timestamp;
+ let byte_access: IMemoryBufferByteAccess;
+ let message_bytes;
+ let message = args.message().expect("get_message failed");
+ timestamp = message.timestamp().expect("get_timestamp failed").duration as u64 / 10;
+ let buffer = message.raw_data().expect("get_raw_data failed");
+ let membuffer = Buffer::create_memory_buffer_over_ibuffer(&buffer).expect("create_memory_buffer_over_ibuffer failed");
+ byte_access = membuffer.create_reference().expect("create_reference failed").try_into().unwrap();
+ message_bytes = unsafe { byte_access.get_buffer().expect("get_buffer failed") }; // TODO: somehow make sure that the buffer is not invalidated while we're reading from it ...
+
+ // The first byte in the message is the status
+ let status = message_bytes[0];
+
+ if !(status == 0xF0 && ignore.contains(Ignore::Sysex) ||
+ status == 0xF1 && ignore.contains(Ignore::Time) ||
+ status == 0xF8 && ignore.contains(Ignore::Time) ||
+ status == 0xFE && ignore.contains(Ignore::ActiveSense))
+ {
+ (handler_data.callback)(timestamp, message_bytes, data);
+ }
+ }
+
+ pub fn connect<F, T: Send + 'static>(
+ self, port: &MidiInputPort, _port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+
+ let in_port = match MidiInPort::from_id_async(&port.id) {
+ Ok(port_async) => match port_async.get() {
+ Ok(port) => port,
+ _ => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ }
+ Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ };
+
+ let handler_data = Arc::new(Mutex::new(HandlerData {
+ ignore_flags: self.ignore_flags,
+ callback: Box::new(callback),
+ user_data: Some(data)
+ }));
+ let handler_data2 = handler_data.clone();
+
+ let handler = TypedEventHandler::new(move |_sender, args| {
+ MidiInput::handle_input(args, &mut *handler_data2.lock().unwrap());
+ Ok(())
+ });
+
+ let event_token = in_port.message_received(&handler).expect("add_message_received failed");
+
+ Ok(MidiInputConnection { port: RtMidiInPort(in_port), event_token: event_token, handler_data: handler_data })
+ }
+}
+
+struct RtMidiInPort(MidiInPort);
+unsafe impl Send for RtMidiInPort {}
+
+pub struct MidiInputConnection<T> {
+ port: RtMidiInPort,
+ event_token: EventRegistrationToken,
+ // TODO: get rid of Arc & Mutex?
+ // synchronization is required because the borrow checker does not
+ // know that the callback we're in here is never called concurrently
+ // (always in sequence)
+ handler_data: Arc<Mutex<HandlerData<T>>>
+}
+
+
+impl<T> MidiInputConnection<T> {
+ pub fn close(self) -> (MidiInput, T) {
+ let _ = self.port.0.remove_message_received(self.event_token);
+ let closable: IClosable = self.port.0.try_into().unwrap();
+ let _ = closable.close();
+ let device_selector = MidiInPort::get_device_selector().expect("get_device_selector failed"); // probably won't ever fail here, because it worked previously
+ let mut handler_data_locked = self.handler_data.lock().unwrap();
+ (MidiInput {
+ selector: device_selector,
+ ignore_flags: handler_data_locked.ignore_flags
+ }, handler_data_locked.user_data.take().unwrap())
+ }
+}
+
+/// This is all the data that is stored on the heap as long as a connection
+/// is opened and passed to the callback handler.
+///
+/// It is important that `user_data` is the last field to not influence
+/// offsets after monomorphization.
+struct HandlerData<T> {
+ ignore_flags: Ignore,
+ callback: Box<dyn FnMut(u64, &[u8], &mut T) + Send>,
+ user_data: Option<T>
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MidiOutputPort {
+ id: HString
+}
+
+unsafe impl Send for MidiOutputPort {} // because HString doesn't ...
+
+pub struct MidiOutput {
+ selector: HString // TODO: change to FastHString?
+}
+
+unsafe impl Send for MidiOutput {} // because HString doesn't ...
+
+impl MidiOutput {
+ pub fn new(_client_name: &str) -> Result<Self, InitError> {
+ let device_selector = MidiOutPort::get_device_selector().map_err(|_| InitError)?;
+ Ok(MidiOutput { selector: device_selector })
+ }
+
+ pub(crate) fn ports_internal(&self) -> Vec<::common::MidiOutputPort> {
+ let device_collection = DeviceInformation::find_all_async_aqs_filter(&self.selector).unwrap().get().expect("find_all_async failed");
+ let count = device_collection.size().expect("get_size failed") as usize;
+ let mut result = Vec::with_capacity(count as usize);
+ for device_info in device_collection.into_iter() {
+ let device_id = device_info.id().expect("get_id failed");
+ result.push(::common::MidiOutputPort {
+ imp: MidiOutputPort { id: device_id }
+ });
+ }
+ result
+ }
+
+ pub fn port_count(&self) -> usize {
+ let device_collection = DeviceInformation::find_all_async_aqs_filter(&self.selector).unwrap().get().expect("find_all_async failed");
+ device_collection.size().expect("get_size failed") as usize
+ }
+
+ pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ let device_info_async = DeviceInformation::create_from_id_async(&port.id).map_err(|_| PortInfoError::InvalidPort)?;
+ let device_info = device_info_async.get().map_err(|_| PortInfoError::InvalidPort)?;
+ let device_name = device_info.name().map_err(|_| PortInfoError::CannotRetrievePortName)?;
+ Ok(device_name.to_string())
+ }
+
+ pub fn connect(self, port: &MidiOutputPort, _port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ let out_port = match MidiOutPort::from_id_async(&port.id) {
+ Ok(port_async) => match port_async.get() {
+ Ok(port) => port,
+ _ => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ }
+ Err(_) => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self))
+ };
+ Ok(MidiOutputConnection { port: out_port })
+ }
+}
+
+pub struct MidiOutputConnection {
+ port: IMidiOutPort
+}
+
+unsafe impl Send for MidiOutputConnection {}
+
+impl MidiOutputConnection {
+ pub fn close(self) -> MidiOutput {
+ let closable: IClosable = self.port.try_into().unwrap();
+ let _ = closable.close();
+ let device_selector = MidiOutPort::get_device_selector().expect("get_device_selector failed"); // probably won't ever fail here, because it worked previously
+ MidiOutput { selector: device_selector }
+ }
+
+ pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
+ let data_writer = DataWriter::new().unwrap();
+ data_writer.write_bytes(message).map_err(|_| SendError::Other("write_bytes failed"))?;
+ let buffer = data_writer.detach_buffer().map_err(|_| SendError::Other("detach_buffer failed"))?;
+ self.port.send_buffer(&buffer).map_err(|_| SendError::Other("send_buffer failed"))?;
+ Ok(())
+ }
+}
diff --git a/third_party/rust/midir/src/common.rs b/third_party/rust/midir/src/common.rs
new file mode 100644
index 0000000000..aed696b60c
--- /dev/null
+++ b/third_party/rust/midir/src/common.rs
@@ -0,0 +1,315 @@
+#![deny(missing_docs)]
+
+use ::errors::*;
+use ::backend::{
+ MidiInputPort as MidiInputPortImpl,
+ MidiInput as MidiInputImpl,
+ MidiInputConnection as MidiInputConnectionImpl,
+ MidiOutputPort as MidiOutputPortImpl,
+ MidiOutput as MidiOutputImpl,
+ MidiOutputConnection as MidiOutputConnectionImpl
+};
+use ::Ignore;
+
+/// Trait that abstracts over input and output ports.
+pub trait MidiIO {
+ /// Type of an input or output port structure.
+ type Port: Clone;
+
+ /// Get a collection of all MIDI input or output ports.
+ /// The resulting vector contains one object per port, which you can use to
+ /// query metadata about the port or connect to it.
+ fn ports(&self) -> Vec<Self::Port>;
+
+ /// Get the number of available MIDI input or output ports.
+ fn port_count(&self) -> usize;
+
+ /// Get the name of a specified MIDI input or output port.
+ ///
+ /// An error will be returned when the port is no longer valid
+ /// (e.g. the respective device has been disconnected).
+ fn port_name(&self, port: &Self::Port) -> Result<String, PortInfoError>;
+}
+
+/// An object representing a single input port.
+/// How the port is identified internally is backend-dependent.
+/// If the backend allows it, port objects remain valid when
+/// other ports in the system change (i.e. it is not just an index).
+///
+/// Use the `ports` method of a `MidiInput` instance to obtain
+/// available ports.
+#[derive(Clone, PartialEq)]
+pub struct MidiInputPort {
+ pub(crate) imp: MidiInputPortImpl
+}
+
+/// A collection of input ports.
+pub type MidiInputPorts = Vec<MidiInputPort>;
+
+/// An instance of `MidiInput` is required for anything related to MIDI input.
+/// Create one with `MidiInput::new`.
+pub struct MidiInput {
+ //ignore_flags: Ignore
+ imp: MidiInputImpl
+}
+
+impl MidiInput {
+ /// Creates a new `MidiInput` object that is required for any MIDI input functionality.
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ MidiInputImpl::new(client_name).map(|imp| MidiInput { imp: imp })
+ }
+
+ /// Set flags to decide what kind of messages should be ignored (i.e., filtered out)
+ /// by this `MidiInput`. By default, no messages are ignored.
+ pub fn ignore(&mut self, flags: Ignore) {
+ self.imp.ignore(flags);
+ }
+
+ /// Get a collection of all MIDI input ports that *midir* can connect to.
+ /// The resulting vector contains one object per port, which you can use to
+ /// query metadata about the port or connect to it in order to receive
+ /// MIDI messages.
+ pub fn ports(&self) -> MidiInputPorts {
+ self.imp.ports_internal()
+ }
+
+ /// Get the number of available MIDI input ports that *midir* can connect to.
+ pub fn port_count(&self) -> usize {
+ self.imp.port_count()
+ }
+
+ /// Get the name of a specified MIDI input port.
+ ///
+ /// An error will be returned when the port is no longer valid
+ /// (e.g. the respective device has been disconnected).
+ pub fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ self.imp.port_name(&port.imp)
+ }
+
+ /// Connect to a specified MIDI input port in order to receive messages.
+ /// For each incoming MIDI message, the provided `callback` function will
+ /// be called. The first parameter of the callback function is a timestamp
+ /// (in microseconds) designating the time since some unspecified point in
+ /// the past (which will not change during the lifetime of a
+ /// `MidiInputConnection`). The second parameter contains the actual bytes
+ /// of the MIDI message.
+ ///
+ /// Additional data that should be passed whenever the callback is
+ /// invoked can be specified by `data`. Use the empty tuple `()` if
+ /// you do not want to pass any additional data.
+ ///
+ /// The connection will be kept open as long as the returned
+ /// `MidiInputConnection` is kept alive.
+ ///
+ /// The `port_name` is an additional name that will be assigned to the
+ /// connection. It is only used by some backends.
+ ///
+ /// An error will be returned when the port is no longer valid
+ /// (e.g. the respective device has been disconnected).
+ pub fn connect<F, T: Send>(
+ self, port: &MidiInputPort, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<MidiInput>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+ match self.imp.connect(&port.imp, port_name, callback, data) {
+ Ok(imp) => Ok(MidiInputConnection { imp: imp }),
+ Err(imp) => {
+ let kind = imp.kind();
+ Err(ConnectError::new(kind, MidiInput { imp: imp.into_inner() }))
+ }
+ }
+ }
+}
+
+impl MidiIO for MidiInput {
+ type Port = MidiInputPort;
+
+ fn ports(&self) -> MidiInputPorts {
+ self.imp.ports_internal()
+ }
+
+ fn port_count(&self) -> usize {
+ self.imp.port_count()
+ }
+
+ fn port_name(&self, port: &MidiInputPort) -> Result<String, PortInfoError> {
+ self.imp.port_name(&port.imp)
+ }
+}
+
+#[cfg(unix)]
+impl<T: Send> ::os::unix::VirtualInput<T> for MidiInput {
+ fn create_virtual<F>(
+ self, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<Self>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static {
+ match self.imp.create_virtual(port_name, callback, data) {
+ Ok(imp) => Ok(MidiInputConnection { imp: imp }),
+ Err(imp) => {
+ let kind = imp.kind();
+ Err(ConnectError::new(kind, MidiInput { imp: imp.into_inner() }))
+ }
+ }
+ }
+}
+
+/// Represents an open connection to a MIDI input port.
+pub struct MidiInputConnection<T: 'static> {
+ imp: MidiInputConnectionImpl<T>
+}
+
+impl<T> MidiInputConnection<T> {
+ /// Closes the connection. The returned values allow you to
+ /// inspect the additional data passed to the callback (the `data`
+ /// parameter of `connect`), or to reuse the `MidiInput` object,
+ /// but they can be safely ignored.
+ pub fn close(self) -> (MidiInput, T) {
+ let (imp, data) = self.imp.close();
+ (MidiInput { imp: imp }, data)
+ }
+}
+
+/// An object representing a single output port.
+/// How the port is identified internally is backend-dependent.
+/// If the backend allows it, port objects remain valid when
+/// other ports in the system change (i.e. it is not just an index).
+///
+/// Use the `ports` method of a `MidiOutput` instance to obtain
+/// available ports.
+#[derive(Clone, PartialEq)]
+pub struct MidiOutputPort {
+ pub(crate) imp: MidiOutputPortImpl
+}
+
+/// A collection of output ports.
+pub type MidiOutputPorts = Vec<MidiOutputPort>;
+
+/// An instance of `MidiOutput` is required for anything related to MIDI output.
+/// Create one with `MidiOutput::new`.
+pub struct MidiOutput {
+ imp: MidiOutputImpl
+}
+
+impl MidiOutput {
+ /// Creates a new `MidiOutput` object that is required for any MIDI output functionality.
+ pub fn new(client_name: &str) -> Result<Self, InitError> {
+ MidiOutputImpl::new(client_name).map(|imp| MidiOutput { imp: imp })
+ }
+
+ /// Get a collection of all MIDI output ports that *midir* can connect to.
+ /// The resulting vector contains one object per port, which you can use to
+ /// query metadata about the port or connect to it in order to send
+ /// MIDI messages.
+ pub fn ports(&self) -> MidiOutputPorts {
+ self.imp.ports_internal()
+ }
+
+ /// Get the number of available MIDI output ports that *midir* can connect to.
+ pub fn port_count(&self) -> usize {
+ self.imp.port_count()
+ }
+
+ /// Get the name of a specified MIDI output port.
+ ///
+ /// An error will be returned when the port is no longer valid
+ /// (e.g. the respective device has been disconnected).
+ pub fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ self.imp.port_name(&port.imp)
+ }
+
+ /// Connect to a specified MIDI output port in order to send messages.
+ /// The connection will be kept open as long as the returned
+ /// `MidiOutputConnection` is kept alive.
+ ///
+ /// The `port_name` is an additional name that will be assigned to the
+ /// connection. It is only used by some backends.
+ ///
+ /// An error will be returned when the port is no longer valid
+ /// (e.g. the respective device has been disconnected).
+ pub fn connect(self, port: &MidiOutputPort, port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ match self.imp.connect(&port.imp, port_name) {
+ Ok(imp) => Ok(MidiOutputConnection { imp: imp }),
+ Err(imp) => {
+ let kind = imp.kind();
+ Err(ConnectError::new(kind, MidiOutput { imp: imp.into_inner() }))
+ }
+ }
+ }
+}
+
+impl MidiIO for MidiOutput {
+ type Port = MidiOutputPort;
+
+ fn ports(&self) -> MidiOutputPorts {
+ self.imp.ports_internal()
+ }
+
+ fn port_count(&self) -> usize {
+ self.imp.port_count()
+ }
+
+ fn port_name(&self, port: &MidiOutputPort) -> Result<String, PortInfoError> {
+ self.imp.port_name(&port.imp)
+ }
+}
+
+#[cfg(unix)]
+impl ::os::unix::VirtualOutput for MidiOutput {
+ fn create_virtual(self, port_name: &str) -> Result<MidiOutputConnection, ConnectError<MidiOutput>> {
+ match self.imp.create_virtual(port_name) {
+ Ok(imp) => Ok(MidiOutputConnection { imp: imp }),
+ Err(imp) => {
+ let kind = imp.kind();
+ Err(ConnectError::new(kind, MidiOutput { imp: imp.into_inner() }))
+ }
+ }
+ }
+}
+
+/// Represents an open connection to a MIDI output port.
+pub struct MidiOutputConnection {
+ imp: MidiOutputConnectionImpl
+}
+
+impl MidiOutputConnection {
+ /// Closes the connection. The returned value allows you to
+ /// reuse the `MidiOutput` object, but it can be safely ignored.
+ pub fn close(self) -> MidiOutput {
+ MidiOutput { imp: self.imp.close() }
+ }
+
+ /// Send a message to the port that this output connection is connected to.
+ /// The message must be a valid MIDI message (see https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message).
+ pub fn send(&mut self, message: &[u8]) -> Result<(), SendError> {
+ self.imp.send(message)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_trait_impls() {
+ // make sure that all the structs implement `Send`
+ fn is_send<T: Send>() {}
+ is_send::<MidiInput>();
+ is_send::<MidiOutput>();
+ #[cfg(not(target_arch = "wasm32"))] {
+ // The story around threading and `Send` on WASM is not clear yet
+ // Tracking issue: https://github.com/Boddlnagg/midir/issues/49
+ // Prev. discussion: https://github.com/Boddlnagg/midir/pull/47
+ is_send::<MidiInputPort>();
+ is_send::<MidiInputConnection<()>>();
+ is_send::<MidiOutputPort>();
+ is_send::<MidiOutputConnection>();
+ }
+
+ // make sure that Midi port structs implement `PartialEq`
+ fn is_partial_eq<T: PartialEq>() {}
+ is_partial_eq::<MidiInputPortImpl>();
+ is_partial_eq::<MidiOutputPortImpl>();
+
+ is_partial_eq::<MidiInputPort>();
+ is_partial_eq::<MidiOutputPort>();
+ }
+}
diff --git a/third_party/rust/midir/src/errors.rs b/third_party/rust/midir/src/errors.rs
new file mode 100644
index 0000000000..69331528c9
--- /dev/null
+++ b/third_party/rust/midir/src/errors.rs
@@ -0,0 +1,114 @@
+use std::error::Error;
+use std::fmt;
+
+const INVALID_PORT_MSG: &str = "invalid port";
+const PORT_OUT_OF_RANGE_MSG: &str = "provided port number was out of range";
+const CANNOT_RETRIEVE_PORT_NAME_MSG: &str = "unknown error when trying to retrieve the port name";
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// An error that can occur during initialization (i.e., while
+/// creating a `MidiInput` or `MidiOutput` object).
+pub struct InitError;
+
+impl Error for InitError {}
+
+impl fmt::Display for InitError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ "MIDI support could not be initialized".fmt(f)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// An error that can occur when retrieving information about
+/// available ports.
+pub enum PortInfoError {
+ PortNumberOutOfRange, // TODO: don't expose this
+ InvalidPort,
+ CannotRetrievePortName,
+}
+
+impl Error for PortInfoError {}
+
+impl fmt::Display for PortInfoError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ PortInfoError::PortNumberOutOfRange => PORT_OUT_OF_RANGE_MSG.fmt(f),
+ PortInfoError::InvalidPort => INVALID_PORT_MSG.fmt(f),
+ PortInfoError::CannotRetrievePortName => CANNOT_RETRIEVE_PORT_NAME_MSG.fmt(f),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// The kind of error for a `ConnectError`.
+pub enum ConnectErrorKind {
+ InvalidPort,
+ Other(&'static str)
+}
+
+impl ConnectErrorKind {}
+
+impl fmt::Display for ConnectErrorKind {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ConnectErrorKind::InvalidPort => INVALID_PORT_MSG.fmt(f),
+ ConnectErrorKind::Other(msg) => msg.fmt(f)
+ }
+ }
+}
+
+/// An error that can occur when trying to connect to a port.
+pub struct ConnectError<T> {
+ kind: ConnectErrorKind,
+ inner: T
+}
+
+impl<T> ConnectError<T> {
+ pub fn new(kind: ConnectErrorKind, inner: T) -> ConnectError<T> {
+ ConnectError { kind: kind, inner: inner }
+ }
+
+ /// Helper method to create ConnectErrorKind::Other.
+ pub fn other(msg: &'static str, inner: T) -> ConnectError<T> {
+ Self::new(ConnectErrorKind::Other(msg), inner)
+ }
+
+ pub fn kind(&self) -> ConnectErrorKind {
+ self.kind
+ }
+
+ pub fn into_inner(self) -> T {
+ self.inner
+ }
+}
+
+impl<T> fmt::Debug for ConnectError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ self.kind.fmt(f)
+ }
+}
+
+impl<T> fmt::Display for ConnectError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.kind.fmt(f)
+ }
+}
+
+impl<T> Error for ConnectError<T> {}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// An error that can occur when sending MIDI messages.
+pub enum SendError {
+ InvalidData(&'static str),
+ Other(&'static str)
+}
+
+impl Error for SendError {}
+
+impl fmt::Display for SendError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ SendError::InvalidData(msg) | SendError::Other(msg) => msg.fmt(f)
+ }
+ }
+}
diff --git a/third_party/rust/midir/src/lib.rs b/third_party/rust/midir/src/lib.rs
new file mode 100644
index 0000000000..99d6840c33
--- /dev/null
+++ b/third_party/rust/midir/src/lib.rs
@@ -0,0 +1,65 @@
+extern crate memalloc;
+
+#[cfg(feature = "jack")]
+#[macro_use] extern crate bitflags;
+
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// An enum that is used to specify what kind of MIDI messages should
+/// be ignored when receiving messages.
+pub enum Ignore {
+ None = 0x00,
+ Sysex = 0x01,
+ Time = 0x02,
+ SysexAndTime = 0x03,
+ ActiveSense = 0x04,
+ SysexAndActiveSense = 0x05,
+ TimeAndActiveSense = 0x06,
+ All = 0x07
+}
+
+impl std::ops::BitOr for Ignore {
+ type Output = Ignore;
+ #[inline(always)]
+ fn bitor(self, rhs: Self) -> Self::Output {
+ // this is safe because all combinations also exist as variants
+ unsafe { std::mem::transmute(self as u8 | rhs as u8) }
+ }
+}
+
+impl Ignore {
+ #[inline(always)]
+ pub fn contains(self, other: Ignore) -> bool {
+ self as u8 & other as u8 != 0
+ }
+}
+
+/// A MIDI structure used internally by some backends to store incoming
+/// messages. Each message represents one and only one MIDI message.
+/// The timestamp is represented as the elapsed microseconds since
+/// a point in time that is arbitrary, but does not change for the
+/// lifetime of a given MidiInputConnection.
+#[derive(Debug, Clone)]
+struct MidiMessage {
+ bytes: Vec<u8>,
+ timestamp: u64
+}
+
+impl MidiMessage {
+ fn new() -> MidiMessage {
+ MidiMessage {
+ bytes: vec![],
+ timestamp: 0
+ }
+ }
+}
+
+pub mod os; // include platform-specific behaviour
+
+mod errors;
+pub use errors::*;
+
+mod common;
+pub use common::*;
+
+mod backend; \ No newline at end of file
diff --git a/third_party/rust/midir/src/os/mod.rs b/third_party/rust/midir/src/os/mod.rs
new file mode 100644
index 0000000000..70ff75cd9d
--- /dev/null
+++ b/third_party/rust/midir/src/os/mod.rs
@@ -0,0 +1 @@
+#[cfg(unix)] pub mod unix; \ No newline at end of file
diff --git a/third_party/rust/midir/src/os/unix.rs b/third_party/rust/midir/src/os/unix.rs
new file mode 100644
index 0000000000..88dcfee1d9
--- /dev/null
+++ b/third_party/rust/midir/src/os/unix.rs
@@ -0,0 +1,27 @@
+use ::ConnectError;
+use ::{MidiInputConnection, MidiOutputConnection};
+
+// TODO: maybe move to module `virtual` instead of `os::unix`?
+
+/// Trait that is implemented by `MidiInput` on platforms that
+/// support virtual ports (currently every platform but Windows).
+pub trait VirtualInput<T: Send> where Self: Sized {
+ /// Creates a virtual input port. Once it has been created,
+ /// other applications can connect to this port and send MIDI
+ /// messages which will be received by this port.
+ fn create_virtual<F>(
+ self, port_name: &str, callback: F, data: T
+ ) -> Result<MidiInputConnection<T>, ConnectError<Self>>
+ where F: FnMut(u64, &[u8], &mut T) + Send + 'static;
+}
+
+/// Trait that is implemented by `MidiOutput` on platforms that
+/// support virtual ports (currently every platform but Windows).
+pub trait VirtualOutput where Self: Sized {
+ /// Creates a virtual output port. Once it has been created,
+ /// other applications can connect to this port and will
+ /// receive MIDI messages that are sent to this port.
+ fn create_virtual(
+ self, port_name: &str
+ ) -> Result<MidiOutputConnection, ConnectError<Self>>;
+} \ No newline at end of file
diff --git a/third_party/rust/midir/tests/virtual.rs b/third_party/rust/midir/tests/virtual.rs
new file mode 100644
index 0000000000..5ac5754038
--- /dev/null
+++ b/third_party/rust/midir/tests/virtual.rs
@@ -0,0 +1,72 @@
+//! This file contains automated tests, but they require virtual ports and therefore can't work on Windows or Web MIDI ...
+#![cfg(not(any(windows, target_arch = "wasm32")))]
+extern crate midir;
+
+use std::thread::sleep;
+use std::time::Duration;
+
+use midir::{MidiInput, MidiOutput, Ignore, MidiOutputPort};
+use midir::os::unix::{VirtualInput, VirtualOutput};
+
+#[test]
+fn end_to_end() {
+ let mut midi_in = MidiInput::new("My Test Input").unwrap();
+ midi_in.ignore(Ignore::None);
+ let midi_out = MidiOutput::new("My Test Output").unwrap();
+
+ let previous_count = midi_out.port_count();
+
+ println!("Creating virtual input port ...");
+ let conn_in = midi_in.create_virtual("midir-test", |stamp, message, _| {
+ println!("{}: {:?} (len = {})", stamp, message, message.len());
+ }, ()).unwrap();
+
+ assert_eq!(midi_out.port_count(), previous_count + 1);
+
+ let new_port: MidiOutputPort = midi_out.ports().into_iter().rev().next().unwrap();
+
+ println!("Connecting to port '{}' ...", midi_out.port_name(&new_port).unwrap());
+ let mut conn_out = midi_out.connect(&new_port, "midir-test").unwrap();
+ println!("Starting to send messages ...");
+ conn_out.send(&[144, 60, 1]).unwrap();
+ sleep(Duration::from_millis(200));
+ conn_out.send(&[144, 60, 0]).unwrap();
+ sleep(Duration::from_millis(50));
+ conn_out.send(&[144, 60, 1]).unwrap();
+ sleep(Duration::from_millis(200));
+ conn_out.send(&[144, 60, 0]).unwrap();
+ sleep(Duration::from_millis(50));
+ println!("Closing output ...");
+ let midi_out = conn_out.close();
+ println!("Closing virtual input ...");
+ let midi_in = conn_in.close().0;
+ assert_eq!(midi_out.port_count(), previous_count);
+
+ let previous_count = midi_in.port_count();
+
+ // reuse midi_in and midi_out, but swap roles
+ println!("\nCreating virtual output port ...");
+ let mut conn_out = midi_out.create_virtual("midir-test").unwrap();
+ assert_eq!(midi_in.port_count(), previous_count + 1);
+
+ let new_port = midi_in.ports().into_iter().rev().next().unwrap();
+
+ println!("Connecting to port '{}' ...", midi_in.port_name(&new_port).unwrap());
+ let conn_in = midi_in.connect(&new_port, "midir-test", |stamp, message, _| {
+ println!("{}: {:?} (len = {})", stamp, message, message.len());
+ }, ()).unwrap();
+ println!("Starting to send messages ...");
+ conn_out.send(&[144, 60, 1]).unwrap();
+ sleep(Duration::from_millis(200));
+ conn_out.send(&[144, 60, 0]).unwrap();
+ sleep(Duration::from_millis(50));
+ conn_out.send(&[144, 60, 1]).unwrap();
+ sleep(Duration::from_millis(200));
+ conn_out.send(&[144, 60, 0]).unwrap();
+ sleep(Duration::from_millis(50));
+ println!("Closing input ...");
+ let midi_in = conn_in.close().0;
+ println!("Closing virtual output ...");
+ conn_out.close();
+ assert_eq!(midi_in.port_count(), previous_count);
+}