use ::std::sync::{Arc, RwLock}; use ::tengri::{space::east, color::ItemTheme}; use ::tengri::{draw::*, term::*}; use crate::{*, device::*, editor::*, sequence::*, clock::*, select::*, sample::*}; /// Arranger. /// /// ``` /// let arranger = tek::Arrangement::default(); /// ``` #[derive(Default, Debug)] pub struct Arrangement { /// Project name. pub name: Arc, /// Base color. pub color: ItemTheme, /// JACK client handle. pub jack: Jack<'static>, /// FIXME a render of the project arrangement, redrawn on update. /// TODO rename to "render_cache" or smth pub arranger: Arc>, /// Display size pub size: Size, /// Display size of clips area pub size_inner: Size, /// Source of time #[cfg(feature = "clock")] pub clock: Clock, /// Allows one MIDI clip to be edited #[cfg(feature = "editor")] pub editor: Option, /// List of global midi inputs #[cfg(feature = "port")] pub midi_ins: Vec, /// List of global midi outputs #[cfg(feature = "port")] pub midi_outs: Vec, /// List of global audio inputs #[cfg(feature = "port")] pub audio_ins: Vec, /// List of global audio outputs #[cfg(feature = "port")] pub audio_outs: Vec, /// Selected UI element #[cfg(feature = "select")] pub selection: Selection, /// Last track number (to avoid duplicate port names) #[cfg(feature = "track")] pub track_last: usize, /// List of tracks #[cfg(feature = "track")] pub tracks: Vec, /// Scroll offset of tracks #[cfg(feature = "track")] pub track_scroll: usize, /// List of scenes #[cfg(feature = "scene")] pub scenes: Vec, /// Scroll offset of scenes #[cfg(feature = "scene")] pub scene_scroll: usize, } /// 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, } /// A scene consists of a set of clips to play together. /// /// ``` /// let scene: tek::Scene = Default::default(); /// let _ = scene.pulses(); /// let _ = scene.is_playing(&[]); /// ``` #[derive(Debug, Default)] pub struct Scene { /// Name of scene pub name: Arc, /// Identifying color of scene pub color: ItemTheme, /// Clips in scene, one per track pub clips: Vec>>>, } impl_has!(Jack<'static>: |self: Arrangement| self.jack); impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } } impl_has!(Size: |self: Arrangement| self.size); impl_has!(Vec: |self: Arrangement| self.tracks); impl_has!(Vec: |self: Arrangement| self.scenes); impl_has!(Vec: |self: Arrangement| self.midi_ins); impl_has!(Vec: |self: Arrangement| self.midi_outs); impl_has!(Clock: |self: Arrangement| self.clock); impl_has!(Selection: |self: Arrangement| self.selection); impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref()); impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut()); impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track()); impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut()); impl >+AsMut>> HasScenes for T {} impl >+AsMut>> HasTracks for T {} impl +AsMutOpt+Send+Sync> HasScene for T {} impl +AsMutOpt+Send+Sync> HasTrack for T {} impl TracksView for T {} impl ClipsView for T {} pub trait ClipsView: TracksView + ScenesView { fn view_scenes_clips <'a> (&'a self) -> impl Draw + 'a { self.clips_size().of(wh_full(above( wh_full(origin_se(fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), thunk(|to: &mut Tui|{ for (track_index, track, _, _) in self.tracks_with_sizes() { to.place(&w_exact(track.width as u16, h_full(self.view_track_clips(track_index, track)))) } Ok(XYWH(0, 0, 0, 0)) })))) } fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Draw + 'a { thunk(move|to: &mut Tui|{ for (scene_index, scene, ..) in self.scenes_with_sizes() { let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); (format!(" ⏹ {}", &clip.name).into(), clip.color) } else { (" ⏹ -- ".into(), ItemTheme::G[32]) }; let fg = theme.lightest.term; let mut outline = theme.base.term; let bg = if self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) { outline = theme.lighter.term; theme.light.term } else if self.selection().track() == Some(track_index) || self.selection().scene() == Some(scene_index) { outline = theme.darkest.term; theme.base.term } else { theme.dark.term }; let w = if self.selection().track() == Some(track_index) && let Some(editor) = self.editor () { (editor.size.w() as usize).max(24).max(track.width) } else { track.width } as u16; let y = if self.selection().scene() == Some(scene_index) && let Some(editor) = self.editor () { (editor.size.h() as usize).max(12) } else { Self::H_SCENE as usize } as u16; let is_selected = self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) && self.is_editing(); to.place(&wh_exact(Some(w), Some(y), below( wh_full(Outer(true, Style::default().fg(outline))), wh_full(below( below( fg_bg(outline, bg, wh_full("")), wh_full(origin_nw(fg_bg(fg, bg, bold(true, &name)))), ), wh_full(when(is_selected, self.editor().map(|e|e.view())))))))); } Ok(XYWH(0, 0, 0, 0)) }) } } pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { fn tracks_width_available (&self) -> u16 { (self.size.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.size.w()); 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() as usize { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; Some(data) } else { None } }) } fn view_track_names (&self, theme: ItemTheme) -> impl Draw { let track_count = self.tracks().len(); let scene_count = self.scenes().len(); let selected = self.selection(); let button = south( 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 = south( button_2("T", "+", false), button_2("S", "+", false)); view_track_row_section(theme, button, button_2, bg(theme.darker.term, h_exact(2, thunk(|to: &mut Tui|{ for (index, track, x1, _x2) in self.tracks_with_sizes() { to.place(&x_push(x1 as u16, w_exact(track_width(index, track), bg(if selected.track() == Some(index) { track.color.light.term } else { track.color.base.term }, south(w_full(origin_nw(east( format!("·t{index:02} "), fg(Rgb(255, 255, 255), bold(true, &track.name)) ))), ""))) ));} Ok(XYWH(0, 0, 0, 0)) })))) } fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { view_track_row_section(theme, south(w_full(origin_w(button_2("o", "utput", false))), thunk(|to: &mut Tui|{ for port in self.midi_outs().iter() { to.place(&w_full(origin_w(port.port_name()))); } Ok(XYWH(0, 0, 0, 0)) })), button_2("O", "+", false), bg(theme.darker.term, origin_w(thunk(|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&w_exact(track_width(index, track), origin_nw(h_full(iter_south(||track.sequencer.midi_outs.iter(), |port, index|fg(Rgb(255, 255, 255), h_exact(1, bg(track.color.dark.term, w_full(origin_w( format!("·o{index:02} {}", port.port_name()))))))))))); } Ok(XYWH(0, 0, 0, 0)) })))) } fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw { let mut h = 0u16; for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } let content = thunk(move|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&wh_exact(track_width(index, track), h + 1, origin_nw(south( bg(track.color.base.term, w_full(origin_w(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 "), )))), iter_south(1, ||track.sequencer.midi_ins.iter(), |port, index|fg_bg(Rgb(255, 255, 255), track.color.dark.term, w_full(origin_w(format!("·i{index:02} {}", port.port_name()))))))))); } Ok(XYWH(0, 0, 0, 0)) }); view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), bg(theme.darker.term, origin_w(content))) } } pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { /// Default scene height. const H_SCENE: usize = 2; /// Default editor height. const H_EDITOR: usize = 15; fn h_scenes (&self) -> u16; fn w_side (&self) -> u16; fn w_mid (&self) -> u16; fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { let mut y = 0; self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ let height = if self.selection().scene() == Some(s) && self.editor().is_some() { 8 } else { Self::H_SCENE }; if y + height <= self.clips_size().h() as usize { let data = (s, scene, y, y + height); y += height; Some(data) } else { None } }) } fn view_scenes_names (&self) -> impl Draw { w_exact(20, thunk(|to: &mut Tui|for (index, scene, ..) in self.scenes_with_sizes() { to.place(&self.view_scene_name(index, scene)); })) } fn view_scene_name <'a> (&'a self, index: usize, scene: &'a Scene) -> impl Draw + 'a { let h = if self.selection().scene() == Some(index) && let Some(_editor) = self.editor() { 7 } else { Self::H_SCENE as u16 }; let bg = if self.selection().scene() == Some(index) { scene.color.light.term } else { scene.color.base.term }; let a = w_full(origin_w(east(format!("·s{index:02} "), fg(g(255), bold(true, &scene.name))))); let b = when(self.selection().scene() == Some(index) && self.is_editing(), wh_full(origin_nw(south( self.editor().as_ref().map(|e|e.clip_status()), self.editor().as_ref().map(|e|e.edit_status()))))); wh_exact(20, h, bg(bg, origin_nw(south(a, b)))) } } pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; } pub trait HasTrackScroll: HasTracks { fn track_scroll (&self) -> usize; } pub trait HasScene: AsRefOpt + AsMutOpt { fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() } fn scene (&self) -> Option<&Scene> { self.as_ref_opt() } } pub trait HasScenes: AsRef> + AsMut> { fn scenes (&self) -> &Vec { self.as_ref() } fn scenes_mut (&mut self) -> &mut Vec { self.as_mut() } /// Generate the default name for a new scene fn scene_default_name (&self) -> Arc { format!("s{:3>}", self.scenes().len() + 1).into() } fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } /// Add multiple scenes fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks { let scene_color_1 = ItemColor::random(); let scene_color_2 = ItemColor::random(); for i in 0..n { let _ = self.scene_add(None, Some( scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() ))?; } Ok(()) } /// Add a scene fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<(usize, &mut Scene)> where Self: HasTracks { let scene = Scene { name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), clips: vec![None;self.tracks().len()], color: color.unwrap_or_else(ItemTheme::random), }; self.scenes_mut().push(scene); let index = self.scenes().len() - 1; Ok((index, &mut self.scenes_mut()[index])) } } pub trait HasTracks: AsRef> + AsMut> { fn tracks (&self) -> &Vec { self.as_ref() } fn tracks_mut (&mut self) -> &mut Vec { self.as_mut() } /// 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 HasTrack: AsRefOpt + AsMutOpt { fn track (&self) -> Option<&Track> { self.as_ref_opt() } fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() } #[cfg(feature = "port")] fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw + '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 Draw + '_ { 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 Draw { 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 Draw { self.track().map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) } } impl_as_ref!(Vec: |self: App| self.project.as_ref()); impl_as_mut!(Vec: |self: App| self.project.as_mut()); #[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); #[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } } impl HasTrackScroll for Arrangement { fn track_scroll (&self) -> usize { self.track_scroll } } 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 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() } 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!() } pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw + 'a { iter(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; iter_east(x1 as u16, width, w_exact(width, fg_bg( track.color.lightest.term, track.color.base.term, callback(index, track))))}) } /// Create a new track connecting the [Sequencer] to a [Sampler]. #[cfg(feature = "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) } #[cfg(feature = "sampler")] 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 } #[cfg(feature = "sampler")] 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 } } pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw + 'a { per_track_top(tracks, move|index, track|h_full(origin_y(callback(index, track)))) } pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw + 'a { origin_x(bg(Reset, iter(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; iter_east(x1 as u16, width, w_exact(width, fg_bg( track.color.lightest.term, track.color.base.term, callback(index, track))))}))) } #[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); #[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); #[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene()); #[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut()); impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } impl HasSceneScroll for Arrangement { fn scene_scroll (&self) -> usize { self.scene_scroll } } impl ScenesView for App { fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(self.w_side()) } fn w_side (&self) -> u16 { 20 } fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } } impl Scene { /// Returns the pulse length of the longest clip in the scene pub fn pulses (&self) -> usize { self.clips.iter().fold(0, |a, p|{ a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) }) } /// Returns true if all clips in the scene are /// currently playing on the given collection of tracks. pub fn is_playing (&self, tracks: &[Track]) -> bool { self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() .all(|(track_index, clip)|match clip { Some(c) => tracks .get(track_index) .map(|track|{ if let Some((_, Some(clip))) = track.sequencer().play_clip() { *clip.read().unwrap() == *c.read().unwrap() } else { false } }) .unwrap_or(false), None => true }) } pub fn clip (&self, index: usize) -> Option<&Arc>> { match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } } 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 Arrangement { /// Create a new arrangement. pub fn new ( jack: &Jack<'static>, name: Option>, clock: Clock, tracks: Vec, scenes: Vec, midi_ins: Vec, midi_outs: Vec, ) -> Self { Self { clock, tracks, scenes, midi_ins, midi_outs, jack: jack.clone(), name: name.unwrap_or_default(), color: ItemTheme::random(), selection: Selection::TrackClip { track: 0, scene: 0 }, ..Default::default() } } /// Width of display pub fn w (&self) -> u16 { self.size.w() as u16 } /// Width allocated for sidebar. pub fn w_sidebar (&self, is_editing: bool) -> u16 { self.w() / if is_editing { 16 } else { 8 } as u16 } /// Width available to display tracks. pub fn w_tracks_area (&self, is_editing: bool) -> u16 { self.w().saturating_sub(self.w_sidebar(is_editing)) } /// Height of display pub fn h (&self) -> u16 { self.size.h() as u16 } /// Height taken by visible device slots. pub fn h_devices (&self) -> u16 { 2 //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) } /// Add multiple tracks #[cfg(feature = "track")] pub fn tracks_add ( &mut self, count: usize, width: Option, mins: &[Connect], mouts: &[Connect], ) -> Usually<()> { 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.track_add(None, Some(color), mins, mouts)?.1; if let Some(width) = width { track.width = width; } } Ok(()) } /// Add a track #[cfg(feature = "track")] pub fn track_add ( &mut self, name: Option<&str>, color: Option, mins: &[Connect], mouts: &[Connect], ) -> Usually<(usize, &mut Track)> { let name: Arc = name.map_or_else( ||format!("trk{:02}", self.track_last).into(), |x|x.to_string().into() ); self.track_last += 1; let track = Track { width: (name.len() + 2).max(12), color: color.unwrap_or_else(ItemTheme::random), sequencer: Sequencer::new( &format!("{name}"), self.jack(), Some(self.clock()), None, mins, mouts )?, 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])) } #[cfg(feature = "track")] pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { south( h_exact(1, self.view_inputs_header()), thunk(|to: &mut Tui|{ for (index, port) in self.midi_ins().iter().enumerate() { to.place(&x_push(index as u16 * 10, h_exact(1, self.view_inputs_row(port)))) } }) ) } #[cfg(feature = "track")] fn view_inputs_header (&self) -> impl Draw + '_ { east(w_exact(20, origin_w(button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))), west(w_exact(4, button_2("I", "+", false)), thunk(move|to: &mut Tui|for (_index, track, x1, _x2) in self.tracks_with_sizes() { #[cfg(feature = "track")] to.place(&x_push(x1 as u16, bg(track.color.dark.term, origin_w(w_exact(track.width as u16, 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 "), )))))) }))) } #[cfg(feature = "track")] fn view_inputs_row (&self, port: &MidiInput) -> impl Draw { east(w_exact(20, origin_w(east(" ● ", bold(true, fg(Rgb(255,255,255), port.port_name()))))), west(w_exact(4, ()), thunk(move|to: &mut Tui|for (_index, track, _x1, _x2) in self.tracks_with_sizes() { #[cfg(feature = "track")] to.place(&bg(track.color.darker.term, origin_w(w_exact(track.width as u16, east!( either(track.sequencer.monitoring, fg(Green, " ● "), " · "), either(track.sequencer.recording, fg(Red, " ● "), " · "), either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), ))))) }))) } #[cfg(feature = "track")] pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw { let mut h = 1; for output in self.midi_outs().iter() { h += 1 + output.connections.len(); } let h = h as u16; let list = south( h_exact(1, w_full(origin_w(button_3("o", "utput", format!("{}", self.midi_outs.len()), false)))), h_exact(h - 1, wh_full(origin_nw(thunk(|to: &mut Tui|{ for (_index, port) in self.midi_outs().iter().enumerate() { to.place(&h_exact(1,w_full(east( origin_w(east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name())))), w_full(origin_e(format!("{}/{} ", port.port().get_connections().len(), port.connections.len()))))))); for (index, conn) in port.connections.iter().enumerate() { to.place(&h_exact(1, w_full(origin_w(format!(" c{index:02}{}", conn.info()))))); } } }))))); h_exact(h, view_track_row_section(theme, list, button_2("O", "+", false), bg(theme.darker.term, origin_w(w_full( thunk(|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&w_exact(track_width(index, track), thunk(|to: &mut Tui|{ to.place(&h_exact(1, origin_w(east( either(true, fg(Green, "play "), "play "), either(false, fg(Yellow, "solo "), "solo "), )))); for (_index, port) in self.midi_outs().iter().enumerate() { to.place(&h_exact(1, origin_w(east( either(true, fg(Green, " ● "), " · "), either(false, fg(Yellow, " ● "), " · "), )))); for (_index, _conn) in port.connections.iter().enumerate() { to.place(&h_exact(1, w_full(""))); } }})))}})))))) } #[cfg(feature = "track")] pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { let mut h = 2u16; for track in self.tracks().iter() { h = h.max(track.devices.len() as u16 * 2); } view_track_row_section(theme, button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), button_2("D", "+", false), thunk(move|to: &mut Tui|for (index, track, _x1, _x2) in self.tracks_with_sizes() { to.place(&wh_exact(track_width(index, track), h + 1, bg(track.color.dark.term, origin_nw(iter_south(2, move||0..h, |_, _index|wh_exact(track.width as u16, 2, fg_bg( ItemTheme::G[32].lightest.term, ItemTheme::G[32].dark.term, origin_nw(format!(" · {}", "--"))))))))); })) } /// Put a clip in a slot #[cfg(feature = "clip")] pub fn clip_put ( &mut self, track: usize, scene: usize, clip: Option>> ) -> Option>> { let old = self.scenes[scene].clips[track].clone(); self.scenes[scene].clips[track] = clip; old } /// Change the color of a clip, returning the previous one #[cfg(feature = "clip")] pub fn clip_set_color (&self, track: usize, scene: usize, color: ItemTheme) -> Option { self.scenes[scene].clips[track].as_ref().map(|clip|{ let mut clip = clip.write().unwrap(); let old = clip.color.clone(); clip.color = color.clone(); panic!("{color:?} {old:?}"); //old }) } /// Toggle looping for the active clip #[cfg(feature = "clip")] pub fn toggle_loop (&mut self) { if let Some(clip) = self.selected_clip() { clip.write().unwrap().toggle_loop() } } /// Get the first sampler of the active track #[cfg(feature = "sampler")] pub fn sampler (&self) -> Option<&Sampler> { self.selected_track()?.sampler(0) } /// Get the first sampler of the active track #[cfg(feature = "sampler")] pub fn sampler_mut (&mut self) -> Option<&mut Sampler> { self.selected_track_mut()?.sampler_mut(0) } } impl ScenesView for Arrangement { fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) } fn w_mid (&self) -> u16 { (self.size.width() as u16).saturating_sub(2 * self.w_side()).max(40) } } impl HasClipsSize for Arrangement { fn clips_size (&self) -> &Size { &self.size_inner } } pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); def_command!(SceneCommand: |scene: Scene| { SetSize { size: usize } => { todo!() }, SetZoom { size: usize } => { todo!() }, SetName { name: Arc } => swap_value(&mut scene.name, name, |name|Self::SetName{name}), SetColor { color: ItemTheme } => swap_value(&mut scene.color, color, |color|Self::SetColor{color}), }); 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 }), }); def_command!(ClipCommand: |clip: MidiClip| { SetColor { color: Option } => { //(SetColor [t: usize, s: usize, c: ItemTheme] //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) todo!() }, SetLoop { looping: Option } => { //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) todo!() } });