mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
extract TransportClock
This commit is contained in:
parent
ccc74fd743
commit
85e243f782
6 changed files with 130 additions and 145 deletions
|
|
@ -103,6 +103,16 @@ pub trait UsecPosition<T> {
|
|||
fn set_usec (&self, usec: T);
|
||||
}
|
||||
|
||||
pub trait LaunchSync<T> {
|
||||
fn sync (&self) -> T;
|
||||
fn set_sync (&self, sync: T);
|
||||
}
|
||||
|
||||
pub trait Quantize<T> {
|
||||
fn quant (&self) -> T;
|
||||
fn set_quant (&self, quant: T);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Keeps track of global time units.
|
||||
pub struct Timebase {
|
||||
|
|
@ -133,47 +143,6 @@ impl PulsesPerQuaver<f64> for Timebase {
|
|||
#[inline] fn set_ppq (&self, ppq: f64) { self.ppq.store(ppq, Ordering::Relaxed); }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TransportTime {
|
||||
/// Current sample sr, tempo, and PPQ.
|
||||
pub timebase: Arc<Timebase>,
|
||||
/// Note quantization factor
|
||||
pub quant: usize,
|
||||
/// Launch quantization factor
|
||||
pub sync: usize,
|
||||
/// Current time in frames
|
||||
pub frame: usize,
|
||||
/// Current time in pulses
|
||||
pub pulse: usize,
|
||||
/// Current time in microseconds
|
||||
pub usecs: usize,
|
||||
/// Pulses per quarter note
|
||||
pub ppq: usize,
|
||||
}
|
||||
impl SampleRate<f64> for TransportTime {
|
||||
#[inline] fn sr (&self) -> f64 { self.timebase.sr() }
|
||||
#[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); }
|
||||
}
|
||||
impl BeatsPerMinute<f64> for TransportTime {
|
||||
#[inline] fn bpm (&self) -> f64 { self.timebase.bpm() }
|
||||
#[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); }
|
||||
}
|
||||
impl PulsesPerQuaver<f64> for TransportTime {
|
||||
const DEFAULT_PPQ: f64 = Timebase::DEFAULT_PPQ;
|
||||
#[inline] fn ppq (&self) -> f64 { self.timebase.ppq() }
|
||||
#[inline] fn set_ppq (&self, ppq: f64) { self.timebase.set_ppq(ppq); }
|
||||
}
|
||||
|
||||
pub trait LaunchSync<T> {
|
||||
fn sync (&self) -> T;
|
||||
fn set_sync (&self, sync: T) -> T;
|
||||
}
|
||||
|
||||
pub trait Quantize<T> {
|
||||
fn quant (&self) -> T;
|
||||
fn set_quant (&self, quant: T);
|
||||
}
|
||||
|
||||
/// (pulses, name)
|
||||
pub const NOTE_DURATIONS: [(usize, &str);26] = [
|
||||
(1, "1/384"),
|
||||
|
|
@ -205,32 +174,22 @@ pub const NOTE_DURATIONS: [(usize, &str);26] = [
|
|||
];
|
||||
|
||||
/// Returns the next shorter length
|
||||
pub fn prev_note_length (ppq: usize) -> usize {
|
||||
pub fn prev_note_length (pulses: usize) -> usize {
|
||||
for i in 1..=16 {
|
||||
let length = NOTE_DURATIONS[16-i].0;
|
||||
if length < ppq {
|
||||
return length
|
||||
}
|
||||
if length < pulses { return length }
|
||||
}
|
||||
ppq
|
||||
pulses
|
||||
}
|
||||
|
||||
/// Returns the next longer length
|
||||
pub fn next_note_length (ppq: usize) -> usize {
|
||||
for (length, _) in &NOTE_DURATIONS {
|
||||
if *length > ppq {
|
||||
return *length
|
||||
}
|
||||
}
|
||||
ppq
|
||||
pub fn next_note_length (pulses: usize) -> usize {
|
||||
for (length, _) in &NOTE_DURATIONS { if *length > pulses { return *length } }
|
||||
pulses
|
||||
}
|
||||
|
||||
pub fn ppq_to_name (ppq: usize) -> &'static str {
|
||||
for (length, name) in &NOTE_DURATIONS {
|
||||
if *length == ppq {
|
||||
return name
|
||||
}
|
||||
}
|
||||
pub fn pulses_to_name (pulses: usize) -> &'static str {
|
||||
for (length, name) in &NOTE_DURATIONS { if *length == pulses { return name } }
|
||||
""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ impl Content for PhraseEditor<Tui> {
|
|||
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
||||
}
|
||||
let mut upper_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
||||
ppq_to_name(time_scale),
|
||||
pulses_to_name(time_scale),
|
||||
time_start,
|
||||
time_point.unwrap_or(0),
|
||||
time_scale,
|
||||
|
|
@ -150,7 +150,7 @@ impl Content for PhraseEditor<Tui> {
|
|||
);
|
||||
if *focused && *entered {
|
||||
upper_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||
ppq_to_name(*note_len),
|
||||
pulses_to_name(*note_len),
|
||||
note_start,
|
||||
note_point.unwrap_or(0),
|
||||
note_clamp.unwrap_or(0),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,28 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TransportTime {
|
||||
/// Current sample sr, tempo, and PPQ.
|
||||
pub timebase: Timebase,
|
||||
/// Playback state
|
||||
pub playing: RwLock<Option<TransportState>>,
|
||||
/// Current time in frames
|
||||
pub frame: AtomicUsize,
|
||||
/// Current time in pulses
|
||||
pub pulse: AtomicUsize,
|
||||
/// Current time in microseconds
|
||||
pub usecs: AtomicUsize,
|
||||
/// Note quantization factor
|
||||
pub quant: AtomicUsize,
|
||||
/// Launch quantization factor
|
||||
pub sync: AtomicUsize,
|
||||
}
|
||||
/// Stores and displays time-related state.
|
||||
pub struct TransportToolbar<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
/// Current sample rate, tempo, and PPQ.
|
||||
pub clock: Arc<TransportTime>,
|
||||
/// Enable metronome?
|
||||
pub metronome: bool,
|
||||
/// Current sample rate, tempo, and PPQ.
|
||||
pub timebase: Arc<Timebase>,
|
||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||
pub jack: Option<JackClient>,
|
||||
/// JACK transport handle.
|
||||
|
|
@ -17,57 +33,76 @@ pub struct TransportToolbar<E: Engine> {
|
|||
pub focused: bool,
|
||||
/// Which part of the toolbar is focused
|
||||
pub focus: TransportToolbarFocus,
|
||||
/// Playback state
|
||||
pub playing: Option<TransportState>,
|
||||
/// Current tempo
|
||||
pub bpm: f64,
|
||||
/// Quantization factor
|
||||
pub quant: usize,
|
||||
/// Launch sync
|
||||
pub sync: usize,
|
||||
/// Current time in frames
|
||||
pub frame: usize,
|
||||
/// Current time in pulses
|
||||
pub pulse: usize,
|
||||
/// Current time in microseconds
|
||||
pub usecs: usize,
|
||||
/// Pulses per quarter note
|
||||
pub ppq: usize,
|
||||
}
|
||||
/// Which item of the transport toolbar is focused
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum TransportToolbarFocus {
|
||||
Bpm,
|
||||
Sync,
|
||||
PlayPause,
|
||||
Clock,
|
||||
Quant,
|
||||
}
|
||||
pub enum TransportToolbarFocus { Bpm, Sync, PlayPause, Clock, Quant, }
|
||||
|
||||
impl SampleRate<f64> for TransportTime {
|
||||
#[inline] fn sr (&self) -> f64 { self.timebase.sr() }
|
||||
#[inline] fn set_sr (&self, sr: f64) { self.timebase.set_sr(sr); }
|
||||
}
|
||||
impl BeatsPerMinute<f64> for TransportTime {
|
||||
#[inline] fn bpm (&self) -> f64 { self.timebase.bpm() }
|
||||
#[inline] fn set_bpm (&self, bpm: f64) { self.timebase.set_bpm(bpm); }
|
||||
}
|
||||
impl PulsesPerQuaver<f64> for TransportTime {
|
||||
const DEFAULT_PPQ: f64 = Timebase::DEFAULT_PPQ;
|
||||
#[inline] fn ppq (&self) -> f64 { self.timebase.ppq() }
|
||||
#[inline] fn set_ppq (&self, ppq: f64) { self.timebase.set_ppq(ppq); }
|
||||
}
|
||||
impl FramePosition<usize> for TransportTime {
|
||||
#[inline] fn frame (&self) -> usize { self.frame.load(Ordering::Relaxed) }
|
||||
#[inline] fn set_frame (&self, frame: usize) { self.frame.store(frame, Ordering::Relaxed); }
|
||||
}
|
||||
impl UsecPosition<usize> for TransportTime {
|
||||
#[inline] fn usec (&self) -> usize { self.usecs.load(Ordering::Relaxed) }
|
||||
#[inline] fn set_usec (&self, usec: usize) { self.usecs.store(usec, Ordering::Relaxed); }
|
||||
}
|
||||
impl PulsePosition<usize> for TransportTime {
|
||||
#[inline] fn pulse (&self) -> usize { self.pulse.load(Ordering::Relaxed) }
|
||||
#[inline] fn set_pulse (&self, usec: usize) { self.pulse.store(usec, Ordering::Relaxed); }
|
||||
}
|
||||
impl Quantize<usize> for TransportTime {
|
||||
#[inline] fn quant (&self) -> usize { self.quant.load(Ordering::Relaxed) }
|
||||
#[inline] fn set_quant (&self, quant: usize) { self.quant.store(quant, Ordering::Relaxed); }
|
||||
}
|
||||
impl LaunchSync<usize> for TransportTime {
|
||||
#[inline] fn sync (&self) -> usize { self.sync.load(Ordering::Relaxed) }
|
||||
#[inline] fn set_sync (&self, sync: usize) { self.sync.store(sync, Ordering::Relaxed); }
|
||||
}
|
||||
impl<E: Engine> TransportToolbar<E> {
|
||||
pub fn new (transport: Option<Transport>) -> Self {
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
let timebase = Timebase::default();
|
||||
Self {
|
||||
_engine: Default::default(),
|
||||
focused: false,
|
||||
focus: TransportToolbarFocus::PlayPause,
|
||||
playing: Some(TransportState::Stopped),
|
||||
bpm: timebase.bpm(),
|
||||
quant: 24,
|
||||
sync: timebase.ppq() as usize * 4,
|
||||
frame: 0,
|
||||
pulse: 0,
|
||||
ppq: 0,
|
||||
usecs: 0,
|
||||
metronome: false,
|
||||
started: None,
|
||||
jack: None,
|
||||
transport,
|
||||
timebase,
|
||||
clock: Arc::new(TransportTime {
|
||||
playing: Some(TransportState::Stopped).into(),
|
||||
quant: 24.into(),
|
||||
sync: (timebase.ppq() as usize * 4).into(),
|
||||
frame: 0.into(),
|
||||
pulse: 0.into(),
|
||||
usecs: 0.into(),
|
||||
timebase,
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn bpm (&self) -> usize { self.clock.bpm() as usize }
|
||||
pub fn ppq (&self) -> usize { self.clock.ppq() as usize }
|
||||
pub fn pulse (&self) -> usize { self.clock.samples_to_pulse(self.clock.frame()as f64) as usize }
|
||||
pub fn usecs (&self) -> usize { self.clock.samples_to_usec(self.clock.frame() as f64) as usize }
|
||||
pub fn quant (&self) -> usize { self.clock.quant() }
|
||||
pub fn sync (&self) -> usize { self.clock.sync() }
|
||||
pub fn toggle_play (&mut self) -> Usually<()> {
|
||||
let transport = self.transport.as_ref().unwrap();
|
||||
self.playing = match self.playing.expect("1st frame has not been processed yet") {
|
||||
let playing = self.clock.playing.read().unwrap().expect("1st frame has not been processed yet");
|
||||
let playing = match playing {
|
||||
TransportState::Stopped => {
|
||||
transport.start()?;
|
||||
Some(TransportState::Starting)
|
||||
|
|
@ -78,28 +113,10 @@ impl<E: Engine> TransportToolbar<E> {
|
|||
Some(TransportState::Stopped)
|
||||
},
|
||||
};
|
||||
*self.clock.playing.write().unwrap() = playing;
|
||||
Ok(())
|
||||
}
|
||||
pub fn bpm (&self) -> usize {
|
||||
self.timebase.bpm() as usize
|
||||
}
|
||||
pub fn ppq (&self) -> usize {
|
||||
self.timebase.ppq() as usize
|
||||
}
|
||||
pub fn pulse (&self) -> usize {
|
||||
self.timebase.samples_to_pulse(self.frame as f64) as usize
|
||||
}
|
||||
pub fn usecs (&self) -> usize {
|
||||
self.timebase.samples_to_usec(self.frame as f64) as usize
|
||||
}
|
||||
pub fn quant (&self) -> usize {
|
||||
self.quant
|
||||
}
|
||||
pub fn sync (&self) -> usize {
|
||||
self.sync
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportToolbarFocus {
|
||||
pub fn next (&mut self) {
|
||||
*self = match self {
|
||||
|
|
|
|||
|
|
@ -25,26 +25,34 @@ impl TransportToolbar<Tui> {
|
|||
}
|
||||
fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char(',')) => { self.bpm -= 1.0; },
|
||||
key!(KeyCode::Char('.')) => { self.bpm += 1.0; },
|
||||
key!(KeyCode::Char('<')) => { self.bpm -= 0.001; },
|
||||
key!(KeyCode::Char('>')) => { self.bpm += 0.001; },
|
||||
key!(KeyCode::Char(',')) => { self.clock.set_bpm(self.clock.bpm() - 1.0); },
|
||||
key!(KeyCode::Char('.')) => { self.clock.set_bpm(self.clock.bpm() + 1.0); },
|
||||
key!(KeyCode::Char('<')) => { self.clock.set_bpm(self.clock.bpm() - 0.001); },
|
||||
key!(KeyCode::Char('>')) => { self.clock.set_bpm(self.clock.bpm() + 0.001); },
|
||||
_ => return Ok(None)
|
||||
}
|
||||
Ok(Some(true))
|
||||
}
|
||||
fn handle_quant (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char(',')) => { self.quant = prev_note_length(self.quant); },
|
||||
key!(KeyCode::Char('.')) => { self.quant = next_note_length(self.quant); },
|
||||
key!(KeyCode::Char(',')) => {
|
||||
self.clock.set_quant(prev_note_length(self.clock.quant()));
|
||||
},
|
||||
key!(KeyCode::Char('.')) => {
|
||||
self.clock.set_quant(next_note_length(self.clock.quant()));
|
||||
},
|
||||
_ => return Ok(None)
|
||||
}
|
||||
return Ok(Some(true))
|
||||
}
|
||||
fn handle_sync (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char(',')) => { self.sync = prev_note_length(self.sync); },
|
||||
key!(KeyCode::Char('.')) => { self.sync = next_note_length(self.sync); },
|
||||
key!(KeyCode::Char(',')) => {
|
||||
self.clock.set_quant(prev_note_length(self.clock.sync()));
|
||||
},
|
||||
key!(KeyCode::Char('.')) => {
|
||||
self.clock.set_quant(next_note_length(self.clock.sync()));
|
||||
},
|
||||
_ => return Ok(None)
|
||||
}
|
||||
return Ok(Some(true))
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ impl<E: Engine> TransportToolbar<E> {
|
|||
let CycleTimes { current_frames, current_usecs, next_usecs, period_usecs } = times;
|
||||
let chunk_size = scope.n_frames() as usize;
|
||||
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
||||
self.frame = transport.pos.frame() as usize;
|
||||
self.clock.set_frame(transport.pos.frame() as usize);
|
||||
let mut reset = false;
|
||||
if self.playing != Some(transport.state) {
|
||||
if *self.clock.playing.read().unwrap() != Some(transport.state) {
|
||||
match transport.state {
|
||||
TransportState::Rolling => {
|
||||
self.started = Some((current_frames as usize, current_usecs as usize));
|
||||
|
|
@ -24,19 +24,15 @@ impl<E: Engine> TransportToolbar<E> {
|
|||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.playing = Some(transport.state);
|
||||
if self.playing == Some(TransportState::Stopped) {
|
||||
};
|
||||
*self.clock.playing.write().unwrap() = Some(transport.state);
|
||||
if *self.clock.playing.read().unwrap() == Some(TransportState::Stopped) {
|
||||
self.started = None;
|
||||
}
|
||||
match self.started {
|
||||
Some((_, usecs)) => {
|
||||
self.usecs = current_usecs as usize - usecs;
|
||||
},
|
||||
None => {
|
||||
self.usecs = 0;
|
||||
}
|
||||
}
|
||||
Some((_, usecs)) => { self.clock.set_usec(current_usecs as usize - usecs); },
|
||||
None => { self.clock.set_usec(0); }
|
||||
};
|
||||
(
|
||||
reset,
|
||||
current_frames as usize,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ impl Content for TransportToolbar<Tui> {
|
|||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
lay!(
|
||||
self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled(
|
||||
match self.playing {
|
||||
match *self.clock.playing.read().unwrap() {
|
||||
Some(TransportState::Stopped) => Some(GRAY_DIM.bold()),
|
||||
Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD),
|
||||
Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
match self.playing {
|
||||
match *self.clock.playing.read().unwrap() {
|
||||
Some(TransportState::Rolling) => "▶ PLAYING",
|
||||
Some(TransportState::Starting) => "READY ...",
|
||||
Some(TransportState::Stopped) => "⏹ STOPPED",
|
||||
|
|
@ -20,19 +20,24 @@ impl Content for TransportToolbar<Tui> {
|
|||
|
||||
row!(
|
||||
self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, row! {
|
||||
"BPM ", format!("{}.{:03}", self.bpm as usize, (self.bpm * 1000.0) % 1000.0)
|
||||
"BPM ", format!("{}.{:03}",
|
||||
self.clock.bpm() as usize,
|
||||
(self.clock.bpm() * 1000.0) % 1000.0
|
||||
)
|
||||
})),
|
||||
//self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, row! {
|
||||
//let quant = self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, row! {
|
||||
//"QUANT ", ppq_to_name(self.quant as usize)
|
||||
//})),
|
||||
self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, row! {
|
||||
"SYNC ", ppq_to_name(self.sync as usize)
|
||||
})),
|
||||
"SYNC ", pulses_to_name(self.sync() as usize)
|
||||
}))
|
||||
).align_w().fill_x(),
|
||||
|
||||
self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{
|
||||
let Self { frame: _frame, pulse, ppq, usecs, .. } = self;
|
||||
let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||
let pulse = self.clock.pulse();
|
||||
let ppq = self.clock.ppq() as usize;
|
||||
let usecs = self.clock.usec();
|
||||
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
|
||||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue