mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 04:36: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 {
|
match self {
|
||||||
Play(start) => state.clock().play_from(start)?,
|
Play(start) => state.clock().play_from(start)?,
|
||||||
Pause(pause) => state.clock().pause_at(pause)?,
|
Pause(pause) => state.clock().pause_at(pause)?,
|
||||||
SeekUsec(usec) => state.clock().current.update_from_usec(usec),
|
SeekUsec(usec) => state.clock().playhead.update_from_usec(usec),
|
||||||
SeekSample(sample) => state.clock().current.update_from_sample(sample),
|
SeekSample(sample) => state.clock().playhead.update_from_sample(sample),
|
||||||
SeekPulse(pulse) => state.clock().current.update_from_pulse(pulse),
|
SeekPulse(pulse) => state.clock().playhead.update_from_pulse(pulse),
|
||||||
SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))),
|
SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))),
|
||||||
SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))),
|
SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))),
|
||||||
SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))),
|
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 {
|
pub struct ClockModel {
|
||||||
/// JACK transport handle.
|
/// JACK transport handle.
|
||||||
pub transport: Arc<Transport>,
|
pub transport: Arc<Transport>,
|
||||||
/// Playback state
|
/// Global temporal resolution (shared by [Instant] fields)
|
||||||
pub playing: Arc<RwLock<Option<TransportState>>>,
|
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
|
/// Global sample and usec at which playback started
|
||||||
pub started: Arc<RwLock<Option<(usize, usize)>>>,
|
pub started: Arc<RwLock<Option<Instant>>>,
|
||||||
/// Current moment in time
|
/// Current playhead position
|
||||||
pub current: Arc<Instant>,
|
pub playhead: Arc<Instant>,
|
||||||
/// Note quantization factor
|
/// Note quantization factor
|
||||||
pub quant: Arc<Quantize>,
|
pub quant: Arc<Quantize>,
|
||||||
/// Launch quantization factor
|
/// Launch quantization factor
|
||||||
|
|
@ -56,14 +58,16 @@ impl From<&Arc<RwLock<JackClient>>> for ClockModel {
|
||||||
let jack = jack.read().unwrap();
|
let jack = jack.read().unwrap();
|
||||||
let chunk = jack.client().buffer_size();
|
let chunk = jack.client().buffer_size();
|
||||||
let transport = jack.client().transport();
|
let transport = jack.client().transport();
|
||||||
|
let timebase = Arc::new(Timebase::default());
|
||||||
Self {
|
Self {
|
||||||
playing: RwLock::new(None).into(),
|
|
||||||
started: RwLock::new(None).into(),
|
|
||||||
current: Instant::default().into(),
|
|
||||||
quant: Arc::new(24.into()),
|
quant: Arc::new(24.into()),
|
||||||
sync: Arc::new(384.into()),
|
sync: Arc::new(384.into()),
|
||||||
transport: Arc::new(transport),
|
transport: Arc::new(transport),
|
||||||
chunk: Arc::new((chunk as usize).into()),
|
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 {
|
impl std::fmt::Debug for ClockModel {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
f.debug_struct("ClockModel")
|
f.debug_struct("ClockModel")
|
||||||
.field("playing", &self.playing)
|
.field("timebase", &self.timebase)
|
||||||
.field("started", &self.started)
|
.field("chunk", &self.chunk)
|
||||||
.field("current", &self.current)
|
.field("quant", &self.quant)
|
||||||
.field("quant", &self.quant)
|
.field("sync", &self.sync)
|
||||||
.field("sync", &self.sync)
|
.field("global", &self.global)
|
||||||
|
.field("playhead", &self.playhead)
|
||||||
|
.field("started", &self.started)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClockModel {
|
impl ClockModel {
|
||||||
pub fn timebase (&self) -> &Arc<Timebase> {
|
pub fn timebase (&self) -> &Arc<Timebase> {
|
||||||
&self.current.timebase
|
&self.timebase
|
||||||
}
|
}
|
||||||
/// Current sample rate
|
/// Current sample rate
|
||||||
pub fn sr (&self) -> &SampleRate {
|
pub fn sr (&self) -> &SampleRate {
|
||||||
&self.timebase().sr
|
&self.timebase.sr
|
||||||
}
|
}
|
||||||
/// Current tempo
|
/// Current tempo
|
||||||
pub fn bpm (&self) -> &BeatsPerMinute {
|
pub fn bpm (&self) -> &BeatsPerMinute {
|
||||||
&self.timebase().bpm
|
&self.timebase.bpm
|
||||||
}
|
}
|
||||||
/// Current MIDI resolution
|
/// Current MIDI resolution
|
||||||
pub fn ppq (&self) -> &PulsesPerQuaver {
|
pub fn ppq (&self) -> &PulsesPerQuaver {
|
||||||
&self.timebase().ppq
|
&self.timebase.ppq
|
||||||
}
|
}
|
||||||
/// Next pulse that matches launch sync (for phrase switchover)
|
/// Next pulse that matches launch sync (for phrase switchover)
|
||||||
pub fn next_launch_pulse (&self) -> usize {
|
pub fn next_launch_pulse (&self) -> usize {
|
||||||
let sync = self.sync.get() as 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 {
|
if pulse % sync == 0 {
|
||||||
pulse
|
pulse
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -112,7 +118,7 @@ impl ClockModel {
|
||||||
self.transport.locate(start)?;
|
self.transport.locate(start)?;
|
||||||
}
|
}
|
||||||
self.transport.start()?;
|
self.transport.start()?;
|
||||||
self.update_transport_state()
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Pause, optionally seeking to a given location afterwards
|
/// Pause, optionally seeking to a given location afterwards
|
||||||
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
||||||
|
|
@ -120,20 +126,42 @@ impl ClockModel {
|
||||||
if let Some(pause) = pause {
|
if let Some(pause) = pause {
|
||||||
self.transport.locate(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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Is currently paused?
|
/// Is currently paused?
|
||||||
pub fn is_stopped (&self) -> bool {
|
pub fn is_stopped (&self) -> bool {
|
||||||
*self.playing.read().unwrap() == Some(TransportState::Stopped)
|
self.started.read().unwrap().is_none()
|
||||||
}
|
}
|
||||||
/// Is currently playing?
|
/// Is currently playing?
|
||||||
pub fn is_rolling (&self) -> bool {
|
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> {
|
impl<'a, T: HasClock> Audio for ClockAudio<'a, T> {
|
||||||
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
let state = self.0.clock();
|
self.0.clock().update_from_scope(scope).unwrap();
|
||||||
|
|
||||||
// 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.
|
|
||||||
});
|
|
||||||
|
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub trait HasPlayPhrase: HasClock {
|
||||||
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
||||||
fn pulses_since_start (&self) -> Option<f64> {
|
fn pulses_since_start (&self) -> Option<f64> {
|
||||||
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +141,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
|
||||||
// First sample to populate. Greater than 0 means that the first
|
// First sample to populate. Greater than 0 means that the first
|
||||||
// pulse of the phrase falls somewhere in the middle of the chunk.
|
// pulse of the phrase falls somewhere in the middle of the chunk.
|
||||||
let sample = started.sample.get() as usize;
|
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);
|
let sample = sample0.saturating_sub(sample);
|
||||||
// Iterator that emits sample (index into output buffer at which to write MIDI event)
|
// 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
|
// 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;
|
//let samples = scope.n_frames() as usize;
|
||||||
if let Some((start_at, phrase)) = &self.next_phrase() {
|
if let Some((start_at, phrase)) = &self.next_phrase() {
|
||||||
let start = start_at.sample.get() as usize;
|
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 it's time to switch to the next phrase:
|
||||||
if start <= sample0.saturating_sub(sample) {
|
if start <= sample0.saturating_sub(sample) {
|
||||||
// Samples elapsed since phrase was supposed to start
|
// Samples elapsed since phrase was supposed to start
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ impl From<&SequencerTui> for SequencerStatusBar {
|
||||||
use SequencerFocus::*;
|
use SequencerFocus::*;
|
||||||
use TransportFocus::*;
|
use TransportFocus::*;
|
||||||
let samples = state.clock.chunk.load(Ordering::Relaxed);
|
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 buffer = samples as f64 / rate;
|
||||||
let default_help = &[("", "⏎", " enter"), ("", "✣", " navigate")];
|
let default_help = &[("", "⏎", " enter"), ("", "✣", " navigate")];
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ pub fn arranger_content_vertical (
|
||||||
factor: usize
|
factor: usize
|
||||||
) -> impl Widget<Engine = Tui> + use<'_> {
|
) -> impl Widget<Engine = Tui> + use<'_> {
|
||||||
let timebase = view.clock().timebase();
|
let timebase = view.clock().timebase();
|
||||||
let current = &view.clock().current;
|
let current = &view.clock().playhead;
|
||||||
let tracks = view.tracks();
|
let tracks = view.tracks();
|
||||||
let scenes = view.scenes();
|
let scenes = view.scenes();
|
||||||
let cols = track_widths(tracks);
|
let cols = track_widths(tracks);
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,12 @@ impl<'a, T: HasClock> From<&'a T> for TransportView where Option<TransportFocus>
|
||||||
Self {
|
Self {
|
||||||
selected,
|
selected,
|
||||||
focused: selected.is_some(),
|
focused: selected.is_some(),
|
||||||
state: state.clock().playing.read().unwrap().clone(),
|
state: Some(state.clock().transport.query_state().unwrap()),
|
||||||
bpm: state.clock().bpm().get(),
|
bpm: state.clock().bpm().get(),
|
||||||
sync: state.clock().sync.get(),
|
sync: state.clock().sync.get(),
|
||||||
quant: state.clock().quant.get(),
|
quant: state.clock().quant.get(),
|
||||||
beat: state.clock().current.format_beat(),
|
beat: state.clock().playhead.format_beat(),
|
||||||
msu: state.clock().current.usec.format_msu(),
|
msu: state.clock().playhead.usec.format_msu(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue