From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- third_party/rust/midir/.cargo-checksum.json | 1 + third_party/rust/midir/CHANGELOG.md | 50 ++ third_party/rust/midir/Cargo.toml | 97 +++ third_party/rust/midir/LICENSE | 21 + third_party/rust/midir/README.md | 23 + .../rust/midir/azure-pipelines-template.yml | 106 +++ third_party/rust/midir/azure-pipelines.yml | 26 + third_party/rust/midir/examples/test_forward.rs | 65 ++ third_party/rust/midir/examples/test_list_ports.rs | 46 ++ third_party/rust/midir/examples/test_play.rs | 74 +++ third_party/rust/midir/examples/test_read_input.rs | 58 ++ third_party/rust/midir/examples/test_reuse.rs | 82 +++ third_party/rust/midir/examples/test_sysex.rs | 79 +++ third_party/rust/midir/src/backend/alsa/mod.rs | 733 +++++++++++++++++++++ third_party/rust/midir/src/backend/coremidi/mod.rs | 351 ++++++++++ third_party/rust/midir/src/backend/jack/mod.rs | 364 ++++++++++ .../rust/midir/src/backend/jack/wrappers.rs | 250 +++++++ third_party/rust/midir/src/backend/mod.rs | 22 + third_party/rust/midir/src/backend/webmidi/mod.rs | 250 +++++++ .../rust/midir/src/backend/winmm/handler.rs | 85 +++ third_party/rust/midir/src/backend/winmm/mod.rs | 536 +++++++++++++++ third_party/rust/midir/src/backend/winrt/mod.rs | 299 +++++++++ third_party/rust/midir/src/common.rs | 315 +++++++++ third_party/rust/midir/src/errors.rs | 114 ++++ third_party/rust/midir/src/lib.rs | 65 ++ third_party/rust/midir/src/os/mod.rs | 1 + third_party/rust/midir/src/os/unix.rs | 27 + third_party/rust/midir/tests/virtual.rs | 72 ++ 28 files changed, 4212 insertions(+) create mode 100644 third_party/rust/midir/.cargo-checksum.json create mode 100644 third_party/rust/midir/CHANGELOG.md create mode 100644 third_party/rust/midir/Cargo.toml create mode 100644 third_party/rust/midir/LICENSE create mode 100644 third_party/rust/midir/README.md create mode 100644 third_party/rust/midir/azure-pipelines-template.yml create mode 100644 third_party/rust/midir/azure-pipelines.yml create mode 100644 third_party/rust/midir/examples/test_forward.rs create mode 100644 third_party/rust/midir/examples/test_list_ports.rs create mode 100644 third_party/rust/midir/examples/test_play.rs create mode 100644 third_party/rust/midir/examples/test_read_input.rs create mode 100644 third_party/rust/midir/examples/test_reuse.rs create mode 100644 third_party/rust/midir/examples/test_sysex.rs create mode 100755 third_party/rust/midir/src/backend/alsa/mod.rs create mode 100644 third_party/rust/midir/src/backend/coremidi/mod.rs create mode 100644 third_party/rust/midir/src/backend/jack/mod.rs create mode 100644 third_party/rust/midir/src/backend/jack/wrappers.rs create mode 100644 third_party/rust/midir/src/backend/mod.rs create mode 100644 third_party/rust/midir/src/backend/webmidi/mod.rs create mode 100644 third_party/rust/midir/src/backend/winmm/handler.rs create mode 100644 third_party/rust/midir/src/backend/winmm/mod.rs create mode 100644 third_party/rust/midir/src/backend/winrt/mod.rs create mode 100644 third_party/rust/midir/src/common.rs create mode 100644 third_party/rust/midir/src/errors.rs create mode 100644 third_party/rust/midir/src/lib.rs create mode 100644 third_party/rust/midir/src/os/mod.rs create mode 100644 third_party/rust/midir/src/os/unix.rs create mode 100644 third_party/rust/midir/tests/virtual.rs (limited to 'third_party/rust/midir') 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! + +* With the exception of message queues, but these can be implemented on top of callbacks using e.g. Rust's channels. + +**midir** currently supports the following platforms/backends: +- [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> { + 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(midi_io: &T, descr: &str) -> Result> { + 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::()?) + .ok_or("Invalid port number")?; + Ok(port.clone()) +} + +#[cfg(target_arch = "wasm32")] +fn run() -> Result<(), Box> { + 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> { + 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 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> { + 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::()?) + .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> { + 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::()?) + .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> { + 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::()?) + .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::()?) + .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> { + 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> { 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(s: &Seq, capability: PortCap, f: F) -> Vec 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 { + 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, +} + +#[derive(Clone, PartialEq)] +pub struct MidiInputPort { + addr: Addr +} + +pub struct MidiInputConnection { + subscription: Option, + thread: Option, T)>>, + vport: i32, // TODO: probably port numbers are only u8, therefore could use Option + trigger_send_fd: i32, +} + +struct HandlerData { + ignore_flags: Ignore, + seq: Seq, + trigger_rcv_fd: i32, + callback: Box, + queue_id: i32, // an input queue is needed to get timestamped events +} + +impl MidiInput { + pub fn new(client_name: &str) -> Result { + 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 { + 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 { + 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( + mut self, port: &MidiInputPort, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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( + mut self, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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 MidiInputConnection { + 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) { + // Request the thread to stop. + let _res = unsafe { self::libc::write(self.trigger_send_fd, &false as *const bool as *const _, mem::size_of::() 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 Drop for MidiInputConnection { + 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, // 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, + vport: i32, + coder: helpers::EventEncoder, + subscription: Option +} + +impl MidiOutput { + pub fn new(client_name: &str) -> Result { + 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 { + helpers::get_port_name(self.seq.as_ref().unwrap(), port.addr) + } + + pub fn connect(mut self, port: &MidiOutputPort, port_name: &str) -> Result> { + 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> { + 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(mut data: HandlerData, user_data: &mut T) -> HandlerData { + 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::() 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::().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 +} + +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 { + 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 { + match port.source.display_name() { + Some(name) => Ok(name), + None => Err(PortInfoError::CannotRetrievePortName) + } + } + + fn handle_input(packets: &PacketList, handler_data: &mut HandlerData) { + 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( + self, port: &MidiInputPort, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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( + self, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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 { + 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>> +} + +impl MidiInputConnection { + 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 { + message: MidiMessage, + ignore_flags: Ignore, + continue_sysex: bool, + callback: Box, + user_data: Option +} + +pub struct MidiOutput { + client: Client +} + +#[derive(Clone)] +pub struct MidiOutputPort { + dest: Arc +} + +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 { + 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 { + match port.dest.display_name() { + Some(name) => Ok(name), + None => Err(PortInfoError::CannotRetrievePortName) + } + } + + pub fn connect(self, port: &MidiOutputPort, port_name: &str) -> Result> { + 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> { + 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), + 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 { + port: Option, + ignore_flags: Ignore, + callback: Box, + user_data: Option +} + +pub struct MidiInput { + ignore_flags: Ignore, + client: Option, +} + +#[derive(Clone, PartialEq)] +pub struct MidiInputPort { + name: CString +} + +pub struct MidiInputConnection { + handler_data: Box>, + client: Option +} + +impl MidiInput { + pub fn new(client_name: &str) -> Result { + 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 { + Ok(port.name.to_string_lossy().into()) + } + + fn activate_callback(&mut self, callback: F, data: T) + -> Box> + 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>(&handler_data) }; + + self.client.as_mut().unwrap().set_process_callback(handle_input::, data_ptr as *mut c_void); + self.client.as_mut().unwrap().activate(); + handler_data + } + + pub fn connect( + mut self, port: &MidiInputPort, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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( + mut self, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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 MidiInputConnection { + 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 Drop for MidiInputConnection { + fn drop(&mut self) { + if self.client.is_some() { + self.close_internal(); + } + } +} + +extern "C" fn handle_input(nframes: jack_nframes_t, arg: *mut c_void) -> i32 { + let data: &mut InputHandlerData = unsafe { &mut *(arg as *mut InputHandlerData) }; + + // 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, + buff_size: Ringbuffer, + buff_message: Ringbuffer, +} + +pub struct MidiOutput { + client: Option, +} + +#[derive(Clone, PartialEq)] +pub struct MidiOutputPort { + name: CString +} + +pub struct MidiOutputConnection { + handler_data: Box, + client: Option +} + +impl MidiOutput { + pub fn new(client_name: &str) -> Result { + 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 { + Ok(port.name.to_string_lossy().into()) + } + + fn activate_callback(&mut self) -> Box { + 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> { + 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> { + 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::()); + debug_assert!(read == mem::size_of::(), "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 { + 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 { + 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 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 = RefCell::new(Static::new()); +} + +struct Static { + pub access: Option, + pub request: Option, + pub ever_requested: bool, + + pub on_ok: Closure, + pub on_err: Closure, +} + +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 { + 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::().size() as usize).unwrap_or(0) + }) + } + + pub fn port_name(&self, port: &MidiInputPort) -> Result { + Ok(port.input.name().unwrap_or_else(|| port.input.id())) + } + + pub fn connect( + self, port: &MidiInputPort, _port_name: &str, mut callback: F, data: T + ) -> Result, ConnectError> + 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); + + input.set_onmidimessage(Some(closure.as_ref().unchecked_ref())); + + closure + }; + + Ok(MidiInputConnection { ignore_flags, input, user_data, closure }) + } +} + +pub struct MidiInputConnection { + ignore_flags: Ignore, + input: web_sys::MidiInput, + user_data: Arc>>, + #[allow(dead_code)] // Must be kept alive until we decide to unregister from input + closure: Closure, +} + +impl MidiInputConnection { + 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 { + 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::().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::().size() as usize).unwrap_or(0) + }) + } + + pub fn port_name(&self, port: &MidiOutputPort) -> Result { + Ok(port.output.name().unwrap_or_else(|| port.output.id())) + } + + pub fn connect(self, port: &MidiOutputPort, _port_name: &str) -> Result> { + 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(_: 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 = unsafe { &mut *(instance_ptr as *mut HandlerData) }; + + // 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::() 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 { + handler_data: Box>, +} + +impl MidiInputPort { + pub fn count() -> UINT { + unsafe { midiInGetNumDevs() } + } + + fn interface_id(port_number: UINT) -> Result, 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::::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 { + let mut device_caps: MaybeUninit = MaybeUninit::uninit(); + let result = unsafe { midiInGetDevCapsW(port_number as UINT_PTR, device_caps.as_mut_ptr(), mem::size_of::() 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 { + Ok(MidiInputPort { + name: Self::name(port_number)?, + interface_id: Self::interface_id(port_number)? + }) + } + + fn current_port_number(&self) -> Option { + 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); +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 { + message: MidiMessage, + sysex_buffer: SysexBuffer, + in_handle: Option, + ignore_flags: Ignore, + callback: Box, + user_data: Option +} + +impl MidiInput { + pub fn new(_client_name: &str) -> Result { + 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 { + Ok(port.name.clone()) + } + + pub fn connect( + self, port: &MidiInputPort, _port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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 = MaybeUninit::uninit(); + let handler_data_ptr: *mut HandlerData = &mut *handler_data; + let result = unsafe { midiInOpen(in_handle.as_mut_ptr(), + port_number as UINT, + handler::handle_input:: 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::()*/) } 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::() 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::() 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 MidiInputConnection { + 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::() as u32); + deallocate((*self.handler_data.sysex_buffer.0[i]).lpData as *mut u8, RT_SYSEX_BUFFER_SIZE/*, mem::align_of::()*/); + // 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 Drop for MidiInputConnection { + 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, 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::::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 { + let mut device_caps: MaybeUninit = MaybeUninit::uninit(); + let result = unsafe { midiOutGetDevCapsW(port_number as UINT_PTR, device_caps.as_mut_ptr(), mem::size_of::() 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 { + Ok(MidiOutputPort { + name: Self::name(port_number)?, + interface_id: Self::interface_id(port_number)? + }) + } + + fn current_port_number(&self) -> Option { + 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 { + 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 { + Ok(port.name.clone()) + } + + pub fn connect(self, port: &MidiOutputPort, _port_name: &str) -> Result> { + let port_number = match port.current_port_number() { + Some(p) => p, + None => return Err(ConnectError::new(ConnectErrorKind::InvalidPort, self)) + }; + let mut out_handle: MaybeUninit = 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::() 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::() 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::() 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, + 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; + + 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, +} + +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 { + 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 { + 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(args: &MidiMessageReceivedEventArgs, handler_data: &mut HandlerData) { + 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( + self, port: &MidiInputPort, _port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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 { + 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>> +} + + +impl MidiInputConnection { + 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 { + ignore_flags: Ignore, + callback: Box, + user_data: Option +} + +#[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 { + 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 { + 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> { + 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; + + /// 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; +} + +/// 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; + +/// 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 { + 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 { + 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( + self, port: &MidiInputPort, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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 { + self.imp.port_name(&port.imp) + } +} + +#[cfg(unix)] +impl ::os::unix::VirtualInput for MidiInput { + fn create_virtual( + self, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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 { + imp: MidiInputConnectionImpl +} + +impl MidiInputConnection { + /// 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; + +/// 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 { + 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 { + 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> { + 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 { + self.imp.port_name(&port.imp) + } +} + +#[cfg(unix)] +impl ::os::unix::VirtualOutput for MidiOutput { + fn create_virtual(self, port_name: &str) -> Result> { + 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() {} + is_send::(); + is_send::(); + #[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::(); + is_send::>(); + is_send::(); + is_send::(); + } + + // make sure that Midi port structs implement `PartialEq` + fn is_partial_eq() {} + is_partial_eq::(); + is_partial_eq::(); + + is_partial_eq::(); + is_partial_eq::(); + } +} 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 { + kind: ConnectErrorKind, + inner: T +} + +impl ConnectError { + pub fn new(kind: ConnectErrorKind, inner: T) -> ConnectError { + ConnectError { kind: kind, inner: inner } + } + + /// Helper method to create ConnectErrorKind::Other. + pub fn other(msg: &'static str, inner: T) -> ConnectError { + Self::new(ConnectErrorKind::Other(msg), inner) + } + + pub fn kind(&self) -> ConnectErrorKind { + self.kind + } + + pub fn into_inner(self) -> T { + self.inner + } +} + +impl fmt::Debug for ConnectError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.kind.fmt(f) + } +} + +impl fmt::Display for ConnectError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.kind.fmt(f) + } +} + +impl Error for ConnectError {} + +#[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, + 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 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( + self, port_name: &str, callback: F, data: T + ) -> Result, ConnectError> + 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>; +} \ 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); +} -- cgit v1.2.3