diff --git a/src/device.rs b/src/device.rs index ce15474d..ffdb21e7 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,25 +1,27 @@ use crate::prelude::*; -mod transport; mod chain; -mod sequencer; -mod sampler; -mod mixer; -mod looper; -mod plugin; mod launcher; +mod looper; +mod mixer; +mod plugin; +mod sampler; +mod sequencer; +mod track; +mod transport; -pub use self::transport::Transport; pub use self::chain::Chain; -pub use self::sequencer::Sequencer; -pub use self::sampler::Sampler; -pub use self::mixer::Mixer; -pub use self::looper::Looper; -pub use self::plugin::Plugin; pub use self::launcher::Launcher; +pub use self::looper::Looper; +pub use self::mixer::Mixer; +pub use self::plugin::Plugin; +pub use self::sampler::Sampler; +pub use self::sequencer::Sequencer; +pub use self::track::Track; +pub use self::transport::Transport; use crossterm::event; -use ::jack::{AudioIn, AudioOut, MidiIn, MidiOut, Port, PortSpec, Client}; +use ::jack::{Port, PortSpec, Client}; pub trait Device: Render + Handle + PortList + Send + Sync { fn boxed (self) -> Box where Self: Sized + 'static { diff --git a/src/device/launcher.rs b/src/device/launcher.rs index 8dbe909c..591abd21 100644 --- a/src/device/launcher.rs +++ b/src/device/launcher.rs @@ -9,8 +9,7 @@ pub struct Launcher { overdub: bool, position: usize, cursor: (usize, usize), - pub tracks: Vec>, - pub chains: Vec>, + pub tracks: Vec, scenes: Vec, show_help: bool, view: LauncherView, @@ -70,7 +69,7 @@ impl Launcher { )?; } } - let chain: &DynamicDevice = &state.chains[i]; + let chain: &DynamicDevice = &state.tracks[i].chain; for midi_out in sequencer.midi_outs()?.iter() { for midi_in in chain.midi_ins()?.iter() { client.connect_ports_by_name(&midi_out, &midi_in)?; @@ -78,7 +77,7 @@ impl Launcher { } for (j, port) in chain.audio_outs()?.iter().enumerate() { for audio_out in audio_outs[j % audio_outs.len()].iter() { - client.connect_ports_by_name(&port, &audio_out); + client.connect_ports_by_name(&port, &audio_out)?; } } } @@ -114,43 +113,18 @@ impl Launcher { Scene::new(&"Scene#08", &[None, None, None, None]), ], tracks: vec![ - Sequencer::new("Drum", &timebase)?, - Sequencer::new("Bass", &timebase)?, - Sequencer::new("Pads", &timebase)?, - Sequencer::new("Lead", &timebase)?, - ], - chains: vec![ - Chain::new("Chain#0000", vec![ - Plugin::lv2( - "Plugin#000", - "file:///home/user/.lv2/ChowKick.lv2", - &[1, 1, 0, 2] - )?.boxed(), + Track::new("Samples", &timebase, vec![ + Sampler::new("Samples")?.boxed(), ])?, - - Chain::new("Chain#0000", vec![ - Plugin::lv2( - "Plugin#001", - "file:///home/user/.lv2/Helm.lv2", - &[1, 0, 0, 2] - )?.boxed(), - ])?, - - Chain::new("Chain#0000", vec![ - Plugin::lv2( - "Plugin#002", - "file:///home/user/.lv2/Helm.lv2", - &[1, 0, 0, 2] - )?.boxed(), - ])?, - - Chain::new("Chain#0000", vec![ - Plugin::lv2( - "Plugin#003", - "file:///home/user/.lv2/Odin2.lv2", - &[1, 0, 0, 2] - )?.boxed(), + Track::new("Kick", &timebase, vec![ + //Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(), ])?, + //Track::new("Bass", &timebase, vec![ + //Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(), + //])?, + //Track::new("Pads", &timebase, vec![ + //Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(), + //])?, ], timebase, show_help: true @@ -196,6 +170,20 @@ impl Launcher { self.cursor.1 + 1 } } + + fn active_track (&self) -> Option<&Track> { + if self.cursor.0 >= 1 { + self.tracks.get(self.cursor.0 as usize - 1 as usize) + } else { + None + } + } + fn active_sequencer <'a> (&'a self) -> Option> { + self.active_track().map(|t|t.sequencer.state()) + } + fn active_chain <'a> (&'a self) -> Option> { + self.active_track().map(|t|t.chain.state()) + } } impl PortList for Launcher {} pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control { @@ -228,8 +216,15 @@ pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually draw_crossings(state, buf, x + w - 2, y + 1); draw_box(buf, Rect { x, y: y + 1, width, height: height - 1 }); let style = Some(Style::default().green().dim()); - let chain = &*state.chains[0].state(); - let (_, plugins) = crate::device::chain::draw_as_row(chain, buf, chain_area, style)?; + let chain = state.active_chain(); + let plugins = if let Some(chain) = &chain { + let (_, plugins) = crate::device::chain::draw_as_row( + &*chain, buf, chain_area, style + )?; + plugins + } else { + vec![] + }; if state.view == LauncherView::Tracks { draw_box_styled(buf, track_area, style); @@ -242,10 +237,14 @@ pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually if state.view == LauncherView::Chains { draw_box_styled(buf, Rect { height: 18, ..chain_area }, style); } - draw_highlight(state, buf, &Some(plugins[chain.focus]), match state.view { - LauncherView::Chains => Style::default().green().not_dim(), - _ => Style::default().green().dim() - }); + if let Some(chain) = &chain { + if let Some(plugin) = plugins.get(chain.focus) { + draw_highlight(state, buf, &Some(*plugin), match state.view { + LauncherView::Chains => Style::default().green().not_dim(), + _ => Style::default().green().dim() + }); + } + } if state.view == LauncherView::Sequencer { draw_box_styled(buf, seq_area, style); @@ -303,7 +302,7 @@ fn draw_tracks ( let mut w = 15; let mut highlight = None; for (i, track) in state.tracks.iter().enumerate() { - let track = track.state(); + let track = track.sequencer.state(); draw_crossings(state, buf, x + w - 2, y); let width = draw_track(state, buf, x + w, y, i as u16, &track); if i + 1 == state.col() { @@ -333,7 +332,7 @@ fn draw_track ( } else { Style::default() }; - for (scene_index, scene) in state.scenes.iter().enumerate() { + for (_, scene) in state.scenes.iter().enumerate() { if let Some(Some(sequence_index)) = scene.clips.get(i as usize) { if let Some(sequence) = track.sequences.get(*sequence_index) { width = width.max(sequence.name.len() as u16 + 5); @@ -376,18 +375,18 @@ fn draw_crossings (state: &Launcher, buf: &mut Buffer, x: u16, y: u16) { fn draw_sequencer ( state: &Launcher, buf: &mut Buffer, x: u16, y: u16, width: u16, height: u16 ) -> Usually<()> { - if let Some(sequencer) = state.tracks.get(state.col().saturating_sub(1)) { + if let Some(track) = state.tracks.get(state.col().saturating_sub(1)) { //crate::device::sequencer::horizontal::footer( //&sequencer.state(), buf, 0, y, width, 0 //); crate::device::sequencer::horizontal::keys( - &sequencer.state(), buf, Rect { x, y: y + 1, width, height } + &track.sequencer.state(), buf, Rect { x, y: y + 1, width, height } )?; crate::device::sequencer::horizontal::lanes( - &sequencer.state(), buf, x, y + 1, width, + &track.sequencer.state(), buf, x, y + 1, width, ); crate::device::sequencer::horizontal::cursor( - &sequencer.state(), buf, x, y + 1, match state.view { + &track.sequencer.state(), buf, x, y + 1, match state.view { LauncherView::Sequencer => Style::default().green().not_dim(), _ => Style::default().green().dim(), } @@ -410,8 +409,8 @@ pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually { }, LauncherView::Sequencer => { let i = state.col().saturating_sub(1); - if let Some(sequencer) = state.tracks.get_mut(i) { - crate::device::sequencer::handle(&mut *sequencer.state(), event)? + if let Some(track) = state.tracks.get_mut(i) { + crate::device::sequencer::handle(&mut *track.sequencer.state(), event)? } else { true } @@ -486,7 +485,7 @@ fn clip_next (state: &mut Launcher) -> Usually { let scene = &mut state.scenes[scene_id]; scene.clips[clip_id] = match scene.clips[clip_id] { None => Some(0), - Some(i) => if i >= state.tracks[clip_id].state().sequences.len().saturating_sub(1) { + Some(i) => if i >= state.tracks[clip_id].sequencer.state().sequences.len().saturating_sub(1) { None } else { Some(i + 1) @@ -501,7 +500,7 @@ fn clip_prev (state: &mut Launcher) -> Usually { let clip_id = state.cursor.0 - 1; let scene = &mut state.scenes[scene_id]; scene.clips[clip_id] = match scene.clips[clip_id] { - None => Some(state.tracks[clip_id].state().sequences.len().saturating_sub(1)), + None => Some(state.tracks[clip_id].sequencer.state().sequences.len().saturating_sub(1)), Some(i) => if i == 0 { None } else { @@ -530,22 +529,22 @@ fn play_start (_: &mut Launcher) -> Usually { } fn record_toggle (s: &mut Launcher) -> Usually { s.recording = !s.recording; - for sequencer in s.tracks.iter() { - sequencer.state().recording = s.recording; + for track in s.tracks.iter() { + track.sequencer.state().recording = s.recording; } Ok(true) } fn overdub_toggle (s: &mut Launcher) -> Usually { s.overdub = !s.overdub; - for sequencer in s.tracks.iter() { - sequencer.state().overdub = s.overdub; + for track in s.tracks.iter() { + track.sequencer.state().overdub = s.overdub; } Ok(true) } fn monitor_toggle (s: &mut Launcher) -> Usually { s.monitoring = !s.monitoring; - for sequencer in s.tracks.iter() { - sequencer.state().monitoring = s.monitoring; + for track in s.tracks.iter() { + track.sequencer.state().monitoring = s.monitoring; } Ok(true) } diff --git a/src/device/sampler.rs b/src/device/sampler.rs index 8dcd25e1..f80c421e 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -36,7 +36,7 @@ impl Sampler { ]; let samples = Arc::new(Mutex::new(samples)); let input = client.register_port("trigger", ::jack::MidiIn::default())?; - Ok(DynamicDevice::new(render, handle, Self::process, Self { + DynamicDevice::new(render, handle, Self::process, Self { name: name.into(), input, selected_sample: 0, @@ -45,7 +45,7 @@ impl Sampler { midi_ins: vec![], audio_ins: vec![], audio_outs: vec![], - })) + }).activate(client) } pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { @@ -80,7 +80,7 @@ impl Sampler { Control::Continue } - fn load_sample (&mut self, path: &str) {} + fn load_sample (&mut self, _path: &str) {} } impl PortList for Sampler { @@ -128,29 +128,32 @@ impl Sample { } -pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, .. }: Rect) +pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, .. }: Rect) -> Usually { + let width = 40; let style = Style::default().gray(); draw_box(buf, Rect { x, y: y, width: 40, height: 12 }); - buf.set_string(x + 1, y + 1, &format!(" {} │", state.name), style.white().bold()); - buf.set_string(x + 0, y + 2, &format!("├--------------------------------------┤"), style.dim()); - buf.set_string(x + 2, y + 3, &format!("C4 Sample#000"), style.bold()); - buf.set_string(x + 2, y + 4, &format!(" {:.03}s", - 100000.0/44100.0), style); - buf.set_string(x + 2, y + 5, &format!("C#4 Sample#001"), style.bold()); - buf.set_string(x + 2, y + 6, &format!(" {:.03}-{:.03}s", - 50000.0/44100.0, 100000.0/44100.0), style); - buf.set_string(x + 2, y + 7, &format!("D4 Sample#002"), style.bold()); - buf.set_string(x + 2, y + 8, &format!(" {:.03}-{:.03}/{:.03}s", - 0.0, 50000.0/44100.0, 100000.0/44100.0), style); - buf.set_string(x + 2, y + 9, &format!("D#4 Sample#003"), style.bold()); - buf.set_string(x + 2, y + 10, &format!(" {:.03}-[{:.03}-{:.03}]/{:.03}s ", - 10000.0/44100.0, 25000.0/44100.0, 50000.0/44100.0, 100000.0/44100.0), style); - //buf.set_string(x + 1, y + 7 + 6, &format!(" Inputs... │ Outputs... "), style.dim()); - //render_table(state, stdout, offset)?; - //render_meters(state, stdout, offset)?; - Ok(Rect { x, y, width: 40, height: 11 }) + let separator = format!("├{}┤", "-".repeat((width - 2).into())); + separator.blit(buf, x, y + 2, Some(style.dim())); + format!(" {} │", state.name).blit(buf, x+1, y+1, Some(style.white().bold())); + for (i, (note, name, cut)) in [ + ("C4", "Sample#000", format!("{:.03}s", + 100000.0/44100.0)), + ("C#4", "Sample#001", format!("{:.03}-{:.03}s", + 50000.0/44100.0, 100000.0/44100.0)), + ("D4", "Sample#002", format!("{:.03}-{:.03}/{:.03}s", + 0.0, 50000.0/44100.0, 100000.0/44100.0)), + ("D#4", "Sample#003", format!("{:.03}-[{:.03}-{:.03}]/{:.03}s ", + 10000.0/44100.0, 25000.0/44100.0, 50000.0/44100.0, 100000.0/44100.0)), + ].iter().enumerate() { + let i = i as u16; + format!("{note:3} {name}") + .blit(buf, x+2, y+3+i*2, Some(style.bold())); + format!(" {cut}") + .blit(buf, x+2, y+4+i*2, Some(style)); + } + Ok(Rect { x, y, width, height: 11 }) } //fn render_table ( diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index 4eb51aa7..b95791d8 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -73,29 +73,23 @@ enum SequencerView { } impl Sequencer { - pub fn new (name: &str, timebase: &Arc) -> Usually> { + pub fn new ( + name: &str, + timebase: &Arc, + sequences: Option>, + ) -> 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(), - midi_in: client.register_port("in", MidiIn::default())?, - midi_out: client.register_port("out", MidiOut::default())?, + midi_in: client.register_port("in", MidiIn::default())?, + midi_out: client.register_port("out", MidiOut::default())?, timebase: timebase.clone(), steps: 64, resolution: 4, sequence: 0, - sequences: vec![ - Sequence::new(&"Phrase#01"), - Sequence::new(&"Phrase#02"), - Sequence::new(&"Phrase#03"), - Sequence::new(&"Phrase#04"), - Sequence::new(&"Phrase#05"), - Sequence::new(&"Phrase#06"), - Sequence::new(&"Phrase#07"), - Sequence::new(&"Phrase#08"), - ], + sequences: sequences.unwrap_or(vec![Sequence::new("")]), notes_on: vec![false;128], playing: TransportState::Starting, @@ -115,14 +109,18 @@ impl Sequencer { pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { // Update time - let mut sequence = &mut self.sequences[self.sequence].notes; + let sequence = self.sequences.get_mut(self.sequence); + if sequence.is_none() { + return Control::Continue + } + let sequence = &mut sequence.unwrap().notes; let transport = self.transport.query().unwrap(); - 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; + 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; // Prepare output buffer let frames = scope.n_frames() as usize; @@ -231,7 +229,7 @@ impl PortList for Sequencer { } } -fn render (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { +fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, .. } = area; let (time0, time1) = s.time_axis; let (note0, note1) = s.note_axis; @@ -281,7 +279,7 @@ pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> draw_rec(buf, x + 13, y + 1, s.recording); draw_dub(buf, x + 20, y + 1, s.overdub); draw_mon(buf, x + 27, y + 1, s.monitoring); - let clips = draw_clips(s, buf, area)?; + let _ = draw_clips(s, buf, area)?; Ok(Rect { x, y, width: area.width, height: 3 }) } pub fn draw_timer ( @@ -401,15 +399,13 @@ fn nop (_: &mut Sequencer) -> Usually { Ok(false) } fn note_add (s: &mut Sequencer) -> Usually { - let pos = s.transport.query().unwrap().pos; - let usecs = s.timebase.frame_to_usec(pos.frame() as usize); let step = (s.time_axis.0 + s.time_cursor) as u32; let start = (step as usize * s.timebase.ppq() / s.resolution) as u32; let end = ((step + 1) as usize * s.timebase.ppq() / s.resolution) as u32; let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8); let note_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() }; let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() }; - let mut sequence = &mut s.sequences[s.sequence].notes; + let sequence = &mut s.sequences[s.sequence].notes; if sequence.contains_key(&start) { sequence.get_mut(&start).unwrap().push(note_on.clone()); } else { diff --git a/src/device/track.rs b/src/device/track.rs new file mode 100644 index 00000000..2a2ddb1b --- /dev/null +++ b/src/device/track.rs @@ -0,0 +1,27 @@ +use crate::prelude::*; +pub struct Track { + pub name: String, + pub sequencer: DynamicDevice, + pub chain: DynamicDevice, +} +impl Track { + pub fn new ( + name: &str, + tempo: &Arc, + devices: Vec> + ) -> Usually { + Ok(Self { + name: name.to_string(), + sequencer: Sequencer::new(&name, tempo, None)?, + chain: Chain::new(&name, devices)?, + }) + } +} +impl PortList for Track { + fn midi_ins (&self) -> Usually> { + self.sequencer.midi_ins() + } + fn audio_outs (&self) -> Usually> { + self.chain.audio_outs() + } +} diff --git a/src/main.rs b/src/main.rs index 5739c28a..9c106899 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ fn main () -> Result<(), Box> { let _cli = cli::Cli::parse(); let xdg = microxdg::XdgApp::new("tek")?; crate::config::create_dirs(&xdg)?; + //run(Sampler::new("Sampler#000")?) run(Launcher::new_with_controller( "Launcher#0", ".*nanoKEY.*",