1use std::collections::HashMap; 2use std::sync::Arc; 3use std::time::Duration; 4use midir::MidiOutput; 5 6use tokio::sync::{ mpsc, Mutex }; 7use tokio::time::Instant; 8use serde::Deserialize; 9use std::thread::sleep; 10 11use crate::loggerbridge::Logger; 12pub struct MidiMessage { 13 pub message: Vec<u8>, 14 pub instant: Instant, 15 pub offset: u64, 16 pub requestedport: String, 17} 18 19pub struct AsyncInputTransmit { 20 pub inner: Mutex<mpsc::Sender<Vec<MidiMessage>>>, 21} 22 23pub fn init( 24 logger: Logger, 25 async_input_receiver: mpsc::Receiver<Vec<MidiMessage>>, 26 mut async_output_receiver: mpsc::Receiver<Vec<MidiMessage>>, 27 async_output_transmitter: mpsc::Sender<Vec<MidiMessage>> 28) { 29 tauri::async_runtime::spawn(async move { async_process_model(async_input_receiver, async_output_transmitter).await }); 30 let message_queue: Arc<Mutex<Vec<MidiMessage>>> = Arc::new(Mutex::new(Vec::new())); 31 /* ........................................................... 32 Listen For incoming messages and add to queue 33 ............................................................*/ 34 let message_queue_clone = Arc::clone(&message_queue); 35 tauri::async_runtime::spawn(async move { 36 loop { 37 if let Some(package) = async_output_receiver.recv().await { 38 let mut message_queue = message_queue_clone.lock().await; 39 let messages = package; 40 for message in messages { 41 (*message_queue).push(message); 42 } 43 } 44 } 45 }); 46 47 let message_queue_clone = Arc::clone(&message_queue); 48 tauri::async_runtime::spawn(async move { 49 /* ........................................................... 50 Open Midi Ports 51 ............................................................*/ 52 let midiout = MidiOutput::new("strudel").unwrap(); 53 let out_ports = midiout.ports(); 54 let mut port_names = Vec::new(); 55 //TODO: Send these print messages to the UI logger instead of the rust console so the user can see them 56 if out_ports.len() == 0 { 57 logger.log( 58 " No MIDI devices found. Connect a device or enable IAC Driver to enable midi.".to_string(), 59 "".to_string() 60 ); 61 // logger(window, " No MIDI devices found. Connect a device or enable IAC Driver.".to_string(), None); 62 return; 63 } 64 // give the frontend couple seconds to load on start, or the log messages will get lost 65 sleep(Duration::from_secs(3)); 66 logger.log(format!("Found {} midi devices!", out_ports.len()), "".to_string()); 67 68 // the user could reference any port at anytime during runtime, 69 // so let's go ahead and open them all (same behavior as web app) 70 let mut output_connections = HashMap::new(); 71 for i in 0..=out_ports.len().saturating_sub(1) { 72 let midiout = MidiOutput::new("strudel").unwrap(); 73 let ports = midiout.ports(); 74 let port = ports.get(i).unwrap(); 75 let port_name = midiout.port_name(port).unwrap(); 76 logger.log(port_name.clone(), "".to_string()); 77 let out_con = midiout.connect(port, &port_name).unwrap(); 78 port_names.insert(i, port_name.clone()); 79 output_connections.insert(port_name, out_con); 80 } 81 /* ........................................................... 82 Process queued messages 83 ............................................................*/ 84 85 loop { 86 let mut message_queue = message_queue_clone.lock().await; 87 88 //iterate over each message, play and remove messages when they are ready 89 message_queue.retain(|message| { 90 if message.instant.elapsed().as_millis() < message.offset.into() { 91 return true; 92 } 93 let mut out_con = output_connections.get_mut(&message.requestedport); 94 95 // WebMidi supports getting a connection by part of its name 96 // ex: 'bus 1' instead of 'IAC Driver bus 1' so let's emulate that behavior 97 if out_con.is_none() { 98 let key = port_names.iter().find(|port_name| { 99 return port_name.contains(&message.requestedport); 100 }); 101 if key.is_some() { 102 out_con = output_connections.get_mut(key.unwrap()); 103 } 104 } 105 106 if out_con.is_some() { 107 // process the message 108 if let Err(err) = (&mut out_con.unwrap()).send(&message.message) { 109 logger.log(format!("Midi message send error: {}", err), "error".to_string()); 110 } 111 } else { 112 logger.log(format!("failed to find midi device: {}", message.requestedport), "error".to_string()); 113 } 114 return false; 115 }); 116 117 sleep(Duration::from_millis(1)); 118 } 119 }); 120} 121 122pub async fn async_process_model( 123 mut input_reciever: mpsc::Receiver<Vec<MidiMessage>>, 124 output_transmitter: mpsc::Sender<Vec<MidiMessage>> 125) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 126 while let Some(input) = input_reciever.recv().await { 127 let output = input; 128 output_transmitter.send(output).await?; 129 } 130 Ok(()) 131} 132#[derive(Deserialize)] 133pub struct MessageFromJS { 134 message: Vec<u8>, 135 offset: u64, 136 requestedport: String, 137} 138// Called from JS 139#[tauri::command] 140pub async fn sendmidi( 141 messagesfromjs: Vec<MessageFromJS>, 142 state: tauri::State<'_, AsyncInputTransmit> 143) -> Result<(), String> { 144 let async_proc_input_tx = state.inner.lock().await; 145 let mut messages_to_process: Vec<MidiMessage> = Vec::new(); 146 147 for m in messagesfromjs { 148 let message_to_process = MidiMessage { 149 instant: Instant::now(), 150 message: m.message, 151 offset: m.offset, 152 requestedport: m.requestedport, 153 }; 154 messages_to_process.push(message_to_process); 155 } 156 157 async_proc_input_tx.send(messages_to_process).await.map_err(|e| e.to_string()) 158}