use crate::*; use std::sync::atomic::Ordering; /// Things that can provide a [jack::Client] reference. /// /// ``` /// use tek::{Jack, HasJack}; /// /// let jack: &Jack = Jacked::default().jack(); /// /// #[derive(Default)] struct Jacked<'j>(Jack<'j>); /// /// impl<'j> tek::HasJack<'j> for Jacked<'j> { /// fn jack (&self) -> &Jack<'j> { &self.0 } /// } /// ``` pub trait HasJack<'j>: Send + Sync { /// Return the internal [jack::Client] handle /// that lets you call the JACK API. fn jack (&self) -> &Jack<'j>; fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { self.jack().with_client(op) } fn port_by_name (&self, name: &str) -> Option> { self.with_client(|client|client.port_by_name(name)) } fn port_by_id (&self, id: u32) -> Option> { self.with_client(|c|c.port_by_id(id)) } fn register_port (&self, name: impl AsRef) -> Usually> { self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?)) } fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> { if enable { self.with_client(|client|match client.register_timebase_callback(false, callback) { Ok(_) => Ok(()), Err(e) => Err(e) })? } Ok(()) } fn sync_follow (&self, _enable: bool) -> Usually<()> { // TODO: sync follow Ok(()) } } /// Trait for thing that has a JACK process callback. pub trait Audio { /// Handle a JACK event. fn handle (&mut self, _event: JackEvent) {} /// Projecss a JACK chunk. fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { Control::Continue } /// The JACK process callback function passed to the server. fn callback ( state: &Arc>, client: &Client, scope: &ProcessScope ) -> Control where Self: Sized { if let Ok(mut state) = state.write() { state.process(client, scope) } else { Control::Quit } } } /// Implement [Audio]: provide JACK callbacks. #[macro_export] macro_rules! audio { (| $self1:ident: $Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident |$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => { impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { #[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb } $(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })? } }; ($Struct:ident: $process:ident, $handle:ident) => { impl Audio for $Struct { #[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control { $process(self, c, s) } #[inline] fn handle (&mut self, e: JackEvent) { $handle(self, e) } } }; ($Struct:ident: $process:ident) => { impl Audio for $Struct { #[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control { $process(self, c, s) } } }; } pub trait JackPerfModel { fn update_from_jack_scope (&self, t0: Option, scope: &ProcessScope); } //pub trait MaybeHas: Send + Sync { //fn get (&self) -> Option<&T>; //} pub trait HasN: Send + Sync { fn get_nth (&self, key: usize) -> &T; fn get_nth_mut (&mut self, key: usize) -> &mut T; } pub trait Gettable { /// Returns current value fn get (&self) -> T; } pub trait Mutable: Gettable { /// Sets new value, returns old fn set (&mut self, value: T) -> T; } pub trait InteriorMutable: Gettable { /// Sets new value, returns old fn set (&self, value: T) -> T; } pub trait NotePoint { fn note_len (&self) -> &AtomicUsize; /// Get the current length of the note cursor. fn get_note_len (&self) -> usize { self.note_len().load(Relaxed) } /// Set the length of the note cursor, returning the previous value. fn set_note_len (&self, x: usize) -> usize { self.note_len().swap(x, Relaxed) } fn note_pos (&self) -> &AtomicUsize; /// Get the current pitch of the note cursor. fn get_note_pos (&self) -> usize { self.note_pos().load(Relaxed).min(127) } /// Set the current pitch fo the note cursor, returning the previous value. fn set_note_pos (&self, x: usize) -> usize { self.note_pos().swap(x.min(127), Relaxed) } } pub trait TimePoint { fn time_pos (&self) -> &AtomicUsize; /// Get the current time position of the note cursor. fn get_time_pos (&self) -> usize { self.time_pos().load(Relaxed) } /// Set the current time position of the note cursor, returning the previous value. fn set_time_pos (&self, x: usize) -> usize { self.time_pos().swap(x, Relaxed) } } pub trait MidiPoint: NotePoint + TimePoint { /// Get the current end of the note cursor. fn get_note_end (&self) -> usize { self.get_time_pos() + self.get_note_len() } } pub trait TimeRange { fn time_len (&self) -> &AtomicUsize; fn get_time_len (&self) -> usize { self.time_len().load(Ordering::Relaxed) } fn time_zoom (&self) -> &AtomicUsize; fn get_time_zoom (&self) -> usize { self.time_zoom().load(Ordering::Relaxed) } fn set_time_zoom (&self, value: usize) -> usize { self.time_zoom().swap(value, Ordering::Relaxed) } fn time_lock (&self) -> &AtomicBool; fn get_time_lock (&self) -> bool { self.time_lock().load(Ordering::Relaxed) } fn set_time_lock (&self, value: bool) -> bool { self.time_lock().swap(value, Ordering::Relaxed) } fn time_start (&self) -> &AtomicUsize; fn get_time_start (&self) -> usize { self.time_start().load(Ordering::Relaxed) } fn set_time_start (&self, value: usize) -> usize { self.time_start().swap(value, Ordering::Relaxed) } fn time_axis (&self) -> &AtomicUsize; fn get_time_axis (&self) -> usize { self.time_axis().load(Ordering::Relaxed) } fn get_time_end (&self) -> usize { self.time_start().get() + self.time_axis().get() * self.time_zoom().get() } } pub trait NoteRange { fn note_lo (&self) -> &AtomicUsize; fn get_note_lo (&self) -> usize { self.note_lo().load(Ordering::Relaxed) } fn set_note_lo (&self, x: usize) -> usize { self.note_lo().swap(x, Ordering::Relaxed) } fn note_axis (&self) -> &AtomicUsize; fn get_note_axis (&self) -> usize { self.note_axis().load(Ordering::Relaxed) } fn get_note_hi (&self) -> usize { (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) } } pub trait MidiRange: TimeRange + NoteRange {} /// A unit of time, represented as an atomic 64-bit float. /// /// According to https://stackoverflow.com/a/873367, as per IEEE754, /// every integer between 1 and 2^53 can be represented exactly. /// This should mean that, even at 192kHz sampling rate, over 1 year of audio /// can be clocked in microseconds with f64 without losing precision. pub trait TimeUnit: InteriorMutable {} pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; } pub trait HasTrackScroll: HasTracks { fn track_scroll (&self) -> usize; } pub trait HasMidiClip { fn clip (&self) -> Option>>; } pub trait HasClipsSize { fn clips_size (&self) -> &Measure; } pub trait HasClock: Send + Sync { fn clock (&self) -> &Clock; fn clock_mut (&mut self) -> &mut Clock; } pub trait HasDevices { fn devices (&self) -> &Vec; fn devices_mut (&mut self) -> &mut Vec; } pub trait HasSelection: Has { fn selection (&self) -> &Selection { self.get() } fn selection_mut (&mut self) -> &mut Selection { self.get_mut() } } pub trait HasWidth { const MIN_WIDTH: usize; /// Increment track width. fn width_inc (&mut self); /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. fn width_dec (&mut self); } pub trait HasMidiBuffers { fn note_buf_mut (&mut self) -> &mut Vec; fn midi_buf_mut (&mut self) -> &mut Vec>>; } pub trait HasSequencer { fn sequencer (&self) -> &Sequencer; fn sequencer_mut (&mut self) -> &mut Sequencer; } pub trait HasScene: Has> + Send + Sync { fn scene (&self) -> Option<&Scene> { Has::>::get(self).as_ref() } fn scene_mut (&mut self) -> &mut Option { Has::>::get_mut(self) } } pub trait HasScenes: Has> + Send + Sync { fn scenes (&self) -> &Vec { Has::>::get(self) } fn scenes_mut (&mut self) -> &mut Vec { Has::>::get_mut(self) } /// 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) } } /// ``` /// use tek::{MidiEditor, HasEditor, tengri::Has}; /// struct TestEditorHost(Option); /// tek::tengri::has!(Option: |self: TestEditorHost|self.0); /// let mut host = TestEditorHost(Some(MidiEditor::default())); /// let _ = host.editor(); /// let _ = host.editor_mut(); /// let _ = host.is_editing(); /// let _ = host.editor_w(); /// let _ = host.editor_h(); /// ``` pub trait HasEditor: Has> { fn editor (&self) -> Option<&MidiEditor> { self.get().as_ref() } fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.get_mut().as_mut() } fn is_editing (&self) -> bool { self.editor().is_some() } fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize } fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize } } pub trait HasClips { fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; fn add_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); self.clips_mut().push(clip.clone()); (self.clips().len() - 1, clip) } } /// Trait for thing that may receive MIDI. pub trait HasMidiIns { fn midi_ins (&self) -> &Vec; fn midi_ins_mut (&mut self) -> &mut Vec; /// Collect MIDI input from app ports (TODO preallocate large buffers) fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> { self.midi_ins().iter() .map(|port|port.port().iter(scope) .map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes))) .collect::>()) .collect::>() } fn midi_ins_with_sizes <'a> (&'a self) -> impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_ins().iter().enumerate().map(move|(i, input)|{ let height = 1 + input.connections().len(); let data = (i, input.port_name(), input.connections(), y, y + height); y += height; data }) } } /// Trait for thing that may output MIDI. pub trait HasMidiOuts { fn midi_outs (&self) -> &Vec; fn midi_outs_mut (&mut self) -> &mut Vec; fn midi_outs_with_sizes <'a> (&'a self) -> impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_outs().iter().enumerate().map(move|(i, output)|{ let height = 1 + output.connections().len(); let data = (i, output.port_name(), output.connections(), y, y + height); y += height; data }) } fn midi_outs_emit (&mut self, scope: &ProcessScope) { for port in self.midi_outs_mut().iter_mut() { port.buffer_emit(scope) } } } 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 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())) } } pub trait HasPlayClip: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; fn play_clip (&self) -> &Option<(Moment, Option>>)>; fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn next_clip (&self) -> &Option<(Moment, Option>>)>; fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); return Some(elapsed) } None } fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { if let Some((started, Some(clip))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let length = clip.read().unwrap().length.max(1); // prevent div0 on empty clip let times = (elapsed as usize / length) as f64; let elapsed = (elapsed as usize % length) as f64; return Some((times, elapsed)) } None } fn enqueue_next (&mut self, clip: Option<&Arc>>) { *self.next_clip_mut() = Some((self.clock().next_launch_instant(), clip.cloned())); *self.reset_mut() = true; } fn play_status (&self) -> impl Content { let (name, color): (Arc, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let MidiClip { ref name, color, .. } = *clip.read().unwrap(); (name.clone(), color) } else { ("".into(), Tui::g(64).into()) }; let time: String = self.pulses_since_start_looped() .map(|(times, time)|format!("{:>3}x {:>}", times+1.0, self.clock().timebase.format_beats_1(time))) .unwrap_or_else(||String::from(" ")).into(); FieldV(color, "Now:", format!("{} {}", time, name)) } fn next_status (&self) -> impl Content { let mut time: Arc = String::from("--.-.--").into(); let mut name: Arc = String::from("").into(); let mut color = ItemTheme::G[64]; let clock = self.clock(); if let Some((t, Some(clip))) = self.next_clip() { let clip = clip.read().unwrap(); name = clip.name.clone(); color = clip.color.clone(); time = { let target = t.pulse.get(); let current = clock.playhead.pulse.get(); if target > current { let remaining = target - current; format!("-{:>}", clock.timebase.format_beats_1(remaining)) } else { String::new() } }.into() } else if let Some((t, Some(clip))) = self.play_clip() { let clip = clip.read().unwrap(); if clip.looped { name = clip.name.clone(); color = clip.color.clone(); let target = t.pulse.get() + clip.length as f64; let current = clock.playhead.pulse.get(); if target > current { time = format!("-{:>}", clock.timebase.format_beats_0(target - current)).into() } } else { name = "Stop".to_string().into(); } }; FieldV(color, "Next:", format!("{} {}", time, name)) } } pub trait MidiMonitor: HasMidiIns + HasMidiBuffers { fn notes_in (&self) -> &Arc>; fn monitoring (&self) -> bool; fn monitoring_mut (&mut self) -> &mut bool; fn toggle_monitor (&mut self) { *self.monitoring_mut() = !self.monitoring(); } fn monitor (&mut self, scope: &ProcessScope) { } } pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip { fn recording (&self) -> bool; fn recording_mut (&mut self) -> &mut bool; fn toggle_record (&mut self) { *self.recording_mut() = !self.recording(); } fn overdub (&self) -> bool; fn overdub_mut (&mut self) -> &mut bool; fn toggle_overdub (&mut self) { *self.overdub_mut() = !self.overdub(); } fn record_clip ( &mut self, scope: &ProcessScope, started: Moment, clip: &Option>>, ) { if let Some(clip) = clip { let sample0 = scope.last_frame_time() as usize; let start = started.sample.get() as usize; let _recording = self.recording(); let timebase = self.clock().timebase().clone(); let quant = self.clock().quant.get(); let mut clip = clip.write().unwrap(); let length = clip.length; for input in self.midi_ins_mut().iter() { for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { if let LiveEvent::Midi { message, .. } = event { clip.record_event({ let sample = (sample0 + sample - start) as f64; let pulse = timebase.samples_to_pulse(sample); let quantized = (pulse / quant).round() * quant; quantized as usize % length }, message); } } } } } fn record_next (&mut self) { // TODO switch to next clip and record into it } } pub trait MidiViewer: Measured + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); fn redraw (&self); fn clip (&self) -> &Option>>; fn clip_mut (&mut self) -> &mut Option>>; fn set_clip (&mut self, clip: Option<&Arc>>) { *self.clip_mut() = clip.cloned(); self.redraw(); } /// Make sure cursor is within note range fn autoscroll (&self) { let note_pos = self.get_note_pos().min(127); let note_lo = self.get_note_lo(); let note_hi = self.get_note_hi(); if note_pos < note_lo { self.note_lo().set(note_pos); } else if note_pos > note_hi { self.note_lo().set((note_lo + note_pos).saturating_sub(note_hi)); } } /// Make sure time range is within display fn autozoom (&self) { if self.time_lock().get() { let time_len = self.get_time_len(); let time_axis = self.get_time_axis(); let time_zoom = self.get_time_zoom(); loop { let time_zoom = self.time_zoom().get(); let time_area = time_axis * time_zoom; if time_area > time_len { let next_time_zoom = note_duration_prev(time_zoom); if next_time_zoom <= 1 { break } let next_time_area = time_axis * next_time_zoom; if next_time_area >= time_len { self.time_zoom().set(next_time_zoom); } else { break } } else if time_area < time_len { let prev_time_zoom = note_duration_next(time_zoom); if prev_time_zoom > 384 { break } let prev_time_area = time_axis * prev_time_zoom; if prev_time_area <= time_len { self.time_zoom().set(prev_time_zoom); } else { break } } } if time_zoom != self.time_zoom().get() { self.redraw() } } //while time_len.div_ceil(time_zoom) > time_axis { //println!("\r{time_len} {time_zoom} {time_axis}"); //time_zoom = Note::next(time_zoom); //} //self.time_zoom().set(time_zoom); } } pub trait AddScene: HasScenes + HasTracks { /// Add multiple scenes 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 { 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)> { 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 ClipsView: TracksView + ScenesView { fn view_scenes_clips <'a> (&'a self) -> impl Content + 'a { self.clips_size().of(Fill::XY(Bsp::a( Fill::XY(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), Thunk::new(|to: &mut TuiOut|for ( track_index, track, _, _ ) in self.tracks_with_sizes() { to.place(&Fixed::X(track.width as u16, Fill::Y(self.view_track_clips(track_index, track)))) })))) } fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { Thunk::new(move|to: &mut TuiOut|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.rgb; let mut outline = theme.base.rgb; let bg = if self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) { outline = theme.lighter.rgb; theme.light.rgb } else if self.selection().track() == Some(track_index) || self.selection().scene() == Some(scene_index) { outline = theme.darkest.rgb; theme.base.rgb } else { theme.dark.rgb }; let w = if self.selection().track() == Some(track_index) && let Some(editor) = self.editor () { (editor.measure_width() 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.measure_height() as usize).max(12) } else { Self::H_SCENE as usize } as u16; to.place(&Fixed::XY(w, y, Bsp::b( Fill::XY(Outer(true, Style::default().fg(outline))), Fill::XY(Bsp::b( Bsp::b( Tui::fg_bg(outline, bg, Fill::XY("")), Fill::XY(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), ), Fill::XY(When::new(self.selection().track() == Some(track_index) && self.selection().scene() == Some(scene_index) && self.is_editing(), self.editor()))))))); }) } } pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll + Measured { fn tracks_width_available (&self) -> u16 { (self.measure_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.measure_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() 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 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 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 Content { Fixed::X(20, Thunk::new(|to: &mut TuiOut|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 Content + '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.rgb } else { scene.color.base.rgb }; let a = Fill::X(Align::w(Bsp::e(format!("·s{index:02} "), Tui::fg(Tui::g(255), Tui::bold(true, &scene.name))))); let b = When::new(self.selection().scene() == Some(index) && self.is_editing(), Fill::XY(Align::nw(Bsp::s( self.editor().as_ref().map(|e|e.clip_status()), self.editor().as_ref().map(|e|e.edit_status()))))); Fixed::XY(20, h, Tui::bg(bg, Align::nw(Bsp::s(a, b)))) } } /// May create new MIDI input ports. pub trait AddMidiIn { fn midi_in_add (&mut self) -> Usually<()>; } /// May create new MIDI output ports. pub trait AddMidiOut { fn midi_out_add (&mut self) -> Usually<()>; } pub trait RegisterPorts: HasJack<'static> { /// Register a MIDI input port. fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; /// Register a MIDI output port. fn midi_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; /// Register an audio input port. fn audio_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; /// Register an audio output port. fn audio_out (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; } pub trait JackPort: HasJack<'static> { type Port: PortSpec + Default; type Pair: PortSpec + Default; fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) -> Usually where Self: Sized; fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { jack.with_client(|c|c.register_port::(name.as_ref(), Default::default())) .map_err(|e|e.into()) } fn port_name (&self) -> &Arc; fn connections (&self) -> &[Connect]; fn port (&self) -> &Port; fn port_mut (&mut self) -> &mut Port; fn into_port (self) -> Port where Self: Sized; fn close (self) -> Usually<()> where Self: Sized { let jack = self.jack().clone(); Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) } fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { self.with_client(|c|c.ports(re_name, re_type, flags)) } fn port_by_id (&self, id: u32) -> Option> { self.with_client(|c|c.port_by_id(id)) } fn port_by_name (&self, name: impl AsRef) -> Option> { self.with_client(|c|c.port_by_name(name.as_ref())) } fn connect_to_matching <'k> (&'k self) -> Usually<()> { for connect in self.connections().iter() { match &connect.name { Some(Exact(name)) => { *connect.status.write().unwrap() = self.connect_exact(name)?; }, Some(RegExp(re)) => { *connect.status.write().unwrap() = self.connect_regexp(re, connect.scope)?; }, _ => {}, }; } Ok(()) } fn connect_exact <'k> (&'k self, name: &str) -> Usually, Arc, ConnectStatus)>> { self.with_client(move|c|{ let mut status = vec![]; for port in c.ports(None, None, PortFlags::empty()).iter() { if port.as_str() == &*name { if let Some(port) = c.port_by_name(port.as_str()) { let port_status = self.connect_to_unowned(&port)?; let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected { break } } } } Ok(status) }) } fn connect_regexp <'k> ( &'k self, re: &str, scope: Option ) -> Usually, Arc, ConnectStatus)>> { self.with_client(move|c|{ let mut status = vec![]; let ports = c.ports(Some(&re), None, PortFlags::empty()); for port in ports.iter() { if let Some(port) = c.port_by_name(port.as_str()) { let port_status = self.connect_to_unowned(&port)?; let name = port.name()?.into(); status.push((port, name, port_status)); if port_status == Connected && scope == Some(One) { break } } } Ok(status) }) } /** Connect to a matching port by name. */ fn connect_to_name (&self, name: impl AsRef) -> Usually { self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { self.connect_to_unowned(port) } else { Ok(Missing) }) } /** Connect to a matching port by reference. */ fn connect_to_unowned (&self, port: &Port) -> Usually { self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { Connected } else if let Ok(_) = c.connect_ports(port, self.port()) { Connected } else { Mismatch })) } /** Connect to an owned matching port by reference. */ fn connect_to_owned (&self, port: &Port) -> Usually { self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { Connected } else if let Ok(_) = c.connect_ports(port, self.port()) { Connected } else { Mismatch })) } }