diff --git a/tek/src/view.rs b/.old/tek.rs.old similarity index 52% rename from tek/src/view.rs rename to .old/tek.rs.old index e5b55980..cabd6d5a 100644 --- a/tek/src/view.rs +++ b/.old/tek.rs.old @@ -1,321 +1,295 @@ -use crate::*; -render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref()))); -edn_provide!('a: Box + 'a>: |self: App|{ - ":editor" => (&self.editor).boxed(), - ":inputs" => self.input_row(self.w(), 3).boxed(), - ":outputs" => self.output_row(self.w(), 3).boxed(), - ":pool" => self.pool().boxed(), - ":sample" => self.sample().boxed(), - ":sampler" => self.sampler().boxed(), - ":scenes" => self.scene_row(self.w(), self.size.h().saturating_sub(9) as u16).boxed(), - ":status" => self.status(0).boxed(), - ":toolbar" => self.toolbar().boxed(), - ":tracks" => self.track_row(self.w(), 3).boxed(), -}); -impl App { - pub fn compact (&self) -> bool { false } - fn w (&self) -> u16 { - self.tracks_with_sizes().last().map(|x|x.3 as u16).unwrap_or(0) - } - fn toolbar (&self) -> impl Content + use<'_> { - Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))) - } - fn status (&self, note_pt: usize) -> impl Content + use<'_> { - self.editor.as_ref() - .map(|e|Bsp::e(e.clip_status(), e.edit_status())) - } - fn pool (&self) -> impl Content + use<'_> { - self.pool.as_ref() - .map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) - } - fn editor (&self) -> impl Content + '_ { - &self.editor - } - fn sample <'a> (&'a self) -> impl Content + 'a { - let compact = self.is_editing(); - if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) { - let note_pt = editor.note_point(); - let sample_h = if compact { 0 } else { 5 }; - return Some(Max::y(sample_h, Fill::xy(Bsp::a( - Fill::x(Align::w(Fixed::y(1, self.status(note_pt)))), - sampler.viewer(note_pt) - )))) - } - None - } - fn sampler (&self) -> impl Content + use<'_> { - let compact = self.is_editing(); - if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) { - let note_pt = editor.note_point(); - let w = if compact { 4 } else { 40 }; - let y = if compact { 1 } else { 0 }; - return Some(Fixed::x(w, Push::y(y, Fill::y(sampler.list(compact, editor))))) - } - None - } +/////////////////////////////////////////////////////////////////////////////////////////////////// - fn row <'a> ( - &'a self, w: u16, h: u16, a: impl Content + 'a, b: impl Content + 'a - ) -> impl Content + 'a { - Fixed::y(h, Bsp::e( - Fixed::xy(self.sidebar_w() as u16, h, a), - Fill::x(Align::c(Fixed::xy(w, h, b))) - )) - } - fn track_row (&self, w: u16, h: u16) -> impl Content + '_ { - self.row(w, h, track_header(&self), track_cells(&self)) - } - fn input_row (&self, w: u16, h: u16) -> impl Content + '_ { - self.row(w, h, input_header(&self), input_cells(&self)) - } - fn output_row (&self, w: u16, h: u16) -> impl Content + '_ { - self.row(w, h, output_header(&self), output_cells(&self)) - } - fn scene_row (&self, w: u16, h: u16) -> impl Content + '_ { - self.row(w, h, scene_header(&self), scene_cells(&self)) - } +//#[cfg(test)] mod test_focus { + //use super::focus::*; + //#[test] fn test_focus () { - pub fn tracks_with_sizes (&self) - -> impl Iterator - { - tracks_with_sizes(self.tracks.iter(), match self.selected { - ArrangerSelection::Track(t) if self.is_editing() => Some(t), - ArrangerSelection::Clip(t, _) if self.is_editing() => Some(t), - _ => None - }, self.editor_w()) - } - pub fn scenes_with_sizes (&self, h: usize) - -> impl Iterator - { - scenes_with_sizes(self.scenes.iter(), &self.selected, self.is_editing(), 2, 15) - } - fn is_editing (&self) -> bool { - self.editing.load(Relaxed) - } - fn editor_w (&self) -> usize { - let editor = self.editor.as_ref().expect("missing editor"); - (5 + (editor.time_len().get() / editor.time_zoom().get())) - .min(self.size.w().saturating_sub(20)) - .max(16) - } - fn sidebar_w (&self) -> u16 { - let w = self.size.w(); - let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let w = if self.is_editing() { 8 } else { w }; - w - } -} -pub fn scenes_with_sizes <'a>( - scenes: impl Iterator + 'a, - selected: &'a ArrangerSelection, - editing: bool, - scene_height: usize, - scene_larger: usize, -) -> impl Iterator + 'a { - let mut y = 0; - let (selected_track, selected_scene) = match selected { - ArrangerSelection::Clip(t, s) => (Some(t), Some(s)), - _ => (None, None) - }; - scenes.enumerate().map(move|(s, scene)|{ - let active = editing && selected_track.is_some() && selected_scene == Some(&s); - let height = if active { scene_larger } else { scene_height }; - let data = (s, scene, y, y + height); - y += height; - data - }) -} -pub fn tracks_with_sizes <'a> ( - tracks: impl Iterator, - active: Option, - bigger: usize -) -> impl Iterator { - let mut x = 0; - tracks.enumerate().map(move |(index, track)|{ - let width = if Some(index) == active { bigger } else { track.width.max(8) }; - let data = (index, track, x, x + width); - x += width; - data - }) -} -pub fn track_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - (||Tui::bg(TuiTheme::g(32), Tui::bold(true, Bsp::s( - row!( - Tui::fg(TuiTheme::g(128), "add "), - Tui::fg(TuiTheme::orange(), "t"), - Tui::fg(TuiTheme::g(128), "rack"), - ), - row!( - Tui::fg(TuiTheme::orange(), "a"), - Tui::fg(TuiTheme::g(128), "dd scene"), - ), - ))).boxed()).into() -} -pub fn track_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - let iter = ||state.tracks_with_sizes(); - (move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { - let name = Push::x(1, &track.name); - let color = track.color; - let fg = color.lightest.rgb; - let bg = color.base.rgb; - let active = state.selected.track() == Some(i); - let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) }; - let border = Style::default().fg(bfg).bg(bg); - Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16, - Outer(border).enclose(Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::x(name))))) - )) - })).boxed()).into() -} -fn help_tag <'a>(before: &'a str, key: &'a str, after: &'a str) -> impl Content + 'a { - let lo = TuiTheme::g(128); - let hi = TuiTheme::orange(); - Tui::bold(true, row!(Tui::fg(lo, before), Tui::fg(hi, key), Tui::fg(lo, after))) -} -fn input_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - let fg = TuiTheme::g(224); - let bg = TuiTheme::g(64); - (move||Bsp::s(help_tag("midi ", "I", "ns"), state.midi_ins.get(0).map(|inp|Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))), - inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, - Tui::fg_bg(fg, bg, connect.info()))))), - ))).boxed()).into() -} -fn input_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - (move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| { - let w = (x2 - x1) as u16; - let color: ItemPalette = track.color.dark.into(); - map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n( - rec_mon(color.base.rgb, false, false), - phat_hi(color.base.rgb, color.dark.rgb) - )))) - })).boxed()).into() -} -fn output_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - let fg = TuiTheme::g(224); - let bg = TuiTheme::g(64); - (move||Bsp::s(help_tag("midi ", "O", "uts"), state.midi_outs.get(0).map(|out|Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))), - out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, - Tui::fg_bg(fg, bg, connect.info()))))), - ))).boxed()).into() -} -fn output_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - (move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| { - let w = (x2 - x1) as u16; - let color: ItemPalette = track.color.dark.into(); - map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n( - mute_solo(color.base.rgb, false, false), - phat_hi(color.dark.rgb, color.darker.rgb) - )))) - })).boxed()).into() -} -fn scene_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - (||{ - let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); - let selected = state.selected.scene(); - Fill::y(Align::c(Map::new(||state.scenes_with_sizes(2), move|(_, scene, y1, y2), i| { - let h = (y2 - y1) as u16; - let name = format!("🭬{}", &scene.name); - let color = scene.color; - let active = selected == Some(i); - let mid = if active { color.light } else { color.base }; - let top = Some(last_color.read().unwrap().base.rgb); - let cell = phat_sel_3( - active, - Tui::bold(true, name.clone()), - Tui::bold(true, name), - top, - mid.rgb, - Color::Rgb(0, 0, 0) - ); - *last_color.write().unwrap() = color; - map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell)) - }))).boxed() - }).into() -} -fn scene_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { - let editing = state.is_editing(); - let tracks = move||state.tracks_with_sizes(); - let scenes = ||state.scenes_with_sizes(2); - let selected_track = state.selected.track(); - let selected_scene = state.selected.scene(); - (move||Fill::y(Align::c(Map::new(tracks, move|(_, track, x1, x2), t| { - let w = (x2 - x1) as u16; - let color: ItemPalette = track.color.dark.into(); - let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); - let cells = Map::new(scenes, move|(_, scene, y1, y2), s| { - let h = (y2 - y1) as u16; - let color = scene.color; - let (name, fg, bg) = if let Some(c) = &scene.clips[t] { - let c = c.read().unwrap(); - (c.name.to_string(), c.color.lightest.rgb, c.color.base.rgb) - } else { - ("⏹ ".to_string(), TuiTheme::g(64), TuiTheme::g(32)) - }; - let last = last_color.read().unwrap().clone(); - let active = editing && selected_scene == Some(s) && selected_track == Some(t); - let editor = Thunk::new(||&state.editor); - let cell = Thunk::new(move||phat_sel_3( - selected_track == Some(t) && selected_scene == Some(s), - Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), - Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), - if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) { - None - } else { - Some(bg.into()) - }, - bg.into(), - bg.into(), - )); - let cell = Either(active, editor, cell); - *last_color.write().unwrap() = bg.into(); - map_south( - y1 as u16, - h + 1, - Fill::x(Fixed::y(h + 1, cell)) - ) - }); - let column = Fixed::x(w, Tui::bg(Color::Reset, Align::y(cells)).boxed()); - Fixed::x(w, map_east(x1 as u16, w, column)) - }))).boxed()).into() -} -fn cell_clip <'a> ( - scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 -) -> impl Content + use<'a> { - scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{ - let clip = clip.read().unwrap(); - let mut bg = TuiTheme::border_bg(); - let name = clip.name.to_string(); - let max_w = name.len().min((w as usize).saturating_sub(2)); - let color = clip.color; - bg = color.dark.rgb; - if let Some((_, Some(ref playing))) = track.player.play_clip() { - if *playing.read().unwrap() == *clip { - bg = color.light.rgb - } - }; - Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w])))); - })) -} -fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content { - row!( - Tui::fg_bg(if rec { Color::Red } else { bg }, bg, "▐"), - Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"), - Tui::fg_bg(if rec { Color::White } else { bg }, bg, "▐"), - Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"), - Tui::fg_bg(if mon { Color::White } else { bg }, bg, "▌"), - ) -} -fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content { - row!( - Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"), - Tui::fg_bg(if mute { Color::White } else { bg }, bg, "▐"), - Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"), - ) -} -fn cell > (color: ItemPalette, field: T) -> impl Content { - Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) -} + //struct FocusTest { + //focused: char, + //cursor: (usize, usize) + //} + + //impl HasFocus for FocusTest { + //type Item = char; + //fn focused (&self) -> Self::Item { + //self.focused + //} + //fn set_focused (&mut self, to: Self::Item) { + //self.focused = to + //} + //} + + //impl FocusGrid for FocusTest { + //fn focus_cursor (&self) -> (usize, usize) { + //self.cursor + //} + //fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { + //&mut self.cursor + //} + //fn focus_layout (&self) -> &[&[Self::Item]] { + //&[ + //&['a', 'a', 'a', 'b', 'b', 'd'], + //&['a', 'a', 'a', 'b', 'b', 'd'], + //&['a', 'a', 'a', 'c', 'c', 'd'], + //&['a', 'a', 'a', 'c', 'c', 'd'], + //&['e', 'e', 'e', 'e', 'e', 'e'], + //] + //} + //} + + //let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; + + //tester.focus_right(); + //assert_eq!(tester.cursor.0, 3); + //assert_eq!(tester.focused, 'b'); + + //tester.focus_down(); + //assert_eq!(tester.cursor.1, 2); + //assert_eq!(tester.focused, 'c'); + + //} +//} +//use crate::*; + +//struct TestEngine([u16;4], Vec>); + +//impl Engine for TestEngine { + //type Unit = u16; + //type Size = [Self::Unit;2]; + //type Area = [Self::Unit;4]; + //type Input = Self; + //type Handled = bool; + //fn exited (&self) -> bool { + //true + //} +//} + +//#[derive(Copy, Clone)] +//struct TestArea(u16, u16); + +//impl Render for TestArea { + //fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + //Ok(Some([to[0], to[1], self.0, self.1])) + //} + //fn render (&self, to: &mut TestEngine) -> Perhaps<[u16;4]> { + //if let Some(layout) = self.layout(to.area())? { + //for y in layout.y()..layout.y()+layout.h()-1 { + //for x in layout.x()..layout.x()+layout.w()-1 { + //to.1[y as usize][x as usize] = '*'; + //} + //} + //Ok(Some(layout)) + //} else { + //Ok(None) + //} + //} +//} + +//#[test] +//fn test_plus_minus () -> Usually<()> { + //let area = [0, 0, 10, 10]; + //let engine = TestEngine(area, vec![vec![' ';10];10]); + //let test = TestArea(4, 4); + //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); + //assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4])); + //Ok(()) +//} + +//#[test] +//fn test_outset_align () -> Usually<()> { + //let area = [0, 0, 10, 10]; + //let engine = TestEngine(area, vec![vec![' ';10];10]); + //let test = TestArea(4, 4); + //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); + //assert_eq!(Margin::X(1, test).layout(area)?, Some([0, 0, 6, 4])); + //assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4])); + //assert_eq!(Align::X(Margin::X(1, test)).layout(area)?, Some([2, 0, 6, 4])); + //assert_eq!(Margin::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4])); + //Ok(()) +//} + +////#[test] +////fn test_misc () -> Usually<()> { + ////let area: [u16;4] = [0, 0, 10, 10]; + ////let test = TestArea(4, 4); + ////assert_eq!(test.layout(area)?, + ////Some([0, 0, 4, 4])); + ////assert_eq!(Align::Center(test).layout(area)?, + ////Some([3, 3, 4, 4])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&test)?; + ////add(&test) + ////})).layout(area)?, + ////Some([3, 1, 4, 8])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&test) + ////})).layout(area)?, + ////Some([2, 0, 6, 10])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) + ////})).layout(area)?, + ////Some([2, 1, 6, 8])); + ////assert_eq!(Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) + ////}).layout(area)?, + ////Some([0, 0, 6, 8])); + ////assert_eq!(Stack::right(|add|{ + ////add(&Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) + ////}))?; + ////add(&Align::Center(TestArea(2 ,2))) + ////}).layout(area)?, + ////Some([0, 0, 8, 8])); + ////Ok(()) +////} + +////#[test] +////fn test_offset () -> Usually<()> { + ////let area: [u16;4] = [50, 50, 100, 100]; + ////let test = TestArea(3, 3); + ////assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3])); + ////assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3])); + ////assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3])); + ////Ok(()) +////} + +////#[test] +////fn test_outset () -> Usually<()> { + ////let area: [u16;4] = [50, 50, 100, 100]; + ////let test = TestArea(3, 3); + ////assert_eq!(Margin::X(1, test).layout(area)?, Some([49, 50, 5, 3])); + ////assert_eq!(Margin::Y(1, test).layout(area)?, Some([50, 49, 3, 5])); + ////assert_eq!(Margin::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5])); + ////Ok(()) +////} + +////#[test] +////fn test_padding () -> Usually<()> { + ////let area: [u16;4] = [50, 50, 100, 100]; + ////let test = TestArea(3, 3); + ////assert_eq!(Padding::X(1, test).layout(area)?, Some([51, 50, 1, 3])); + ////assert_eq!(Padding::Y(1, test).layout(area)?, Some([50, 51, 3, 1])); + ////assert_eq!(Padding::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1])); + ////Ok(()) +////} + +////#[test] +////fn test_stuff () -> Usually<()> { + ////let area: [u16;4] = [0, 0, 100, 100]; + ////assert_eq!("1".layout(area)?, + ////Some([0, 0, 1, 1])); + ////assert_eq!("333".layout(area)?, + ////Some([0, 0, 3, 1])); + ////assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?, + ////Some([0, 0, 3, 1])); + ////assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?, + ////Some([0, 0, 3, 2])); + ////assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?, + ////Some([0, 0, 4, 1])); + ////assert_eq!(Stack::down(|add|{ + ////add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?; + ////add(&"55555") + ////}).layout(area)?, + ////Some([0, 0, 5, 2])); + ////let area: [u16;4] = [1, 1, 100, 100]; + ////assert_eq!(Margin::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////Some([0, 1, 6, 1])); + ////assert_eq!(Margin::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////Some([1, 0, 4, 3])); + ////assert_eq!(Margin::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////Some([0, 0, 6, 3])); + ////assert_eq!(Stack::down(|add|{ + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333")) + ////}).layout(area)?, + ////Some([1, 1, 5, 6])); + ////let area: [u16;4] = [1, 1, 95, 100]; + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333")) + ////})).layout(area)?, + ////Some([46, 48, 5, 6])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Layers::new(|add|{ + //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,128,0))))?; + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333"))?; + //////add(&Background(Color::Rgb(0,128,0)))?; + ////Ok(()) + ////}))?; + ////add(&Layers::new(|add|{ + //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,0,128))))?; + ////add(&Margin::XY(1, 1, "555"))?; + ////add(&Margin::XY(1, 1, "777777"))?; + //////add(&Background(Color::Rgb(0,0,128)))?; + ////Ok(()) + ////})) + ////})).layout(area)?, + ////Some([46, 48, 5, 6])); + ////Ok(()) +////} + +//#[derive(Default)] pub struct Sequencer { + //pub jack: Arc>, + //pub compact: bool, + //pub editor: MidiEditor, + //pub midi_buf: Vec>>, + //pub note_buf: Vec, + //pub perf: PerfModel, + //pub player: MidiPlayer, + //pub pool: MidiPool, + //pub selectors: bool, + //pub size: Measure, + //pub status: bool, + //pub transport: bool, +//} +//has_size!(|self:Sequencer|&self.size); +//has_clock!(|self:Sequencer|&self.player.clock); +//has_clips!(|self:Sequencer|self.pool.clips); +//has_editor!(|self:Sequencer|self.editor); +//has_player!(|self:Sequencer|self.player); + +//#[derive(Default)] pub struct Groovebox { + //pub jack: Arc>, + //pub compact: bool, + //pub editor: MidiEditor, + //pub midi_buf: Vec>>, + //pub note_buf: Vec, + //pub perf: PerfModel, + //pub player: MidiPlayer, + //pub pool: MidiPool, + //pub sampler: Sampler, + //pub size: Measure, + //pub status: bool, +//} +//has_clock!(|self: Groovebox|self.player.clock()); + +//#[derive(Default)] pub struct Arranger { + //pub clock: Clock, + //pub color: ItemPalette, + //pub compact: bool, + //pub editing: AtomicBool, + //pub editor: MidiEditor, + //pub jack: Arc>, + //pub midi_buf: Vec>>, + //pub midi_ins: Vec>, + //pub midi_outs: Vec>, + //pub note_buf: Vec, + //pub perf: PerfModel, + //pub pool: MidiPool, + //pub scenes: Vec, + //pub selected: ArrangerSelection, + //pub size: Measure, + //pub splits: [u16;2], + //pub tracks: Vec, +//} +//has_clock!(|self: Arranger|&self.clock); +//has_clips!(|self: Arranger|self.pool.clips); +//has_editor!(|self: Arranger|self.editor); /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1052,3 +1026,497 @@ fn cell > (color: ItemPalette, field: T) -> impl Content>>), +//} +//#[derive(Clone, Debug)] pub enum GrooveboxCommand { + //Compact(bool), + //History(isize), + //Clock(ClockCommand), + //Pool(PoolCommand), + //Editor(MidiEditCommand), + //Enqueue(Option>>), + //Sampler(SamplerCommand), +//} +//#[derive(Clone, Debug)] pub enum ArrangerCommand { + //History(isize), + //Color(ItemPalette), + //Clock(ClockCommand), + //Scene(SceneCommand), + //Track(TrackCommand), + //Clip(ClipCommand), + //Select(ArrangerSelection), + //Zoom(usize), + //Pool(PoolCommand), + //Editor(MidiEditCommand), + //StopAll, + //Clear, +//} + +//command!(|self: SequencerCommand, state: Sequencer|match self { + //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, + //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, + //Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None }, + //Self::History(delta) => { todo!("undo/redo") }, + + //Self::Pool(cmd) => match cmd { + //// autoselect: automatically load selected clip in editor + //PoolCommand::Select(_) => { + //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; + //state.editor.set_clip(state.pool.clip().as_ref()); + //undo + //}, + //// update color in all places simultaneously + //PoolCommand::Clip(PoolCmd::SetColor(index, _)) => { + //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; + //state.editor.set_clip(state.pool.clip().as_ref()); + //undo + //}, + //_ => cmd.delegate(&mut state.pool, Self::Pool)? + //}, + //Self::Compact(compact) => if state.compact != compact { + //state.compact = compact; + //Some(Self::Compact(!compact)) + //} else { + //None + //}, +//}); +//command!(|self: GrooveboxCommand, state: Groovebox|match self { + //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, + //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, + //Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None }, + //Self::History(delta) => { todo!("undo/redo") }, + //Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?, + + //Self::Pool(cmd) => match cmd { + //// autoselect: automatically load selected clip in editor + //PoolCommand::Select(_) => { + //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; + //state.editor.set_clip(state.pool.clip().as_ref()); + //undo + //}, + //// update color in all places simultaneously + //PoolCommand::Clip(PoolCmd::SetColor(index, _)) => { + //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; + //state.editor.set_clip(state.pool.clip().as_ref()); + //undo + //}, + //_ => cmd.delegate(&mut state.pool, Self::Pool)? + //}, + //Self::Compact(compact) => if state.compact != compact { + //state.compact = compact; + //Some(Self::Compact(!compact)) + //} else { + //None + //}, +//}); +//command!(|self: ArrangerCommand, state: Arranger|match self { + //Self::Clear => { todo!() }, + //Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, + //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, + //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, + //Self::History(_) => { todo!() }, + //Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, + //Self::Select(s) => { state.selected = s; None }, + //Self::Track(cmd) => cmd.delegate(state, Self::Track)?, + //Self::Zoom(_) => { todo!(); }, + + //Self::StopAll => { + //for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); } + //None + //}, + //Self::Color(palette) => { + //let old = state.color; + //state.color = palette; + //Some(Self::Color(old)) + //}, + //Self::Pool(cmd) => { + //match cmd { + //// autoselect: automatically load selected clip in editor + //PoolCommand::Select(_) => { + //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; + //state.editor.set_clip(state.pool.clip().as_ref()); + //undo + //}, + //// reload clip in editor to update color + //PoolCommand::Clip(PoolClipCommand::SetColor(index, _)) => { + //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; + //state.editor.set_clip(state.pool.clip().as_ref()); + //undo + //}, + //_ => cmd.delegate(&mut state.pool, Self::Pool)? + //} + //}, +//}); +//command!(|self: SceneCommand, state: Arranger|match self { + //Self::Add => { state.scene_add(None, None)?; None } + //Self::Del(index) => { state.scene_del(index); None }, + //Self::SetColor(index, color) => { + //let old = state.scenes[index].color; + //state.scenes[index].color = color; + //Some(Self::SetColor(index, old)) + //}, + //Self::Enqueue(scene) => { + //for track in 0..state.tracks.len() { + //state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); + //} + //None + //}, + //_ => None +//}); +//command!(|self: TrackCommand, state: Arranger|match self { + //Self::Add => { state.track_add(None, None)?; None }, + //Self::Del(index) => { state.track_del(index); None }, + //Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, + //Self::SetColor(index, color) => { + //let old = state.tracks[index].color; + //state.tracks[index].color = color; + //Some(Self::SetColor(index, old)) + //}, + //_ => None +//}); +//command!(|self: ClipCommand, state: Arranger|match self { + //Self::Get(track, scene) => { todo!() }, + //Self::Put(track, scene, clip) => { + //let old = state.scenes[scene].clips[track].clone(); + //state.scenes[scene].clips[track] = clip; + //Some(Self::Put(track, scene, old)) + //}, + //Self::Enqueue(track, scene) => { + //state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); + //None + //}, + //_ => None +//}); +//keymap!(KEYS_SEQUENCER = |state: Sequencer, input: Event| SequencerCommand { + //// TODO: k: toggle on-screen keyboard + //ctrl(key(Char('k'))) => { todo!("keyboard") }, + //// Transport: Play/pause + //key(Char(' ')) => SeqCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), + //// Transport: Play from start or rewind to start + //shift(key(Char(' '))) => SeqCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), + //// u: undo + //key(Char('u')) => SeqCmd::History(-1), + //// Shift-U: redo + //key(Char('U')) => SeqCmd::History( 1), + //// Tab: Toggle compact mode + //key(Tab) => SeqCmd::Compact(!state.compact), + //// q: Enqueue currently edited clip + //key(Char('q')) => SeqCmd::Enqueue(state.pool.clip().clone()), + //// 0: Enqueue clip 0 (stop all) + //key(Char('0')) => SeqCmd::Enqueue(Some(state.clips()[0].clone())), + //// e: Toggle between editing currently playing or other clip + ////key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_clip() { + ////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); + ////let selected = state.pool.clip().clone(); + ////SeqCmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { + ////selected + ////} else { + ////playing.clone() + ////}))) + ////} else { + ////return None + ////} +//}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { + //SeqCmd::Editor(command) +//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { + //SeqCmd::Pool(command) +//} else { + //return None +//}); +//keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { + //// Tab: Toggle compact mode + //key(Tab) => GrvCmd::Compact(!state.compact), + //// q: Enqueue currently edited clip + //key(Char('q')) => GrvCmd::Enqueue(state.pool.clip().clone()), + //// 0: Enqueue clip 0 (stop all) + //key(Char('0')) => GrvCmd::Enqueue(Some(state.pool.clips()[0].clone())), + //// TODO: k: toggle on-screen keyboard + //ctrl(key(Char('k'))) => todo!("keyboard"), + //// Transport: Play from start or rewind to start + //ctrl(key(Char(' '))) => GrvCmd::Clock( + //if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } + //), + //// Shift-R: toggle recording + //shift(key(Char('R'))) => GrvCmd::Sampler(if state.sampler.recording.is_some() { + //SmplCmd::RecordFinish + //} else { + //SmplCmd::RecordBegin(u7::from(state.editor.note_point() as u8)) + //}), + //// Shift-Del: delete sample + //shift(key(Delete)) => GrvCmd::Sampler( + //SmplCmd::SetSample(u7::from(state.editor.note_point() as u8), None) + //), + //// e: Toggle between editing currently playing or other clip + ////shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_clip() { + ////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); + ////let selected = state.pool.clip().clone().map(|s|s.read().unwrap().clone()); + ////GrvCmd::Editor(Show(if selected != editing { + ////selected + ////} else { + ////Some(playing.clone()) + ////})) + ////} else { + ////return None + ////}, +//}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { + //GrvCmd::Editor(command) +//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { + //GrvCmd::Pool(command) +//} else { + //return None +//}); +//keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { + //key(Char('u')) => ArrCmd::History(-1), + //key(Char('U')) => ArrCmd::History(1), + //// TODO: k: toggle on-screen keyboard + //ctrl(key(Char('k'))) => { todo!("keyboard") }, + //// Transport: Play/pause + //key(Char(' ')) => ArrCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), + //// Transport: Play from start or rewind to start + //shift(key(Char(' '))) => ArrCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), + //key(Char('e')) => ArrCmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())), + //ctrl(key(Char('a'))) => ArrCmd::Scene(SceneCommand::Add), + //ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(SceneCommand::Add), + //ctrl(key(Char('t'))) => ArrCmd::Track(TrackCommand::Add), + //// Tab: Toggle visibility of clip pool column + //key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)), +//}, { + //use ArrangerSelection as Selected; + //use SceneCommand as Scene; + //use TrackCommand as Track; + //use ClipCommand as Clip; + //let t_len = state.tracks.len(); + //let s_len = state.scenes.len(); + //match state.selected { + //Selected::Clip(t, s) => clip_keymap(state, input, t, s), + //Selected::Scene(s) => scene_keymap(state, input, s), + //Selected::Track(t) => track_keymap(state, input, t), + //Selected::Mix => match input { + + //kpat!(Delete) => Some(ArrCmd::Clear), + //kpat!(Char('0')) => Some(ArrCmd::StopAll), + //kpat!(Char('c')) => Some(ArrCmd::Color(ItemPalette::random())), + + //kpat!(Up) => return None, + //kpat!(Down) => Some(ArrCmd::Select(Selected::Scene(0))), + //kpat!(Left) => return None, + //kpat!(Right) => Some(ArrCmd::Select(Selected::Track(0))), + + //_ => None + //}, + //} +//}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { + //Some(ArrCmd::Editor(command)) +//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { + //Some(ArrCmd::Pool(command)) +//} else { + //None +//})?); + +//fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option { + //use ArrangerSelection as Selected; + //use SceneCommand as Scene; + //use TrackCommand as Track; + //use ClipCommand as Clip; + //let t_len = state.tracks.len(); + //let s_len = state.scenes.len(); + //Some(match input { + + //kpat!(Char('g')) => ArrCmd::Pool(PoolCommand::Select(0)), + //kpat!(Char('q')) => ArrCmd::Clip(Clip::Enqueue(t, s)), + //kpat!(Char('l')) => ArrCmd::Clip(Clip::SetLoop(t, s, false)), + + //kpat!(Enter) => if state.scenes[s].clips[t].is_none() { + //// FIXME: get this clip from the pool (autoregister via intmut) + //let (_, clip) = state.add_clip(); + //ArrCmd::Clip(Clip::Put(t, s, Some(clip))) + //} else { + //return None + //}, + //kpat!(Delete) => ArrCmd::Clip(Clip::Put(t, s, None)), + //kpat!(Char('p')) => ArrCmd::Clip(Clip::Put(t, s, state.pool.clip().clone())), + //kpat!(Char(',')) => ArrCmd::Clip(Clip::Put(t, s, None)), + //kpat!(Char('.')) => ArrCmd::Clip(Clip::Put(t, s, None)), + //kpat!(Char('<')) => ArrCmd::Clip(Clip::Put(t, s, None)), + //kpat!(Char('>')) => ArrCmd::Clip(Clip::Put(t, s, None)), + + //kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }), + //kpat!(Down) => ArrCmd::Select(Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1)))), + //kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) }), + //kpat!(Right) => ArrCmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)), + + //_ => return None + //}) +//} +//fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option { + //use ArrangerSelection as Selected; + //use SceneCommand as Scene; + //use TrackCommand as Track; + //use ClipCommand as Clip; + //let t_len = state.tracks.len(); + //let s_len = state.scenes.len(); + //Some(match input { + + //kpat!(Char(',')) => ArrCmd::Scene(Scene::Swap(s, s - 1)), + //kpat!(Char('.')) => ArrCmd::Scene(Scene::Swap(s, s + 1)), + //kpat!(Char('<')) => ArrCmd::Scene(Scene::Swap(s, s - 1)), + //kpat!(Char('>')) => ArrCmd::Scene(Scene::Swap(s, s + 1)), + //kpat!(Char('q')) => ArrCmd::Scene(Scene::Enqueue(s)), + //kpat!(Delete) => ArrCmd::Scene(Scene::Del(s)), + //kpat!(Char('c')) => ArrCmd::Scene(Scene::SetColor(s, ItemPalette::random())), + + //kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }), + //kpat!(Down) => ArrCmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1)))), + //kpat!(Left) => return None, + //kpat!(Right) => ArrCmd::Select(Selected::Clip(0, s)), + + //_ => return None + //}) +//} +//fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option { + //use ArrangerSelection as Selected; + //use SceneCommand as Scene; + //use TrackCommand as Track; + //use ClipCommand as Clip; + //let t_len = state.tracks.len(); + //let s_len = state.scenes.len(); + //Some(match input { + + //kpat!(Char(',')) => ArrCmd::Track(Track::Swap(t, t - 1)), + //kpat!(Char('.')) => ArrCmd::Track(Track::Swap(t, t + 1)), + //kpat!(Char('<')) => ArrCmd::Track(Track::Swap(t, t - 1)), + //kpat!(Char('>')) => ArrCmd::Track(Track::Swap(t, t + 1)), + //kpat!(Delete) => ArrCmd::Track(Track::Del(t)), + //kpat!(Char('c')) => ArrCmd::Track(Track::SetColor(t, ItemPalette::random())), + + //kpat!(Up) => return None, + //kpat!(Down) => ArrCmd::Select(Selected::Clip(t, 0)), + //kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }), + //kpat!(Right) => ArrCmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1)))), + + //_ => return None + //}) +//} +//impl HasJack for Arranger { + //fn jack (&self) -> &Arc> { &self.jack } +//} + +//audio!(|self: Sequencer, client, scope|{ + //// Start profiling cycle + //let t0 = self.perf.get_t0(); + + //// Update transport clock + //if Control::Quit == ClockAudio(self).process(client, scope) { + //return Control::Quit + //} + //// Update MIDI sequencer + //if Control::Quit == PlayerAudio( + //&mut self.player, &mut self.note_buf, &mut self.midi_buf + //).process(client, scope) { + //return Control::Quit + //} + + //// End profiling cycle + //self.perf.update(t0, scope); + + //Control::Continue +//}); + +//audio!(|self: Groovebox, client, scope|{ + //// Start profiling cycle + //let t0 = self.perf.get_t0(); + + //// Update transport clock + //if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { + //return Control::Quit + //} + + //// Update MIDI sequencer + //if Control::Quit == PlayerAudio( + //&mut self.player, &mut self.note_buf, &mut self.midi_buf + //).process(client, scope) { + //return Control::Quit + //} + + //// Update sampler + //if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { + //return Control::Quit + //} + + //// TODO move these to editor and sampler: + //for RawMidi { time, bytes } in self.player.midi_ins[0].port.iter(scope) { + //if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + //match message { + //MidiMessage::NoteOn { ref key, .. } => { + //self.editor.set_note_point(key.as_int() as usize); + //}, + //MidiMessage::Controller { controller, value } => { + //if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { + //sample.write().unwrap().handle_cc(controller, value) + //} + //} + //_ => {} + //} + //} + //} + + //// End profiling cycle + //self.perf.update(t0, scope); + + //Control::Continue +//}); + +//audio!(|self: Arranger, client, scope|{ + //// Start profiling cycle + //let t0 = self.perf.get_t0(); + + //// Update transport clock + //if Control::Quit == ClockAudio(self).process(client, scope) { + //return Control::Quit + //} + + ////// Update MIDI sequencers + ////let tracks = &mut self.tracks; + ////let note_buf = &mut self.note_buf; + ////let midi_buf = &mut self.midi_buf; + ////if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { + ////return Control::Quit + ////} + + //// FIXME: one of these per playing track + ////self.now.set(0.); + ////if let ArrangerSelection::Clip(t, s) = self.selected { + ////let clip = self.scenes.get(s).map(|scene|scene.clips.get(t)); + ////if let Some(Some(Some(clip))) = clip { + ////if let Some(track) = self.tracks().get(t) { + ////if let Some((ref started_at, Some(ref playing))) = track.player.play_clip { + ////let clip = clip.read().unwrap(); + ////if *playing.read().unwrap() == *clip { + ////let pulse = self.current().pulse.get(); + ////let start = started_at.pulse.get(); + ////let now = (pulse - start) % clip.length as f64; + ////self.now.set(now); + ////} + ////} + ////} + ////} + ////} + + //// End profiling cycle + //self.perf.update(t0, scope); + //return Control::Continue +//}); diff --git a/cli/tek.rs b/cli/tek.rs index 680db546..d2466dfe 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -149,7 +149,7 @@ pub fn main () -> Usually<()> { TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({ App::arranger( jack, - PoolModel::default(), + MidiPool::default(), MidiEditor::default(), &midi_froms, &midi_tos, default_sampler(jack)?, audio_froms, audio_tos, scenes, tracks, track_width @@ -163,7 +163,7 @@ pub fn main () -> Usually<()> { //midi_ins: vec![JackPort::::new(jack, format!("M/{name}"), &midi_froms)?,], //midi_outs: vec![JackPort::::new(jack, format!("{name}/M"), &midi_tos)?, ], //clock, - //pool: PoolModel::default(),//(&clip).into(), + //pool: MidiPool::default(),//(&clip).into(), //editor: MidiEditor::default(),//(&clip).into(), //selected: ArrangerSelection::Clip(0, 0), //scenes: vec![], diff --git a/midi/src/midi_pool.rs b/midi/src/midi_pool.rs index a31aefa0..250d49a2 100644 --- a/midi/src/midi_pool.rs +++ b/midi/src/midi_pool.rs @@ -22,7 +22,7 @@ pub trait HasClips { } } #[derive(Debug)] -pub struct PoolModel { +pub struct MidiPool { pub visible: bool, /// Collection of clips pub clips: Arc>>>>, @@ -43,7 +43,7 @@ pub enum PoolMode { /// Save clip to disk Export(usize, FileBrowser), } -impl Default for PoolModel { +impl Default for MidiPool { fn default () -> Self { Self { visible: true, @@ -53,15 +53,15 @@ impl Default for PoolModel { } } } -from!(|clip:&Arc>|PoolModel = { +from!(|clip:&Arc>|MidiPool = { let model = Self::default(); model.clips.write().unwrap().push(clip.clone()); model.clip.store(1, Relaxed); model }); -has_clips!(|self: PoolModel|self.clips); -has_clip!(|self: PoolModel|self.clips().get(self.clip_index()).map(|c|c.clone())); -impl PoolModel { +has_clips!(|self: MidiPool|self.clips); +has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone())); +impl MidiPool { pub(crate) fn clip_index (&self) -> usize { self.clip.load(Relaxed) } diff --git a/midi/src/midi_pool_cmd.rs b/midi/src/midi_pool_cmd.rs index f163bba5..ba2f677f 100644 --- a/midi/src/midi_pool_cmd.rs +++ b/midi/src/midi_pool_cmd.rs @@ -15,8 +15,8 @@ pub enum PoolCommand { /// Export to file Export(FileBrowserCommand), } -impl EdnCommand for PoolCommand { - fn from_edn <'a> (state: &PoolModel, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { +impl EdnCommand for PoolCommand { + fn from_edn <'a> (state: &MidiPool, head: &EdnItem<&str>, tail: &'a [EdnItem]) -> Self { todo!() } } @@ -101,7 +101,7 @@ impl Command for PoolClipCommand { }) } } -command!(|self:PoolCommand, state: PoolModel|{ +command!(|self:PoolCommand, state: MidiPool|{ use PoolCommand::*; match self { Show(visible) => { @@ -153,14 +153,14 @@ command!(|self:PoolCommand, state: PoolModel|{ Clip(command) => command.execute(state)?.map(Clip), } }); -input_to_command!(PoolCommand: |state: PoolModel, input: Event|match state.clips_mode() { +input_to_command!(PoolCommand: |state: MidiPool, input: Event|match state.clips_mode() { Some(PoolMode::Rename(..)) => Self::Rename(ClipRenameCommand::input_to_command(state, input)?), Some(PoolMode::Length(..)) => Self::Length(ClipLengthCommand::input_to_command(state, input)?), Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?), Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?), _ => to_clips_command(state, input)? }); -fn to_clips_command (state: &PoolModel, input: &Event) -> Option { +fn to_clips_command (state: &MidiPool, input: &Event) -> Option { use KeyCode::{Up, Down, Delete, Char}; use PoolCommand as Cmd; let index = state.clip_index(); @@ -209,7 +209,7 @@ fn to_clips_command (state: &PoolModel, input: &Event) -> Option { _ => return None }) } -command!(|self: FileBrowserCommand, state: PoolModel|{ +command!(|self: FileBrowserCommand, state: MidiPool|{ use PoolMode::*; use FileBrowserCommand::*; let mode = &mut state.mode; @@ -238,7 +238,7 @@ command!(|self: FileBrowserCommand, state: PoolModel|{ }; None }); -input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{ +input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{ use FileBrowserCommand::*; use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char}; if let Some(PoolMode::Import(_index, browser)) = &state.mode { @@ -283,7 +283,7 @@ pub enum ClipLengthCommand { Inc, Dec, } -command!(|self: ClipLengthCommand,state:PoolModel|{ +command!(|self: ClipLengthCommand,state:MidiPool|{ use ClipLengthCommand::*; use ClipLengthFocus::*; match state.clips_mode_mut().clone() { @@ -317,7 +317,7 @@ command!(|self: ClipLengthCommand,state:PoolModel|{ }; None }); -input_to_command!(ClipLengthCommand: |state: PoolModel, input: Event|{ +input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{ if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() { match input { kpat!(Up) => Self::Inc, @@ -332,8 +332,8 @@ input_to_command!(ClipLengthCommand: |state: PoolModel, input: Event|{ unreachable!() } }); -impl InputToCommand for ClipRenameCommand { - fn input_to_command (state: &PoolModel, input: &Event) -> Option { +impl InputToCommand for ClipRenameCommand { + fn input_to_command (state: &MidiPool, input: &Event) -> Option { use KeyCode::{Char, Backspace, Enter, Esc}; if let Some(PoolMode::Rename(_, ref old_name)) = state.clips_mode() { Some(match input { @@ -363,8 +363,8 @@ pub enum ClipRenameCommand { Confirm, Set(Arc), } -impl Command for ClipRenameCommand { - fn execute (self, state: &mut PoolModel) -> Perhaps { +impl Command for ClipRenameCommand { + fn execute (self, state: &mut MidiPool) -> Perhaps { use ClipRenameCommand::*; match state.clips_mode_mut().clone() { Some(PoolMode::Rename(clip, ref mut old_name)) => match self { diff --git a/midi/src/midi_pool_tui.rs b/midi/src/midi_pool_tui.rs index 03e846e9..28127243 100644 --- a/midi/src/midi_pool_tui.rs +++ b/midi/src/midi_pool_tui.rs @@ -1,8 +1,8 @@ use crate::*; -pub struct PoolView<'a>(pub bool, pub &'a PoolModel); +pub struct PoolView<'a>(pub bool, pub &'a MidiPool); render!(TuiOut: (self: PoolView<'a>) => { let Self(compact, model) = self; - let PoolModel { clips, mode, .. } = self.1; + let MidiPool { clips, mode, .. } = self.1; let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into()); let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x)); let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x); diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index 2ec16aae..15e404dd 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -2,6 +2,31 @@ use crate::*; use EdnItem::*; use ClockCommand::{Play, Pause}; pub const TRACK_MIN_WIDTH: usize = 9; +impl HasJack for App { + fn jack (&self) -> &Arc> { &self.jack } +} +/// Hosts the JACK callback for a collection of tracks +pub struct TracksAudio<'a>( + // Track collection + pub &'a mut [ArrangerTrack], + /// Note buffer + pub &'a mut Vec, + /// Note chunk buffer + pub &'a mut Vec>>, +); +impl Audio for TracksAudio<'_> { + #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + let model = &mut self.0; + let note_buffer = &mut self.1; + let output_buffer = &mut self.2; + for track in model.iter_mut() { + if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit { + return Control::Quit + } + } + Control::Continue + } +} command!(|self: ClipCommand, state: App|match self { _ => todo!("clip command") }); command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") }); command!(|self: TrackCommand, state: App|match self { _ => todo!("track command") }); diff --git a/tek/src/audio.rs b/tek/src/audio.rs deleted file mode 100644 index 0ad1613c..00000000 --- a/tek/src/audio.rs +++ /dev/null @@ -1,232 +0,0 @@ -use crate::*; -impl HasJack for App { - fn jack (&self) -> &Arc> { &self.jack } -} -//impl HasJack for Arranger { - //fn jack (&self) -> &Arc> { &self.jack } -//} -audio!(|self: App, client, scope|{ - // Start profiling cycle - let t0 = self.perf.get_t0(); - // Update transport clock - if Control::Quit == ClockAudio(self).process(client, scope) { - return Control::Quit - } - // Collect MIDI input (TODO preallocate) - let midi_in = self.midi_ins.iter() - .map(|port|port.port.iter(scope) - .map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes))) - .collect::>()) - .collect::>(); - // Update standalone MIDI sequencer - if let Some(player) = self.player.as_mut() { - if Control::Quit == PlayerAudio( - player, - &mut self.note_buf, - &mut self.midi_buf, - ).process(client, scope) { - return Control::Quit - } - } - // Update standalone sampler - if let Some(sampler) = self.sampler.as_mut() { - if Control::Quit == SamplerAudio(sampler).process(client, scope) { - return Control::Quit - } - //for port in midi_in.iter() { - //for message in port.iter() { - //match message { - //Ok(M - //} - //} - //} - } - // TODO move these to editor and sampler?: - for port in midi_in.iter() { - for event in port.iter() { - match event { - (time, Ok(LiveEvent::Midi {message, ..})) => match message { - MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => { - editor.set_note_point(key.as_int() as usize); - }, - MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = ( - self.editor.as_ref(), - self.sampler.as_ref(), - ) => { - // TODO: give sampler its own cursor - if let Some(sample) = &sampler.mapped[editor.note_point()] { - sample.write().unwrap().handle_cc(*controller, *value) - } - } - _ =>{} - }, - _ =>{} - } - } - } - - // Update track sequencers - let tracks = &mut self.tracks; - let note_buf = &mut self.note_buf; - let midi_buf = &mut self.midi_buf; - if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { - return Control::Quit - } - - // TODO: update timeline position in editor. - // must be in sync with clip's playback. since - // a clip can be on multiple tracks and launched - // at different times, add a playhead with the - // playing track's color. - //self.now.set(0.); - //if let ArrangerSelection::Clip(t, s) = self.selected { - //let clip = self.scenes.get(s).map(|scene|scene.clips.get(t)); - //if let Some(Some(Some(clip))) = clip { - //if let Some(track) = self.tracks().get(t) { - //if let Some((ref started_at, Some(ref playing))) = track.player.play_clip { - //let clip = clip.read().unwrap(); - //if *playing.read().unwrap() == *clip { - //let pulse = self.current().pulse.get(); - //let start = started_at.pulse.get(); - //let now = (pulse - start) % clip.length as f64; - //self.now.set(now); - //} - //} - //} - //} - //} - - // End profiling cycle - self.perf.update(t0, scope); - Control::Continue -}); - -/// Hosts the JACK callback for a collection of tracks -pub struct TracksAudio<'a>( - // Track collection - pub &'a mut [ArrangerTrack], - /// Note buffer - pub &'a mut Vec, - /// Note chunk buffer - pub &'a mut Vec>>, -); - -impl Audio for TracksAudio<'_> { - #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - let model = &mut self.0; - let note_buffer = &mut self.1; - let output_buffer = &mut self.2; - for track in model.iter_mut() { - if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit { - return Control::Quit - } - } - Control::Continue - } -} - -//audio!(|self: Sequencer, client, scope|{ - //// Start profiling cycle - //let t0 = self.perf.get_t0(); - - //// Update transport clock - //if Control::Quit == ClockAudio(self).process(client, scope) { - //return Control::Quit - //} - //// Update MIDI sequencer - //if Control::Quit == PlayerAudio( - //&mut self.player, &mut self.note_buf, &mut self.midi_buf - //).process(client, scope) { - //return Control::Quit - //} - - //// End profiling cycle - //self.perf.update(t0, scope); - - //Control::Continue -//}); - -//audio!(|self: Groovebox, client, scope|{ - //// Start profiling cycle - //let t0 = self.perf.get_t0(); - - //// Update transport clock - //if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { - //return Control::Quit - //} - - //// Update MIDI sequencer - //if Control::Quit == PlayerAudio( - //&mut self.player, &mut self.note_buf, &mut self.midi_buf - //).process(client, scope) { - //return Control::Quit - //} - - //// Update sampler - //if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { - //return Control::Quit - //} - - //// TODO move these to editor and sampler: - //for RawMidi { time, bytes } in self.player.midi_ins[0].port.iter(scope) { - //if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { - //match message { - //MidiMessage::NoteOn { ref key, .. } => { - //self.editor.set_note_point(key.as_int() as usize); - //}, - //MidiMessage::Controller { controller, value } => { - //if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { - //sample.write().unwrap().handle_cc(controller, value) - //} - //} - //_ => {} - //} - //} - //} - - //// End profiling cycle - //self.perf.update(t0, scope); - - //Control::Continue -//}); - -//audio!(|self: Arranger, client, scope|{ - //// Start profiling cycle - //let t0 = self.perf.get_t0(); - - //// Update transport clock - //if Control::Quit == ClockAudio(self).process(client, scope) { - //return Control::Quit - //} - - ////// Update MIDI sequencers - ////let tracks = &mut self.tracks; - ////let note_buf = &mut self.note_buf; - ////let midi_buf = &mut self.midi_buf; - ////if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { - ////return Control::Quit - ////} - - //// FIXME: one of these per playing track - ////self.now.set(0.); - ////if let ArrangerSelection::Clip(t, s) = self.selected { - ////let clip = self.scenes.get(s).map(|scene|scene.clips.get(t)); - ////if let Some(Some(Some(clip))) = clip { - ////if let Some(track) = self.tracks().get(t) { - ////if let Some((ref started_at, Some(ref playing))) = track.player.play_clip { - ////let clip = clip.read().unwrap(); - ////if *playing.read().unwrap() == *clip { - ////let pulse = self.current().pulse.get(); - ////let start = started_at.pulse.get(); - ////let now = (pulse - start) % clip.length as f64; - ////self.now.set(now); - ////} - ////} - ////} - ////} - ////} - - //// End profiling cycle - //self.perf.update(t0, scope); - //return Control::Continue -//}); diff --git a/tek/src/control.rs b/tek/src/control.rs deleted file mode 100644 index 73ab4a4c..00000000 --- a/tek/src/control.rs +++ /dev/null @@ -1,518 +0,0 @@ -use crate::*; -use EdnItem::*; -use ClockCommand::{Play, Pause}; -use KeyCode::{Tab, Char}; -use SamplerCommand as SmplCmd; -use MidiEditCommand as EditCmd; -use PoolClipCommand as PoolCmd; -handle!(TuiIn: |self: App, input| Ok(None)); -#[derive(Clone, Debug)] pub enum AppCommand { - Clear, - Clip(ClipCommand), - Clock(ClockCommand), - Color(ItemPalette), - Compact(Option), - Editor(MidiEditCommand), - Enqueue(Option>>), - History(isize), - Pool(PoolCommand), - Sampler(SamplerCommand), - Scene(SceneCommand), - Select(ArrangerSelection), - StopAll, - Track(TrackCommand), - Zoom(Option), -} -edn_command!(AppCommand: |state: App| { - ("clear" [] Self::Clear) - ("stop-all" [] Self::StopAll) - ("compact" [c: bool ] Self::Compact(c)) - ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) - ("history" [d: isize] Self::History(d.unwrap_or(0))) - ("zoom" [z: usize] Self::Zoom(z)) - ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b))) - ("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b))) - ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b))) - ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b))) - ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b))) - ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b))) - ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b))) - ("select" [s: ArrangerSelection] Self::Select(s.expect("no selection"))) - ("enqueue" [c: Arc>] Self::Enqueue(c)) -}); -command!(|self: AppCommand, state: App|match self { - Self::Clear => { todo!() }, - Self::Zoom(_) => { todo!(); }, - Self::History(delta) => { todo!("undo/redo") }, - Self::Select(s) => { state.selected = s; None }, - Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - Self::Scene(cmd) => match cmd { - SceneCommand::Add => { state.scene_add(None, None)?; None } - SceneCommand::Del(index) => { state.scene_del(index); None }, - SceneCommand::SetColor(index, color) => { - let old = state.scenes[index].color; - state.scenes[index].color = color; - Some(SceneCommand::SetColor(index, old)) - }, - SceneCommand::Enqueue(scene) => { - for track in 0..state.tracks.len() { - state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); - } - None - }, - _ => None - }.map(Self::Scene), - Self::Track(cmd) => match cmd { - TrackCommand::Add => { state.track_add(None, None)?; None }, - TrackCommand::Del(index) => { state.track_del(index); None }, - TrackCommand::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, - TrackCommand::SetColor(index, color) => { - let old = state.tracks[index].color; - state.tracks[index].color = color; - Some(TrackCommand::SetColor(index, old)) - }, - _ => None - }.map(Self::Track), - Self::Clip(cmd) => match cmd { - ClipCommand::Get(track, scene) => { todo!() }, - ClipCommand::Put(track, scene, clip) => { - let old = state.scenes[scene].clips[track].clone(); - state.scenes[scene].clips[track] = clip; - Some(ClipCommand::Put(track, scene, old)) - }, - ClipCommand::Enqueue(track, scene) => { - state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); - None - }, - _ => None - }.map(Self::Clip), - Self::Editor(cmd) => - state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), - Self::Sampler(cmd) => - state.sampler.as_mut().map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(), - Self::Enqueue(clip) => - state.player.as_mut().map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(), - Self::StopAll => { - for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); } - None - }, - Self::Color(palette) => { - let old = state.color; - state.color = palette; - Some(Self::Color(old)) - }, - Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() { - let undo = cmd.clone().delegate(pool, Self::Pool)?; - if let Some(editor) = state.editor.as_mut() { - match cmd { - // autoselect: automatically load selected clip in editor - // autocolor: update color in all places simultaneously - PoolCommand::Select(_) | PoolCommand::Clip(PoolCmd::SetColor(_, _)) => - editor.set_clip(pool.clip().as_ref()), - _ => {} - } - }; - undo - } else { - None - }, - Self::Compact(compact) => match compact { - Some(compact) => { - if state.compact != compact { - state.compact = compact; - Some(Self::Compact(Some(!compact))) - } else { - None - } - }, - None => { - state.compact = !state.compact; - Some(Self::Compact(Some(!state.compact))) - } - } -}); - -/////////////////////////////////////////////////////////////////////////////////////////////////// -//handle!(TuiIn: |self: Sequencer, input|SequencerCommand::execute_with_state(self, input.event())); -//handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event())); -//handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); -//use SequencerCommand as SeqCmd; -//use GrooveboxCommand as GrvCmd; -//use ArrangerCommand as ArrCmd; -//#[derive(Clone, Debug)] pub enum SequencerCommand { - //Compact(bool), - //History(isize), - //Clock(ClockCommand), - //Pool(PoolCommand), - //Editor(MidiEditCommand), - //Enqueue(Option>>), -//} -//#[derive(Clone, Debug)] pub enum GrooveboxCommand { - //Compact(bool), - //History(isize), - //Clock(ClockCommand), - //Pool(PoolCommand), - //Editor(MidiEditCommand), - //Enqueue(Option>>), - //Sampler(SamplerCommand), -//} -//#[derive(Clone, Debug)] pub enum ArrangerCommand { - //History(isize), - //Color(ItemPalette), - //Clock(ClockCommand), - //Scene(SceneCommand), - //Track(TrackCommand), - //Clip(ClipCommand), - //Select(ArrangerSelection), - //Zoom(usize), - //Pool(PoolCommand), - //Editor(MidiEditCommand), - //StopAll, - //Clear, -//} - -//command!(|self: SequencerCommand, state: Sequencer|match self { - //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, - //Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None }, - //Self::History(delta) => { todo!("undo/redo") }, - - //Self::Pool(cmd) => match cmd { - //// autoselect: automatically load selected clip in editor - //PoolCommand::Select(_) => { - //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - //state.editor.set_clip(state.pool.clip().as_ref()); - //undo - //}, - //// update color in all places simultaneously - //PoolCommand::Clip(PoolCmd::SetColor(index, _)) => { - //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - //state.editor.set_clip(state.pool.clip().as_ref()); - //undo - //}, - //_ => cmd.delegate(&mut state.pool, Self::Pool)? - //}, - //Self::Compact(compact) => if state.compact != compact { - //state.compact = compact; - //Some(Self::Compact(!compact)) - //} else { - //None - //}, -//}); -//command!(|self: GrooveboxCommand, state: Groovebox|match self { - //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, - //Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None }, - //Self::History(delta) => { todo!("undo/redo") }, - //Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?, - - //Self::Pool(cmd) => match cmd { - //// autoselect: automatically load selected clip in editor - //PoolCommand::Select(_) => { - //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - //state.editor.set_clip(state.pool.clip().as_ref()); - //undo - //}, - //// update color in all places simultaneously - //PoolCommand::Clip(PoolCmd::SetColor(index, _)) => { - //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - //state.editor.set_clip(state.pool.clip().as_ref()); - //undo - //}, - //_ => cmd.delegate(&mut state.pool, Self::Pool)? - //}, - //Self::Compact(compact) => if state.compact != compact { - //state.compact = compact; - //Some(Self::Compact(!compact)) - //} else { - //None - //}, -//}); -//command!(|self: ArrangerCommand, state: Arranger|match self { - //Self::Clear => { todo!() }, - //Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?, - //Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - //Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?, - //Self::History(_) => { todo!() }, - //Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, - //Self::Select(s) => { state.selected = s; None }, - //Self::Track(cmd) => cmd.delegate(state, Self::Track)?, - //Self::Zoom(_) => { todo!(); }, - - //Self::StopAll => { - //for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); } - //None - //}, - //Self::Color(palette) => { - //let old = state.color; - //state.color = palette; - //Some(Self::Color(old)) - //}, - //Self::Pool(cmd) => { - //match cmd { - //// autoselect: automatically load selected clip in editor - //PoolCommand::Select(_) => { - //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - //state.editor.set_clip(state.pool.clip().as_ref()); - //undo - //}, - //// reload clip in editor to update color - //PoolCommand::Clip(PoolClipCommand::SetColor(index, _)) => { - //let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - //state.editor.set_clip(state.pool.clip().as_ref()); - //undo - //}, - //_ => cmd.delegate(&mut state.pool, Self::Pool)? - //} - //}, -//}); -//command!(|self: SceneCommand, state: Arranger|match self { - //Self::Add => { state.scene_add(None, None)?; None } - //Self::Del(index) => { state.scene_del(index); None }, - //Self::SetColor(index, color) => { - //let old = state.scenes[index].color; - //state.scenes[index].color = color; - //Some(Self::SetColor(index, old)) - //}, - //Self::Enqueue(scene) => { - //for track in 0..state.tracks.len() { - //state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); - //} - //None - //}, - //_ => None -//}); -//command!(|self: TrackCommand, state: Arranger|match self { - //Self::Add => { state.track_add(None, None)?; None }, - //Self::Del(index) => { state.track_del(index); None }, - //Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, - //Self::SetColor(index, color) => { - //let old = state.tracks[index].color; - //state.tracks[index].color = color; - //Some(Self::SetColor(index, old)) - //}, - //_ => None -//}); -//command!(|self: ClipCommand, state: Arranger|match self { - //Self::Get(track, scene) => { todo!() }, - //Self::Put(track, scene, clip) => { - //let old = state.scenes[scene].clips[track].clone(); - //state.scenes[scene].clips[track] = clip; - //Some(Self::Put(track, scene, old)) - //}, - //Self::Enqueue(track, scene) => { - //state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); - //None - //}, - //_ => None -//}); -//keymap!(KEYS_SEQUENCER = |state: Sequencer, input: Event| SequencerCommand { - //// TODO: k: toggle on-screen keyboard - //ctrl(key(Char('k'))) => { todo!("keyboard") }, - //// Transport: Play/pause - //key(Char(' ')) => SeqCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), - //// Transport: Play from start or rewind to start - //shift(key(Char(' '))) => SeqCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), - //// u: undo - //key(Char('u')) => SeqCmd::History(-1), - //// Shift-U: redo - //key(Char('U')) => SeqCmd::History( 1), - //// Tab: Toggle compact mode - //key(Tab) => SeqCmd::Compact(!state.compact), - //// q: Enqueue currently edited clip - //key(Char('q')) => SeqCmd::Enqueue(state.pool.clip().clone()), - //// 0: Enqueue clip 0 (stop all) - //key(Char('0')) => SeqCmd::Enqueue(Some(state.clips()[0].clone())), - //// e: Toggle between editing currently playing or other clip - ////key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_clip() { - ////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); - ////let selected = state.pool.clip().clone(); - ////SeqCmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { - ////selected - ////} else { - ////playing.clone() - ////}))) - ////} else { - ////return None - ////} -//}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { - //SeqCmd::Editor(command) -//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - //SeqCmd::Pool(command) -//} else { - //return None -//}); -//keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { - //// Tab: Toggle compact mode - //key(Tab) => GrvCmd::Compact(!state.compact), - //// q: Enqueue currently edited clip - //key(Char('q')) => GrvCmd::Enqueue(state.pool.clip().clone()), - //// 0: Enqueue clip 0 (stop all) - //key(Char('0')) => GrvCmd::Enqueue(Some(state.pool.clips()[0].clone())), - //// TODO: k: toggle on-screen keyboard - //ctrl(key(Char('k'))) => todo!("keyboard"), - //// Transport: Play from start or rewind to start - //ctrl(key(Char(' '))) => GrvCmd::Clock( - //if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } - //), - //// Shift-R: toggle recording - //shift(key(Char('R'))) => GrvCmd::Sampler(if state.sampler.recording.is_some() { - //SmplCmd::RecordFinish - //} else { - //SmplCmd::RecordBegin(u7::from(state.editor.note_point() as u8)) - //}), - //// Shift-Del: delete sample - //shift(key(Delete)) => GrvCmd::Sampler( - //SmplCmd::SetSample(u7::from(state.editor.note_point() as u8), None) - //), - //// e: Toggle between editing currently playing or other clip - ////shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_clip() { - ////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); - ////let selected = state.pool.clip().clone().map(|s|s.read().unwrap().clone()); - ////GrvCmd::Editor(Show(if selected != editing { - ////selected - ////} else { - ////Some(playing.clone()) - ////})) - ////} else { - ////return None - ////}, -//}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { - //GrvCmd::Editor(command) -//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - //GrvCmd::Pool(command) -//} else { - //return None -//}); -//keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { - //key(Char('u')) => ArrCmd::History(-1), - //key(Char('U')) => ArrCmd::History(1), - //// TODO: k: toggle on-screen keyboard - //ctrl(key(Char('k'))) => { todo!("keyboard") }, - //// Transport: Play/pause - //key(Char(' ')) => ArrCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), - //// Transport: Play from start or rewind to start - //shift(key(Char(' '))) => ArrCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), - //key(Char('e')) => ArrCmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())), - //ctrl(key(Char('a'))) => ArrCmd::Scene(SceneCommand::Add), - //ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(SceneCommand::Add), - //ctrl(key(Char('t'))) => ArrCmd::Track(TrackCommand::Add), - //// Tab: Toggle visibility of clip pool column - //key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)), -//}, { - //use ArrangerSelection as Selected; - //use SceneCommand as Scene; - //use TrackCommand as Track; - //use ClipCommand as Clip; - //let t_len = state.tracks.len(); - //let s_len = state.scenes.len(); - //match state.selected { - //Selected::Clip(t, s) => clip_keymap(state, input, t, s), - //Selected::Scene(s) => scene_keymap(state, input, s), - //Selected::Track(t) => track_keymap(state, input, t), - //Selected::Mix => match input { - - //kpat!(Delete) => Some(ArrCmd::Clear), - //kpat!(Char('0')) => Some(ArrCmd::StopAll), - //kpat!(Char('c')) => Some(ArrCmd::Color(ItemPalette::random())), - - //kpat!(Up) => return None, - //kpat!(Down) => Some(ArrCmd::Select(Selected::Scene(0))), - //kpat!(Left) => return None, - //kpat!(Right) => Some(ArrCmd::Select(Selected::Track(0))), - - //_ => None - //}, - //} -//}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) { - //Some(ArrCmd::Editor(command)) -//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - //Some(ArrCmd::Pool(command)) -//} else { - //None -//})?); - -//fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option { - //use ArrangerSelection as Selected; - //use SceneCommand as Scene; - //use TrackCommand as Track; - //use ClipCommand as Clip; - //let t_len = state.tracks.len(); - //let s_len = state.scenes.len(); - //Some(match input { - - //kpat!(Char('g')) => ArrCmd::Pool(PoolCommand::Select(0)), - //kpat!(Char('q')) => ArrCmd::Clip(Clip::Enqueue(t, s)), - //kpat!(Char('l')) => ArrCmd::Clip(Clip::SetLoop(t, s, false)), - - //kpat!(Enter) => if state.scenes[s].clips[t].is_none() { - //// FIXME: get this clip from the pool (autoregister via intmut) - //let (_, clip) = state.add_clip(); - //ArrCmd::Clip(Clip::Put(t, s, Some(clip))) - //} else { - //return None - //}, - //kpat!(Delete) => ArrCmd::Clip(Clip::Put(t, s, None)), - //kpat!(Char('p')) => ArrCmd::Clip(Clip::Put(t, s, state.pool.clip().clone())), - //kpat!(Char(',')) => ArrCmd::Clip(Clip::Put(t, s, None)), - //kpat!(Char('.')) => ArrCmd::Clip(Clip::Put(t, s, None)), - //kpat!(Char('<')) => ArrCmd::Clip(Clip::Put(t, s, None)), - //kpat!(Char('>')) => ArrCmd::Clip(Clip::Put(t, s, None)), - - //kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }), - //kpat!(Down) => ArrCmd::Select(Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1)))), - //kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) }), - //kpat!(Right) => ArrCmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)), - - //_ => return None - //}) -//} -//fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option { - //use ArrangerSelection as Selected; - //use SceneCommand as Scene; - //use TrackCommand as Track; - //use ClipCommand as Clip; - //let t_len = state.tracks.len(); - //let s_len = state.scenes.len(); - //Some(match input { - - //kpat!(Char(',')) => ArrCmd::Scene(Scene::Swap(s, s - 1)), - //kpat!(Char('.')) => ArrCmd::Scene(Scene::Swap(s, s + 1)), - //kpat!(Char('<')) => ArrCmd::Scene(Scene::Swap(s, s - 1)), - //kpat!(Char('>')) => ArrCmd::Scene(Scene::Swap(s, s + 1)), - //kpat!(Char('q')) => ArrCmd::Scene(Scene::Enqueue(s)), - //kpat!(Delete) => ArrCmd::Scene(Scene::Del(s)), - //kpat!(Char('c')) => ArrCmd::Scene(Scene::SetColor(s, ItemPalette::random())), - - //kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }), - //kpat!(Down) => ArrCmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1)))), - //kpat!(Left) => return None, - //kpat!(Right) => ArrCmd::Select(Selected::Clip(0, s)), - - //_ => return None - //}) -//} -//fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option { - //use ArrangerSelection as Selected; - //use SceneCommand as Scene; - //use TrackCommand as Track; - //use ClipCommand as Clip; - //let t_len = state.tracks.len(); - //let s_len = state.scenes.len(); - //Some(match input { - - //kpat!(Char(',')) => ArrCmd::Track(Track::Swap(t, t - 1)), - //kpat!(Char('.')) => ArrCmd::Track(Track::Swap(t, t + 1)), - //kpat!(Char('<')) => ArrCmd::Track(Track::Swap(t, t - 1)), - //kpat!(Char('>')) => ArrCmd::Track(Track::Swap(t, t + 1)), - //kpat!(Delete) => ArrCmd::Track(Track::Del(t)), - //kpat!(Char('c')) => ArrCmd::Track(Track::SetColor(t, ItemPalette::random())), - - //kpat!(Up) => return None, - //kpat!(Down) => ArrCmd::Select(Selected::Clip(t, 0)), - //kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }), - //kpat!(Right) => ArrCmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1)))), - - //_ => return None - //}) -//} diff --git a/tek/src/lib.rs b/tek/src/lib.rs index 458dae2a..561027f0 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -5,44 +5,39 @@ #![feature(if_let_guard)] #![feature(impl_trait_in_assoc_type)] #![feature(type_alias_impl_trait)] - /// Standard result type. pub type Usually = std::result::Result>; /// Standard optional result type. pub type Perhaps = std::result::Result, Box>; - -pub mod model; pub use self::model::*; -pub mod view; pub use self::view::*; -pub mod control; pub use self::control::*; -pub mod audio; pub use self::audio::*; - pub mod arranger; pub use self::arranger::*; pub mod mixer; pub use self::mixer::*; - -pub use ::tek_time; pub use ::tek_time::*; -pub use ::tek_jack; pub use ::tek_jack::{*, jack::{*, contrib::*}}; -pub use ::tek_midi; pub use ::tek_midi::{*, midly::{*, num::*, live::*}}; -pub use ::tek_sampler::{self, *}; -pub use ::tek_plugin::{self, *}; pub use ::tek_tui::{ *, tek_edn::*, tek_input::*, tek_output::*, - crossterm::{ - self, - event::{ - Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, - KeyCode::{self, *}, - } + crossterm, + crossterm::event::{ + Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, + KeyCode::{self, *}, }, + ratatui, ratatui::{ - self, prelude::{Color, Style, Stylize, Buffer, Modifier}, buffer::Cell, } }; - +pub use ::tek_time::{self, *}; +pub use ::tek_jack::{self, *, jack::{*, contrib::*}}; +pub use ::tek_midi::{self, *, midly::{*, num::*, live::*}}; +pub use ::tek_sampler::{self, *}; +pub use ::tek_plugin::{self, *}; +use EdnItem::*; +pub(crate) use ClockCommand::{Play, Pause}; +pub(crate) use KeyCode::{Tab, Char}; +pub(crate) use SamplerCommand as SmplCmd; +pub(crate) use MidiEditCommand as EditCmd; +pub(crate) use PoolClipCommand as PoolCmd; pub(crate) use std::cmp::{Ord, Eq, PartialEq}; pub(crate) use std::collections::BTreeMap; pub(crate) use std::error::Error; @@ -56,240 +51,623 @@ pub(crate) use std::thread::{spawn, JoinHandle}; pub(crate) use std::time::Duration; pub(crate) use std::path::PathBuf; pub(crate) use std::ffi::OsString; +#[derive(Default)] pub struct App { + pub jack: Arc>, + pub edn: String, + pub clock: Clock, + pub color: ItemPalette, + pub editing: AtomicBool, + pub pool: Option, + pub editor: Option, + pub player: Option, + pub sampler: Option, + pub midi_buf: Vec>>, + pub midi_ins: Vec>, + pub midi_outs: Vec>, + pub audio_ins: Vec>, + pub audio_outs: Vec>, + pub note_buf: Vec, + pub tracks: Vec, + pub scenes: Vec, + pub selected: ArrangerSelection, + pub splits: Vec, + pub size: Measure, + pub perf: PerfModel, + pub compact: bool, +} +impl App { + pub fn sequencer ( + jack: &Arc>, + pool: MidiPool, + editor: MidiEditor, + player: Option, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + ) -> Self { + Self { + edn: include_str!("../edn/sequencer-view.edn").to_string(), + jack: jack.clone(), + pool: Some(pool), + editor: Some(editor), + player: player, + editing: false.into(), + midi_buf: vec![vec![];65536], + color: ItemPalette::random(), + ..Default::default() + } + } + pub fn groovebox ( + jack: &Arc>, + pool: MidiPool, + editor: MidiEditor, + player: Option, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + sampler: Sampler, + audio_froms: &[&[PortConnection]], + audio_tos: &[&[PortConnection]], + ) -> Self { + Self { + edn: include_str!("../edn/groovebox-view.edn").to_string(), + sampler: Some(sampler), + ..Self::sequencer( + jack, pool, editor, + player, midi_froms, midi_tos + ) + } + } + pub fn arranger ( + jack: &Arc>, + pool: MidiPool, + editor: MidiEditor, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + sampler: Sampler, + audio_froms: &[&[PortConnection]], + audio_tos: &[&[PortConnection]], + scenes: usize, + tracks: usize, + track_width: usize, + ) -> Self { + let mut arranger = Self { + edn: include_str!("../edn/arranger-view.edn").to_string(), + ..Self::groovebox( + jack, pool, editor, + None, midi_froms, midi_tos, + sampler, audio_froms, audio_tos + ) + }; + arranger.scenes_add(scenes); + arranger.tracks_add(tracks, track_width, &[], &[]); + arranger + } +} +audio!(|self: App, client, scope|{ + // Start profiling cycle + let t0 = self.perf.get_t0(); + // Update transport clock + if Control::Quit == ClockAudio(self).process(client, scope) { + return Control::Quit + } + // Collect MIDI input (TODO preallocate) + let midi_in = self.midi_ins.iter() + .map(|port|port.port.iter(scope) + .map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes))) + .collect::>()) + .collect::>(); + // Update standalone MIDI sequencer + if let Some(player) = self.player.as_mut() { + if Control::Quit == PlayerAudio( + player, + &mut self.note_buf, + &mut self.midi_buf, + ).process(client, scope) { + return Control::Quit + } + } + // Update standalone sampler + if let Some(sampler) = self.sampler.as_mut() { + if Control::Quit == SamplerAudio(sampler).process(client, scope) { + return Control::Quit + } + //for port in midi_in.iter() { + //for message in port.iter() { + //match message { + //Ok(M + //} + //} + //} + } + // TODO move these to editor and sampler?: + for port in midi_in.iter() { + for event in port.iter() { + match event { + (time, Ok(LiveEvent::Midi {message, ..})) => match message { + MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => { + editor.set_note_point(key.as_int() as usize); + }, + MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = ( + self.editor.as_ref(), + self.sampler.as_ref(), + ) => { + // TODO: give sampler its own cursor + if let Some(sample) = &sampler.mapped[editor.note_point()] { + sample.write().unwrap().handle_cc(*controller, *value) + } + } + _ =>{} + }, + _ =>{} + } + } + } + // Update track sequencers + let tracks = &mut self.tracks; + let note_buf = &mut self.note_buf; + let midi_buf = &mut self.midi_buf; + if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { + return Control::Quit + } + + // TODO: update timeline position in editor. + // must be in sync with clip's playback. since + // a clip can be on multiple tracks and launched + // at different times, add a playhead with the + // playing track's color. + //self.now.set(0.); + //if let ArrangerSelection::Clip(t, s) = self.selected { + //let clip = self.scenes.get(s).map(|scene|scene.clips.get(t)); + //if let Some(Some(Some(clip))) = clip { + //if let Some(track) = self.tracks().get(t) { + //if let Some((ref started_at, Some(ref playing))) = track.player.play_clip { + //let clip = clip.read().unwrap(); + //if *playing.read().unwrap() == *clip { + //let pulse = self.current().pulse.get(); + //let start = started_at.pulse.get(); + //let now = (pulse - start) % clip.length as f64; + //self.now.set(now); + //} + //} + //} + //} + //} + + // End profiling cycle + self.perf.update(t0, scope); + Control::Continue +}); +has_size!(|self: App|&self.size); +has_clock!(|self: App|&self.clock); +has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); +has_editor!(|self: App|self.editor.as_ref().expect("no editor")); +edn_provide!(u16: |self: App|{ + ":sample-h" => if self.compact() { 0 } else { 5 }, + ":samples-w" => if self.compact() { 4 } else { 11 }, + ":samples-y" => if self.compact() { 1 } else { 0 }, + ":pool-w" => if self.compact() { 5 } else { + let w = self.size.w(); + if w > 60 { 20 } else if w > 40 { 15 } else { 10 } + } +}); +edn_provide!(Color: |self: App| { _ => return None }); +edn_provide!(usize: |self: App| { _ => return None }); +edn_provide!(isize: |self: App| { _ => return None }); +edn_provide!(bool: |self: App| { _ => return None }); +edn_provide!(ArrangerSelection: |self: App| { _ => return None }); +edn_provide!(Arc>: |self: App| { _ => return None }); +handle!(TuiIn: |self: App, input| Ok(None)); +#[derive(Clone, Debug)] pub enum AppCommand { + Clear, + Clip(ClipCommand), + Clock(ClockCommand), + Color(ItemPalette), + Compact(Option), + Editor(MidiEditCommand), + Enqueue(Option>>), + History(isize), + Pool(PoolCommand), + Sampler(SamplerCommand), + Scene(SceneCommand), + Select(ArrangerSelection), + StopAll, + Track(TrackCommand), + Zoom(Option), +} +edn_command!(AppCommand: |state: App| { + ("clear" [] Self::Clear) + ("stop-all" [] Self::StopAll) + ("compact" [c: bool ] Self::Compact(c)) + ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) + ("history" [d: isize] Self::History(d.unwrap_or(0))) + ("zoom" [z: usize] Self::Zoom(z)) + ("select" [s: ArrangerSelection] Self::Select(s.expect("no selection"))) + ("enqueue" [c: Arc>] Self::Enqueue(c)) + ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b))) + ("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b))) + ("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b))) + ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b))) + ("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b))) + ("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b))) + ("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b))) +}); +command!(|self: AppCommand, state: App|match self { + Self::Clear => { todo!() }, + Self::Zoom(_) => { todo!(); }, + Self::History(delta) => { todo!("undo/redo") }, + Self::Select(s) => { state.selected = s; None }, + Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, + Self::Scene(cmd) => match cmd { + SceneCommand::Add => { state.scene_add(None, None)?; None } + SceneCommand::Del(index) => { state.scene_del(index); None }, + SceneCommand::SetColor(index, color) => { + let old = state.scenes[index].color; + state.scenes[index].color = color; + Some(SceneCommand::SetColor(index, old)) + }, + SceneCommand::Enqueue(scene) => { + for track in 0..state.tracks.len() { + state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); + } + None + }, + _ => None + }.map(Self::Scene), + Self::Track(cmd) => match cmd { + TrackCommand::Add => { state.track_add(None, None)?; None }, + TrackCommand::Del(index) => { state.track_del(index); None }, + TrackCommand::Stop(track) => { state.tracks[track].player.enqueue_next(None); None }, + TrackCommand::SetColor(index, color) => { + let old = state.tracks[index].color; + state.tracks[index].color = color; + Some(TrackCommand::SetColor(index, old)) + }, + _ => None + }.map(Self::Track), + Self::Clip(cmd) => match cmd { + ClipCommand::Get(track, scene) => { todo!() }, + ClipCommand::Put(track, scene, clip) => { + let old = state.scenes[scene].clips[track].clone(); + state.scenes[scene].clips[track] = clip; + Some(ClipCommand::Put(track, scene, old)) + }, + ClipCommand::Enqueue(track, scene) => { + state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref()); + None + }, + _ => None + }.map(Self::Clip), + Self::Editor(cmd) => + state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), + Self::Sampler(cmd) => + state.sampler.as_mut().map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(), + Self::Enqueue(clip) => + state.player.as_mut().map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(), + Self::StopAll => { + for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); } + None + }, + Self::Color(palette) => { + let old = state.color; + state.color = palette; + Some(Self::Color(old)) + }, + Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() { + let undo = cmd.clone().delegate(pool, Self::Pool)?; + if let Some(editor) = state.editor.as_mut() { + match cmd { + // autoselect: automatically load selected clip in editor + // autocolor: update color in all places simultaneously + PoolCommand::Select(_) | PoolCommand::Clip(PoolCmd::SetColor(_, _)) => + editor.set_clip(pool.clip().as_ref()), + _ => {} + } + }; + undo + } else { + None + }, + Self::Compact(compact) => match compact { + Some(compact) => { + if state.compact != compact { + state.compact = compact; + Some(Self::Compact(Some(!compact))) + } else { + None + } + }, + None => { + state.compact = !state.compact; + Some(Self::Compact(Some(!state.compact))) + } + } +}); +render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref()))); +edn_provide!('a: Box + 'a>: |self: App|{ + ":editor" => (&self.editor).boxed(), + ":pool" => self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))).boxed(), + ":sample" => self.sample().boxed(), + ":sampler" => self.sampler().boxed(), + ":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(), + ":toolbar" => Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))).boxed(), + ":tracks" => self.row(self.w(), 3, track_header(&self), track_cells(&self)).boxed(), + ":inputs" => self.row(self.w(), 3, input_header(&self), input_cells(&self)).boxed(), + ":outputs" => self.row(self.w(), 3, output_header(&self), output_cells(&self)).boxed(), + ":scenes" => self.scene_row(self.w(), self.size.h().saturating_sub(9) as u16).boxed(), +}); +impl App { + fn compact (&self) -> bool { false } + fn editor (&self) -> impl Content + '_ { &self.editor } + fn w (&self) -> u16 { self.tracks_with_sizes().last().map(|x|x.3 as u16).unwrap_or(0) } + fn pool (&self) -> impl Content + use<'_> { self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) } + fn sample <'a> (&'a self) -> impl Content + 'a { + let compact = self.is_editing(); + if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) { + let note_pt = editor.note_point(); + let sample_h = if compact { 0 } else { 5 }; + return Some(Max::y(sample_h, Fill::x(sampler.viewer(note_pt)))) + } + None + } + fn sampler (&self) -> impl Content + use<'_> { + let compact = self.is_editing(); + if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) { + let note_pt = editor.note_point(); + let w = if compact { 4 } else { 40 }; + let y = if compact { 1 } else { 0 }; + return Some(Fixed::x(w, Push::y(y, Fill::y(sampler.list(compact, editor))))) + } + None + } + fn row <'a> ( + &'a self, w: u16, h: u16, a: impl Content + 'a, b: impl Content + 'a + ) -> impl Content + 'a { + Fixed::y(h, Bsp::e( + Fixed::xy(self.sidebar_w() as u16, h, a), + Fill::x(Align::c(Fixed::xy(w, h, b))) + )) + } + pub fn tracks_with_sizes (&self) + -> impl Iterator + { + tracks_with_sizes(self.tracks.iter(), match self.selected { + ArrangerSelection::Track(t) if self.is_editing() => Some(t), + ArrangerSelection::Clip(t, _) if self.is_editing() => Some(t), + _ => None + }, self.editor_w()) + } + pub fn scenes_with_sizes (&self, h: usize) + -> impl Iterator + { + scenes_with_sizes(self.scenes.iter(), &self.selected, self.is_editing(), 2, 15) + } + fn is_editing (&self) -> bool { + self.editing.load(Relaxed) + } + fn editor_w (&self) -> usize { + let editor = self.editor.as_ref().expect("missing editor"); + (5 + (editor.time_len().get() / editor.time_zoom().get())) + .min(self.size.w().saturating_sub(20)) + .max(16) + } + fn sidebar_w (&self) -> u16 { + let w = self.size.w(); + let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + let w = if self.is_editing() { 8 } else { w }; + w + } +} +pub fn scenes_with_sizes <'a>( + scenes: impl Iterator + 'a, + selected: &'a ArrangerSelection, + editing: bool, + scene_height: usize, + scene_larger: usize, +) -> impl Iterator + 'a { + let mut y = 0; + let (selected_track, selected_scene) = match selected { + ArrangerSelection::Clip(t, s) => (Some(t), Some(s)), + _ => (None, None) + }; + scenes.enumerate().map(move|(s, scene)|{ + let active = editing && selected_track.is_some() && selected_scene == Some(&s); + let height = if active { scene_larger } else { scene_height }; + let data = (s, scene, y, y + height); + y += height; + data + }) +} +pub fn tracks_with_sizes <'a> ( + tracks: impl Iterator, + active: Option, + bigger: usize +) -> impl Iterator { + let mut x = 0; + tracks.enumerate().map(move |(index, track)|{ + let width = if Some(index) == active { bigger } else { track.width.max(8) }; + let data = (index, track, x, x + width); + x += width; + data + }) +} +pub fn track_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + (||Tui::bg(TuiTheme::g(32), Tui::bold(true, Bsp::s( + row!( + Tui::fg(TuiTheme::g(128), "add "), + Tui::fg(TuiTheme::orange(), "t"), + Tui::fg(TuiTheme::g(128), "rack"), + ), + row!( + Tui::fg(TuiTheme::orange(), "a"), + Tui::fg(TuiTheme::g(128), "dd scene"), + ), + ))).boxed()).into() +} +pub fn track_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + let iter = ||state.tracks_with_sizes(); + (move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { + let name = Push::x(1, &track.name); + let color = track.color; + let fg = color.lightest.rgb; + let bg = color.base.rgb; + let active = state.selected.track() == Some(i); + let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) }; + let border = Style::default().fg(bfg).bg(bg); + Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16, + Outer(border).enclose(Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::x(name))))) + )) + })).boxed()).into() +} +fn help_tag <'a>(before: &'a str, key: &'a str, after: &'a str) -> impl Content + 'a { + let lo = TuiTheme::g(128); + let hi = TuiTheme::orange(); + Tui::bold(true, row!(Tui::fg(lo, before), Tui::fg(hi, key), Tui::fg(lo, after))) +} +fn input_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + let fg = TuiTheme::g(224); + let bg = TuiTheme::g(64); + (move||Bsp::s(help_tag("midi ", "I", "ns"), state.midi_ins.get(0).map(|inp|Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))), + inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, + Tui::fg_bg(fg, bg, connect.info()))))), + ))).boxed()).into() +} +fn input_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + (move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| { + let w = (x2 - x1) as u16; + let color: ItemPalette = track.color.dark.into(); + map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n( + rec_mon(color.base.rgb, false, false), + phat_hi(color.base.rgb, color.dark.rgb) + )))) + })).boxed()).into() +} +fn output_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + let fg = TuiTheme::g(224); + let bg = TuiTheme::g(64); + (move||Bsp::s(help_tag("midi ", "O", "uts"), state.midi_outs.get(0).map(|out|Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))), + out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, + Tui::fg_bg(fg, bg, connect.info()))))), + ))).boxed()).into() +} +fn output_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + (move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| { + let w = (x2 - x1) as u16; + let color: ItemPalette = track.color.dark.into(); + map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n( + mute_solo(color.base.rgb, false, false), + phat_hi(color.dark.rgb, color.darker.rgb) + )))) + })).boxed()).into() +} +fn scene_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + (||{ + let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); + let selected = state.selected.scene(); + Fill::y(Align::c(Map::new(||state.scenes_with_sizes(2), move|(_, scene, y1, y2), i| { + let h = (y2 - y1) as u16; + let name = format!("🭬{}", &scene.name); + let color = scene.color; + let active = selected == Some(i); + let mid = if active { color.light } else { color.base }; + let top = Some(last_color.read().unwrap().base.rgb); + let cell = phat_sel_3( + active, + Tui::bold(true, name.clone()), + Tui::bold(true, name), + top, + mid.rgb, + Color::Rgb(0, 0, 0) + ); + *last_color.write().unwrap() = color; + map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell)) + }))).boxed() + }).into() +} +fn scene_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> { + let editing = state.is_editing(); + let tracks = move||state.tracks_with_sizes(); + let scenes = ||state.scenes_with_sizes(2); + let selected_track = state.selected.track(); + let selected_scene = state.selected.scene(); + (move||Fill::y(Align::c(Map::new(tracks, move|(_, track, x1, x2), t| { + let w = (x2 - x1) as u16; + let color: ItemPalette = track.color.dark.into(); + let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); + let cells = Map::new(scenes, move|(_, scene, y1, y2), s| { + let h = (y2 - y1) as u16; + let color = scene.color; + let (name, fg, bg) = if let Some(c) = &scene.clips[t] { + let c = c.read().unwrap(); + (c.name.to_string(), c.color.lightest.rgb, c.color.base.rgb) + } else { + ("⏹ ".to_string(), TuiTheme::g(64), TuiTheme::g(32)) + }; + let last = last_color.read().unwrap().clone(); + let active = editing && selected_scene == Some(s) && selected_track == Some(t); + let editor = Thunk::new(||&state.editor); + let cell = Thunk::new(move||phat_sel_3( + selected_track == Some(t) && selected_scene == Some(s), + Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), + Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), + if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) { + None + } else { + Some(bg.into()) + }, + bg.into(), + bg.into(), + )); + let cell = Either(active, editor, cell); + *last_color.write().unwrap() = bg.into(); + map_south( + y1 as u16, + h + 1, + Fill::x(Fixed::y(h + 1, cell)) + ) + }); + let column = Fixed::x(w, Tui::bg(Color::Reset, Align::y(cells)).boxed()); + Fixed::x(w, map_east(x1 as u16, w, column)) + }))).boxed()).into() +} +fn cell_clip <'a> ( + scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 +) -> impl Content + use<'a> { + scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{ + let clip = clip.read().unwrap(); + let mut bg = TuiTheme::border_bg(); + let name = clip.name.to_string(); + let max_w = name.len().min((w as usize).saturating_sub(2)); + let color = clip.color; + bg = color.dark.rgb; + if let Some((_, Some(ref playing))) = track.player.play_clip() { + if *playing.read().unwrap() == *clip { + bg = color.light.rgb + } + }; + Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w])))); + })) +} +fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content { + row!( + Tui::fg_bg(if rec { Color::Red } else { bg }, bg, "▐"), + Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"), + Tui::fg_bg(if rec { Color::White } else { bg }, bg, "▐"), + Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"), + Tui::fg_bg(if mon { Color::White } else { bg }, bg, "▌"), + ) +} +fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content { + row!( + Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"), + Tui::fg_bg(if mute { Color::White } else { bg }, bg, "▐"), + Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"), + ) +} +fn cell > (color: ItemPalette, field: T) -> impl Content { + Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) +} #[cfg(test)] fn test_tek () { // TODO } - -//#[cfg(test)] mod test_focus { - //use super::focus::*; - //#[test] fn test_focus () { - - //struct FocusTest { - //focused: char, - //cursor: (usize, usize) - //} - - //impl HasFocus for FocusTest { - //type Item = char; - //fn focused (&self) -> Self::Item { - //self.focused - //} - //fn set_focused (&mut self, to: Self::Item) { - //self.focused = to - //} - //} - - //impl FocusGrid for FocusTest { - //fn focus_cursor (&self) -> (usize, usize) { - //self.cursor - //} - //fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { - //&mut self.cursor - //} - //fn focus_layout (&self) -> &[&[Self::Item]] { - //&[ - //&['a', 'a', 'a', 'b', 'b', 'd'], - //&['a', 'a', 'a', 'b', 'b', 'd'], - //&['a', 'a', 'a', 'c', 'c', 'd'], - //&['a', 'a', 'a', 'c', 'c', 'd'], - //&['e', 'e', 'e', 'e', 'e', 'e'], - //] - //} - //} - - //let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; - - //tester.focus_right(); - //assert_eq!(tester.cursor.0, 3); - //assert_eq!(tester.focused, 'b'); - - //tester.focus_down(); - //assert_eq!(tester.cursor.1, 2); - //assert_eq!(tester.focused, 'c'); - - //} -//} -//use crate::*; - -//struct TestEngine([u16;4], Vec>); - -//impl Engine for TestEngine { - //type Unit = u16; - //type Size = [Self::Unit;2]; - //type Area = [Self::Unit;4]; - //type Input = Self; - //type Handled = bool; - //fn exited (&self) -> bool { - //true - //} -//} - -//#[derive(Copy, Clone)] -//struct TestArea(u16, u16); - -//impl Render for TestArea { - //fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - //Ok(Some([to[0], to[1], self.0, self.1])) - //} - //fn render (&self, to: &mut TestEngine) -> Perhaps<[u16;4]> { - //if let Some(layout) = self.layout(to.area())? { - //for y in layout.y()..layout.y()+layout.h()-1 { - //for x in layout.x()..layout.x()+layout.w()-1 { - //to.1[y as usize][x as usize] = '*'; - //} - //} - //Ok(Some(layout)) - //} else { - //Ok(None) - //} - //} -//} - -//#[test] -//fn test_plus_minus () -> Usually<()> { - //let area = [0, 0, 10, 10]; - //let engine = TestEngine(area, vec![vec![' ';10];10]); - //let test = TestArea(4, 4); - //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); - //assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4])); - //Ok(()) -//} - -//#[test] -//fn test_outset_align () -> Usually<()> { - //let area = [0, 0, 10, 10]; - //let engine = TestEngine(area, vec![vec![' ';10];10]); - //let test = TestArea(4, 4); - //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); - //assert_eq!(Margin::X(1, test).layout(area)?, Some([0, 0, 6, 4])); - //assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4])); - //assert_eq!(Align::X(Margin::X(1, test)).layout(area)?, Some([2, 0, 6, 4])); - //assert_eq!(Margin::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4])); - //Ok(()) -//} - -////#[test] -////fn test_misc () -> Usually<()> { - ////let area: [u16;4] = [0, 0, 10, 10]; - ////let test = TestArea(4, 4); - ////assert_eq!(test.layout(area)?, - ////Some([0, 0, 4, 4])); - ////assert_eq!(Align::Center(test).layout(area)?, - ////Some([3, 3, 4, 4])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&test)?; - ////add(&test) - ////})).layout(area)?, - ////Some([3, 1, 4, 8])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&test) - ////})).layout(area)?, - ////Some([2, 0, 6, 10])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&Padding::XY(2, 2, test)) - ////})).layout(area)?, - ////Some([2, 1, 6, 8])); - ////assert_eq!(Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&Padding::XY(2, 2, test)) - ////}).layout(area)?, - ////Some([0, 0, 6, 8])); - ////assert_eq!(Stack::right(|add|{ - ////add(&Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&Padding::XY(2, 2, test)) - ////}))?; - ////add(&Align::Center(TestArea(2 ,2))) - ////}).layout(area)?, - ////Some([0, 0, 8, 8])); - ////Ok(()) -////} - -////#[test] -////fn test_offset () -> Usually<()> { - ////let area: [u16;4] = [50, 50, 100, 100]; - ////let test = TestArea(3, 3); - ////assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3])); - ////assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3])); - ////assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3])); - ////Ok(()) -////} - -////#[test] -////fn test_outset () -> Usually<()> { - ////let area: [u16;4] = [50, 50, 100, 100]; - ////let test = TestArea(3, 3); - ////assert_eq!(Margin::X(1, test).layout(area)?, Some([49, 50, 5, 3])); - ////assert_eq!(Margin::Y(1, test).layout(area)?, Some([50, 49, 3, 5])); - ////assert_eq!(Margin::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5])); - ////Ok(()) -////} - -////#[test] -////fn test_padding () -> Usually<()> { - ////let area: [u16;4] = [50, 50, 100, 100]; - ////let test = TestArea(3, 3); - ////assert_eq!(Padding::X(1, test).layout(area)?, Some([51, 50, 1, 3])); - ////assert_eq!(Padding::Y(1, test).layout(area)?, Some([50, 51, 3, 1])); - ////assert_eq!(Padding::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1])); - ////Ok(()) -////} - -////#[test] -////fn test_stuff () -> Usually<()> { - ////let area: [u16;4] = [0, 0, 100, 100]; - ////assert_eq!("1".layout(area)?, - ////Some([0, 0, 1, 1])); - ////assert_eq!("333".layout(area)?, - ////Some([0, 0, 3, 1])); - ////assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?, - ////Some([0, 0, 3, 1])); - ////assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?, - ////Some([0, 0, 3, 2])); - ////assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?, - ////Some([0, 0, 4, 1])); - ////assert_eq!(Stack::down(|add|{ - ////add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?; - ////add(&"55555") - ////}).layout(area)?, - ////Some([0, 0, 5, 2])); - ////let area: [u16;4] = [1, 1, 100, 100]; - ////assert_eq!(Margin::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, - ////Some([0, 1, 6, 1])); - ////assert_eq!(Margin::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, - ////Some([1, 0, 4, 3])); - ////assert_eq!(Margin::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, - ////Some([0, 0, 6, 3])); - ////assert_eq!(Stack::down(|add|{ - ////add(&Margin::XY(1, 1, "1"))?; - ////add(&Margin::XY(1, 1, "333")) - ////}).layout(area)?, - ////Some([1, 1, 5, 6])); - ////let area: [u16;4] = [1, 1, 95, 100]; - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Margin::XY(1, 1, "1"))?; - ////add(&Margin::XY(1, 1, "333")) - ////})).layout(area)?, - ////Some([46, 48, 5, 6])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Layers::new(|add|{ - //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,128,0))))?; - ////add(&Margin::XY(1, 1, "1"))?; - ////add(&Margin::XY(1, 1, "333"))?; - //////add(&Background(Color::Rgb(0,128,0)))?; - ////Ok(()) - ////}))?; - ////add(&Layers::new(|add|{ - //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,0,128))))?; - ////add(&Margin::XY(1, 1, "555"))?; - ////add(&Margin::XY(1, 1, "777777"))?; - //////add(&Background(Color::Rgb(0,0,128)))?; - ////Ok(()) - ////})) - ////})).layout(area)?, - ////Some([46, 48, 5, 6])); - ////Ok(()) -////} diff --git a/tek/src/model.rs b/tek/src/model.rs deleted file mode 100644 index b30e41c7..00000000 --- a/tek/src/model.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::*; -#[derive(Default)] pub struct App { - pub jack: Arc>, - pub edn: String, - pub clock: Clock, - pub color: ItemPalette, - pub editing: AtomicBool, - pub pool: Option, - pub editor: Option, - pub player: Option, - pub sampler: Option, - pub midi_buf: Vec>>, - pub midi_ins: Vec>, - pub midi_outs: Vec>, - pub audio_ins: Vec>, - pub audio_outs: Vec>, - pub note_buf: Vec, - pub tracks: Vec, - pub scenes: Vec, - pub selected: ArrangerSelection, - pub splits: Vec, - pub size: Measure, - pub perf: PerfModel, - pub compact: bool, -} -impl App { - pub fn sequencer ( - jack: &Arc>, - pool: PoolModel, - editor: MidiEditor, - player: Option, - midi_froms: &[PortConnection], - midi_tos: &[PortConnection], - ) -> Self { - Self { - edn: include_str!("../edn/sequencer-view.edn").to_string(), - jack: jack.clone(), - pool: Some(pool), - editor: Some(editor), - player: player, - editing: false.into(), - midi_buf: vec![vec![];65536], - color: ItemPalette::random(), - ..Default::default() - } - } - pub fn groovebox ( - jack: &Arc>, - pool: PoolModel, - editor: MidiEditor, - player: Option, - midi_froms: &[PortConnection], - midi_tos: &[PortConnection], - sampler: Sampler, - audio_froms: &[&[PortConnection]], - audio_tos: &[&[PortConnection]], - ) -> Self { - Self { - edn: include_str!("../edn/groovebox-view.edn").to_string(), - sampler: Some(sampler), - ..Self::sequencer( - jack, pool, editor, - player, midi_froms, midi_tos - ) - } - } - pub fn arranger ( - jack: &Arc>, - pool: PoolModel, - editor: MidiEditor, - midi_froms: &[PortConnection], - midi_tos: &[PortConnection], - sampler: Sampler, - audio_froms: &[&[PortConnection]], - audio_tos: &[&[PortConnection]], - scenes: usize, - tracks: usize, - track_width: usize, - ) -> Self { - let mut arranger = Self { - edn: include_str!("../edn/arranger-view.edn").to_string(), - ..Self::groovebox( - jack, pool, editor, - None, midi_froms, midi_tos, - sampler, audio_froms, audio_tos - ) - }; - arranger.scenes_add(scenes); - arranger.tracks_add(tracks, track_width, &[], &[]); - arranger - } -} -has_size!(|self: App|&self.size); -has_clock!(|self: App|&self.clock); -has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); -has_editor!(|self: App|self.editor.as_ref().expect("no editor")); -edn_provide!(u16: |self: App|{ - ":sample-h" => if self.compact() { 0 } else { 5 }, - ":samples-w" => if self.compact() { 4 } else { 11 }, - ":samples-y" => if self.compact() { 1 } else { 0 }, - ":pool-w" => if self.compact() { 5 } else { - let w = self.size.w(); - if w > 60 { 20 } else if w > 40 { 15 } else { 10 } - } -}); -edn_provide!(Color: |self: App| { _ => return None }); -edn_provide!(usize: |self: App| { _ => return None }); -edn_provide!(isize: |self: App| { _ => return None }); -edn_provide!(bool: |self: App| { _ => return None }); -edn_provide!(ArrangerSelection: |self: App| { _ => return None }); -edn_provide!(Arc>: |self: App| { _ => return None }); - -//#[derive(Default)] pub struct Sequencer { - //pub jack: Arc>, - //pub compact: bool, - //pub editor: MidiEditor, - //pub midi_buf: Vec>>, - //pub note_buf: Vec, - //pub perf: PerfModel, - //pub player: MidiPlayer, - //pub pool: PoolModel, - //pub selectors: bool, - //pub size: Measure, - //pub status: bool, - //pub transport: bool, -//} -//has_size!(|self:Sequencer|&self.size); -//has_clock!(|self:Sequencer|&self.player.clock); -//has_clips!(|self:Sequencer|self.pool.clips); -//has_editor!(|self:Sequencer|self.editor); -//has_player!(|self:Sequencer|self.player); - -//#[derive(Default)] pub struct Groovebox { - //pub jack: Arc>, - //pub compact: bool, - //pub editor: MidiEditor, - //pub midi_buf: Vec>>, - //pub note_buf: Vec, - //pub perf: PerfModel, - //pub player: MidiPlayer, - //pub pool: PoolModel, - //pub sampler: Sampler, - //pub size: Measure, - //pub status: bool, -//} -//has_clock!(|self: Groovebox|self.player.clock()); - -//#[derive(Default)] pub struct Arranger { - //pub clock: Clock, - //pub color: ItemPalette, - //pub compact: bool, - //pub editing: AtomicBool, - //pub editor: MidiEditor, - //pub jack: Arc>, - //pub midi_buf: Vec>>, - //pub midi_ins: Vec>, - //pub midi_outs: Vec>, - //pub note_buf: Vec, - //pub perf: PerfModel, - //pub pool: PoolModel, - //pub scenes: Vec, - //pub selected: ArrangerSelection, - //pub size: Measure, - //pub splits: [u16;2], - //pub tracks: Vec, -//} -//has_clock!(|self: Arranger|&self.clock); -//has_clips!(|self: Arranger|self.pool.clips); -//has_editor!(|self: Arranger|self.editor);