kovaxis/midly

Cannot use Arena together with the midir crate

Closed this issue · 2 comments

I'm using midir to connect to a midi device and receive and store midi events.
Here's the problem: https://docs.rs/midir/0.7.0/midir/struct.MidiInput.html#method.connect requires a closure that is Send. If a closure owns an Arena, it is not Send.

Here is my project, so you may judge whether it is a use case you want to support:

Dependencies:

[dependencies]
midir = "0.7.0"
midly = "0.5.0"

main.rs:

#![feature(try_blocks)]
#![feature(with_options)]
use midir::{Ignore, MidiInput};
use midly::{live::LiveEvent, num::u28, Arena, Format, Fps, Header, Timing, TrackEvent};
use std::{
    error::Error,
    fs::File,
    sync::{Arc, Mutex},
    thread,
    time::{Duration, Instant},
};

const TICKS_PER_FRAME: u8 = 80;
const TICKS_PER_SECOND: u64 = 24 * (TICKS_PER_FRAME as u64);

fn midi_header() -> Header {
    Header::new(
        Format::SingleTrack,
        Timing::Timecode(Fps::Fps24, TICKS_PER_FRAME),
    )
}
fn main() -> Result<(), Box<dyn Error>> {
    let mut midi_in = MidiInput::new("midir reading input")?;
    midi_in.ignore(Ignore::None);

    let in_ports = midi_in.ports();
    let in_port = in_ports[6].clone();
    let in_port_name = midi_in.port_name(&in_port)?;

    let file = File::with_options()
        .append(true)
        .create(true)
        .open("out.midi");
    let file = Arc::new(Mutex::new(file));
    let last_change = Arc::new(Mutex::new(Instant::now()));

    let track = Arc::new(Mutex::new(Vec::<TrackEvent>::new()));
    let arena = Arena::new();

    let _conn_in = midi_in.connect(
        &in_port,
        "midir-read-input",
        move |us, mut message, _| {
            let result: Result<(), midly::Error> = try {
                println!("{}: {:?} (len = {})", us, message, message.len());

                let tick = (us * TICKS_PER_SECOND) / 1_000_000;

                let event = LiveEvent::parse(&message)?;

                let mut track = track.lock().unwrap();
                track.push(TrackEvent {
                    delta: u28::try_from(tick as u32).unwrap(),
                    kind: event.as_track_event(&arena),
                });

                *last_change.lock().unwrap() = Instant::now();
            };
            if let Err(e) = result {
                println!("ERROR HANDLING MIDI EVENT: {:?}", e);
            }
        },
        (),
    )?;

    println!(
        "Connection open, reading input from '{}' (press enter to exit) ...",
        in_port_name
    );

    std::thread::sleep(Duration::from_secs(9999999999999999));
    println!("Closing connection");
    Ok(())
}

Error:

error[E0277]: `*mut [u8]` cannot be sent between threads safely
  --> src/main.rs:40:28
   |
40 |     let _conn_in = midi_in.connect(
   |                            ^^^^^^^ `*mut [u8]` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `*mut [u8]`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<*mut [u8]>`
   = note: required because it appears within the type `alloc::raw_vec::RawVec<*mut [u8]>`
   = note: required because it appears within the type `std::vec::Vec<*mut [u8]>`
   = note: required because it appears within the type `std::cell::UnsafeCell<std::vec::Vec<*mut [u8]>>`
   = note: required because it appears within the type `midly::Arena`
   = note: required because it appears within the type `[closure@src/main.rs:43:9: 63:10 track:std::sync::Arc<std::sync::Mutex<std::vec::Vec<midly::TrackEvent<'_>>>>, arena:midly::Arena, last_change:std::sync::Arc<std::sync::Mutex<std::time::Instant>>]`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `transcribe`.

I could get around this issue by running my program in another thread (in my case I chose to go async so I use a single-threaded executor) which holds the Arena, and using a mpsc channel to send message from the closure provided to midir to said thread.
This requires of course that I do message.to_vec() before I send it in the channel. I don't really mind the allocation but it feels a bit ironic that this whole workaround which includes extra allocation was required due to midly's effort to avoid allocations.
However, it's not too bad, just requires some setting up and careful consideration with threads and channels (and in my case async executors).

First of all, thanks for bringing this up!

There's no intrinsic issue preventing Arena from being Send, I just forgot to implement it. Just fixed it and publishing 0.5.1 along with some other minor fixes. Make sure to run cargo update!