From 77519dbb5cd0f7d9d8eedee56832f91b8019b422 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 12 Sep 2024 15:30:38 +0300 Subject: [PATCH] unify arranger, sequencer, transport --- crates/tek_sequencer/src/arranger.rs | 900 ++++++++++++++++++- crates/tek_sequencer/src/arranger_focus.rs | 101 --- crates/tek_sequencer/src/arranger_handle.rs | 97 -- crates/tek_sequencer/src/arranger_rename.rs | 107 --- crates/tek_sequencer/src/arranger_track.rs | 54 -- crates/tek_sequencer/src/arranger_view_h.rs | 263 ------ crates/tek_sequencer/src/arranger_view_v.rs | 262 ------ crates/tek_sequencer/src/lib.rs | 16 - crates/tek_sequencer/src/sequencer.rs | 487 ++++++++++ crates/tek_sequencer/src/sequencer_cli.rs | 37 - crates/tek_sequencer/src/sequencer_handle.rs | 46 - crates/tek_sequencer/src/sequencer_main.rs | 37 + crates/tek_sequencer/src/sequencer_view.rs | 203 ----- crates/tek_sequencer/src/sequencer_view_h.rs | 241 ----- crates/tek_sequencer/src/sequencer_view_v.rs | 0 crates/tek_sequencer/src/transport.rs | 202 +++++ crates/tek_sequencer/src/transport_handle.rs | 68 -- crates/tek_sequencer/src/transport_view.rs | 136 --- 18 files changed, 1618 insertions(+), 1639 deletions(-) delete mode 100644 crates/tek_sequencer/src/arranger_focus.rs delete mode 100644 crates/tek_sequencer/src/arranger_handle.rs delete mode 100644 crates/tek_sequencer/src/arranger_rename.rs delete mode 100644 crates/tek_sequencer/src/arranger_track.rs delete mode 100644 crates/tek_sequencer/src/arranger_view_h.rs delete mode 100644 crates/tek_sequencer/src/arranger_view_v.rs delete mode 100644 crates/tek_sequencer/src/sequencer_cli.rs delete mode 100644 crates/tek_sequencer/src/sequencer_handle.rs delete mode 100644 crates/tek_sequencer/src/sequencer_view.rs delete mode 100644 crates/tek_sequencer/src/sequencer_view_h.rs delete mode 100644 crates/tek_sequencer/src/sequencer_view_v.rs delete mode 100644 crates/tek_sequencer/src/transport_handle.rs delete mode 100644 crates/tek_sequencer/src/transport_view.rs diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index f2f74642..27103ad0 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -106,14 +106,22 @@ impl Widget for Arranger { } fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { let area = (|to|match self.mode { - ArrangerViewMode::Horizontal => - super::arranger_view_h::draw(self, to), - ArrangerViewMode::VerticalCompact1 => - super::arranger_view_v::draw_compact_1(self, to), - ArrangerViewMode::VerticalCompact2 => - super::arranger_view_v::draw_compact_2(self, to), - ArrangerViewMode::VerticalExpanded => - super::arranger_view_v::draw_expanded(self, to), + ArrangerViewMode::Horizontal => draw_h(self, to), + ArrangerViewMode::VerticalCompact1 => { + let track_cols = track_clip_name_lengths(self.tracks.as_slice()); + let scene_rows = (0..=self.scenes.len()).map(|i|(96, 96*i)).collect::>(); + draw_v(self, to, track_cols.as_slice(), scene_rows.as_slice()) + }, + ArrangerViewMode::VerticalCompact2 => { + let track_cols = track_clip_name_lengths(self.tracks.as_slice()); + let scene_rows = (0..=self.scenes.len()).map(|i|(192, 192*i)).collect::>(); + draw_v(self, to, track_cols.as_slice(), scene_rows.as_slice()) + }, + ArrangerViewMode::VerticalExpanded => { + let track_cols = track_clip_name_lengths(self.tracks.as_slice()); + let scene_rows = scene_ppqs(self.tracks.as_slice(), self.scenes.as_slice()); + draw_v(self, to, track_cols.as_slice(), scene_rows.as_slice()) + }, })(&mut to.alter_area(|[x, y, w, h]|[ x + 1, y + 1, @@ -137,3 +145,879 @@ impl Focusable for Arranger { self.focused = focused } } + +#[derive(PartialEq, Clone, Copy)] +/// 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_mix (&self) -> bool { + match self { Self::Mix => true, _ => false } + } + 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(t) => if *t == 0 { + Self::Mix + } else { + Self::Track(*t - 1) + }, + Self::Clip(t, s) => if *t == 0 { + Self::Scene(*s) + } else { + 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::Clip(*t, 0), + 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(s) => if *s == 0 { + Self::Mix + } else { + Self::Scene(*s - 1) + }, + Self::Clip(t, s) => if *s == 0 { + Self::Track(*t) + } else { + Self::Clip(*t, s.saturating_sub(1)) + } + } + } +} + + +impl Handle for Arranger { + fn handle (&mut self, from: &Tui) -> Perhaps { + if let Some(modal) = self.modal.as_mut() { + let result = modal.handle(from)?; + if modal.exited() { + self.modal = None; + } + return Ok(result) + } + match from.event() { + // mode_switch: switch the display mode + key!(KeyCode::Char('`')) => { + self.mode.to_next(); + Ok(Some(true)) + }, + // cursor_up: move cursor up + key!(KeyCode::Up) => { + match self.mode { + ArrangerViewMode::Horizontal => self.track_prev(), + _ => self.scene_prev(), + }; + self.show_phrase()?; + Ok(Some(true)) + }, + // cursor_down + key!(KeyCode::Down) => { + match self.mode { + ArrangerViewMode::Horizontal => self.track_next(), + _ => self.scene_next(), + }; + self.show_phrase()?; + Ok(Some(true)) + }, + // cursor left + key!(KeyCode::Left) => { + match self.mode { + ArrangerViewMode::Horizontal => self.scene_prev(), + _ => self.track_prev(), + }; + self.show_phrase()?; + Ok(Some(true)) + }, + // cursor right + key!(KeyCode::Right) => { + match self.mode { + ArrangerViewMode::Horizontal => self.scene_next(), + _ => self.track_next(), + }; + self.show_phrase()?; + Ok(Some(true)) + }, + // increment: use next clip here + key!(KeyCode::Char('.')) => { + self.phrase_next(); + Ok(Some(true)) + }, + // decrement: use previous next clip here + key!(KeyCode::Char(',')) => { + self.phrase_prev(); + Ok(Some(true)) + }, + // decrement: use previous clip here + key!(KeyCode::Enter) => { + self.activate(); + Ok(Some(true)) + }, + // scene_add: add a new scene + key!(Ctrl-KeyCode::Char('a')) => { + self.scene_add(None)?; + Ok(Some(true)) + }, + // track_add: add a new scene + key!(Ctrl-KeyCode::Char('t')) => { + self.track_add(None)?; + Ok(Some(true)) + }, + // rename: add a new scene + key!(KeyCode::Char('n')) => { + self.rename_selected(); + Ok(Some(true)) + }, + // length: add a new scene + key!(KeyCode::Char('l')) => { + todo!(); + Ok(Some(true)) + }, + // color: set color of item at cursor + key!(KeyCode::Char('c')) => { + todo!(); + Ok(Some(true)) + }, + _ => Ok(None) + } + } +} +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(), + } + ))); + } +} +/// 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 +} +impl ArrangerRenameModal { + pub fn new (target: ArrangerFocus, value: &Arc>) -> Self { + Self { + _engine: Default::default(), + done: false, + target, + value: value.read().unwrap().clone(), + cursor: value.read().unwrap().len(), + result: value.clone(), + } + } +} +impl Widget for ArrangerRenameModal { + type Engine = Tui; + fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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, Nord::BG0); + 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)) + } +} +impl Handle for ArrangerRenameModal { + fn handle (&mut self, from: &Tui) -> 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 exit (&mut self) { + self.done = true + } +} + +/// Track management methods +impl Arranger { + pub fn track (&self) -> Option<&Sequencer> { + self.selected.track().map(|t|self.tracks.get(t)).flatten() + } + pub fn track_mut (&mut self) -> Option<&mut Sequencer> { + self.selected.track().map(|t|self.tracks.get_mut(t)).flatten() + } + pub fn track_next (&mut self) { + self.selected.track_next(self.tracks.len() - 1) + } + pub fn track_prev (&mut self) { + self.selected.track_prev() + } + pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Sequencer> { + self.tracks.push(name.map_or_else( + || Sequencer::new(&self.track_default_name()), + |name| Sequencer::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) + } +} + +pub fn track_name_max_len (tracks: &[Sequencer]) -> usize { + tracks.iter() + .map(|s|s.name.read().unwrap().len()) + .fold(0, usize::max) +} + +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 +} + +/// Draw arranger with 1 row per scene. +pub fn draw_v_compact_1 <'a> ( + state: &Arranger, to: &mut Tui +) -> Perhaps<[u16;4]> { + let track_cols = track_clip_name_lengths(state.tracks.as_slice()); + let scene_rows = (0..=state.scenes.len()).map(|i|(96, 96*i)).collect::>(); + draw_v(state, to, track_cols.as_slice(), scene_rows.as_slice()) +} + +/// Draw arranger with 2 rows per scene. +pub fn draw_v_compact_2 <'a> ( + state: &Arranger, to: &mut Tui +) -> Perhaps<[u16;4]> { + let track_cols = track_clip_name_lengths(state.tracks.as_slice()); + let scene_rows = (0..=state.scenes.len()).map(|i|(192, 192*i)).collect::>(); + draw_v(state, to, track_cols.as_slice(), scene_rows.as_slice()) +} + +/// Draw arranger with number of rows per scene proportional to duration of scene. +pub fn draw_v_expanded <'a> ( + state: &Arranger, to: &mut Tui +) -> Perhaps<[u16;4]> { + let track_cols = track_clip_name_lengths(state.tracks.as_slice()); + let scene_rows = scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice()); + draw_v(state, to, track_cols.as_slice(), scene_rows.as_slice()) +} + +pub fn draw_v <'a, 'b> ( + state: &Arranger, + to: &mut Tui, + cols: &'b [(usize, usize)], + rows: &'b [(usize, usize)], +) -> Perhaps<[u16;4]> { + let area = to.area(); + let area = [area.x(), area.y(), area.w(), 2 + (rows[rows.len() - 1].1 / 96) as u16]; + let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16; + let tracks = state.tracks.as_ref(); + let scenes = state.scenes.as_ref(); + Layers::new(|add|{ + //.add_ref(&FillBg(Color::Rgb(30, 33, 36)))//COLOR_BG1))//bg_lo(state.focused, state.entered))) + add(&ColumnSeparators(offset, cols))?; + add(&RowSeparators(rows))?; + add(&CursorFocus(state.selected, offset, cols, rows))?; + add(&Split::right(|add|{ + add(&TracksHeader(offset, cols, tracks))?; + add(&SceneRows(offset, cols, rows, tracks, scenes)) + })) + }).render(to.with_rect(area)) +} + +struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]); + +impl<'a> Widget for ColumnSeparators<'a> { + type Engine = Tui; + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let Self(offset, cols) = self; + let style = Some(Style::default().fg(Nord::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)?; + } + } + Ok(Some(area)) + } +} + +struct RowSeparators<'a>(&'a [(usize, usize)]); + +impl<'a> Widget for RowSeparators<'a> { + type Engine = Tui; + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let Self(rows) = self; + 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) { + let cell = to.buffer().get_mut(x, y); + cell.modifier = Modifier::UNDERLINED; + cell.underline_color = Nord::SEPARATOR; + } + } + Ok(Some(area)) + } +} + +struct CursorFocus<'a>( + ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)] +); + +impl<'a> Widget for CursorFocus<'a> { + type Engine = Tui; + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let Self(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 => { + to.fill_bg(area, COLOR_BG0); + 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_BG5); + to.fill_fg([x + width, y, 1, height], COLOR_BG5); + } + if let Some([_, y, _, height]) = scene_area { + to.fill_ul([area.x(), y - 1, area.w(), 1], COLOR_BG5); + to.fill_ul([area.x(), y + height - 1, area.w(), 1], COLOR_BG5); + } + if let Some(clip_area) = clip_area { + to.fill_bg(clip_area, COLOR_BG0); + } else if let Some(track_area) = track_area { + to.fill_bg(track_area, COLOR_BG0); + } else if let Some(scene_area) = scene_area { + to.fill_bg(scene_area, COLOR_BG0); + } + Ok(Some(area)) + } +} + +struct TracksHeader<'a>(u16, &'a[(usize, usize)], &'a [Sequencer]); + +impl<'a> Widget for TracksHeader<'a> { + type Engine = Tui; + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let Self(offset, track_cols, tracks) = *self; + let [x, y, width, _] = area; + for (track, (w, x)) in tracks.iter().zip(track_cols) { + let x = *x as u16; + if x > width { + break + } + let name = track.name.read().unwrap(); + to.fill_bg([offset + x, y, *w as u16, 2], COLOR_BG1); + to.blit(&*name, offset + x + 1, y, Some(Style::default().white()))?; + } + Ok(Some([x, y, width, 2])) + } +} + +struct SceneRows<'a>(u16, &'a[(usize, usize)], &'a[(usize, usize)], &'a[Sequencer], &'a[Scene]); + +impl<'a> Widget for SceneRows<'a> { + type Engine = Tui; + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let Self(offset, track_cols, scene_rows, tracks, scenes) = *self; + let black = Some(Style::default().fg(Nord::SEPARATOR)); + let [_, mut y, _, _height] = area; + for (_, x) in track_cols.iter() { + let x = *x as u16; + if x > 0 { + for y in area.y()-2..y-2 { + to.blit(&"▎", x - 1, y, black)?; + } + } + } + for (scene, (pulses, _)) in scenes.iter().zip(scene_rows) { + //if y > height { + //break + //} + let h = 1.max((pulses / 96) as u16); + SceneRow(tracks, scene, track_cols, offset) + .render(to.with_area(area.x(), y, area.w(), h))?; + y = y + h + } + Ok(Some(area)) + } +} + +struct SceneRow<'a>(&'a[Sequencer], &'a Scene, &'a[(usize, usize)], u16); + +impl<'a> Widget for SceneRow<'a> { + type Engine = Tui; + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let Self(tracks, scene, track_cols, offset) = self; + let [x, y, width, _] = area; + let playing = scene.is_playing(tracks); + to.blit(&if playing { "▶" } else { " " }, x, y, None)?; + to.blit(&*scene.name.read().unwrap(), x + 1, y, Some(Style::default().white()))?; + to.fill_bg([x, y, offset.saturating_sub(1), area.h()], COLOR_BG1); + for (track, (w, x)) in track_cols.iter().enumerate() { + let x = *x as u16 + offset; + if x > width { + break + } + if let (Some(track), Some(Some(clip))) = ( + tracks.get(track), scene.clips.get(track) + ) { + SceneClip(track, *clip).render(to.with_area(x, y, *w as u16, area.h()))?; + } + } + Ok(Some(area)) + } +} + +struct SceneClip<'a>(&'a Sequencer, usize); + +impl<'a> Widget for SceneClip<'a> { + type Engine = Tui; + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let Self(track, clip) = self; + let style = Some(Style::default().white()); + if let Some(phrase) = track.phrases.get(*clip) { + let phrase = phrase.read().unwrap(); + let name = phrase.name.read().unwrap(); + to.blit(&format!("{clip:02} {name}"), area.x() + 1, area.y(), style)?; + to.fill_bg(area, if track.sequence == Some(*clip) { + Nord::PLAYING + } else { + COLOR_BG1 + }); + } else { + to.fill_bg(area, COLOR_BG0) + } + Ok(Some(area)) + } +} + +pub fn draw_h (state: &Arranger, to: &mut Tui) -> Perhaps<[u16;4]> { + let area = to.area(); + let area = [area.x(), area.y(), area.w(), area.h().min((2 + state.tracks.len() * 2) as u16)]; + let tracks = state.tracks.as_slice(); + Layers::new(|add|{ + add(&state.focused.then_some(FillBg(COLOR_BG0)))?; + add(&Split::right(|add|{ + add(&TrackNameColumn(tracks, state.selected))?; + add(&TrackMonitorColumn(tracks))?; + add(&TrackRecordColumn(tracks))?; + add(&TrackOverdubColumn(tracks))?; + add(&TrackEraseColumn(tracks))?; + add(&TrackGainColumn(tracks))?; + add(&TrackScenesColumn(tracks, state.scenes.as_slice(), state.selected))?; + Ok(()) + })) + }).render(to.with_rect(area)) +} + +struct TrackNameColumn<'a>(&'a [Sequencer], ArrangerFocus); + +impl<'a> Widget for TrackNameColumn<'a> { + type Engine = Tui; + fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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)) + } +} + +struct TrackMonitorColumn<'a>(&'a [Sequencer]); + +impl<'a> Widget for TrackMonitorColumn<'a> { + type Engine = Tui; + fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + 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])) + } +} diff --git a/crates/tek_sequencer/src/arranger_focus.rs b/crates/tek_sequencer/src/arranger_focus.rs deleted file mode 100644 index 5700dfee..00000000 --- a/crates/tek_sequencer/src/arranger_focus.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::*; - -#[derive(PartialEq, Clone, Copy)] -/// 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_mix (&self) -> bool { - match self { Self::Mix => true, _ => false } - } - 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(t) => if *t == 0 { - Self::Mix - } else { - Self::Track(*t - 1) - }, - Self::Clip(t, s) => if *t == 0 { - Self::Scene(*s) - } else { - 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::Clip(*t, 0), - 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(s) => if *s == 0 { - Self::Mix - } else { - Self::Scene(*s - 1) - }, - Self::Clip(t, s) => if *s == 0 { - Self::Track(*t) - } else { - Self::Clip(*t, s.saturating_sub(1)) - } - } - } -} - diff --git a/crates/tek_sequencer/src/arranger_handle.rs b/crates/tek_sequencer/src/arranger_handle.rs deleted file mode 100644 index 1dbbdc72..00000000 --- a/crates/tek_sequencer/src/arranger_handle.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::*; - -impl Handle for Arranger { - fn handle (&mut self, from: &Tui) -> Perhaps { - if let Some(modal) = self.modal.as_mut() { - let result = modal.handle(from)?; - if modal.exited() { - self.modal = None; - } - return Ok(result) - } - match from.event() { - // mode_switch: switch the display mode - key!(KeyCode::Char('`')) => { - self.mode.to_next(); - Ok(Some(true)) - }, - // cursor_up: move cursor up - key!(KeyCode::Up) => { - match self.mode { - ArrangerViewMode::Horizontal => self.track_prev(), - _ => self.scene_prev(), - }; - self.show_phrase()?; - Ok(Some(true)) - }, - // cursor_down - key!(KeyCode::Down) => { - match self.mode { - ArrangerViewMode::Horizontal => self.track_next(), - _ => self.scene_next(), - }; - self.show_phrase()?; - Ok(Some(true)) - }, - // cursor left - key!(KeyCode::Left) => { - match self.mode { - ArrangerViewMode::Horizontal => self.scene_prev(), - _ => self.track_prev(), - }; - self.show_phrase()?; - Ok(Some(true)) - }, - // cursor right - key!(KeyCode::Right) => { - match self.mode { - ArrangerViewMode::Horizontal => self.scene_next(), - _ => self.track_next(), - }; - self.show_phrase()?; - Ok(Some(true)) - }, - // increment: use next clip here - key!(KeyCode::Char('.')) => { - self.phrase_next(); - Ok(Some(true)) - }, - // decrement: use previous next clip here - key!(KeyCode::Char(',')) => { - self.phrase_prev(); - Ok(Some(true)) - }, - // decrement: use previous clip here - key!(KeyCode::Enter) => { - self.activate(); - Ok(Some(true)) - }, - // scene_add: add a new scene - key!(Ctrl-KeyCode::Char('a')) => { - self.scene_add(None)?; - Ok(Some(true)) - }, - // track_add: add a new scene - key!(Ctrl-KeyCode::Char('t')) => { - self.track_add(None)?; - Ok(Some(true)) - }, - // rename: add a new scene - key!(KeyCode::Char('n')) => { - self.rename_selected(); - Ok(Some(true)) - }, - // length: add a new scene - key!(KeyCode::Char('l')) => { - todo!(); - Ok(Some(true)) - }, - // color: set color of item at cursor - key!(KeyCode::Char('c')) => { - todo!(); - Ok(Some(true)) - }, - _ => Ok(None) - } - } -} diff --git a/crates/tek_sequencer/src/arranger_rename.rs b/crates/tek_sequencer/src/arranger_rename.rs deleted file mode 100644 index ce547f1d..00000000 --- a/crates/tek_sequencer/src/arranger_rename.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::*; -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(), - } - ))); - } -} -/// 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 -} -impl ArrangerRenameModal { - pub fn new (target: ArrangerFocus, value: &Arc>) -> Self { - Self { - _engine: Default::default(), - done: false, - target, - value: value.read().unwrap().clone(), - cursor: value.read().unwrap().len(), - result: value.clone(), - } - } -} -impl Widget for ArrangerRenameModal { - type Engine = Tui; - fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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, Nord::BG0); - 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)) - } -} -impl Handle for ArrangerRenameModal { - fn handle (&mut self, from: &Tui) -> 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 exit (&mut self) { - self.done = true - } -} diff --git a/crates/tek_sequencer/src/arranger_track.rs b/crates/tek_sequencer/src/arranger_track.rs deleted file mode 100644 index 74404c54..00000000 --- a/crates/tek_sequencer/src/arranger_track.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::*; - -use super::Arranger; - -/// Track management methods -impl Arranger { - pub fn track (&self) -> Option<&Sequencer> { - self.selected.track().map(|t|self.tracks.get(t)).flatten() - } - pub fn track_mut (&mut self) -> Option<&mut Sequencer> { - self.selected.track().map(|t|self.tracks.get_mut(t)).flatten() - } - pub fn track_next (&mut self) { - self.selected.track_next(self.tracks.len() - 1) - } - pub fn track_prev (&mut self) { - self.selected.track_prev() - } - pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Sequencer> { - self.tracks.push(name.map_or_else( - || Sequencer::new(&self.track_default_name()), - |name| Sequencer::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) - } -} - -pub fn track_name_max_len (tracks: &[Sequencer]) -> usize { - tracks.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - -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 -} diff --git a/crates/tek_sequencer/src/arranger_view_h.rs b/crates/tek_sequencer/src/arranger_view_h.rs deleted file mode 100644 index 11649f56..00000000 --- a/crates/tek_sequencer/src/arranger_view_h.rs +++ /dev/null @@ -1,263 +0,0 @@ -use crate::*; - -pub fn draw (state: &Arranger, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let area = [area.x(), area.y(), area.w(), area.h().min((2 + state.tracks.len() * 2) as u16)]; - let tracks = state.tracks.as_slice(); - Layers::new(|add|{ - add(&state.focused.then_some(FillBg(COLOR_BG0)))?; - add(&Split::right(|add|{ - add(&TrackNameColumn(tracks, state.selected))?; - add(&TrackMonitorColumn(tracks))?; - add(&TrackRecordColumn(tracks))?; - add(&TrackOverdubColumn(tracks))?; - add(&TrackEraseColumn(tracks))?; - add(&TrackGainColumn(tracks))?; - add(&TrackScenesColumn(tracks, state.scenes.as_slice(), state.selected))?; - Ok(()) - })) - }).render(to.with_rect(area)) -} - -struct TrackNameColumn<'a>(&'a [Sequencer], ArrangerFocus); - -impl<'a> Widget for TrackNameColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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)) - } -} - -struct TrackMonitorColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackMonitorColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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, area: [u16;4]) -> Perhaps<[u16;4]> { - todo!() - } - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - 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])) - } -} diff --git a/crates/tek_sequencer/src/arranger_view_v.rs b/crates/tek_sequencer/src/arranger_view_v.rs deleted file mode 100644 index 9a537e95..00000000 --- a/crates/tek_sequencer/src/arranger_view_v.rs +++ /dev/null @@ -1,262 +0,0 @@ -use crate::*; - -/// Draw arranger with 1 row per scene. -pub fn draw_compact_1 <'a> ( - state: &Arranger, to: &mut Tui -) -> Perhaps<[u16;4]> { - let track_cols = track_clip_name_lengths(state.tracks.as_slice()); - let scene_rows = (0..=state.scenes.len()).map(|i|(96, 96*i)).collect::>(); - draw(state, to, track_cols.as_slice(), scene_rows.as_slice()) -} - -/// Draw arranger with 2 rows per scene. -pub fn draw_compact_2 <'a> ( - state: &Arranger, to: &mut Tui -) -> Perhaps<[u16;4]> { - let track_cols = track_clip_name_lengths(state.tracks.as_slice()); - let scene_rows = (0..=state.scenes.len()).map(|i|(192, 192*i)).collect::>(); - draw(state, to, track_cols.as_slice(), scene_rows.as_slice()) -} - -/// Draw arranger with number of rows per scene proportional to duration of scene. -pub fn draw_expanded <'a> ( - state: &Arranger, to: &mut Tui -) -> Perhaps<[u16;4]> { - let track_cols = track_clip_name_lengths(state.tracks.as_slice()); - let scene_rows = scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice()); - draw(state, to, track_cols.as_slice(), scene_rows.as_slice()) -} - -pub fn draw <'a, 'b> ( - state: &Arranger, - to: &mut Tui, - cols: &'b [(usize, usize)], - rows: &'b [(usize, usize)], -) -> Perhaps<[u16;4]> { - let area = to.area(); - let area = [area.x(), area.y(), area.w(), 2 + (rows[rows.len() - 1].1 / 96) as u16]; - let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16; - let tracks = state.tracks.as_ref(); - let scenes = state.scenes.as_ref(); - Layers::new(|add|{ - //.add_ref(&FillBg(Color::Rgb(30, 33, 36)))//COLOR_BG1))//bg_lo(state.focused, state.entered))) - add(&ColumnSeparators(offset, cols))?; - add(&RowSeparators(rows))?; - add(&CursorFocus(state.selected, offset, cols, rows))?; - add(&Split::right(|add|{ - add(&TracksHeader(offset, cols, tracks))?; - add(&SceneRows(offset, cols, rows, tracks, scenes)) - })) - }).render(to.with_rect(area)) -} - -struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]); - -impl<'a> Widget for ColumnSeparators<'a> { - type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let Self(offset, cols) = self; - let style = Some(Style::default().fg(Nord::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)?; - } - } - Ok(Some(area)) - } -} - -struct RowSeparators<'a>(&'a [(usize, usize)]); - -impl<'a> Widget for RowSeparators<'a> { - type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let Self(rows) = self; - 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) { - let cell = to.buffer().get_mut(x, y); - cell.modifier = Modifier::UNDERLINED; - cell.underline_color = Nord::SEPARATOR; - } - } - Ok(Some(area)) - } -} - -struct CursorFocus<'a>( - ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)] -); - -impl<'a> Widget for CursorFocus<'a> { - type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let Self(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 => { - to.fill_bg(area, COLOR_BG0); - 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_BG5); - to.fill_fg([x + width, y, 1, height], COLOR_BG5); - } - if let Some([_, y, _, height]) = scene_area { - to.fill_ul([area.x(), y - 1, area.w(), 1], COLOR_BG5); - to.fill_ul([area.x(), y + height - 1, area.w(), 1], COLOR_BG5); - } - if let Some(clip_area) = clip_area { - to.fill_bg(clip_area, COLOR_BG0); - } else if let Some(track_area) = track_area { - to.fill_bg(track_area, COLOR_BG0); - } else if let Some(scene_area) = scene_area { - to.fill_bg(scene_area, COLOR_BG0); - } - Ok(Some(area)) - } -} - -struct TracksHeader<'a>(u16, &'a[(usize, usize)], &'a [Sequencer]); - -impl<'a> Widget for TracksHeader<'a> { - type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let Self(offset, track_cols, tracks) = *self; - let [x, y, width, _] = area; - for (track, (w, x)) in tracks.iter().zip(track_cols) { - let x = *x as u16; - if x > width { - break - } - let name = track.name.read().unwrap(); - to.fill_bg([offset + x, y, *w as u16, 2], COLOR_BG1); - to.blit(&*name, offset + x + 1, y, Some(Style::default().white()))?; - } - Ok(Some([x, y, width, 2])) - } -} - -struct SceneRows<'a>(u16, &'a[(usize, usize)], &'a[(usize, usize)], &'a[Sequencer], &'a[Scene]); - -impl<'a> Widget for SceneRows<'a> { - type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let Self(offset, track_cols, scene_rows, tracks, scenes) = *self; - let black = Some(Style::default().fg(Nord::SEPARATOR)); - let [_, mut y, _, _height] = area; - for (_, x) in track_cols.iter() { - let x = *x as u16; - if x > 0 { - for y in area.y()-2..y-2 { - to.blit(&"▎", x - 1, y, black)?; - } - } - } - for (scene, (pulses, _)) in scenes.iter().zip(scene_rows) { - //if y > height { - //break - //} - let h = 1.max((pulses / 96) as u16); - SceneRow(tracks, scene, track_cols, offset) - .render(to.with_area(area.x(), y, area.w(), h))?; - y = y + h - } - Ok(Some(area)) - } -} - -struct SceneRow<'a>(&'a[Sequencer], &'a Scene, &'a[(usize, usize)], u16); - -impl<'a> Widget for SceneRow<'a> { - type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let Self(tracks, scene, track_cols, offset) = self; - let [x, y, width, _] = area; - let playing = scene.is_playing(tracks); - to.blit(&if playing { "▶" } else { " " }, x, y, None)?; - to.blit(&*scene.name.read().unwrap(), x + 1, y, Some(Style::default().white()))?; - to.fill_bg([x, y, offset.saturating_sub(1), area.h()], COLOR_BG1); - for (track, (w, x)) in track_cols.iter().enumerate() { - let x = *x as u16 + offset; - if x > width { - break - } - if let (Some(track), Some(Some(clip))) = ( - tracks.get(track), scene.clips.get(track) - ) { - SceneClip(track, *clip).render(to.with_area(x, y, *w as u16, area.h()))?; - } - } - Ok(Some(area)) - } -} - -struct SceneClip<'a>(&'a Sequencer, usize); - -impl<'a> Widget for SceneClip<'a> { - type Engine = Tui; - fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - let area = to.area(); - let Self(track, clip) = self; - let style = Some(Style::default().white()); - if let Some(phrase) = track.phrases.get(*clip) { - let phrase = phrase.read().unwrap(); - let name = phrase.name.read().unwrap(); - to.blit(&format!("{clip:02} {name}"), area.x() + 1, area.y(), style)?; - to.fill_bg(area, if track.sequence == Some(*clip) { - Nord::PLAYING - } else { - COLOR_BG1 - }); - } else { - to.fill_bg(area, COLOR_BG0) - } - Ok(Some(area)) - } -} diff --git a/crates/tek_sequencer/src/lib.rs b/crates/tek_sequencer/src/lib.rs index cff77681..5c1917b9 100644 --- a/crates/tek_sequencer/src/lib.rs +++ b/crates/tek_sequencer/src/lib.rs @@ -8,25 +8,9 @@ pub(crate) use std::sync::{Arc, RwLock}; submod! { arranger - arranger_focus - arranger_handle - arranger_track - arranger_rename midi phrase scene sequencer - sequencer_cli - sequencer_handle - sequencer_view transport - transport_handle - transport_view -} - -pubmod! { - arranger_view_h - arranger_view_v - sequencer_view_h - sequencer_view_v } diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index a55f66dc..8fde92c4 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -197,3 +197,490 @@ impl Sequencer { } } } + +impl Widget for Sequencer { + type Engine = Tui; + fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> { + todo!() + } + fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { + self.horizontal_draw(to)?; + if self.focused && self.entered { + Corners(Style::default().green().not_dim()).draw(to)?; + } + Ok(Some(to.area())) + } +} + +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(); + fill_seq_bg(&mut buffer, phrase.length, self.ppq)?; + fill_seq_fg(&mut buffer, &phrase)?; + self.buffer = buffer; + } else { + self.buffer = Default::default(); + } + Ok(()) + } + + pub(crate) fn style_focus (&self) -> Option