diff --git a/.old/from_arranger.rs b/.old/from_arranger.rs new file mode 100644 index 00000000..07502a6b --- /dev/null +++ b/.old/from_arranger.rs @@ -0,0 +1,188 @@ + + +//pub struct ArrangerVCursor { + //cols: Vec<(usize, usize)>, + //rows: Vec<(usize, usize)>, + //color: ItemPalette, + //reticle: Reticle, + //selected: ArrangerSelection, + //scenes_w: u16, +//} + +//pub(crate) const HEADER_H: u16 = 0; // 5 +//pub(crate) const SCENES_W_OFFSET: u16 = 0; +//from!(|args:(&Arranger, usize)|ArrangerVCursor = Self { + //cols: Arranger::track_widths(&args.0.tracks), + //rows: Arranger::scene_heights(&args.0.scenes, args.1), + //selected: args.0.selected(), + //scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.0.scenes) as u16, + //color: args.0.color, + //reticle: Reticle(Style { + //fg: Some(args.0.color.lighter.rgb), + //bg: None, + //underline_color: None, + //add_modifier: Modifier::empty(), + //sub_modifier: Modifier::DIM + //}), +//}); +//impl Content for ArrangerVCursor { + //fn render (&self, to: &mut TuiOut) { + //let area = to.area(); + //let focused = true; + //let selected = self.selected; + //let get_track_area = |t: usize| [ + //self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(), + //self.cols[t].0 as u16, area.h(), + //]; + //let get_scene_area = |s: usize| [ + //area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16, + //area.w(), (self.rows[s].0 / PPQ) as u16 + //]; + //let get_clip_area = |t: usize, s: usize| [ + //(self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1), + //HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16, + //self.cols[t].0 as u16 + 2, + //(self.rows[s].0 / PPQ) as u16 + //]; + //let mut track_area: Option<[u16;4]> = None; + //let mut scene_area: Option<[u16;4]> = None; + //let mut clip_area: Option<[u16;4]> = None; + //let area = match selected { + //ArrangerSelection::Mix => area, + //ArrangerSelection::Track(t) => { + //track_area = Some(get_track_area(t)); + //area + //}, + //ArrangerSelection::Scene(s) => { + //scene_area = Some(get_scene_area(s)); + //area + //}, + //ArrangerSelection::Clip(t, s) => { + //track_area = Some(get_track_area(t)); + //scene_area = Some(get_scene_area(s)); + //clip_area = Some(get_clip_area(t, s)); + //area + //}, + //}; + //let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0); + //if let Some([x, y, width, height]) = track_area { + //to.fill_fg([x, y, 1, height], bg); + //to.fill_fg([x + width, y, 1, height], bg); + //} + //if let Some([_, y, _, height]) = scene_area { + //to.fill_ul([area.x(), y - 1, area.w(), 1], bg); + //to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); + //} + //if focused { + //to.place(if let Some(clip_area) = clip_area { + //clip_area + //} else if let Some(track_area) = track_area { + //track_area.clip_h(HEADER_H) + //} else if let Some(scene_area) = scene_area { + //scene_area.clip_w(self.scenes_w) + //} else { + //area.clip_w(self.scenes_w).clip_h(HEADER_H) + //}, &self.reticle) + //}; + //} +//} +//impl Arranger { + //fn render_mode (state: &Self) -> impl Content + use<'_> { + //match state.mode { + //ArrangerMode::H => todo!("horizontal arranger"), + //ArrangerMode::V(factor) => Self::render_mode_v(state, factor), + //} + //} +//} +//render!(TuiOut: (self: Arranger) => { + //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; + //let color = self.color; + //let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)), + //Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)), + //Bsp::n(TransportView::new(true, &self.clock), + //Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)), + //Bsp::n(Fill::x(Fixed::y(20, + //Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")), + //Bsp::a( + //Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))), + //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); + //self.size.of(layout) +//}); + //Align::n(Fill::xy(lay!( + //Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))), + //Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))), + //Align::n(Fill::xy(ArrangerVColSep::from(self))), + //Align::n(Fill::xy(ArrangerVClips::new(self, 1))), + //Align::n(Fill::xy(ArrangerVCursor::from((self, 1)))))))))))))))); + //Align::n(Fill::xy(":"))))))))))))); + //"todo:")))))))); + //Bsp::s( + //Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))), + //Bsp::s( + //Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))), + //Fill::x(Fixed::y(1, ArrangerVOuts::from(self))))))))))))); + //Bsp::s( + //Bsp::s( + //Bsp::s( + //Fill::xy(ArrangerVClips::new(self, 1)), + //Fill::x(ArrangerVOuts::from(self))))) + + //let cell = phat_sel_3( + //selected_track == Some(i) && selected_scene == Some(j), + //Tui::fg(TuiTheme::g(64), Push::x(1, name)), + //Tui::fg(TuiTheme::g(64), Push::x(1, name)), + //if selected_track == Some(i) && selected_scene.map(|s|s+1) == Some(j) { + //None + //} else { + //Some(TuiTheme::g(32).into()) + //}, + //TuiTheme::g(32).into(), + //TuiTheme::g(32).into(), + //); + // TODO: port per track: + //for connection in midi_from.iter() { + //let mut split = connection.as_ref().split("="); + //let number = split.next().unwrap().trim(); + //if let Ok(track) = number.parse::() { + //if track < 1 { + //panic!("Tracks start from 1") + //} + //if track > count { + //panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") + //} + //if let Some(port) = split.next() { + //if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { + ////jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?; + //} else { + //panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + //} + //} else { + //panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") + //} + //} else { + //panic!("Failed to parse track number: {number}") + //} + //} + //for connection in midi_to.iter() { + //let mut split = connection.as_ref().split("="); + //let number = split.next().unwrap().trim(); + //if let Ok(track) = number.parse::() { + //if track < 1 { + //panic!("Tracks start from 1") + //} + //if track > count { + //panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") + //} + //if let Some(port) = split.next() { + //if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { + ////jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?; + //} else { + //panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + //} + //} else { + //panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") + //} + //} else { + //panic!("Failed to parse track number: {number}") + //} + //} diff --git a/cli/tek.rs b/cli/tek.rs index 3de6d8cd..b9d67570 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -112,7 +112,7 @@ pub fn main () -> Usually<()> { //} //}; Ok(match cli.mode { - TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui { + TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(ClockTui { jack: jack.clone(), clock: default_clock(jack), }))?)?, TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok( diff --git a/jack/src/has_jack.rs b/jack/src/has_jack.rs new file mode 100644 index 00000000..068d6b30 --- /dev/null +++ b/jack/src/has_jack.rs @@ -0,0 +1,5 @@ +use crate::*; + +pub trait HasJack { + fn jack (&self) -> &Arc>; +} diff --git a/jack/src/lib.rs b/jack/src/lib.rs index b48668fe..63a51818 100644 --- a/jack/src/lib.rs +++ b/jack/src/lib.rs @@ -11,6 +11,7 @@ pub(crate) use ::jack::{ }; mod from_jack; pub use self::from_jack::*; +mod has_jack; pub use self::has_jack::*; mod jack_audio; pub use self::jack_audio::*; mod jack_connect; pub use self::jack_connect::*; mod jack_event; pub use self::jack_event::*; diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index ba5b1486..9963d600 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -1,81 +1,74 @@ use crate::*; use ClockCommand::{Play, Pause}; use self::ArrangerCommand as Cmd; -impl Arranger { - pub fn activate (&mut self) -> Usually<()> { - if let ArrangerSelection::Scene(s) = self.selected { - for (t, track) in self.tracks.iter_mut().enumerate() { - let clip = self.scenes[s].clips[t].clone(); - if track.player.play_clip.is_some() || clip.is_some() { - track.player.enqueue_next(clip.as_ref()); - } - } - if self.clock().is_stopped() { - self.clock().play_from(Some(0))?; - } - } else if let ArrangerSelection::Clip(t, s) = self.selected { - let clip = self.scenes[s].clips[t].clone(); - self.tracks[t].player.enqueue_next(clip.as_ref()); - }; - Ok(()) - } - pub fn selected_clip (&self) -> Option>> { - self.selected_scene()?.clips.get(self.selected.track()?)?.clone() - } - pub fn toggle_loop (&mut self) { - if let Some(clip) = self.selected_clip() { - clip.write().unwrap().toggle_loop() - } - } - pub fn randomize_color (&mut self) { - match self.selected { - ArrangerSelection::Mix => { self.color = ItemPalette::random() }, - ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() }, - ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() }, - ArrangerSelection::Clip(t, s) => if let Some(clip) = &self.scenes[s].clips[t] { - clip.write().unwrap().color = ItemPalette::random(); - } - } - } + +impl Arrangement for App { + fn tracks (&self) -> &Vec { &self.tracks } + fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } + fn scenes (&self) -> &Vec { &self.scenes } + fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } + fn selected (&self) -> &ArrangerSelection { &self.selected } + fn selected_mut (&mut self) -> &mut ArrangerSelection { &mut self.selected } } -impl Arranger { - pub fn track_next_name (&self) -> Arc { - format!("Trk{:02}", self.tracks.len() + 1).into() +impl Arrangement for Arranger { + fn tracks (&self) -> &Vec { &self.tracks } + fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } + fn scenes (&self) -> &Vec { &self.scenes } + fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } + fn selected (&self) -> &ArrangerSelection { &self.selected } + fn selected_mut (&mut self) -> &mut ArrangerSelection { &mut self.selected } +} +pub trait Arrangement: HasClock + HasJack { + fn tracks (&self) -> &Vec; + fn tracks_mut (&mut self) -> &mut Vec; + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + fn selected (&self) -> &ArrangerSelection; + fn selected_mut (&mut self) -> &mut ArrangerSelection; + + fn track_next_name (&self) -> Arc { + format!("Trk{:02}", self.tracks().len() + 1).into() } - pub fn track_add (&mut self, name: Option<&str>, color: Option) + fn track (&self) -> Option<&ArrangerTrack> { + self.selected().track().and_then(|s|self.tracks().get(s)) + } + fn track_mut (&mut self) -> Option<&mut ArrangerTrack> { + self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) + } + fn track_del (&mut self, index: usize) { + self.tracks_mut().remove(index); + for scene in self.scenes_mut().iter_mut() { + scene.clips.remove(index); + } + } + fn track_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerTrack> { let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let track = ArrangerTrack { width: (name.len() + 2).max(9), color: color.unwrap_or_else(ItemPalette::random), - player: MidiPlayer::from(&self.clock), + player: MidiPlayer::from(self.clock()), name, }; - self.tracks.push(track); - let len = self.tracks.len(); + self.tracks_mut().push(track); + let len = self.tracks().len(); let index = len - 1; - for scene in self.scenes.iter_mut() { + for scene in self.scenes_mut().iter_mut() { while scene.clips.len() < len { scene.clips.push(None); } } - Ok(&mut self.tracks[index]) + Ok(&mut self.tracks_mut()[index]) } - pub fn track_del (&mut self, index: usize) { - self.tracks.remove(index); - for scene in self.scenes.iter_mut() { - scene.clips.remove(index); - } - } - pub fn tracks_add ( + fn tracks_add ( &mut self, count: usize, width: usize, midi_from: &[PortConnection], midi_to: &[PortConnection], ) -> Usually<()> { - let jack = self.jack.clone(); + let jack = self.jack().clone(); let track_color_1 = ItemColor::random(); let track_color_2 = ItemColor::random(); for i in 0..count { @@ -89,33 +82,32 @@ impl Arranger { } Ok(()) } -} -impl Arranger { - pub fn scene_add (&mut self, name: Option<&str>, color: Option) + + fn scene_default_name (&self) -> Arc { + format!("Sc{:3>}", self.scenes().len() + 1).into() + } + fn scene (&self) -> Option<&ArrangerScene> { + self.selected().scene().and_then(|s|self.scenes().get(s)) + } + fn scene_mut (&mut self) -> Option<&mut ArrangerScene> { + self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s)) + } + fn scene_del (&mut self, index: usize) { + todo!("delete scene"); + } + fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerScene> { let scene = ArrangerScene { name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), - clips: vec![None;self.tracks.len()], + clips: vec![None;self.tracks().len()], color: color.unwrap_or_else(ItemPalette::random), }; - self.scenes.push(scene); - let index = self.scenes.len() - 1; - Ok(&mut self.scenes[index]) + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok(&mut self.scenes_mut()[index]) } - pub fn scene_del (&mut self, index: usize) { - todo!("delete scene"); - } - fn scene_default_name (&self) -> Arc { - format!("Sc{:3>}", self.scenes.len() + 1).into() - } - pub fn selected_scene (&self) -> Option<&ArrangerScene> { - self.selected.scene().and_then(|s|self.scenes.get(s)) - } - pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> { - self.selected.scene().and_then(|s|self.scenes.get_mut(s)) - } - pub fn scenes_add (&mut self, n: usize) -> Usually<()> { + fn scenes_add (&mut self, n: usize) -> Usually<()> { let scene_color_1 = ItemColor::random(); let scene_color_2 = ItemColor::random(); for i in 0..n { @@ -125,19 +117,16 @@ impl Arranger { } Ok(()) } + } -impl ArrangerTrack { - fn longest_name (tracks: &[Self]) -> usize { - tracks.iter().map(|s|s.name.len()).fold(0, usize::max) - } - fn width_inc (&mut self) { - self.width += 1; - } - fn width_dec (&mut self) { - if self.width > Arranger::TRACK_MIN_WIDTH { - self.width -= 1; - } - } + +#[derive(Default)] pub struct ArrangerScene { + /// Name of scene + pub(crate) name: Arc, + /// Clips in scene, one per track + pub(crate) clips: Vec>>>, + /// Identifying color of scene + pub(crate) color: ItemPalette, } impl ArrangerScene { pub fn longest_name (scenes: &[Self]) -> usize { @@ -171,190 +160,120 @@ impl ArrangerScene { match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } } } - -//pub struct ArrangerVCursor { - //cols: Vec<(usize, usize)>, - //rows: Vec<(usize, usize)>, - //color: ItemPalette, - //reticle: Reticle, - //selected: ArrangerSelection, - //scenes_w: u16, -//} - -//pub(crate) const HEADER_H: u16 = 0; // 5 -//pub(crate) const SCENES_W_OFFSET: u16 = 0; -//from!(|args:(&Arranger, usize)|ArrangerVCursor = Self { - //cols: Arranger::track_widths(&args.0.tracks), - //rows: Arranger::scene_heights(&args.0.scenes, args.1), - //selected: args.0.selected(), - //scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.0.scenes) as u16, - //color: args.0.color, - //reticle: Reticle(Style { - //fg: Some(args.0.color.lighter.rgb), - //bg: None, - //underline_color: None, - //add_modifier: Modifier::empty(), - //sub_modifier: Modifier::DIM - //}), -//}); -//impl Content for ArrangerVCursor { - //fn render (&self, to: &mut TuiOut) { - //let area = to.area(); - //let focused = true; - //let selected = self.selected; - //let get_track_area = |t: usize| [ - //self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(), - //self.cols[t].0 as u16, area.h(), - //]; - //let get_scene_area = |s: usize| [ - //area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16, - //area.w(), (self.rows[s].0 / PPQ) as u16 - //]; - //let get_clip_area = |t: usize, s: usize| [ - //(self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1), - //HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16, - //self.cols[t].0 as u16 + 2, - //(self.rows[s].0 / PPQ) as u16 - //]; - //let mut track_area: Option<[u16;4]> = None; - //let mut scene_area: Option<[u16;4]> = None; - //let mut clip_area: Option<[u16;4]> = None; - //let area = match selected { - //ArrangerSelection::Mix => area, - //ArrangerSelection::Track(t) => { - //track_area = Some(get_track_area(t)); - //area - //}, - //ArrangerSelection::Scene(s) => { - //scene_area = Some(get_scene_area(s)); - //area - //}, - //ArrangerSelection::Clip(t, s) => { - //track_area = Some(get_track_area(t)); - //scene_area = Some(get_scene_area(s)); - //clip_area = Some(get_clip_area(t, s)); - //area - //}, - //}; - //let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0); - //if let Some([x, y, width, height]) = track_area { - //to.fill_fg([x, y, 1, height], bg); - //to.fill_fg([x + width, y, 1, height], bg); - //} - //if let Some([_, y, _, height]) = scene_area { - //to.fill_ul([area.x(), y - 1, area.w(), 1], bg); - //to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); - //} - //if focused { - //to.place(if let Some(clip_area) = clip_area { - //clip_area - //} else if let Some(track_area) = track_area { - //track_area.clip_h(HEADER_H) - //} else if let Some(scene_area) = scene_area { - //scene_area.clip_w(self.scenes_w) - //} else { - //area.clip_w(self.scenes_w).clip_h(HEADER_H) - //}, &self.reticle) - //}; - //} -//} -//impl Arranger { - //fn render_mode (state: &Self) -> impl Content + use<'_> { - //match state.mode { - //ArrangerMode::H => todo!("horizontal arranger"), - //ArrangerMode::V(factor) => Self::render_mode_v(state, factor), - //} - //} -//} -//render!(TuiOut: (self: Arranger) => { - //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; - //let color = self.color; - //let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)), - //Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)), - //Bsp::n(TransportView::new(true, &self.clock), - //Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)), - //Bsp::n(Fill::x(Fixed::y(20, - //Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")), - //Bsp::a( - //Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))), - //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); - //self.size.of(layout) -//}); - //Align::n(Fill::xy(lay!( - //Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))), - //Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))), - //Align::n(Fill::xy(ArrangerVColSep::from(self))), - //Align::n(Fill::xy(ArrangerVClips::new(self, 1))), - //Align::n(Fill::xy(ArrangerVCursor::from((self, 1)))))))))))))))); - //Align::n(Fill::xy(":"))))))))))))); - //"todo:")))))))); - //Bsp::s( - //Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))), - //Bsp::s( - //Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))), - //Fill::x(Fixed::y(1, ArrangerVOuts::from(self))))))))))))); - //Bsp::s( - //Bsp::s( - //Bsp::s( - //Fill::xy(ArrangerVClips::new(self, 1)), - //Fill::x(ArrangerVOuts::from(self))))) - - //let cell = phat_sel_3( - //selected_track == Some(i) && selected_scene == Some(j), - //Tui::fg(TuiTheme::g(64), Push::x(1, name)), - //Tui::fg(TuiTheme::g(64), Push::x(1, name)), - //if selected_track == Some(i) && selected_scene.map(|s|s+1) == Some(j) { - //None - //} else { - //Some(TuiTheme::g(32).into()) - //}, - //TuiTheme::g(32).into(), - //TuiTheme::g(32).into(), - //); - // TODO: port per track: - //for connection in midi_from.iter() { - //let mut split = connection.as_ref().split("="); - //let number = split.next().unwrap().trim(); - //if let Ok(track) = number.parse::() { - //if track < 1 { - //panic!("Tracks start from 1") - //} - //if track > count { - //panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") - //} - //if let Some(port) = split.next() { - //if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { - ////jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?; - //} else { - //panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - //} - //} else { - //panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") - //} - //} else { - //panic!("Failed to parse track number: {number}") - //} - //} - //for connection in midi_to.iter() { - //let mut split = connection.as_ref().split("="); - //let number = split.next().unwrap().trim(); - //if let Ok(track) = number.parse::() { - //if track < 1 { - //panic!("Tracks start from 1") - //} - //if track > count { - //panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") - //} - //if let Some(port) = split.next() { - //if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { - ////jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?; - //} else { - //panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - //} - //} else { - //panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") - //} - //} else { - //panic!("Failed to parse track number: {number}") - //} - //} +#[derive(Debug)] pub struct ArrangerTrack { + /// Name of track + pub name: Arc, + /// Preferred width of track column + pub width: usize, + /// Identifying color of track + pub color: ItemPalette, + /// MIDI player state + pub player: MidiPlayer, +} +has_clock!(|self:ArrangerTrack|self.player.clock()); +has_player!(|self:ArrangerTrack|self.player); +impl ArrangerTrack { + fn longest_name (tracks: &[Self]) -> usize { + tracks.iter().map(|s|s.name.len()).fold(0, usize::max) + } + fn width_inc (&mut self) { + self.width += 1; + } + fn width_dec (&mut self) { + if self.width > Arranger::TRACK_MIN_WIDTH { + self.width -= 1; + } + } +} +#[derive(PartialEq, Clone, Copy, Debug, Default)] +/// Represents the current user selection in the arranger +pub enum ArrangerSelection { + /// The whole mix is selected + #[default] Mix, + /// A track is selected. + Track(usize), + /// A scene is selected. + Scene(usize), + /// A clip (track × scene) is selected. + Clip(usize, usize), +} +/// Focus identification methods +impl ArrangerSelection { + pub fn track (&self) -> Option { + use ArrangerSelection::*; + match self { + Clip(t, _) => Some(*t), + Track(t) => Some(*t), + _ => None + } + } + pub fn scene (&self) -> Option { + use ArrangerSelection::*; + match self { + Clip(_, s) => Some(*s), + Scene(s) => Some(*s), + _ => None + } + } + pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) } + pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) } + pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) } + pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) } + pub fn description ( + &self, + tracks: &[ArrangerTrack], + scenes: &[ArrangerScene], + ) -> Arc { + format!("Selected: {}", match self { + Self::Mix => "Everything".to_string(), + Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) + .unwrap_or_else(||"T??".into()), + Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) + .unwrap_or_else(||"S??".into()), + Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { + (Some(_), Some(scene)) => match scene.clip(*t) { + Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name), + None => format!("T{t} S{s}: Empty") + }, + _ => format!("T{t} S{s}: Empty"), + } + }).into() + } +} +impl Arranger { + pub fn activate (&mut self) -> Usually<()> { + if let ArrangerSelection::Scene(s) = self.selected { + for (t, track) in self.tracks.iter_mut().enumerate() { + let clip = self.scenes[s].clips[t].clone(); + if track.player.play_clip.is_some() || clip.is_some() { + track.player.enqueue_next(clip.as_ref()); + } + } + if self.clock().is_stopped() { + self.clock().play_from(Some(0))?; + } + } else if let ArrangerSelection::Clip(t, s) = self.selected { + let clip = self.scenes[s].clips[t].clone(); + self.tracks[t].player.enqueue_next(clip.as_ref()); + }; + Ok(()) + } + pub fn clip (&self) -> Option>> { + self.scene()?.clips.get(self.selected.track()?)?.clone() + } + pub fn toggle_loop (&mut self) { + if let Some(clip) = self.clip() { + clip.write().unwrap().toggle_loop() + } + } + pub fn randomize_color (&mut self) { + match self.selected { + ArrangerSelection::Mix => { self.color = ItemPalette::random() }, + ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() }, + ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() }, + ArrangerSelection::Clip(t, s) => if let Some(clip) = &self.scenes[s].clips[t] { + clip.write().unwrap().color = ItemPalette::random(); + } + } + } +} diff --git a/tek/src/audio.rs b/tek/src/audio.rs index 33e3c4e2..926cc3f2 100644 --- a/tek/src/audio.rs +++ b/tek/src/audio.rs @@ -1,4 +1,105 @@ 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 +}); audio!(|self: Sequencer, client, scope|{ // Start profiling cycle @@ -8,7 +109,6 @@ audio!(|self: Sequencer, client, scope|{ 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 diff --git a/tek/src/control.rs b/tek/src/control.rs index 6642e5f8..6373e8ec 100644 --- a/tek/src/control.rs +++ b/tek/src/control.rs @@ -153,12 +153,51 @@ impl TrackCommand { } command!(|self: AppCommand, state: App|match self { Self::Clear => { todo!() }, - Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?, - Self::History(delta) => { todo!("undo/redo") }, - Self::Select(s) => { state.selected = s; None }, - Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?, - Self::Track(cmd) => cmd.delegate(state, Self::Track)?, 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(), @@ -176,20 +215,20 @@ command!(|self: AppCommand, state: App|match self { 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 - }, - // 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::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) => if state.compact != compact { diff --git a/tek/src/lib.rs b/tek/src/lib.rs index d1055b9b..c655817b 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -1,9 +1,10 @@ #![allow(unused)] #![allow(clippy::unit_arg)] #![feature(adt_const_params)] -#![feature(type_alias_impl_trait)] -#![feature(impl_trait_in_assoc_type)] #![feature(associated_type_defaults)] +#![feature(if_let_guard)] +#![feature(impl_trait_in_assoc_type)] +#![feature(type_alias_impl_trait)] /// Standard result type. pub type Usually = std::result::Result>; diff --git a/tek/src/model.rs b/tek/src/model.rs index 0f137c4d..f664101d 100644 --- a/tek/src/model.rs +++ b/tek/src/model.rs @@ -21,6 +21,7 @@ use crate::*; pub splits: Vec, pub size: Measure, pub perf: PerfModel, + pub compact: bool, } impl App { pub fn sequencer ( @@ -88,84 +89,8 @@ impl App { } has_size!(|self: App|&self.size); has_clock!(|self: App|&self.clock); -has_clips!(|self: App|self.pool.clips); -has_editor!(|self: App|self.editor); - -#[derive(Debug)] pub struct ArrangerTrack { - /// Name of track - pub name: Arc, - /// Preferred width of track column - pub width: usize, - /// Identifying color of track - pub color: ItemPalette, - /// MIDI player state - pub player: MidiPlayer, -} -has_clock!(|self:ArrangerTrack|self.player.clock()); -has_player!(|self:ArrangerTrack|self.player); -#[derive(Default)] pub struct ArrangerScene { - /// Name of scene - pub(crate) name: Arc, - /// Clips in scene, one per track - pub(crate) clips: Vec>>>, - /// Identifying color of scene - pub(crate) color: ItemPalette, -} -#[derive(PartialEq, Clone, Copy, Debug, Default)] -/// Represents the current user selection in the arranger -pub enum ArrangerSelection { - /// The whole mix is selected - #[default] Mix, - /// A track is selected. - Track(usize), - /// A scene is selected. - Scene(usize), - /// A clip (track × scene) is selected. - Clip(usize, usize), -} -/// Focus identification methods -impl ArrangerSelection { - pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) } - pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) } - pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) } - pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) } - pub fn description ( - &self, - tracks: &[ArrangerTrack], - scenes: &[ArrangerScene], - ) -> Arc { - format!("Selected: {}", match self { - Self::Mix => "Everything".to_string(), - Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) - .unwrap_or_else(||"T??".into()), - Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) - .unwrap_or_else(||"S??".into()), - Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { - (Some(_), Some(scene)) => match scene.clip(*t) { - Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name), - None => format!("T{t} S{s}: Empty") - }, - _ => format!("T{t} S{s}: Empty"), - } - }).into() - } - pub fn track (&self) -> Option { - use ArrangerSelection::*; - match self { - Clip(t, _) => Some(*t), - Track(t) => Some(*t), - _ => None - } - } - pub fn scene (&self) -> Option { - use ArrangerSelection::*; - match self { - Clip(_, s) => Some(*s), - Scene(s) => Some(*s), - _ => None - } - } -} +has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); +has_editor!(|self: App|self.editor.as_ref().expect("no editor")); #[derive(Default)] pub struct Sequencer { pub jack: Arc>, diff --git a/tek/src/select.rs b/tek/src/select.rs new file mode 100644 index 00000000..c7b7e813 --- /dev/null +++ b/tek/src/select.rs @@ -0,0 +1 @@ +use crate::*; diff --git a/tek/src/view.rs b/tek/src/view.rs index 0d8a4767..3c8cb9f5 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -1,6 +1,5 @@ use crate::*; render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref()))); -audio!(|self: App, _client, _scope|Control::Continue); impl EdnViewData for &App { fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { use EdnItem::*; @@ -38,7 +37,7 @@ impl EdnViewData for &App { impl App { fn compact (&self) -> bool { false } fn toolbar (&self) -> impl Content + use<'_> { - Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock)))) + 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() @@ -356,7 +355,7 @@ impl EdnViewData for &Sequencer { impl Sequencer { const EDN: &'static str = include_str!("../edn/sequencer.edn"); fn toolbar_view (&self) -> impl Content + use<'_> { - Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock)))) + Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.player.clock)))) } fn status_view (&self) -> impl Content + use<'_> { Bsp::e( @@ -434,7 +433,7 @@ impl Groovebox { Fill::x(Fixed::y(2, lay!( Fill::x(Align::w(Meter("L/", self.sampler.input_meter[0]))), Fill::x(Align::e(Meter("R/", self.sampler.input_meter[1]))), - Align::x(TransportView::new(true, &self.player.clock)), + Align::x(ClockView::new(true, &self.player.clock)), ))) } fn status (&self) -> impl Content + use<'_> { @@ -604,7 +603,7 @@ impl Arranger { pub const TRACK_MIN_WIDTH: usize = 9; fn toolbar (&self) -> impl Content + use<'_> { - Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock)))) + Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))) } fn pool (&self) -> impl Content + use<'_> { Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact, &self.pool))) diff --git a/time/src/clock_tui.rs b/time/src/clock_tui.rs index d89df929..4c371db2 100644 --- a/time/src/clock_tui.rs +++ b/time/src/clock_tui.rs @@ -2,31 +2,31 @@ use crate::*; use KeyCode::*; use ClockCommand::{Play, Pause}; /// Transport clock app. -pub struct TransportTui { +pub struct ClockTui { pub jack: Arc>, pub clock: Clock, } -handle!(TuiIn: |self: TransportTui, input|ClockCommand::execute_with_state(self, input.event())); -keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand { +handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event())); +keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand { key(Char(' ')) => if state.clock().is_stopped() { Play(None) } else { Pause(None) }, shift(key(Char(' '))) => if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } }); -has_clock!(|self: TransportTui|&self.clock); -audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); -render!(TuiOut: (self: TransportTui) => TransportView { +has_clock!(|self: ClockTui|&self.clock); +audio!(|self: ClockTui, client, scope|ClockAudio(self).process(client, scope)); +render!(TuiOut: (self: ClockTui) => ClockView { compact: false, clock: &self.clock }); -pub struct TransportView<'a> { pub compact: bool, pub clock: &'a Clock } -impl<'a> TransportView<'a> { +pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock } +impl<'a> ClockView<'a> { pub fn new (compact: bool, clock: &'a Clock) -> Self { Self { compact, clock } } } -render!(TuiOut: (self: TransportView<'a>) => Outer( +render!(TuiOut: (self: ClockView<'a>) => Outer( Style::default().fg(TuiTheme::g(255)) ).enclose(row!( OutputStats::new(self.compact, self.clock),