move ClockModel to tek_api

This commit is contained in:
🪞👃🪞 2024-11-26 22:02:41 +01:00
parent 4fdc3911e4
commit a26a1f2967
13 changed files with 214 additions and 222 deletions

View file

@ -1,5 +1,9 @@
use crate::*;
pub trait HasClock: Send + Sync {
fn clock (&self) -> &ClockModel;
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClockCommand {
Play(Option<u32>),
@ -12,113 +16,150 @@ pub enum ClockCommand {
SetSync(f64),
}
impl<T: ClockApi> Command<T> for ClockCommand {
impl<T: HasClock> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
use ClockCommand::*;
match self {
Play(start) => state.play_from(start)?,
Pause(pause) => state.pause_at(pause)?,
SeekUsec(usec) => state.current().update_from_usec(usec),
SeekSample(sample) => state.current().update_from_sample(sample),
SeekPulse(pulse) => state.current().update_from_pulse(pulse),
SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))),
SetQuant(quant) => return Ok(Some(SetQuant(state.quant().set(quant)))),
SetSync(sync) => return Ok(Some(SetSync(state.sync().set(sync)))),
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),
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)))),
};
Ok(None)
}
}
pub trait ClockApi: Send + Sync {
/// Note quantization factor
fn quant (&self) -> &Arc<Quantize>;
/// Launch quantization factor
fn sync (&self) -> &Arc<LaunchSync>;
/// Current moment in time
fn current (&self) -> &Arc<Instant>;
/// Current temporal resolutions
fn timebase (&self) -> &Arc<Timebase> { &self.current().timebase }
/// Current sample rate
fn sr (&self) -> &SampleRate { &self.timebase().sr }
/// Current tempo
fn bpm (&self) -> &BeatsPerMinute { &self.timebase().bpm }
/// Current MIDI resolution
fn ppq (&self) -> &PulsesPerQuaver { &self.timebase().ppq }
/// Handle to JACK transport
fn transport_handle (&self) -> &Arc<Transport>;
#[derive(Clone)]
pub struct ClockModel {
/// JACK transport handle.
pub transport: Arc<Transport>,
/// Playback state
fn transport_state (&self) -> &Arc<RwLock<Option<TransportState>>>;
pub playing: Arc<RwLock<Option<TransportState>>>,
/// Global sample and usec at which playback started
fn transport_offset (&self) -> &Arc<RwLock<Option<(usize, usize)>>>;
pub started: Arc<RwLock<Option<(usize, usize)>>>,
/// Current moment in time
pub current: Arc<Instant>,
/// Note quantization factor
pub quant: Arc<Quantize>,
/// Launch quantization factor
pub sync: Arc<LaunchSync>,
}
fn next_launch_pulse (&self) -> usize {
let sync = self.sync().get() as usize;
let pulse = self.current().pulse.get() as usize;
impl From<&Arc<Transport>> for ClockModel {
fn from (transport: &Arc<Transport>) -> Self {
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: transport.clone(),
}
}
}
impl std::fmt::Debug for ClockModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("editor")
.field("playing", &self.playing)
.field("started", &self.started)
.field("current", &self.current)
.field("quant", &self.quant)
.field("sync", &self.sync)
.finish()
}
}
impl ClockModel {
pub fn timebase (&self) -> &Arc<Timebase> {
&self.current.timebase
}
/// Current sample rate
pub fn sr (&self) -> &SampleRate {
&self.timebase().sr
}
/// Current tempo
pub fn bpm (&self) -> &BeatsPerMinute {
&self.timebase().bpm
}
/// Current MIDI resolution
pub fn ppq (&self) -> &PulsesPerQuaver {
&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;
if pulse % sync == 0 {
pulse
} else {
(pulse / sync + 1) * sync
}
}
fn play_from (&mut self, start: Option<u32>) -> Usually<()> {
/// Start playing, optionally seeking to a given location beforehand
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
if let Some(start) = start {
self.transport_handle().locate(start)?;
self.transport.locate(start)?;
}
self.transport_handle().start()?;
self.transport.start()?;
self.update_transport_state()
}
fn is_rolling (&self) -> bool {
*self.transport_state().read().unwrap() == Some(TransportState::Rolling)
}
fn pause_at (&mut self, pause: Option<u32>) -> Usually<()> {
self.transport_handle().stop()?;
/// Pause, optionally seeking to a given location afterwards
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
self.transport.stop()?;
if let Some(pause) = pause {
self.transport_handle().locate(pause)?;
self.transport.locate(pause)?;
}
self.update_transport_state()
}
fn is_stopped (&self) -> bool {
*self.transport_state().read().unwrap() == Some(TransportState::Stopped)
}
fn update_transport_state (&self) -> Usually<()> {
*self.transport_state().write().unwrap() = Some(self.transport_handle().query_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(())
}
fn toggle_play (&self) -> Usually<()> {
let playing = self.transport_state().read().unwrap().expect("1st sample has not been processed yet");
let playing = match playing {
TransportState::Stopped => {
self.transport_handle().start()?;
Some(TransportState::Starting)
},
_ => {
self.transport_handle().stop()?;
self.transport_handle().locate(0)?;
Some(TransportState::Stopped)
},
};
*self.transport_state().write().unwrap() = playing;
Ok(())
/// Is currently paused?
pub fn is_stopped (&self) -> bool {
*self.playing.read().unwrap() == Some(TransportState::Stopped)
}
/// Is currently playing?
pub fn is_rolling (&self) -> bool {
*self.playing.read().unwrap() == Some(TransportState::Rolling)
}
//fn toggle_play (&self) -> Usually<()> {
//let playing = self.playing.read().unwrap().expect("1st sample has not been processed yet");
//let playing = match playing {
//TransportState::Stopped => {
//self.transport.start()?;
//Some(TransportState::Starting)
//},
//_ => {
//self.transport.stop()?;
//self.transport.locate(0)?;
//Some(TransportState::Stopped)
//},
//};
//*self.playing.write().unwrap() = playing;
//Ok(())
//}
}
/// Hosts the JACK callback for updating the temporal pointer and playback status.
pub struct ClockAudio<'a, T: ClockApi>(pub &'a mut T);
pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T);
impl<'a, T: ClockApi> Audio for ClockAudio<'a, T> {
impl<'a, T: HasClock> Audio for ClockAudio<'a, T> {
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let state = &mut self.0;
let state = self.0.clock();
let times = scope.cycle_times().unwrap();
let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
let _chunk_size = scope.n_frames() as usize;
let transport = state.transport_handle().query().unwrap();
state.current().sample.set(transport.pos.frame() as f64);
let mut playing = state.transport_state().write().unwrap();
let mut started = state.transport_offset().write().unwrap();
let transport = state.transport.query().unwrap();
state.current.sample.set(transport.pos.frame() as f64);
let mut playing = state.playing.write().unwrap();
let mut started = state.started.write().unwrap();
if *playing != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
@ -134,7 +175,7 @@ impl<'a, T: ClockApi> Audio for ClockAudio<'a, T> {
if *playing == Some(TransportState::Stopped) {
*started = None;
}
state.current().update_from_usec(match *started {
state.current.update_from_usec(match *started {
Some((_, usecs)) => current_usecs as f64 - usecs as f64,
None => 0.
});

View file

@ -7,7 +7,7 @@ pub trait HasPlayer: JackApi {
pub trait MidiPlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
pub trait HasPhrase: ClockApi {
pub trait HasPhrase: HasClock {
fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool;
@ -21,23 +21,22 @@ pub trait HasPhrase: ClockApi {
fn next_phrase_mut (&mut self)
-> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
let start = self.next_launch_pulse();
*self.next_phrase_mut() = Some((
Instant::from_pulse(self.timebase(), start as f64),
phrase.map(|p|p.clone())
));
let start = self.clock().next_launch_pulse() as f64;
let instant = Instant::from_pulse(&self.clock().timebase(), start);
let phrase = phrase.map(|p|p.clone());
*self.next_phrase_mut() = Some((instant, phrase));
*self.reset_mut() = true;
}
fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
Some(self.current().pulse.get() - started.pulse.get())
Some(self.clock().current.pulse.get() - started.pulse.get())
} else {
None
}
}
}
pub trait MidiInputApi: ClockApi + HasPhrase {
pub trait MidiInputApi: HasPhrase {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
fn has_midi_ins (&self) -> bool {
@ -71,38 +70,40 @@ pub trait MidiInputApi: ClockApi + HasPhrase {
let sample0 = scope.last_frame_time() as usize;
// For highlighting keys and note repeat
let notes_in = self.notes_in().clone();
if let (true, Some((started, ref phrase))) = (self.is_rolling(), self.play_phrase().clone()) {
let start = started.sample.get() as usize;
let quant = self.quant().get();
let timebase = self.timebase().clone();
let monitoring = self.monitoring();
let recording = self.recording();
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
if monitoring {
midi_buf[sample].push(bytes.to_vec())
}
if recording {
if let Some(phrase) = phrase {
let mut phrase = phrase.write().unwrap();
let length = phrase.length;
phrase.record_event({
let sample = (sample0 + sample - start) as f64;
let pulse = timebase.samples_to_pulse(sample);
let quantized = (pulse / quant).round() * quant;
let looped = quantized as usize % length;
looped
}, message);
if self.clock().is_rolling() {
if let Some((started, ref phrase)) = self.play_phrase().clone() {
let start = started.sample.get() as usize;
let quant = self.clock().quant.get();
let timebase = self.clock().timebase().clone();
let monitoring = self.monitoring();
let recording = self.recording();
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
if monitoring {
midi_buf[sample].push(bytes.to_vec())
}
if recording {
if let Some(phrase) = phrase {
let mut phrase = phrase.write().unwrap();
let length = phrase.length;
phrase.record_event({
let sample = (sample0 + sample - start) as f64;
let pulse = timebase.samples_to_pulse(sample);
let quantized = (pulse / quant).round() * quant;
let looped = quantized as usize % length;
looped
}, message);
}
}
update_keys(&mut*notes_in.write().unwrap(), &message);
}
update_keys(&mut*notes_in.write().unwrap(), &message);
}
}
}
}
if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase()) {
// TODO switch to next phrase and record into it
if let Some((start_at, phrase)) = &self.next_phrase() {
// TODO switch to next phrase and record into it
}
}
}
@ -125,7 +126,7 @@ pub trait MidiInputApi: ClockApi + HasPhrase {
}
pub trait MidiOutputApi: ClockApi + HasPhrase {
pub trait MidiOutputApi: HasPhrase {
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
@ -162,14 +163,14 @@ pub trait MidiOutputApi: ClockApi + HasPhrase {
) -> bool {
let mut next = false;
// Write MIDI events from currently playing phrase (if any) to MIDI output buffer
if self.is_rolling() {
if self.clock().is_rolling() {
let sample0 = scope.last_frame_time() as usize;
let samples = scope.n_frames() as usize;
// If no phrase is playing, prepare for switchover immediately
next = self.play_phrase().is_none();
let phrase = self.play_phrase();
let started0 = self.transport_offset();
let timebase = self.timebase();
let started0 = &self.clock().started;
let timebase = self.clock().timebase();
let notes_out = self.notes_out();
let next_phrase = self.next_phrase();
if let Some((started, phrase)) = phrase {
@ -232,18 +233,18 @@ pub trait MidiOutputApi: ClockApi + HasPhrase {
note_buffer: &mut Vec<u8>,
output_buffer: &mut Vec<Vec<Vec<u8>>>
) {
if self.is_rolling() {
if self.clock().is_rolling() {
let sample0 = scope.last_frame_time() as usize;
//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.transport_offset().read().unwrap().unwrap().0;
let sample = self.clock().started.read().unwrap().unwrap().0;
// If it's time to switch to the next phrase:
if start <= sample0.saturating_sub(sample) {
// Samples elapsed since phrase was supposed to start
let skipped = sample0 - start;
// Switch over to enqueued phrase
let started = Instant::from_sample(&self.timebase(), start as f64);
let started = Instant::from_sample(&self.clock().timebase(), start as f64);
*self.play_phrase_mut() = Some((started, phrase.clone()));
// Unset enqueuement (TODO: where to implement looping?)
*self.next_phrase_mut() = None