diff --git a/Cargo.lock b/Cargo.lock index e29eb72f..d38d0b0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,6 +212,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -759,6 +768,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r8brain-rs" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d8a95a5235085537051f80f1cdf704e41b1a1c749c067d381412c62da88b44" +dependencies = [ + "cmake", +] + [[package]] name = "ratatui" version = "0.26.3" @@ -1021,6 +1039,7 @@ dependencies = [ "microxdg", "midly", "music-math", + "r8brain-rs", "ratatui", "rlsf", "toml", diff --git a/Cargo.toml b/Cargo.toml index e7f3b855..f5b03080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,4 @@ music-math = "0.1.1" atomic_float = "1.0.0" fraction = "0.15.3" rlsf = "0.2.1" +r8brain-rs = "0.3.5" diff --git a/src/control.rs b/src/control.rs index eb49fa36..530daef6 100644 --- a/src/control.rs +++ b/src/control.rs @@ -5,7 +5,7 @@ pub mod sampler; pub use self::focus::*; -use crate::{core::*, handle, App}; +use crate::{core::*, handle, App, AppSection}; handle!(App |self, e| { if let Some(ref mut modal) = self.modal { @@ -14,27 +14,81 @@ handle!(App |self, e| { return Ok(true) }; } - handle_keymap(self, e, KEYMAP) + let handle_focused = |state: &mut Self|match state.section { + AppSection::Grid => + handle_keymap(state, e, KEYMAP_GRID), + AppSection::Sequencer => + handle_keymap(state, e, KEYMAP_SEQUENCER), + AppSection::Chain => + handle_keymap(state, e, KEYMAP_CHAIN), + }; + Ok(if self.entered { + handle_focused(self)? + || handle_keymap(self, e, KEYMAP)? + || handle_keymap(self, e, KEYMAP_FOCUS)? + } else { + handle_keymap(self, e, KEYMAP)? + || handle_keymap(self, e, KEYMAP_FOCUS)? + || handle_focused(self)? + }) +}); + +const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { + [Tab, NONE, "focus_next", "focus next area", focus_next], + [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], + [Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{ + app.entered = false; + Ok(true) + }], + [Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{ + app.entered = true; + Ok(true) + }], }); const KEYMAP: &'static [KeyBinding] = keymap!(App { - [Char('+'), NONE, "quant_inc", "Zoom in", |app: &mut App| { + + [F(1), NONE, "help_toggle", "toggle help", |_: &mut App| {Ok(true)}], + + [Up, NONE, "focus_prev", "focus previous area", focus_prev], + [Down, NONE, "focus_next", "focus next area", focus_next], + + [Char(' '), NONE, "play_toggle", "play or pause", |app: &mut App| { + app.toggle_play()?; + Ok(true) + }], + [Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| { + app.track_mut().map(|t|t.1.toggle_record()); + Ok(true) + }], + [Char('o'), NONE, "overdub_toggle", "toggle overdub", |app: &mut App| { + app.track_mut().map(|t|t.1.toggle_overdub()); + Ok(true) + }], + [Char('m'), NONE, "monitor_toggle", "toggle monitor", |app: &mut App| { + app.track_mut().map(|t|t.1.toggle_monitor()); + Ok(true) + }], + + [Char('+'), NONE, "quant_inc", "Quantize coarser", |app: &mut App| { app.quant = next_note_length(app.quant); Ok(true) }], - [Char('_'), NONE, "quant_dec", "Zoom out", |app: &mut App| { + [Char('_'), NONE, "quant_dec", "Quantize finer", |app: &mut App| { app.quant = prev_note_length(app.quant); Ok(true) }], - [Char('='), NONE, "zoom_in", "Zoom in", |app: &mut App| { + + [Char('='), NONE, "zoom_in", "Zoom in", |app: &mut App| { app.time_zoom = prev_note_length(app.time_zoom); Ok(true) }], - [Char('-'), NONE, "zoom_out", "Zoom out", |app: &mut App| { + [Char('-'), NONE, "zoom_out", "Zoom out", |app: &mut App| { app.time_zoom = next_note_length(app.time_zoom); Ok(true) }], - [Char('x'), NONE, "extend", "double the current clip", |app: &mut App| { + + [Char('x'), NONE, "extend", "double the current clip", |app: &mut App| { if let Some(phrase) = app.phrase_mut() { let mut notes = BTreeMap::new(); for (time, events) in phrase.notes.iter() { @@ -45,6 +99,7 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { } Ok(true) }], + [Char('l'), NONE, "loop_toggle", "toggle looping", |_app: &mut App| { // TODO: This toggles the loop flag for the clip under the cursor. Ok(true) @@ -65,10 +120,8 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { // TODO: This moves the loop end to the next quant. Ok(true) }], - [Char(' '), NONE, "play_toggle", "play or pause", |app: &mut App| { - app.toggle_play()?; - Ok(true) - }], + + [Char('a'), CONTROL, "scene_add", "add a new scene", |app: &mut App| { app.add_scene(None)?; Ok(true) @@ -77,160 +130,131 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { app.add_track(None)?; Ok(true) }], - [Char('`'), NONE, "mode_switch", "switch the display mode", |app: &mut App| { - match app.section { - 0 => {app.grid_mode = !app.grid_mode; Ok(true)}, - 1 => {app.chain_mode = !app.chain_mode; Ok(true)}, - 2 => {app.seq_mode = !app.seq_mode; Ok(true)}, - _ => Ok(false) - } - }], - [F(1), NONE, "help_toggle", "toggle help", |_: &mut App| {Ok(true)}], - [Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| { - app.track_mut().map(|t|t.1.toggle_record()); - Ok(true) - }], - [Char('d'), NONE, "overdub_toggle", "toggle overdub", |app: &mut App| { - app.track_mut().map(|t|t.1.toggle_overdub()); - Ok(true) - }], - [Char('m'), NONE, "monitor_toggle", "toggle input monitoring", |app: &mut App| { - app.track_mut().map(|t|t.1.toggle_monitor()); - Ok(true) - }], -//fn toggle_record (s: &mut Launcher) -> Usually { - //s.sequencer_mut().map(|s|s.recording = !s.recording); - //Ok(true) -//} -//fn toggle_overdub (s: &mut Launcher) -> Usually { - //s.sequencer_mut().map(|s|s.overdub = !s.overdub); - //Ok(true) -//} + [Char('.'), NONE, "cursor_inc", "increment value", increment], + [Char(','), NONE, "cursor_dec", "decrement value", decrement], + [Delete, CONTROL, "cursor_delete", "delete item at cursor", delete], + [Char('d'), CONTROL, "cursor_duplicate", "duplicate scene or track", duplicate], -//fn toggle_monitor (s: &mut Launcher) -> Usually { - //s.sequencer_mut().map(|s|s.monitoring = !s.monitoring); - //Ok(true) -//} - [Tab, NONE, "focus_next", "focus next area", focus_next], - [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], - [Esc, NONE, "focus_out", "unfocus", escape], - [Up, NONE, "cursor_up", "move cursor up", |app: &mut App| { - if app.entered { - match app.section { - 0 => match app.grid_mode { - false => {app.scene_cursor = app.scene_cursor.saturating_sub(1); Ok(true)}, - true => {app.track_cursor = app.track_cursor.saturating_sub(1); Ok(true)}, - }, - 2 => { app.note_cursor = app.note_cursor.saturating_sub(1); Ok(true) } - _ => Ok(false) - } - } else { - focus_next(app) - } - }], - [Down, NONE, "cursor_down", "move cursor down", |app: &mut App| { - if app.entered { - match app.section { - 0 => match app.grid_mode { - false => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); Ok(true)}, - true => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); Ok(true)}, - }, - 2 => { app.note_cursor = app.note_cursor + 1; Ok(true) } - _ => Ok(false) - } - } else { - focus_prev(app) - } - }], - [Left, NONE, "cursor_left", "move cursor left", |app: &mut App| { - match app.section { - 0 => match app.grid_mode { - false => {app.track_cursor = app.track_cursor.saturating_sub(1); Ok(true)}, - true => {app.scene_cursor = app.scene_cursor.saturating_sub(1); Ok(true)}, - }, - 1 => { - if let Some((_, track)) = app.track_mut() { - track.device = track.device.saturating_sub(1); - Ok(true) - } else { - Ok(false) - } - }, - 2 => { - if app.entered { - app.time_cursor = app.time_cursor.saturating_sub(1); - } else { - app.time_start = app.time_start.saturating_sub(1); - } - Ok(true) - }, - _ => Ok(false) - } - }], - [Right, NONE, "cursor_right", "move cursor right", |app: &mut App| { - match app.section { - 0 => match app.grid_mode { - false => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); Ok(true)}, - true => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); Ok(true)}, - }, - 1 => { - if let Some((_, track)) = app.track_mut() { - track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); - Ok(true) - } else { - Ok(false) - } - }, - 2 => { - if app.entered { - app.time_cursor = app.time_cursor + 1; - } else { - app.time_start = app.time_start + 1; - } - Ok(true) - }, - _ => Ok(false) - } - }], - [Char('.'), NONE, "cursor_inc", "increment value", increment], - [Char(','), NONE, "cursor_dec", "decrement value", decrement], - [Delete, CONTROL, "cursor_delete", "delete track", delete], - [Char('d'), CONTROL, "cursor_duplicate", "duplicate scene or track", duplicate], - [Enter, NONE, "cursor_activate", "activate item at cursor", enter], //[Char('r'), CONTROL, "rename", "rename current element", rename], +// [Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind], +}); + +const KEYMAP_GRID: &'static [KeyBinding] = keymap!(App { + [Up, NONE, "grid_cursor_up", "move cursor up", |app: &mut App| Ok(match app.grid_mode { + false => {app.scene_cursor = app.scene_cursor.saturating_sub(1); true}, + true => {app.track_cursor = app.track_cursor.saturating_sub(1); true}, + })], + [Down, NONE, "grid_cursor_down", "move cursor down", |app: &mut App| Ok(match app.grid_mode { + false => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); true}, + true => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); true}, + })], + [Left, NONE, "grid_cursor_left", "move cursor left", |app: &mut App| Ok(match app.grid_mode { + false => {app.track_cursor = app.track_cursor.saturating_sub(1); true}, + true => {app.scene_cursor = app.scene_cursor.saturating_sub(1); true}, + })], + [Right, NONE, "grid_cursor_right", "move cursor right", |app: &mut App| Ok(match app.grid_mode { + false => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); true}, + true => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); true}, + })], + [Enter, NONE, "grid_activate", "activate item at cursor", |app: &mut App| Ok( + if app.scene_cursor == 0 { + false + } else { + let scene = &app.scenes[app.scene_cursor - 1]; + if app.track_cursor == 0 { + for (i, track) in app.tracks.iter_mut().enumerate() { + track.sequence = scene.clips[i]; + track.reset = true; + } + } else { + let track = &mut app.tracks[app.track_cursor - 1]; + track.sequence = scene.clips[app.track_cursor - 1]; + track.reset = true; + }; + true + } + )], + [Char('.'), NONE, "grid_increment", "increment item at cursor", clip_next], + [Char(','), NONE, "grid_decrement", "decrement item at cursor", clip_prev], + [Char('`'), NONE, "grid_mode_switch", "switch the display mode", |app: &mut App| { + app.grid_mode = !app.seq_mode; + Ok(true) + }], +}); + +const KEYMAP_SEQUENCER: &'static [KeyBinding] = keymap!(App { + [Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| { + app.note_cursor = app.note_cursor.saturating_sub(1); + Ok(true) + }], + [Down, NONE, "seq_cursor_down", "move cursor up", |app: &mut App| { + app.note_cursor = app.note_cursor + 1; + Ok(true) + }], + [Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| { + if app.entered { + app.time_cursor = app.time_cursor.saturating_sub(1); + } else { + app.time_start = app.time_start.saturating_sub(1); + } + Ok(true) + }], + [Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| { + if app.entered { + app.time_cursor = app.time_cursor + 1; + } else { + app.time_start = app.time_start + 1; + } + Ok(true) + }], + [Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| { + app.seq_mode = !app.seq_mode; + Ok(true) + }], // [Char('a'), NONE, "note_add", "Add note", note_add], // [Char('z'), NONE, "note_del", "Delete note", note_del], // [CapsLock, NONE, "advance", "Toggle auto advance", nop], // [Char('w'), NONE, "rest", "Advance by note duration", nop], -// [Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind], -// [Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next], -// [Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev], +}); + +const KEYMAP_CHAIN: &'static [KeyBinding] = keymap!(App { + [Up, NONE, "chain_cursor_up", "move cursor up", |_: &mut App| Ok(true)], + [Down, NONE, "chain_cursor_down", "move cursor down", |_: &mut App| Ok(true)], + [Left, NONE, "chain_cursor_left", "move cursor left", |app: &mut App| { + if let Some((_, track)) = app.track_mut() { + track.device = track.device.saturating_sub(1); + Ok(true) + } else { + Ok(false) + } + }], + [Right, NONE, "chain_cursor_right", "move cursor right", |app: &mut App| { + if let Some((_, track)) = app.track_mut() { + track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); + Ok(true) + } else { + Ok(false) + } + }], + [Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| { + app.chain_mode = !app.seq_mode; + Ok(true) + }], }); fn focus_next (app: &mut App) -> Usually { - if app.section >= 2 { - app.section = 0; - } else { - app.section = app.section + 1; - } + app.section.next(); Ok(true) } fn focus_prev (app: &mut App) -> Usually { - if app.section == 0 { - app.section = 2; - } else { - app.section = app.section - 1; - } + app.section.prev(); Ok(true) } fn increment (app: &mut App) -> Usually { - match app.section { - 0 => clip_next(app), - _ => Ok(false) - } + Ok(false) } fn clip_next (_: &mut App) -> Usually { Ok(true) } @@ -252,10 +276,7 @@ fn clip_next (_: &mut App) -> Usually { Ok(true) } //} fn decrement (app: &mut App) -> Usually { - match app.section { - 0 => clip_prev(app), - _ => Ok(false) - } + Ok(false) } fn clip_prev (_: &mut App) -> Usually { Ok(true) } @@ -278,107 +299,11 @@ fn clip_prev (_: &mut App) -> Usually { Ok(true) } fn delete (app: &mut App) -> Usually { match app.section { - 0 => delete_track(app), + AppSection::Grid => delete_track(app), _ => Ok(false) } } -fn enter (app: &mut App) -> Usually { - if app.entered { - activate(app) - } else { - app.entered = true; - Ok(true) - } -} - -fn activate (app: &mut App) -> Usually { - Ok(match app.section { - 0 => { - if app.scene_cursor == 0 { - false - } else { - let scene = &app.scenes[app.scene_cursor - 1]; - if app.track_cursor == 0 { - for (i, track) in app.tracks.iter_mut().enumerate() { - track.sequence = scene.clips[i]; - track.reset = true; - } - } else { - let track = &mut app.tracks[app.track_cursor - 1]; - track.sequence = scene.clips[app.track_cursor - 1]; - track.reset = true; - }; - true - } - }, - _ => false - }) -} -//fn activate (_: &mut Launcher) -> Usually { - //unimplemented!(); - ////if let ( - ////Some((_scene_id, scene)), - ////Some((track_id, track)), - ////) = (state.scene_mut(), state.track_mut()) { - ////// Launch clip - ////if let Some(phrase_id) = scene.clips.get(track_id) { - ////track.sequencer.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() { - ////if let Some(phrase_id) = scene.clips.get(track_id) { - ////track.sequencer.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? - ////} - - ////let track = state.active_track().unwrap(); - ////let scene = state.active_scene(); - ////if state.cursor.1 >= 2 { - ////if let Some(Some(index)) = scene.clips.get(state.cursor.1 - 2) { - ////track.enqueue(index) - ////} else { - ////} - ////} - ////if state.cursor.0 >= 1 { - ////let sequencer = state.tracks.get_mut(state.cursor.0 - 1); - ////if state.cursor.1 >= 2 { - ////let scene = state.scenes.get_mut(state.cursor.1 - 2); - ////if let Some(index) = scene.get(state.cursor.0 - 1) { - ////let phrase = sequencer.phrases.get(index); - ////} else { - ////let index = sequencer.phrases.len(); - ////let phrase = Phrase::new(&format!("Phrase#{index}")); - ////sequencer.phrases.push(phrase); - ////scene[state.cursor.0 - 1] = Some(index); - ////} - ////} - ////} - //Ok(true) -//} - - -fn escape (app: &mut App) -> Usually { - if app.entered { - app.entered = false; - Ok(true) - } else { - Ok(false) - } -} - fn delete_track (app: &mut App) -> Usually { if app.tracks.len() > 0 { let track = app.tracks.remove(app.track_cursor); @@ -390,78 +315,3 @@ fn delete_track (app: &mut App) -> Usually { } fn duplicate (_: &mut App) -> Usually { Ok(true) } - -fn rename (_: &mut App) -> Usually { Ok(true) } - -//use crate::{core::*, model::*}; -//use super::focus::*; - -//pub fn handle (state: &mut Chain, event: &AppEvent) -> Usually { - //Ok(handle_focus(state, event, keymap!(Chain { - //[Up, NONE, "focus_up", "focus row above", - //|s: &mut Chain|s.handle_focus(&FocusEvent::Backward)], - //[Down, NONE, "focus_down", "focus row below", - //|s: &mut Chain|s.handle_focus(&FocusEvent::Forward)], - //[Enter, NONE, "focus_down", "focus row below", - //|s: &mut Chain|s.handle_focus(&FocusEvent::Inward)], - //[Esc, NONE, "focus_down", "focus row below", - //|s: &mut Chain|s.handle_focus(&FocusEvent::Outward)], - //}))? || handle_keymap(state, event, keymap!(Chain { - //[Char('a'), NONE, "add_device", "add a device", add_device] - //}))?) -//} - -//fn add_device (state: &mut Chain) -> Usually { - //state.adding = true; - //Ok(true) -//} - -//impl Focus for Chain { - //fn unfocus (&mut self) { - //self.focused = false - //} - //fn focused (&self) -> Option<&Box> { - //match self.focused { - //true => self.items.get(self.focus), - //false => None - //} - //} - //fn focused_mut (&mut self) -> Option<&mut Box> { - //match self.focused { - //true => self.items.get_mut(self.focus), - //false => None - //} - //} - //fn handle_focus (&mut self, event: &FocusEvent) -> Usually { - //Ok(match event { - //FocusEvent::Backward => { - //if self.focus == 0 { - //self.focus = self.items.len(); - //} - //self.focus = self.focus - 1; - //true - //}, - //FocusEvent::Forward => { - //self.focus = self.focus + 1; - //if self.focus >= self.items.len() { - //self.focus = 0; - //} - //true - //}, - //FocusEvent::Inward => { - //self.focused = true; - //self.items[self.focus].handle(&AppEvent::Focus)?; - //true - //}, - //FocusEvent::Outward => { - //if self.focused { - //self.focused = false; - //self.items[self.focus].handle(&AppEvent::Blur)?; - //true - //} else { - //false - //} - //}, - //}) - //} -//} diff --git a/src/main.rs b/src/main.rs index 324ec7f9..e00dff29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,15 +87,6 @@ pub fn main () -> Usually<()> { sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh2.wav"), ])))?, |track, device|{ device.connect_midi_in(0, &track.midi_out.clone_unowned())?; - Ok(()) - })?; - - track.add_device_with_cb(Plugin::lv2( - "Panagement", - "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2" - )?, |track, device|{ - device.connect_audio_in(0, &track.devices[0].audio_outs()?[0])?; - device.connect_audio_in(0, &track.devices[0].audio_outs()?[1])?; if let Some(Some(left)) = audio_outs.get(0) { device.connect_audio_out(0, left)?; } @@ -105,6 +96,21 @@ pub fn main () -> Usually<()> { Ok(()) })?; + //track.add_device_with_cb(Plugin::lv2( + //"Panagement", + //"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2" + //)?, |track, device|{ + //device.connect_audio_in(0, &track.devices[0].audio_outs()?[0])?; + //device.connect_audio_in(0, &track.devices[0].audio_outs()?[1])?; + //if let Some(Some(left)) = audio_outs.get(0) { + //device.connect_audio_out(0, left)?; + //} + //if let Some(Some(right)) = audio_outs.get(0) { + //device.connect_audio_out(1, right)?; + //} + //Ok(()) + //})?; + track.sequence = Some(1); // FIXME track.add_phrase("4 kicks", ppq * 4, Some(phrase! { @@ -122,9 +128,18 @@ pub fn main () -> Usually<()> { })); track.add_phrase("D-Beat", ppq * 4, Some(phrase! { 00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, - 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, - 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - 08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, + 02 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() }, + 04 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() }, + 06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 08 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() }, + 10 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() }, + 12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 13 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() }, + 14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, + 15 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() }, + 00 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() }, + 04 * ppq/4 => MidiMessage::NoteOn { key: 38.into(), vel: 100.into() }, + 08 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() }, 10 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, })); @@ -168,46 +183,53 @@ pub fn main () -> Usually<()> { Ok(()) })?; track.sequence = Some(0); // FIXME - track.add_phrase("Custom", ppq * 4, None); track.add_phrase("Offbeat", ppq * 4, Some(phrase! { - 00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, - 02 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - 04 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, - 06 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - 08 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, - 10 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, - 12 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, - 14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + //00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, + //02 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + //04 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, + //06 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + //08 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, + //10 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, + //12 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, + //14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, })); + track.add_phrase("Custom1", ppq * 4, None); + track.add_phrase("Custom2", ppq * 4, None); + track.add_phrase("Custom3", ppq * 4, None); + track.add_phrase("Custom4", ppq * 4, None); Ok(()) })?; state.add_track_with_cb(Some("Lead"), |_, track|{ - //track.add_device_with_cb(Plugin::lv2( - //"Helm", - //"file:///home/user/.lv2/Helm.lv2" - //)?, |track, device|{ - //device.connect_midi_in(0, &track.midi_out.clone_unowned())?; - //if let Some(Some(left)) = audio_outs.get(0) { - //device.connect_audio_out(0, left)?; - //} - //if let Some(Some(right)) = audio_outs.get(0) { - //device.connect_audio_out(1, right)?; - //} - //Ok(()) - //})?; + track.add_device_with_cb(Plugin::lv2( + "Odin2", + "file:///home/user/.lv2/Odin2.lv2" + )?, |track, device|{ + device.connect_midi_in(0, &track.midi_out.clone_unowned())?; + if let Some(Some(left)) = audio_outs.get(0) { + device.connect_audio_out(0, left)?; + } + if let Some(Some(right)) = audio_outs.get(0) { + device.connect_audio_out(1, right)?; + } + Ok(()) + })?; track.sequence = Some(0); // FIXME - track.add_phrase("Custom", ppq * 4, None); + track.add_phrase("Custom0", ppq * 4, None); + track.add_phrase("Custom1", ppq * 4, None); + track.add_phrase("Custom2", ppq * 4, None); + track.add_phrase("Custom3", ppq * 4, None); + track.add_phrase("Custom4", ppq * 4, None); Ok(()) })?; state.scenes = vec![ Scene::new("Intro", vec![None, Some(0), None, None]), - Scene::new("Hook", vec![Some(0), Some(1), None, None]), - Scene::new("Verse", vec![Some(2), Some(0), Some(0), None]), - Scene::new("Chorus", vec![Some(1), Some(1), None, None]), - Scene::new("Bridge", vec![Some(3), Some(0), Some(0), None]), - Scene::new("Outro", vec![None, Some(1), None, None]), + Scene::new("Hook", vec![Some(0), Some(1), Some(0), None]), + Scene::new("Verse", vec![Some(2), Some(2), Some(1), None]), + Scene::new("Chorus", vec![Some(1), Some(3), Some(2), None]), + Scene::new("Bridge", vec![Some(3), Some(4), Some(3), None]), + Scene::new("Outro", vec![None, Some(1), Some(4), None]), ]; Ok(()) diff --git a/src/model.rs b/src/model.rs index 1e4b148c..e33c3259 100644 --- a/src/model.rs +++ b/src/model.rs @@ -44,7 +44,7 @@ pub struct App { /// Optional modal dialog pub modal: Option>, /// Currently focused section - pub section: usize, + pub section: AppSection, /// Whether the section is focused pub entered: bool, /// Current frame @@ -290,3 +290,31 @@ struct SelectionMut<'a> { pub phrase_id: Option, pub phrase: Option<&'a mut Phrase>, } + +#[derive(PartialEq, Clone, Copy)] +pub enum AppSection { + Grid, + Sequencer, + Chain, +} +impl Default for AppSection { + fn default () -> Self { + Self::Grid + } +} +impl AppSection { + pub fn prev (&mut self) { + *self = match self { + Self::Grid => Self::Chain, + Self::Sequencer => Self::Grid, + Self::Chain => Self::Sequencer, + } + } + pub fn next (&mut self) { + *self = match self { + Self::Grid => Self::Sequencer, + Self::Sequencer => Self::Chain, + Self::Chain => Self::Grid, + } + } +} diff --git a/src/model/phrase.rs b/src/model/phrase.rs index 023072e6..7b1bfc9d 100644 --- a/src/model/phrase.rs +++ b/src/model/phrase.rs @@ -3,6 +3,7 @@ use crate::core::*; /// Define a MIDI phrase. #[macro_export] macro_rules! phrase { ($($t:expr => $msg:expr),* $(,)?) => {{ + #[allow(unused_mut)] let mut phrase = BTreeMap::new(); $(phrase.insert($t, vec![]);)* $(phrase.get_mut(&$t).unwrap().push($msg);)* diff --git a/src/model/track.rs b/src/model/track.rs index ac5cadae..c3a98d50 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -124,7 +124,7 @@ impl Track { reset: bool, scope: &ProcessScope, (frame0, frames): (usize, usize), - (usec0, usecs): (usize, usize), + (_usec0, _usecs): (usize, usize), period: f64, ) { // Need to be borrowed outside the conditionals? diff --git a/src/view.rs b/src/view.rs index ae230e24..123b2838 100644 --- a/src/view.rs +++ b/src/view.rs @@ -16,7 +16,7 @@ pub use self::focus::*; pub use self::chain::ChainView; pub use self::sequencer::SequencerView; -use crate::{render, App, core::*}; +use crate::{render, App, AppSection, core::*}; render!(App |self, buf, area| { let Rect { x, y, width, height } = area; @@ -31,7 +31,7 @@ render!(App |self, buf, area| { x, y, width, height: height / 3 })? }; - if self.section == 0 { + if self.section == AppSection::Grid { QuarterV(if self.entered { Style::default().green() } else { @@ -43,7 +43,7 @@ render!(App |self, buf, area| { let chain = self.draw_chain(buf, Rect { x, y: y + height - height / 3 - 1, width, height: height / 3 })?; - if self.section == 1 { + if self.section == AppSection::Chain { QuarterV(if self.entered { Style::default().green() } else { @@ -53,7 +53,7 @@ render!(App |self, buf, area| { let phrase = self.draw_phrase(buf, Rect { x, y, width, height: height - height / 3 })?; - if self.section == 2 { + if self.section == AppSection::Sequencer { QuarterV(if self.entered { Style::default().green() } else { @@ -78,7 +78,7 @@ impl App { for track in self.tracks.iter() { track.name.blit(buf, x + 1, area.y, Some(Style::default().white().bold())); x = x + ChainView { - focused: self.section == 1, + focused: self.section == AppSection::Chain, track: Some(track), vertical: true, } @@ -105,7 +105,7 @@ impl App { SceneGridViewHorizontal { buf, area, - focused: self.section == 0, + focused: self.section == AppSection::Grid, entered: self.entered, scenes: &self.scenes, tracks: &self.tracks, @@ -116,7 +116,7 @@ impl App { SceneGridViewVertical { buf, area, - focused: self.section == 0, + focused: self.section == AppSection::Grid, entered: self.entered, scenes: &self.scenes, tracks: &self.tracks, @@ -125,7 +125,7 @@ impl App { } fn draw_chain (&self, buf: &mut Buffer, area: Rect) -> Usually { ChainView { - focused: self.section == 1, + focused: self.section == AppSection::Chain, track: self.tracks.get(self.track_cursor - 1), vertical: false, }.render(buf, area) @@ -134,7 +134,7 @@ impl App { let phrase = self.phrase(); let seq_area = SequencerView { phrase, - focused: self.section == 2, + focused: self.section == AppSection::Sequencer, ppq: self.timebase.ppq() as usize, now: self.timebase.frame_to_pulse(self.playhead as f64) as usize, time_cursor: self.time_cursor, @@ -144,7 +144,7 @@ impl App { note_start: self.note_start, }.render(buf, area)?; let track = self.tracks.get(self.track_cursor - 1).unwrap(); - if phrase.is_none() && self.section == 2 { + if phrase.is_none() && self.section == AppSection::Sequencer { let label = format!("[ENTER] Create new clip: {}", track.name); let x = area.x + seq_area.width / 2 - (label.len() / 2) as u16; let y = area.y + seq_area.height / 2; diff --git a/src/view/grid.rs b/src/view/grid.rs index 2d0bc803..01c253e1 100644 --- a/src/view/grid.rs +++ b/src/view/grid.rs @@ -17,7 +17,7 @@ impl<'a> SceneGridViewVertical<'a> { pub fn draw (&mut self) -> Usually { self.area.height = self.scenes.len() as u16 + 3; let Rect { x, y, width, height } = self.area; - let style = Some(Style::default().green().dim()); + //let style = Some(Style::default().green().dim()); fill_bg(&mut self.buf, self.area, if self.focused && self.entered { Color::Rgb(25, 60, 15) } else if self.focused { @@ -160,7 +160,11 @@ impl<'a> SceneGridViewVertical<'a> { let index = index as u16; let label = if let Some(Some(clip)) = clip { if let Some(phrase) = self.tracks[track].phrases.get(*clip) { - format!("⯈{}", phrase.name) + format!("{} {}", if self.tracks[track].sequence == Some(*clip) { + "⯈" + } else { + " " + }, phrase.name) } else { format!("????") } @@ -200,7 +204,7 @@ impl<'a> SceneGridViewHorizontal<'a> { } pub fn draw (&mut self) -> Usually { self.area.height = self.tracks.len() as u16 * 2 + 2; - let style = Some(Style::default().green().dim()); + //let style = Some(Style::default().green().dim()); let Rect { x, y, width, height } = self.area; fill_bg(&mut self.buf, self.area, if self.focused && self.entered { Color::Rgb(25, 60, 15) diff --git a/src/view/sequencer.rs b/src/view/sequencer.rs index b21b82f1..9ab2802a 100644 --- a/src/view/sequencer.rs +++ b/src/view/sequencer.rs @@ -1,4 +1,4 @@ -use crate::{core::*,model::*,view::*}; +use crate::{core::*,model::*}; #[derive(Debug, Clone)] pub enum SequencerMode { Tiny, Compact, Horizontal, Vertical, } @@ -136,41 +136,81 @@ mod horizontal { time0: usize, note0: usize, ) { - let Rect { x, y, width, height } = area; - //let time0 = time0 / time_z; - //let time1 = time0 + width as usize; - //let note1 = note0 + height as usize; - let bg = Style::default(); + let bg = Style::default(); let (bw, wh) = (bg.dim(), bg.white().not_dim()); + + let Rect { x, y, width, height } = area; + let offset = 5; - for x in x+offset..x+width-offset { + let phrase_area = Rect { + x: x + offset, y, width: width - offset, height: height - 2 + }; + + let mut cell_bg = Cell::default(); + cell_bg.set_char('·'); + cell_bg.set_style(bw); + + let mut cell_bg_tick = Cell::default(); + cell_bg_tick.set_char('|'); + cell_bg_tick.set_style(bw); + + let mut cell_a = Cell::default(); + cell_a.set_char('▀'); + cell_a.set_style(wh); + + let mut cell_b = Cell::default(); + cell_b.set_char('▄'); + cell_b.set_style(wh); + + let mut cell_ab = Cell::default(); + cell_ab.set_char('█'); + cell_ab.set_style(wh); + + let mut steps = vec![]; + for x in phrase_area.x .. phrase_area.x + phrase_area.width { let step = (time0 + (x-offset) as usize) * time_z; + let next_step = (1 + time0 + (x-offset) as usize) * time_z; if step > phrase.length { break } - let next_step = (time0 + (x-offset) as usize + 1) * time_z; - if step % ppq == 0 { - "|".blit(buf, x as u16, y, Some(Style::default().dim())); + steps.push((x, step, next_step)); + let cell = if step % ppq == 0 { + &cell_bg_tick + } else { + &cell_bg + }; + for y in phrase_area.y .. phrase_area.y + phrase_area.height { + if y == phrase_area.y { + if step % (4 * ppq) == 0 { + format!("{}", 1 + step / (4 * ppq)).blit(buf, x, y, None); + } else if step % ppq == 0 { + *buf.get_mut(x, y) = cell.clone(); + } + } else { + *buf.get_mut(x, y) = cell.clone(); + } } - let bar = 4 * ppq; - if step % bar == 0 { - format!("{}", (step/bar)+1) - .blit(buf, x as u16, y, Some(Style::default().bold().not_dim())); - } - for index in 0..height-2 { - let note_a = note0 + index as usize * 2; - let note_b = note0 + index as usize * 2 + 1; - let (character, mut style) = match ( - phrase.contains_note_on(u7::from_int_lossy(note_a as u8), step, next_step), - phrase.contains_note_on(u7::from_int_lossy(note_b as u8), step, next_step), - ) { - (true, true) => ("█", wh), - (false, true) => ("▀", wh), - (true, false) => ("▄", wh), - (false, false) => (if step % ppq == 0 { "|" } else { "·" }, bw), + } + + for index in 0..height-2 { + let note_a = note0 + index as usize * 2; + let note_b = note0 + index as usize * 2 + 1; + for (x, step, next_step) in steps.iter() { + let (a, b) = ( + phrase.contains_note_on(u7::from_int_lossy(note_a as u8), *step, *next_step), + phrase.contains_note_on(u7::from_int_lossy(note_b as u8), *step, *next_step), + ); + let cell = if a && b { + &cell_ab + } else if a { + &cell_a + } else if b { + &cell_b + } else { + continue }; let y = y + height.saturating_sub(index+2) as u16; - character.blit(buf, x, y, Some(style)); + *buf.get_mut(*x, y) = cell.clone() } } }