mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
update clock model
This commit is contained in:
parent
86649ef994
commit
54057afad8
5 changed files with 66 additions and 74 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue