use crate::{*, arrange::*}; /// A track consists of a sequencer and zero or more devices chained after it. /// /// ``` /// let track: tek::Track = Default::default(); /// ``` #[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, } 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() }) } pub fn audio_ins (&self) -> &[AudioInput] { self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() } pub fn audio_outs (&self) -> &[AudioOutput] { self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() } pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a) ) -> impl Draw { iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{ fg_bg( track.color.lightest.term, track.color.base.term, callback(index, track) ).exact_w((x2 - x1) as u16) })) } pub fn stop (&mut self) -> Perhaps { self.sequencer.enqueue_next(None); Ok(None) } } 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; } } } impl + AsMutOpt> HasTrack for T {} pub trait HasTrack: AsRefOpt + AsMutOpt { fn track (&self) -> Option<&Track> { self.as_ref_opt() } fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() } } impl + Namespace> + Namespace + Namespace> > TrackController for T {} #[tek_proc::commands(TrackCommand)] pub trait TrackController: HasTrack + Namespace + Namespace> + Namespace + Namespace> { #[command(Stop = "track/stop")] fn track_stop (&mut self) -> Perhaps { self.track_mut().map(|track|track.sequencer.enqueue_next(None)); Ok(None) } #[command(SetMute = "track/mute")] fn track_set_mute (&mut self, _mute: Option) -> Perhaps { todo!() } #[command(SetSolo = "track/solo")] fn track_set_solo (&mut self, _solo: Option) -> Perhaps { todo!() } #[command(SetSize = "track/size")] fn track_set_size (&mut self, _size: usize) -> Perhaps { todo!() } #[command(SetZoom = "track/zoom")] fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps { todo!() } #[command(SetName = "track/name")] fn track_set_name (&mut self, name: Arc) -> Perhaps { self.track_mut() .map(|track|swap_value(&mut track.name, &name, |name|TrackCommand::SetName { name })) .transpose() .map(Option::flatten) } #[command(SetColor = "track/color")] fn track_set_color (&mut self, color: ItemTheme) -> Perhaps { self.track_mut() .map(|track|swap_value(&mut track.color, &color, |color|TrackCommand::SetColor { color })) .transpose() .map(Option::flatten) } #[command(SetRec = "track/rec")] fn track_set_rec (&mut self, rec: Option) -> Perhaps { self.track_mut() .map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCommand::SetRec { rec })) .transpose() .map(Option::flatten) } #[command(SetMon = "track/mon")] fn track_set_mon (&mut self, mon: Option) -> Perhaps { self.track_mut() .map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCommand::SetMon { mon })) .transpose() .map(Option::flatten) } } pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { track.width as u16 } def_sizes_iter!(TracksSizes => Track); impl_has!(Vec: |self: Arrangement| self.tracks); impl_as_ref!(Vec: |self: App| self.project.as_ref()); impl_as_mut!(Vec: |self: App| self.project.as_mut()); impl> + AsMut> + HasClock + HasTrackScroll> HasTracks for T {} pub trait HasTracks: AsRef> + AsMut> + HasClock + HasTrackScroll { /// Spacing between tracks. const TRACK_SPACING: usize = 0; /// Read-only reference to collection of [Track]s. fn tracks (&self) -> &Vec { self.as_ref() } /// Mutable reference to collection of [Track]s. fn tracks_mut (&mut self) -> &mut Vec { self.as_mut() } /// Run audio callbacks for every track and every device fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control where Self: HasJack<'static> { 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 } /// Add multiple tracks fn tracks_add_many ( &mut self, count: usize, width: Option, mins: Arc<[Connect]>, mouts: Arc<[Connect]>, ) -> Usually<()> where Self: HasJack<'static> + HasScenes { let track_color_1 = ItemColor::random(); let track_color_2 = ItemColor::random(); for i in 0..count { let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1; if let Some(width) = width { track.width = width; } } Ok(()) } /// Add a track fn tracks_add_one ( &mut self, name: Option>, color: Option, mins: Arc<[Connect]>, mouts: Arc<[Connect]>, ) -> Usually<(usize, &mut Track)> where Self: HasJack<'static> + HasScenes { let name: Arc = name.map_or_else( ||format!("trk{}", self.tracks_last()).into(), |x|x.to_string().into() ); *self.tracks_last_mut() += 1; let track = Track { width: (name.len() + 2).max(12), color: color.unwrap_or_else(ItemTheme::random), sequencer: Sequencer::new( &name, self.jack(), Some(self.clock()), None, mins.as_ref(), mouts.as_ref() )?, name, ..Default::default() }; self.tracks_mut().push(track); let len = self.tracks().len(); let index = len - 1; for scene in self.scenes_mut().iter_mut() { while scene.clips.len() < len { scene.clips.push(None); } } Ok((index, &mut self.tracks_mut()[index])) } fn track_longest_name (&self) -> usize { self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) } } impl + Namespace + Namespace> + Namespace> + Namespace>> + Namespace> + Namespace>> + Namespace>>>>> > TracksController for T {} #[tek_proc::commands(TracksCommand)] pub trait TracksController: HasTracks + HasScenes + HasJack<'static> + Namespace + Namespace> + Namespace> + Namespace>> + Namespace> + Namespace>> + Namespace>>>>> { #[command(Stop = "stop")] /// Stop all playing clips fn tracks_stop_all (&mut self) -> Perhaps { for track in self.tracks_mut().iter_mut() { track.sequencer.enqueue_next(None); } Ok(None) } #[command(Launch = "launch")] /// Launch multiple clips fn tracks_launch ( &mut self, clips: Option>>>> ) -> Perhaps { 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); } } Ok(None) } #[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps where Self: HasScenes + HasJack<'static> { let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; Ok(None) } } impl< T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack > TracksView for T {} pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack { /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { let _editor_width = self.editor().map(|e|e.size.w()); let _active_track = self.selection().track(); let mut x = 0; let w = self.clips_size().w() as usize; self.tracks().iter().enumerate().map_while(move |(index, track)|{ let width = track.width.max(8); if x + width < w { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; Some(data) } else { None } }) } /// Draw name of each track fn view_track_names (&self, theme: ItemTheme) -> impl Draw { let selected = self.selection(); east( south( button_3( "t", "rack ", east( selected.track().map(|track|east(track, "/")), self.tracks().len() ), false ), button_3( "s", "cene ", east( selected.scene().map(|scene|east(scene, "/")), self.scenes().len() ), false ) ), west( south( button_2("T", "+", false), button_2("S", "+", false), ), bg(theme.darker.term, iter_east(||self.tracks_with_sizes() .map(|(index, track, _x1, _x2)|{ let b = if selected.track() == Some(index) { track.color.light.term } else { track.color.base.term }; bg(b, south( east!( "·t", index, " ", fg(Rgb(255, 255, 255), bold(true, &track.name)) ) .align_nw() .full_w(), "" )) .exact_w(track_width(index, track)) .exact_h(2) }))) ) ) } /// Draw outputs per track fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { view_track_row_section(theme, south(button_2("o", "utput", false).align_w().full_w(), draw(|to: &mut Tui|{ for port in self.midi_outs().iter() { let _ = port.port_name().align_w().full_w().draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) })), button_2("O", "+", false), bg(theme.darker.term, draw(|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { let f = Rgb(255, 255, 255); let b = track.color.dark.term; iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{ fg(f, bg(b, east!( "·o", index, " ", port.port_name() ) .full_w() .align_w() ).exact_h(1)) })) .full_h() .align_nw() .exact_w(track_width(index, track)) .draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) }).align_w())) } /// Draw inputs per track fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw { let mut height = 0u16; for track in self.tracks().iter() { height = height.max(track.sequencer.midi_ins.len() as u16); } view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), bg(theme.darker.term, draw(move|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { south( bg(track.color.base.term, east!( either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "), either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), ).align_w().full_w()), iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term, east!( "·i", index, " ", port.port_name() ).align_w().full_w()))) ).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) }).align_w())) } fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { let height = self.tracks_devices_height(); let btn1 = button_3("d", "evice", self.track().map(|t|t.devices.len()).unwrap_or(0), false); let btn2 = button_2("D", "+", false); view_track_row_section(theme, btn1, btn2, iter_east(move||self.tracks_with_sizes() .enumerate() .map(move|(index, (_, track, _x1, _x2))|bg( track.color.dark.term, iter_south(move||(0..height).map(|_|fg_bg( ItemTheme::G[32].lightest.term, ItemTheme::G[32].dark.term, " · --".align_nw() ).exact_wh(track.width as u16, 2))) .align_nw() ) .exact_wh( Some(track_width(index, track)), Some(height + 1), )))) } fn tracks_devices_height (&self) -> u16 { let mut h = 2; for track in self.tracks().iter() { h = h.max(track.devices.len() * 2); } h as u16 } fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { let title_1 = button_3("i", "nput ", self.midi_ins().len(), false).align_w().exact_wh(20, 1); let title_2 = button_2("I", "+", false).exact_wh(4, 1); east(title_1, west(title_2, draw(move|to: &mut Tui|{ for (_index, track, x1, _x2) in self.tracks_with_sizes() { let _ = south( bg(track.color.dark.term, east!( either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), either(track.sequencer.recording, fg(Red, "rec "), "rec "), either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "), ).exact_w(track.width as u16)).align_w().push_x(x1 as u16), draw(move |to: &mut Tui|{ for (index, port) in self.midi_ins().as_slice().iter().enumerate() { let _ = east( east( " ● ", bold(true, fg(Rgb(255,255,255), port.port_name())) ).align_w().exact_w(20), west( ().exact_w(4), bg(track.color.darker.term, east!( either(track.sequencer.monitoring, fg(Green, " ● "), " · "), either(track.sequencer.recording, fg(Red, " ● "), " · "), either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), ).exact_w(track.width as u16).align_w()) ) ).push_x(index as u16 * 10).exact_h(1).draw(to)?; } todo!() }) ).draw(to)?; } Ok(Some(to.area())) }))) } fn outputs_height (&self) -> u16 { let mut h = 1; for output in self.midi_outs().iter() { h += 1 + output.connections.len(); } h as u16 } fn view_outputs (&self, theme: ItemTheme) -> impl Draw { let height = self.outputs_height(); let list = south( button_3( "o", "utput", self.midi_outs().len(), false ).align_w().full_w().exact_h(1), draw(|to: &mut Tui|{ for (_index, port) in self.midi_outs().iter().enumerate() { east( east( " ● ", fg(Rgb(255,255,255), bold(true, port.port_name())) ).align_w(), east!( port.port().get_connections().len(), "/", port.connections.len(), " " ).align_e().full_w().exact_h(1)).full_w().draw(to)?; for (index, conn) in port.connections.iter().enumerate() { east!( " c", index, conn.info() ).align_w().full_w().exact_h(1).draw(to)?; } } todo!(); }).align_nw().full_wh().exact_h(height - 1) ); view_track_row_section(theme, list, button_2("O", "+", false), bg(theme.darker.term, draw(|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { let _ = draw(|to: &mut Tui|{ east( either(true, fg(Green, "play "), "play "), either(false, fg(Yellow, "solo "), "solo "), ).align_w().exact_h(1).draw(to)?; for (_index, port) in self.midi_outs().iter().enumerate() { east( either(true, fg(Green, " ● "), " · "), either(false, fg(Yellow, " ● "), " · "), ).align_w().exact_h(1).draw(to)?; for (_index, _conn) in port.connections.iter().enumerate() { "".full_w().exact_h(1).draw(to)?; } } todo!() }).exact_w(track_width(index, track)).draw(to)?; } todo!() }).align_w().full_w())).exact_h(height) } } pub trait HasTrackScroll { fn track_scroll (&self) -> usize; fn tracks_last (&self) -> usize; fn tracks_last_mut (&mut self) -> &mut usize; } impl HasTrackScroll for &mut Arrangement { fn track_scroll (&self) -> usize { self.track_scroll } fn tracks_last (&self) -> usize { self.track_last } fn tracks_last_mut (&mut self) -> &mut usize { &mut self.track_last } } impl HasTrackScroll for Arrangement { fn track_scroll (&self) -> usize { self.track_scroll } fn tracks_last (&self) -> usize { self.track_last } fn tracks_last_mut (&mut self) -> &mut usize { &mut self.track_last } } impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } fn tracks_last (&self) -> usize { self.project.track_last } fn tracks_last_mut (&mut self) -> &mut usize { &mut self.project.track_last } } fn view_track_row_section <'a> ( _theme: ItemTheme, button: impl Draw, button_add: impl Draw, content: impl Draw, ) -> impl Draw { west( button_add.align_nw().exact_w(4).full_h(), east( button.align_nw().full_h().exact_w(20), content.align_c().full_wh() ) ) }