use crate::*; def_sizes_iter!(TracksSizes => Track); impl> + Send + Sync> HasTracks for T {} pub trait HasTracks: Has> + Send + Sync { fn tracks (&self) -> &Vec { Has::>::get(self) } fn tracks_mut (&mut self) -> &mut Vec { Has::>::get_mut(self) } /// Run audio callbacks for every track and every device fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control { for track in self.tracks_mut().iter_mut() { if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { return Control::Quit } for device in track.devices.iter_mut() { if Control::Quit == DeviceAudio(device).process(client, scope) { return Control::Quit } } } Control::Continue } fn track_longest_name (&self) -> usize { self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) } /// Stop all playing clips fn tracks_stop_all (&mut self) { for track in self.tracks_mut().iter_mut() { track.sequencer.enqueue_next(None); } } /// Stop all playing clips fn tracks_launch (&mut self, clips: Option>>>>) { if let Some(clips) = clips { for (clip, track) in clips.iter().zip(self.tracks_mut()) { track.sequencer.enqueue_next(clip.as_ref()); } } else { for track in self.tracks_mut().iter_mut() { track.sequencer.enqueue_next(None); } } } /// Spacing between tracks. const TRACK_SPACING: usize = 0; } pub trait HasTrackScroll: HasTracks { fn track_scroll (&self) -> usize; } impl HasTrackScroll for Arrangement { fn track_scroll (&self) -> usize { self.track_scroll } } pub trait HasTrack { fn track (&self) -> Option<&Track>; fn track_mut (&mut self) -> Option<&mut Track>; #[cfg(feature = "port")] fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { self.track().map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins)) } #[cfg(feature = "port")] fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content + '_ { self.track().map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs)) } #[cfg(feature = "port")] fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content { self.track().map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins())) } #[cfg(feature = "port")] fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content { self.track().map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) } } impl> HasTrack for T { fn track (&self) -> Option<&Track> { self.get() } fn track_mut (&mut self) -> Option<&mut Track> { self.get_mut() } } #[derive(Debug, Default)] pub struct Track { /// Name of track pub name: Arc, /// Identifying color of track pub color: ItemTheme, /// Preferred width of track column pub width: usize, /// MIDI sequencer state pub sequencer: Sequencer, /// Device chain pub devices: Vec, } has!(Clock: |self: Track|self.sequencer.clock); has!(Sequencer: |self: Track|self.sequencer); impl Track { /// Create a new track with only the default [Sequencer]. pub fn new ( name: &impl AsRef, color: Option, jack: &Jack<'static>, clock: Option<&Clock>, clip: Option<&Arc>>, midi_from: &[Connect], midi_to: &[Connect], ) -> Usually { Ok(Self { name: name.as_ref().into(), color: color.unwrap_or_default(), sequencer: Sequencer::new(format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to)?, ..Default::default() }) } fn audio_ins (&self) -> &[AudioInput] { self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() } fn audio_outs (&self) -> &[AudioOutput] { self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() } fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } fn _todo_usize_stub_ (&self) -> usize { todo!() } fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } } impl HasWidth for Track { const MIN_WIDTH: usize = 9; fn width_inc (&mut self) { self.width += 1; } fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } } #[cfg(feature = "sampler")] impl Track { /// Create a new track connecting the [Sequencer] to a [Sampler]. pub fn new_with_sampler ( name: &impl AsRef, color: Option, jack: &Jack<'static>, clock: Option<&Clock>, clip: Option<&Arc>>, midi_from: &[Connect], midi_to: &[Connect], audio_from: &[&[Connect];2], audio_to: &[&[Connect];2], ) -> Usually { let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; let client_name = jack.with_client(|c|c.name().to_string()); let port_name = track.sequencer.midi_outs[0].port_name(); let connect = [Connect::exact(format!("{client_name}:{}", port_name))]; track.devices.push(Device::Sampler(Sampler::new( jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to )?)); Ok(track) } pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { for device in self.devices.iter() { match device { Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, _ => {} } } None } pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { for device in self.devices.iter_mut() { match device { Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, _ => {} } } None } } def_command!(TrackCommand: |track: Track| { Stop => { track.sequencer.enqueue_next(None); Ok(None) }, SetMute { mute: Option } => todo!(), SetSolo { solo: Option } => todo!(), SetSize { size: usize } => todo!(), SetZoom { zoom: usize } => todo!(), SetName { name: Arc } => swap_value(&mut track.name, name, |name|Self::SetName { name }), SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }), SetRec { rec: Option } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), SetMon { mon: Option } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), }); impl ClipsView for T {} pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasSize + HasTrackScroll + HasSelection + HasEditor + HasClipsSize { fn tracks_width_available (&self) -> u16 { (self.width() as u16).saturating_sub(40) } /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { let _editor_width = self.editor().map(|e|e.width()); let _active_track = self.selection().track(); let mut x = 0; self.tracks().iter().enumerate().map_while(move |(index, track)|{ let width = track.width.max(8); if x + width < self.clips_size().w() { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; Some(data) } else { None } }) } fn view_track_names (&self, theme: ItemTheme) -> impl Content { let track_count = self.tracks().len(); let scene_count = self.scenes().len(); let selected = self.selection(); let button = Bsp::s( button_3("t", "rack ", format!("{}{track_count}", selected.track() .map(|track|format!("{track}/")).unwrap_or_default()), false), button_3("s", "cene ", format!("{}{scene_count}", selected.scene() .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); let button_2 = Bsp::s( button_2("T", "+", false), button_2("S", "+", false)); view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb, Fixed::Y(2, Thunk::new(|to: &mut TuiOut|{ for (index, track, x1, _x2) in self.tracks_with_sizes() { to.place(&Push::X(x1 as u16, Fixed::X(track_width(index, track), Tui::bg(if selected.track() == Some(index) { track.color.light.rgb } else { track.color.base.rgb }, Bsp::s(Fill::X(Align::nw(Bsp::e( format!("·t{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) ))), ""))) ));}})))) } fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Content { view_track_row_section(theme, Bsp::s(Fill::X(Align::w(button_2("o", "utput", false))), Thunk::new(|to: &mut TuiOut|for port in self.midi_outs().iter() { to.place(&Fill::X(Align::w(port.port_name()))); })), button_2("O", "+", false), Tui::bg(theme.darker.rgb, Align::w(Thunk::new(|to: &mut TuiOut|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&Fixed::X(track_width(index, track), Align::nw(Fill::Y(Map::south(1, ||track.sequencer.midi_outs.iter(), |port, index|Tui::fg(Rgb(255, 255, 255), Fixed::Y(1, Tui::bg(track.color.dark.rgb, Fill::X(Align::w( format!("·o{index:02} {}", port.port_name())))))))))));}})))) } fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { let mut h = 0u16; for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } let content = Thunk::new(move|to: &mut TuiOut|for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&Fixed::XY(track_width(index, track), h + 1, Align::nw(Bsp::s( Tui::bg(track.color.base.rgb, Fill::X(Align::w(row!( Either::new(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), Either::new(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), Either::new(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), )))), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, Fill::X(Align::w(format!("·i{index:02} {}", port.port_name()))))))))); }); view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), Tui::bg(theme.darker.rgb, Align::w(content))) } } pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { track.width as u16 } fn view_track_header (theme: ItemTheme, content: impl Content) -> impl Content { Fixed::X(12, Tui::bg(theme.darker.rgb, Fill::X(Align::e(content)))) } fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) -> impl Content + use<'a, T> { let ins = ports.len() as u16; let frame = Outer(true, Style::default().fg(Tui::g(96))); let iter = move||ports.iter(); let names = Map::south(1, iter, move|port, index|Fill::Y(Align::w(format!(" {index} {}", port.port_name())))); let field = FieldV(theme, title, names); Fixed::XY(20, 1 + ins, frame.enclose(Fixed::XY(20, 1 + ins, field))) } impl Track { pub fn per <'a, T: Content + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; map_east(x1 as u16, width, Fixed::X(width, Tui::fg_bg( track.color.lightest.rgb, track.color.base.rgb, callback(index, track))))}) } } pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { Align::x(Tui::bg(Reset, Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; map_east(x1 as u16, width, Fixed::X(width, Tui::fg_bg( track.color.lightest.rgb, track.color.base.rgb, callback(index, track))))}))) } pub(crate) fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Content + 'a { per_track_top(tracks, move|index, track|Fill::Y(Align::y(callback(index, track)))) } pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a ) -> impl Content + 'a { Map::new(iter, move|( _index, name, connections, y, y2 ): (usize, &'a Arc, &'a [Connect], usize, usize), _| map_south(y as u16, (y2-y) as u16, Bsp::s( Fill::Y(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(&" 󰣲 ", name))))), Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1, Fill::Y(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, &connect.info))))))))) } //pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( //fg: Color, bg: Color, iter: &mut impl Iterator, &'a [Connect], usize, usize)> //) -> impl Content + 'a { //Fill::XY(Thunk::new(move|to: &mut TuiOut|for (_, _, connections, y, y2) in &mut *iter { //to.place(&map_south(y as u16, (y2-y) as u16, Bsp::s( //Fill::Y(Tui::bold(true, wrap(bg, fg, Fill::Y(Align::w(&"▞▞▞▞ ▞▞▞▞"))))), //Thunk::new(|to: &mut TuiOut|for (index, _connection) in connections.iter().enumerate() { //to.place(&map_south(index as u16, 1, Fill::Y(Align::w(Tui::bold(false, //wrap(bg, fg, Fill::Y(&""))))))) //}) //))) //})) //} //track_scroll: Fill::Y(Fixed::Y(1, ScrollbarH { //offset: arrangement.track_scroll, //length: h_tracks_area as usize, //total: h_scenes as usize, //})), //take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));