diff --git a/src/control.rs b/src/control.rs index b10e4a2a..59f17867 100644 --- a/src/control.rs +++ b/src/control.rs @@ -2,7 +2,7 @@ use crate::{core::*, handle, App, AppFocus}; -submod!{ arranger chain focus mixer plugin sampler sequencer transport } +submod!{ chain focus mixer plugin sampler transport } handle!{ App |self, e| { @@ -31,9 +31,9 @@ fn handle_focused (state: &mut App, e: &AppEvent) -> Usually { AppFocus::Transport => handle_keymap(state, e, crate::control::transport::KEYMAP_TRANSPORT), AppFocus::Arranger => - handle_keymap(state, e, crate::control::arranger::KEYMAP_ARRANGER), + handle_keymap(state, e, crate::devices::arranger::KEYMAP_ARRANGER), AppFocus::Sequencer => - handle_keymap(state, e, crate::control::sequencer::KEYMAP_SEQUENCER), + handle_keymap(state, e, crate::devices::sequencer::KEYMAP_SEQUENCER), AppFocus::Chain => Ok(if state.entered { handle_device(state, e)? || handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)? diff --git a/src/control/arranger.rs b/src/control/arranger.rs deleted file mode 100644 index 92295d50..00000000 --- a/src/control/arranger.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::{core::*, model::App}; - -/// Key bindings for arranger section. -pub const KEYMAP_ARRANGER: &'static [KeyBinding] = keymap!(App { - [Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| { - app.arranger.mode = !app.arranger.mode; - Ok(true) - }], - [Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| { - match app.arranger.mode { - false => app.arranger.scene_prev(), - true => app.arranger.track_prev(), - }; - app.sequencer.show(app.arranger.phrase())?; - Ok(true) - }], - [Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| { - match app.arranger.mode { - false => app.arranger.scene_next(), - true => app.arranger.track_next(), - }; - app.sequencer.show(app.arranger.phrase())?; - Ok(true) - }], - [Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| { - match app.arranger.mode { - false => app.arranger.track_prev(), - true => app.arranger.scene_prev(), - }; - app.sequencer.show(app.arranger.phrase())?; - Ok(true) - }], - [Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| { - match app.arranger.mode { - false => app.arranger.track_next(), - true => app.arranger.scene_next() - }; - app.sequencer.show(app.arranger.phrase())?; - Ok(true) - }], - [Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| { - app.arranger.phrase_next(); - app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); - Ok(true) - }], - [Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| { - app.arranger.phrase_prev(); - app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); - Ok(true) - }], - [Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| { - app.arranger.activate(); - Ok(true) - }], -}); diff --git a/src/control/sequencer.rs b/src/control/sequencer.rs deleted file mode 100644 index f640cba7..00000000 --- a/src/control/sequencer.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::{core::*, model::App}; - -/// Key bindings for phrase editor. -pub const KEYMAP_SEQUENCER: &'static [KeyBinding] = keymap!(App { - [Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| { - match app.sequencer.entered { - true => { app.sequencer.note_axis.point_dec(); }, - false => { app.sequencer.note_axis.start_dec(); }, - } - Ok(true) - }], - [Down, NONE, "seq_cursor_down", "move cursor down", |app: &mut App| { - match app.sequencer.entered { - true => { app.sequencer.note_axis.point_inc(); }, - false => { app.sequencer.note_axis.start_inc(); }, - } - Ok(true) - }], - [Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| { - match app.sequencer.entered { - true => { app.sequencer.time_axis.point_dec(); }, - false => { app.sequencer.time_axis.start_dec(); }, - } - Ok(true) - }], - [Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| { - match app.sequencer.entered { - true => { app.sequencer.time_axis.point_inc(); }, - false => { app.sequencer.time_axis.start_inc(); }, - } - Ok(true) - }], - [Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| { - app.sequencer.mode = !app.sequencer.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], -}); diff --git a/src/devices.rs b/src/devices.rs new file mode 100644 index 00000000..3b09d5b7 --- /dev/null +++ b/src/devices.rs @@ -0,0 +1 @@ +crate::core::pubmod! { sequencer arranger } diff --git a/src/view/arranger.rs b/src/devices/arranger.rs similarity index 54% rename from src/view/arranger.rs rename to src/devices/arranger.rs index 80245725..8e430af0 100644 --- a/src/view/arranger.rs +++ b/src/devices/arranger.rs @@ -1,5 +1,76 @@ +/// Arrangement editor. + use crate::{core::*, model::*, view::*}; +/// Key bindings for arranger section. +pub const KEYMAP_ARRANGER: &'static [KeyBinding] = keymap!(App { + [Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| { + app.arranger.mode = !app.arranger.mode; + Ok(true) + }], + [Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| { + match app.arranger.mode { + false => app.arranger.scene_prev(), + true => app.arranger.track_prev(), + }; + app.sequencer.show(app.arranger.phrase())?; + Ok(true) + }], + [Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| { + match app.arranger.mode { + false => app.arranger.scene_next(), + true => app.arranger.track_next(), + }; + app.sequencer.show(app.arranger.phrase())?; + Ok(true) + }], + [Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| { + match app.arranger.mode { + false => app.arranger.track_prev(), + true => app.arranger.scene_prev(), + }; + app.sequencer.show(app.arranger.phrase())?; + Ok(true) + }], + [Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| { + match app.arranger.mode { + false => app.arranger.track_next(), + true => app.arranger.scene_next() + }; + app.sequencer.show(app.arranger.phrase())?; + Ok(true) + }], + [Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| { + app.arranger.phrase_next(); + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + Ok(true) + }], + [Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| { + app.arranger.phrase_prev(); + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + Ok(true) + }], + [Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| { + app.arranger.activate(); + Ok(true) + }], +}); + +/// Represents the tracks and scenes of the composition. +pub struct Arranger { + /// Display mode of arranger + pub mode: bool, + /// Currently selected element. + pub selected: ArrangerFocus, + /// Collection of tracks. + pub tracks: Vec, + /// Collection of scenes. + pub scenes: Vec, + + pub focused: bool, + pub entered: bool, +} + render!(Arranger |self, buf, area| { let mut area = area; area.height = area.height.min(1 + if self.mode { @@ -21,6 +92,33 @@ render!(Arranger |self, buf, area| { impl Arranger { + pub fn new () -> Self { + Self { + mode: false, + selected: ArrangerFocus::Clip(0, 0), + scenes: vec![], + tracks: vec![], + entered: true, + focused: true, + } + } + + pub fn activate (&mut self) { + match self.selected { + ArrangerFocus::Scene(s) => { + for (track_index, track) in self.tracks.iter_mut().enumerate() { + track.sequence = self.scenes[s].clips[track_index]; + track.reset = true; + } + }, + ArrangerFocus::Clip(t, s) => { + self.tracks[t].sequence = self.scenes[s].clips[t]; + self.tracks[t].reset = true; + }, + _ => {} + } + } + fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually { return Split::right([ @@ -270,4 +368,195 @@ impl Arranger { }, ]).render(buf, area) } + +} + +/// Track management methods +impl Arranger { + pub fn track (&self) -> Option<&Track> { + self.selected.track().map(|t|self.tracks.get(t)).flatten() + } + pub fn track_mut (&mut self) -> Option<&mut Track> { + self.selected.track().map(|t|self.tracks.get_mut(t)).flatten() + } + pub fn track_next (&mut self) { + self.selected.track_next(self.tracks.len()) + } + pub fn track_prev (&mut self) { + self.selected.track_prev() + } + pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> { + self.tracks.push(name.map_or_else( + || Track::new(&self.track_default_name()), + |name| Track::new(name), + )?); + let index = self.tracks.len() - 1; + Ok(&mut self.tracks[index]) + } + pub fn track_del (&mut self) { + unimplemented!("Arranger::track_del"); + } + pub fn track_default_name (&self) -> String { + format!("Track {}", self.tracks.len() + 1) + } +} + +/// Scene management methods +impl Arranger { + pub fn scene (&self) -> Option<&Scene> { + self.selected.scene().map(|s|self.scenes.get(s)).flatten() + } + pub fn scene_mut (&mut self) -> Option<&mut Scene> { + self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten() + } + pub fn scene_next (&mut self) { + self.selected.scene_next(self.scenes.len()) + } + pub fn scene_prev (&mut self) { + self.selected.scene_prev() + } + pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> { + let clips = vec![None;self.tracks.len()]; + self.scenes.push(match name { + Some(name) => Scene::new(name, clips), + None => Scene::new(&self.track_default_name(), clips), + }); + let index = self.scenes.len() - 1; + Ok(&mut self.scenes[index]) + } + pub fn scene_del (&mut self) { + unimplemented!("Arranger::scene_del"); + } + pub fn scene_default_name (&self) -> String { + format!("Scene {}", self.scenes.len() + 1) + } +} + +/// Phrase management methods +impl Arranger { + pub fn phrase (&self) -> Option<&Arc>> { + let track_id = self.selected.track()?; + self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?) + } + //pub fn phrase_mut (&mut self) -> Option<&mut Phrase> { + //let track_id = self.selected.track()?; + //let clip = *self.scene()?.clips.get(track_id)?; + //self.tracks.get_mut(track_id)?.phrases.get_mut(clip?) + //} + pub fn phrase_next (&mut self) { + unimplemented!(); + //if let Some((track_index, track)) = self.track_mut() { + //let phrases = track.phrases.len(); + //if let Some((_, scene)) = self.scene_mut() { + //if let Some(phrase_index) = scene.clips[track_index] { + //if phrase_index >= phrases - 1 { + //scene.clips[track_index] = None; + //} else { + //scene.clips[track_index] = Some(phrase_index + 1); + //} + //} else if phrases > 0 { + //scene.clips[track_index] = Some(0); + //} + //} + //} + } + pub fn phrase_prev (&mut self) { + unimplemented!(); + //if let Some((track_index, track)) = self.track_mut() { + //let phrases = track.phrases.len(); + //if let Some((_, scene)) = self.scene_mut() { + //if let Some(phrase_index) = scene.clips[track_index] { + //scene.clips[track_index] = if phrase_index == 0 { + //None + //} else { + //Some(phrase_index - 1) + //}; + //} else if phrases > 0 { + //scene.clips[track_index] = Some(phrases - 1); + //} + //} + //} + } +} + +#[derive(PartialEq)] +/// Represents the current user selection in the arranger +pub enum ArrangerFocus { + /// The whole mix is selected + Mix, + /// A track is selected. + Track(usize), + /// A scene is selected. + Scene(usize), + /// A clip (track × scene) is selected. + Clip(usize, usize), +} + +/// Focus identification methods +impl ArrangerFocus { + pub fn is_track (&self) -> bool { + match self { Self::Track(_) => true, _ => false } + } + pub fn is_scene (&self) -> bool { + match self { Self::Scene(_) => true, _ => false } + } + pub fn is_clip (&self) -> bool { + match self { Self::Clip(_, _) => true, _ => false } + } +} + +/// Track focus methods +impl ArrangerFocus { + pub fn track (&self) -> Option { + match self { + Self::Clip(t, _) => Some(*t), + Self::Track(t) => Some(*t), + _ => None + } + } + pub fn track_next (&mut self, last_track: usize) { + *self = match self { + Self::Mix => Self::Track(0), + Self::Track(t) => Self::Track(last_track.min(*t + 1)), + Self::Scene(s) => Self::Clip(0, *s), + Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s), + } + } + pub fn track_prev (&mut self) { + *self = match self { + Self::Mix => Self::Mix, + Self::Scene(s) => Self::Scene(*s), + Self::Track(0) => Self::Mix, + Self::Track(t) => Self::Track(*t - 1), + Self::Clip(t, s) => Self::Clip(t.saturating_sub(1), *s), + } + } +} + +/// Scene focus methods +impl ArrangerFocus { + pub fn scene (&self) -> Option { + match self { + Self::Clip(_, s) => Some(*s), + Self::Scene(s) => Some(*s), + _ => None + } + } + pub fn scene_next (&mut self, last_scene: usize) { + *self = match self { + Self::Mix => Self::Scene(0), + Self::Track(t) => Self::Scene(*t), + Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)), + Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)), + } + } + pub fn scene_prev (&mut self) { + *self = match self { + Self::Mix => Self::Mix, + Self::Track(t) => Self::Track(*t), + Self::Scene(0) => Self::Mix, + Self::Scene(s) => Self::Scene(*s - 1), + Self::Clip(t, s) => Self::Clip(*t, s.saturating_sub(1)), + } + } } diff --git a/src/devices/sequencer.rs b/src/devices/sequencer.rs new file mode 100644 index 00000000..9c1dae52 --- /dev/null +++ b/src/devices/sequencer.rs @@ -0,0 +1,361 @@ +/// Phrase editor. + +use crate::{core::*, model::*, view::*}; + +/// Key bindings for phrase editor. +pub const KEYMAP_SEQUENCER: &'static [KeyBinding] = keymap!(App { + [Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| { + match app.sequencer.entered { + true => { app.sequencer.note_axis.point_dec(); }, + false => { app.sequencer.note_axis.start_dec(); }, + } + Ok(true) + }], + [Down, NONE, "seq_cursor_down", "move cursor down", |app: &mut App| { + match app.sequencer.entered { + true => { app.sequencer.note_axis.point_inc(); }, + false => { app.sequencer.note_axis.start_inc(); }, + } + Ok(true) + }], + [Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| { + match app.sequencer.entered { + true => { app.sequencer.time_axis.point_dec(); }, + false => { app.sequencer.time_axis.start_dec(); }, + } + Ok(true) + }], + [Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| { + match app.sequencer.entered { + true => { app.sequencer.time_axis.point_inc(); }, + false => { app.sequencer.time_axis.start_inc(); }, + } + Ok(true) + }], + [Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| { + app.sequencer.mode = !app.sequencer.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], + */ +}); + +/// Phrase editor. +pub struct Sequencer { + pub mode: bool, + pub focused: bool, + pub entered: bool, + + pub phrase: Option>>, + pub buffer: Buffer, + pub keys: Buffer, + /// Highlight input keys + pub keys_in: [bool; 128], + /// Highlight output keys + pub keys_out: [bool; 128], + + pub now: usize, + pub ppq: usize, + pub note_axis: FixedAxis, + pub time_axis: ScaledAxis, + +} + +render!(Sequencer |self, buf, area| { + fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered)); + self.horizontal_draw(buf, area)?; + if self.focused && self.entered { + Corners(Style::default().green().not_dim()).draw(buf, area)?; + } + Ok(area) +}); + +impl Sequencer { + pub fn new () -> Self { + Self { + buffer: Buffer::empty(Rect::default()), + keys: keys_vert(), + entered: false, + focused: false, + mode: false, + keys_in: [false;128], + keys_out: [false;128], + phrase: None, + now: 0, + ppq: 96, + note_axis: FixedAxis { + start: 12, + point: Some(36) + }, + time_axis: ScaledAxis { + start: 0, + scale: 24, + point: Some(0) + }, + } + } + /// Select which pattern to display. This pre-renders it to the buffer at full resolution. + /// FIXME: Support phrases longer that 65536 ticks + pub fn show (&mut self, phrase: Option<&Arc>>) -> Usually<()> { + self.phrase = phrase.map(Clone::clone); + if let Some(ref phrase) = self.phrase { + let width = u16::MAX.min(phrase.read().unwrap().length as u16); + let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height: 64 }); + let phrase = phrase.read().unwrap(); + fill_seq_bg(&mut buffer, phrase.length, self.ppq)?; + fill_seq_fg(&mut buffer, &phrase)?; + self.buffer = buffer; + } else { + self.buffer = Buffer::empty(Rect::default()) + } + Ok(()) + } + + fn style_focus (&self) -> Option