diff --git a/src/control.rs b/src/control.rs index d2507ae6..08780477 100644 --- a/src/control.rs +++ b/src/control.rs @@ -44,8 +44,8 @@ fn handle_focused (state: &mut App, e: &AppEvent) -> Usually { } fn handle_device (state: &mut App, e: &AppEvent) -> Usually { - state.track_mut() - .and_then(|(_, track)|track.device_mut()) + state.arranger.track() + .and_then(|track|track.device_mut()) .map(|mut device|device.handle(e)) .transpose() .map(|x|x.unwrap_or(false)) @@ -58,15 +58,15 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding] = keymap!(App { Ok(true) }], [Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| { - app.track_mut().map(|t|t.1.toggle_record()); + app.arranger.track_mut().map(|t|t.toggle_record()); Ok(true) }], [Char('o'), NONE, "overdub_toggle", "toggle overdub", |app: &mut App| { - app.track_mut().map(|t|t.1.toggle_overdub()); + app.arranger.track_mut().map(|t|t.toggle_overdub()); Ok(true) }], [Char('m'), NONE, "monitor_toggle", "toggle monitor", |app: &mut App| { - app.track_mut().map(|t|t.1.toggle_monitor()); + app.arranger.track_mut().map(|t|t.toggle_monitor()); Ok(true) }], [Char('+'), NONE, "quant_inc", "quantize coarser", |app: &mut App| { @@ -86,12 +86,12 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding] = keymap!(App { Ok(true) }], [Char('x'), NONE, "extend", "double the current clip", |app: &mut App| { - if let Some(phrase) = app.phrase_mut() { + app.arranger.phrase_mut().map(|phrase|{ let mut notes = phrase.notes.clone(); notes.extend_from_slice(&mut phrase.notes); phrase.notes = notes; phrase.length = phrase.length * 2; - } + }); Ok(true) }], [Char('l'), NONE, "loop_toggle", "toggle looping", |_app: &mut App| { @@ -115,11 +115,11 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding] = keymap!(App { Ok(true) }], [Char('a'), CONTROL, "scene_add", "add a new scene", |app: &mut App| { - app.add_scene(None)?; + app.arranger.scene_add(None)?; Ok(true) }], [Char('t'), CONTROL, "track_add", "add a new track", |app: &mut App| { - app.add_track(None)?; + app.arranger.track_add(None)?; Ok(true) }], }); diff --git a/src/control/arranger.rs b/src/control/arranger.rs index 2277d98d..4b0804de 100644 --- a/src/control/arranger.rs +++ b/src/control/arranger.rs @@ -3,57 +3,43 @@ 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; + app.arranger.mode = !app.arranger.mode; Ok(true) }], [Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| Ok( - match app.arranger_mode { - false => {app.prev_scene();true}, - true => {app.prev_track();true}, + match app.arranger.mode { + false => {app.arranger.scene_prev();true}, + true => {app.arranger.track_prev();true}, } )], [Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok( - match app.arranger_mode { - false => {app.next_scene();true}, - true => {app.next_track();true}, + match app.arranger.mode { + false => {app.arranger.scene_next();true}, + true => {app.arranger.track_next();true}, } )], [Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok( - match app.arranger_mode { - false => {app.prev_track();true}, - true => {app.prev_scene();true}, + match app.arranger.mode { + false => {app.arranger.track_prev();true}, + true => {app.arranger.scene_prev();true}, } )], [Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok( - match app.arranger_mode { - false => {app.next_track();true}, - true => {app.next_scene();true} - } - )], - [Enter, NONE, "arranger_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 + match app.arranger.mode { + false => {app.arranger.track_next();true}, + true => {app.arranger.scene_next();true} } )], [Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| { - app.next_phrase(); + app.arranger.phrase_next(); Ok(true) }], [Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| { - app.prev_phrase(); + app.arranger.phrase_prev(); + Ok(true) + }], + [Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| { + app.arranger.activate(); Ok(true) }], }); diff --git a/src/control/chain.rs b/src/control/chain.rs index f5f5e0d2..9552843a 100644 --- a/src/control/chain.rs +++ b/src/control/chain.rs @@ -9,20 +9,18 @@ pub const KEYMAP_CHAIN: &'static [KeyBinding] = keymap!(App { Ok(true) }], [Left, NONE, "chain_cursor_left", "move cursor left", |app: &mut App| { - if let Some((_, track)) = app.track_mut() { + if let Some(track) = app.arranger.track_mut() { track.device = track.device.saturating_sub(1); - Ok(true) - } else { - Ok(false) + return Ok(true) } + Ok(false) }], [Right, NONE, "chain_cursor_right", "move cursor right", |app: &mut App| { - if let Some((_, track)) = app.track_mut() { + if let Some(track) = app.arranger.track_mut() { track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); - Ok(true) - } else { - Ok(false) + return Ok(true) } + Ok(false) }], [Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| { app.chain_mode = !app.seq_mode; diff --git a/src/edn.rs b/src/edn.rs index 1e77b946..3e2e9a5f 100644 --- a/src/edn.rs +++ b/src/edn.rs @@ -116,7 +116,7 @@ impl Scene { Edn::Map(map) => { let key = map.get(&Edn::Key(":name")); if let Some(Edn::Str(n)) = key { - name = Some(String::from(*n)); + name = Some(*n); } else { panic!("unexpected key in scene '{name:?}': {key:?}") } @@ -129,25 +129,23 @@ impl Scene { }, _ => panic!("unexpected in scene '{name:?}': {edn:?}") }); - app.add_scene_with_clips(name.as_deref(), &clips) - //for edn in args { - //match end { - //} - //} + let scene = app.arranger.scene_add(name)?; + scene.clips = clips; + Ok(scene) } } impl Track { fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> { let ppq = app.transport.ppq(); - let mut name = app.new_track_name(); + let mut name = None; let mut _gain = 0.0f64; let mut devices: Vec = vec![]; let mut phrases: Vec = vec![]; edn!(edn in args { Edn::Map(map) => { if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); + name = Some(*n); } if let Some(Edn::Double(g)) = map.get(&Edn::Key(":gain")) { _gain = f64::from(*g) @@ -163,12 +161,17 @@ impl Track { Some(Edn::Symbol("lv2")) => { devices.push(LV2Plugin::load_edn(&args[1..])?) }, - None => panic!("empty list track {name}"), - _ => panic!("unexpected in track {name}: {:?}", args.get(0).unwrap()) + None => panic!("empty list track {}", + name.unwrap_or("") + ), + _ => panic!("unexpected in track {}: {:?}", + name.unwrap_or(""), + args.get(0).unwrap() + ) }, _ => {} }); - let track = app.add_track(Some(name.as_str()))?; + let track = app.arranger.track_add(name)?; for phrase in phrases { track.phrases.push(phrase); } for device in devices { track.add_device(device)?; } Ok(track) @@ -214,8 +217,8 @@ impl Phrase { args.get(1), ) { let (key, vel) = ( - u7::from((*key as u8).min(127)), - u7::from((*vel as u8).min(127)) + u7::from((*key as u8).min(127)), + u7::from((*vel as u8).min(127)), ); phrase.notes[time].push(MidiMessage::NoteOn { key, vel }) } else { diff --git a/src/main.rs b/src/main.rs index cac73c9c..24bd17f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ pub fn main () -> Usually<()> { let app = app.read().unwrap(); let jack = app.jack.as_ref().unwrap(); let midi_in = jack.register_port("midi-in", MidiIn)?; - let midi_outs = app.tracks.iter() + let midi_outs = app.arranger.tracks.iter() .map(|t|Some(jack.register_port(&t.name, MidiOut).unwrap())) .collect::>(); (midi_in, midi_outs) @@ -40,10 +40,10 @@ pub fn main () -> Usually<()> { } } app.midi_in = Some(Arc::new(midi_in)); - for (index, track) in app.tracks.iter_mut().enumerate() { + for (index, track) in app.arranger.tracks.iter_mut().enumerate() { track.midi_out = midi_outs[index].take(); } - for track in app.tracks.iter() { + for track in app.arranger.tracks.iter() { track.connect_first_device()?; track.connect_last_device(&app)?; } diff --git a/src/model.rs b/src/model.rs index 809232a4..ee9160dd 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,6 +1,6 @@ //! Application state. -submod! { looper mixer phrase plugin sampler scene track transport } +submod! { arranger looper mixer phrase plugin sampler scene track transport } use crate::{core::*, view::*}; @@ -13,8 +13,6 @@ pub struct App { pub midi_in: Option>>, /// Names of ports to connect to main MIDI IN. pub midi_ins: Vec, - /// Display mode of arranger section - pub arranger_mode: bool, /// Display mode of chain section pub chain_mode: bool, /// Display mode of sequencer seciton @@ -23,24 +21,12 @@ pub struct App { pub seq_buf: BufferedSequencerView, /// Optional modal dialog pub modal: Option>, - /// Currently focused section - pub section: AppFocus, - /// Whether the current focus section has input priority - pub entered: bool, /// Display position of cursor within note range pub note_cursor: usize, /// Range of notes to display pub note_start: usize, /// Display position of cursor within time range pub time_cursor: usize, - /// Focused scene+1, 0 is track list - pub scene_cursor: usize, - /// Collection of scenes - pub scenes: Vec, - /// Focused track+1, 0 is scene list - pub track_cursor: usize, - /// Collection of tracks - pub tracks: Vec, /// Paths to user directories xdg: Option>, /// Main audio outputs. @@ -48,7 +34,13 @@ pub struct App { /// Number of frames requested by process callback chunk_size: usize, /// Transport model and view. - pub transport: TransportToolbar, + pub transport: TransportToolbar, + /// Arraneger model and view. + pub arranger: Arranger, + /// Currently focused section + pub section: AppFocus, + /// Whether the current focus section has input priority + pub entered: bool, } impl App { @@ -58,7 +50,7 @@ impl App { let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0); Ok(Self { transport: TransportToolbar::new(Some(jack.transport())), - arranger_mode: false, + arranger: Arranger::new(), audio_outs: vec![], chain_mode: false, chunk_size: 0, @@ -68,14 +60,10 @@ impl App { midi_ins: vec![], note_cursor: 0, note_start: 2, - scene_cursor: 1, - scenes: vec![], section: AppFocus::default(), seq_mode: false, seq_buf: BufferedSequencerView::new(96, 16384), time_cursor: 0, - track_cursor: 1, - tracks: vec![], modal: first_run.then( ||Exit::boxed(crate::config::SetupModal(Some(xdg.clone()), false)) ), @@ -88,7 +76,7 @@ process!(App |self, _client, scope| { reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs ) = self.transport.update(&scope); self.chunk_size = chunk_size; - for track in self.tracks.iter_mut() { + for track in self.arranger.tracks.iter_mut() { track.process( self.midi_in.as_ref().map(|p|p.iter(&scope)), &self.transport.timebase, diff --git a/src/model/arranger.rs b/src/model/arranger.rs new file mode 100644 index 00000000..91c0da4d --- /dev/null +++ b/src/model/arranger.rs @@ -0,0 +1,228 @@ +use crate::{core::*, model::*}; + +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, +} + +#[derive(PartialEq)] +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), +} + +/// 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 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 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)), + } + } +} + +/// Represents the tracks and scenes of the composition. +impl Arranger { + pub fn new () -> Self { + Self { + mode: false, + selected: ArrangerFocus::Mix, + scenes: vec![], + tracks: vec![], + } + } + 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; + }, + _ => {} + } + } +} + +/// 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(match name { + Some(name) => Track::new(name, None, None)?, + None => Track::new(&self.track_default_name(), None, None)? + }); + 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<&Phrase> { + 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); + //} + //} + //} + } +} diff --git a/src/model/phrase.rs b/src/model/phrase.rs index 7be3e904..af2b1fcf 100644 --- a/src/model/phrase.rs +++ b/src/model/phrase.rs @@ -1,50 +1,4 @@ -use crate::{core::*, model::App}; - -impl App { - pub fn next_phrase (&mut self) { - 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 prev_phrase (&mut self) { - 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); - } - } - } - } - pub fn phrase (&self) -> Option<&Phrase> { - let (track_id, track) = self.track()?; - let (_, scene) = self.scene()?; - track.phrases.get((*scene.clips.get(track_id)?)?) - } - pub fn phrase_mut (&mut self) -> Option<&mut Phrase> { - let (track_id, _) = self.track()?; - let (_, scene) = self.scene()?; - let clip = (*scene.clips.get(track_id)?)?; - self.track_mut()?.1.phrases.get_mut(clip) - } -} +use crate::core::*; /// Define a MIDI phrase. #[macro_export] macro_rules! phrase { diff --git a/src/model/scene.rs b/src/model/scene.rs index 272a3576..7c23249a 100644 --- a/src/model/scene.rs +++ b/src/model/scene.rs @@ -1,48 +1,3 @@ -use crate::{core::*, model::*}; - -impl App { - pub fn next_scene (&mut self) { - self.scene_cursor = self.scenes.len().min(self.scene_cursor + 1); - } - pub fn prev_scene (&mut self) { - self.scene_cursor = self.scene_cursor.saturating_sub(1); - } - fn new_scene_name (&self) -> String { - format!("Scene {}", self.scenes.len() + 1) - } - pub fn add_scene (&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.new_scene_name(), clips) - }); - self.scene_cursor = self.scenes.len(); - Ok(&mut self.scenes[self.scene_cursor - 1]) - } - pub fn add_scene_with_clips ( - &mut self, - name: Option<&str>, - clips: &[Option] - ) -> Usually<&mut Scene> { - let name = name.ok_or_else(||self.new_scene_name())?; - self.scenes.push(Scene::new(&name, Vec::from(clips))); - self.scene_cursor = self.scenes.len(); - Ok(&mut self.scenes[self.scene_cursor - 1]) - } - pub fn scene (&self) -> Option<(usize, &Scene)> { - match self.scene_cursor { 0 => None, _ => { - let id = self.scene_cursor as usize - 1; - self.scenes.get(id).map(|t|(id, t)) - } } - } - pub fn scene_mut (&mut self) -> Option<(usize, &mut Scene)> { - match self.scene_cursor { 0 => None, _ => { - let id = self.scene_cursor as usize - 1; - self.scenes.get_mut(id).map(|t|(id, t)) - } } - } -} - /// A collection of phrases to play on each track. pub struct Scene { pub name: String, @@ -50,7 +5,7 @@ pub struct Scene { } impl Scene { - fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { + pub fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { Self { name: name.as_ref().into(), clips: clips.as_ref().iter().map(|x|x.clone()).collect() diff --git a/src/model/track.rs b/src/model/track.rs index ca1c4782..c7e47933 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -1,46 +1,5 @@ use crate::{core::*, model::*}; -impl App { - pub fn next_track (&mut self) { - self.track_cursor = self.tracks.len().min(self.track_cursor + 1); - } - pub fn prev_track (&mut self) { - self.track_cursor = self.track_cursor.saturating_sub(1); - } - pub fn new_track_name (&self) -> String { - format!("Track {}", self.tracks.len() + 1) - } - pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> { - self.tracks.push(match name { - Some(name) => Track::new(name, None, None)?, - None => Track::new(&self.new_track_name(), None, None)? - }); - self.track_cursor = self.tracks.len(); - Ok(&mut self.tracks[self.track_cursor - 1]) - } - pub fn del_track (&mut self) -> Usually { - if self.tracks.len() > 0 { - let track = self.tracks.remove(self.track_cursor); - self.track_cursor = self.track_cursor.saturating_sub(1); - track.midi_out.map(|port|self.client().unregister_port(port)).transpose()?; - return Ok(true) - } - Ok(false) - } - pub fn track (&self) -> Option<(usize, &Track)> { - match self.track_cursor { 0 => None, _ => { - let id = self.track_cursor as usize - 1; - self.tracks.get(id).map(|t|(id, t)) - } } - } - pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> { - match self.track_cursor { 0 => None, _ => { - let id = self.track_cursor as usize - 1; - self.tracks.get_mut(id).map(|t|(id, t)) - } } - } -} - /// A sequencer track. pub struct Track { pub name: String, @@ -72,7 +31,7 @@ pub struct Track { } impl Track { - fn new ( + pub fn new ( name: &str, phrases: Option>, devices: Option>, diff --git a/src/view.rs b/src/view.rs index 61cb2500..d8c33a25 100644 --- a/src/view.rs +++ b/src/view.rs @@ -9,8 +9,8 @@ submod! { render!(App |self, buf, area| { Split::down([ &self.transport, - &ArrangerView::new(&self, !self.arranger_mode), - &If(self.track_cursor > 0, &Split::right([ + &ArrangerView::new(&self, !self.arranger.mode), + &If(self.arranger.selected.is_clip(), &Split::right([ &ChainView::vertical(&self), &SequencerView::new(&self), ])) diff --git a/src/view/arranger.rs b/src/view/arranger.rs index 3e1b4ee3..3880f3d9 100644 --- a/src/view/arranger.rs +++ b/src/view/arranger.rs @@ -14,12 +14,17 @@ pub struct ArrangerView<'a> { impl<'a> ArrangerView<'a> { pub fn new (app: &'a App, vertical: bool) -> Self { Self { + vertical, focused: app.section == AppFocus::Arranger, entered: app.entered, - scenes: &app.scenes, - tracks: &app.tracks, - cursor: (app.track_cursor, app.scene_cursor), - vertical + scenes: &app.arranger.scenes, + tracks: &app.arranger.tracks, + cursor: match app.arranger.selected { + ArrangerFocus::Mix => (0, 0), + ArrangerFocus::Scene(s) => (0, s + 1), + ArrangerFocus::Track(t) => (t + 1, 0), + ArrangerFocus::Clip(t, s) => (t + 1, s + 1), + }, } } } @@ -100,8 +105,7 @@ impl<'a> ArrangerView<'a> { let hi = (track_index + 1 == self.cursor.0) && (scene_index + 1 == self.cursor.1); let style = Some(Nord::style_hi(self.focused, hi)); let y = 1 + y + 2 * scene_index as u16; - "┊".blit(buf, x, y, Some(Style::default().dim()))?; - "┊".blit(buf, x, y + 1, Some(Style::default().dim()))?; + "┊".blit(buf, x, y + 1, style)?; label.blit(buf, x, y, style)?; } if track_index + 1 == self.cursor.0 { diff --git a/src/view/chain.rs b/src/view/chain.rs index c601e260..0484244c 100644 --- a/src/view/chain.rs +++ b/src/view/chain.rs @@ -19,10 +19,7 @@ impl<'a> ChainView<'a> { direction, entered: app.entered, focused: app.section == AppFocus::Chain, - track: match app.track_cursor { - 0 => None, - _ => app.tracks.get(app.track_cursor - 1) - }, + track: app.arranger.track() } } } diff --git a/src/view/sequencer.rs b/src/view/sequencer.rs index 3310eedb..dfa54530 100644 --- a/src/view/sequencer.rs +++ b/src/view/sequencer.rs @@ -105,12 +105,9 @@ pub struct SequencerView<'a> { impl<'a> SequencerView<'a> { pub fn new (app: &'a App) -> Self { - let track = match app.track_cursor { - 0 => None, - _ => app.tracks.get(app.track_cursor - 1) - }; + let track = app.arranger.track(); Self { - phrase: app.phrase(), + phrase: app.arranger.phrase(), focused: app.section == AppFocus::Sequencer, entered: app.entered, ppq: app.transport.ppq(), @@ -120,7 +117,7 @@ impl<'a> SequencerView<'a> { time_zoom: app.seq_buf.time_zoom, note_cursor: app.note_cursor, note_start: app.note_start, - notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] }, + notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] }, notes_out: if let Some(track) = track { &track.notes_out } else { &[false;128] }, } }