From c6429986803fc0148a46a0fe3b6aac3c9cd5f587 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 1 Oct 2024 05:50:45 +0300 Subject: [PATCH] separate arranger/sequencer/transport tui view from core model --- crates/tek_sequencer/src/lib.rs | 2 +- crates/tek_sequencer/src/sequencer.rs | 1373 ++------------------- crates/tek_sequencer/src/sequencer_tui.rs | 1112 +++++++++++++++++ 3 files changed, 1240 insertions(+), 1247 deletions(-) create mode 100644 crates/tek_sequencer/src/sequencer_tui.rs diff --git a/crates/tek_sequencer/src/lib.rs b/crates/tek_sequencer/src/lib.rs index 17e39c06..dca60713 100644 --- a/crates/tek_sequencer/src/lib.rs +++ b/crates/tek_sequencer/src/lib.rs @@ -6,4 +6,4 @@ pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage}; pub(crate) use tek_core::jack::*; pub(crate) use std::sync::{Arc, RwLock}; -submod! { sequencer } +submod! { sequencer sequencer_tui } diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index cecabec7..d1637f6e 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -1,24 +1,5 @@ use crate::*; -const CORNERS: CornersTall = CornersTall(NOT_DIM_GREEN); - -tui_style!(GRAY_DIM = - Some(Color::Gray), None, None, Modifier::DIM, Modifier::empty()); -tui_style!(GRAY_NOT_DIM_BOLD = - Some(Color::Gray), None, None, Modifier::BOLD, Modifier::DIM); -tui_style!(NOT_DIM_BOLD = - None, None, None, Modifier::BOLD, Modifier::DIM); -tui_style!(NOT_DIM_GREEN = - Some(Color::Rgb(96, 255, 32)), Some(COLOR_BG1), None, Modifier::empty(), Modifier::DIM); -tui_style!(NOT_DIM = - None, None, None, Modifier::empty(), Modifier::DIM); -tui_style!(WHITE_NOT_DIM_BOLD = - Some(Color::White), None, None, Modifier::BOLD, Modifier::DIM); -tui_style!(STYLE_LABEL = - Some(Color::Reset), None, None, Modifier::empty(), Modifier::BOLD); -tui_style!(STYLE_VALUE = - Some(Color::White), None, None, Modifier::BOLD, Modifier::DIM); - /////////////////////////////////////////////////////////////////////////////////////////////////// /// Represents the tracks and scenes of the composition. @@ -201,158 +182,6 @@ impl Arranger { } } -impl Arranger { - pub fn rename_selected (&mut self) { - self.modal = Some(Box::new(ArrangerRenameModal::new( - self.selected, - &match self.selected { - ArrangerFocus::Mix => self.name.clone(), - ArrangerFocus::Track(t) => self.tracks[t].name.clone(), - ArrangerFocus::Scene(s) => self.scenes[s].name.clone(), - ArrangerFocus::Clip(t, s) => self.tracks[t].phrases[s].read().unwrap().name.clone(), - } - ))); - } -} - -impl Handle for Arranger { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(modal) = self.modal.as_mut() { - let result = modal.handle(from)?; - if from.is_done() { - self.modal = None; - } - return Ok(result) - } - match from.event() { - // mode_switch: switch the display mode - key!(KeyCode::Char('`')) => { - self.mode.to_next() - }, - // cursor_up: move cursor up - key!(KeyCode::Up) => { - match self.mode { - ArrangerViewMode::Horizontal => self.track_prev(), - _ => self.scene_prev(), - }; - self.show_phrase()?; - }, - // cursor_down - key!(KeyCode::Down) => { - match self.mode { - ArrangerViewMode::Horizontal => self.track_next(), - _ => self.scene_next(), - }; - self.show_phrase()?; - }, - // cursor left - key!(KeyCode::Left) => { - match self.mode { - ArrangerViewMode::Horizontal => self.scene_prev(), - _ => self.track_prev(), - }; - self.show_phrase()?; - }, - // cursor right - key!(KeyCode::Right) => { - match self.mode { - ArrangerViewMode::Horizontal => self.scene_next(), - _ => self.track_next(), - }; - self.show_phrase()?; - }, - // increment: use next clip here - key!(KeyCode::Char('.')) => { - self.phrase_next(); - }, - // decrement: use previous next clip here - key!(KeyCode::Char(',')) => { - self.phrase_prev(); - }, - // decrement: use previous clip here - key!(KeyCode::Enter) => { - self.activate(); - }, - // scene_add: add a new scene - key!(Ctrl-KeyCode::Char('a')) => { - self.scene_add(None)?; - }, - // track_add: add a new scene - key!(Ctrl-KeyCode::Char('t')) => { - self.track_add(None)?; - }, - // rename: add a new scene - key!(KeyCode::Char('n')) => { - self.rename_selected(); - }, - // length: add a new scene - key!(KeyCode::Char('l')) => { - todo!(); - }, - // color: set color of item at cursor - key!(KeyCode::Char('c')) => { - todo!(); - }, - _ => return Ok(None) - } - Ok(Some(true)) - } -} - -impl Content for Arranger { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(move |add|{ - match self.mode { - ArrangerViewMode::Horizontal => add(&HorizontalArranger(&self)), - ArrangerViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)) - }?; - add(&Align::SE(self.selected.description( - &self.tracks, - &self.scenes, - ).as_str())) - }) - } -} - -pub fn scene_ppqs ( - tracks: &[Sequencer], - scenes: &[Scene] -) -> Vec<(usize, usize)> { - let mut total = 0; - let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{ - let pulses = scene.pulses(tracks).max(96); - total = total + pulses; - (pulses, total - pulses) - }).collect(); - scenes.push((0, total)); - scenes -} - -pub fn track_clip_name_lengths (tracks: &[Sequencer]) -> Vec<(usize, usize)> { - let mut total = 0; - let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{ - let len = 4 + track.phrases - .iter() - .fold(track.name.read().unwrap().len(), |len, phrase|{ - len.max(phrase.read().unwrap().name.read().unwrap().len()) - }); - total = total + len; - (len, total - len) - }).collect(); - lengths.push((0, total)); - lengths -} - -impl Focusable for Arranger { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} - /////////////////////////////////////////////////////////////////////////////////////////////////// #[derive(PartialEq, Clone, Copy)] @@ -486,7 +315,6 @@ impl ArrangerFocus { /// Display mode of arranger #[derive(PartialEq)] pub enum ArrangerViewMode { Horizontal, Vertical(usize) } - /// Arranger display mode can be cycled impl ArrangerViewMode { /// Cycle arranger display mode @@ -503,585 +331,133 @@ impl ArrangerViewMode { /////////////////////////////////////////////////////////////////////////////////////////////////// -struct VerticalArranger<'a, E: Engine>(&'a Arranger, usize); -impl<'a> Content for VerticalArranger<'a, Tui> { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self(state, factor) = self; - let ppq = 96; - let (cols, rows) = match factor { - 0 => ( - track_clip_name_lengths(state.tracks.as_slice()), - scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice()), - ), - factor => ( - track_clip_name_lengths(state.tracks.as_slice()), - (0..=state.scenes.len()).map(|i|(factor*ppq, factor*ppq*i)).collect::>(), - ), - }; - //let height = rows.last().map(|(w,y)|(y+w)/ppq).unwrap_or(16); - let tracks: &[Sequencer] = state.tracks.as_ref(); - let scenes: &[Scene] = state.scenes.as_ref(); - let offset = 4 + scene_name_max_len(scenes) as u16; - Layers::new(move |add|{ - let rows: &[(usize, usize)] = rows.as_ref(); - let cols: &[(usize, usize)] = cols.as_ref(); - let track_titles = row!((track, (w, _)) in tracks.iter().zip(cols) => - (&track.name.read().unwrap().as_str() as &dyn Widget) - .min_xy(*w as u16, 2).push_x(offset)); - let scene_name = |scene, playing: bool, height|row!( - if playing { "▶ " } else { " " }, - (scene as &Scene).name.read().unwrap().as_str(), - ).fixed_xy(offset.saturating_sub(1), height); - let scene_clip = |scene, track: usize, w: u16, h: u16|Layers::new(move |add|{ - let mut color = Color::Rgb(40, 50, 30); - match (tracks.get(track), (scene as &Scene).clips.get(track)) { - (Some(track), Some(Some(clip))) => match track.phrases.get(*clip) { - Some(phrase) => { - let name = &(phrase as &Arc>).read().unwrap().name; - let name = name.read().unwrap(); - let name = format!("{clip:02} {}", name); - add(&name.as_str().push_x(1))?; - if (track as &Sequencer<_>).sequence == Some(*clip) { - color = COLOR_PLAYING - } else { - color = COLOR_BG1 - }; - }, - _ => {} - }, - _ => {} - }; - add(&Background(color)) - }).fixed_xy(w, h); - let tracks_clips = col!((scene, (pulses, _)) in scenes.iter().zip(rows) => { - let height = 1.max((pulses / 96) as u16); - let playing = scene.is_playing(tracks); - Stack::right(move |add| { - add(&scene_name(scene, playing, height))?; - for (track, (w, _x)) in cols.iter().enumerate() { - add(&scene_clip(scene, track, *w as u16, height))?; - } - Ok(()) - }).fixed_y(height) - }); - add(&VerticalArrangerGrid(offset, &rows, &cols))?; - add(&VerticalArrangerCursor(state.focused, state.selected, offset, &cols, &rows))?; - add(&col!(track_titles, tracks_clips))?; - Ok(()) - }) - .bg(Color::Rgb(28, 35, 25)) - .border(Lozenge(Style::default() - .bg(Color::Rgb(40, 50, 30)) - .fg(Color::Rgb(70, 80, 50)))) - } -} - -pub fn scene_name_max_len (scenes: &[Scene]) -> usize { - scenes.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct VerticalArrangerGrid<'a>(u16, &'a [(usize, usize)], &'a [(usize, usize)]); - -impl<'a> Widget for VerticalArrangerGrid<'a> { - type Engine = Tui; - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - let Self(offset, rows, cols) = self; - let style = Some(Style::default().fg(COLOR_SEPARATOR)); - for (_, x) in cols.iter() { - let x = offset + area.x() + *x as u16 - 1; - for y in area.y()..area.y2() { - to.blit(&"▎", x, y, style); - } - } - for (_, y) in rows.iter() { - let y = area.y() + (*y / 96) as u16 + 1; - if y >= to.buffer.area.height { - break - } - for x in area.x()..area.x2().saturating_sub(2) { - if x < to.buffer.area.x && y < to.buffer.area.y { - let cell = to.buffer.get_mut(x, y); - cell.modifier = Modifier::UNDERLINED; - cell.underline_color = COLOR_SEPARATOR; - } - } - } - Ok(()) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct VerticalArrangerCursor<'a>( - bool, ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)] +pub struct VerticalArranger<'a, E: Engine>( + pub &'a Arranger, pub usize +); +pub struct VerticalArrangerGrid<'a>( + pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)] +); +pub struct VerticalArrangerCursor<'a>( + pub bool, pub ArrangerFocus, pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)], ); -impl<'a> Widget for VerticalArrangerCursor<'a> { - type Engine = Tui; - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - let Self(focused, selected, offset, cols, rows) = *self; - let get_track_area = |t: usize| [ - offset + area.x() + cols[t].1 as u16 - 1, - area.y(), - cols[t].0 as u16, - area.h() - ]; - let get_scene_area = |s: usize| [ - area.x(), - 2 + area.y() + (rows[s].1 / 96) as u16, - area.w(), - (rows[s].0 / 96) as u16 - ]; - let get_clip_area = |t: usize, s: usize| [ - offset + area.x() + cols[t].1 as u16 - 1, - 2 + area.y() + (rows[s].1 / 96) as u16, - cols[t].0 as u16, - (rows[s].0 / 96) as u16 - ]; - let mut track_area: Option<[u16;4]> = None; - let mut scene_area: Option<[u16;4]> = None; - let mut clip_area: Option<[u16;4]> = None; - let area = match selected { - ArrangerFocus::Mix => { - if focused { - to.fill_bg(area, Color::Rgb(40, 50, 30)); - } - area - }, - ArrangerFocus::Track(t) => { - track_area = Some(get_track_area(t)); - area - }, - ArrangerFocus::Scene(s) => { - scene_area = Some(get_scene_area(s)); - area - }, - ArrangerFocus::Clip(t, s) => { - track_area = Some(get_track_area(t)); - scene_area = Some(get_scene_area(s)); - clip_area = Some(get_clip_area(t, s)); - area - }, - }; - if let Some([x, y, width, height]) = track_area { - to.fill_fg([x, y, 1, height], Color::Rgb(70, 80, 50)); - to.fill_fg([x + width, y, 1, height], Color::Rgb(70, 80, 50)); - } - if let Some([_, y, _, height]) = scene_area { - to.fill_ul([area.x(), y - 1, area.w(), 1], Color::Rgb(70, 80, 50)); - to.fill_ul([area.x(), y + height - 1, area.w(), 1], Color::Rgb(70, 80, 50)); - } - if focused { - if let Some(clip_area) = clip_area { - to.render_in(clip_area, &CORNERS)?; - to.fill_bg(clip_area, Color::Rgb(40, 50, 30)); - } else if let Some(track_area) = track_area { - to.render_in(track_area.clip_h(2), &CORNERS)?; - to.fill_bg(track_area, Color::Rgb(40, 50, 30)); - } else if let Some(scene_area) = scene_area { - to.render_in(scene_area.clip_w(offset-1), &CORNERS)?; - to.fill_bg(scene_area, Color::Rgb(40, 50, 30)); - } - } - Ok(()) - } -} - /////////////////////////////////////////////////////////////////////////////////////////////////// -struct HorizontalArranger<'a, E: Engine>( - &'a Arranger +pub struct HorizontalArranger<'a, E: Engine>( + pub &'a Arranger +); +pub struct TrackNameColumn<'a, E: Engine>( + pub &'a [Sequencer], pub ArrangerFocus +); +pub struct TrackMonitorColumn<'a, E: Engine>( + pub &'a [Sequencer] +); +pub struct TrackRecordColumn<'a, E: Engine>( + pub &'a [Sequencer] +); +pub struct TrackOverdubColumn<'a, E: Engine>( + pub &'a [Sequencer] +); +pub struct TrackEraseColumn<'a, E: Engine>( + pub &'a [Sequencer] +); +pub struct TrackGainColumn<'a, E: Engine>( + pub &'a [Sequencer] +); +pub struct TrackScenesColumn<'a, E: Engine>( + pub &'a [Sequencer], pub &'a [Scene], pub ArrangerFocus ); - -impl<'a> Content for HorizontalArranger<'a, Tui> { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Arranger { tracks, focused, selected, scenes, .. } = self.0; - let tracks = tracks.as_slice(); - Layers::new(|add|{ - add(&focused.then_some(Background(Color::Rgb(40, 50, 30))))?; - add(&Stack::right(|add|{ - add(&TrackNameColumn(tracks, *selected))?; - add(&TrackMonitorColumn(tracks))?; - add(&TrackRecordColumn(tracks))?; - add(&TrackOverdubColumn(tracks))?; - add(&TrackEraseColumn(tracks))?; - add(&TrackGainColumn(tracks))?; - add(&TrackScenesColumn(tracks, scenes.as_slice(), *selected))?; - Ok(()) - })) - }) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct TrackNameColumn<'a>(&'a [Sequencer], ArrangerFocus); - -impl<'a> Widget for TrackNameColumn<'a> { - type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, _to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks, selected) = self; - //let yellow = Some(Style::default().yellow().bold().not_dim()); - //let white = Some(Style::default().white().bold().not_dim()); - //let area = to.area(); - //let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()]; - //let offset = 0; // track scroll offset - //for y in 0..area.h() { - //if y == 0 { - //to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2 + offset; - //if let Some(track) = tracks.get(index) { - //let selected = selected.track() == Some(index); - //let style = if selected { yellow } else { white }; - //to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?; - //to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?; - //} - //} - //} - //Ok(Some(area)) - } -} - -pub fn track_name_max_len (tracks: &[Sequencer]) -> usize { - tracks.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct TrackMonitorColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackMonitorColumn<'a> { - type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, _to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let on = Some(Style::default().not_dim().green().bold()); - //let off = Some(DIM); - //area.x += 1; - //for y in 0..area.h() { - //if y == 0 { - ////" MON ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(track) = tracks.get(index) { - //let style = if track.monitoring { on } else { off }; - //to.blit(&" MON ", area.x(), area.y() + y, style)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct TrackRecordColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackRecordColumn<'a> { - type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, _to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let on = Some(Style::default().not_dim().red().bold()); - //let off = Some(Style::default().dim()); - //area.x += 1; - //for y in 0..area.h() { - //if y == 0 { - ////" REC ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(track) = tracks.get(index) { - //let style = if track.recording { on } else { off }; - //to.blit(&" REC ", area.x(), area.y() + y, style)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct TrackOverdubColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackOverdubColumn<'a> { - type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, _to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let on = Some(Style::default().not_dim().yellow().bold()); - //let off = Some(Style::default().dim()); - //area.x = area.x + 1; - //for y in 0..area.h() { - //if y == 0 { - ////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(track) = tracks.get(index) { - //to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub { - //on - //} else { - //off - //})?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct TrackEraseColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackEraseColumn<'a> { - type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, _to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let off = Some(Style::default().dim()); - //area.x = area.x + 1; - //for y in 0..area.h() { - //if y == 0 { - ////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(_) = tracks.get(index) { - //to.blit(&" DEL ", area.x(), area.y() + y, off)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct TrackGainColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackGainColumn<'a> { - type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, _to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let off = Some(Style::default().dim()); - //area.x = area.x() + 1; - //for y in 0..area.h() { - //if y == 0 { - ////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(_) = tracks.get(index) { - //to.blit(&" +0.0 ", area.x(), area.y() + y, off)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 7; - //Ok(Some(area)) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -struct TrackScenesColumn<'a>(&'a [Sequencer], &'a [Scene], ArrangerFocus); - -impl<'a> Widget for TrackScenesColumn<'a> { - type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let Self(tracks, scenes, selected) = self; - let area = to.area(); - let mut x2 = 0; - let [x, y, _, height] = area; - for (scene_index, scene) in scenes.iter().enumerate() { - let active_scene = selected.scene() == Some(scene_index); - let sep = Some(if active_scene { - Style::default().yellow().not_dim() - } else { - Style::default().dim() - }); - for y in y+1..y+height { - to.blit(&"│", x + x2, y, sep); - } - let name = scene.name.read().unwrap(); - let mut x3 = name.len() as u16; - to.blit(&*name, x + x2, y, sep); - for (i, clip) in scene.clips.iter().enumerate() { - let active_track = selected.track() == Some(i); - if let Some(clip) = clip { - let y2 = y + 2 + i as u16 * 2; - let label = match tracks[i].phrases.get(*clip) { - Some(phrase) => &format!("{}", phrase.read().unwrap().name.read().unwrap()), - None => "...." - }; - to.blit(&label, x + x2, y2, Some(if active_track && active_scene { - Style::default().not_dim().yellow().bold() - } else { - Style::default().not_dim() - })); - x3 = x3.max(label.len() as u16) - } - } - x2 = x2 + x3 + 1; - } - //Ok(Some([x, y, x2, height])) - Ok(()) - } -} /////////////////////////////////////////////////////////////////////////////////////////////////// /// Appears on first run (i.e. if state dir is missing). pub struct ArrangerRenameModal { - _engine: std::marker::PhantomData, - done: bool, - target: ArrangerFocus, - value: String, - result: Arc>, - cursor: usize + _engine: std::marker::PhantomData, + pub done: bool, + pub target: ArrangerFocus, + pub value: String, + pub result: Arc>, + pub cursor: usize } impl ArrangerRenameModal { pub fn new (target: ArrangerFocus, value: &Arc>) -> Self { Self { _engine: Default::default(), - done: false, + done: false, + value: value.read().unwrap().clone(), + cursor: value.read().unwrap().len(), + result: value.clone(), target, - value: value.read().unwrap().clone(), - cursor: value.read().unwrap().len(), - result: value.clone(), - } - } -} - -impl Content for ArrangerRenameModal { - type Engine = Tui; - fn content (&self) -> impl Widget { - todo!(); - Layers::new(|add|{Ok(())}) - //let area = to.area(); - //let y = area.y() + area.h() / 2; - //let bg_area = [1, y - 1, area.w() - 2, 3]; - //to.fill_bg(bg_area, COLOR_BG1); - //Lozenge(Style::default().bold().white().dim()).draw(to.with_rect(bg_area)); - //let label = match self.target { - //ArrangerFocus::Mix => "Rename project:", - //ArrangerFocus::Track(_) => "Rename track:", - //ArrangerFocus::Scene(_) => "Rename scene:", - //ArrangerFocus::Clip(_, _) => "Rename clip:", - //}; - //let style = Some(Style::default().not_bold().white().not_dim()); - //to.blit(&label, area.x() + 3, y, style); - //let style = Some(Style::default().bold().white().not_dim()); - //to.blit(&self.value, area.x() + 3 + label.len() as u16 + 1, y, style); - //let style = Some(Style::default().bold().white().not_dim().reversed()); - //to.blit(&"▂", area.x() + 3 + label.len() as u16 + 1 + self.cursor as u16, y, style); - //Ok(Some(area)) - //Ok(()) - } -} - -impl Handle for ArrangerRenameModal { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - TuiEvent::Input(Event::Key(k)) => { - match k.code { - KeyCode::Esc => { - self.exit(); - }, - KeyCode::Enter => { - *self.result.write().unwrap() = self.value.clone(); - self.exit(); - }, - KeyCode::Left => { - self.cursor = self.cursor.saturating_sub(1); - }, - KeyCode::Right => { - self.cursor = self.value.len().min(self.cursor + 1) - }, - KeyCode::Backspace => { - let last = self.value.len().saturating_sub(1); - self.value = format!("{}{}", - &self.value[0..self.cursor.min(last)], - &self.value[self.cursor.min(last)..last] - ); - self.cursor = self.cursor.saturating_sub(1) - } - KeyCode::Char(c) => { - self.value.insert(self.cursor, c); - self.cursor = self.value.len().min(self.cursor + 1) - }, - _ => {} - } - Ok(Some(true)) - }, - _ => Ok(None), } } } impl Exit for ArrangerRenameModal { - fn exited (&self) -> bool { - self.done + fn exited (&self) -> bool { self.done } + fn exit (&mut self) { self.done = true } +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A collection of phrases to play on each track. +#[derive(Default)] +pub struct Scene { + pub name: Arc>, + pub clips: Vec>, +} +impl Scene { + pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { + let mut name = None; + let mut clips = vec![]; + edn!(edn in args { + Edn::Map(map) => { + let key = map.get(&Edn::Key(":name")); + if let Some(Edn::Str(n)) = key { + name = Some(*n); + } else { + panic!("unexpected key in scene '{name:?}': {key:?}") + } + }, + Edn::Symbol("_") => { + clips.push(None); + }, + Edn::Int(i) => { + clips.push(Some(*i as usize)); + }, + _ => panic!("unexpected in scene '{name:?}': {edn:?}") + }); + let scene = Self::new(name.unwrap_or(""), clips); + Ok(scene) } - fn exit (&mut self) { - self.done = true + pub fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { + let name = Arc::new(RwLock::new(name.as_ref().into())); + let clips = clips.as_ref().iter().map(|x|x.clone()).collect(); + Self { name, clips, } + } + /// Returns the pulse length of the longest phrase in the scene + pub fn pulses (&self, tracks: &[Sequencer]) -> usize { + self.clips.iter().enumerate() + .filter_map(|(i, c)|c + .map(|c|tracks + .get(i) + .map(|track|track + .phrases + .get(c)))) + .filter_map(|p|p) + .filter_map(|p|p) + .fold(0, |a, p|a.max(p.read().unwrap().length)) + } + /// Returns true if all phrases in the scene are currently playing + pub fn is_playing (&self, tracks: &[Sequencer]) -> bool { + self.clips.iter().enumerate() + .all(|(track_index, phrase_index)|match phrase_index { + Some(i) => tracks + .get(track_index) + .map(|track|track.sequence == Some(*i)) + .unwrap_or(false), + None => true + }) } } @@ -1089,24 +465,22 @@ impl Exit for ArrangerRenameModal { /// Phrase editor. pub struct Sequencer { - pub name: Arc>, - pub mode: bool, - pub focused: bool, - pub entered: bool, - - pub phrase: Option>>, - pub transport: Option>>>, - pub buffer: BigBuffer, - pub keys: Buffer, + pub name: Arc>, + pub mode: bool, + pub focused: bool, + pub entered: bool, + pub phrase: Option>>, + pub transport: Option>>>, + pub buffer: BigBuffer, + pub keys: Buffer, /// Highlight input keys - pub keys_in: [bool; 128], + 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, + pub keys_out: [bool; 128], + pub now: usize, + pub ppq: usize, + pub note_axis: FixedAxis, + pub time_axis: ScaledAxis, /// Play input through output. pub monitoring: bool, /// Write input to sequence. @@ -1154,8 +528,8 @@ impl Sequencer { now: 0, ppq: 96, transport: None, - note_axis: FixedAxis { start: 12, point: Some(36) }, - time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, + note_axis: FixedAxis { start: 12, point: Some(36) }, + time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, } } pub fn toggle_monitor (&mut self) { @@ -1282,129 +656,7 @@ impl Sequencer { _ => {} } } - /// Select which pattern to display. This pre-renders it to the buffer at full resolution. - pub fn show (&mut self, phrase: Option<&Arc>>) -> Usually<()> { - self.phrase = phrase.map(Clone::clone); - if let Some(ref phrase) = self.phrase { - let width = usize::MAX.min(phrase.read().unwrap().length); - let mut buffer = BigBuffer::new(width, 64); - let phrase = phrase.read().unwrap(); - Self::fill_seq_bg(&mut buffer, phrase.length, self.ppq)?; - Self::fill_seq_fg(&mut buffer, &phrase)?; - self.buffer = buffer; - } else { - self.buffer = Default::default(); - } - Ok(()) - } - - fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) -> Usually<()> { - for x in 0..buf.width { - if x as usize >= length { - break - } - let style = Style::default(); - buf.get_mut(x, 0).map(|cell|{ - cell.set_char('-'); - cell.set_style(style); - }); - for y in 0 .. buf.height { - buf.get_mut(x, y).map(|cell|{ - cell.set_char(if ppq == 0 { - '·' - } else if x % (4 * ppq) == 0 { - '│' - } else if x % ppq == 0 { - '╎' - } else { - '·' - }); - cell.set_fg(Color::Gray); - cell.modifier = Modifier::DIM; - }); - } - } - Ok(()) - } - - fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) -> Usually<()> { - let mut notes_on = [false;128]; - for x in 0..buf.width { - if x as usize >= phrase.length { - break - } - if let Some(notes) = phrase.notes.get(x as usize) { - if phrase.percussive { - for note in notes { - match note { - MidiMessage::NoteOn { key, .. } => - notes_on[key.as_int() as usize] = true, - _ => {} - } - } - } else { - for note in notes { - match note { - MidiMessage::NoteOn { key, .. } => - notes_on[key.as_int() as usize] = true, - MidiMessage::NoteOff { key, .. } => - notes_on[key.as_int() as usize] = false, - _ => {} - } - } - } - for y in 0..buf.height/2 { - if y >= 64 { - break - } - if let Some(block) = half_block( - notes_on[y as usize * 2], - notes_on[y as usize * 2 + 1], - ) { - buf.get_mut(x, y).map(|cell|{ - cell.set_char(block); - cell.set_fg(Color::White); - }); - } - } - if phrase.percussive { - notes_on.fill(false); - } - } - } - Ok(()) - } - - pub(crate) fn style_focus (&self) -> Option