mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +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_input::*,
|
||||||
tek_output::*,
|
tek_output::*,
|
||||||
|
tek_edn::*,
|
||||||
crossterm::event::*,
|
crossterm::event::*,
|
||||||
ratatui::style::{Style, Stylize, Color}
|
ratatui::style::{Style, Stylize, Color}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,11 @@ pub enum MidiEditCommand {
|
||||||
SetTimeLock(bool),
|
SetTimeLock(bool),
|
||||||
Show(Option<Arc<RwLock<MidiClip>>>),
|
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()));
|
handle!(TuiIn: |self: MidiEditor, input|MidiEditCommand::execute_with_state(self, input.event()));
|
||||||
keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand {
|
keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand {
|
||||||
key(Up) => SetNoteCursor(s.note_point() + 1),
|
key(Up) => SetNoteCursor(s.note_point() + 1),
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@ pub enum MidiPoolCommand {
|
||||||
SetLength(usize, usize),
|
SetLength(usize, usize),
|
||||||
SetColor(usize, ItemColor),
|
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 {
|
impl<T: HasClips> Command<T> for MidiPoolCommand {
|
||||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||||
|
|
@ -191,6 +196,11 @@ pub enum PoolCommand {
|
||||||
/// Export to file
|
/// Export to file
|
||||||
Export(FileBrowserCommand),
|
Export(FileBrowserCommand),
|
||||||
}
|
}
|
||||||
|
impl PoolCommand {
|
||||||
|
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
command!(|self:PoolCommand, state: PoolModel|{
|
command!(|self:PoolCommand, state: PoolModel|{
|
||||||
use PoolCommand::*;
|
use PoolCommand::*;
|
||||||
|
|
|
||||||
|
|
@ -663,6 +663,11 @@ handle!(TuiIn: |self: SamplerTui, input|SamplerTuiCommand::execute_with_state(se
|
||||||
Select(usize),
|
Select(usize),
|
||||||
Sample(SamplerCommand),
|
Sample(SamplerCommand),
|
||||||
}
|
}
|
||||||
|
impl SamplerTuiCommand {
|
||||||
|
pub fn from_edn <'a> (head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)] pub enum SamplerCommand {
|
#[derive(Clone, Debug)] pub enum SamplerCommand {
|
||||||
RecordBegin(u7),
|
RecordBegin(u7),
|
||||||
|
|
@ -674,6 +679,11 @@ handle!(TuiIn: |self: SamplerTui, input|SamplerTuiCommand::execute_with_state(se
|
||||||
NoteOn(u7, u7),
|
NoteOn(u7, u7),
|
||||||
NoteOff(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{
|
input_to_command!(SamplerTuiCommand: |state: SamplerTui, input: Event|match state.mode{
|
||||||
Some(SamplerMode::Import(..)) => Self::Import(
|
Some(SamplerMode::Import(..)) => Self::Import(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use EdnItem::*;
|
||||||
use ClockCommand::{Play, Pause};
|
use ClockCommand::{Play, Pause};
|
||||||
use KeyCode::{Tab, Char};
|
use KeyCode::{Tab, Char};
|
||||||
use SequencerCommand as SeqCmd;
|
use SequencerCommand as SeqCmd;
|
||||||
|
|
@ -15,7 +16,7 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
||||||
|
|
||||||
#[derive(Clone, Debug)] pub enum AppCommand {
|
#[derive(Clone, Debug)] pub enum AppCommand {
|
||||||
Clear,
|
Clear,
|
||||||
Clip(ArrangerClipCommand),
|
Clip(ClipCommand),
|
||||||
Clock(ClockCommand),
|
Clock(ClockCommand),
|
||||||
Color(ItemPalette),
|
Color(ItemPalette),
|
||||||
Compact(bool),
|
Compact(bool),
|
||||||
|
|
@ -24,12 +25,101 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
||||||
History(isize),
|
History(isize),
|
||||||
Pool(PoolCommand),
|
Pool(PoolCommand),
|
||||||
Sampler(SamplerCommand),
|
Sampler(SamplerCommand),
|
||||||
Scene(ArrangerSceneCommand),
|
Scene(SceneCommand),
|
||||||
Select(ArrangerSelection),
|
Select(ArrangerSelection),
|
||||||
StopAll,
|
StopAll,
|
||||||
Track(ArrangerTrackCommand),
|
Track(TrackCommand),
|
||||||
Zoom(usize),
|
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 {
|
#[derive(Clone, Debug)] pub enum SequencerCommand {
|
||||||
Compact(bool),
|
Compact(bool),
|
||||||
History(isize),
|
History(isize),
|
||||||
|
|
@ -51,9 +141,9 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
||||||
History(isize),
|
History(isize),
|
||||||
Color(ItemPalette),
|
Color(ItemPalette),
|
||||||
Clock(ClockCommand),
|
Clock(ClockCommand),
|
||||||
Scene(ArrangerSceneCommand),
|
Scene(SceneCommand),
|
||||||
Track(ArrangerTrackCommand),
|
Track(TrackCommand),
|
||||||
Clip(ArrangerClipCommand),
|
Clip(ClipCommand),
|
||||||
Select(ArrangerSelection),
|
Select(ArrangerSelection),
|
||||||
Zoom(usize),
|
Zoom(usize),
|
||||||
Pool(PoolCommand),
|
Pool(PoolCommand),
|
||||||
|
|
@ -61,32 +151,7 @@ handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self,
|
||||||
StopAll,
|
StopAll,
|
||||||
Clear,
|
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 {
|
command!(|self: SequencerCommand, state: Sequencer|match self {
|
||||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
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::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) => {
|
Self::SetColor(index, color) => {
|
||||||
let old = state.scenes[index].color;
|
let old = state.scenes[index].color;
|
||||||
state.scenes[index].color = color;
|
state.scenes[index].color = color;
|
||||||
|
|
@ -199,9 +264,9 @@ command!(|self: ArrangerSceneCommand, state: Arranger|match self {
|
||||||
},
|
},
|
||||||
_ => None
|
_ => None
|
||||||
});
|
});
|
||||||
command!(|self: ArrangerTrackCommand, state: Arranger|match self {
|
command!(|self: TrackCommand, state: Arranger|match self {
|
||||||
Self::Add => { state.track_add(None, None)?; None },
|
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::Stop(track) => { state.tracks[track].player.enqueue_next(None); None },
|
||||||
Self::SetColor(index, color) => {
|
Self::SetColor(index, color) => {
|
||||||
let old = state.tracks[index].color;
|
let old = state.tracks[index].color;
|
||||||
|
|
@ -210,7 +275,7 @@ command!(|self: ArrangerTrackCommand, state: Arranger|match self {
|
||||||
},
|
},
|
||||||
_ => None
|
_ => None
|
||||||
});
|
});
|
||||||
command!(|self: ArrangerClipCommand, state: Arranger|match self {
|
command!(|self: ClipCommand, state: Arranger|match self {
|
||||||
Self::Get(track, scene) => { todo!() },
|
Self::Get(track, scene) => { todo!() },
|
||||||
Self::Put(track, scene, clip) => {
|
Self::Put(track, scene, clip) => {
|
||||||
let old = state.scenes[scene].clips[track].clone();
|
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
|
// 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)) }),
|
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())),
|
key(Char('e')) => ArrCmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())),
|
||||||
ctrl(key(Char('a'))) => ArrCmd::Scene(ArrangerSceneCommand::Add),
|
ctrl(key(Char('a'))) => ArrCmd::Scene(SceneCommand::Add),
|
||||||
ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(ArrangerSceneCommand::Add),
|
ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(SceneCommand::Add),
|
||||||
ctrl(key(Char('t'))) => ArrCmd::Track(ArrangerTrackCommand::Add),
|
ctrl(key(Char('t'))) => ArrCmd::Track(TrackCommand::Add),
|
||||||
// Tab: Toggle visibility of clip pool column
|
// Tab: Toggle visibility of clip pool column
|
||||||
key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)),
|
key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)),
|
||||||
}, {
|
}, {
|
||||||
use ArrangerSelection as Selected;
|
use ArrangerSelection as Selected;
|
||||||
use ArrangerSceneCommand as Scene;
|
use SceneCommand as Scene;
|
||||||
use ArrangerTrackCommand as Track;
|
use TrackCommand as Track;
|
||||||
use ArrangerClipCommand as Clip;
|
use ClipCommand as Clip;
|
||||||
let t_len = state.tracks.len();
|
let t_len = state.tracks.len();
|
||||||
let s_len = state.scenes.len();
|
let s_len = state.scenes.len();
|
||||||
match state.selected {
|
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> {
|
fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<ArrangerCommand> {
|
||||||
use ArrangerSelection as Selected;
|
use ArrangerSelection as Selected;
|
||||||
use ArrangerSceneCommand as Scene;
|
use SceneCommand as Scene;
|
||||||
use ArrangerTrackCommand as Track;
|
use TrackCommand as Track;
|
||||||
use ArrangerClipCommand as Clip;
|
use ClipCommand as Clip;
|
||||||
let t_len = state.tracks.len();
|
let t_len = state.tracks.len();
|
||||||
let s_len = state.scenes.len();
|
let s_len = state.scenes.len();
|
||||||
Some(match input {
|
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> {
|
fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCommand> {
|
||||||
use ArrangerSelection as Selected;
|
use ArrangerSelection as Selected;
|
||||||
use ArrangerSceneCommand as Scene;
|
use SceneCommand as Scene;
|
||||||
use ArrangerTrackCommand as Track;
|
use TrackCommand as Track;
|
||||||
use ArrangerClipCommand as Clip;
|
use ClipCommand as Clip;
|
||||||
let t_len = state.tracks.len();
|
let t_len = state.tracks.len();
|
||||||
let s_len = state.scenes.len();
|
let s_len = state.scenes.len();
|
||||||
Some(match input {
|
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('>')) => 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!(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!(Char('c')) => ArrCmd::Scene(Scene::SetColor(s, ItemPalette::random())),
|
||||||
|
|
||||||
kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }),
|
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> {
|
fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCommand> {
|
||||||
use ArrangerSelection as Selected;
|
use ArrangerSelection as Selected;
|
||||||
use ArrangerSceneCommand as Scene;
|
use SceneCommand as Scene;
|
||||||
use ArrangerTrackCommand as Track;
|
use TrackCommand as Track;
|
||||||
use ArrangerClipCommand as Clip;
|
use ClipCommand as Clip;
|
||||||
let t_len = state.tracks.len();
|
let t_len = state.tracks.len();
|
||||||
let s_len = state.scenes.len();
|
let s_len = state.scenes.len();
|
||||||
Some(match input {
|
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!(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!(Char('c')) => ArrCmd::Track(Track::SetColor(t, ItemPalette::random())),
|
||||||
|
|
||||||
kpat!(Up) => return None,
|
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 crate::*;
|
||||||
use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync};
|
use KeyCode::*;
|
||||||
//use FocusCommand::{Next, Prev};
|
use ClockCommand::{Play, Pause};
|
||||||
use KeyCode::{Enter, Left, Right, Char};
|
|
||||||
/// Transport clock app.
|
/// Transport clock app.
|
||||||
pub struct TransportTui {
|
pub struct TransportTui {
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
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 clock_tui; pub use self::clock_tui::*;
|
||||||
mod microsecond; pub use self::microsecond::*;
|
mod microsecond; pub use self::microsecond::*;
|
||||||
mod moment; pub use self::moment::*;
|
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 use ::atomic_float; pub(crate) use atomic_float::*;
|
||||||
pub(crate) use ::tek_tui::{
|
pub(crate) use ::tek_tui::{
|
||||||
*,
|
*,
|
||||||
tek_input::*,
|
|
||||||
tek_output::*,
|
tek_output::*,
|
||||||
crossterm::event::KeyCode,
|
tek_input::*,
|
||||||
ratatui::prelude::*
|
tek_edn::*,
|
||||||
|
ratatui::prelude::*,
|
||||||
|
crossterm::event::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait HasClock: Send + Sync {
|
#[cfg(test)] #[test] fn test_time () -> Usually<()> {
|
||||||
fn clock (&self) -> &Clock;
|
// TODO!
|
||||||
}
|
|
||||||
|
|
||||||
#[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()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
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