use std::{ sync::mpsc::{self, Receiver, Sender}, thread::{self}, time::Duration, }; use anyhow::{anyhow, Result}; use midly::Smf; use notan::{draw::*, prelude::*}; use serialport::SerialPortType; #[derive(Clone, Copy, Debug)] pub enum Instruction { NoteOn(u8), NoteOff, Modulation { depth: u8, rate: u8 }, Frequency(u16), } pub struct SerialDevice { pub port: Box, } const CHANNEL_COUNT: u8 = 4; impl SerialDevice { pub fn new() -> Result { let ports = serialport::available_ports()?; let mut this: anyhow::Result = Err(anyhow!("No serial port found")); for port in ports { match port.port_type { SerialPortType::UsbPort(_) => { this = Ok(Self { port: serialport::new(port.port_name, 115200).open()?, }); break; } _ => {} }; } this } fn note_off(&mut self, ch: u8) -> Result<()> { assert!((0..CHANNEL_COUNT).contains(&ch)); let bytes: Vec = vec![0x80 + ch]; self.port.write(&bytes)?; Ok(()) } fn note_on(&mut self, ch: u8, note: u8) -> Result<()> { assert!((0..CHANNEL_COUNT).contains(&ch)); assert!((0..=127).contains(¬e)); let bytes: Vec = vec![0x90 + ch, note]; self.port.write(&bytes)?; Ok(()) } fn modulation(&mut self, ch: u8, depth: u8, rate: u8) -> Result<()> { assert!((0..CHANNEL_COUNT).contains(&ch)); assert!((0..=127).contains(&depth)); let bytes: Vec = vec![0xA0 + ch, depth, rate]; self.port.write(&bytes)?; Ok(()) } fn frequency_on(&mut self, ch: u8, freq: u16) -> Result<()> { assert!((0..CHANNEL_COUNT).contains(&ch)); let bytes: Vec = vec![0xB0 + ch, (freq & 0xff) as u8, (freq >> 8) as u8]; self.port.write(&bytes)?; Ok(()) } pub fn stop(&mut self) -> Result<()> { let bytes: Vec = vec![0xff]; self.port.write(&bytes)?; Ok(()) } pub fn execute(&mut self, ch: u8, instruction: Instruction) -> Result<()> { match instruction { Instruction::NoteOn(note) => self.note_on(ch, note), Instruction::NoteOff => self.note_off(ch), Instruction::Modulation { depth, rate } => self.modulation(ch, depth, rate), Instruction::Frequency(freq) => self.frequency_on(ch, freq), } } } pub struct Pattern { pub rows: Vec<[Option; CHANNEL_COUNT as usize]>, } impl Pattern { pub fn new() -> Self { let mut rows = Vec::new(); rows.resize_with(256, || [None; CHANNEL_COUNT as usize]); Self { rows } } } pub struct TimeSignature { pub numerator: u8, pub denominator: u8, } impl Default for TimeSignature { fn default() -> Self { Self { numerator: 4, denominator: 4, } } } pub struct Song { pub bpm: f64, pub time_signature: TimeSignature, pub patterns: Vec, pub pattern_order: Vec, // index into patterns } impl Song { const DEFAULT_BPM: f64 = 120.0; pub fn new(bpm: Option, time_signature: Option) -> Self { Self { bpm: bpm.unwrap_or(Self::DEFAULT_BPM), time_signature: time_signature.unwrap_or_default(), patterns: vec![Pattern::new()], pattern_order: vec![0], } } pub fn empty(bpm: Option, time_signature: Option) -> Self { Self { bpm: bpm.unwrap_or(Self::DEFAULT_BPM), time_signature: time_signature.unwrap_or_default(), patterns: Vec::new(), pattern_order: Vec::new(), } } pub fn from_midi(_smf: Smf) -> Result { todo!("TBD"); } } struct AppSongState { pub song: Song, pub current_pattern: usize, pub current_row: usize, pub current_column: u8, pub playing: bool, } pub enum SerialCommand { Instruction(u8, Instruction), Stop, } pub enum SerialStatus { SerialConnected { name: String }, SerialDisconnected, } #[derive(AppState)] struct SingerApp { song_state: Option, tx: Sender, rx: Receiver, connected: Option, } impl SingerApp { pub fn new() -> Self { let (tx_cmd, rx_cmd) = mpsc::channel(); let (tx_status, rx_status) = mpsc::channel(); Self::spawn_serial_worker(rx_cmd, tx_status); Self { song_state: None, tx: tx_cmd, rx: rx_status, connected: None, } } fn spawn_serial_worker(rx: Receiver, tx: Sender) { thread::spawn(move || { let _ = tx.send(SerialStatus::SerialDisconnected); let mut device: Option = None; loop { if device.is_none() { match SerialDevice::new() { Ok(new_device) => { let name = new_device .port .name() .unwrap_or("".to_string()); println!("Serial device connected: {name}"); let _ = tx.send(SerialStatus::SerialConnected { name }); device = Some(new_device); } Err(_err) => { thread::sleep(Duration::from_secs(1)); continue; } } } match rx.recv_timeout(Duration::from_millis(100)) { Ok(command) => { if let Some(dev) = device.as_mut() { match command { SerialCommand::Instruction(ch, instr) => { if let Err(e) = dev.execute(ch, instr) { eprintln!("Disconnected: {e}"); device = None; let _ = tx.send(SerialStatus::SerialDisconnected); } } SerialCommand::Stop => { if let Err(e) = dev.stop() { eprintln!("Disconnected: {e}"); device = None; let _ = tx.send(SerialStatus::SerialDisconnected); } } } } } Err(mpsc::RecvTimeoutError::Timeout) => {} Err(_) => break, } } }); } fn update(&mut self) { while let Ok(status) = self.rx.try_recv() { match status { SerialStatus::SerialDisconnected => self.connected = None, SerialStatus::SerialConnected { name } => self.connected = Some(name), } } } fn draw(&mut self, gfx: &mut Graphics) { let mut draw = gfx.create_draw(); draw.clear(Color::WHITE); draw.line((40.0, 40.0), (100.0, 200.0)) .width(15.0) .color(Color::BLUE); draw.rect((220.0, 100.0), (120.0, 60.0)).color(Color::GREEN); let status_color = if self.connected.is_some() { Color::GREEN } else { Color::GRAY }; draw.rect((14.0, 14.0), (20.0, 20.0)).color(status_color); gfx.render(&draw); } } fn setup(_gfx: &mut Graphics) -> SingerApp { SingerApp::new() } fn update(_app: &mut notan::app::App, state: &mut SingerApp) { state.update(); } fn draw(gfx: &mut Graphics, state: &mut SingerApp) { state.draw(gfx); } #[notan_main] fn main() -> Result<(), String> { notan::init_with(setup) .add_config(WindowConfig::new().set_title("singer").set_fullscreen(true)) .add_config(DrawConfig) .update(update) .draw(draw) .build() }