update clock model

This commit is contained in:
🪞👃🪞 2024-11-28 17:39:07 +01:00
parent 86649ef994
commit 54057afad8
5 changed files with 66 additions and 74 deletions

View file

@ -22,9 +22,9 @@ impl<T: HasClock> Command<T> for ClockCommand {
match self {
Play(start) => state.clock().play_from(start)?,
Pause(pause) => state.clock().pause_at(pause)?,
SeekUsec(usec) => state.clock().current.update_from_usec(usec),
SeekSample(sample) => state.clock().current.update_from_sample(sample),
SeekPulse(pulse) => state.clock().current.update_from_pulse(pulse),
SeekUsec(usec) => state.clock().playhead.update_from_usec(usec),
SeekSample(sample) => state.clock().playhead.update_from_sample(sample),
SeekPulse(pulse) => state.clock().playhead.update_from_pulse(pulse),
SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))),
SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))),
SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))),
@ -37,12 +37,14 @@ impl<T: HasClock> Command<T> for ClockCommand {
pub struct ClockModel {
/// JACK transport handle.
pub transport: Arc<Transport>,
/// Playback state
pub playing: Arc<RwLock<Option<TransportState>>>,
/// Global temporal resolution (shared by [Instant] fields)
pub timebase: Arc<Timebase>,
/// Current global sample and usec (monotonic from JACK clock)
pub global: Arc<Instant>,
/// Global sample and usec at which playback started
pub started: Arc<RwLock<Option<(usize, usize)>>>,
/// Current moment in time
pub current: Arc<Instant>,
pub started: Arc<RwLock<Option<Instant>>>,
/// Current playhead position
pub playhead: Arc<Instant>,
/// Note quantization factor
pub quant: Arc<Quantize>,
/// Launch quantization factor
@ -56,14 +58,16 @@ impl From<&Arc<RwLock<JackClient>>> for ClockModel {
let jack = jack.read().unwrap();
let chunk = jack.client().buffer_size();
let transport = jack.client().transport();
let timebase = Arc::new(Timebase::default());
Self {
playing: RwLock::new(None).into(),
started: RwLock::new(None).into(),
current: Instant::default().into(),
quant: Arc::new(24.into()),
sync: Arc::new(384.into()),
transport: Arc::new(transport),
chunk: Arc::new((chunk as usize).into()),
global: Arc::new(Instant::zero(&timebase)),
playhead: Arc::new(Instant::zero(&timebase)),
started: RwLock::new(None).into(),
timebase,
}
}
}
@ -71,35 +75,37 @@ impl From<&Arc<RwLock<JackClient>>> for ClockModel {
impl std::fmt::Debug for ClockModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("ClockModel")
.field("playing", &self.playing)
.field("started", &self.started)
.field("current", &self.current)
.field("quant", &self.quant)
.field("sync", &self.sync)
.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 ClockModel {
pub fn timebase (&self) -> &Arc<Timebase> {
&self.current.timebase
&self.timebase
}
/// Current sample rate
pub fn sr (&self) -> &SampleRate {
&self.timebase().sr
&self.timebase.sr
}
/// Current tempo
pub fn bpm (&self) -> &BeatsPerMinute {
&self.timebase().bpm
&self.timebase.bpm
}
/// Current MIDI resolution
pub fn ppq (&self) -> &PulsesPerQuaver {
&self.timebase().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.current.pulse.get() as usize;
let pulse = self.playhead.pulse.get() as usize;
if pulse % sync == 0 {
pulse
} else {
@ -112,7 +118,7 @@ impl ClockModel {
self.transport.locate(start)?;
}
self.transport.start()?;
self.update_transport_state()
Ok(())
}
/// Pause, optionally seeking to a given location afterwards
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
@ -120,20 +126,42 @@ impl ClockModel {
if let Some(pause) = pause {
self.transport.locate(pause)?;
}
self.update_transport_state()
}
/// Update the state according to JACK transport
pub fn update_transport_state (&self) -> Usually<()> {
*self.playing.write().unwrap() = Some(self.transport.query_state()?);
Ok(())
}
/// Is currently paused?
pub fn is_stopped (&self) -> bool {
*self.playing.read().unwrap() == Some(TransportState::Stopped)
self.started.read().unwrap().is_none()
}
/// Is currently playing?
pub fn is_rolling (&self) -> bool {
*self.playing.read().unwrap() == Some(TransportState::Rolling)
self.started.read().unwrap().is_some()
}
/// Update chunk size
pub fn set_chunk (&self, n_frames: usize) {
self.chunk.store(n_frames, Ordering::Relaxed);
}
pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> {
self.set_chunk(scope.n_frames() as usize);
let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?;
let mut started = self.started.write().unwrap();
match self.transport.query_state()? {
TransportState::Rolling => {
if started.is_none() {
*started = Some(Instant::from_sample(&self.timebase, current_frames as f64));
}
},
TransportState::Stopped => {
if started.is_some() {
*started = None;
}
},
_ => {}
};
self.playhead.update_from_sample(match *started {
Some(ref instant) => current_frames as f64 - instant.sample.get(),
None => 0.
});
Ok(())
}
}
@ -142,43 +170,7 @@ pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T);
impl<'a, T: HasClock> Audio for ClockAudio<'a, T> {
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let state = self.0.clock();
// Update chunk size
state.chunk.store(scope.n_frames() as usize, Ordering::Relaxed);
// Query transport state
let transport = state.transport.query().unwrap();
// FIXME duplicated
state.current.sample.set(transport.pos.frame() as f64);
// Update play/pause state and starting point
let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times().unwrap();
let mut playing = state.playing.write().unwrap();
let mut started = state.started.write().unwrap();
if *playing != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
*started = Some((current_frames as usize, current_usecs as usize))
},
TransportState::Stopped => {
*started = None
},
_ => {}
}
};
*playing = Some(transport.state);
if *playing == Some(TransportState::Stopped) {
*started = None;
}
// FIXME duplicated
state.current.update_from_usec(match *started {
Some((_, usecs)) => current_usecs as f64 - usecs as f64,
None => 0.
});
self.0.clock().update_from_scope(scope).unwrap();
Control::Continue
}
}

View file

@ -16,7 +16,7 @@ pub trait HasPlayPhrase: HasClock {
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
Some(self.clock().current.pulse.get() - started.pulse.get())
Some(self.clock().playhead.pulse.get() - started.pulse.get())
} else {
None
}
@ -141,7 +141,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
// First sample to populate. Greater than 0 means that the first
// pulse of the phrase falls somewhere in the middle of the chunk.
let sample = started.sample.get() as usize;
let sample = sample + started0.read().unwrap().unwrap().0;
let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize;
let sample = sample0.saturating_sub(sample);
// Iterator that emits sample (index into output buffer at which to write MIDI event)
// paired with pulse (index into phrase from which to take the MIDI event) for each
@ -200,7 +200,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
//let samples = scope.n_frames() as usize;
if let Some((start_at, phrase)) = &self.next_phrase() {
let start = start_at.sample.get() as usize;
let sample = self.clock().started.read().unwrap().unwrap().0;
let sample = self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize;
// If it's time to switch to the next phrase:
if start <= sample0.saturating_sub(sample) {
// Samples elapsed since phrase was supposed to start

View file

@ -131,7 +131,7 @@ impl From<&SequencerTui> for SequencerStatusBar {
use SequencerFocus::*;
use TransportFocus::*;
let samples = state.clock.chunk.load(Ordering::Relaxed);
let rate = state.clock.current.timebase.sr.get() as f64;
let rate = state.clock.timebase.sr.get() as f64;
let buffer = samples as f64 / rate;
let default_help = &[("", "", " enter"), ("", "", " navigate")];
Self {

View file

@ -92,7 +92,7 @@ pub fn arranger_content_vertical (
factor: usize
) -> impl Widget<Engine = Tui> + use<'_> {
let timebase = view.clock().timebase();
let current = &view.clock().current;
let current = &view.clock().playhead;
let tracks = view.tracks();
let scenes = view.scenes();
let cols = track_widths(tracks);

View file

@ -60,12 +60,12 @@ impl<'a, T: HasClock> From<&'a T> for TransportView where Option<TransportFocus>
Self {
selected,
focused: selected.is_some(),
state: state.clock().playing.read().unwrap().clone(),
state: Some(state.clock().transport.query_state().unwrap()),
bpm: state.clock().bpm().get(),
sync: state.clock().sync.get(),
quant: state.clock().quant.get(),
beat: state.clock().current.format_beat(),
msu: state.clock().current.usec.format_msu(),
beat: state.clock().playhead.format_beat(),
msu: state.clock().playhead.usec.format_msu(),
}
}
}