diff --git a/shell.nix b/shell.nix index 0c77854c..c811f617 100644 --- a/shell.nix +++ b/shell.nix @@ -16,5 +16,7 @@ # for ChowKick.lv2: freetype libgcc.lib + # for Panagement + xorg.libX11 ]); } diff --git a/src/core/mod.rs b/src/core/mod.rs index b55650b9..6932aa65 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -12,13 +12,8 @@ pub use std::thread::{spawn, JoinHandle}; pub use std::time::Duration; pub use std::collections::BTreeMap; pub use std::sync::{ - Arc, - Mutex, MutexGuard, - atomic::{ - Ordering, - AtomicBool, - AtomicUsize - }, + Arc, Mutex, MutexGuard, + atomic::{Ordering, AtomicBool, AtomicUsize}, mpsc::{self, channel, Sender, Receiver} }; @@ -33,21 +28,8 @@ pub use ::crossterm::{ }, }; -pub use ::ratatui::{ - prelude::{ - Buffer, Rect, Style, Color, CrosstermBackend, Layout, Stylize, Direction, - Line, Constraint - }, - widgets::{Widget, WidgetRef}, - //style::Stylize, -}; - -pub use ::midly::{ - MidiMessage, - live::LiveEvent, - num::u7 -}; - +pub use ::ratatui::prelude::*; +pub use ::midly::{MidiMessage, live::LiveEvent, num::u7}; pub use crate::{key, keymap}; /// Run a device as the root of the app. diff --git a/src/core/render.rs b/src/core/render.rs index 5560a6d3..e4dc0f98 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -28,13 +28,13 @@ impl Render for Box { } } -impl WidgetRef for &dyn Render { +impl ratatui::widgets::WidgetRef for &dyn Render { fn render_ref (&self, area: Rect, buf: &mut Buffer) { Render::render(*self, buf, area).expect("Failed to render device."); } } -impl WidgetRef for dyn Render { +impl ratatui::widgets::WidgetRef for dyn Render { fn render_ref (&self, area: Rect, buf: &mut Buffer) { Render::render(self, buf, area).expect("Failed to render device."); } diff --git a/src/core/time.rs b/src/core/time.rs index e65ddbab..234ace3b 100644 --- a/src/core/time.rs +++ b/src/core/time.rs @@ -2,11 +2,11 @@ use crate::core::*; pub struct Timebase { /// Frames per second - pub rate: AtomicUsize, + pub rate: AtomicUsize, /// Beats per minute - pub tempo: AtomicUsize, + pub bpm: AtomicUsize, /// Ticks per beat - pub ppq: AtomicUsize, + pub ppq: AtomicUsize, } enum QuantizeMode { @@ -35,14 +35,26 @@ impl Timebase { #[inline] pub fn rate (&self) -> usize { self.rate.load(Ordering::Relaxed) } - #[inline] pub fn tempo (&self) -> usize { - self.tempo.load(Ordering::Relaxed) + #[inline] pub fn bpm (&self) -> usize { + self.bpm.load(Ordering::Relaxed) } #[inline] pub fn ppq (&self) -> usize { self.ppq.load(Ordering::Relaxed) } + #[inline] pub fn pulse_to_frame (&self, pulses: usize) -> usize { + let beat = self.bpm() / 60; + let quaver = beat / 4; + let pulse = quaver / self.ppq(); + pulse * pulses + } + #[inline] pub fn frame_to_pulse (&self, frames: usize) -> usize { + let beat = self.bpm() / 60; + let quaver = beat / 4; + let pulse = quaver / self.ppq(); + frames / pulse + } #[inline] fn beats_per_second (&self) -> f64 { - self.tempo() as f64 / 60000.0 + self.bpm() as f64 / 60000.0 } #[inline] fn frames_per_second (&self) -> usize { self.rate() @@ -72,7 +84,7 @@ impl Timebase { self.usec_per_beat() * beats_per_bar } #[inline] pub fn usec_per_beat (&self) -> usize { - 60_000_000_000 / self.tempo() + 60_000_000_000 / self.bpm() } #[inline] pub fn usec_per_step (&self, divisor: usize) -> usize { self.usec_per_beat() / divisor @@ -93,9 +105,9 @@ impl Timebase { #[inline] pub fn note_to_frame (&self, note: &NoteDuration) -> usize { self.usec_to_frame(self.note_to_usec(note)) } - pub fn frames_to_ticks (&self, start: usize, end: usize, fpl: usize) -> Vec<(usize, usize)> { - let start_frame = start % fpl; - let end_frame = end % fpl; + pub fn frames_to_ticks (&self, start: usize, end: usize, quant: usize) -> Vec<(usize, usize)> { + let start_frame = start % quant; + let end_frame = end % quant; let fpt = self.frames_per_tick(); let mut ticks = vec![]; let mut add_frame = |frame: f64|{ @@ -115,7 +127,7 @@ impl Timebase { loop { add_frame(frame as f64); frame = frame + 1; - if frame >= fpl { + if frame >= quant { frame = 0; loop { add_frame(frame as f64); diff --git a/src/device/chain/plugin.rs b/src/device/chain/plugin.rs index 24bfba76..3bf27523 100644 --- a/src/device/chain/plugin.rs +++ b/src/device/chain/plugin.rs @@ -1,6 +1,6 @@ use crate::core::*; -mod lv2; +mod lv2; pub use lv2::*; mod vst2; mod vst3; @@ -8,7 +8,6 @@ pub struct Plugin { name: String, path: Option, plugin: Option, - offset: usize, selected: usize, mapping: bool, midi_ins: Vec>, @@ -18,12 +17,7 @@ pub struct Plugin { } enum PluginKind { - LV2 { - world: ::livi::World, - features: Arc<::livi::Features>, - port_list: Vec<::livi::Port>, - instance: ::livi::Instance, - }, + LV2(LV2Plugin), VST2 { instance: ::vst::host::PluginInstance }, @@ -34,57 +28,66 @@ const HELM: &'static str = "file:///nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-h impl Plugin { /// Load a LV2 plugin. - pub fn lv2 (name: &str, path: &str, ports: &[usize;4]) -> Usually> { - let plugin = Self::new(name, ports)?; - let mut state = plugin.state(); - state.plugin = Some(self::lv2::plug(path)?); + pub fn lv2 (name: &str, path: &str) -> Usually> { + let host = Self::new(name)?; + let plugin = LV2Plugin::new(path)?; + let mut state = host.state(); + let client = host.client.as_ref().unwrap().as_client(); + let (midi_ins, midi_outs, audio_ins, audio_outs) = ( + plugin.plugin.port_counts().atom_sequence_inputs, + plugin.plugin.port_counts().atom_sequence_outputs, + plugin.plugin.port_counts().audio_inputs, + plugin.plugin.port_counts().audio_outputs, + ); + state.midi_ins = { + let mut ports = vec![]; + for i in 0..midi_ins { + ports.push(client.register_port(&format!("midi-in-{i}"), MidiIn::default())?) + } + ports + }; + state.midi_outs = { + let mut ports = vec![]; + for i in 0..midi_outs { + ports.push(client.register_port(&format!("midi-out-{i}"), MidiOut::default())?) + } + ports + }; + state.audio_ins = { + let mut ports = vec![]; + for i in 0..audio_ins { + ports.push(client.register_port(&format!("audio-in-{i}"), AudioIn::default())?) + } + ports + }; + state.audio_outs = { + let mut ports = vec![]; + for i in 0..audio_outs { + ports.push(client.register_port(&format!("audio-out-{i}"), AudioOut::default())?) + } + ports + }; + state.plugin = Some(PluginKind::LV2(plugin)); state.path = Some(String::from(path)); std::mem::drop(state); - Ok(plugin) + Ok(host) } - pub fn new (name: &str, ports: &[usize;4]) -> Usually> { - let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; - let [midi_ins, midi_outs, audio_ins, audio_outs] = ports; + pub fn new (name: &str) -> Usually> { DynamicDevice::new(render, handle, Self::process, Self { - name: name.into(), - path: None, - plugin: None, - offset: 0, - selected: 0, - mapping: false, - midi_ins: { - let mut ports = vec![]; - for i in 0..*midi_ins { - ports.push(client.register_port(&format!("midi-in-{i}"), MidiIn::default())?) - } - ports - }, - midi_outs: { - let mut ports = vec![]; - for i in 0..*midi_outs { - ports.push(client.register_port(&format!("midi-out-{i}"), MidiOut::default())?) - } - ports - }, - audio_ins: { - let mut ports = vec![]; - for i in 0..*audio_ins { - ports.push(client.register_port(&format!("audio-in-{i}"), AudioIn::default())?) - } - ports - }, - audio_outs: { - let mut ports = vec![]; - for i in 0..*audio_outs { - ports.push(client.register_port(&format!("audio-out-{i}"), AudioOut::default())?) - } - ports - }, - }).activate(client) + name: name.into(), + path: None, + plugin: None, + selected: 0, + mapping: false, + midi_ins: vec![], + midi_outs: vec![], + audio_ins: vec![], + audio_outs: vec![], + }).activate(Client::new(name, ClientOptions::NO_START_SERVER)?.0) } pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { match self.plugin.as_mut() { - Some(PluginKind::LV2 { features, ref mut instance, .. }) => { + Some(PluginKind::LV2(LV2Plugin { features, ref mut instance, .. })) => { let urid = features.midi_urid(); let mut inputs = vec![]; for port in self.midi_ins.iter() { @@ -171,7 +174,7 @@ pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect) let Rect { x, y, height, .. } = area; let mut width = 40u16; match &state.plugin { - Some(PluginKind::LV2 { port_list, instance, .. }) => { + Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => { let start = state.selected.saturating_sub((height as usize / 2).saturating_sub(1)); let end = start + height as usize - 2; //draw_box(buf, Rect { x, y, width, height }); @@ -218,7 +221,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually { s.selected = s.selected - 1 } else { s.selected = match &s.plugin { - Some(PluginKind::LV2 { port_list, .. }) => port_list.len() - 1, + Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, _ => 0 } } @@ -229,7 +232,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually { |s: &mut Plugin|{ s.selected = s.selected + 1; match &s.plugin { - Some(PluginKind::LV2 { port_list, .. }) => { + Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => { if s.selected >= port_list.len() { s.selected = 0; } @@ -242,7 +245,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually { [Char(','), NONE, "decrement", "decrement value", |s: &mut Plugin|{ match s.plugin.as_mut() { - Some(PluginKind::LV2 { port_list, ref mut instance, .. }) => { + Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { let index = port_list[s.selected].index; if let Some(value) = instance.control_input(index) { instance.set_control_input(index, value - 0.01); @@ -256,7 +259,7 @@ pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually { [Char('.'), NONE, "increment", "increment value", |s: &mut Plugin|{ match s.plugin.as_mut() { - Some(PluginKind::LV2 { port_list, ref mut instance, .. }) => { + Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { let index = port_list[s.selected].index; if let Some(value) = instance.control_input(index) { instance.set_control_input(index, value + 0.01); diff --git a/src/device/chain/plugin/lv2.rs b/src/device/chain/plugin/lv2.rs index ffb02270..063c44a7 100644 --- a/src/device/chain/plugin/lv2.rs +++ b/src/device/chain/plugin/lv2.rs @@ -1,28 +1,41 @@ use crate::core::*; -use super::*; -pub fn plug (uri: &str) -> Usually { - let world = ::livi::World::with_load_bundle(&uri); - let features = world.build_features(::livi::FeaturesBuilder { - min_block_length: 1, - max_block_length: 65536, - }); - let mut plugin = None; - for p in world.iter_plugins() { - plugin = Some(p); - break - } - let plugin = plugin.unwrap(); - let mut port_list = vec![]; - for port in plugin.ports() { - port_list.push(port); - } - Ok(PluginKind::LV2 { - instance: unsafe { - plugin.instantiate(features.clone(), 48000.0).expect("boop") - }, - port_list, - features, - world, - }) +pub struct LV2Plugin { + pub world: ::livi::World, + pub instance: ::livi::Instance, + pub plugin: ::livi::Plugin, + pub features: Arc<::livi::Features>, + pub port_list: Vec<::livi::Port>, +} + +impl LV2Plugin { + pub fn new (uri: &str) -> Usually { + // Get 1st plugin at URI + let world = ::livi::World::with_load_bundle(&uri); + let features = ::livi::FeaturesBuilder { min_block_length: 1, max_block_length: 65536 }; + let features = world.build_features(features); + let mut plugin = None; + for p in world.iter_plugins() { + plugin = Some(p); + break + } + let plugin = plugin.unwrap(); + + // Instantiate + Ok(Self { + world, + instance: unsafe { + plugin.instantiate(features.clone(), 48000.0).expect("boop") + }, + port_list: { + let mut port_list = vec![]; + for port in plugin.ports() { + port_list.push(port); + } + port_list + }, + plugin, + features, + }) + } } diff --git a/src/device/launcher/grid.rs b/src/device/launcher/grid.rs index 9622e868..6a42b045 100644 --- a/src/device/launcher/grid.rs +++ b/src/device/launcher/grid.rs @@ -1,22 +1,16 @@ use crate::core::*; use super::*; -pub struct LauncherGridView<'a> { +pub struct LauncherGrid<'a> { state: &'a Launcher, buf: &'a mut Buffer, area: Rect, focused: bool, - separator: String } -impl<'a> LauncherGridView<'a> { +impl<'a> LauncherGrid<'a> { pub fn new (state: &'a Launcher, buf: &'a mut Buffer, area: Rect, focused: bool) -> Self { - let separator = format!("├{}┤", "-".repeat((area.width - 2).into())); - Self { state, buf, area, separator, focused } + Self { state, buf, area, focused } } pub fn draw (&mut self) -> Usually { - //self.separator_h(0, false); - //self.separator_h(2, false); - //self.separator_h((self.state.cursor.1 * 2) as u16, true); - //self.separator_h(((self.state.cursor.1 + 1) * 2) as u16, true); self.area.height = self.state.scenes.len() as u16 + 2; let style = Some(Style::default().green().dim()); if self.focused { @@ -24,9 +18,7 @@ impl<'a> LauncherGridView<'a> { lozenge_left(self.buf, x, y, height, style); lozenge_right(self.buf, x + width - 1, y, height, style); } - let columns = self.column_names(); - let mut x = self.area.x; for (i, w) in self.column_widths().iter().enumerate() { if x >= self.area.x + self.area.width { @@ -36,7 +28,6 @@ impl<'a> LauncherGridView<'a> { x = x + w; self.separator_v(x, i == self.state.cursor.0); } - let (mut x, y) = (self.area.x, self.area.y); for (i, title) in columns.iter().enumerate() { if x >= self.area.x + self.area.width { @@ -53,17 +44,16 @@ impl<'a> LauncherGridView<'a> { let w = (title.len() as u16).max(10) + 3; x = x + w; } - "+Add track…".blit(self.buf, x + 2, y, Some(Style::default().dim())); Ok(self.area) } fn column_names (&self) -> Vec<&'a str> { - let mut columns = vec![self.state.name.as_str()]; + let mut column_names = vec![self.state.name.as_str()]; for track in self.state.tracks.iter() { - columns.push(track.name.as_str()); + column_names.push(track.name.as_str()); } - columns + column_names } fn column_widths (&self) -> Vec { @@ -150,18 +140,11 @@ impl<'a> LauncherGridView<'a> { } } - fn separator_h (&mut self, y: u16, highlight: bool) { - let style = Some(self.highlight(highlight)); - self.separator.blit(self.buf, self.area.x, self.area.y + y, style); - } - fn separator_v (&mut self, x: u16, highlight: bool) { let style = Some(self.highlight(highlight)); - //"┬".blit(self.buf, x, self.area.y + 0, style); for y in self.area.y+1..self.area.y+self.area.height-1 { "┊".blit(self.buf, x, y, style); } - //"┴".blit(self.buf, x, self.area.y+self.area.height-1, style); } } diff --git a/src/device/launcher/handle.rs b/src/device/launcher/handle.rs index 0974c680..9ebb3849 100644 --- a/src/device/launcher/handle.rs +++ b/src/device/launcher/handle.rs @@ -57,6 +57,10 @@ fn activate (state: &mut Launcher) -> Usually { if let Some(phrase_id) = scene.clips.get(track_id) { track.sequencer.state().sequence = *phrase_id; } + if state.playing == TransportState::Stopped { + state.transport.start()?; + state.playing = TransportState::Starting; + } } else if let Some((scene_id, scene)) = state.scene() { // Launch scene for (track_id, track) in state.tracks.iter().enumerate() { @@ -64,6 +68,10 @@ fn activate (state: &mut Launcher) -> Usually { track.sequencer.state().sequence = *phrase_id; } } + if state.playing == TransportState::Stopped { + state.transport.start()?; + state.playing = TransportState::Starting; + } } else if let Some((track_id, track)) = state.track() { // Rename track? } diff --git a/src/device/launcher/mod.rs b/src/device/launcher/mod.rs index 1407bd72..0f3f2a11 100644 --- a/src/device/launcher/mod.rs +++ b/src/device/launcher/mod.rs @@ -131,10 +131,10 @@ impl Launcher { } } } fn sequencer <'a> (&'a self) -> Option> { - self.track().map(|t|t.1.sequencer.state()) + Some(self.track()?.1.sequencer.state()) } fn chain <'a> (&'a self) -> Option> { - self.track().map(|t|t.1.chain.state()) + Some(self.track()?.1.chain.state()) } fn phrase_id (&self) -> Option { let (track_id, _) = self.track()?; @@ -190,10 +190,10 @@ pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually Usually, frame: usize) { - let tick = (frame as f64 / timebase.frames_per_tick()) as usize; - let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq()); - let (bars, beats) = (beats / 4, beats % 4); - let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1); - timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim())); -} fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, height } = area; let style = Some(Style::default().green().dim()); @@ -239,50 +217,46 @@ fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Us }, _ => {}, }; - if let Some((_, track)) = state.track() { - let frame = state.position; - let timebase = &state.timebase; - let tick = (frame as f64 / timebase.frames_per_tick()) as usize; - let sequencer = track.sequencer.state(); - let zoom = sequencer.resolution; - - let steps = if let Some(_phrase) = sequencer.phrase() { 0 } else { 0 } / 4; // TODO - - crate::device::sequencer::horizontal::timer(buf, x+5, y, - steps, sequencer.steps * zoom, sequencer.time_axis.0, sequencer.time_axis.1 - ); - crate::device::sequencer::horizontal::keys(buf, Rect { x, y: y + 1, width, height }, - sequencer.note_axis.1 - )?; - if let Some(id) = state.phrase_id() { - if let Some(phrase) = sequencer.phrases.get(id) { - crate::device::sequencer::horizontal::lanes(buf, x, y + 1, - &phrase, - sequencer.timebase.ppq() as u32, - sequencer.resolution as u32, - sequencer.time_axis.0 as u32, - sequencer.time_axis.1 as u32, - sequencer.note_axis.0 as u32, - sequencer.note_axis.1 as u32, - ); - } + let track = state.track(); + if track.is_none() { + return Ok(area); + } + let track = track.unwrap().1; + let sequencer = track.sequencer.state(); + crate::device::sequencer::horizontal::timer( + buf, x+5, y, + sequencer.time_axis.0, + sequencer.time_axis.0 + area.width, + 0 + ); + crate::device::sequencer::horizontal::keys( + buf, Rect { x, y: y + 1, width, height }, + sequencer.note_axis.1 + )?; + if let Some(id) = state.phrase_id() { + if let Some(phrase) = sequencer.phrases.get(id) { + crate::device::sequencer::horizontal::lanes( + buf, x, y + 1, + &phrase, + sequencer.timebase.ppq() as u32, + sequencer.time_zoom as u32, + sequencer.time_axis.0 as u32, + sequencer.time_axis.0 as u32 + area.width as u32, + sequencer.note_axis.0 as u32, + sequencer.note_axis.1 as u32, + ); } - let cursor_style = match view { - LauncherView::Sequencer => Style::default().green().not_dim(), - _ => Style::default().green().dim(), - }; - crate::device::sequencer::horizontal::cursor(buf, x, y + 1, cursor_style, - sequencer.time_cursor, - sequencer.note_cursor - ); } + let cursor_style = match view { + LauncherView::Sequencer => Style::default().green().not_dim(), + _ => Style::default().green().dim(), + }; + crate::device::sequencer::horizontal::cursor(buf, x, y + 1, cursor_style, + sequencer.time_cursor, + sequencer.note_cursor + ); Ok(area) } -fn draw_highlight (buf: &mut Buffer, highlight: &Option, style: Style) { - if let Some(area) = highlight { - draw_box_styled(buf, *area, Some(style)); - } -} fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { let style = Some(Style::default().green().dim()); match state.view { diff --git a/src/device/sequencer/handle.rs b/src/device/sequencer/handle.rs index a3c156cf..0e2f67b0 100644 --- a/src/device/sequencer/handle.rs +++ b/src/device/sequencer/handle.rs @@ -51,8 +51,8 @@ fn note_add (s: &mut Sequencer) -> Usually { return Ok(false) } 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 start = (step as usize * s.timebase.ppq() / s.time_zoom) as u32; + let end = ((step + 1) as usize * s.timebase.ppq() / s.time_zoom) as u32; let key = u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8); let note_on = MidiMessage::NoteOn { key, vel: 100.into() }; let note_off = MidiMessage::NoteOff { key, vel: 100.into() }; @@ -179,14 +179,14 @@ fn toggle_monitor (s: &mut Sequencer) -> Usually { Ok(true) } fn quantize_next (s: &mut Sequencer) -> Usually { - if s.resolution < 64 { - s.resolution = s.resolution * 2; + if s.time_zoom < 64 { + s.time_zoom = s.time_zoom * 2; } Ok(true) } fn quantize_prev (s: &mut Sequencer) -> Usually { - if s.resolution > 1 { - s.resolution = s.resolution / 2; + if s.time_zoom > 1 { + s.time_zoom = s.time_zoom / 2; } Ok(true) } diff --git a/src/device/sequencer/horizontal.rs b/src/device/sequencer/horizontal.rs index 759705b9..c0e46b8f 100644 --- a/src/device/sequencer/horizontal.rs +++ b/src/device/sequencer/horizontal.rs @@ -10,13 +10,13 @@ pub fn draw ( area.x = area.x + 13; let Rect { x, y, width, .. } = area; keys(buf, area, s.note_axis.1)?; - timer(buf, x + 6, y - 1, beat, s.steps, s.time_axis.0, s.time_axis.1); + timer(buf, x+6, y-1, s.time_axis.0, s.time_axis.1, beat as u16); let height = 32.max(s.note_axis.1 - s.note_axis.0) / 2; if let Some(phrase) = s.phrase() { lanes(buf, x, y, phrase, s.timebase.ppq() as u32, - s.resolution as u32, + s.time_zoom as u32, s.time_axis.0 as u32, s.time_axis.1 as u32, s.note_axis.0 as u32, @@ -36,9 +36,9 @@ pub fn draw ( }) } -pub fn timer (buf: &mut Buffer, x: u16, y: u16, beat: usize, steps: usize, time0: u16, time1: u16) { +pub fn timer (buf: &mut Buffer, x: u16, y: u16, time0: u16, time1: u16, now: u16) { for step in time0..time1 { - buf.set_string(x + step, y, &"-", if beat % steps == step as usize { + buf.set_string(x + step, y, &"-", if step == now { Style::default().yellow().bold().not_dim() } else { Style::default() @@ -123,7 +123,7 @@ pub fn footer (s: &Sequencer, buf: &mut Buffer, mut x: u16, y: u16, width: u16, { for (_, [letter, title, value]) in [ ["S", &format!("ync"), &format!("<4/4>")], - ["Q", &format!("uant"), &format!("<1/{}>", 4 * s.resolution)], + ["Q", &format!("uant"), &format!("<1/{}>", 4 * s.time_zoom)], ["N", &format!("ote"), &format!("{} ({}-{})", s.note_axis.0 + s.note_cursor, s.note_axis.0, diff --git a/src/device/sequencer/mod.rs b/src/device/sequencer/mod.rs index 1bde4073..8985e4bb 100644 --- a/src/device/sequencer/mod.rs +++ b/src/device/sequencer/mod.rs @@ -19,9 +19,6 @@ pub struct Sequencer { pub midi_out: Port, /// Holds info about tempo pub timebase: Arc, - /// Steps in sequence, e.g. 64 16ths = 4 beat loop. - /// FIXME: play start / end / loop in ppm - pub steps: usize, /// Phrase selector pub sequence: Option, /// Map: tick -> MIDI events at tick @@ -42,9 +39,8 @@ pub struct Sequencer { pub note_axis: (u16, u16), /// Position of cursor within note range pub note_cursor: u16, - /// Sequencer resolution, e.g. 16 steps per beat. - /// FIXME: grid in ppm will simplify calculations - pub resolution: usize, + /// PPM per display unit + pub time_zoom: usize, /// Range of time steps to display pub time_axis: (u16, u16), /// Position of cursor within time range @@ -68,8 +64,6 @@ impl Sequencer { midi_out: client.register_port("out", MidiOut::default())?, timebase: timebase.clone(), - steps: 16, - resolution: 4, sequence: Some(0), phrases: phrases.unwrap_or(vec![ Phrase::new("Phrase0", 4*timebase.ppq() as u32, None) @@ -85,13 +79,14 @@ impl Sequencer { mode: SequencerView::Horizontal, note_axis: (36, 68), note_cursor: 0, + time_zoom: 24, time_axis: (0, 64), time_cursor: 0, }).activate(client) } pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> { - self.sequence.map(|s|self.phrases.get(s))? + self.phrases.get(self.sequence?) } } @@ -111,7 +106,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let pos = s.transport.query().unwrap().pos; let frame = pos.frame(); let usecs = s.timebase.frame_to_usec(frame as usize); - let ustep = s.timebase.usec_per_step(s.resolution as usize); + let ustep = s.timebase.usec_per_step(s.time_zoom as usize); let steps = usecs / ustep; let header = draw_header(s, buf, area, steps)?; let piano = match s.mode { @@ -141,12 +136,12 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { } pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually { - let Rect { x, y, width, .. } = area; - let rep = beat / s.steps; - let step = beat % s.steps; - let reps = s.steps / s.resolution; - let steps = s.steps % s.resolution; - draw_timer(buf, x + width - 2, y + 1, &format!("{rep}.{step:02} / {reps}.{steps}")); + let Rect { x, y, .. } = area; + //let rep = beat / s.steps; + //let step = beat % s.steps; + //let reps = s.steps / s.time_zoom; + //let steps = s.steps % s.time_zoom; + //draw_timer(buf, x + width - 2, y + 1, &format!("{rep}.{step:02} / {reps}.{steps}")); let style = Style::default().gray(); crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing); let separator = format!("├{}┤", "-".repeat((area.width - 2).into())); diff --git a/src/device/sequencer/phrase.rs b/src/device/sequencer/phrase.rs index 1b30ab69..fb35860e 100644 --- a/src/device/sequencer/phrase.rs +++ b/src/device/sequencer/phrase.rs @@ -14,32 +14,44 @@ impl Phrase { pub fn new (name: &str, length: u32, notes: Option) -> Self { Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) } } + pub fn frames (&self, timebase: &Arc) -> usize { + timebase.pulse_to_frame(self.length as usize) + } + pub fn frame_to_pulse (&self, timebase: &Arc, frame: usize) -> usize { + timebase.frame_to_pulse(frame) % self.length as usize + } /** Write a chunk of MIDI events to an output port. */ pub fn chunk_out ( &self, - output: &mut MIDIChunk, - start: usize, - length: usize, - timebase: &Arc, - steps: usize, - resolution: usize, + output: &mut MIDIChunk, + notes_on: &mut Vec, + timebase: &Arc, + frame0: usize, + frames: usize, ) { - let quant = timebase.frames_per_beat() as usize * steps / resolution; - let ticks = timebase.frames_to_ticks(start, start + length, quant); + let quant = self.frames(timebase); + let ticks = timebase.frames_to_ticks(frame0, frame0 + frames, quant); for (time, tick) in ticks.iter() { - if let Some(events) = self.notes.get(&(*tick as u32)) { - for message in events.iter() { - let mut buf = vec![]; - let channel = 0.into(); - let message = *message; - LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); - let t = *time as usize; - if output[t].is_none() { - output[t] = Some(vec![]); - } - if let Some(Some(frame)) = output.get_mut(t) { - frame.push(buf); - } + let events = self.notes.get(&(*tick as u32)); + if events.is_none() { + continue + } + for message in events.unwrap().iter() { + let mut buf = vec![]; + let channel = 0.into(); + let message = *message; + match message { + MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, + MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, + _ => {} + } + LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); + let t = *time as usize; + if output[t].is_none() { + output[t] = Some(vec![]); + } + if let Some(Some(frame)) = output.get_mut(t) { + frame.push(buf); } } } @@ -47,60 +59,54 @@ impl Phrase { /** React a chunk of MIDI events from an input port. */ pub fn chunk_in ( &mut self, - port: &Port, - scope: &ProcessScope, + input: ::jack::MidiIter, + notes_on: &mut Vec, mut monitor: Option<&mut MIDIChunk>, record: bool, - notes_on: &mut Vec, - tick: u32, + timebase: &Arc, + frame0: usize, ) { - for event in port.iter(scope) { - let msg = LiveEvent::parse(event.bytes).unwrap(); - match msg { - LiveEvent::Midi { message, .. } => match message { - MidiMessage::NoteOn { key, vel: _ } => { - notes_on[key.as_int() as usize] = true; - if let Some(ref mut monitor) = monitor { - let t = event.time as usize; - if monitor[t].is_none() { - monitor[t] = Some(vec![]); - } - if let Some(Some(frame)) = monitor.get_mut(t) { - frame.push(event.bytes.into()) - } + for RawMidi { time, bytes } in input { + let time = time as usize; + let pulse = timebase.frame_to_pulse(frame0 + time) as u32; + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + if let MidiMessage::NoteOn { key, vel: _ } = message { + notes_on[key.as_int() as usize] = true; + if let Some(ref mut monitor) = monitor { + if monitor[time].is_none() { + monitor[time] = Some(vec![]); } - if record { - let contains = self.notes.contains_key(&tick); - if contains { - self.notes.get_mut(&tick).unwrap().push(message.clone()); - } else { - self.notes.insert(tick, vec![message.clone()]); - } + if let Some(Some(frame)) = monitor.get_mut(time) { + frame.push(bytes.into()) } - }, - midly::MidiMessage::NoteOff { key, vel: _ } => { - notes_on[key.as_int() as usize] = false; - if let Some(ref mut monitor) = monitor { - let t = event.time as usize; - if monitor[t].is_none() { - monitor[t] = Some(vec![]); - } - if let Some(Some(frame)) = monitor.get_mut(t) { - frame.push(event.bytes.into()) - } + } + if record { + let contains = self.notes.contains_key(&pulse); + if contains { + self.notes.get_mut(&pulse).unwrap().push(message.clone()); + } else { + self.notes.insert(pulse, vec![message.clone()]); } - if record { - let contains = self.notes.contains_key(&tick); - if contains { - self.notes.get_mut(&tick).unwrap().push(message.clone()); - } else { - self.notes.insert(tick, vec![message.clone()]); - } + } + } else if let midly::MidiMessage::NoteOff { key, vel: _ } = message { + notes_on[key.as_int() as usize] = false; + if let Some(ref mut monitor) = monitor { + if monitor[time].is_none() { + monitor[time] = Some(vec![]); } - }, - _ => {} - }, - _ => {} + if let Some(Some(frame)) = monitor.get_mut(time) { + frame.push(bytes.into()) + } + } + if record { + let contains = self.notes.contains_key(&pulse); + if contains { + self.notes.get_mut(&pulse).unwrap().push(message.clone()); + } else { + self.notes.insert(pulse, vec![message.clone()]); + } + } + } } } } diff --git a/src/device/sequencer/process.rs b/src/device/sequencer/process.rs index 66943acf..5b7af3ae 100644 --- a/src/device/sequencer/process.rs +++ b/src/device/sequencer/process.rs @@ -2,15 +2,15 @@ use crate::core::*; use super::*; pub fn process (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Control { - // Get currently playing sequence + // Get currently playing phrase if state.sequence.is_none() { return Control::Continue } - let sequence = state.phrases.get_mut(state.sequence.unwrap()); - if sequence.is_none() { + let phrase = state.phrases.get_mut(state.sequence.unwrap()); + if phrase.is_none() { return Control::Continue } - let sequence = sequence.unwrap(); + let phrase = phrase.unwrap(); // Prepare output buffer and transport let frame = scope.last_frame_time() as usize;//transport.pos.frame() as usize; let frames = scope.n_frames() as usize; @@ -21,29 +21,28 @@ pub fn process (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Cont all_notes_off(&mut output); } state.playing = transport.state; - // Play from sequence into output buffer + // Play from phrase into output buffer if state.playing == TransportState::Rolling { - sequence.chunk_out( + phrase.chunk_out( &mut output, - frame, - frames, + &mut state.notes_on, &state.timebase, - state.steps, - state.resolution, + frame, + frames ); } - // Play from input to monitor, and record into sequence. - let usecs = state.timebase.frame_to_usec(frame); - let steps = usecs / state.timebase.usec_per_step(state.resolution as usize); - let step = steps % state.steps; - let tick = (step * state.timebase.ppq() / state.resolution) as u32; - sequence.chunk_in( - &state.midi_in, - &scope, + // Play from input to monitor, and record into phrase. + //let usecs = state.timebase.frame_to_usec(frame); + //let steps = usecs / state.timebase.usec_per_step(state.time_zoom as usize); + //let step = steps % state.steps; + //let tick = (step * state.timebase.ppq() / state.time_zoom) as u32; + phrase.chunk_in( + state.midi_in.iter(scope), + &mut state.notes_on, Some(&mut output), state.recording && state.playing == TransportState::Rolling, - &mut state.notes_on, - tick + &state.timebase, + frame, ); // Write to port from output buffer // (containing notes from sequence and/or monitor) diff --git a/src/device/sequencer/vertical.rs b/src/device/sequencer/vertical.rs index 6f485b6d..67ed884c 100644 --- a/src/device/sequencer/vertical.rs +++ b/src/device/sequencer/vertical.rs @@ -32,16 +32,16 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) { let y = y - time0 + step / 2; let step = step as usize; //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); - if step % s.resolution == 0 { + if step % s.time_zoom == 0 { buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); } for k in note0..note1 { let key = ::midly::num::u7::from_int_lossy(k as u8); if step % 2 == 0 { let (a, b, c) = ( - (step + 0) as u32 * ppq / s.resolution as u32, - (step + 1) as u32 * ppq / s.resolution as u32, - (step + 2) as u32 * ppq / s.resolution as u32, + (step + 0) as u32 * ppq / s.time_zoom as u32, + (step + 1) as u32 * ppq / s.time_zoom as u32, + (step + 2) as u32 * ppq / s.time_zoom as u32, ); let (character, style) = match ( contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b), @@ -55,7 +55,7 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) { buf.set_string(x + 5 + k - note0, y, character, style); } } - if beat % s.steps == step as usize { + if beat == step as usize { buf.set_string(x + 4, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); for key in note0..note1 { let _color = if s.notes_on[key as usize] { @@ -71,7 +71,7 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) { pub fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, height: u16) { buf.set_string(x + 2, y + height + 1, format!( "Q 1/{} | N {} ({}-{}) | T {} ({}-{})", - 4 * s.resolution, + 4 * s.time_zoom, s.note_axis.0 + s.note_cursor, s.note_axis.0, s.note_axis.1 - 1, @@ -102,11 +102,11 @@ pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) { } let mut color = KEY_STYLE[key as usize % 12]; let mut is_on = s.notes_on[key as usize]; - let step = beat % s.steps; + let step = beat; let (a, b, c) = ( - (step + 0) as u32 * ppq / s.resolution as u32, - (step + 1) as u32 * ppq / s.resolution as u32, - (step + 2) as u32 * ppq / s.resolution as u32, + (step + 0) as u32 * ppq / s.time_zoom as u32, + (step + 1) as u32 * ppq / s.time_zoom as u32, + (step + 2) as u32 * ppq / s.time_zoom as u32, ); let key = ::midly::num::u7::from(key as u8); is_on = is_on || contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b); diff --git a/src/device/transport.rs b/src/device/transport.rs index 9cd2de10..fb9d8d06 100644 --- a/src/device/transport.rs +++ b/src/device/transport.rs @@ -1,6 +1,13 @@ use crate::core::*; use crate::layout::*; +pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc, frame: usize) { + let tick = (frame as f64 / timebase.frames_per_tick()) as usize; + let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq()); + let (bars, beats) = (beats / 4, beats % 4); + let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1); + timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim())); +} pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) { let style = Style::default().gray(); match state { @@ -34,10 +41,25 @@ pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) { Style::default().bold().dim() })) } +pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) { + let style = Style::default().not_dim(); + "BPM" + .blit(buf, x, y, Some(style)); + format!("{:03}.{:03}", bpm / 1000, bpm % 1000) + .blit(buf, x + 4, y, Some(style.bold())); + "SYNC" + .blit(buf, x + 13, y, Some(style)); + "4/4" + .blit(buf, x + 18, y, Some(style.bold())); + "QUANT" + .blit(buf, x + 23, y, Some(style)); + "1/16" + .blit(buf, x + 29, y, Some(style.bold())); +} pub struct Transport { name: String, - /// Holds info about tempo + /// Holds info about bpm timebase: Arc, transport: ::jack::Transport, @@ -51,7 +73,7 @@ impl Transport { name: name.into(), timebase: Arc::new(Timebase { rate: AtomicUsize::new(client.sample_rate()), - tempo: AtomicUsize::new(113000), + bpm: AtomicUsize::new(113000), ppq: AtomicUsize::new(96), }), transport @@ -86,6 +108,10 @@ pub fn process (_: &mut Transport, _: &Client, _: &ProcessScope) -> Control { Control::Continue } +pub fn handle (_: &mut Transport, _: &AppEvent) -> Usually { + Ok(false) +} + pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) -> Usually { @@ -102,8 +128,8 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) "REC", "DUB", &format!("BPM {:03}.{:03}", - state.timebase.tempo() / 1000, - state.timebase.tempo() % 1000, + state.timebase.bpm() / 1000, + state.timebase.bpm() % 1000, ), "0.0+00", "0:00.000", @@ -114,163 +140,4 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) x = x + 2; } Ok(area) - //buf.set_string(area.x, area.y + 5, "Witty Gerbil - Sha Na Na", label.bold()); - //&format!(" │ 00:00.00 / 00:00.00"), style); - //draw_leaf(buf, area, 1, 0, "REC"); - //draw_leaf(buf, area, 1, 5, "DUB"); - //draw_leaf(buf, area, 1, 10, "STOP"); - //draw_leaf(buf, area, 1, 16, "PLAY/PAUSE"); - //draw_leaf(buf, area, 1, 28, "START"); - //draw_leaf(buf, area, 1, 35, "Project: Witty Gerbil - Sha Na Na "); - //draw_leaf(buf, area, 3, 0, &format!("BPM {:03}.{:03}", - //state.bpm as u64, - //((state.bpm % 1.0) * 1000.0) as u64 - //)); - //let position = state.transport.as_ref().map(|t|t.query()); - //if let Some(Ok(position)) = position { - //let rate = position.pos.frame_rate().unwrap(); - //let frame = position.pos.frame(); - //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() - ////); - //draw_leaf(buf, area, 3, 13, &format!("BBT {bars:04}:{beat:02}.{:02}", - //(beat_sub * 16.0) as u32 - //)); - //let time = frame as f64 / rate as f64; - //let seconds = time % 60.0; - //let msec = seconds % 1.0; - //let minutes = (time / 60.0) % 60.0; - //let hours = time / 3600.0; - //draw_leaf(buf, area, 3, 29, &format!("Time {:02}:{:02}:{:02}.{:03}", - //hours as u64, - //minutes as u64, - //seconds as u64, - //(msec * 1000.0) as u64 - //)); - //draw_leaf(buf, area, 3, 48, &format!("Rate {:>6}Hz", rate)); - //draw_leaf(buf, area, 3, 63, &format!("Frame {:>10}", frame)); - //} - //let bbt = position.pos.bbt().map(|mut bbt|*bbt - //.with_bpm(state.bpm) - //.with_timesig(state.timesig.0, state.timesig.1)); - //.unwrap(); - //Line::from("Project:").render(area, buf); - //if let Ok(position) = state.transport.query() { - //let frame = position.pos.frame(); - //let rate = position.pos.frame_rate(); - //let bbt = position.pos.bbt().map(|mut bbt|*bbt - //.with_bpm(state.bpm) - //.with_timesig(state.timesig.0, state.timesig.1)); - //Line::from("Frame:").render(area.clone().offset(Offset { x: 0, y: 1 }), buf); - //Line::from(format!("{frame}")).render(area.clone().offset(Offset { x: 0, y: 2 }), buf); - //Line::from("Rate:").render(area.clone().offset(Offset { x: 10, y: 1 }), buf); - //Line::from(match rate { - //Some(rate) => format!("{rate}Hz"), - //None => String::from("(none)"), - //}).render(area.clone().offset(Offset { x: 10, y: 2 }), buf); - //Line::from("Time:").render(area.clone().offset(Offset { x: 20, y: 1 }), buf); - //Line::from(match rate { - //Some(rate) => format!("{:.03}", frame as f64 / rate as f64), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 20, y: 2 }), buf); - //Line::from("BPM:").render(area.clone().offset(Offset { x: 30, y: 1 }), buf); - //Line::from(match bbt { - //Some(bbt) => format!("{:.01}", bbt.bpm), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 30, y: 2 }), buf); - //Line::from("TimeSig:").render(area.clone().offset(Offset { x: 40, y: 1 }), buf); - //Line::from(match bbt { - //Some(bbt) => format!("{}/{}", bbt.sig_num, bbt.sig_denom), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 40, y: 2 }), buf); - //Line::from("Beat:").render(area.clone().offset(Offset { x: 50, y: 1 }), buf); - //Line::from(match bbt { - //Some(bbt) => format!("{}.{}.{}", bbt.bar, bbt.beat, bbt.tick), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 50, y: 2 }), buf); - //} } - -//pub fn render ( - //state: &mut Transport, - //stdout: &mut Stdout, - //mut offset: (u16, u16) -//) -> Result<(), Box> { - //let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); - //stdout.queue(move_to( 1, 0))?.queue( - //Print("Project: ") - //)?.queue(move_to(10, 0))?.queue( - //PrintStyledContent(state.title.clone().white().bold()) - //)?; - - //if let Ok(position) = state.transport.query() { - //let frame = position.pos.frame(); - //let rate = position.pos.frame_rate(); - //let bbt = position.pos.bbt().map(|mut bbt|*bbt - //.with_bpm(state.bpm) - //.with_timesig(state.timesig.0, state.timesig.1)); - //stdout - //.queue(move_to( 1, 1))?.queue(Print("Frame: "))? - //.queue(move_to( 1, 2))?.queue( - //PrintStyledContent( - //format!("{frame}").white().bold(), - //))? - //.queue(move_to(11, 1))?.queue(Print("Rate: "))? - //.queue(move_to(11, 2))?.queue( - //PrintStyledContent(match rate { - //Some(rate) => format!("{rate}Hz"), - //None => String::from("(none)"), - //}.white().bold()) - //)? - //.queue(move_to(20, 1))?.queue(Print("Time: "))? - //.queue(move_to(20, 2))?.queue( - //PrintStyledContent(match rate { - //Some(rate) => format!("{:.03}", frame as f64 / rate as f64), - //None => String::from("(none)") - //}.white().bold()) - //)? - //.queue(move_to(30, 1))?.queue(Print("BPM: "))? - //.queue(move_to(30, 2))?.queue( - //PrintStyledContent(match bbt { - //Some(bbt) => format!("{:.01}", bbt.bpm), - //None => String::from("(none)") - //}.white().bold()) - //)? - //.queue(move_to(39, 1))?.queue(Print("Timesig: "))? - //.queue(move_to(39, 2))?.queue( - //PrintStyledContent(match bbt { - //Some(bbt) => format!("{}/{}", bbt.sig_num, bbt.sig_denom), - //None => String::from("(none)") - //}.white().bold()) - //)? - //.queue(move_to(50, 1))?.queue(Print("Beat: "))? - //.queue(move_to(50, 2))?.queue( - //PrintStyledContent(match bbt { - //Some(bbt) => format!("{}.{}.{}", bbt.bar, bbt.beat, bbt.tick), - //None => String::from("(none)") - //}.white().bold()) - //)?; - //} - //Ok(()) -//} - -pub fn handle (_: &mut Transport, _: &AppEvent) -> Usually { - Ok(false) -} - -pub const ACTIONS: [(&'static str, &'static str);4] = [ - ("?", "Toggle help"), - ("(Shift-)Tab", "Switch pane"), - ("Arrows", "Navigate"), - ("(Shift-)Space", "⯈ Play/pause"), -]; diff --git a/src/main.rs b/src/main.rs index b88ba66d..12aa14e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,9 +44,9 @@ fn main () -> Result<(), Box> { let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"]; let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?; let timebase = Arc::new(Timebase { - rate: AtomicUsize::new(client.sample_rate()), - tempo: AtomicUsize::new(150000), - ppq: AtomicUsize::new(96), + rate: AtomicUsize::new(client.sample_rate()), + bpm: AtomicUsize::new(99000), + ppq: AtomicUsize::new(96), }); let ppq = timebase.ppq() as u32; macro_rules! play { @@ -54,84 +54,67 @@ fn main () -> Result<(), Box> { ( $t1 * ppq / 4, vec![ $($msg),* ] ) } } + macro_rules! phrase { + ($($t:expr => $msg:expr),* $(,)?) => {{ + let mut phrase = BTreeMap::new(); + $(phrase.insert($t, vec![]);)* + $(phrase.get_mut(&$t).unwrap().push($msg);)* + phrase + }} + } let app = Launcher::new("Launcher#0", &timebase, Some(vec![ Track::new("Drums", &timebase, Some(vec![ - //Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(), + Sampler::new("Sampler", Some(BTreeMap::from([ sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"), sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"), sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"), ])))?.boxed(), + + Plugin::lv2("Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2")?.boxed(), + ]), Some(vec![ - Phrase::new("KSH", ppq * 4, Some(BTreeMap::from([ - play!(0 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ] ), - play!(1 => [ - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - play!(2 => [ - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - play!(4 => [ - MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - play!(6 => [ - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - play!(8 => [ - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - play!(10 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - play!(12 => [ - MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - play!(14 => [ - MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - ]), - ]))), - Phrase::new("4K", ppq * 4, Some(BTreeMap::from([ - play!(0 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - ] ), - play!(4 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - ]), - play!(8 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - ]), - play!(12 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - ]), - ]))), - Phrase::new("KS", ppq * 4, Some(BTreeMap::from([ - play!(0 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - ] ), - play!(4 => [ - MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - ]), - play!(10 => [ - MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - ]), - play!(12 => [ - MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - ]), - ]))), + + Phrase::new("KSH", ppq * 4, Some(phrase! { + 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 01 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 02 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + 04 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 08 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 11 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + 12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + 14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() } + })), + + Phrase::new("4K", ppq * 4, Some(phrase! { + 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + })), + + Phrase::new("KS", ppq * 4, Some(phrase! { + 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + 10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + })), + ]))?, Track::new("Odin2", &timebase, Some(vec![ - //Plugin::lv2("Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(), - Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(), + + Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?.boxed(), + ]), Some(vec![ Phrase::new("E G A Bb", ppq * 4, Some(BTreeMap::from([ play!(2 => [ @@ -197,6 +180,7 @@ fn main () -> Result<(), Box> { ]))) ]))?, + //Plugin::lv2("Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(), //Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(), //Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(), //Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(),