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. pub struct Arranger { /// Name of arranger pub name: Arc>, /// Collection of tracks. pub tracks: Vec>, /// Collection of scenes. pub scenes: Vec, /// Currently selected element. pub selected: ArrangerFocus, /// Display mode of arranger pub mode: ArrangerViewMode, /// Slot for modal dialog displayed on top of app. pub modal: Option>>, /// Whether the arranger is currently focused pub focused: bool } impl Arranger { pub fn new (name: &str) -> Self { Self { name: Arc::new(RwLock::new(name.into())), mode: ArrangerViewMode::VerticalCompact2, selected: ArrangerFocus::Clip(0, 0), scenes: vec![], tracks: vec![], modal: None, focused: false } } 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; }, _ => {} } } pub fn sequencer (&self) -> Option<&Sequencer> { self.selected.track() .map(|track|self.tracks.get(track)) .flatten() } pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer> { self.selected.track() .map(|track|self.tracks.get_mut(track)) .flatten() } pub fn show_phrase (&mut self) -> Usually<()> { //unimplemented!() //let phrase = self.phrase(); //self.sequencer.show(phrase) Ok(()) } pub fn is_first_row (&self) -> bool { let selected = self.selected; selected.is_mix() || selected.is_track() || match selected { ArrangerFocus::Clip(_, s) => s == 0, _ => false } } pub fn is_last_row (&self) -> bool { let selected = self.selected; match selected { ArrangerFocus::Scene(s) => s == self.scenes.len() - 1, ArrangerFocus::Clip(_, s) => s == self.scenes.len() - 1, _ => false } } 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 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() - 1) } 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.scene_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) } pub fn phrase (&self) -> Option<&Arc>> { let track_id = self.selected.track()?; self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?) } pub fn phrase_next (&mut self) { let track_index = self.selected.track(); let scene_index = self.selected.scene(); track_index .and_then(|index|self.tracks.get_mut(index).map(|track|(index, track))) .and_then(|(track_index, track)|{ let phrases = track.phrases.len(); scene_index .and_then(|index|self.scenes.get_mut(index)) .and_then(|scene|{ 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); } Some(()) }) }); } pub fn phrase_prev (&mut self) { let track_index = self.selected.track(); let scene_index = self.selected.scene(); track_index .and_then(|index|self.tracks.get_mut(index).map(|track|(index, track))) .and_then(|(track_index, track)|{ let phrases = track.phrases.len(); scene_index .and_then(|index|self.scenes.get_mut(index)) .and_then(|scene|{ 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); } Some(()) }) }); } } 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 modal.exited() { 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|{ //Lozenge(Style::default().fg(Nord::BG2)) //.draw(&mut to.alter_area(|[x, y, w, h]|[ //x.saturating_sub(1), //y.saturating_sub(1), //w + 2, //h + 2, //])) match self.mode { ArrangerViewMode::Horizontal => add(&ArrangerViewHorizontal(&self)), ArrangerViewMode::VerticalCompact1 => add(&ArrangerViewVertical( &self, track_clip_name_lengths(self.tracks.as_slice()).as_slice(), (0..=self.scenes.len()).map(|i|(96, 96*i)).collect::>().as_slice(), )), ArrangerViewMode::VerticalCompact2 => add(&ArrangerViewVertical( &self, track_clip_name_lengths(self.tracks.as_slice()).as_slice(), (0..=self.scenes.len()).map(|i|(192, 192*i)).collect::>().as_slice() )), ArrangerViewMode::VerticalExpanded => add(&ArrangerViewVertical( &self, track_clip_name_lengths(self.tracks.as_slice()).as_slice(), scene_ppqs(self.tracks.as_slice(), self.scenes.as_slice()).as_slice(), )), } }) } } 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)] /// 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 } } 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) } } } 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)) } } } } /////////////////////////////////////////////////////////////////////////////////////////////////// /// Display mode of arranger #[derive(PartialEq)] pub enum ArrangerViewMode { VerticalExpanded, VerticalCompact1, VerticalCompact2, Horizontal } /// Arranger display mode can be cycled impl ArrangerViewMode { /// Cycle arranger display mode pub fn to_next (&mut self) { *self = match self { Self::VerticalExpanded => Self::VerticalCompact1, Self::VerticalCompact1 => Self::VerticalCompact2, Self::VerticalCompact2 => Self::Horizontal, Self::Horizontal => Self::VerticalExpanded, } } } /////////////////////////////////////////////////////////////////////////////////////////////////// struct ArrangerViewVertical<'a, 'b, E: Engine>( &'a Arranger, &'b [(usize, usize)], &'b [(usize, usize)] ); impl<'a, 'b> Content for ArrangerViewVertical<'a, 'b, Tui> { type Engine = Tui; fn content (&self) -> impl Widget { let Self(state, cols, rows) = self; let tracks: &[Sequencer] = state.tracks.as_ref(); let scenes = state.scenes.as_ref(); let offset = 4 + scene_name_max_len(scenes) as u16; Layers::new(move |add|{ add(&ColumnSeparators(offset, cols))?; add(&RowSeparators(rows))?; add(&CursorFocus(state.focused, state.selected, offset, cols, rows))?; add(&Split::down(|add|{ add(&Push::X(offset, Split::right(move |add|{ for (track, (w, _)) in tracks.iter().zip(*cols) { add(&Min::XY(*w as u16, 2, Layers::new(|add|{ add(&Background(COLOR_BG1))?; add(&track.name.read().unwrap().as_str()) })))?; } Ok(()) })))?; add(&Split::down(move |add| { for (scene, (pulses, _)) in scenes.iter().zip(*rows) { let height = 1.max((pulses / 96) as u16); let playing = scene.is_playing(tracks); add(&Fixed::Y(height, Split::right(move |add| { add(&Fixed::XY(offset.saturating_sub(1), height, Split::right(|add|{ add(&if playing { "▶ " } else { " " })?; add(&scene.name.read().unwrap().as_str()) })))?; for (track, (w, _x)) in cols.iter().enumerate() { add(&Fixed::XY(*w as u16, height, Layers::new(move |add|{ let mut color = COLOR_BG0; if let (Some(track), Some(Some(clip))) = ( tracks.get(track), scene.clips.get(track), ) { if let Some(phrase) = track.phrases.get(*clip) { add(&Push::X(1, format!( "{clip:02} {}", phrase.read().unwrap().name.read().unwrap() ).as_str()))?; color = if track.sequence == Some(*clip) { Nord::PLAYING } else { COLOR_BG1 }; } } add(&Background(color)) })))?; } Ok(()) })))?; } Ok(()) })) })) }) } } pub fn scene_name_max_len (scenes: &[Scene]) -> usize { scenes.iter() .map(|s|s.name.read().unwrap().len()) .fold(0, usize::max) } /////////////////////////////////////////////////////////////////////////////////////////////////// struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]); impl<'a> Widget for ColumnSeparators<'a> { type Engine = Tui; fn render (&self, to: &mut TuiOutput) -> Usually<()> { 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(()) } } /////////////////////////////////////////////////////////////////////////////////////////////////// struct RowSeparators<'a>(&'a [(usize, usize)]); impl<'a> Widget for RowSeparators<'a> { type Engine = Tui; fn render (&self, to: &mut TuiOutput) -> Usually<()> { 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) { 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 = Nord::SEPARATOR; } } } Ok(()) } } /////////////////////////////////////////////////////////////////////////////////////////////////// struct CursorFocus<'a>( bool, ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)] ); impl<'a> Widget for CursorFocus<'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_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 focused { 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)) Ok(()) } } /////////////////////////////////////////////////////////////////////////////////////////////////// struct ArrangerViewHorizontal<'a, E: Engine>( &'a Arranger ); impl<'a> Content for ArrangerViewHorizontal<'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_BG0)))?; add(&Split::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 } 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, _to: [u16;2]) -> Perhaps<[u16;2]> { todo!() } fn render (&self, to: &mut TuiOutput) -> Usually<()> { 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)) 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 exit (&mut self) { self.done = true } } /////////////////////////////////////////////////////////////////////////////////////////////////// /// 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, /// Highlight input keys pub keys_in: [bool; 128], /// Highlight output keys pub keys_out: [bool; 128], pub now: usize, pub ppq: usize, pub note_axis: FixedAxis, pub time_axis: ScaledAxis, /// Play input through output. pub monitoring: bool, /// Write input to sequence. pub recording: bool, /// Overdub input to sequence. pub overdub: bool, /// Map: tick -> MIDI events at tick pub phrases: Vec>>, /// Phrase selector pub sequence: Option, /// Output from current sequence. pub midi_out: Option>, /// MIDI output buffer midi_out_buf: Vec>>, /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) /// Highlight keys on piano roll. pub notes_in: [bool;128], /// Highlight keys on piano roll. pub notes_out: [bool;128], } impl Sequencer { pub fn new (name: &str) -> Self { Self { name: Arc::new(RwLock::new(name.into())), monitoring: false, recording: false, overdub: true, phrases: vec![], sequence: None, midi_out: None, midi_out_buf: vec![Vec::with_capacity(16);16384], reset: true, notes_in: [false;128], notes_out: [false;128], buffer: Default::default(), keys: keys_vert(), entered: false, focused: false, mode: false, keys_in: [false;128], keys_out: [false;128], phrase: None, now: 0, ppq: 96, transport: None, note_axis: FixedAxis { start: 12, point: Some(36) }, time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, } } pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; } pub fn toggle_record (&mut self) { self.recording = !self.recording; } pub fn toggle_overdub (&mut self) { self.overdub = !self.overdub; } pub fn process ( &mut self, input: Option, timebase: &Arc, playing: Option, started: Option<(usize, usize)>, quant: usize, reset: bool, scope: &ProcessScope, (frame0, frames): (usize, usize), (_usec0, _usecs): (usize, usize), period: f64, ) { if self.midi_out.is_some() { // Clear the section of the output buffer that we will be using for frame in &mut self.midi_out_buf[0..frames] { frame.clear(); } // Emit "all notes off" at start of buffer if requested if self.reset { all_notes_off(&mut self.midi_out_buf); self.reset = false; } else if reset { all_notes_off(&mut self.midi_out_buf); } } if let ( Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase) ) = ( playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id)) ) { phrase.read().map(|phrase|{ if self.midi_out.is_some() { phrase.process_out( &mut self.midi_out_buf, &mut self.notes_out, timebase, (frame0.saturating_sub(start_frame), frames, period) ); } }).unwrap(); let mut phrase = phrase.write().unwrap(); let length = phrase.length; // Monitor and record input if input.is_some() && (self.recording || self.monitoring) { // For highlighting keys and note repeat for (frame, event, bytes) in parse_midi_input(input.unwrap()) { match event { LiveEvent::Midi { message, .. } => { if self.monitoring { self.midi_out_buf[frame].push(bytes.to_vec()) } if self.recording { phrase.record_event({ let pulse = timebase.frame_to_pulse( (frame0 + frame - start_frame) as f64 ); let quantized = ( pulse / quant as f64 ).round() as usize * quant; let looped = quantized % length; looped }, message); } match message { MidiMessage::NoteOn { key, .. } => { self.notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { self.notes_in[key.as_int() as usize] = false; }, _ => {} } }, _ => {} } } } } else if input.is_some() && self.midi_out.is_some() && self.monitoring { for (frame, event, bytes) in parse_midi_input(input.unwrap()) { self.process_monitor_event(frame, &event, bytes) } } if let Some(out) = &mut self.midi_out { write_midi_output(&mut out.writer(scope), &self.midi_out_buf, frames); } } #[inline] fn process_monitor_event (&mut self, frame: usize, event: &LiveEvent, bytes: &[u8]) { match event { LiveEvent::Midi { message, .. } => { self.write_to_output_buffer(frame, bytes); self.process_monitor_message(&message); }, _ => {} } } #[inline] fn write_to_output_buffer (&mut self, frame: usize, bytes: &[u8]) { self.midi_out_buf[frame].push(bytes.to_vec()); } #[inline] fn process_monitor_message (&mut self, message: &MidiMessage) { match message { MidiMessage::NoteOn { key, .. } => { self.notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { self.notes_in[key.as_int() as usize] = false; }, _ => {} } } /// 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