diff --git a/packages/desktopbridge/loggerbridge.mjs b/packages/desktopbridge/loggerbridge.mjs new file mode 100644 index 00000000..6ab16dc5 --- /dev/null +++ b/packages/desktopbridge/loggerbridge.mjs @@ -0,0 +1,11 @@ +import { listen } from '@tauri-apps/api/event'; +import { logger } from '../core/logger.mjs'; + +// listen for log events from the Tauri backend and log in the UI +await listen('log-event', (e) => { + if (e.payload == null) { + return; + } + const { message, message_type } = e.payload; + logger(message, message_type); +}); diff --git a/src-tauri/src/loggerbridge.rs b/src-tauri/src/loggerbridge.rs new file mode 100644 index 00000000..f7fa9acf --- /dev/null +++ b/src-tauri/src/loggerbridge.rs @@ -0,0 +1,20 @@ +use std::sync::Arc; +use tauri::Window; + +#[derive(Clone, serde::Serialize)] +pub struct LoggerPayload { + pub message: String, + pub message_type: String, +} + +#[derive(Clone)] +pub struct Logger { + pub window: Arc, +} + +impl Logger { + pub fn log(&self, message: String, message_type: String) { + println!("{}", message); + let _ = self.window.emit("log-event", LoggerPayload { message, message_type }); + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c91e37df..6162bd61 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,9 +3,21 @@ mod midibridge; mod oscbridge; +mod loggerbridge; +use std::sync::Arc; +use std::thread::sleep; +use std::time::Duration; + +use loggerbridge::Logger; +use tauri::Manager; use tokio::sync::mpsc; use tokio::sync::Mutex; - +// the payload type must implement `Serialize` and `Clone`. +#[derive(Clone, serde::Serialize)] +struct Payload { + message: String, + message_type: String, +} fn main() { let (async_input_transmitter_midi, async_input_receiver_midi) = mpsc::channel(1); let (async_output_transmitter_midi, async_output_receiver_midi) = mpsc::channel(1); @@ -20,9 +32,16 @@ fn main() { inner: Mutex::new(async_input_transmitter_osc), }) .invoke_handler(tauri::generate_handler![midibridge::sendmidi, oscbridge::sendosc]) - .setup(|_app| { - midibridge::init(async_input_receiver_midi, async_output_receiver_midi, async_output_transmitter_midi); - oscbridge::init(async_input_receiver_osc, async_output_receiver_osc, async_output_transmitter_osc); + .setup(|app| { + let window = Arc::new(app.get_window("main").unwrap()); + let logger = Logger { window }; + midibridge::init( + logger.clone(), + async_input_receiver_midi, + async_output_receiver_midi, + async_output_transmitter_midi + ); + oscbridge::init(logger, async_input_receiver_osc, async_output_receiver_osc, async_output_transmitter_osc); Ok(()) }) .run(tauri::generate_context!()) diff --git a/src-tauri/src/midibridge.rs b/src-tauri/src/midibridge.rs index c049e406..34936dcb 100644 --- a/src-tauri/src/midibridge.rs +++ b/src-tauri/src/midibridge.rs @@ -2,10 +2,13 @@ use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use midir::MidiOutput; + use tokio::sync::{ mpsc, Mutex }; use tokio::time::Instant; use serde::Deserialize; use std::thread::sleep; + +use crate::loggerbridge::Logger; pub struct MidiMessage { pub message: Vec, pub instant: Instant, @@ -18,6 +21,7 @@ pub struct AsyncInputTransmit { } pub fn init( + logger: Logger, async_input_receiver: mpsc::Receiver>, mut async_output_receiver: mpsc::Receiver>, async_output_transmitter: mpsc::Sender> @@ -50,10 +54,13 @@ pub fn init( let mut port_names = Vec::new(); //TODO: Send these print messages to the UI logger instead of the rust console so the user can see them if out_ports.len() == 0 { - println!(" No MIDI devices found. Connect a device or enable IAC Driver."); + logger.log(" No MIDI devices found. Connect a device or enable IAC Driver.".to_string(), "".to_string()); + // logger(window, " No MIDI devices found. Connect a device or enable IAC Driver.".to_string(), None); return; } - println!("Found {} midi devices!", out_ports.len()); + // give the frontend couple seconds to load on start, or the log messages will get lost + sleep(Duration::from_secs(3)); + logger.log(format!("Found {} midi devices!", out_ports.len()), "".to_string()); // the user could reference any port at anytime during runtime, // so let's go ahead and open them all (same behavior as web app) @@ -63,7 +70,7 @@ pub fn init( let ports = midiout.ports(); let port = ports.get(i).unwrap(); let port_name = midiout.port_name(port).unwrap(); - println!("{}", port_name); + logger.log(port_name.clone(), "".to_string()); let out_con = midiout.connect(port, &port_name).unwrap(); port_names.insert(i, port_name.clone()); output_connections.insert(port_name, out_con); @@ -96,10 +103,10 @@ pub fn init( if out_con.is_some() { // process the message if let Err(err) = (&mut out_con.unwrap()).send(&message.message) { - println!("Midi message send error: {}", err); + logger.log(format!("Midi message send error: {}", err), "warning".to_string()); } } else { - println!("failed to find midi device: {}", message.requestedport); + logger.log(format!("failed to find midi device: {}", message.requestedport), "warning".to_string()); } return false; }); diff --git a/src-tauri/src/oscbridge.rs b/src-tauri/src/oscbridge.rs index c8a1ee09..64d411b1 100644 --- a/src-tauri/src/oscbridge.rs +++ b/src-tauri/src/oscbridge.rs @@ -1,5 +1,6 @@ use rosc::{ encoder, OscTime }; use rosc::{ OscMessage, OscPacket, OscType, OscBundle }; + use std::net::UdpSocket; use std::time::Duration; @@ -7,6 +8,8 @@ use std::sync::Arc; use tokio::sync::{ mpsc, Mutex }; use serde::Deserialize; use std::thread::sleep; + +use crate::loggerbridge::Logger; pub struct OscMsg { pub msg_buf: Vec, pub timestamp: u64, @@ -22,6 +25,7 @@ const NANOS_PER_SECOND: f64 = 1.0e9; const SECONDS_PER_NANO: f64 = 1.0 / NANOS_PER_SECOND; pub fn init( + logger: Logger, async_input_receiver: mpsc::Receiver>, mut async_output_receiver: mpsc::Receiver>, async_output_transmitter: mpsc::Sender> @@ -64,7 +68,10 @@ pub fn init( message_queue.retain(|message| { let result = sock.send(&message.msg_buf); if result.is_err() { - println!("OSC Message failed to send, the server might no longer be available"); + logger.log( + format!("OSC Message failed to send, the server might no longer be available"), + "warning".to_string() + ); } return false; }); diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 6cb4eb7d..173bb455 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -37,14 +37,22 @@ const modules = [ import('@strudel.cycles/core'), import('@strudel.cycles/tonal'), import('@strudel.cycles/mini'), - isTauri() ? import('@strudel/desktopbridge/midibridge.mjs') : import('@strudel.cycles/midi'), import('@strudel.cycles/xen'), import('@strudel.cycles/webaudio'), - isTauri() ? import('@strudel/desktopbridge/oscbridge.mjs') : import('@strudel.cycles/osc'), + import('@strudel.cycles/serial'), import('@strudel.cycles/soundfonts'), import('@strudel.cycles/csound'), ]; +if (isTauri()) { + modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); +} else { + modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); +} const modulesLoading = evalScope( controls, // sadly, this cannot be exported from core direclty