diff --git a/app/.scratch.rs b/app/.scratch.rs index 1844b74f..6c98f399 100644 --- a/app/.scratch.rs +++ b/app/.scratch.rs @@ -1,4 +1,50 @@ + //pub fn track_counter (cache: &Arc>, track: usize, tracks: usize) + //-> Arc> + //{ + //let data = (track, tracks); + //cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); + //cache.read().unwrap().trks.view.clone() + //} + + //pub fn scene_add (cache: &Arc>, scene: usize, scenes: usize, is_editing: bool) + //-> impl Content + //{ + //let data = (scene, scenes); + //cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); + //button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing) + //} + + + + //pub fn view_h2 (&self) -> impl Content { + //let cache = self.project.clock.view_cache.clone(); + //let cache = cache.read().unwrap(); + //add(&Fixed::x(15, Align::w(Bsp::s( + //FieldH(theme, "Beat", cache.beat.view.clone()), + //FieldH(theme, "Time", cache.time.view.clone()), + //)))); + //add(&Fixed::x(13, Align::w(Bsp::s( + //Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))), + //Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))), + //)))); + //add(&Fixed::x(12, Align::w(Bsp::s( + //Fill::X(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))), + //Fill::X(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))), + //)))); + //add(&Bsp::s( + //Fill::X(Align::w(FieldH(theme, "Selected", Align::w(self.selection().describe( + //self.tracks(), + //self.scenes() + //))))), + //Fill::X(Align::w(FieldH(theme, format!("History ({})", self.history.len()), + //self.history.last().map(|last|Fill::X(Align::w(format!("{:?}", last.0))))))) + //)); + ////if let Some(last) = self.history.last() { + ////add(&FieldV(theme, format!("History ({})", self.history.len()), + ////Fill::X(Align::w(format!("{:?}", last.0))))); + ////} + //} /////////////////////////////////////////////////////////////////////////////////////////////////// //pub fn view_nil (_: &App) -> TuiCb { @@ -1137,3 +1183,6 @@ //cols //Thunk::new(|to: &mut TuiOut|{ //}) +//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); + diff --git a/app/tek_impls.rs b/app/tek_impls.rs index 71cca19c..563ce8ff 100644 --- a/app/tek_impls.rs +++ b/app/tek_impls.rs @@ -16,20 +16,26 @@ impl Clip mod app { use crate::*; - impl_has!(Clock: |self: App|self.project.clock); - impl_has!(Vec: |self: App|self.project.midi_ins); - impl_has!(Vec: |self: App|self.project.midi_outs); - impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt()); - impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt()); - impl_has!(Dialog: |self: App|self.dialog); - impl_has!(Jack<'static>: |self: App|self.jack); - impl_has!(Measure: |self: App|self.size); - impl_has!(Pool: |self: App|self.pool); - impl_has!(Selection: |self: App|self.project.selection); - has_clips!( |self: App|self.pool.clips); + impl_has!(Clock: |self: App|self.project.clock); + impl_has!(Vec: |self: App|self.project.midi_ins); + impl_has!(Vec: |self: App|self.project.midi_outs); + impl_has!(Dialog: |self: App|self.dialog); + impl_has!(Jack<'static>: |self: App|self.jack); + impl_has!(Measure: |self: App|self.size); + impl_has!(Pool: |self: App|self.pool); + impl_has!(Selection: |self: App|self.project.selection); + impl_as_ref!(Vec: |self: App|self.project.as_ref()); + impl_as_mut!(Vec: |self: App|self.project.as_mut()); + impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt()); + impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt()); + has_clips!( |self: App|self.pool.clips); impl_audio!(App: tek_jack_process, tek_jack_event); - impl_as_ref!(Vec: |self: App| self.project.as_ref()); - impl_as_mut!(Vec: |self: App| self.project.as_mut()); + handle!(TuiIn: |self: App, input|{ + let commands = collect_commands(self, input)?; + let history = execute_commands(self, commands)?; + self.history.extend(history.into_iter()); + Ok(None) + }); impl Draw for App { fn draw (&self, to: &mut TuiOut) { @@ -45,12 +51,6 @@ mod app { } } - handle!(TuiIn: |self: App, input|{ - let commands = collect_commands(self, input)?; - let history = execute_commands(self, commands)?; - self.history.extend(history.into_iter()); - Ok(None) - }); impl<'a> Namespace<'a, AppCommand> for App { symbols!('a |app| -> AppCommand { @@ -312,27 +312,18 @@ mod app { mod arrange { use crate::*; - impl_has!(Jack<'static>: |self: Arrangement| self.jack); impl_has!(Measure: |self: Arrangement| self.size); impl_has!(Vec: |self: Arrangement| self.tracks); impl_has!(Vec: |self: Arrangement| self.scenes); - #[cfg(feature = "port")] impl_has!(Vec: |self: Arrangement| self.midi_ins); - #[cfg(feature = "port")] impl_has!(Vec: |self: Arrangement| self.midi_outs); - #[cfg(feature = "clock")] impl_has!(Clock: |self: Arrangement| self.clock); - #[cfg(feature = "select")] impl_has!(Selection: |self: Arrangement| self.selection); - #[cfg(feature = "select")] impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref()); - #[cfg(feature = "select")] impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut()); - #[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track()); - #[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut()); - - #[cfg(feature = "select")] impl Arrangement { - #[cfg(feature = "port")] fn selected_midi_in (&self) -> Option { todo!() } - #[cfg(feature = "port")] fn selected_midi_out (&self) -> Option { todo!() } - fn selected_device (&self) -> Option { todo!() } - fn unselect (&self) -> Selection { Selection::Nothing } - } - + 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 Arrangement { /// Create a new arrangement. pub fn new ( @@ -551,8 +542,7 @@ mod arrange { self.selected_track_mut()?.sampler_mut(0) } } - - #[cfg(feature = "scene")] impl ScenesView for Arrangement { + impl ScenesView for Arrangement { fn h_scenes (&self) -> u16 { (self.measure_height() as u16).saturating_sub(20) } @@ -563,7 +553,6 @@ mod arrange { (self.measure_width() as u16).saturating_sub(2 * self.w_side()).max(40) } } - impl HasClipsSize for Arrangement { fn clips_size (&self) -> &Measure { &self.size_inner } } @@ -571,21 +560,7 @@ mod arrange { mod browse { use crate::*; - - impl PartialEq for BrowseTarget { - fn eq (&self, other: &Self) -> bool { - match self { - Self::ImportSample(_) => false, - Self::ExportSample(_) => false, - Self::ImportClip(_) => false, - Self::ExportClip(_) => false, - #[allow(unused)] t => matches!(other, t) - } - } - } - impl Browse { - pub fn new (cwd: Option) -> Usually { let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; let mut dirs = vec![]; @@ -603,19 +578,10 @@ mod browse { } Ok(Self { cwd, dirs, files, ..Default::default() }) } - - pub fn len (&self) -> usize { - self.dirs.len() + self.files.len() - } - - pub fn is_dir (&self) -> bool { - self.index < self.dirs.len() - } - - pub fn is_file (&self) -> bool { - self.index >= self.dirs.len() - } - + pub fn chdir (&self) -> Usually { Self::new(Some(self.path())) } + pub fn len (&self) -> usize { self.dirs.len() + self.files.len() } + pub fn is_dir (&self) -> bool { self.index < self.dirs.len() } + pub fn is_file (&self) -> bool { self.index >= self.dirs.len() } pub fn path (&self) -> PathBuf { self.cwd.join(if self.is_dir() { &self.dirs[self.index].0 @@ -625,22 +591,10 @@ mod browse { unreachable!() }) } - - pub fn chdir (&self) -> Usually { - Self::new(Some(self.path())) - } - - fn _todo_stub_path_buf (&self) -> PathBuf { - todo!() - } - fn _todo_stub_usize (&self) -> usize { - todo!() - } - fn _todo_stub_arc_str (&self) -> Arc { - todo!() - } + fn _todo_stub_path_buf (&self) -> PathBuf { todo!() } + fn _todo_stub_usize (&self) -> usize { todo!() } + fn _todo_stub_arc_str (&self) -> Arc { todo!() } } - impl HasContent for Browse { fn content (&self) -> impl Content { Map::south(1, ||EntriesIterator { @@ -651,7 +605,6 @@ mod browse { }, |entry, _index|Fill::X(Align::w(entry))) } } - impl<'a> Iterator for EntriesIterator<'a> { type Item = Modify<&'a str>; fn next (&mut self) -> Option { @@ -669,285 +622,261 @@ mod browse { } } } -} -//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); - - -impl std::fmt::Debug for Clock { - fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("Clock") - .field("timebase", &self.timebase) - .field("chunk", &self.chunk) - .field("quant", &self.quant) - .field("sync", &self.sync) - .field("global", &self.global) - .field("playhead", &self.playhead) - .field("started", &self.started) - .finish() + impl PartialEq for BrowseTarget { + fn eq (&self, other: &Self) -> bool { + match self { + Self::ImportSample(_) => false, + Self::ExportSample(_) => false, + Self::ImportClip(_) => false, + Self::ExportClip(_) => false, + #[allow(unused)] t => matches!(other, t) + } + } } } -impl Clock { - pub fn new (jack: &Jack<'static>, bpm: Option) -> Usually { - let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); - let timebase = Arc::new(Timebase::default()); - let clock = Self { - quant: Arc::new(24.into()), - sync: Arc::new(384.into()), - transport: Arc::new(Some(transport)), - chunk: Arc::new((chunk as usize).into()), - global: Arc::new(Moment::zero(&timebase)), - playhead: Arc::new(Moment::zero(&timebase)), - offset: Arc::new(Moment::zero(&timebase)), - started: RwLock::new(None).into(), - timebase, - midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))), - midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))), - click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))), - ..Default::default() - }; - if let Some(bpm) = bpm { - clock.timebase.bpm.set(bpm); - } - Ok(clock) - } - pub fn timebase (&self) -> &Arc { - &self.timebase - } - /// Current sample rate - pub fn sr (&self) -> &SampleRate { - &self.timebase.sr - } - /// Current tempo - pub fn bpm (&self) -> &Bpm { - &self.timebase.bpm - } - /// Current MIDI resolution - pub fn ppq (&self) -> &Ppq { - &self.timebase.ppq - } - /// Next pulse that matches launch sync (for phrase switchover) - pub fn next_launch_pulse (&self) -> usize { - let sync = self.sync.get() as usize; - let pulse = self.playhead.pulse.get() as usize; - if pulse % sync == 0 { - pulse - } else { - (pulse / sync + 1) * sync +mod clock { + use crate::*; + impl std::fmt::Debug for Clock { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Clock") + .field("timebase", &self.timebase) + .field("chunk", &self.chunk) + .field("quant", &self.quant) + .field("sync", &self.sync) + .field("global", &self.global) + .field("playhead", &self.playhead) + .field("started", &self.started) + .finish() } } - /// Start playing, optionally seeking to a given location beforehand - pub fn play_from (&self, start: Option) -> Usually<()> { - if let Some(transport) = self.transport.as_ref() { - if let Some(start) = start { - transport.locate(start)?; - } - transport.start()?; - } - Ok(()) - } - /// Pause, optionally seeking to a given location afterwards - pub fn pause_at (&self, pause: Option) -> Usually<()> { - if let Some(transport) = self.transport.as_ref() { - transport.stop()?; - if let Some(pause) = pause { - transport.locate(pause)?; - } - } - Ok(()) - } - /// Is currently paused? - pub fn is_stopped (&self) -> bool { - self.started.read().unwrap().is_none() - } - /// Is currently playing? - pub fn is_rolling (&self) -> bool { - self.started.read().unwrap().is_some() - } - /// Update chunk size - pub fn set_chunk (&self, n_frames: usize) { - self.chunk.store(n_frames, Relaxed); - } - pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { - // Store buffer length - self.set_chunk(scope.n_frames() as usize); - - // Store reported global frame and usec - let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; - self.global.sample.set(current_frames as f64); - self.global.usec.set(current_usecs as f64); - - let mut started = self.started.write().unwrap(); - - // If transport has just started or just stopped, - // update starting point: - if let Some(transport) = self.transport.as_ref() { - match (transport.query_state()?, started.as_ref()) { - (TransportState::Rolling, None) => { - let moment = Moment::zero(&self.timebase); - moment.sample.set(current_frames as f64); - moment.usec.set(current_usecs as f64); - *started = Some(moment); - }, - (TransportState::Stopped, Some(_)) => { - *started = None; - }, - _ => {} + impl Clock { + pub fn new (jack: &Jack<'static>, bpm: Option) -> Usually { + let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); + let timebase = Arc::new(Timebase::default()); + let clock = Self { + quant: Arc::new(24.into()), + sync: Arc::new(384.into()), + transport: Arc::new(Some(transport)), + chunk: Arc::new((chunk as usize).into()), + global: Arc::new(Moment::zero(&timebase)), + playhead: Arc::new(Moment::zero(&timebase)), + offset: Arc::new(Moment::zero(&timebase)), + started: RwLock::new(None).into(), + timebase, + midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))), + midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))), + click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))), + ..Default::default() }; - } - - self.playhead.update_from_sample(started.as_ref() - .map(|started|current_frames as f64 - started.sample.get()) - .unwrap_or(0.)); - - Ok(()) - } - - pub fn bbt (&self) -> PositionBBT { - let pulse = self.playhead.pulse.get() as i32; - let ppq = self.timebase.ppq.get() as i32; - let bpm = self.timebase.bpm.get(); - let bar = (pulse / ppq) / 4; - PositionBBT { - bar: 1 + bar, - beat: 1 + (pulse / ppq) % 4, - tick: (pulse % ppq), - bar_start_tick: (bar * 4 * ppq) as f64, - beat_type: 4., - beats_per_bar: 4., - beats_per_minute: bpm, - ticks_per_beat: ppq as f64 - } - } - - pub fn next_launch_instant (&self) -> Moment { - Moment::from_pulse(self.timebase(), self.next_launch_pulse() as f64) - } - - /// Get index of first sample to populate. - /// - /// Greater than 0 means that the first pulse of the clip - /// falls somewhere in the middle of the chunk. - pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{ - (scope.last_frame_time() as usize).saturating_sub( - started.sample.get() as usize + - self.started.read().unwrap().as_ref().unwrap().sample.get() as usize - ) - } - - // Get iterator that emits sample paired with pulse. - // - // * Sample: index into output buffer at which to write MIDI event - // * Pulse: index into clip from which to take the MIDI event - // - // Emitted for each sample of the output buffer that corresponds to a MIDI pulse. - pub fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> Ticker { - self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize) - } -} - -impl Clock { - fn _todo_provide_u32 (&self) -> u32 { - todo!() - } - fn _todo_provide_opt_u32 (&self) -> Option { - todo!() - } - fn _todo_provide_f64 (&self) -> f64 { - todo!() - } -} - -impl Command for ClockCommand { - fn execute (&self, state: &mut T) -> Perhaps { - self.execute(state.clock_mut()) // awesome - } -} - - -impl ClockView { - pub const BEAT_EMPTY: &'static str = "-.-.--"; - pub const TIME_EMPTY: &'static str = "-.---s"; - pub const BPM_EMPTY: &'static str = "---.---"; - - //pub fn track_counter (cache: &Arc>, track: usize, tracks: usize) - //-> Arc> - //{ - //let data = (track, tracks); - //cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); - //cache.read().unwrap().trks.view.clone() - //} - - //pub fn scene_add (cache: &Arc>, scene: usize, scenes: usize, is_editing: bool) - //-> impl Content - //{ - //let data = (scene, scenes); - //cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); - //button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing) - //} - - pub fn update_clock (cache: &Arc>, clock: &Clock, compact: bool) { - let rate = clock.timebase.sr.get(); - let chunk = clock.chunk.load(Relaxed) as f64; - let lat = chunk / rate * 1000.; - let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); - let mut cache = cache.write().unwrap(); - cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); - cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); - cache.sr.update(Some((compact, rate)), |buf,_,_|{ - buf.clear(); - if compact { - write!(buf, "{:.1}kHz", rate / 1000.) - } else { - write!(buf, "{:.0}Hz", rate) + if let Some(bpm) = bpm { + clock.timebase.bpm.set(bpm); } - }); - if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { - let pulse = clock.timebase.usecs_to_pulse(now); - let time = now/1000000.; - let bpm = clock.timebase.bpm.get(); - cache.beat.update(Some(pulse), |buf, _, _|{ - buf.clear(); - clock.timebase.format_beats_1_to(buf, pulse) - }); - cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); - cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); - } else { - cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY)); - cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY)); - cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY)); + Ok(clock) + } + pub fn timebase (&self) -> &Arc { + &self.timebase + } + /// Current sample rate + pub fn sr (&self) -> &SampleRate { + &self.timebase.sr + } + /// Current tempo + pub fn bpm (&self) -> &Bpm { + &self.timebase.bpm + } + /// Current MIDI resolution + pub fn ppq (&self) -> &Ppq { + &self.timebase.ppq + } + /// Next pulse that matches launch sync (for phrase switchover) + pub fn next_launch_pulse (&self) -> usize { + let sync = self.sync.get() as usize; + let pulse = self.playhead.pulse.get() as usize; + if pulse % sync == 0 { + pulse + } else { + (pulse / sync + 1) * sync + } + } + /// Start playing, optionally seeking to a given location beforehand + pub fn play_from (&self, start: Option) -> Usually<()> { + if let Some(transport) = self.transport.as_ref() { + if let Some(start) = start { + transport.locate(start)?; + } + transport.start()?; + } + Ok(()) + } + /// Pause, optionally seeking to a given location afterwards + pub fn pause_at (&self, pause: Option) -> Usually<()> { + if let Some(transport) = self.transport.as_ref() { + transport.stop()?; + if let Some(pause) = pause { + transport.locate(pause)?; + } + } + Ok(()) + } + /// Is currently paused? + pub fn is_stopped (&self) -> bool { + self.started.read().unwrap().is_none() + } + /// Is currently playing? + pub fn is_rolling (&self) -> bool { + self.started.read().unwrap().is_some() + } + /// Update chunk size + pub fn set_chunk (&self, n_frames: usize) { + self.chunk.store(n_frames, Relaxed); + } + pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { + // Store buffer length + self.set_chunk(scope.n_frames() as usize); + + // Store reported global frame and usec + let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; + self.global.sample.set(current_frames as f64); + self.global.usec.set(current_usecs as f64); + + let mut started = self.started.write().unwrap(); + + // If transport has just started or just stopped, + // update starting point: + if let Some(transport) = self.transport.as_ref() { + match (transport.query_state()?, started.as_ref()) { + (TransportState::Rolling, None) => { + let moment = Moment::zero(&self.timebase); + moment.sample.set(current_frames as f64); + moment.usec.set(current_usecs as f64); + *started = Some(moment); + }, + (TransportState::Stopped, Some(_)) => { + *started = None; + }, + _ => {} + }; + } + + self.playhead.update_from_sample(started.as_ref() + .map(|started|current_frames as f64 - started.sample.get()) + .unwrap_or(0.)); + + Ok(()) + } + + pub fn bbt (&self) -> PositionBBT { + let pulse = self.playhead.pulse.get() as i32; + let ppq = self.timebase.ppq.get() as i32; + let bpm = self.timebase.bpm.get(); + let bar = (pulse / ppq) / 4; + PositionBBT { + bar: 1 + bar, + beat: 1 + (pulse / ppq) % 4, + tick: (pulse % ppq), + bar_start_tick: (bar * 4 * ppq) as f64, + beat_type: 4., + beats_per_bar: 4., + beats_per_minute: bpm, + ticks_per_beat: ppq as f64 + } + } + + pub fn next_launch_instant (&self) -> Moment { + Moment::from_pulse(self.timebase(), self.next_launch_pulse() as f64) + } + + /// Get index of first sample to populate. + /// + /// Greater than 0 means that the first pulse of the clip + /// falls somewhere in the middle of the chunk. + pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{ + (scope.last_frame_time() as usize).saturating_sub( + started.sample.get() as usize + + self.started.read().unwrap().as_ref().unwrap().sample.get() as usize + ) + } + + // Get iterator that emits sample paired with pulse. + // + // * Sample: index into output buffer at which to write MIDI event + // * Pulse: index into clip from which to take the MIDI event + // + // Emitted for each sample of the output buffer that corresponds to a MIDI pulse. + pub fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> Ticker { + self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize) } } - - //pub fn view_h2 (&self) -> impl Content { - //let cache = self.project.clock.view_cache.clone(); - //let cache = cache.read().unwrap(); - //add(&Fixed::x(15, Align::w(Bsp::s( - //FieldH(theme, "Beat", cache.beat.view.clone()), - //FieldH(theme, "Time", cache.time.view.clone()), - //)))); - //add(&Fixed::x(13, Align::w(Bsp::s( - //Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))), - //Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))), - //)))); - //add(&Fixed::x(12, Align::w(Bsp::s( - //Fill::X(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))), - //Fill::X(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))), - //)))); - //add(&Bsp::s( - //Fill::X(Align::w(FieldH(theme, "Selected", Align::w(self.selection().describe( - //self.tracks(), - //self.scenes() - //))))), - //Fill::X(Align::w(FieldH(theme, format!("History ({})", self.history.len()), - //self.history.last().map(|last|Fill::X(Align::w(format!("{:?}", last.0))))))) - //)); - ////if let Some(last) = self.history.last() { - ////add(&FieldV(theme, format!("History ({})", self.history.len()), - ////Fill::X(Align::w(format!("{:?}", last.0))))); - ////} - //} + impl Clock { + fn _todo_provide_u32 (&self) -> u32 { + todo!() + } + fn _todo_provide_opt_u32 (&self) -> Option { + todo!() + } + fn _todo_provide_f64 (&self) -> f64 { + todo!() + } + } + impl Command for ClockCommand { + fn execute (&self, state: &mut T) -> Perhaps { + self.execute(state.clock_mut()) // awesome + } + } + impl ClockView { + pub const BEAT_EMPTY: &'static str = "-.-.--"; + pub const TIME_EMPTY: &'static str = "-.---s"; + pub const BPM_EMPTY: &'static str = "---.---"; + pub fn update_clock (cache: &Arc>, clock: &Clock, compact: bool) { + let rate = clock.timebase.sr.get(); + let chunk = clock.chunk.load(Relaxed) as f64; + let lat = chunk / rate * 1000.; + let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); + let mut cache = cache.write().unwrap(); + cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); + cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); + cache.sr.update(Some((compact, rate)), |buf,_,_|{ + buf.clear(); + if compact { + write!(buf, "{:.1}kHz", rate / 1000.) + } else { + write!(buf, "{:.0}Hz", rate) + } + }); + if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { + let pulse = clock.timebase.usecs_to_pulse(now); + let time = now/1000000.; + let bpm = clock.timebase.bpm.get(); + cache.beat.update(Some(pulse), |buf, _, _|{ + buf.clear(); + clock.timebase.format_beats_1_to(buf, pulse) + }); + cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); + cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); + } else { + cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY)); + cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY)); + cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY)); + } + } + } + impl_default!(ClockView: { + let mut beat = String::with_capacity(16); + let _ = write!(beat, "{}", Self::BEAT_EMPTY); + let mut time = String::with_capacity(16); + let _ = write!(time, "{}", Self::TIME_EMPTY); + let mut bpm = String::with_capacity(16); + let _ = write!(bpm, "{}", Self::BPM_EMPTY); + Self { + beat: Memo::new(None, beat), + time: Memo::new(None, time), + bpm: Memo::new(None, bpm), + sr: Memo::new(None, String::with_capacity(16)), + buf: Memo::new(None, String::with_capacity(16)), + lat: Memo::new(None, String::with_capacity(16)), + } + }); } impl Connect { @@ -1114,55 +1043,10 @@ impl Default for Binding { } } } -impl Default for AppCommand { fn default () -> Self { Self::Nop } } -impl Default for MenuItem { fn default () -> Self { Self("".into(), Arc::new(Box::new(|_|Ok(())))) } } -impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } } -impl Default for MidiEditor { fn default () -> Self { Self { size: Measure::new(0, 0), mode: PianoHorizontal::new(None) } } } -impl Default for OctaveVertical { - fn default () -> Self { - Self { on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] } - } -} -impl Default for MidiCursor { - fn default () -> Self { - Self { - time_pos: Arc::new(0.into()), - note_pos: Arc::new(36.into()), - note_len: Arc::new(24.into()), - } - } -} -impl Default for ClockView { - fn default () -> Self { - let mut beat = String::with_capacity(16); - let _ = write!(beat, "{}", Self::BEAT_EMPTY); - let mut time = String::with_capacity(16); - let _ = write!(time, "{}", Self::TIME_EMPTY); - let mut bpm = String::with_capacity(16); - let _ = write!(bpm, "{}", Self::BPM_EMPTY); - Self { - beat: Memo::new(None, beat), - time: Memo::new(None, time), - bpm: Memo::new(None, bpm), - sr: Memo::new(None, String::with_capacity(16)), - buf: Memo::new(None, String::with_capacity(16)), - lat: Memo::new(None, String::with_capacity(16)), - } - } -} -impl Default for Pool { - fn default () -> Self { - //use PoolMode::*; - Self { - clip: 0.into(), - mode: None, - visible: true, - #[cfg(feature = "clip")] clips: Arc::from(RwLock::from(vec![])), - #[cfg(feature = "sampler")] samples: Arc::from(RwLock::from(vec![])), - #[cfg(feature = "browse")] browse: None, - } - } -} + +impl_default!(AppCommand: Self::Nop); +impl_default!(MenuItem: Self("".into(), Arc::new(Box::new(|_|Ok(()))))); +impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ)); impl Gettable for AtomicBool { fn get (&self) -> bool { self.load(Relaxed) } } impl InteriorMutable for AtomicBool { fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } } @@ -1549,6 +1433,11 @@ mod midi { time_zoom: Arc::new(data.0.into()), time_lock: Arc::new(data.1.into()), }); + impl_default!(MidiCursor: Self { + time_pos: Arc::new(0.into()), + note_pos: Arc::new(36.into()), + note_len: Arc::new(24.into()), + }); impl NotePoint for MidiCursor { fn note_len (&self) -> &AtomicUsize { @@ -2103,32 +1992,27 @@ mod audio { #[cfg(feature = "sequencer")] mod sequencer { use crate::*; - impl_has!(Sequencer: |self: Track| self.sequencer); - #[cfg(feature = "clock")] impl_has!(Clock: |self: Sequencer|self.clock); - #[cfg(feature = "port")] impl_has!(Vec: |self: Sequencer|self.midi_ins); - #[cfg(feature = "port")] impl_has!(Vec: |self: Sequencer|self.midi_outs); + impl_has!(Sequencer: |self: Track| self.sequencer); + impl_has!(Clock: |self: Sequencer| self.clock); + impl_has!(Vec: |self: Sequencer| self.midi_ins); + impl_has!(Vec: |self: Sequencer| self.midi_outs); impl_has!(Measure: |self: MidiEditor| self.size); impl_has!(Measure: |self: PianoHorizontal| self.size); - impl Default for Sequencer { - fn default () -> Self { - Self { - #[cfg(feature = "clock")] clock: Clock::default(), - #[cfg(feature = "clip")] play_clip: None, - #[cfg(feature = "clip")] next_clip: None, - #[cfg(feature = "port")] midi_ins: vec![], - #[cfg(feature = "port")] midi_outs: vec![], - - recording: false, - monitoring: true, - overdub: false, - notes_in: RwLock::new([false;128]).into(), - notes_out: RwLock::new([false;128]).into(), - note_buf: vec![0;8], - midi_buf: vec![], - reset: true, - } - } - } + impl_default!(Sequencer: Self { + clock: Clock::default(), + play_clip: None, + next_clip: None, + midi_ins: vec![], + midi_outs: vec![], + recording: false, + monitoring: true, + overdub: false, + notes_in: RwLock::new([false;128]).into(), + notes_out: RwLock::new([false;128]).into(), + note_buf: vec![0;8], + midi_buf: vec![], + reset: true, + }); impl Sequencer { pub fn new ( name: impl AsRef, @@ -2355,6 +2239,12 @@ mod audio { model.redraw(); model }); + impl_default!(MidiEditor: Self { + size: Measure::new(0, 0), mode: PianoHorizontal::new(None) + }); + impl_default!(OctaveVertical: Self { + on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] + }); impl MidiEditor { /// Put note at current position pub fn put_note (&mut self, advance: bool) { @@ -3494,6 +3384,14 @@ mod pool { model.clip.store(1, Relaxed); model }); + impl_default!(Pool: Self { + browse: None, + clip: 0.into(), + clips: Arc::from(RwLock::from(vec![])), + mode: None, + samples: Arc::from(RwLock::from(vec![])), + visible: true, + }); impl Pool { pub fn clip_index (&self) -> usize { self.clip.load(Relaxed) diff --git a/tengri b/tengri index d1c08df5..f1dda6af 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit d1c08df5351ce8c3913723602a05268d593c9a45 +Subproject commit f1dda6af07b94928481d062c3d3fda5b9e969633