Boddlnagg / midir

Cross-platform realtime MIDI processing in Rust.
MIT License
629 stars 78 forks source link

Please, help with borow checker #80

Closed Levitanus closed 3 years ago

Levitanus commented 3 years ago

I'm sorry for bothering here: I'm newbie in rust, but for the day I cannot figure out how to handle midi_input in my code:

I try to use it inside iced application cycle, but, any attempts to get data from an opened connection ends with no possibility to get reference to the data, or pass self to the callback (which I can understand).

extern crate iced;
extern crate midir;

use std::error::Error;
use std::io::{stdin, stdout, Write};
use std::option::Option::Some;

use iced::{executor, Application, Column, Command, Element, Settings, Text};
use midir::{Ignore, MidiInput, MidiInputPort};

pub fn main() -> iced::Result {
    BigNote::run(Settings::default())
}

struct BigNote {
    note: i32,
    notes: Vec<u8>,
    need_init: bool,

    midi_input: MidiInput,
}

#[derive(Debug, Clone, Copy)]
pub enum Message {
    MidiFired,
    Init,
}

impl Application for BigNote {
    type Executor = executor::Default;
    type Message = Message;
    type Flags = ();

    fn new(_flags: ()) -> (BigNote, Command<self::Message>) {
        (
            BigNote {
                note: -1,
                need_init: true,
                notes: Vec::new(),
                midi_input: MidiInput::new(&String::from("my_big_note")).unwrap(),
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("MyBigNote by Levitanus")
    }

    fn view(&mut self) -> Element<Message> {
        Column::new()
            .push(Text::new(self.note.to_string()).size(50))
            .into()
    }

    fn update(&mut self, message: Message) -> Command<Message> {
        if self.need_init == true {
            self.need_init = false;
            let port = &self.midi_input.ports()[1];
            let conn_in = self
                .midi_input
                .connect(
                    port,
                    "midir-test",
                    |stamp, message, log: &mut Vec<i32>| {
                        // The last of the three callback parameters is the object that we pass in as last parameter of `connect`.
                        println!(
                            "{}: {:?} (len = {}) Data len = {}",
                            stamp,
                            message,
                            message.len(),
                            log.len()
                        );
                    },
                    Vec::new(),
                )
                .unwrap();
        }
        Command::none()
    }
}

  --> src/main.rs:75:27
   |
75 |               let conn_in = self
   |  ___________________________^
76 | |                 .midi_input
   | |___________________________^ move occurs because `self.midi_input` has type `MidiInput`, which does not implement the `Copy` trait```
Levitanus commented 3 years ago

P.S. If I do something with rtmidi (at least in its Python binding) — I would not use callback at all, but take messages from the queue one by one, but here I stunned...

Boddlnagg commented 3 years ago

I don't know how iced works, but it looks wrong that you try to open the MIDI connection in every update. You probably have to call connect already in new and not just store a MidiInput in BigNote, but rather a MidiInputConnection.

You might also need to create a channel (https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html) to move the messages from the closure that you pass into connect into the update function (you can move the Sender into the closure).

Levitanus commented 3 years ago

Thanks a lot! I'll try to investigate. Afraid, I cannot use connect in the new(), as later when gui is opened — the port will be chosen. But right now I try at least build it somehow).

Boddlnagg commented 3 years ago

Afraid, I cannot use connect in the new(), as later when gui is opened — the port will be chosen.

Then you have to store an Option<MidiInputConnection> in your state, and call connect only once when the port is chosen. Then store the resulting connection.

Levitanus commented 3 years ago

I've managed finally, thanks! https://github.com/hecrj/iced/issues/405

Boddlnagg commented 3 years ago

Great to hear!