use impl_default, remove some feature annotations
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
okay stopped screaming 2026-02-23 23:10:57 +02:00
parent ab90129f4c
commit 4f5c332f48
3 changed files with 373 additions and 426 deletions

View file

@ -1,4 +1,50 @@
//pub fn track_counter (cache: &Arc<RwLock<Self>>, track: usize, tracks: usize)
//-> Arc<RwLock<String>>
//{
//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<RwLock<Self>>, scene: usize, scenes: usize, is_editing: bool)
//-> impl Content<TuiOut>
//{
//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<TuiOut> {
//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()));

View file

@ -16,20 +16,26 @@ impl <T: TracksView+ScenesView+Send+Sync> Clip
mod app {
use crate::*;
impl_has!(Clock: |self: App|self.project.clock);
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
impl_has!(Vec<MidiOutput>: |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<TuiOut>: |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<MidiInput>: |self: App|self.project.midi_ins);
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
impl_has!(Dialog: |self: App|self.dialog);
impl_has!(Jack<'static>: |self: App|self.jack);
impl_has!(Measure<TuiOut>: |self: App|self.size);
impl_has!(Pool: |self: App|self.pool);
impl_has!(Selection: |self: App|self.project.selection);
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
impl_as_mut!(Vec<Scene>: |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<Scene>: |self: App| self.project.as_ref());
impl_as_mut!(Vec<Scene>: |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<TuiOut> 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<TuiOut>: |self: Arrangement| self.size);
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
#[cfg(feature = "port")] impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
#[cfg(feature = "port")] impl_has!(Vec<MidiOutput>: |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<MidiInput> { todo!() }
#[cfg(feature = "port")] fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
fn selected_device (&self) -> Option<Device> { todo!() }
fn unselect (&self) -> Selection { Selection::Nothing }
}
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
impl_has!(Vec<MidiOutput>: |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<TuiOut> { &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<PathBuf>) -> Usually<Self> {
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> { 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> {
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<str> {
todo!()
}
fn _todo_stub_path_buf (&self) -> PathBuf { todo!() }
fn _todo_stub_usize (&self) -> usize { todo!() }
fn _todo_stub_arc_str (&self) -> Arc<str> { todo!() }
}
impl HasContent<TuiOut> for Browse {
fn content (&self) -> impl Content<TuiOut> {
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<Self::Item> {
@ -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<f64>) -> Usually<Self> {
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<Timebase> {
&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<u32>) -> 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<u32>) -> 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<f64>) -> Usually<Self> {
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<u32> {
todo!()
}
fn _todo_provide_f64 (&self) -> f64 {
todo!()
}
}
impl<T: HasClock> Command<T> for ClockCommand {
fn execute (&self, state: &mut T) -> Perhaps<Self> {
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<RwLock<Self>>, track: usize, tracks: usize)
//-> Arc<RwLock<String>>
//{
//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<RwLock<Self>>, scene: usize, scenes: usize, is_editing: bool)
//-> impl Content<TuiOut>
//{
//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<RwLock<Self>>, 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<Timebase> {
&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<u32>) -> 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<u32>) -> 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<TuiOut> {
//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<u32> {
todo!()
}
fn _todo_provide_f64 (&self) -> f64 {
todo!()
}
}
impl<T: HasClock> Command<T> for ClockCommand {
fn execute (&self, state: &mut T) -> Perhaps<Self> {
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<RwLock<Self>>, 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<C: Default> Default for Binding<C> {
}
}
}
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<bool> for AtomicBool { fn get (&self) -> bool { self.load(Relaxed) } }
impl InteriorMutable<bool> 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<MidiInput>: |self: Sequencer|self.midi_ins);
#[cfg(feature = "port")] impl_has!(Vec<MidiOutput>: |self: Sequencer|self.midi_outs);
impl_has!(Sequencer: |self: Track| self.sequencer);
impl_has!(Clock: |self: Sequencer| self.clock);
impl_has!(Vec<MidiInput>: |self: Sequencer| self.midi_ins);
impl_has!(Vec<MidiOutput>: |self: Sequencer| self.midi_outs);
impl_has!(Measure<TuiOut>: |self: MidiEditor| self.size);
impl_has!(Measure<TuiOut>: |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<str>,
@ -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)