mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
stub out some of the edn command readers
This commit is contained in:
parent
1aa0551931
commit
4fb703d05d
8 changed files with 381 additions and 287 deletions
|
|
@ -20,6 +20,7 @@ pub(crate) use ::tek_tui::{
|
|||
*,
|
||||
tek_input::*,
|
||||
tek_output::*,
|
||||
tek_edn::*,
|
||||
crossterm::event::*,
|
||||
ratatui::style::{Style, Stylize, Color}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -152,6 +152,11 @@ pub enum MidiEditCommand {
|
|||
SetTimeLock(bool),
|
||||
Show(Option<Arc<RwLock<MidiClip>>>),
|
||||
}
|
||||
impl MidiEditCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
handle!(TuiIn: |self: MidiEditor, input|MidiEditCommand::execute_with_state(self, input.event()));
|
||||
keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand {
|
||||
key(Up) => SetNoteCursor(s.note_point() + 1),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ pub enum MidiPoolCommand {
|
|||
SetLength(usize, usize),
|
||||
SetColor(usize, ItemColor),
|
||||
}
|
||||
impl MidiPoolCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasClips> Command<T> for MidiPoolCommand {
|
||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||
|
|
@ -191,6 +196,11 @@ pub enum PoolCommand {
|
|||
/// Export to file
|
||||
Export(FileBrowserCommand),
|
||||
}
|
||||
impl PoolCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
command!(|self:PoolCommand, state: PoolModel|{
|
||||
use PoolCommand::*;
|
||||
|
|
|
|||
|
|
@ -663,6 +663,11 @@ handle!(TuiIn: |self: SamplerTui, input|SamplerTuiCommand::execute_with_state(se
|
|||
Select(usize),
|
||||
Sample(SamplerCommand),
|
||||
}
|
||||
impl SamplerTuiCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)] pub enum SamplerCommand {
|
||||
RecordBegin(u7),
|
||||
|
|
@ -674,6 +679,11 @@ handle!(TuiIn: |self: SamplerTui, input|SamplerTuiCommand::execute_with_state(se
|
|||
NoteOn(u7, u7),
|
||||
NoteOff(u7),
|
||||
}
|
||||
impl SamplerCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
input_to_command!(SamplerTuiCommand: |state: SamplerTui, input: Event|match state.mode{
|
||||
Some(SamplerMode::Import(..)) => Self::Import(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::*;
|
||||
use EdnItem::*;
|
||||
use ClockCommand::{Play, Pause};
|
||||
use KeyCode::{Tab, Char};
|
||||
use SequencerCommand as SeqCmd;
|
||||
|
|
@ -15,7 +16,7 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
|||
|
||||
#[derive(Clone, Debug)] pub enum AppCommand {
|
||||
Clear,
|
||||
Clip(ArrangerClipCommand),
|
||||
Clip(ClipCommand),
|
||||
Clock(ClockCommand),
|
||||
Color(ItemPalette),
|
||||
Compact(bool),
|
||||
|
|
@ -24,12 +25,101 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
|||
History(isize),
|
||||
Pool(PoolCommand),
|
||||
Sampler(SamplerCommand),
|
||||
Scene(ArrangerSceneCommand),
|
||||
Scene(SceneCommand),
|
||||
Select(ArrangerSelection),
|
||||
StopAll,
|
||||
Track(ArrangerTrackCommand),
|
||||
Track(TrackCommand),
|
||||
Zoom(usize),
|
||||
}
|
||||
impl AppCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
match (head, tail) {
|
||||
(Key("clear"), [ ]) => Self::Clear,
|
||||
(Key("clip"), [a, b @ ..]) => Self::Clip(ClipCommand::from_edn(&a.to_ref(), b)),
|
||||
(Key("clock"), [a, b @ ..]) => Self::Clock(ClockCommand::from_edn(&a.to_ref(), b)),
|
||||
(Key("color"), [a ]) => Self::Color(ItemPalette::default()),
|
||||
(Key("compact"), [a ]) => Self::Compact(true),
|
||||
(Key("editor"), [a, b @ ..]) => Self::Editor(MidiEditCommand::from_edn(&a.to_ref(), b)),
|
||||
(Key("enqueue"), [a ]) => Self::Enqueue(None),
|
||||
(Key("history"), [a ]) => Self::History(0),
|
||||
(Key("pool"), [a, b @ ..]) => Self::Pool(PoolCommand::from_edn(&a.to_ref(), b)),
|
||||
(Key("sampler"), [a, b @ ..]) => Self::Sampler(SamplerCommand::from_edn(&a.to_ref(), b)),
|
||||
(Key("scene"), [a, b @ ..]) => Self::Scene(SceneCommand::from_edn(&a.to_ref(), b)),
|
||||
(Key("select"), [a ]) => Self::Select(ArrangerSelection::Mix),
|
||||
(Key("stop-all"), [ ]) => Self::StopAll,
|
||||
(Key("track"), [a, b @ ..]) => Self::Track(TrackCommand::from_edn(&a.to_ref(), b)),
|
||||
(Key("zoom"), [a ]) => Self::Zoom(0),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)] pub enum ClipCommand {
|
||||
Get(usize, usize),
|
||||
Put(usize, usize, Option<Arc<RwLock<MidiClip>>>),
|
||||
Enqueue(usize, usize),
|
||||
Edit(Option<Arc<RwLock<MidiClip>>>),
|
||||
SetLoop(usize, usize, bool),
|
||||
SetColor(usize, usize, ItemPalette),
|
||||
}
|
||||
impl ClipCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
match (head, tail) {
|
||||
(Key("get"), [a, b ]) => Self::Get(0, 0),
|
||||
(Key("put"), [a, b, c ]) => Self::Put(0, 0, None),
|
||||
(Key("enqueue"), [a, b ]) => Self::Enqueue(0, 0),
|
||||
(Key("edit"), [a ]) => Self::Edit(None),
|
||||
(Key("loop"), [a, b, c ]) => Self::SetLoop(0, 0, true),
|
||||
(Key("color"), [a, b, c ]) => Self::SetColor(0, 0, ItemPalette::random()),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)] pub enum SceneCommand {
|
||||
Add,
|
||||
Del(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
SetColor(usize, ItemPalette),
|
||||
Enqueue(usize),
|
||||
}
|
||||
impl SceneCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
match (head, tail) {
|
||||
(Key("add"), [ ]) => Self::Add,
|
||||
(Key("del"), [a ]) => Self::Del(0),
|
||||
(Key("swap"), [a, b ]) => Self::Swap(0, 0),
|
||||
(Key("size"), [a ]) => Self::SetSize(0),
|
||||
(Key("zoom"), [a, ]) => Self::SetZoom(0),
|
||||
(Key("color"), [a, b, ]) => Self::SetColor(0, ItemPalette::random()),
|
||||
(Key("enqueue"), [a, ]) => Self::Enqueue(0),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)] pub enum TrackCommand {
|
||||
Add,
|
||||
Del(usize),
|
||||
Stop(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
SetColor(usize, ItemPalette),
|
||||
}
|
||||
impl TrackCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
match (head, tail) {
|
||||
(Key("add"), [ ]) => Self::Add,
|
||||
(Key("del"), [a ]) => Self::Del(0),
|
||||
(Key("stop"), [a ]) => Self::Stop(0),
|
||||
(Key("swap"), [a, b ]) => Self::Swap(0, 0),
|
||||
(Key("size"), [a ]) => Self::SetSize(0),
|
||||
(Key("zoom"), [a, ]) => Self::SetZoom(0),
|
||||
(Key("color"), [a, b, ]) => Self::SetColor(0, ItemPalette::random()),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)] pub enum SequencerCommand {
|
||||
Compact(bool),
|
||||
History(isize),
|
||||
|
|
@ -51,9 +141,9 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
|||
History(isize),
|
||||
Color(ItemPalette),
|
||||
Clock(ClockCommand),
|
||||
Scene(ArrangerSceneCommand),
|
||||
Track(ArrangerTrackCommand),
|
||||
Clip(ArrangerClipCommand),
|
||||
Scene(SceneCommand),
|
||||
Track(TrackCommand),
|
||||
Clip(ClipCommand),
|
||||
Select(ArrangerSelection),
|
||||
Zoom(usize),
|
||||
Pool(PoolCommand),
|
||||
|
|
@ -61,32 +151,7 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
|||
StopAll,
|
||||
Clear,
|
||||
}
|
||||
#[derive(Clone, Debug)] pub enum ArrangerClipCommand {
|
||||
Get(usize, usize),
|
||||
Put(usize, usize, Option<Arc<RwLock<MidiClip>>>),
|
||||
Enqueue(usize, usize),
|
||||
Edit(Option<Arc<RwLock<MidiClip>>>),
|
||||
SetLoop(usize, usize, bool),
|
||||
SetColor(usize, usize, ItemPalette),
|
||||
}
|
||||
#[derive(Clone, Debug)] pub enum ArrangerSceneCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
SetColor(usize, ItemPalette),
|
||||
Enqueue(usize),
|
||||
}
|
||||
#[derive(Clone, Debug)] pub enum ArrangerTrackCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
Stop(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
SetColor(usize, ItemPalette),
|
||||
}
|
||||
|
||||
|
||||
command!(|self: SequencerCommand, state: Sequencer|match self {
|
||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||
|
|
@ -183,9 +248,9 @@ command!(|self: ArrangerCommand, state: Arranger|match self {
|
|||
}
|
||||
},
|
||||
});
|
||||
command!(|self: ArrangerSceneCommand, state: Arranger|match self {
|
||||
command!(|self: SceneCommand, state: Arranger|match self {
|
||||
Self::Add => { state.scene_add(None, None)?; None }
|
||||
Self::Delete(index) => { state.scene_del(index); None },
|
||||
Self::Del(index) => { state.scene_del(index); None },
|
||||
Self::SetColor(index, color) => {
|
||||
let old = state.scenes[index].color;
|
||||
state.scenes[index].color = color;
|
||||
|
|
@ -199,9 +264,9 @@ command!(|self: ArrangerSceneCommand, state: Arranger|match self {
|
|||
},
|
||||
_ => None
|
||||
});
|
||||
command!(|self: ArrangerTrackCommand, state: Arranger|match self {
|
||||
command!(|self: TrackCommand, state: Arranger|match self {
|
||||
Self::Add => { state.track_add(None, None)?; None },
|
||||
Self::Delete(index) => { state.track_del(index); None },
|
||||
Self::Del(index) => { state.track_del(index); None },
|
||||
Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None },
|
||||
Self::SetColor(index, color) => {
|
||||
let old = state.tracks[index].color;
|
||||
|
|
@ -210,7 +275,7 @@ command!(|self: ArrangerTrackCommand, state: Arranger|match self {
|
|||
},
|
||||
_ => None
|
||||
});
|
||||
command!(|self: ArrangerClipCommand, state: Arranger|match self {
|
||||
command!(|self: ClipCommand, state: Arranger|match self {
|
||||
Self::Get(track, scene) => { todo!() },
|
||||
Self::Put(track, scene, clip) => {
|
||||
let old = state.scenes[scene].clips[track].clone();
|
||||
|
|
@ -311,16 +376,16 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
|
|||
// Transport: Play from start or rewind to start
|
||||
shift(key(Char(' '))) => ArrCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
key(Char('e')) => ArrCmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())),
|
||||
ctrl(key(Char('a'))) => ArrCmd::Scene(ArrangerSceneCommand::Add),
|
||||
ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(ArrangerSceneCommand::Add),
|
||||
ctrl(key(Char('t'))) => ArrCmd::Track(ArrangerTrackCommand::Add),
|
||||
ctrl(key(Char('a'))) => ArrCmd::Scene(SceneCommand::Add),
|
||||
ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(SceneCommand::Add),
|
||||
ctrl(key(Char('t'))) => ArrCmd::Track(TrackCommand::Add),
|
||||
// Tab: Toggle visibility of clip pool column
|
||||
key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)),
|
||||
}, {
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
use SceneCommand as Scene;
|
||||
use TrackCommand as Track;
|
||||
use ClipCommand as Clip;
|
||||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
match state.selected {
|
||||
|
|
@ -351,9 +416,9 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
|
|||
|
||||
fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<ArrangerCommand> {
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
use SceneCommand as Scene;
|
||||
use TrackCommand as Track;
|
||||
use ClipCommand as Clip;
|
||||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
Some(match input {
|
||||
|
|
@ -386,9 +451,9 @@ fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<A
|
|||
}
|
||||
fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCommand> {
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
use SceneCommand as Scene;
|
||||
use TrackCommand as Track;
|
||||
use ClipCommand as Clip;
|
||||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
Some(match input {
|
||||
|
|
@ -398,7 +463,7 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
|
|||
kpat!(Char('<')) => ArrCmd::Scene(Scene::Swap(s, s - 1)),
|
||||
kpat!(Char('>')) => ArrCmd::Scene(Scene::Swap(s, s + 1)),
|
||||
kpat!(Char('q')) => ArrCmd::Scene(Scene::Enqueue(s)),
|
||||
kpat!(Delete) => ArrCmd::Scene(Scene::Delete(s)),
|
||||
kpat!(Delete) => ArrCmd::Scene(Scene::Del(s)),
|
||||
kpat!(Char('c')) => ArrCmd::Scene(Scene::SetColor(s, ItemPalette::random())),
|
||||
|
||||
kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }),
|
||||
|
|
@ -411,9 +476,9 @@ fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCo
|
|||
}
|
||||
fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCommand> {
|
||||
use ArrangerSelection as Selected;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
use SceneCommand as Scene;
|
||||
use TrackCommand as Track;
|
||||
use ClipCommand as Clip;
|
||||
let t_len = state.tracks.len();
|
||||
let s_len = state.scenes.len();
|
||||
Some(match input {
|
||||
|
|
@ -422,7 +487,7 @@ fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCo
|
|||
kpat!(Char('.')) => ArrCmd::Track(Track::Swap(t, t + 1)),
|
||||
kpat!(Char('<')) => ArrCmd::Track(Track::Swap(t, t - 1)),
|
||||
kpat!(Char('>')) => ArrCmd::Track(Track::Swap(t, t + 1)),
|
||||
kpat!(Delete) => ArrCmd::Track(Track::Delete(t)),
|
||||
kpat!(Delete) => ArrCmd::Track(Track::Del(t)),
|
||||
kpat!(Char('c')) => ArrCmd::Track(Track::SetColor(t, ItemPalette::random())),
|
||||
|
||||
kpat!(Up) => return None,
|
||||
|
|
|
|||
223
time/src/clock.rs
Normal file
223
time/src/clock.rs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
use crate::*;
|
||||
use EdnItem::*;
|
||||
|
||||
pub trait HasClock: Send + Sync {
|
||||
fn clock (&self) -> &Clock;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clock {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clock (&$self) -> &Clock { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts the JACK callback for updating the temporal pointer and playback status.
|
||||
pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T);
|
||||
|
||||
impl<T: HasClock> Audio for ClockAudio<'_, T> {
|
||||
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
self.0.clock().update_from_scope(scope).unwrap();
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ClockCommand {
|
||||
Play(Option<u32>),
|
||||
Pause(Option<u32>),
|
||||
SeekUsec(f64),
|
||||
SeekSample(f64),
|
||||
SeekPulse(f64),
|
||||
SetBpm(f64),
|
||||
SetQuant(f64),
|
||||
SetSync(f64),
|
||||
}
|
||||
impl ClockCommand {
|
||||
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasClock> Command<T> for ClockCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
use ClockCommand::*;
|
||||
match self {
|
||||
Play(start) => state.clock().play_from(start)?,
|
||||
Pause(pause) => state.clock().pause_at(pause)?,
|
||||
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)))),
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Clock {
|
||||
/// JACK transport handle.
|
||||
pub transport: Arc<Option<Transport>>,
|
||||
/// Global temporal resolution (shared by [Moment] fields)
|
||||
pub timebase: Arc<Timebase>,
|
||||
/// Current global sample and usec (monotonic from JACK clock)
|
||||
pub global: Arc<Moment>,
|
||||
/// Global sample and usec at which playback started
|
||||
pub started: Arc<RwLock<Option<Moment>>>,
|
||||
/// Playback offset (when playing not from start)
|
||||
pub offset: Arc<Moment>,
|
||||
/// Current playhead position
|
||||
pub playhead: Arc<Moment>,
|
||||
/// Note quantization factor
|
||||
pub quant: Arc<Quantize>,
|
||||
/// Launch quantization factor
|
||||
pub sync: Arc<LaunchSync>,
|
||||
/// Size of buffer in samples
|
||||
pub chunk: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
from!(|jack: &Arc<RwLock<JackConnection>>| Clock = {
|
||||
let jack = jack.read().unwrap();
|
||||
let chunk = jack.client().buffer_size();
|
||||
let transport = jack.client().transport();
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
Self {
|
||||
quant: Arc::new(24.into()),
|
||||
sync: Arc::new(384.into()),
|
||||
transport: Arc::new(Some(transport)),
|
||||
chunk: Arc::new((chunk as usize).into()),
|
||||
global: Arc::new(Moment::zero(&timebase)),
|
||||
playhead: Arc::new(Moment::zero(&timebase)),
|
||||
offset: Arc::new(Moment::zero(&timebase)),
|
||||
started: RwLock::new(None).into(),
|
||||
timebase,
|
||||
}
|
||||
});
|
||||
|
||||
impl std::fmt::Debug for Clock {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("Clock")
|
||||
.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 Clock {
|
||||
pub fn timebase (&self) -> &Arc<Timebase> {
|
||||
&self.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.playhead.pulse.get() as usize;
|
||||
if pulse % sync == 0 {
|
||||
pulse
|
||||
} else {
|
||||
(pulse / sync + 1) * sync
|
||||
}
|
||||
}
|
||||
/// Start playing, optionally seeking to a given location beforehand
|
||||
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
if let Some(start) = start {
|
||||
transport.locate(start)?;
|
||||
}
|
||||
transport.start()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Pause, optionally seeking to a given location afterwards
|
||||
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
transport.stop()?;
|
||||
if let Some(pause) = pause {
|
||||
transport.locate(pause)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Is currently paused?
|
||||
pub fn is_stopped (&self) -> bool {
|
||||
self.started.read().unwrap().is_none()
|
||||
}
|
||||
/// Is currently playing?
|
||||
pub fn is_rolling (&self) -> bool {
|
||||
self.started.read().unwrap().is_some()
|
||||
}
|
||||
/// Update chunk size
|
||||
pub fn set_chunk (&self, n_frames: usize) {
|
||||
self.chunk.store(n_frames, Relaxed);
|
||||
}
|
||||
pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> {
|
||||
// Store buffer length
|
||||
self.set_chunk(scope.n_frames() as usize);
|
||||
|
||||
// Store reported global frame and usec
|
||||
let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?;
|
||||
self.global.sample.set(current_frames as f64);
|
||||
self.global.usec.set(current_usecs as f64);
|
||||
|
||||
let mut started = self.started.write().unwrap();
|
||||
|
||||
// If transport has just started or just stopped,
|
||||
// update starting point:
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
match (transport.query_state()?, started.as_ref()) {
|
||||
(TransportState::Rolling, None) => {
|
||||
let moment = Moment::zero(&self.timebase);
|
||||
moment.sample.set(current_frames as f64);
|
||||
moment.usec.set(current_usecs as f64);
|
||||
*started = Some(moment);
|
||||
},
|
||||
(TransportState::Stopped, Some(_)) => {
|
||||
*started = None;
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
self.playhead.update_from_sample(started.as_ref()
|
||||
.map(|started|current_frames as f64 - started.sample.get())
|
||||
.unwrap_or(0.));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bbt (&self) -> PositionBBT {
|
||||
let pulse = self.playhead.pulse.get() as i32;
|
||||
let ppq = self.timebase.ppq.get() as i32;
|
||||
let bpm = self.timebase.bpm.get();
|
||||
let bar = (pulse / ppq) / 4;
|
||||
PositionBBT {
|
||||
bar: 1 + bar,
|
||||
beat: 1 + (pulse / ppq) % 4,
|
||||
tick: (pulse % ppq),
|
||||
bar_start_tick: (bar * 4 * ppq) as f64,
|
||||
beat_type: 4.,
|
||||
beats_per_bar: 4.,
|
||||
beats_per_minute: bpm,
|
||||
ticks_per_beat: ppq as f64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::*;
|
||||
use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync};
|
||||
//use FocusCommand::{Next, Prev};
|
||||
use KeyCode::{Enter, Left, Right, Char};
|
||||
use KeyCode::*;
|
||||
use ClockCommand::{Play, Pause};
|
||||
/// Transport clock app.
|
||||
pub struct TransportTui {
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
|
|
|
|||
233
time/src/lib.rs
233
time/src/lib.rs
|
|
@ -1,3 +1,4 @@
|
|||
mod clock; pub use self::clock::*;
|
||||
mod clock_tui; pub use self::clock_tui::*;
|
||||
mod microsecond; pub use self::microsecond::*;
|
||||
mod moment; pub use self::moment::*;
|
||||
|
|
@ -15,234 +16,14 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
|||
pub use ::atomic_float; pub(crate) use atomic_float::*;
|
||||
pub(crate) use ::tek_tui::{
|
||||
*,
|
||||
tek_input::*,
|
||||
tek_output::*,
|
||||
crossterm::event::KeyCode,
|
||||
ratatui::prelude::*
|
||||
tek_input::*,
|
||||
tek_edn::*,
|
||||
ratatui::prelude::*,
|
||||
crossterm::event::*,
|
||||
};
|
||||
|
||||
pub trait HasClock: Send + Sync {
|
||||
fn clock (&self) -> &Clock;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clock {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clock (&$self) -> &Clock { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts the JACK callback for updating the temporal pointer and playback status.
|
||||
pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T);
|
||||
|
||||
impl<T: HasClock> Audio for ClockAudio<'_, T> {
|
||||
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
self.0.clock().update_from_scope(scope).unwrap();
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ClockCommand {
|
||||
Play(Option<u32>),
|
||||
Pause(Option<u32>),
|
||||
SeekUsec(f64),
|
||||
SeekSample(f64),
|
||||
SeekPulse(f64),
|
||||
SetBpm(f64),
|
||||
SetQuant(f64),
|
||||
SetSync(f64),
|
||||
}
|
||||
|
||||
impl<T: HasClock> Command<T> for ClockCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
use ClockCommand::*;
|
||||
match self {
|
||||
Play(start) => state.clock().play_from(start)?,
|
||||
Pause(pause) => state.clock().pause_at(pause)?,
|
||||
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)))),
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Clock {
|
||||
/// JACK transport handle.
|
||||
pub transport: Arc<Option<Transport>>,
|
||||
/// Global temporal resolution (shared by [Moment] fields)
|
||||
pub timebase: Arc<Timebase>,
|
||||
/// Current global sample and usec (monotonic from JACK clock)
|
||||
pub global: Arc<Moment>,
|
||||
/// Global sample and usec at which playback started
|
||||
pub started: Arc<RwLock<Option<Moment>>>,
|
||||
/// Playback offset (when playing not from start)
|
||||
pub offset: Arc<Moment>,
|
||||
/// Current playhead position
|
||||
pub playhead: Arc<Moment>,
|
||||
/// Note quantization factor
|
||||
pub quant: Arc<Quantize>,
|
||||
/// Launch quantization factor
|
||||
pub sync: Arc<LaunchSync>,
|
||||
/// Size of buffer in samples
|
||||
pub chunk: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
from!(|jack: &Arc<RwLock<JackConnection>>| Clock = {
|
||||
let jack = jack.read().unwrap();
|
||||
let chunk = jack.client().buffer_size();
|
||||
let transport = jack.client().transport();
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
Self {
|
||||
quant: Arc::new(24.into()),
|
||||
sync: Arc::new(384.into()),
|
||||
transport: Arc::new(Some(transport)),
|
||||
chunk: Arc::new((chunk as usize).into()),
|
||||
global: Arc::new(Moment::zero(&timebase)),
|
||||
playhead: Arc::new(Moment::zero(&timebase)),
|
||||
offset: Arc::new(Moment::zero(&timebase)),
|
||||
started: RwLock::new(None).into(),
|
||||
timebase,
|
||||
}
|
||||
});
|
||||
|
||||
impl std::fmt::Debug for Clock {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("Clock")
|
||||
.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 Clock {
|
||||
pub fn timebase (&self) -> &Arc<Timebase> {
|
||||
&self.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.playhead.pulse.get() as usize;
|
||||
if pulse % sync == 0 {
|
||||
pulse
|
||||
} else {
|
||||
(pulse / sync + 1) * sync
|
||||
}
|
||||
}
|
||||
/// Start playing, optionally seeking to a given location beforehand
|
||||
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
if let Some(start) = start {
|
||||
transport.locate(start)?;
|
||||
}
|
||||
transport.start()?;
|
||||
}
|
||||
#[cfg(test)] #[test] fn test_time () -> Usually<()> {
|
||||
// TODO!
|
||||
Ok(())
|
||||
}
|
||||
/// Pause, optionally seeking to a given location afterwards
|
||||
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
transport.stop()?;
|
||||
if let Some(pause) = pause {
|
||||
transport.locate(pause)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Is currently paused?
|
||||
pub fn is_stopped (&self) -> bool {
|
||||
self.started.read().unwrap().is_none()
|
||||
}
|
||||
/// Is currently playing?
|
||||
pub fn is_rolling (&self) -> bool {
|
||||
self.started.read().unwrap().is_some()
|
||||
}
|
||||
/// Update chunk size
|
||||
pub fn set_chunk (&self, n_frames: usize) {
|
||||
self.chunk.store(n_frames, Relaxed);
|
||||
}
|
||||
pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> {
|
||||
// Store buffer length
|
||||
self.set_chunk(scope.n_frames() as usize);
|
||||
|
||||
// Store reported global frame and usec
|
||||
let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?;
|
||||
self.global.sample.set(current_frames as f64);
|
||||
self.global.usec.set(current_usecs as f64);
|
||||
|
||||
let mut started = self.started.write().unwrap();
|
||||
|
||||
// If transport has just started or just stopped,
|
||||
// update starting point:
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
match (transport.query_state()?, started.as_ref()) {
|
||||
(TransportState::Rolling, None) => {
|
||||
let moment = Moment::zero(&self.timebase);
|
||||
moment.sample.set(current_frames as f64);
|
||||
moment.usec.set(current_usecs as f64);
|
||||
*started = Some(moment);
|
||||
},
|
||||
(TransportState::Stopped, Some(_)) => {
|
||||
*started = None;
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
self.playhead.update_from_sample(started.as_ref()
|
||||
.map(|started|current_frames as f64 - started.sample.get())
|
||||
.unwrap_or(0.));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bbt (&self) -> PositionBBT {
|
||||
let pulse = self.playhead.pulse.get() as i32;
|
||||
let ppq = self.timebase.ppq.get() as i32;
|
||||
let bpm = self.timebase.bpm.get();
|
||||
let bar = (pulse / ppq) / 4;
|
||||
PositionBBT {
|
||||
bar: 1 + bar,
|
||||
beat: 1 + (pulse / ppq) % 4,
|
||||
tick: (pulse % ppq),
|
||||
bar_start_tick: (bar * 4 * ppq) as f64,
|
||||
beat_type: 4.,
|
||||
beats_per_bar: 4.,
|
||||
beats_per_minute: bpm,
|
||||
ticks_per_beat: ppq as f64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#[cfg(test)]
|
||||
//mod test {
|
||||
//use super::*;
|
||||
//#[test]
|
||||
//fn test_samples_to_ticks () {
|
||||
//let ticks = Ticks(12.3).between_samples(0, 100).collect::<Vec<_>>();
|
||||
//println!("{ticks:?}");
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue