diff --git a/src/device.rs b/src/device.rs index 26582d9a..d17939c6 100644 --- a/src/device.rs +++ b/src/device.rs @@ -19,6 +19,11 @@ pub use self::plugin::Plugin; pub use self::launcher::Launcher; use crossterm::event; +use ::jack::{AudioIn, AudioOut, MidiIn, MidiOut, Port, PortSpec, Client}; + +pub trait Device: Render + Handle + DevicePorts + Send + Sync {} + +impl Device for T {} pub trait Handle { // Returns Ok(true) if the device handled the event. @@ -36,15 +41,59 @@ pub trait Render { } } +pub trait DevicePorts { + fn audio_ins (&self) -> Usually> { + Ok(vec![]) + } + fn audio_outs (&self) -> Usually> { + Ok(vec![]) + } + fn midi_ins (&self) -> Usually> { + Ok(vec![]) + } + fn midi_outs (&self) -> Usually> { + Ok(vec![]) + } + fn connect (&mut self, connect: bool, source: &str, target: &str) + -> Usually<()> + { + Ok(()) + } + fn connect_all (&mut self, connections: &[(bool, &str, &str)]) + -> Usually<()> + { + for (connect, source, target) in connections.iter() { + self.connect(*connect, source, target)?; + } + Ok(()) + } +} + impl Render for Box { fn render (&self, b: &mut Buffer, a: Rect) -> Usually { (**self).render(b, a) } } -pub trait Device: Render + Handle + Send + Sync {} +pub struct DevicePort { + pub name: String, + pub port: Port, + pub connect: Vec, +} -impl Device for T {} +impl DevicePort { + pub fn new (client: &Client, name: &str, connect: &[&str]) -> Usually { + let mut connects = vec![]; + for port in connect.iter() { + connects.push(port.to_string()); + } + Ok(Self { + name: name.to_string(), + port: client.register_port(name, T::default())?, + connect: connects, + }) + } +} impl WidgetRef for &dyn Render { fn render_ref (&self, area: Rect, buf: &mut Buffer) { @@ -147,6 +196,21 @@ impl Render for DynamicDevice { } } +impl DevicePorts for DynamicDevice { + fn audio_ins (&self) -> Usually> { + self.state().audio_ins() + } + fn audio_outs (&self) -> Usually> { + self.state().audio_outs() + } + fn midi_ins (&self) -> Usually> { + self.state().midi_ins() + } + fn midi_outs (&self) -> Usually> { + self.state().midi_outs() + } +} + type DynamicAsyncClient = AsyncClient; type DynamicNotifications = Notifications>; type DynamicProcessHandler = ClosureProcessHandler; diff --git a/src/device/chain.rs b/src/device/chain.rs index 5c28d125..b016c881 100644 --- a/src/device/chain.rs +++ b/src/device/chain.rs @@ -34,7 +34,7 @@ pub fn process (_: &mut Chain, _: &Client, _: &ProcessScope) -> Control { pub fn render (state: &Chain, buf: &mut Buffer, area: Rect) -> Usually { - let Rect { x, y, .. } = area; + let Rect { mut x, mut y, width, height } = area; let selected = Some(if state.focused { Style::default().green().not_dim() } else { @@ -52,8 +52,57 @@ pub fn render (state: &Chain, buf: &mut Buffer, area: Rect) draw_box_styled(buf, area, selected) }, ChainView::Column => { - let (area, areas) = Column::draw(buf, area, &state.items, 0)?; - draw_box_styled(buf, areas[state.focus], selected); + //let (area, areas) = Column::draw(buf, area, &state.items, 0)?; + let mut w = 0u16; + let mut y = area.y; + let mut frames = vec![]; + for (i, device) in state.items.iter().enumerate() { + + let midi_ins = device.midi_ins()?; + let midi_outs = device.midi_outs()?; + let audio_ins = device.audio_ins()?; + let audio_outs = device.audio_outs()?; + + y = y + midi_ins.len() as u16; + let frame = device.render(buf, Rect { x, y, width, height: height - y })?; + frames.push(frame); + w = w.max(frame.width); + + y = y - midi_ins.len() as u16; + for port in midi_ins.iter() { + buf.set_string(x + frame.width - 10, y, + &format!(" MIDI {port} "), + Style::default().black().bold().on_green()); + y = y + 1; + } + + y = y - audio_ins.len() as u16; + for port in audio_ins.iter() { + buf.set_string(x + frame.width - 10, y, + &format!(" MIDI {port} "), + Style::default().black().bold().on_red()); + y = y + 1; + } + + y = y + frame.height - 1; + + y = y + midi_outs.len() as u16; + for port in midi_outs.iter() { + buf.set_string(x + 2, y, + &format!(" MIDI {port} "), + Style::default().black().bold().on_green()); + y = y + 1; + } + + y = y + audio_outs.len() as u16; + for port in audio_outs.iter() { + buf.set_string(x + 2, y, + &format!(" Audio {port} "), + Style::default().black().bold().on_red()); + y = y + 1; + } + } + draw_box_styled(buf, frames[state.focus], selected); area }, }) @@ -159,3 +208,5 @@ pub fn handle (state: &mut Chain, event: &AppEvent) -> Usually { |s: &mut Chain|s.handle_focus(&FocusEvent::Outward)] })) } + +impl DevicePorts for Chain {} diff --git a/src/device/plugin.rs b/src/device/plugin.rs index e2220805..0e6e18b5 100644 --- a/src/device/plugin.rs +++ b/src/device/plugin.rs @@ -119,3 +119,12 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually { }] })) } + +impl DevicePorts for Plugin { + fn midi_ins (&self) -> Usually> { + Ok(vec!["in".into()]) + } + fn audio_outs (&self) -> Usually> { + Ok(vec!["out L".into(), "out R".into()]) + } +} diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index c1e956ab..1f44fa18 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -13,9 +13,14 @@ mod vertical; use vertical::*; pub struct Sequencer { - name: String, + name: String, /// JACK transport handle. - transport: ::jack::Transport, + transport: ::jack::Transport, + /// JACK MIDI input port that will be created. + input_port: Port, + /// JACK MIDI output port that will be created. + output_port: Port, + /// Holds info about tempo timebase: Arc, /// Sequencer resolution, e.g. 16 steps per beat. @@ -24,29 +29,21 @@ pub struct Sequencer { /// Steps in sequence, e.g. 64 16ths = 4 beat loop. /// FIXME: play start / end / loop in ppm steps: usize, - - /// JACK MIDI input port that will be created. - input_port: Port, - /// Port name patterns to connect to. - input_connect: Vec, - /// Play input through output. - monitoring: bool, - /// Red keys on piano roll. - notes_on: Vec, - /// Write input to sequence. - recording: bool, /// Sequence selector - sequence: usize, + sequence: usize, /// Map: tick -> MIDI events at tick - sequences: Vec, - /// Don't delete when recording. - overdub: bool, + sequences: Vec, + /// Red keys on piano roll. + notes_on: Vec, + /// Play sequence to output. - playing: bool, - /// JACK MIDI output port that will be created. - output_port: Port, - /// Port name patterns to connect to. - output_connect: Vec, + playing: TransportState, + /// Play input through output. + monitoring: bool, + /// Write input to sequence. + recording: bool, + /// Don't delete when recording. + overdub: bool, /// Display mode mode: SequencerView, @@ -71,130 +68,101 @@ enum SequencerView { impl Sequencer { pub fn new (name: &str, timebase: &Arc) -> Usually> { let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; + let transport = client.transport(); + let state = transport.query_state()?; DynamicDevice::new(render, handle, Self::process, Self { - name: name.into(), - transport: client.transport(), - timebase: timebase.clone(), - steps: 64, - resolution: 8, + name: name.into(), + input_port: client.register_port("in", MidiIn::default())?, + output_port: client.register_port("out", MidiOut::default())?, - input_port: client.register_port("in", MidiIn::default())?, - input_connect: vec!["nanoKEY Studio * (capture): *".into()], - monitoring: true, - notes_on: vec![false;128], - recording: false, - overdub: true, - sequence: 0, - sequences: vec![std::collections::BTreeMap::new();8], - playing: true, - output_port: client.register_port("out", MidiOut::default())?, - output_connect: vec![], + timebase: timebase.clone(), + steps: 64, + resolution: 8, + sequence: 0, + sequences: vec![std::collections::BTreeMap::new();8], + notes_on: vec![false;128], - mode: SequencerView::Horizontal, - note_axis: (36, 68), - note_cursor: 0, - time_axis: (0, 64), - time_cursor: 0, + playing: TransportState::Starting, + monitoring: true, + recording: true, + overdub: true, + transport, + + mode: SequencerView::Horizontal, + note_axis: (36, 68), + note_cursor: 0, + time_axis: (0, 64), + time_cursor: 0, }).activate(client) } fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let transport = self.transport.query().unwrap(); - process_out(self, scope, &transport); - process_in(self, scope, &transport); - Control::Continue - } -} - -impl Ports for Sequencer { - fn audio_ins (&self) -> Usually> { - Ok(vec![]) - } - fn audio_outs (&self) -> Usually> { - Ok(vec![]) - } - fn midi_ins (&self) -> Usually> { - Ok(vec![self.input_port.short_name()?]) - } - fn midi_outs (&self) -> Usually> { - Ok(vec![self.output_port.short_name()?]) - } - fn connect (&mut self, connect: bool, source: &str, target: &str) {} -} - -fn process_in (s: &mut Sequencer, scope: &ProcessScope, transport: &::jack::TransportStatePosition) { - if !s.recording { - return - } - let pos = &transport.pos; - let usecs = s.timebase.frame_to_usec(pos.frame() as usize); - let steps = usecs / s.timebase.usec_per_step(s.resolution as usize); - let step = steps % s.steps; - let tick = step * s.timebase.ppq() / s.resolution; - let mut sequence = &mut s.sequences[s.sequence]; - let mut writer = s.output_port.writer(scope); - for event in s.input_port.iter(scope) { - if s.monitoring { - writer.write(&event).expect(&format!("{event:?}")) + self.playing = transport.state; + let pos = &transport.pos; + let usecs = self.timebase.frame_to_usec(pos.frame() as usize); + let steps = usecs / self.timebase.usec_per_step(self.resolution as usize); + let step = steps % self.steps; + let tick = step * self.timebase.ppq() / self.resolution; + let mut sequence = &mut self.sequences[self.sequence]; + let mut writer = self.output_port.writer(scope); + if self.playing == TransportState::Rolling { + let frame = transport.pos.frame() as usize; + let ticks = self.timebase.frames_to_ticks( + frame, + frame + scope.n_frames() as usize, + self.timebase.fpb() as usize * self.steps / self.resolution + ); + for (time, tick) in ticks.iter() { + if let Some(events) = sequence.get(&(*tick as u32)) { + for message in events.iter() { + let mut buf = vec![]; + ::midly::live::LiveEvent::Midi { + channel: 0.into(), + message: *message, + }.write(&mut buf).unwrap(); + let midi = ::jack::RawMidi { + time: *time as u32, + bytes: &buf + }; + writer.write(&midi).expect(&format!("{midi:?}")); + //panic!("{midi:?}"); + } + } + } } - if s.recording { - match midly::live::LiveEvent::parse(event.bytes).unwrap() { - midly::live::LiveEvent::Midi { channel: _, message } => match message { - midly::MidiMessage::NoteOn { key, vel: _ } => { - s.notes_on[key.as_int() as usize] = true; - let contains = sequence.contains_key(&(tick as u32)); - if contains { - sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); - } else { - sequence.insert(tick as u32, vec![message.clone()]); - } - }, - midly::MidiMessage::NoteOff { key, vel: _ } => { - s.notes_on[key.as_int() as usize] = false; - let contains = sequence.contains_key(&(tick as u32)); - if contains { - sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); - } else { - sequence.insert(tick as u32, vec![message.clone()]); - } + for event in self.input_port.iter(scope) { + if self.monitoring { + writer.write(&event).expect(&format!("{event:?}")) + } + if self.recording { + match midly::live::LiveEvent::parse(event.bytes).unwrap() { + midly::live::LiveEvent::Midi { channel: _, message } => match message { + midly::MidiMessage::NoteOn { key, vel: _ } => { + self.notes_on[key.as_int() as usize] = true; + let contains = sequence.contains_key(&(tick as u32)); + if contains { + sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); + } else { + sequence.insert(tick as u32, vec![message.clone()]); + } + }, + midly::MidiMessage::NoteOff { key, vel: _ } => { + self.notes_on[key.as_int() as usize] = false; + let contains = sequence.contains_key(&(tick as u32)); + if contains { + sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); + } else { + sequence.insert(tick as u32, vec![message.clone()]); + } + }, + _ => {} }, _ => {} - }, - _ => {} - } - } - } -} - -fn process_out (s: &mut Sequencer, scope: &ProcessScope, transport: &::jack::TransportStatePosition) { - if !s.playing { - return - } - if transport.state != ::jack::TransportState::Rolling { - return - } - let frame = transport.pos.frame() as usize; - let ticks = s.timebase.frames_to_ticks( - frame, - frame + scope.n_frames() as usize, - s.timebase.fpb() as usize * s.steps / s.resolution - ); - let mut writer = s.output_port.writer(scope); - for (time, tick) in ticks.iter() { - if let Some(events) = s.sequences[s.sequence].get(&(*tick as u32)) { - for message in events.iter() { - let mut buf = vec![]; - ::midly::live::LiveEvent::Midi { - channel: 1.into(), - message: *message, - }.write(&mut buf).unwrap(); - let midi = ::jack::RawMidi { - time: *time as u32, - bytes: &buf - }; - writer.write(&midi).expect(&format!("{midi:?}")) + } } } + Control::Continue } } @@ -243,11 +211,15 @@ fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usu let style = Style::default().gray(); let timer = format!("{rep}.{step:02} / {reps}.{steps}"); buf.set_string(x + width - 2 - timer.len() as u16, y + 1, &timer, style.bold().not_dim()); - if s.playing { - buf.set_string(x + 2, y + 1, &format!("▶ PLAYING"), style.not_dim().white().bold()); - } else { - buf.set_string(x + 2, y + 1, &format!("⏹ STOPPED"), style.dim().bold()); - } + buf.set_string(x + 2, y + 1, &match s.playing { + TransportState::Rolling => format!("▶ PLAYING"), + TransportState::Starting => format!("READY ..."), + TransportState::Stopped => format!("⏹ STOPPED") + }, match s.playing { + TransportState::Rolling => style.dim().bold(), + TransportState::Starting => style.not_dim().bold(), + TransportState::Stopped => style.not_dim().white().bold() + }); buf.set_string(x, y + 2, format!("├{}┤", "-".repeat((area.width - 2).into())), style.dim()); //buf.set_string(x + 2, y + 2, //&format!("▶ PLAY"), if s.playing { @@ -282,10 +254,9 @@ fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let style = Style::default().gray(); for i in 0..8 { buf.set_string(x + 2, y + 3 + i*2, &format!("▶ {}", &s.name), if i as usize == s.sequence { - if s.playing { - style.white().bold() - } else { - style.not_dim().bold() + match s.playing { + TransportState::Rolling => style.white().bold(), + _ => style.not_dim().bold() } } else { style.dim() @@ -458,17 +429,23 @@ impl SequencerView { } } fn stop_and_rewind (s: &mut Sequencer) -> Usually { - s.playing = false; + s.transport.stop()?; + s.transport.locate(0)?; + s.playing = TransportState::Stopped; Ok(true) } fn toggle_play (s: &mut Sequencer) -> Usually { - s.playing = !s.playing; - if s.playing { - s.transport.start()?; - } else { - s.transport.stop()?; - s.transport.locate(0)?; - } + s.playing = match s.playing { + TransportState::Stopped => { + s.transport.start()?; + TransportState::Starting + }, + _ => { + s.transport.stop()?; + s.transport.locate(0)?; + TransportState::Stopped + }, + }; Ok(true) } fn toggle_record (s: &mut Sequencer) -> Usually { @@ -525,3 +502,12 @@ fn quantize_prev (s: &mut Sequencer) -> Usually { Ok(()) } } + +impl DevicePorts for Sequencer { + fn midi_ins (&self) -> Usually> { + Ok(vec!["in".into()]) + } + fn midi_outs (&self) -> Usually> { + Ok(vec!["out".into()]) + } +} diff --git a/src/device/sequencer/vertical.rs b/src/device/sequencer/vertical.rs index 86c4df9c..0d3fad50 100644 --- a/src/device/sequencer/vertical.rs +++ b/src/device/sequencer/vertical.rs @@ -14,7 +14,7 @@ pub fn draw_vertical ( let height = (s.time_axis.1-s.time_axis.0)/2; footer(s, buf, x, y, height); playhead(s, buf, x, y); - Ok(Rect { x, y, width: area.width, height: height + 1 }) + Ok(Rect { x, y, width: area.width, height: height + 3 }) } fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) { diff --git a/src/main.rs b/src/main.rs index 33391181..5a312c60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,16 +12,14 @@ pub mod draw; pub mod config; pub mod layout; pub mod time; -pub mod port; -use crate::device::*; -use crate::layout::*; +use crate::prelude::*; fn main () -> Result<(), Box> { let _cli = cli::Cli::parse(); let xdg = microxdg::XdgApp::new("dawdle")?; crate::config::create_dirs(&xdg)?; - let transport = Transport::new("Transport")?; + let transport = crate::device::Transport::new("Transport")?; let timebase = transport.state.lock().unwrap().timebase(); crate::device::run(Chain::new("Chain#0000", vec![ Box::new(Sequencer::new("Phrase#000", &timebase)?), diff --git a/src/port.rs b/src/port.rs deleted file mode 100644 index 336e3497..00000000 --- a/src/port.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::prelude::*; - -pub trait Ports { - fn audio_ins (&self) -> Usually>; - fn audio_outs (&self) -> Usually>; - fn midi_ins (&self) -> Usually>; - fn midi_outs (&self) -> Usually>; - fn connect (&mut self, connect: bool, source: &str, target: &str); - fn connect_all (&mut self, connections: &[(bool, &str, &str)]) { - for (connect, source, target) in connections.iter() { - self.connect(*connect, source, target) - } - } -} diff --git a/src/prelude.rs b/src/prelude.rs index d9351ea2..a1feb9a0 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,7 +3,6 @@ pub type Usually = Result>; pub use crate::draw::*; pub use crate::device::*; pub use crate::time::*; -pub use crate::port::*; pub use crate::layout::*; pub use std::error::Error;