mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +01:00
move ClockModel to tek_api
This commit is contained in:
parent
4fdc3911e4
commit
a26a1f2967
13 changed files with 214 additions and 222 deletions
|
|
@ -1,5 +1,9 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait HasClock: Send + Sync {
|
||||||
|
fn clock (&self) -> &ClockModel;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ClockCommand {
|
pub enum ClockCommand {
|
||||||
Play(Option<u32>),
|
Play(Option<u32>),
|
||||||
|
|
@ -12,113 +16,150 @@ pub enum ClockCommand {
|
||||||
SetSync(f64),
|
SetSync(f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ClockApi> Command<T> for ClockCommand {
|
impl<T: HasClock> Command<T> for ClockCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
use ClockCommand::*;
|
use ClockCommand::*;
|
||||||
match self {
|
match self {
|
||||||
Play(start) => state.play_from(start)?,
|
Play(start) => state.clock().play_from(start)?,
|
||||||
Pause(pause) => state.pause_at(pause)?,
|
Pause(pause) => state.clock().pause_at(pause)?,
|
||||||
SeekUsec(usec) => state.current().update_from_usec(usec),
|
SeekUsec(usec) => state.clock().current.update_from_usec(usec),
|
||||||
SeekSample(sample) => state.current().update_from_sample(sample),
|
SeekSample(sample) => state.clock().current.update_from_sample(sample),
|
||||||
SeekPulse(pulse) => state.current().update_from_pulse(pulse),
|
SeekPulse(pulse) => state.clock().current.update_from_pulse(pulse),
|
||||||
SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))),
|
SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))),
|
||||||
SetQuant(quant) => return Ok(Some(SetQuant(state.quant().set(quant)))),
|
SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))),
|
||||||
SetSync(sync) => return Ok(Some(SetSync(state.sync().set(sync)))),
|
SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))),
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ClockApi: Send + Sync {
|
#[derive(Clone)]
|
||||||
/// Note quantization factor
|
pub struct ClockModel {
|
||||||
fn quant (&self) -> &Arc<Quantize>;
|
/// JACK transport handle.
|
||||||
/// Launch quantization factor
|
pub transport: Arc<Transport>,
|
||||||
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>;
|
|
||||||
/// Playback state
|
/// Playback state
|
||||||
fn transport_state (&self) -> &Arc<RwLock<Option<TransportState>>>;
|
pub playing: Arc<RwLock<Option<TransportState>>>,
|
||||||
/// Global sample and usec at which playback started
|
/// 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 {
|
impl From<&Arc<Transport>> for ClockModel {
|
||||||
let sync = self.sync().get() as usize;
|
fn from (transport: &Arc<Transport>) -> Self {
|
||||||
let pulse = self.current().pulse.get() as usize;
|
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 {
|
if pulse % sync == 0 {
|
||||||
pulse
|
pulse
|
||||||
} else {
|
} else {
|
||||||
(pulse / sync + 1) * sync
|
(pulse / sync + 1) * sync
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Start playing, optionally seeking to a given location beforehand
|
||||||
fn play_from (&mut self, start: Option<u32>) -> Usually<()> {
|
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
|
||||||
if let Some(start) = start {
|
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()
|
self.update_transport_state()
|
||||||
}
|
}
|
||||||
fn is_rolling (&self) -> bool {
|
/// Pause, optionally seeking to a given location afterwards
|
||||||
*self.transport_state().read().unwrap() == Some(TransportState::Rolling)
|
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
||||||
}
|
self.transport.stop()?;
|
||||||
|
|
||||||
fn pause_at (&mut self, pause: Option<u32>) -> Usually<()> {
|
|
||||||
self.transport_handle().stop()?;
|
|
||||||
if let Some(pause) = pause {
|
if let Some(pause) = pause {
|
||||||
self.transport_handle().locate(pause)?;
|
self.transport.locate(pause)?;
|
||||||
}
|
}
|
||||||
self.update_transport_state()
|
self.update_transport_state()
|
||||||
}
|
}
|
||||||
fn is_stopped (&self) -> bool {
|
/// Update the state according to JACK transport
|
||||||
*self.transport_state().read().unwrap() == Some(TransportState::Stopped)
|
pub fn update_transport_state (&self) -> Usually<()> {
|
||||||
}
|
*self.playing.write().unwrap() = Some(self.transport.query_state()?);
|
||||||
|
|
||||||
fn update_transport_state (&self) -> Usually<()> {
|
|
||||||
*self.transport_state().write().unwrap() = Some(self.transport_handle().query_state()?);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
/// Is currently paused?
|
||||||
fn toggle_play (&self) -> Usually<()> {
|
pub fn is_stopped (&self) -> bool {
|
||||||
let playing = self.transport_state().read().unwrap().expect("1st sample has not been processed yet");
|
*self.playing.read().unwrap() == Some(TransportState::Stopped)
|
||||||
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 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.
|
/// 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 {
|
#[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 times = scope.cycle_times().unwrap();
|
||||||
let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
|
let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
|
||||||
let _chunk_size = scope.n_frames() as usize;
|
let _chunk_size = scope.n_frames() as usize;
|
||||||
let transport = state.transport_handle().query().unwrap();
|
let transport = state.transport.query().unwrap();
|
||||||
state.current().sample.set(transport.pos.frame() as f64);
|
state.current.sample.set(transport.pos.frame() as f64);
|
||||||
let mut playing = state.transport_state().write().unwrap();
|
let mut playing = state.playing.write().unwrap();
|
||||||
let mut started = state.transport_offset().write().unwrap();
|
let mut started = state.started.write().unwrap();
|
||||||
if *playing != Some(transport.state) {
|
if *playing != Some(transport.state) {
|
||||||
match transport.state {
|
match transport.state {
|
||||||
TransportState::Rolling => {
|
TransportState::Rolling => {
|
||||||
|
|
@ -134,7 +175,7 @@ impl<'a, T: ClockApi> Audio for ClockAudio<'a, T> {
|
||||||
if *playing == Some(TransportState::Stopped) {
|
if *playing == Some(TransportState::Stopped) {
|
||||||
*started = None;
|
*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,
|
Some((_, usecs)) => current_usecs as f64 - usecs as f64,
|
||||||
None => 0.
|
None => 0.
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ pub trait HasPlayer: JackApi {
|
||||||
|
|
||||||
pub trait MidiPlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
|
pub trait MidiPlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
|
||||||
|
|
||||||
pub trait HasPhrase: ClockApi {
|
pub trait HasPhrase: HasClock {
|
||||||
fn reset (&self) -> bool;
|
fn reset (&self) -> bool;
|
||||||
fn reset_mut (&mut self) -> &mut bool;
|
fn reset_mut (&mut self) -> &mut bool;
|
||||||
|
|
||||||
|
|
@ -21,23 +21,22 @@ pub trait HasPhrase: ClockApi {
|
||||||
fn next_phrase_mut (&mut self)
|
fn next_phrase_mut (&mut self)
|
||||||
-> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
-> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
||||||
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||||
let start = self.next_launch_pulse();
|
let start = self.clock().next_launch_pulse() as f64;
|
||||||
*self.next_phrase_mut() = Some((
|
let instant = Instant::from_pulse(&self.clock().timebase(), start);
|
||||||
Instant::from_pulse(self.timebase(), start as f64),
|
let phrase = phrase.map(|p|p.clone());
|
||||||
phrase.map(|p|p.clone())
|
*self.next_phrase_mut() = Some((instant, phrase));
|
||||||
));
|
|
||||||
*self.reset_mut() = true;
|
*self.reset_mut() = true;
|
||||||
}
|
}
|
||||||
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.current().pulse.get() - started.pulse.get())
|
Some(self.clock().current.pulse.get() - started.pulse.get())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MidiInputApi: ClockApi + HasPhrase {
|
pub trait MidiInputApi: HasPhrase {
|
||||||
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
|
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
|
||||||
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
|
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
|
||||||
fn has_midi_ins (&self) -> bool {
|
fn has_midi_ins (&self) -> bool {
|
||||||
|
|
@ -71,38 +70,40 @@ pub trait MidiInputApi: ClockApi + HasPhrase {
|
||||||
let sample0 = scope.last_frame_time() as usize;
|
let sample0 = scope.last_frame_time() as usize;
|
||||||
// For highlighting keys and note repeat
|
// For highlighting keys and note repeat
|
||||||
let notes_in = self.notes_in().clone();
|
let notes_in = self.notes_in().clone();
|
||||||
if let (true, Some((started, ref phrase))) = (self.is_rolling(), self.play_phrase().clone()) {
|
if self.clock().is_rolling() {
|
||||||
let start = started.sample.get() as usize;
|
if let Some((started, ref phrase)) = self.play_phrase().clone() {
|
||||||
let quant = self.quant().get();
|
let start = started.sample.get() as usize;
|
||||||
let timebase = self.timebase().clone();
|
let quant = self.clock().quant.get();
|
||||||
let monitoring = self.monitoring();
|
let timebase = self.clock().timebase().clone();
|
||||||
let recording = self.recording();
|
let monitoring = self.monitoring();
|
||||||
for input in self.midi_ins_mut().iter() {
|
let recording = self.recording();
|
||||||
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
for input in self.midi_ins_mut().iter() {
|
||||||
if let LiveEvent::Midi { message, .. } = event {
|
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
||||||
if monitoring {
|
if let LiveEvent::Midi { message, .. } = event {
|
||||||
midi_buf[sample].push(bytes.to_vec())
|
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 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 Some((start_at, phrase)) = &self.next_phrase() {
|
||||||
if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase()) {
|
// TODO switch to next phrase and record into it
|
||||||
// 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 (&self) -> &Vec<Port<MidiOut>>;
|
||||||
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
|
||||||
|
|
@ -162,14 +163,14 @@ pub trait MidiOutputApi: ClockApi + HasPhrase {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut next = false;
|
let mut next = false;
|
||||||
// Write MIDI events from currently playing phrase (if any) to MIDI output buffer
|
// 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 sample0 = scope.last_frame_time() as usize;
|
||||||
let samples = scope.n_frames() as usize;
|
let samples = scope.n_frames() as usize;
|
||||||
// If no phrase is playing, prepare for switchover immediately
|
// If no phrase is playing, prepare for switchover immediately
|
||||||
next = self.play_phrase().is_none();
|
next = self.play_phrase().is_none();
|
||||||
let phrase = self.play_phrase();
|
let phrase = self.play_phrase();
|
||||||
let started0 = self.transport_offset();
|
let started0 = &self.clock().started;
|
||||||
let timebase = self.timebase();
|
let timebase = self.clock().timebase();
|
||||||
let notes_out = self.notes_out();
|
let notes_out = self.notes_out();
|
||||||
let next_phrase = self.next_phrase();
|
let next_phrase = self.next_phrase();
|
||||||
if let Some((started, phrase)) = phrase {
|
if let Some((started, phrase)) = phrase {
|
||||||
|
|
@ -232,18 +233,18 @@ pub trait MidiOutputApi: ClockApi + HasPhrase {
|
||||||
note_buffer: &mut Vec<u8>,
|
note_buffer: &mut Vec<u8>,
|
||||||
output_buffer: &mut Vec<Vec<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 sample0 = scope.last_frame_time() as usize;
|
||||||
//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.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 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
|
||||||
let skipped = sample0 - start;
|
let skipped = sample0 - start;
|
||||||
// Switch over to enqueued phrase
|
// 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()));
|
*self.play_phrase_mut() = Some((started, phrase.clone()));
|
||||||
// Unset enqueuement (TODO: where to implement looping?)
|
// Unset enqueuement (TODO: where to implement looping?)
|
||||||
*self.next_phrase_mut() = None
|
*self.next_phrase_mut() = None
|
||||||
|
|
|
||||||
|
|
@ -91,30 +91,6 @@ impl TuiTheme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_clock_api {
|
|
||||||
($Struct:ident $(:: $field:ident)*) => {
|
|
||||||
impl ClockApi for $Struct {
|
|
||||||
fn quant (&self) -> &Arc<Quantize> {
|
|
||||||
&self$(.$field)*.quant
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &Arc<LaunchSync> {
|
|
||||||
&self$(.$field)*.sync
|
|
||||||
}
|
|
||||||
fn current (&self) -> &Arc<Instant> {
|
|
||||||
&self$(.$field)*.current
|
|
||||||
}
|
|
||||||
fn transport_handle (&self) -> &Arc<Transport> {
|
|
||||||
&self$(.$field)*.transport
|
|
||||||
}
|
|
||||||
fn transport_state (&self) -> &Arc<RwLock<Option<TransportState>>> {
|
|
||||||
&self$(.$field)*.playing
|
|
||||||
}
|
|
||||||
fn transport_offset (&self) -> &Arc<RwLock<Option<(usize, usize)>>> {
|
|
||||||
&self$(.$field)*.started
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro_rules! impl_midi_player {
|
macro_rules! impl_midi_player {
|
||||||
($Struct:ident $(:: $field:ident)*) => {
|
($Struct:ident $(:: $field:ident)*) => {
|
||||||
impl HasPhrase for $Struct {
|
impl HasPhrase for $Struct {
|
||||||
|
|
@ -184,12 +160,6 @@ macro_rules! impl_midi_player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_clock_api!(TransportTui::clock);
|
|
||||||
impl_clock_api!(SequencerTui::clock);
|
|
||||||
impl_clock_api!(ArrangerTui::clock);
|
|
||||||
impl_clock_api!(PhrasePlayerModel::clock);
|
|
||||||
impl_clock_api!(ArrangerTrack::player::clock);
|
|
||||||
|
|
||||||
impl_midi_player!(SequencerTui::player);
|
impl_midi_player!(SequencerTui::player);
|
||||||
impl_midi_player!(ArrangerTrack::player);
|
impl_midi_player!(ArrangerTrack::player);
|
||||||
impl_midi_player!(PhrasePlayerModel);
|
impl_midi_player!(PhrasePlayerModel);
|
||||||
|
|
@ -197,18 +167,6 @@ impl_midi_player!(PhrasePlayerModel);
|
||||||
use std::fmt::{Debug, Formatter, Error};
|
use std::fmt::{Debug, Formatter, Error};
|
||||||
type DebugResult = std::result::Result<(), Error>;
|
type DebugResult = std::result::Result<(), Error>;
|
||||||
|
|
||||||
impl Debug for ClockModel {
|
|
||||||
fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult {
|
|
||||||
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 Debug for TransportTui {
|
impl Debug for TransportTui {
|
||||||
fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult {
|
fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult {
|
||||||
f.debug_struct("Measure")
|
f.debug_struct("Measure")
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,16 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasClock for ArrangerTui {
|
||||||
|
fn clock (&self) -> &ClockModel {
|
||||||
|
&self.clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl HasClock for ArrangerTrack {
|
||||||
|
fn clock (&self) -> &ClockModel {
|
||||||
|
&self.player.clock()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl HasPhrases for ArrangerTui {
|
impl HasPhrases for ArrangerTui {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
&self.phrases.phrases
|
&self.phrases.phrases
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,12 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasClock for SequencerTui {
|
||||||
|
fn clock (&self) -> &ClockModel {
|
||||||
|
&self.clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HasPhrases for SequencerTui {
|
impl HasPhrases for SequencerTui {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
&self.phrases.phrases
|
&self.phrases.phrases
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasClock for TransportTui {
|
||||||
|
fn clock (&self) -> &ClockModel {
|
||||||
|
&self.clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Which item of the transport toolbar is focused
|
/// Which item of the transport toolbar is focused
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum TransportFocus {
|
pub enum TransportFocus {
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,8 @@ impl ArrangerControl for ArrangerTui {
|
||||||
track.enqueue_next(phrase.as_ref());
|
track.enqueue_next(phrase.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.is_stopped() {
|
if self.clock().is_stopped() {
|
||||||
self.play_from(Some(0))?;
|
self.clock().play_from(Some(0))?;
|
||||||
}
|
}
|
||||||
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||||
let phrase = self.scenes()[s].clips[t].clone();
|
let phrase = self.scenes()[s].clips[t].clone();
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,11 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
// Play/pause
|
// Play/pause
|
||||||
key!(Char(' ')) => Clock(
|
key!(Char(' ')) => Clock(
|
||||||
if state.is_stopped() { Play(None) } else { Pause(None) }
|
if state.clock().is_stopped() { Play(None) } else { Pause(None) }
|
||||||
),
|
),
|
||||||
// Play from start/rewind to start
|
// Play from start/rewind to start
|
||||||
key!(Shift-Char(' ')) => Clock(
|
key!(Shift-Char(' ')) => Clock(
|
||||||
if state.is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
||||||
),
|
),
|
||||||
// Edit phrase
|
// Edit phrase
|
||||||
key!(Char('e')) => match state.focused() {
|
key!(Char('e')) => match state.focused() {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ impl<T: TransportControl> Command<T> for TransportCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TransportControl: ClockApi + FocusGrid + HasEnter {
|
pub trait TransportControl: HasClock + FocusGrid + HasEnter {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus>;
|
fn transport_focused (&self) -> Option<TransportFocus>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,36 +73,36 @@ where
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key!(Left) => Focus(FocusCommand::Prev),
|
key!(Left) => Focus(FocusCommand::Prev),
|
||||||
key!(Right) => Focus(FocusCommand::Next),
|
key!(Right) => Focus(FocusCommand::Next),
|
||||||
key!(Char(' ')) => Clock(if state.is_stopped() {
|
key!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||||
ClockCommand::Play(None)
|
ClockCommand::Play(None)
|
||||||
} else {
|
} else {
|
||||||
ClockCommand::Pause(None)
|
ClockCommand::Pause(None)
|
||||||
}),
|
}),
|
||||||
key!(Shift-Char(' ')) => Clock(if state.is_stopped() {
|
key!(Shift-Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||||
ClockCommand::Play(Some(0))
|
ClockCommand::Play(Some(0))
|
||||||
} else {
|
} else {
|
||||||
ClockCommand::Pause(Some(0))
|
ClockCommand::Pause(Some(0))
|
||||||
}),
|
}),
|
||||||
_ => match state.transport_focused().unwrap() {
|
_ => match state.transport_focused().unwrap() {
|
||||||
TransportFocus::Bpm => match input.event() {
|
TransportFocus::Bpm => match input.event() {
|
||||||
key!(Char(',')) => Clock(SetBpm(state.bpm().get() - 1.0)),
|
key!(Char(',')) => Clock(SetBpm(state.clock().bpm().get() - 1.0)),
|
||||||
key!(Char('.')) => Clock(SetBpm(state.bpm().get() + 1.0)),
|
key!(Char('.')) => Clock(SetBpm(state.clock().bpm().get() + 1.0)),
|
||||||
key!(Char('<')) => Clock(SetBpm(state.bpm().get() - 0.001)),
|
key!(Char('<')) => Clock(SetBpm(state.clock().bpm().get() - 0.001)),
|
||||||
key!(Char('>')) => Clock(SetBpm(state.bpm().get() + 0.001)),
|
key!(Char('>')) => Clock(SetBpm(state.clock().bpm().get() + 0.001)),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
TransportFocus::Quant => match input.event() {
|
TransportFocus::Quant => match input.event() {
|
||||||
key!(Char(',')) => Clock(SetQuant(state.quant().prev())),
|
key!(Char(',')) => Clock(SetQuant(state.clock().quant.prev())),
|
||||||
key!(Char('.')) => Clock(SetQuant(state.quant().next())),
|
key!(Char('.')) => Clock(SetQuant(state.clock().quant.next())),
|
||||||
key!(Char('<')) => Clock(SetQuant(state.quant().prev())),
|
key!(Char('<')) => Clock(SetQuant(state.clock().quant.prev())),
|
||||||
key!(Char('>')) => Clock(SetQuant(state.quant().next())),
|
key!(Char('>')) => Clock(SetQuant(state.clock().quant.next())),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
TransportFocus::Sync => match input.event() {
|
TransportFocus::Sync => match input.event() {
|
||||||
key!(Char(',')) => Clock(SetSync(state.sync().prev())),
|
key!(Char(',')) => Clock(SetSync(state.clock().sync.prev())),
|
||||||
key!(Char('.')) => Clock(SetSync(state.sync().next())),
|
key!(Char('.')) => Clock(SetSync(state.clock().sync.next())),
|
||||||
key!(Char('<')) => Clock(SetSync(state.sync().prev())),
|
key!(Char('<')) => Clock(SetSync(state.clock().sync.prev())),
|
||||||
key!(Char('>')) => Clock(SetSync(state.sync().next())),
|
key!(Char('>')) => Clock(SetSync(state.clock().sync.next())),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
TransportFocus::Clock => match input.event() {
|
TransportFocus::Clock => match input.event() {
|
||||||
|
|
@ -114,14 +114,14 @@ where
|
||||||
},
|
},
|
||||||
TransportFocus::PlayPause => match input.event() {
|
TransportFocus::PlayPause => match input.event() {
|
||||||
key!(Enter) => Clock(
|
key!(Enter) => Clock(
|
||||||
if state.is_stopped() {
|
if state.clock().is_stopped() {
|
||||||
ClockCommand::Play(None)
|
ClockCommand::Play(None)
|
||||||
} else {
|
} else {
|
||||||
ClockCommand::Pause(None)
|
ClockCommand::Pause(None)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
key!(Shift-Enter) => Clock(
|
key!(Shift-Enter) => Clock(
|
||||||
if state.is_stopped() {
|
if state.clock().is_stopped() {
|
||||||
ClockCommand::Play(Some(0))
|
ClockCommand::Play(Some(0))
|
||||||
} else {
|
} else {
|
||||||
ClockCommand::Pause(Some(0))
|
ClockCommand::Pause(Some(0))
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ClockModel {
|
|
||||||
/// JACK transport handle.
|
|
||||||
pub(crate) transport: Arc<Transport>,
|
|
||||||
/// Playback state
|
|
||||||
pub(crate) playing: Arc<RwLock<Option<TransportState>>>,
|
|
||||||
/// Global sample and usec at which playback started
|
|
||||||
pub(crate) started: Arc<RwLock<Option<(usize, usize)>>>,
|
|
||||||
/// Current moment in time
|
|
||||||
pub(crate) current: Arc<Instant>,
|
|
||||||
/// Note quantization factor
|
|
||||||
pub(crate) quant: Arc<Quantize>,
|
|
||||||
/// Launch quantization factor
|
|
||||||
pub(crate) sync: Arc<LaunchSync>,
|
|
||||||
/// TODO: Enable metronome?
|
|
||||||
pub(crate) metronome: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
metronome: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -46,3 +46,9 @@ impl From<&ClockModel> for PhrasePlayerModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasClock for PhrasePlayerModel {
|
||||||
|
fn clock (&self) -> &ClockModel {
|
||||||
|
&self.clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ pub fn arranger_content_vertical (
|
||||||
view: &ArrangerTui,
|
view: &ArrangerTui,
|
||||||
factor: usize
|
factor: usize
|
||||||
) -> impl Widget<Engine = Tui> + use<'_> {
|
) -> impl Widget<Engine = Tui> + use<'_> {
|
||||||
let timebase = view.timebase();
|
let timebase = view.clock().timebase();
|
||||||
let current = view.current();
|
let current = &view.clock().current;
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -54,22 +54,18 @@ impl Content for TransportView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> From<&'a T> for TransportView
|
impl<'a, T: HasClock> From<&'a T> for TransportView where Option<TransportFocus>: From<&'a T> {
|
||||||
where
|
|
||||||
T: ClockApi,
|
|
||||||
Option<TransportFocus>: From<&'a T>
|
|
||||||
{
|
|
||||||
fn from (state: &'a T) -> Self {
|
fn from (state: &'a T) -> Self {
|
||||||
let selected = state.into();
|
let selected = state.into();
|
||||||
Self {
|
Self {
|
||||||
selected,
|
selected,
|
||||||
focused: selected.is_some(),
|
focused: selected.is_some(),
|
||||||
state: state.transport_state().read().unwrap().clone(),
|
state: state.clock().playing.read().unwrap().clone(),
|
||||||
bpm: state.bpm().get(),
|
bpm: state.clock().bpm().get(),
|
||||||
sync: state.sync().get(),
|
sync: state.clock().sync.get(),
|
||||||
quant: state.quant().get(),
|
quant: state.clock().quant.get(),
|
||||||
beat: state.current().format_beat(),
|
beat: state.clock().current.format_beat(),
|
||||||
msu: state.current().usec.format_msu(),
|
msu: state.clock().current.usec.format_msu(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue