From e4f39427578a167a465ae4be8fd04b2e9648afb6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 16 Jun 2024 19:13:50 +0300 Subject: [PATCH] dynamic device activation --- Cargo.lock | 55 +++ Cargo.toml | 1 + src/device.rs | 165 ++------- src/device/sequencer.rs | 770 +++++++++++++++++++--------------------- src/device/transport.rs | 35 +- src/draw.rs | 27 +- src/layout.rs | 4 +- src/main.rs | 11 +- src/prelude.rs | 3 + 9 files changed, 484 insertions(+), 587 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0442fa8..751a4a65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,7 @@ dependencies = [ "crossterm", "jack", "microxdg", + "midly", "ratatui", "toml", ] @@ -239,6 +240,31 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crossterm" version = "0.27.0" @@ -420,6 +446,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdcd3dc4ca0d1a9b1b80576639e93911a3e1db25a31ab6e6ba33027429adde5e" +[[package]] +name = "midly" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a" +dependencies = [ + "rayon", +] + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -529,6 +564,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 1ee5e462..ec3f3987 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ backtrace = "0.3.72" microxdg = "0.1.2" toml = "0.8.12" better-panic = "0.3.0" +midly = "0.5" diff --git a/src/device.rs b/src/device.rs index cb3fe14d..4d16bbfa 100644 --- a/src/device.rs +++ b/src/device.rs @@ -102,6 +102,7 @@ pub struct DynamicDevice { pub render: MutexUsually + Send>>, pub handle: ArcUsually<()> + Send>>>, pub process: ArcControl + Send>>>, + client: Option } impl Device for DynamicDevice { @@ -113,7 +114,11 @@ impl Device for DynamicDevice { } } -impl DynamicDevice { +type DynamicAsyncClient = AsyncClient; +type DynamicNotifications = Notifications>; +type DynamicProcessHandler = ClosureProcessHandler; + +impl DynamicDevice { fn new <'a, R, H, P> (render: R, handle: H, process: P, state: T) -> Self where R: FnMut(&T, &mut Buffer, Rect)->Usually + Send + 'static, H: FnMut(&mut T, &AppEvent) -> Result<(), Box> + Send + 'static, @@ -124,16 +129,14 @@ impl DynamicDevice { render: Mutex::new(Box::new(render)), handle: Arc::new(Mutex::new(Box::new(handle))), process: Arc::new(Mutex::new(Box::new(process))), + client: None, } } fn state (&self) -> std::sync::MutexGuard<'_, T> { self.state.lock().unwrap() } -} - -impl DynamicDevice { - fn activate (&self, client: Client) -> Usually<()> { - let notifications = { + fn activate (mut self, client: Client) -> Usually { + self.client = Some(client.activate_async(Notifications(Box::new({ let state = self.state.clone(); let handle = self.handle.clone(); move|event|{ @@ -141,9 +144,7 @@ impl DynamicDevice { let mut handle = handle.lock().unwrap(); handle(&mut state, &event).unwrap() } - }; - let notifications = Notifications(Box::new(notifications)); - let handler = ClosureProcessHandler::new(Box::new({ + }) as Box), ClosureProcessHandler::new(Box::new({ let state = self.state.clone(); let process = self.process.clone(); move|client: &Client, scope: &ProcessScope|{ @@ -151,9 +152,8 @@ impl DynamicDevice { let mut process = process.lock().unwrap(); (process)(&mut state, client, scope) } - }) as BoxedProcessHandler); - client.activate_async(notifications, handler)?; - Ok(()) + }) as BoxedProcessHandler))?); + Ok(self) } } @@ -185,14 +185,6 @@ pub enum AppEvent { Jack(JackEvent) } -pub fn activate_jack_client ( - client: Client, - notifications: N, - handler: BoxedProcessHandler -) -> Result, Box> { - Ok(client.activate_async(notifications, ClosureProcessHandler::new(handler))?) -} - fn panic_hook (info: &std::panic::PanicInfo) { stdout() .execute(crossterm::terminal::LeaveAlternateScreen) @@ -223,14 +215,17 @@ pub struct Notifications(T); impl NotificationHandler for Notifications { fn thread_init (&self, _: &Client) { + println!("JACK: thread init"); self.0(AppEvent::Jack(JackEvent::ThreadInit)); } fn shutdown (&mut self, status: ClientStatus, reason: &str) { + println!("JACK: shutdown with status {:?} because \"{}\"", status, reason); self.0(AppEvent::Jack(JackEvent::Shutdown)); } fn freewheel (&mut self, _: &Client, is_enabled: bool) { + println!("JACK: freewheel mode is {}", if is_enabled { "on" } else { "off" }); self.0(AppEvent::Jack(JackEvent::Freewheel)); } @@ -240,145 +235,41 @@ impl NotificationHandler for Notifications { } fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { + println!("JACK: {} client with name \"{name}\"", + if is_reg { "registered" } else { "unregistered" }); self.0(AppEvent::Jack(JackEvent::ClientRegistration)); } fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { + println!("JACK: {} port with id {port_id}", + if is_reg { "registered" } else { "unregistered" }); self.0(AppEvent::Jack(JackEvent::PortRegistration)); } fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + println!("JACK: port with id {id} renamed from {old} to {new}",); self.0(AppEvent::Jack(JackEvent::PortRename)); Control::Continue } - fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { + fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) { + println!("JACK: ports with id {a} and {b} are {}", if are { + "connected" + } else { + "disconnected" + }); self.0(AppEvent::Jack(JackEvent::PortsConnected)); } fn graph_reorder (&mut self, _: &Client) -> Control { + println!("JACK: graph reordered"); self.0(AppEvent::Jack(JackEvent::GraphReorder)); Control::Continue } fn xrun (&mut self, _: &Client) -> Control { + println!("JACK: xrun occurred"); self.0(AppEvent::Jack(JackEvent::XRun)); Control::Continue } } - -//pub struct Jack { - //client: OptionControl + Send>> - //>>, - //pub transport: Option, - //audio_ins: BTreeMap>, - //audio_outs: BTreeMap>, - //midi_ins: BTreeMap>, - //midi_outs: BTreeMap>, -//} - -//impl Jack { - //pub fn init_from_cli (options: &Cli) - //-> Result>, Box> - //{ - //let jack = Self::init(&options.jack_client_name)?; - //{ - //let jack = jack.clone(); - //let mut jack = jack.lock().unwrap(); - //for port in options.jack_audio_ins.iter() { - //jack.add_audio_in(port)?; - //} - //for port in options.jack_audio_outs.iter() { - //jack.add_audio_out(port)?; - //} - //for port in options.jack_midi_ins.iter() { - //jack.add_midi_in(port)?; - //} - //for port in options.jack_midi_outs.iter() { - //jack.add_midi_out(port)?; - //} - //} - //Ok(jack.clone()) - //} - //fn init (name: &str) - //-> Result>, Box> - //{ - //let jack = Arc::new(Mutex::new(Self { - //client: None, - //transport: None, - //audio_ins: BTreeMap::new(), - //audio_outs: BTreeMap::new(), - //midi_ins: BTreeMap::new(), - //midi_outs: BTreeMap::new(), - //})); - //let (client, status) = Client::new(name, ClientOptions::NO_START_SERVER)?; - //println!("Client status: {status:?}"); - //let jack1 = jack.clone(); - //let mut jack1 = jack1.lock().unwrap(); - //let jack2 = jack.clone(); - //jack1.transport = Some(client.transport()); - //jack1.client = Some(client.activate_async( - //Notifications, ClosureProcessHandler::new(Box::new( - //move |_client: &Client, _ps: &ProcessScope| -> Control { - //let jack = jack2.lock().expect("Failed to lock jack mutex"); - //jack.read_inputs(); - //jack.write_outputs(); - //Control::Continue - //} - //) as BoxControl + Send>) - //)?); - //Ok(jack) - //} - //fn start (&self) { - //} - //fn process (&self, _: &Client, ps: &ProcessScope) -> Control { - //Control::Continue - //} - //fn add_audio_in (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, AudioIn::default())?; - //self.audio_ins.insert(name.into(), port); - //Ok(self) - //} - //fn add_audio_out (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, AudioOut::default())?; - //self.audio_outs.insert(name.into(), port); - //Ok(self) - //} - //fn add_midi_in (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, MidiIn::default())?; - //self.midi_ins.insert(name.into(), port); - //Ok(self) - //} - //fn add_midi_out (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, MidiOut::default())?; - //self.midi_outs.insert(name.into(), port); - //Ok(self) - //} - //fn read_inputs (&self) { - //// read input buffers - ////println!("read"); - //} - //fn write_outputs (&self) { - //// clear output buffers - //// write output buffers - ////println!("write"); - //} -//} diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index c7656f21..e89d737c 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -3,108 +3,97 @@ use ratatui::style::Stylize; pub struct Sequencer { name: String, - mode: SequencerMode, - cursor: (u16, u16, u16), + mode: SequencerView, + note_axis: (u16, u16), + note_cursor: u16, + time_axis: (u16, u16), + time_cursor: u16, rate: Hz, tempo: Tempo, - sequences: Vec, - input_port: ::jack::Port<::jack::MidiIn>, + transport: ::jack::Transport, + sequence: std::collections::BTreeMap>>, + ppq: u32, + input_port: Port, input_connect: Vec, - output_port: ::jack::Port<::jack::MidiOut>, + output_port: Port, output_connect: Vec, + playing: bool, + recording: bool, + overdub: bool, } -pub enum SequencerMode { +enum SequencerView { Tiny, Compact, - Vertical, Horizontal, + Vertical, } impl Sequencer { - pub fn new (name: &str) -> Result, Box> { + pub fn new (name: &str) -> Usually> { let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; - let mut input = client.register_port("input", ::jack::MidiIn::default())?; - let mut output = client.register_port("output", ::jack::MidiOut::default())?; - let device = DynamicDevice::new(render, handle, process, Self { - name: name.into(), - mode: SequencerMode::Vertical, - cursor: (11, 0, 0), - rate: Hz(client.sample_rate() as u32), - tempo: Tempo(120000), - sequences: vec![ - Sequence::new(&client, "Rhythm#000",)?, - Sequence::new(&client, "Rhythm#001",)?, - Sequence::new(&client, "Rhythm#002",)?, - Sequence::new(&client, "Rhythm#003",)?, - ], - input_port: client.register_port("in", ::jack::MidiIn::default())?, - input_connect: vec![], - output_port: client.register_port("out", ::jack::MidiOut::default())?, - output_connect: vec![], - }); - device.activate(client)?; - Ok(device) + DynamicDevice::new( + render, + handle, + process, + Self { + name: name.into(), + mode: SequencerView::Horizontal, + note_axis: (36, 60), + note_cursor: 0, + time_axis: (0, 64), + time_cursor: 0, + rate: Hz(client.sample_rate() as u32), + tempo: Tempo(120000), + transport: client.transport(), + input_port: client.register_port("in", MidiIn::default())?, + input_connect: vec!["nanoKEY Studio * (capture): *".into()], + output_port: client.register_port("out", MidiOut::default())?, + output_connect: vec![], + ppq: 96, + sequence: std::collections::BTreeMap::new(), + playing: true, + recording: true, + overdub: true, + } + ).activate(client) } } pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) -> Control { - for seq in state.sequences.iter() { - seq.chunk_out(scope, &mut state.output_port); - seq.chunk_in(scope, &state.input_port); - } + process_out(state, scope); + process_in(state, scope); Control::Continue } -pub struct Sequence { - is_recording: bool, - overdub: bool, - ppq: u32, - buffer: std::collections::BTreeMap>>>, - loop_points: Option<(u32, u32)>, - is_playing: bool, -} - -impl Sequence { - fn new (client: &Client, name: &str) -> Usually { - Ok(Self { - is_recording: false, - overdub: false, - ppq: 96, - buffer: std::collections::BTreeMap::new(), - loop_points: None, - is_playing: false - }) - } - fn chunk_in ( - &self, - scope: &ProcessScope, - port: &::jack::Port<::jack::MidiIn>, - ) { - if self.is_recording { - for event in port.iter(scope) { - let ::jack::RawMidi { time, bytes } = event; - println!("\n{time} {bytes:?}"); - } - } - } - fn chunk_out ( - &self, - scope: &ProcessScope, - port: &mut ::jack::Port<::jack::MidiOut>, - ) { - if self.is_playing { - let size = scope.n_frames(); - let start = scope.last_frame_time(); - let end = start + size; - let mut writer = port.writer(scope); - for time in 0..size { - // TODO - } +fn process_in (state: &Sequencer, scope: &ProcessScope) { + for event in state.input_port.iter(scope) { + match midly::live::LiveEvent::parse(event.bytes).unwrap() { + midly::live::LiveEvent::Midi { channel, message } => match message { + midly::MidiMessage::NoteOn { key, vel } => { + panic!("on") + }, + midly::MidiMessage::NoteOff { key, vel } => { + panic!("off") + }, + _ => {} + }, + _ => {} } } } +fn process_out (state: &mut Sequencer, scope: &ProcessScope) { + if state.playing { + let size = scope.n_frames(); + let start = scope.last_frame_time(); + let end = start + size; + let mut writer = state.output_port.writer(scope); + for time in 0..size { + // TODO + } + } +} impl NotificationHandler for Sequencer { fn thread_init (&self, _: &Client) {} @@ -127,112 +116,152 @@ impl NotificationHandler for Sequencer { } } -pub const ACTIONS: [(&'static str, &'static str);4] = [ - ("+/-", "Zoom"), - ("A/D", "Add/delete note"), - ("]/[", "Duration"), - ("CapsLock", "Auto advance"), +pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box> { + match event { + AppEvent::Input(Event::Key(event)) => { + for (code, _, _, command) in COMMANDS.iter() { + if *code == event.code { + command(state); + break + } + } + }, + _ => {} + }; + Ok(()) +} + +const COMMANDS: [(KeyCode, &'static str, &'static str, &'static dyn Fn(&mut Sequencer));18] = [ + (KeyCode::Up, "cursor_up", "move cursor up", &cursor_up), + (KeyCode::Down, "cursor_down", "move cursor down", &cursor_down), + (KeyCode::Left, "cursor_left", "move cursor left", &cursor_left), + (KeyCode::Right, "cursor_right", "move cursor right", &cursor_right), + (KeyCode::Char(']'), "cursor_inc", "increase note duration", &cursor_inc), + (KeyCode::Char('['), "cursor_dec", "decrease note duration", &cursor_dec), + (KeyCode::Char('`'), "mode_next", "next view mode", &mode_next), + (KeyCode::Tab, "mode_prev", "previous view mode", &mode_prev), + (KeyCode::Char('+'), "zoom_in", "Zoom in", &nop), + (KeyCode::Char('-'), "zoom_out", "Zoom out", &nop), + (KeyCode::Char('a'), "note_add", "Add note", ¬e_add), + (KeyCode::Char('d'), "note_del", "Delete note", ¬e_del), + (KeyCode::CapsLock, "advance", "Toggle auto advance", &nop), + (KeyCode::Char('w'), "rest", "Advance by note duration", &nop), + (KeyCode::Char('r'), "record", "Toggle recodring", &toggle_record), + (KeyCode::Char('o'), "overdub", "Toggle overdub", &toggle_overdub), + (KeyCode::Char('p'), "play", "Toggle play/pause", &toggle_play), + (KeyCode::Char('s'), "stop", "Stop and rewind", &nop), ]; -pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box> { - - if let AppEvent::Focus = event { +fn nop (_: &mut Sequencer) { +} +fn note_add (s: &mut Sequencer) { + let time = (s.time_axis.0 + s.time_cursor) as u32; + let time_start = time * s.ppq; + let time_end = (time + 1) * s.ppq; + let note_on = vec![0, 0, 0]; + let note_off = vec![0, 0, 0]; + if s.sequence.contains_key(&time_start) { + s.sequence.get_mut(&time_start).unwrap().push(note_off.clone()); + } else { + s.sequence.insert(time_start, vec![note_on]); } - - if let AppEvent::Input(Event::Key(event)) = event { - match event.code { - KeyCode::Down => { - state.cursor.0 = if state.cursor.0 >= 23 { - 0 - } else { - state.cursor.0 + 1 - } - }, - KeyCode::Up => { - state.cursor.0 = if state.cursor.0 == 0 { - 23 - } else { - state.cursor.0 - 1 - } - }, - KeyCode::Left => { - state.cursor.1 = if state.cursor.1 == 0 { - 63 - } else { - state.cursor.1 - 1 - } - }, - KeyCode::Right => { - state.cursor.1 = if state.cursor.1 == 63 { - 0 - } else { - state.cursor.1 + 1 - } - }, - KeyCode::Char('[') => { - if state.cursor.2 > 0 { - state.cursor.2 = state.cursor.2 - 1 - } - }, - KeyCode::Char(']') => { - state.cursor.2 = state.cursor.2 + 1 - }, - KeyCode::Char('i') => { - //state.inputs_open.fetch_xor(true, Ordering::Relaxed); - }, - KeyCode::Char('o') => { - //state.outputs_open.fetch_xor(true, Ordering::Relaxed); - }, - KeyCode::Char('a') => { - let row = state.cursor.0 as usize; - let step = state.cursor.1 as usize; - let duration = state.cursor.2 as usize; - //let mut sequence = state.sequence.lock().unwrap(); - //sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128)); - //if state.cursor.2 > 0 { - //sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35)); - //} - }, - _ => { - println!("{event:?}"); - } - } + if s.sequence.contains_key(&time_end) { + s.sequence.get_mut(&time_end).unwrap().push(note_off.clone()); + } else { + s.sequence.insert(time_end, vec![note_off]); } - Ok(()) +} +fn note_del (_: &mut Sequencer) { +} +fn cursor_up (s: &mut Sequencer) { + let time = s.time_axis.1 - s.time_axis.0; + let note = s.note_axis.1 - s.note_axis.0; + match s.mode { + SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) - 1) % time }, + SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) - 1) % note }, + _ => unimplemented!() + } +} +fn cursor_down (s: &mut Sequencer) { + let time = s.time_axis.1 - s.time_axis.0; + let note = s.note_axis.1 - s.note_axis.0; + match s.mode { + SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) + 1) % time }, + SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) + 1) % note }, + _ => unimplemented!() + } +} +fn cursor_left (s: &mut Sequencer) { + let time = s.time_axis.1 - s.time_axis.0; + let note = s.note_axis.1 - s.note_axis.0; + match s.mode { + SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) - 1) % note }, + SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) - 1) % time }, + _ => unimplemented!() + } +} +fn cursor_right (s: &mut Sequencer) { + let time = s.time_axis.1 - s.time_axis.0; + let note = s.note_axis.1 - s.note_axis.0; + match s.mode { + SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) + 1) % note }, + SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) + 1) % time }, + _ => unimplemented!() + } +} +fn cursor_inc (s: &mut Sequencer) { + //s.cursor.2 = s.cursor.2 + 1 +} +fn cursor_dec (s: &mut Sequencer) { + //if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 } +} +fn mode_next (s: &mut Sequencer) { + s.mode = SequencerView::Horizontal +} +fn mode_prev (s: &mut Sequencer) { + s.mode = SequencerView::Vertical +} +fn toggle_play (s: &mut Sequencer) { + s.playing = !s.playing +} +fn toggle_record (s: &mut Sequencer) { + s.recording = !s.recording +} +fn toggle_overdub (s: &mut Sequencer) { + s.overdub = !s.overdub } fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { + let Rect { x, y, width, height } = area; + let (time0, time1) = sequencer.time_axis; + let (note0, note1) = sequencer.note_axis; let header = draw_sequencer_header(sequencer, buf, area)?; let piano = match sequencer.mode { - SequencerMode::Tiny => Rect { - x: area.x, - y: area.y, - width: area.width, - height: 0 - }, - SequencerMode::Compact => Rect { - x: area.x, - y: area.y, - width: area.width, - height: 0 - }, - SequencerMode::Vertical => draw_sequencer_vertical(sequencer, buf, Rect { - x: area.x, - y: area.y + header.height, - width: area.width, - height: area.height - header.height + SequencerView::Tiny => + Rect { x, y, width, height: 0 }, + SequencerView::Compact => + Rect { x, y, width, height: 0 }, + SequencerView::Vertical => draw_sequencer_vertical(sequencer, buf, Rect { + x, + y: y + header.height, + width: 3 + note1 - note0, + height: 3 + time1 - time0, + })?, + SequencerView::Horizontal => draw_sequencer_horizontal(sequencer, buf, Rect { + x, + y: y + header.height, + width: 3 + time1 - time0, + height: 3 + note1 - note0, })?, - _ => unimplemented!() }; - //draw_box_styled(buf, area, Some(Style::default().red())); - Ok(Rect { - x: area.x, - y: area.y, - width: header.width.max(piano.width), - height: header.height + piano.height - }) + Ok(draw_box(buf, Rect { + x, + y, + width: header.width.max(piano.width), + height: header.height + piano.height + 1 + })) } fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { @@ -240,8 +269,23 @@ fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) - let style = Style::default().gray(); buf.set_string(x + 1, y + 1, &format!(" │ 00:00.00 / 00:00.00"), style); buf.set_string(x + 2, y + 1, &format!("{}", &sequencer.name), style.white().bold()); - buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ ⏺ REC │ ⏺ DUB "), style); - Ok(Rect { x, y, width, height: 4 }) + buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style); + buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if sequencer.playing { + Style::default().green() + } else { + Style::default().dim() + }); + buf.set_string(x + 24, y + 2, &format!("⏺ REC"), if sequencer.recording { + Style::default().red() + } else { + Style::default().dim() + }); + buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if sequencer.overdub { + Style::default().yellow() + } else { + Style::default().dim() + }); + Ok(Rect { x, y, width: 39, height: 4 }) } const KEY_WHITE: Style = Style { @@ -267,33 +311,134 @@ const KEY_HORIZONTAL_STYLE: [Style;12] = [ fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, .. } = area; - for i in 0..area.width-7 { - let color = KEY_HORIZONTAL_STYLE[i as usize % 12]; - buf.set_string(x + 5 + i, y - 1, &format!("▄"), color); - buf.set_string(x + 5 + i, y, &format!("▀"), color); - } + let (time0, time1) = sequencer.time_axis; + let (note0, note1) = sequencer.note_axis; let bg = Style::default().on_black(); - for i in 0..area.height - 8 { - buf.set_string(x + 5, y + 1 + i, &" ".repeat((area.width-7)as usize), bg); - if i % 4 == 0 { - buf.set_string(x + 2, y + 1 + i, &format!("{:2}", i / 4 + 1), Style::default()); + for step in 0..(time1-time0)/2 { + let y = y + step - time0; + buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); + if step % 2 == 0 { + buf.set_string(x + 2, y, &format!("{:2} ", step / 2 + 1), Style::default()); } } - for octave in -2i16..=8i16 { - let x = x + 5 + (24i16 + octave * 12) as u16; - if x >= area.x + area.width - 6 { - break + for key in note0..note1.max(note0+32) { + let x = x + 5 + key - note0; + let color = KEY_HORIZONTAL_STYLE[key as usize % 12]; + buf.set_string(x, y - 1, &format!("▄"), color); + if key % 12 == 0 { + let octave = format!("C{}", (key / 12) as i8 - 4); + buf.set_string(x, y + 1, &octave, Style::default()); } - buf.set_string( - x, - y + 1, - &format!("C{octave}"), - Style::default() - ); } - Ok(Rect { x, y, width: area.width, height: area.height - 6 }) + buf.set_string( + x + 5 + sequencer.note_cursor, + y + sequencer.time_cursor / 2, + if sequencer.time_cursor % 2 == 0 { "▀" } else { "▄" }, + Style::default() + ); + Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 }) } +const KEYS_VERTICAL: [&'static str; 6] = [ + "▀", "▀", "▀", "█", "▄", "▄", +]; + +fn draw_sequencer_horizontal (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { + let Rect { x, y, .. } = area; + let (time0, time1) = sequencer.time_axis; + let (note0, note1) = sequencer.note_axis; + let bw = Style::default().dim(); + let bg = Style::default().on_black(); + for i in 0..32.max(note1-note0)/2 { + let y = y + i; + buf.set_string(x + 1, y, KEYS_VERTICAL[(i % 6) as usize], bw); + buf.set_string(x + 2, y, "█", bw); + buf.set_string(x + 5, y, &" ".repeat((time1 - time0) as usize), bg); + if i % 6 == 0 { + let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4); + buf.set_string(x+5, y, &octave, Style::default()); + } + } + for step in time0..time1 { + let time_start = step as u32 * sequencer.ppq; + let time_end = (step + 1) as u32 * sequencer.ppq; + for (i, (t, events)) in sequencer.sequence.range(time_start..time_end).enumerate() { + if events.len() > 0 { + buf.set_string(x + 5 + step as u16, y, "█", bw); + } + } + } + buf.set_string( + x + 5 + sequencer.time_cursor, + y + sequencer.note_cursor / 2, + if sequencer.note_cursor % 2 == 0 { "▀" } else { "▄" }, + Style::default() + ); + Ok(Rect { x, y, width: time1 - time0 + 6, height: 32.max(note1 - note0) / 2 }) +} + + //buf.set_string(x + 3, y, "╭1.1.", Style::default().dim()); + //buf.set_string(x + 3 + 16, y, "╭1.2.", Style::default().dim()); + //buf.set_string(x + 3 + 32, y, "╭1.3.", Style::default().dim()); + //buf.set_string(x + 3 + 48, y, "╭1.4.", Style::default().dim()); + ////buf.set_string(x + 2, y, "╭", Style::default().dim()); + ////buf.set_string(x + 2, y + 13, "╰", Style::default().dim()); + //buf.set_string(x + 2 + 65, y, "╮", Style::default().dim()); + //buf.set_string(x + 2 + 65, y + 13, "╯", Style::default().dim()); + //let transport = sequencer.transport.query().unwrap(); + //let frame = transport.pos.frame(); + //let rate = transport.pos.frame_rate().unwrap(); + //let second = (frame as f64) / (rate as f64); + //let minute = second / 60f64; + //let bpm = 120f64; + //let div = 4; + //let beats = minute * bpm; + //let bars = beats as u32 / div as u32; + //let beat = beats as u32 % div as u32 + 1; + //let beat_sub = beats % 1.0; + //buf.set_string( + //x - 18, y + height, + //format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32), + //Style::default() + //); + //let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 { + //ratatui::style::Color::Gray + //} else if step == sequencer.cursor.1 as usize || key == sequencer.cursor.0/2 { + //ratatui::style::Color::Black + //} else { + //ratatui::style::Color::Reset + //}; + //let top = sequence[(key * 2) as usize][step].is_some(); + //let bottom = sequence[(key * 2 + 1) as usize][step].is_some(); + //match (top, bottom) { + //(true, true) => { + //buf.set_string(x + 3 + step as u16, y + 1 + key, "█", + //Style::default().yellow().not_dim().bold().bg(bg)); + //}, + //(true, false) => { + //buf.set_string(x + 3 + step as u16, y + 1 + key, "▀", + //Style::default().yellow().not_dim().bold().bg(bg)); + //}, + //(false, true) => { + //buf.set_string(x + 3 + step as u16, y + 1 + key, "▄", + //Style::default().yellow().not_dim().bold().bg(bg)); + //}, + //(false, false) => if step % 16 == 0 { + //buf.set_string(x + 3 + step as u16, y + 1 + key, "┊", + //Style::default().dim().bg(bg)) + //} else { + //buf.set_string(x + 3 + step as u16, y + 1 + key, "·", + //Style::default().dim().bg(bg)) + //}, + //} + //for step in 0..64 { + //if step % 8 == 0 { + //buf.set_string(x + 3 + step as u16, y + 1 + 12, [ + //"|a", "|b", "|c", "|d", "|e", "|f", "|g", "|h" + //][step / 8 as usize], Style::default().dim()) + //} + //} + //{ //let mut area = area.clone(); //area.y = area.y + 3; @@ -371,219 +516,32 @@ fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) //} //} // - -fn draw_sequence_button ( - buf: &mut Buffer, - x: u16, - y: u16, - name: &str -) { - draw_box(buf, Rect { x, y, width: 18, height: 3 }); - buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name), - Style::default().white().bold().not_dim()); - buf.set_string(x + 4, y + 0, "┬", Style::default().gray()); - buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim()); - buf.set_string(x + 4, y + 2, "┴", Style::default().gray()); -} - -fn draw_rec_dub_button ( - buf: &mut Buffer, - x: u16, - y: u16, -) { - draw_box(buf, Rect { x, y, width: 18, height: 3 }); - buf.set_string(x + 1, y + 1, " ⏺ REC DUB ", - Style::default().white().bold().not_dim()); - buf.set_string(x + 5, y + 0, "┬", Style::default().gray()); - buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim()); - buf.set_string(x + 5, y + 2, "┴", Style::default().gray()); - buf.set_string(x + 11, y + 0, "┬", Style::default().gray()); - buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim()); - buf.set_string(x + 11, y + 2, "┴", Style::default().gray()); -} - -const NOTE_NAMES: [&'static str;12] = [ - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", -]; - -const KEYS_VERTICAL: [&'static str; 6] = [ - "▀", "▀", "▀", "█", "▄", "▄", -]; - -fn draw_sequence_keys ( - area: Rect, - buf: &mut Buffer, - transport: &::jack::TransportStatePosition, - sequence: &Arc>>>>, - cursor: &(u16, u16, u16) -) { - buf.set_string(area.x + 3, area.y, "╭1.1.", Style::default().dim()); - buf.set_string(area.x + 3 + 16, area.y, "╭1.2.", Style::default().dim()); - buf.set_string(area.x + 3 + 32, area.y, "╭1.3.", Style::default().dim()); - buf.set_string(area.x + 3 + 48, area.y, "╭1.4.", Style::default().dim()); - //buf.set_string(area.x + 2, area.y, "╭", Style::default().dim()); - //buf.set_string(area.x + 2, area.y + 13, "╰", Style::default().dim()); - buf.set_string(area.x + 2 + 65, area.y, "╮", Style::default().dim()); - buf.set_string(area.x + 2 + 65, area.y + 13, "╯", Style::default().dim()); - let frame = transport.pos.frame(); - let rate = transport.pos.frame_rate().unwrap(); - let second = (frame as f64) / (rate as f64); - let minute = second / 60f64; - let bpm = 120f64; - let div = 4; - let beats = minute * bpm; - let bars = beats as u32 / div as u32; - let beat = beats as u32 % div as u32 + 1; - let beat_sub = beats % 1.0; - //buf.set_string( - //area.x - 18, area.y + area.height, - //format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32), - //Style::default() - //); - let sequence = sequence.lock().unwrap(); - for key in 0..12 { - buf.set_string(area.x, area.y + 1 + key, KEYS_VERTICAL[(key % 6) as usize], - Style::default().dim()); - buf.set_string(area.x + 1, area.y + 1 + key, "█", - Style::default().dim()); - for step in 0..64 { - let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 { - ratatui::style::Color::Gray - } else if step == cursor.1 as usize || key == cursor.0/2 { - ratatui::style::Color::Black - } else { - ratatui::style::Color::Reset - }; - let top = sequence[(key * 2) as usize][step].is_some(); - let bottom = sequence[(key * 2 + 1) as usize][step].is_some(); - match (top, bottom) { - (true, true) => { - buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "█", - Style::default().yellow().not_dim().bold().bg(bg)); - }, - (true, false) => { - buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▀", - Style::default().yellow().not_dim().bold().bg(bg)); - }, - (false, true) => { - buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▄", - Style::default().yellow().not_dim().bold().bg(bg)); - }, - (false, false) => if step % 16 == 0 { - buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "┊", - Style::default().dim().bg(bg)) - } else { - buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "·", - Style::default().dim().bg(bg)) - }, - } - } - } - - for step in 0..64 { - if step % 8 == 0 { - buf.set_string(area.x + 3 + step as u16, area.y + 1 + 12, [ - "|A", "|B", "|C", "|D", "|E", "|F", "|G", "|H" - ][step / 8 as usize], Style::default().dim()) - } - } -} - -fn draw_sequence_cursor ( - area: Rect, buf: &mut Buffer, cursor: &(u16, u16, u16) -) { - let cursor_character = match cursor.0 % 2 { - 0 => "▀", - 1 => "▄", - _ => unreachable!() - }; - let cursor_y = cursor.0 / 2; - let cursor_text = if cursor.2 == 0 { - cursor_character.into() - } else { - cursor_character.repeat(cursor.2 as usize) - }; - let style = Style::default().yellow().dim(); - buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style); -} - -//pub type MIDISequenceVoice = std::collections::BTreeMap; - -//#[derive(Clone)] -//pub enum NoteEvent { - //On(u8), - //Off(u8), +//fn draw_sequence_button ( + //buf: &mut Buffer, + //x: u16, + //y: u16, + //name: &str +//) { + //draw_box(buf, Rect { x, y, width: 18, height: 3 }); + //buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name), + //Style::default().white().bold().not_dim()); + //buf.set_string(x + 4, y + 0, "┬", Style::default().gray()); + //buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim()); + //buf.set_string(x + 4, y + 2, "┴", Style::default().gray()); //} -//const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new(); - -//impl MIDISequence { - //fn new () -> Self { - //Self { - //channels: [ - //MIDISequenceChannel::new(1), - //MIDISequenceChannel::new(2), - //MIDISequenceChannel::new(3), - //MIDISequenceChannel::new(4), - //MIDISequenceChannel::new(5), - //MIDISequenceChannel::new(6), - //MIDISequenceChannel::new(7), - //MIDISequenceChannel::new(8), - //MIDISequenceChannel::new(9), - //MIDISequenceChannel::new(10), - //MIDISequenceChannel::new(11), - //MIDISequenceChannel::new(12), - //MIDISequenceChannel::new(13), - //MIDISequenceChannel::new(14), - //MIDISequenceChannel::new(15), - //MIDISequenceChannel::new(16), - //] - //} - //} +//fn draw_rec_dub_button ( + //buf: &mut Buffer, + //x: u16, + //y: u16, +//) { + //draw_box(buf, Rect { x, y, width: 18, height: 3 }); + //buf.set_string(x + 1, y + 1, " ⏺ REC DUB ", + //Style::default().white().bold().not_dim()); + //buf.set_string(x + 5, y + 0, "┬", Style::default().gray()); + //buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim()); + //buf.set_string(x + 5, y + 2, "┴", Style::default().gray()); + //buf.set_string(x + 11, y + 0, "┬", Style::default().gray()); + //buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim()); + //buf.set_string(x + 11, y + 2, "┴", Style::default().gray()); //} - -//pub struct MIDISequence { - //channels: [MIDISequenceChannel;16], -//} -//pub struct MIDISequenceChannel { - //number: u8, - //notes: [MIDISequenceVoice;128], -//} -//impl MIDISequenceChannel { - //fn new (number: u8) -> Self { - //Self { - //number, - //notes: [VOICE_EMPTY;128] - //} - //} -//} - -//#[derive(Clone)] -//pub enum SequencerEvent { - //NoteOn(u8, u8), - //NoteOff(u8) -//} - - //let buffer_index = self.to_buffer_index(chunk_start + frame, scope, bpm); - //let value = frame_steps[(start_looped + frame as usize) % loop_frames]; - //if let Some(step) = value { - //for track in sequence.iter() { - //for event in track[step].iter() { - //writer.write(&::jack::RawMidi { - //time: frame as u32, - //bytes: &match event { - //SequencerEvent::NoteOn(pitch, velocity) => [ - //0b10010000, - //*pitch, - //*velocity - //], - //SequencerEvent::NoteOff(pitch) => [ - //0b10000000, - //*pitch, - //0b00000000 - //], - //} - //}).unwrap() - //} - //} - //} diff --git a/src/device/transport.rs b/src/device/transport.rs index c76f5cfe..071df5bc 100644 --- a/src/device/transport.rs +++ b/src/device/transport.rs @@ -10,12 +10,12 @@ pub struct Transport { impl Transport { pub fn new (name: &str) -> Result, Box> { let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; - Ok(DynamicDevice::new(render, handle, process, Self { + DynamicDevice::new(render, handle, process, Self { name: name.into(), transport: client.transport(), timesig: (4.0, 4.0), bpm: 113.0, - })) + }).activate(client) } pub fn play_from_start_or_stop_and_rewind (&mut self) { @@ -233,39 +233,22 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [ ("(Shift-)Space", "⯈ Play/pause"), ]; -struct TransportJack; - -impl NotificationHandler for TransportJack { - fn thread_init (&self, _: &Client) { - } - - fn shutdown (&mut self, status: ClientStatus, reason: &str) { - } - - fn freewheel (&mut self, _: &Client, is_enabled: bool) { - } - +impl NotificationHandler for Transport { + fn thread_init (&self, _: &Client) {} + fn shutdown (&mut self, status: ClientStatus, reason: &str) {} + fn freewheel (&mut self, _: &Client, is_enabled: bool) {} fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { Control::Quit } - - fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { - } - - fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { - } - + fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {} + fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {} fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { Control::Continue } - - fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { - } - + fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {} fn graph_reorder (&mut self, _: &Client) -> Control { Control::Continue } - fn xrun (&mut self, _: &Client) -> Control { Control::Continue } diff --git a/src/draw.rs b/src/draw.rs index 2b8d03b9..5d878516 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -38,9 +38,9 @@ pub fn draw_leaf (buffer: &mut Buffer, area: Rect, y: u16, x: u16, text: &str) { buffer.set_string(area.x + x, area.y + 1 + y, bottom, border); } -pub fn draw_box (buffer: &mut Buffer, area: Rect) { +pub fn draw_box (buffer: &mut Buffer, area: Rect) -> Rect { if area.width < 1 || area.height < 1 { - return + return area } let border = Style::default().gray().dim(); let top = format!("╭{}╮", "─".repeat((area.width - 2).into())); @@ -51,11 +51,12 @@ pub fn draw_box (buffer: &mut Buffer, area: Rect) { buffer.set_string(area.x + area.width - 1, y, format!("│"), border); } buffer.set_string(area.x, area.y + area.height - 1, bottom, border); + area } -pub fn draw_box_styled (buffer: &mut Buffer, area: Rect, style: Option