mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
phlat 2
This commit is contained in:
parent
c5d1e4b7ba
commit
5aaa0f3e4e
8 changed files with 1006 additions and 1014 deletions
676
src/clock.rs
676
src/clock.rs
|
|
@ -1,676 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use ::std::sync::Arc;
|
|
||||||
use ::atomic_float::AtomicF64;
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
/// Update memoized render of clock values.
|
|
||||||
/// ```
|
|
||||||
/// tek::App::default().update_clock();
|
|
||||||
/// ```
|
|
||||||
pub fn update_clock (&self) {
|
|
||||||
ClockView::update_clock(
|
|
||||||
&self.project.clock.view_cache, self.clock(), self.size.w() > 80
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The source of time.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let clock = tek::Clock::default();
|
|
||||||
/// ```
|
|
||||||
#[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>,
|
|
||||||
// Cache of formatted strings
|
|
||||||
pub view_cache: Arc<RwLock<ClockView>>,
|
|
||||||
/// For syncing the clock to an external source
|
|
||||||
#[cfg(feature = "port")] pub midi_in: Arc<RwLock<Option<MidiInput>>>,
|
|
||||||
/// For syncing other devices to this clock
|
|
||||||
#[cfg(feature = "port")] pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
|
||||||
/// For emitting a metronome
|
|
||||||
#[cfg(feature = "port")] pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl<T: HasClock> Act<T> for ClockCmd {
|
|
||||||
//fn act (&self, state: &mut T) -> Perhaps<Self> {
|
|
||||||
//self.act(state.clock_mut()) // awesome
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// Quantization setting for launching clips
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct LaunchSync (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Quantization setting for notes
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Quantize (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// A unit of time, represented as an atomic 64-bit float.
|
|
||||||
///
|
|
||||||
/// According to https://stackoverflow.com/a/873367, as per IEEE754,
|
|
||||||
/// every integer between 1 and 2^53 can be represented exactly.
|
|
||||||
/// This should mean that, even at 192kHz sampling rate, over 1 year of audio
|
|
||||||
/// can be clocked in microseconds with f64 without losing precision.
|
|
||||||
pub trait TimeUnit: InteriorMutable<f64> {}
|
|
||||||
|
|
||||||
pub const DEFAULT_PPQ: f64 = 96.0;
|
|
||||||
|
|
||||||
/// FIXME: remove this and use PPQ from timebase everywhere:
|
|
||||||
pub const PPQ: usize = 96;
|
|
||||||
|
|
||||||
/// (pulses, name), assuming 96 PPQ
|
|
||||||
pub const NOTE_DURATIONS: [(usize, &str);26] = [
|
|
||||||
(1, "1/384"), (2, "1/192"),
|
|
||||||
(3, "1/128"), (4, "1/96"),
|
|
||||||
(6, "1/64"), (8, "1/48"),
|
|
||||||
(12, "1/32"), (16, "1/24"),
|
|
||||||
(24, "1/16"), (32, "1/12"),
|
|
||||||
(48, "1/8"), (64, "1/6"),
|
|
||||||
(96, "1/4"), (128, "1/3"),
|
|
||||||
(192, "1/2"), (256, "2/3"),
|
|
||||||
(384, "1/1"), (512, "4/3"),
|
|
||||||
(576, "3/2"), (768, "2/1"),
|
|
||||||
(1152, "3/1"), (1536, "4/1"),
|
|
||||||
(2304, "6/1"), (3072, "8/1"),
|
|
||||||
(3456, "9/1"), (6144, "16/1"),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const NOTE_NAMES: [&str; 128] = [
|
|
||||||
"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
|
|
||||||
"C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
|
|
||||||
"C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
|
|
||||||
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
|
|
||||||
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
|
|
||||||
"C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
|
|
||||||
"C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
|
|
||||||
"C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
|
|
||||||
"C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
|
|
||||||
"C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9",
|
|
||||||
"C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10",
|
|
||||||
];
|
|
||||||
|
|
||||||
impl LaunchSync {
|
|
||||||
pub fn next (&self) -> f64 {
|
|
||||||
note_duration_next(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
pub fn prev (&self) -> f64 {
|
|
||||||
note_duration_prev(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Quantize {
|
|
||||||
pub fn next (&self) -> f64 {
|
|
||||||
note_duration_next(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
pub fn prev (&self) -> f64 {
|
|
||||||
note_duration_prev(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement an arithmetic operation for a unit of time
|
|
||||||
#[macro_export] macro_rules! impl_op {
|
|
||||||
($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => {
|
|
||||||
impl $Op<Self> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other.get(); Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl $Op<usize> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other as f64; Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl $Op<f64> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other; Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Clock {
|
|
||||||
fn fmt (&self, f: &mut 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 new (jack: &Jack<'static>, bpm: Option<f64>) -> Usually<Self> {
|
|
||||||
let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
|
|
||||||
let timebase = Arc::new(Timebase::default());
|
|
||||||
let clock = 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,
|
|
||||||
midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))),
|
|
||||||
midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))),
|
|
||||||
click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
if let Some(bpm) = bpm {
|
|
||||||
clock.timebase.bpm.set(bpm);
|
|
||||||
}
|
|
||||||
Ok(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) -> &Bpm {
|
|
||||||
&self.timebase.bpm
|
|
||||||
}
|
|
||||||
/// Current MIDI resolution
|
|
||||||
pub fn ppq (&self) -> &Ppq {
|
|
||||||
&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.try_read().unwrap().is_none()
|
|
||||||
}
|
|
||||||
/// Is currently playing?
|
|
||||||
pub fn is_rolling (&self) -> bool {
|
|
||||||
self.started.try_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.try_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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_launch_instant (&self) -> Moment {
|
|
||||||
Moment::from_pulse(self.timebase(), self.next_launch_pulse() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get index of first sample to populate.
|
|
||||||
///
|
|
||||||
/// Greater than 0 means that the first pulse of the clip
|
|
||||||
/// falls somewhere in the middle of the chunk.
|
|
||||||
pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{
|
|
||||||
(scope.last_frame_time() as usize).saturating_sub(
|
|
||||||
started.sample.get() as usize +
|
|
||||||
self.started.try_read().unwrap().as_ref().unwrap().sample.get() as usize
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get iterator that emits sample paired with pulse.
|
|
||||||
//
|
|
||||||
// * Sample: index into output buffer at which to write MIDI event
|
|
||||||
// * Pulse: index into clip from which to take the MIDI event
|
|
||||||
//
|
|
||||||
// Emitted for each sample of the output buffer that corresponds to a MIDI pulse.
|
|
||||||
pub fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> Ticker {
|
|
||||||
self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_has!(Clock: |self: Track|self.sequencer.clock);
|
|
||||||
impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ));
|
|
||||||
|
|
||||||
/// Define and implement a unit of time
|
|
||||||
#[macro_export] macro_rules! impl_time_unit {
|
|
||||||
($T:ident) => {
|
|
||||||
impl Gettable<f64> for $T {
|
|
||||||
fn get (&self) -> f64 { self.0.load(Relaxed) }
|
|
||||||
}
|
|
||||||
impl InteriorMutable<f64> for $T {
|
|
||||||
fn set (&self, value: f64) -> f64 {
|
|
||||||
let old = self.get();
|
|
||||||
self.0.store(value, Relaxed);
|
|
||||||
old
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TimeUnit for $T {}
|
|
||||||
impl_op!($T, Add, add, |a, b|{a + b});
|
|
||||||
impl_op!($T, Sub, sub, |a, b|{a - b});
|
|
||||||
impl_op!($T, Mul, mul, |a, b|{a * b});
|
|
||||||
impl_op!($T, Div, div, |a, b|{a / b});
|
|
||||||
impl_op!($T, Rem, rem, |a, b|{a % b});
|
|
||||||
impl From<f64> for $T { fn from (value: f64) -> Self { Self(value.into()) } }
|
|
||||||
impl From<usize> for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } }
|
|
||||||
impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } }
|
|
||||||
impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } }
|
|
||||||
impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } }
|
|
||||||
impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } }
|
|
||||||
impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_time_unit!(SampleCount);
|
|
||||||
impl_time_unit!(SampleRate);
|
|
||||||
impl_time_unit!(Microsecond);
|
|
||||||
impl_time_unit!(Quantize);
|
|
||||||
impl_time_unit!(Ppq);
|
|
||||||
impl_time_unit!(Pulse);
|
|
||||||
impl_time_unit!(Bpm);
|
|
||||||
impl_time_unit!(LaunchSync);
|
|
||||||
|
|
||||||
pub trait Gettable<T> {
|
|
||||||
/// Returns current value
|
|
||||||
fn get (&self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Mutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&mut self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InteriorMutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Timebase::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone)] pub struct Timebase {
|
|
||||||
/// Audio samples per second
|
|
||||||
pub sr: SampleRate,
|
|
||||||
/// MIDI beats per minute
|
|
||||||
pub bpm: Bpm,
|
|
||||||
/// MIDI ticks per beat
|
|
||||||
pub ppq: Ppq,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Audio sample rate in Hz (samples per second)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Tempo in beats per minute
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Bpm (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// MIDI resolution in PPQ (pulses per quarter note)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Ppq (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
impl Timebase {
|
|
||||||
/// Specify sample rate, BPM and PPQ
|
|
||||||
pub fn new (
|
|
||||||
s: impl Into<SampleRate>,
|
|
||||||
b: impl Into<Bpm>,
|
|
||||||
p: impl Into<Ppq>
|
|
||||||
) -> Self {
|
|
||||||
Self { sr: s.into(), bpm: b.into(), ppq: p.into() }
|
|
||||||
}
|
|
||||||
/// Iterate over ticks between start and end.
|
|
||||||
#[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> Ticker {
|
|
||||||
Ticker { spp: self.samples_per_pulse(), sample: start, start, end }
|
|
||||||
}
|
|
||||||
/// Return the duration fo a beat in microseconds
|
|
||||||
#[inline] pub fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm.get() }
|
|
||||||
/// Return the number of beats in a second
|
|
||||||
#[inline] pub fn beat_per_second (&self) -> f64 { self.bpm.get() / 60f64 }
|
|
||||||
/// Return the number of microseconds corresponding to a note of the given duration
|
|
||||||
#[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 {
|
|
||||||
4.0 * self.usec_per_beat() * num / den
|
|
||||||
}
|
|
||||||
/// Return duration of a pulse in microseconds (BPM-dependent)
|
|
||||||
#[inline] pub fn pulse_per_usec (&self) -> f64 { self.ppq.get() / self.usec_per_beat() }
|
|
||||||
/// Return duration of a pulse in microseconds (BPM-dependent)
|
|
||||||
#[inline] pub fn usec_per_pulse (&self) -> f64 { self.usec_per_beat() / self.ppq.get() }
|
|
||||||
/// Return number of pulses to which a number of microseconds corresponds (BPM-dependent)
|
|
||||||
#[inline] pub fn usecs_to_pulse (&self, usec: f64) -> f64 { usec * self.pulse_per_usec() }
|
|
||||||
/// Convert a number of pulses to a sample number (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() }
|
|
||||||
/// Return number of pulses in a second (BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_per_second (&self) -> f64 { self.beat_per_second() * self.ppq.get() }
|
|
||||||
/// Return fraction of a pulse to which a sample corresponds (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_per_sample (&self) -> f64 {
|
|
||||||
self.usec_per_pulse() / self.sr.usec_per_sample()
|
|
||||||
}
|
|
||||||
/// Return number of samples in a pulse (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn samples_per_pulse (&self) -> f64 {
|
|
||||||
self.sr.get() / self.pulses_per_second()
|
|
||||||
}
|
|
||||||
/// Convert a number of pulses to a sample number (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_to_sample (&self, p: f64) -> f64 {
|
|
||||||
self.pulses_per_sample() * p
|
|
||||||
}
|
|
||||||
/// Convert a number of samples to a pulse number (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn samples_to_pulse (&self, s: f64) -> f64 {
|
|
||||||
s / self.pulses_per_sample()
|
|
||||||
}
|
|
||||||
/// Return the number of samples corresponding to a note of the given duration
|
|
||||||
#[inline] pub fn note_to_samples (&self, note: (f64, f64)) -> f64 {
|
|
||||||
self.usec_to_sample(self.note_to_usec(note))
|
|
||||||
}
|
|
||||||
/// Return the number of samples corresponding to the given number of microseconds
|
|
||||||
#[inline] pub fn usec_to_sample (&self, usec: f64) -> f64 {
|
|
||||||
usec * self.sr.get() / 1000f64
|
|
||||||
}
|
|
||||||
/// Return the quantized position of a moment in time given a step
|
|
||||||
#[inline] pub fn quantize (&self, step: (f64, f64), time: f64) -> (f64, f64) {
|
|
||||||
let step = self.note_to_usec(step);
|
|
||||||
(time / step, time % step)
|
|
||||||
}
|
|
||||||
/// Quantize a collection of events
|
|
||||||
#[inline] pub fn quantize_into <E: Iterator<Item=(f64, f64)> + Sized, T> (
|
|
||||||
&self, step: (f64, f64), events: E
|
|
||||||
) -> Vec<(f64, f64)> {
|
|
||||||
events.map(|(time, event)|(self.quantize(step, time).0, event)).collect()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 0
|
|
||||||
#[inline] pub fn format_beats_0 (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
|
||||||
format!("{}.{}.{pulses:02}", beats / 4, beats % 4).into()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar starting from 0
|
|
||||||
#[inline] pub fn format_beats_0_short (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
|
||||||
format!("{}.{}", beats / 4, beats % 4).into()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
|
||||||
#[inline] pub fn format_beats_1 (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let mut string = String::with_capacity(16);
|
|
||||||
self.format_beats_1_to(&mut string, pulse).expect("failed to format {pulse} into beat");
|
|
||||||
string.into()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
|
||||||
#[inline] pub fn format_beats_1_to (&self, w: &mut impl std::fmt::Write, pulse: f64) -> Result<(), std::fmt::Error> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
|
||||||
write!(w, "{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1)
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
|
||||||
#[inline] pub fn format_beats_1_short (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
|
||||||
format!("{}.{}", beats / 4 + 1, beats % 4 + 1).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SampleRate {
|
|
||||||
/// Return the duration of a sample in microseconds (floating)
|
|
||||||
#[inline] pub fn usec_per_sample (&self) -> f64 {
|
|
||||||
1_000_000f64 / self.get()
|
|
||||||
}
|
|
||||||
/// Return the duration of a sample in microseconds (floating)
|
|
||||||
#[inline] pub fn sample_per_usec (&self) -> f64 {
|
|
||||||
self.get() / 1_000_000f64
|
|
||||||
}
|
|
||||||
/// Convert a number of samples to microseconds (floating)
|
|
||||||
#[inline] pub fn samples_to_usec (&self, samples: f64) -> f64 {
|
|
||||||
self.usec_per_sample() * samples
|
|
||||||
}
|
|
||||||
/// Convert a number of microseconds to samples (floating)
|
|
||||||
#[inline] pub fn usecs_to_sample (&self, usecs: f64) -> f64 {
|
|
||||||
self.sample_per_usec() * usecs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A point in time in all time scales (microsecond, sample, MIDI pulse)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Moment::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default, Clone)] pub struct Moment {
|
|
||||||
pub timebase: Arc<Timebase>,
|
|
||||||
/// Current time in microseconds
|
|
||||||
pub usec: Microsecond,
|
|
||||||
/// Current time in audio samples
|
|
||||||
pub sample: SampleCount,
|
|
||||||
/// Current time in MIDI pulses
|
|
||||||
pub pulse: Pulse,
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Moment2::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default)] pub enum Moment2 {
|
|
||||||
#[default] None,
|
|
||||||
Zero,
|
|
||||||
Usec(Microsecond),
|
|
||||||
Sample(SampleCount),
|
|
||||||
Pulse(Pulse),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Timestamp in microseconds
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Microsecond(0.0.into());
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Microsecond (pub AtomicF64);
|
|
||||||
|
|
||||||
/// Timestamp in audio samples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::SampleCount(0.0.into());
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct SampleCount (pub AtomicF64);
|
|
||||||
|
|
||||||
/// Timestamp in MIDI pulses
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Pulse(0.0.into());
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Pulse (pub AtomicF64);
|
|
||||||
|
|
||||||
impl Moment {
|
|
||||||
pub fn zero (timebase: &Arc<Timebase>) -> Self {
|
|
||||||
Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() }
|
|
||||||
}
|
|
||||||
pub fn from_usec (timebase: &Arc<Timebase>, usec: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
usec: usec.into(),
|
|
||||||
sample: timebase.sr.usecs_to_sample(usec).into(),
|
|
||||||
pulse: timebase.usecs_to_pulse(usec).into(),
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_sample (timebase: &Arc<Timebase>, sample: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
sample: sample.into(),
|
|
||||||
usec: timebase.sr.samples_to_usec(sample).into(),
|
|
||||||
pulse: timebase.samples_to_pulse(sample).into(),
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_pulse (timebase: &Arc<Timebase>, pulse: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
pulse: pulse.into(),
|
|
||||||
sample: timebase.pulses_to_sample(pulse).into(),
|
|
||||||
usec: timebase.pulses_to_usec(pulse).into(),
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline] pub fn update_from_usec (&self, usec: f64) {
|
|
||||||
self.usec.set(usec);
|
|
||||||
self.pulse.set(self.timebase.usecs_to_pulse(usec));
|
|
||||||
self.sample.set(self.timebase.sr.usecs_to_sample(usec));
|
|
||||||
}
|
|
||||||
#[inline] pub fn update_from_sample (&self, sample: f64) {
|
|
||||||
self.usec.set(self.timebase.sr.samples_to_usec(sample));
|
|
||||||
self.pulse.set(self.timebase.samples_to_pulse(sample));
|
|
||||||
self.sample.set(sample);
|
|
||||||
}
|
|
||||||
#[inline] pub fn update_from_pulse (&self, pulse: f64) {
|
|
||||||
self.usec.set(self.timebase.pulses_to_usec(pulse));
|
|
||||||
self.pulse.set(pulse);
|
|
||||||
self.sample.set(self.timebase.pulses_to_sample(pulse));
|
|
||||||
}
|
|
||||||
#[inline] pub fn format_beat (&self) -> Arc<str> {
|
|
||||||
self.timebase.format_beats_1(self.pulse.get()).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Microsecond {
|
|
||||||
#[inline] pub fn format_msu (&self) -> Arc<str> {
|
|
||||||
let usecs = self.get() as usize;
|
|
||||||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
|
||||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
|
||||||
format!("{minutes}:{seconds:02}:{msecs:03}").into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator that emits subsequent ticks within a range.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let iter = tek::Ticker::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Ticker {
|
|
||||||
pub spp: f64,
|
|
||||||
pub sample: usize,
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Ticker {
|
|
||||||
type Item = (usize, usize);
|
|
||||||
fn next (&mut self) -> Option<Self::Item> {
|
|
||||||
loop {
|
|
||||||
if self.sample > self.end { return None }
|
|
||||||
let spp = self.spp;
|
|
||||||
let sample = self.sample as f64;
|
|
||||||
let start = self.start;
|
|
||||||
let end = self.end;
|
|
||||||
self.sample += 1;
|
|
||||||
//println!("{spp} {sample} {start} {end}");
|
|
||||||
let jitter = sample.rem_euclid(spp); // ramps
|
|
||||||
let next_jitter = (sample + 1.0).rem_euclid(spp);
|
|
||||||
if jitter > next_jitter { // at crossing:
|
|
||||||
let time = (sample as usize) % (end as usize-start as usize);
|
|
||||||
let tick = (sample / spp) as usize;
|
|
||||||
return Some((time, tick))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
191
src/device.rs
191
src/device.rs
|
|
@ -1,191 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
/// FIXME: generalize. Set picked device in device pick dialog.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// tek::App::default().device_pick(0);
|
|
||||||
/// ```
|
|
||||||
pub fn device_pick (&mut self, index: usize) {
|
|
||||||
self.dialog = Dialog::Device(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: generalize. Add device to current track.
|
|
||||||
pub fn add_device (&mut self, index: usize) -> Usually<()> {
|
|
||||||
match index {
|
|
||||||
0 => {
|
|
||||||
let name = self.jack.with_client(|c|c.name().to_string());
|
|
||||||
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name();
|
|
||||||
let track = self.track().expect("no active track");
|
|
||||||
let port = format!("{}/Sampler", &track.name);
|
|
||||||
let connect = Connect::exact(format!("{name}:{midi}"));
|
|
||||||
let sampler = if let Ok(sampler) = Sampler::new(
|
|
||||||
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
|
|
||||||
) {
|
|
||||||
self.dialog = Dialog::None;
|
|
||||||
Device::Sampler(sampler)
|
|
||||||
} else {
|
|
||||||
self.dialog = Dialog::Message("Failed to add device.".into());
|
|
||||||
return Err("failed to add device".into())
|
|
||||||
};
|
|
||||||
let track = self.track_mut().expect("no active track");
|
|
||||||
track.devices.push(sampler);
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
1 => {
|
|
||||||
todo!();
|
|
||||||
//Ok(())
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasJack<'static> for App {
|
|
||||||
fn jack (&self) -> &Jack<'static> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_audio!(App: tek_jack_process, tek_jack_event);
|
|
||||||
|
|
||||||
fn tek_jack_process (state: &mut App, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
let t0 = state.perf.get_t0();
|
|
||||||
state.clock().update_from_scope(scope).unwrap();
|
|
||||||
let midi_in = state.project.midi_input_collect(scope);
|
|
||||||
if let Some(editor) = &state.editor() {
|
|
||||||
let mut pitch: Option<u7> = None;
|
|
||||||
for port in midi_in.iter() {
|
|
||||||
for event in port.iter() {
|
|
||||||
if let (_, Ok(LiveEvent::Midi {message: MidiMessage::NoteOn {key, ..}, ..}))
|
|
||||||
= event
|
|
||||||
{
|
|
||||||
pitch = Some(key.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(pitch) = pitch {
|
|
||||||
editor.set_note_pos(pitch.as_int() as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = state.project.tracks_jack_process(client, scope);
|
|
||||||
state.perf.update_from_jack_scope(t0, scope);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tek_jack_event (state: &mut App, event: JackEvent) {
|
|
||||||
use JackEvent::*;
|
|
||||||
match event {
|
|
||||||
SampleRate(sr) => { state.clock().timebase.sr.set(sr as f64); },
|
|
||||||
PortRegistration(_id, true) => {
|
|
||||||
//let port = self.jack().port_by_id(id);
|
|
||||||
//println!("\rport add: {id} {port:?}");
|
|
||||||
//println!("\rport add: {id}");
|
|
||||||
},
|
|
||||||
PortRegistration(_id, false) => {
|
|
||||||
/*println!("\rport del: {id}")*/
|
|
||||||
},
|
|
||||||
PortsConnected(_a, _b, true) => { /*println!("\rport conn: {a} {b}")*/ },
|
|
||||||
PortsConnected(_a, _b, false) => { /*println!("\rport disc: {a} {b}")*/ },
|
|
||||||
ClientRegistration(_id, true) => {},
|
|
||||||
ClientRegistration(_id, false) => {},
|
|
||||||
ThreadInit => {},
|
|
||||||
XRun => {},
|
|
||||||
GraphReorder => {},
|
|
||||||
_ => { panic!("{event:?}"); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tek_proc::command(Device)]
|
|
||||||
#[tek_proc::keyword(Device)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum DeviceCmd {}
|
|
||||||
|
|
||||||
impl Device {
|
|
||||||
pub fn name (&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(sampler) => sampler.name.as_ref(),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn midi_ins (&self) -> &[MidiInput] {
|
|
||||||
match self {
|
|
||||||
//Self::Sampler(Sampler { midi_in, .. }) => &[midi_in],
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn midi_outs (&self) -> &[MidiOutput] {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(_) => &[],
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(),
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(),
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A device that can be plugged into the chain.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let device = tek::Device::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub enum Device {
|
|
||||||
#[default]
|
|
||||||
Bypass,
|
|
||||||
Mute,
|
|
||||||
#[cfg(feature = "sampler")]
|
|
||||||
Sampler(Sampler),
|
|
||||||
#[cfg(feature = "lv2")] // TODO
|
|
||||||
Lv2(Lv2),
|
|
||||||
#[cfg(feature = "vst2")] // TODO
|
|
||||||
Vst2,
|
|
||||||
#[cfg(feature = "vst3")] // TODO
|
|
||||||
Vst3,
|
|
||||||
#[cfg(feature = "clap")] // TODO
|
|
||||||
Clap,
|
|
||||||
#[cfg(feature = "sf2")] // TODO
|
|
||||||
Sf2,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Some sort of wrapper?
|
|
||||||
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
|
||||||
|
|
||||||
impl_audio!(|self: DeviceAudio<'a>, client, scope|{
|
|
||||||
use Device::*;
|
|
||||||
match self.0 {
|
|
||||||
Mute => { Control::Continue },
|
|
||||||
Bypass => { /*TODO*/ Control::Continue },
|
|
||||||
#[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope),
|
|
||||||
#[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope),
|
|
||||||
#[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO
|
|
||||||
#[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO
|
|
||||||
#[cfg(feature = "clap")] Clap => { todo!() }, // TODO
|
|
||||||
#[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn device_kinds () -> &'static [&'static str] {
|
|
||||||
&[
|
|
||||||
#[cfg(feature = "sampler")] "Sampler",
|
|
||||||
#[cfg(feature = "lv2")] "Plugin (LV2)",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<Vec<Device>> + AsMut<Vec<Device>>> HasDevices for T {
|
|
||||||
fn devices (&self) -> &Vec<Device> {
|
|
||||||
self.as_ref()
|
|
||||||
}
|
|
||||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
|
||||||
self.as_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
221
src/mixer.rs
221
src/mixer.rs
|
|
@ -93,3 +93,224 @@ pub fn to_rms (samples: &[f32]) -> f32 {
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
(sum / samples.len() as f32).sqrt()
|
(sum / samples.len() as f32).sqrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A track consists of a sequencer and zero or more devices chained after it.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let track: tek::Track = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Track {
|
||||||
|
/// Name of track
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Identifying color of track
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Preferred width of track column
|
||||||
|
pub width: usize,
|
||||||
|
/// MIDI sequencer state
|
||||||
|
pub sequencer: Sequencer,
|
||||||
|
/// Device chain
|
||||||
|
pub devices: Vec<Device>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Track {
|
||||||
|
/// Create a new track with only the default [Sequencer].
|
||||||
|
pub fn new (
|
||||||
|
name: &impl AsRef<str>,
|
||||||
|
color: Option<ItemTheme>,
|
||||||
|
jack: &Jack<'static>,
|
||||||
|
clock: Option<&Clock>,
|
||||||
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
|
midi_from: &[Connect],
|
||||||
|
midi_to: &[Connect],
|
||||||
|
) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: name.as_ref().into(),
|
||||||
|
color: color.unwrap_or_default(),
|
||||||
|
sequencer: Sequencer::new(
|
||||||
|
format!("{}/sequencer", name.as_ref()),
|
||||||
|
jack, clock, clip, midi_from, midi_to
|
||||||
|
)?,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn new_added (
|
||||||
|
jack: &Jack<'static>,
|
||||||
|
clock: Option<&Clock>,
|
||||||
|
name: &impl AsRef<str>,
|
||||||
|
color: Option<ItemTheme>,
|
||||||
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
|
mins: Arc<[Connect]>,
|
||||||
|
mouts: Arc<[Connect]>,
|
||||||
|
) -> Usually<Self> {
|
||||||
|
let name = name.as_ref();
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
width: (name.len() + 2).max(12),
|
||||||
|
color: color.unwrap_or_else(ItemTheme::random),
|
||||||
|
sequencer: Sequencer::new(name, jack, clock, clip, mins.as_ref(), mouts.as_ref())?,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||||
|
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||||
|
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
pub fn stop (&mut self) -> Perhaps<TrackCmd> {
|
||||||
|
self.sequencer.enqueue_next(None);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasWidth for Track {
|
||||||
|
const MIN_WIDTH: usize = 9;
|
||||||
|
fn width_inc (&mut self) {
|
||||||
|
self.width += 1;
|
||||||
|
}
|
||||||
|
fn width_dec (&mut self) {
|
||||||
|
if self.width > Track::MIN_WIDTH { self.width -= 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def_sizes_iter!(TracksSizes => Track);
|
||||||
|
impl_has!(Vec<Track>: |self: Project| self.tracks);
|
||||||
|
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
|
||||||
|
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
||||||
|
impl_has!(Sequencer: |self: Track| self.sequencer);
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
/// FIXME: generalize. Set picked device in device pick dialog.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// tek::App::default().device_pick(0);
|
||||||
|
/// ```
|
||||||
|
pub fn device_pick (&mut self, index: usize) {
|
||||||
|
self.dialog = Dialog::Device(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: generalize. Add device to current track.
|
||||||
|
pub fn add_device (&mut self, index: usize) -> Usually<()> {
|
||||||
|
match index {
|
||||||
|
0 => {
|
||||||
|
let name = self.jack.with_client(|c|c.name().to_string());
|
||||||
|
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name();
|
||||||
|
let track = self.track().expect("no active track");
|
||||||
|
let port = format!("{}/Sampler", &track.name);
|
||||||
|
let connect = Connect::exact(format!("{name}:{midi}"));
|
||||||
|
let sampler = if let Ok(sampler) = Sampler::new(
|
||||||
|
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
|
||||||
|
) {
|
||||||
|
self.dialog = Dialog::None;
|
||||||
|
Device::Sampler(sampler)
|
||||||
|
} else {
|
||||||
|
self.dialog = Dialog::Message("Failed to add device.".into());
|
||||||
|
return Err("failed to add device".into())
|
||||||
|
};
|
||||||
|
let track = self.track_mut().expect("no active track");
|
||||||
|
track.devices.push(sampler);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
todo!();
|
||||||
|
//Ok(())
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(Device)]
|
||||||
|
#[tek_proc::keyword(Device)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DeviceCmd {}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
pub fn name (&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Sampler(sampler) => sampler.name.as_ref(),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn midi_ins (&self) -> &[MidiInput] {
|
||||||
|
match self {
|
||||||
|
//Self::Sampler(Sampler { midi_in, .. }) => &[midi_in],
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn midi_outs (&self) -> &[MidiOutput] {
|
||||||
|
match self {
|
||||||
|
Self::Sampler(_) => &[],
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||||
|
match self {
|
||||||
|
Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(),
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||||
|
match self {
|
||||||
|
Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(),
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A device that can be plugged into the chain.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let device = tek::Device::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub enum Device {
|
||||||
|
#[default]
|
||||||
|
Bypass,
|
||||||
|
Mute,
|
||||||
|
#[cfg(feature = "sampler")]
|
||||||
|
Sampler(Sampler),
|
||||||
|
#[cfg(feature = "lv2")] // TODO
|
||||||
|
Lv2(Lv2),
|
||||||
|
#[cfg(feature = "vst2")] // TODO
|
||||||
|
Vst2,
|
||||||
|
#[cfg(feature = "vst3")] // TODO
|
||||||
|
Vst3,
|
||||||
|
#[cfg(feature = "clap")] // TODO
|
||||||
|
Clap,
|
||||||
|
#[cfg(feature = "sf2")] // TODO
|
||||||
|
Sf2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some sort of wrapper?
|
||||||
|
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
||||||
|
|
||||||
|
impl_audio!(|self: DeviceAudio<'a>, client, scope|{
|
||||||
|
use Device::*;
|
||||||
|
match self.0 {
|
||||||
|
Mute => { Control::Continue },
|
||||||
|
Bypass => { /*TODO*/ Control::Continue },
|
||||||
|
#[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope),
|
||||||
|
#[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope),
|
||||||
|
#[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO
|
||||||
|
#[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO
|
||||||
|
#[cfg(feature = "clap")] Clap => { todo!() }, // TODO
|
||||||
|
#[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn device_kinds () -> &'static [&'static str] {
|
||||||
|
&[
|
||||||
|
#[cfg(feature = "sampler")] "Sampler",
|
||||||
|
#[cfg(feature = "lv2")] "Plugin (LV2)",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<Vec<Device>> + AsMut<Vec<Device>>> HasDevices for T {
|
||||||
|
fn devices (&self) -> &Vec<Device> {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
||||||
|
self.as_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -471,6 +471,60 @@ pub trait SelectionModel: HasSelection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A scene consists of a set of clips to play together.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let scene: tek::Scene = Default::default();
|
||||||
|
/// let _ = scene.pulses();
|
||||||
|
/// let _ = scene.is_playing(&[]);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Scene {
|
||||||
|
/// Name of scene
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Identifying color of scene
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Clips in scene, one per track
|
||||||
|
pub clips: Pool<MidiClip>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
pub const DEFAULT_HEIGHT: usize = 2;
|
||||||
|
/// Get pulse length of the longest clip in the scene
|
||||||
|
pub fn pulses (&self) -> usize {
|
||||||
|
self.clips.read().iter().fold(0, |a, p|{
|
||||||
|
a.max(p.read().as_ref().map(|q|q.read().length).unwrap_or(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// True if all clips in scene are currently playing on given tracks.
|
||||||
|
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
||||||
|
// Not "playing" if there are no clips
|
||||||
|
if !self.clips.read().iter().any(|clip|clip.read().is_some()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Playing if at least 1 clip is playing
|
||||||
|
self.clips.read().iter().enumerate().all(|(track_index, clip)|match &*clip.read() {
|
||||||
|
Some(c) => tracks
|
||||||
|
.get(track_index)
|
||||||
|
.map(|track|{
|
||||||
|
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
||||||
|
*clip.read() == *c.read()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false),
|
||||||
|
None => true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
|
||||||
|
|
||||||
|
def_sizes_iter!(ScenesSizes => Scene);
|
||||||
|
impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
||||||
|
impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
||||||
|
|
||||||
//impl_from!(Pool<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
|
//impl_from!(Pool<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
|
||||||
//let model = Self::default();
|
//let model = Self::default();
|
||||||
//model.clips.try_write().unwrap().push(clip.clone());
|
//model.clips.try_write().unwrap().push(clip.clone());
|
||||||
55
src/scene.rs
55
src/scene.rs
|
|
@ -1,55 +0,0 @@
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// A scene consists of a set of clips to play together.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let scene: tek::Scene = Default::default();
|
|
||||||
/// let _ = scene.pulses();
|
|
||||||
/// let _ = scene.is_playing(&[]);
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Scene {
|
|
||||||
/// Name of scene
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Identifying color of scene
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Clips in scene, one per track
|
|
||||||
pub clips: Pool<MidiClip>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scene {
|
|
||||||
pub const DEFAULT_HEIGHT: usize = 2;
|
|
||||||
/// Get pulse length of the longest clip in the scene
|
|
||||||
pub fn pulses (&self) -> usize {
|
|
||||||
self.clips.read().iter().fold(0, |a, p|{
|
|
||||||
a.max(p.read().as_ref().map(|q|q.read().length).unwrap_or(0))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// True if all clips in scene are currently playing on given tracks.
|
|
||||||
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
|
||||||
// Not "playing" if there are no clips
|
|
||||||
if !self.clips.read().iter().any(|clip|clip.read().is_some()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Playing if at least 1 clip is playing
|
|
||||||
self.clips.read().iter().enumerate().all(|(track_index, clip)|match &*clip.read() {
|
|
||||||
Some(c) => tracks
|
|
||||||
.get(track_index)
|
|
||||||
.map(|track|{
|
|
||||||
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
|
||||||
*clip.read() == *c.read()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(false),
|
|
||||||
None => true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
|
|
||||||
|
|
||||||
def_sizes_iter!(ScenesSizes => Scene);
|
|
||||||
impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
|
||||||
impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
|
||||||
675
src/sequence.rs
675
src/sequence.rs
|
|
@ -1,4 +1,679 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use ::std::sync::Arc;
|
||||||
|
use ::atomic_float::AtomicF64;
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
/// Update memoized render of clock values.
|
||||||
|
/// ```
|
||||||
|
/// tek::App::default().update_clock();
|
||||||
|
/// ```
|
||||||
|
pub fn update_clock (&self) {
|
||||||
|
ClockView::update_clock(
|
||||||
|
&self.project.clock.view_cache, self.clock(), self.size.w() > 80
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The source of time.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let clock = tek::Clock::default();
|
||||||
|
/// ```
|
||||||
|
#[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>,
|
||||||
|
// Cache of formatted strings
|
||||||
|
pub view_cache: Arc<RwLock<ClockView>>,
|
||||||
|
/// For syncing the clock to an external source
|
||||||
|
#[cfg(feature = "port")] pub midi_in: Arc<RwLock<Option<MidiInput>>>,
|
||||||
|
/// For syncing other devices to this clock
|
||||||
|
#[cfg(feature = "port")] pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
||||||
|
/// For emitting a metronome
|
||||||
|
#[cfg(feature = "port")] pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
//impl<T: HasClock> Act<T> for ClockCmd {
|
||||||
|
//fn act (&self, state: &mut T) -> Perhaps<Self> {
|
||||||
|
//self.act(state.clock_mut()) // awesome
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
/// Quantization setting for launching clips
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct LaunchSync (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Quantization setting for notes
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Quantize (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// A unit of time, represented as an atomic 64-bit float.
|
||||||
|
///
|
||||||
|
/// According to https://stackoverflow.com/a/873367, as per IEEE754,
|
||||||
|
/// every integer between 1 and 2^53 can be represented exactly.
|
||||||
|
/// This should mean that, even at 192kHz sampling rate, over 1 year of audio
|
||||||
|
/// can be clocked in microseconds with f64 without losing precision.
|
||||||
|
pub trait TimeUnit: InteriorMutable<f64> {}
|
||||||
|
|
||||||
|
pub const DEFAULT_PPQ: f64 = 96.0;
|
||||||
|
|
||||||
|
/// FIXME: remove this and use PPQ from timebase everywhere:
|
||||||
|
pub const PPQ: usize = 96;
|
||||||
|
|
||||||
|
/// (pulses, name), assuming 96 PPQ
|
||||||
|
pub const NOTE_DURATIONS: [(usize, &str);26] = [
|
||||||
|
(1, "1/384"), (2, "1/192"),
|
||||||
|
(3, "1/128"), (4, "1/96"),
|
||||||
|
(6, "1/64"), (8, "1/48"),
|
||||||
|
(12, "1/32"), (16, "1/24"),
|
||||||
|
(24, "1/16"), (32, "1/12"),
|
||||||
|
(48, "1/8"), (64, "1/6"),
|
||||||
|
(96, "1/4"), (128, "1/3"),
|
||||||
|
(192, "1/2"), (256, "2/3"),
|
||||||
|
(384, "1/1"), (512, "4/3"),
|
||||||
|
(576, "3/2"), (768, "2/1"),
|
||||||
|
(1152, "3/1"), (1536, "4/1"),
|
||||||
|
(2304, "6/1"), (3072, "8/1"),
|
||||||
|
(3456, "9/1"), (6144, "16/1"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const NOTE_NAMES: [&str; 128] = [
|
||||||
|
"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
|
||||||
|
"C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
|
||||||
|
"C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
|
||||||
|
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
|
||||||
|
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
|
||||||
|
"C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
|
||||||
|
"C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
|
||||||
|
"C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
|
||||||
|
"C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
|
||||||
|
"C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9",
|
||||||
|
"C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10",
|
||||||
|
];
|
||||||
|
|
||||||
|
impl LaunchSync {
|
||||||
|
pub fn next (&self) -> f64 {
|
||||||
|
note_duration_next(self.get() as usize) as f64
|
||||||
|
}
|
||||||
|
pub fn prev (&self) -> f64 {
|
||||||
|
note_duration_prev(self.get() as usize) as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Quantize {
|
||||||
|
pub fn next (&self) -> f64 {
|
||||||
|
note_duration_next(self.get() as usize) as f64
|
||||||
|
}
|
||||||
|
pub fn prev (&self) -> f64 {
|
||||||
|
note_duration_prev(self.get() as usize) as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement an arithmetic operation for a unit of time
|
||||||
|
#[macro_export] macro_rules! impl_op {
|
||||||
|
($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => {
|
||||||
|
impl $Op<Self> for $T {
|
||||||
|
type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output {
|
||||||
|
let $a = self.get(); let $b = other.get(); Self($impl.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl $Op<usize> for $T {
|
||||||
|
type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output {
|
||||||
|
let $a = self.get(); let $b = other as f64; Self($impl.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl $Op<f64> for $T {
|
||||||
|
type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output {
|
||||||
|
let $a = self.get(); let $b = other; Self($impl.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Clock {
|
||||||
|
fn fmt (&self, f: &mut 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 new (jack: &Jack<'static>, bpm: Option<f64>) -> Usually<Self> {
|
||||||
|
let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
|
||||||
|
let timebase = Arc::new(Timebase::default());
|
||||||
|
let clock = 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,
|
||||||
|
midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))),
|
||||||
|
midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))),
|
||||||
|
click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
if let Some(bpm) = bpm {
|
||||||
|
clock.timebase.bpm.set(bpm);
|
||||||
|
}
|
||||||
|
Ok(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) -> &Bpm {
|
||||||
|
&self.timebase.bpm
|
||||||
|
}
|
||||||
|
/// Current MIDI resolution
|
||||||
|
pub fn ppq (&self) -> &Ppq {
|
||||||
|
&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.try_read().unwrap().is_none()
|
||||||
|
}
|
||||||
|
/// Is currently playing?
|
||||||
|
pub fn is_rolling (&self) -> bool {
|
||||||
|
self.started.try_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.try_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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_launch_instant (&self) -> Moment {
|
||||||
|
Moment::from_pulse(self.timebase(), self.next_launch_pulse() as f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get index of first sample to populate.
|
||||||
|
///
|
||||||
|
/// Greater than 0 means that the first pulse of the clip
|
||||||
|
/// falls somewhere in the middle of the chunk.
|
||||||
|
pub fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{
|
||||||
|
(scope.last_frame_time() as usize).saturating_sub(
|
||||||
|
started.sample.get() as usize +
|
||||||
|
self.started.try_read().unwrap().as_ref().unwrap().sample.get() as usize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get iterator that emits sample paired with pulse.
|
||||||
|
//
|
||||||
|
// * Sample: index into output buffer at which to write MIDI event
|
||||||
|
// * Pulse: index into clip from which to take the MIDI event
|
||||||
|
//
|
||||||
|
// Emitted for each sample of the output buffer that corresponds to a MIDI pulse.
|
||||||
|
pub fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> Ticker {
|
||||||
|
self.timebase().pulses_between_samples(offset, offset + scope.n_frames() as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_has!(Clock: |self: Track|self.sequencer.clock);
|
||||||
|
impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ));
|
||||||
|
|
||||||
|
/// Define and implement a unit of time
|
||||||
|
#[macro_export] macro_rules! impl_time_unit {
|
||||||
|
($T:ident) => {
|
||||||
|
impl Gettable<f64> for $T {
|
||||||
|
fn get (&self) -> f64 { self.0.load(Relaxed) }
|
||||||
|
}
|
||||||
|
impl InteriorMutable<f64> for $T {
|
||||||
|
fn set (&self, value: f64) -> f64 {
|
||||||
|
let old = self.get();
|
||||||
|
self.0.store(value, Relaxed);
|
||||||
|
old
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TimeUnit for $T {}
|
||||||
|
impl_op!($T, Add, add, |a, b|{a + b});
|
||||||
|
impl_op!($T, Sub, sub, |a, b|{a - b});
|
||||||
|
impl_op!($T, Mul, mul, |a, b|{a * b});
|
||||||
|
impl_op!($T, Div, div, |a, b|{a / b});
|
||||||
|
impl_op!($T, Rem, rem, |a, b|{a % b});
|
||||||
|
impl From<f64> for $T { fn from (value: f64) -> Self { Self(value.into()) } }
|
||||||
|
impl From<usize> for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } }
|
||||||
|
impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } }
|
||||||
|
impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } }
|
||||||
|
impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } }
|
||||||
|
impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } }
|
||||||
|
impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_time_unit!(SampleCount);
|
||||||
|
impl_time_unit!(SampleRate);
|
||||||
|
impl_time_unit!(Microsecond);
|
||||||
|
impl_time_unit!(Quantize);
|
||||||
|
impl_time_unit!(Ppq);
|
||||||
|
impl_time_unit!(Pulse);
|
||||||
|
impl_time_unit!(Bpm);
|
||||||
|
impl_time_unit!(LaunchSync);
|
||||||
|
|
||||||
|
pub trait Gettable<T> {
|
||||||
|
/// Returns current value
|
||||||
|
fn get (&self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Mutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&mut self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InteriorMutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Timebase::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)] pub struct Timebase {
|
||||||
|
/// Audio samples per second
|
||||||
|
pub sr: SampleRate,
|
||||||
|
/// MIDI beats per minute
|
||||||
|
pub bpm: Bpm,
|
||||||
|
/// MIDI ticks per beat
|
||||||
|
pub ppq: Ppq,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio sample rate in Hz (samples per second)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Tempo in beats per minute
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Bpm (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// MIDI resolution in PPQ (pulses per quarter note)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Ppq (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
impl Timebase {
|
||||||
|
/// Specify sample rate, BPM and PPQ
|
||||||
|
pub fn new (
|
||||||
|
s: impl Into<SampleRate>,
|
||||||
|
b: impl Into<Bpm>,
|
||||||
|
p: impl Into<Ppq>
|
||||||
|
) -> Self {
|
||||||
|
Self { sr: s.into(), bpm: b.into(), ppq: p.into() }
|
||||||
|
}
|
||||||
|
/// Iterate over ticks between start and end.
|
||||||
|
#[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> Ticker {
|
||||||
|
Ticker { spp: self.samples_per_pulse(), sample: start, start, end }
|
||||||
|
}
|
||||||
|
/// Return the duration fo a beat in microseconds
|
||||||
|
#[inline] pub fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm.get() }
|
||||||
|
/// Return the number of beats in a second
|
||||||
|
#[inline] pub fn beat_per_second (&self) -> f64 { self.bpm.get() / 60f64 }
|
||||||
|
/// Return the number of microseconds corresponding to a note of the given duration
|
||||||
|
#[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 {
|
||||||
|
4.0 * self.usec_per_beat() * num / den
|
||||||
|
}
|
||||||
|
/// Return duration of a pulse in microseconds (BPM-dependent)
|
||||||
|
#[inline] pub fn pulse_per_usec (&self) -> f64 { self.ppq.get() / self.usec_per_beat() }
|
||||||
|
/// Return duration of a pulse in microseconds (BPM-dependent)
|
||||||
|
#[inline] pub fn usec_per_pulse (&self) -> f64 { self.usec_per_beat() / self.ppq.get() }
|
||||||
|
/// Return number of pulses to which a number of microseconds corresponds (BPM-dependent)
|
||||||
|
#[inline] pub fn usecs_to_pulse (&self, usec: f64) -> f64 { usec * self.pulse_per_usec() }
|
||||||
|
/// Convert a number of pulses to a sample number (SR- and BPM-dependent)
|
||||||
|
#[inline] pub fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() }
|
||||||
|
/// Return number of pulses in a second (BPM-dependent)
|
||||||
|
#[inline] pub fn pulses_per_second (&self) -> f64 { self.beat_per_second() * self.ppq.get() }
|
||||||
|
/// Return fraction of a pulse to which a sample corresponds (SR- and BPM-dependent)
|
||||||
|
#[inline] pub fn pulses_per_sample (&self) -> f64 {
|
||||||
|
self.usec_per_pulse() / self.sr.usec_per_sample()
|
||||||
|
}
|
||||||
|
/// Return number of samples in a pulse (SR- and BPM-dependent)
|
||||||
|
#[inline] pub fn samples_per_pulse (&self) -> f64 {
|
||||||
|
self.sr.get() / self.pulses_per_second()
|
||||||
|
}
|
||||||
|
/// Convert a number of pulses to a sample number (SR- and BPM-dependent)
|
||||||
|
#[inline] pub fn pulses_to_sample (&self, p: f64) -> f64 {
|
||||||
|
self.pulses_per_sample() * p
|
||||||
|
}
|
||||||
|
/// Convert a number of samples to a pulse number (SR- and BPM-dependent)
|
||||||
|
#[inline] pub fn samples_to_pulse (&self, s: f64) -> f64 {
|
||||||
|
s / self.pulses_per_sample()
|
||||||
|
}
|
||||||
|
/// Return the number of samples corresponding to a note of the given duration
|
||||||
|
#[inline] pub fn note_to_samples (&self, note: (f64, f64)) -> f64 {
|
||||||
|
self.usec_to_sample(self.note_to_usec(note))
|
||||||
|
}
|
||||||
|
/// Return the number of samples corresponding to the given number of microseconds
|
||||||
|
#[inline] pub fn usec_to_sample (&self, usec: f64) -> f64 {
|
||||||
|
usec * self.sr.get() / 1000f64
|
||||||
|
}
|
||||||
|
/// Return the quantized position of a moment in time given a step
|
||||||
|
#[inline] pub fn quantize (&self, step: (f64, f64), time: f64) -> (f64, f64) {
|
||||||
|
let step = self.note_to_usec(step);
|
||||||
|
(time / step, time % step)
|
||||||
|
}
|
||||||
|
/// Quantize a collection of events
|
||||||
|
#[inline] pub fn quantize_into <E: Iterator<Item=(f64, f64)> + Sized, T> (
|
||||||
|
&self, step: (f64, f64), events: E
|
||||||
|
) -> Vec<(f64, f64)> {
|
||||||
|
events.map(|(time, event)|(self.quantize(step, time).0, event)).collect()
|
||||||
|
}
|
||||||
|
/// Format a number of pulses into Beat.Bar.Pulse starting from 0
|
||||||
|
#[inline] pub fn format_beats_0 (&self, pulse: f64) -> Arc<str> {
|
||||||
|
let pulse = pulse as usize;
|
||||||
|
let ppq = self.ppq.get() as usize;
|
||||||
|
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||||
|
format!("{}.{}.{pulses:02}", beats / 4, beats % 4).into()
|
||||||
|
}
|
||||||
|
/// Format a number of pulses into Beat.Bar starting from 0
|
||||||
|
#[inline] pub fn format_beats_0_short (&self, pulse: f64) -> Arc<str> {
|
||||||
|
let pulse = pulse as usize;
|
||||||
|
let ppq = self.ppq.get() as usize;
|
||||||
|
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
||||||
|
format!("{}.{}", beats / 4, beats % 4).into()
|
||||||
|
}
|
||||||
|
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
||||||
|
#[inline] pub fn format_beats_1 (&self, pulse: f64) -> Arc<str> {
|
||||||
|
let mut string = String::with_capacity(16);
|
||||||
|
self.format_beats_1_to(&mut string, pulse).expect("failed to format {pulse} into beat");
|
||||||
|
string.into()
|
||||||
|
}
|
||||||
|
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
||||||
|
#[inline] pub fn format_beats_1_to (&self, w: &mut impl std::fmt::Write, pulse: f64) -> Result<(), std::fmt::Error> {
|
||||||
|
let pulse = pulse as usize;
|
||||||
|
let ppq = self.ppq.get() as usize;
|
||||||
|
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||||
|
write!(w, "{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1)
|
||||||
|
}
|
||||||
|
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
||||||
|
#[inline] pub fn format_beats_1_short (&self, pulse: f64) -> Arc<str> {
|
||||||
|
let pulse = pulse as usize;
|
||||||
|
let ppq = self.ppq.get() as usize;
|
||||||
|
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
||||||
|
format!("{}.{}", beats / 4 + 1, beats % 4 + 1).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SampleRate {
|
||||||
|
/// Return the duration of a sample in microseconds (floating)
|
||||||
|
#[inline] pub fn usec_per_sample (&self) -> f64 {
|
||||||
|
1_000_000f64 / self.get()
|
||||||
|
}
|
||||||
|
/// Return the duration of a sample in microseconds (floating)
|
||||||
|
#[inline] pub fn sample_per_usec (&self) -> f64 {
|
||||||
|
self.get() / 1_000_000f64
|
||||||
|
}
|
||||||
|
/// Convert a number of samples to microseconds (floating)
|
||||||
|
#[inline] pub fn samples_to_usec (&self, samples: f64) -> f64 {
|
||||||
|
self.usec_per_sample() * samples
|
||||||
|
}
|
||||||
|
/// Convert a number of microseconds to samples (floating)
|
||||||
|
#[inline] pub fn usecs_to_sample (&self, usecs: f64) -> f64 {
|
||||||
|
self.sample_per_usec() * usecs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A point in time in all time scales (microsecond, sample, MIDI pulse)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Moment::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default, Clone)] pub struct Moment {
|
||||||
|
pub timebase: Arc<Timebase>,
|
||||||
|
/// Current time in microseconds
|
||||||
|
pub usec: Microsecond,
|
||||||
|
/// Current time in audio samples
|
||||||
|
pub sample: SampleCount,
|
||||||
|
/// Current time in MIDI pulses
|
||||||
|
pub pulse: Pulse,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Moment2::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default)] pub enum Moment2 {
|
||||||
|
#[default] None,
|
||||||
|
Zero,
|
||||||
|
Usec(Microsecond),
|
||||||
|
Sample(SampleCount),
|
||||||
|
Pulse(Pulse),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Timestamp in microseconds
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Microsecond(0.0.into());
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Microsecond (pub AtomicF64);
|
||||||
|
|
||||||
|
/// Timestamp in audio samples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::SampleCount(0.0.into());
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct SampleCount (pub AtomicF64);
|
||||||
|
|
||||||
|
/// Timestamp in MIDI pulses
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Pulse(0.0.into());
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Pulse (pub AtomicF64);
|
||||||
|
|
||||||
|
impl Moment {
|
||||||
|
pub fn zero (timebase: &Arc<Timebase>) -> Self {
|
||||||
|
Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() }
|
||||||
|
}
|
||||||
|
pub fn from_usec (timebase: &Arc<Timebase>, usec: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
usec: usec.into(),
|
||||||
|
sample: timebase.sr.usecs_to_sample(usec).into(),
|
||||||
|
pulse: timebase.usecs_to_pulse(usec).into(),
|
||||||
|
timebase: timebase.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_sample (timebase: &Arc<Timebase>, sample: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
sample: sample.into(),
|
||||||
|
usec: timebase.sr.samples_to_usec(sample).into(),
|
||||||
|
pulse: timebase.samples_to_pulse(sample).into(),
|
||||||
|
timebase: timebase.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_pulse (timebase: &Arc<Timebase>, pulse: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
pulse: pulse.into(),
|
||||||
|
sample: timebase.pulses_to_sample(pulse).into(),
|
||||||
|
usec: timebase.pulses_to_usec(pulse).into(),
|
||||||
|
timebase: timebase.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline] pub fn update_from_usec (&self, usec: f64) {
|
||||||
|
self.usec.set(usec);
|
||||||
|
self.pulse.set(self.timebase.usecs_to_pulse(usec));
|
||||||
|
self.sample.set(self.timebase.sr.usecs_to_sample(usec));
|
||||||
|
}
|
||||||
|
#[inline] pub fn update_from_sample (&self, sample: f64) {
|
||||||
|
self.usec.set(self.timebase.sr.samples_to_usec(sample));
|
||||||
|
self.pulse.set(self.timebase.samples_to_pulse(sample));
|
||||||
|
self.sample.set(sample);
|
||||||
|
}
|
||||||
|
#[inline] pub fn update_from_pulse (&self, pulse: f64) {
|
||||||
|
self.usec.set(self.timebase.pulses_to_usec(pulse));
|
||||||
|
self.pulse.set(pulse);
|
||||||
|
self.sample.set(self.timebase.pulses_to_sample(pulse));
|
||||||
|
}
|
||||||
|
#[inline] pub fn format_beat (&self) -> Arc<str> {
|
||||||
|
self.timebase.format_beats_1(self.pulse.get()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Microsecond {
|
||||||
|
#[inline] pub fn format_msu (&self) -> Arc<str> {
|
||||||
|
let usecs = self.get() as usize;
|
||||||
|
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||||
|
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
||||||
|
format!("{minutes}:{seconds:02}:{msecs:03}").into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator that emits subsequent ticks within a range.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let iter = tek::Ticker::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Ticker {
|
||||||
|
pub spp: f64,
|
||||||
|
pub sample: usize,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Ticker {
|
||||||
|
type Item = (usize, usize);
|
||||||
|
fn next (&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
if self.sample > self.end { return None }
|
||||||
|
let spp = self.spp;
|
||||||
|
let sample = self.sample as f64;
|
||||||
|
let start = self.start;
|
||||||
|
let end = self.end;
|
||||||
|
self.sample += 1;
|
||||||
|
//println!("{spp} {sample} {start} {end}");
|
||||||
|
let jitter = sample.rem_euclid(spp); // ramps
|
||||||
|
let next_jitter = (sample + 1.0).rem_euclid(spp);
|
||||||
|
if jitter > next_jitter { // at crossing:
|
||||||
|
let time = (sample as usize) % (end as usize-start as usize);
|
||||||
|
let tick = (sample / spp) as usize;
|
||||||
|
return Some((time, tick))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains state for playing a clip
|
/// Contains state for playing a clip
|
||||||
///
|
///
|
||||||
|
|
|
||||||
61
src/tek.rs
61
src/tek.rs
|
|
@ -10,18 +10,14 @@ macro_rules! def_sizes_iter {
|
||||||
|
|
||||||
mod bind; pub use self::bind::*;
|
mod bind; pub use self::bind::*;
|
||||||
mod cli; pub use self::cli::*;
|
mod cli; pub use self::cli::*;
|
||||||
mod clock; pub use self::clock::*;
|
|
||||||
mod conf; pub use self::conf::*;
|
mod conf; pub use self::conf::*;
|
||||||
mod deps; pub use self::deps::*;
|
mod deps; pub use self::deps::*;
|
||||||
mod device; pub use self::device::*;
|
|
||||||
mod mixer; pub use self::mixer::*;
|
mod mixer; pub use self::mixer::*;
|
||||||
mod mode; pub use self::mode::*;
|
mod mode; pub use self::mode::*;
|
||||||
mod pool; pub use self::pool::*;
|
mod pool; pub use self::pool::*;
|
||||||
mod proj; pub use self::proj::*;
|
mod project; pub use self::project::*;
|
||||||
mod sample; pub use self::sample::*;
|
mod sample; pub use self::sample::*;
|
||||||
mod sequence; pub use self::sequence::*;
|
mod sequence; pub use self::sequence::*;
|
||||||
mod scene; pub use self::scene::*;
|
|
||||||
mod track; pub use self::track::*;
|
|
||||||
mod view; pub use self::view::*;
|
mod view; pub use self::view::*;
|
||||||
|
|
||||||
#[cfg(feature = "lv2")] mod lv2;
|
#[cfg(feature = "lv2")] mod lv2;
|
||||||
|
|
@ -184,6 +180,61 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasJack<'static> for App {
|
||||||
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_audio!(App: tek_jack_process, tek_jack_event);
|
||||||
|
|
||||||
|
fn tek_jack_process (state: &mut App, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
let t0 = state.perf.get_t0();
|
||||||
|
state.clock().update_from_scope(scope).unwrap();
|
||||||
|
let midi_in = state.project.midi_input_collect(scope);
|
||||||
|
if let Some(editor) = &state.editor() {
|
||||||
|
let mut pitch: Option<u7> = None;
|
||||||
|
for port in midi_in.iter() {
|
||||||
|
for event in port.iter() {
|
||||||
|
if let (_, Ok(LiveEvent::Midi {message: MidiMessage::NoteOn {key, ..}, ..}))
|
||||||
|
= event
|
||||||
|
{
|
||||||
|
pitch = Some(key.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(pitch) = pitch {
|
||||||
|
editor.set_note_pos(pitch.as_int() as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = state.project.tracks_jack_process(client, scope);
|
||||||
|
state.perf.update_from_jack_scope(t0, scope);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tek_jack_event (state: &mut App, event: JackEvent) {
|
||||||
|
use JackEvent::*;
|
||||||
|
match event {
|
||||||
|
SampleRate(sr) => { state.clock().timebase.sr.set(sr as f64); },
|
||||||
|
PortRegistration(_id, true) => {
|
||||||
|
//let port = self.jack().port_by_id(id);
|
||||||
|
//println!("\rport add: {id} {port:?}");
|
||||||
|
//println!("\rport add: {id}");
|
||||||
|
},
|
||||||
|
PortRegistration(_id, false) => {
|
||||||
|
/*println!("\rport del: {id}")*/
|
||||||
|
},
|
||||||
|
PortsConnected(_a, _b, true) => { /*println!("\rport conn: {a} {b}")*/ },
|
||||||
|
PortsConnected(_a, _b, false) => { /*println!("\rport disc: {a} {b}")*/ },
|
||||||
|
ClientRegistration(_id, true) => {},
|
||||||
|
ClientRegistration(_id, false) => {},
|
||||||
|
ThreadInit => {},
|
||||||
|
XRun => {},
|
||||||
|
GraphReorder => {},
|
||||||
|
_ => { panic!("{event:?}"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_arc_str <'a> (_: &App, src: impl Language) -> Perhaps<Arc<str>> {
|
fn get_arc_str <'a> (_: &App, src: impl Language) -> Perhaps<Arc<str>> {
|
||||||
Ok(src.src()?.map(|x|x.into()))
|
Ok(src.src()?.map(|x|x.into()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
87
src/track.rs
87
src/track.rs
|
|
@ -1,87 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// A track consists of a sequencer and zero or more devices chained after it.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let track: tek::Track = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct Track {
|
|
||||||
/// Name of track
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Identifying color of track
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Preferred width of track column
|
|
||||||
pub width: usize,
|
|
||||||
/// MIDI sequencer state
|
|
||||||
pub sequencer: Sequencer,
|
|
||||||
/// Device chain
|
|
||||||
pub devices: Vec<Device>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Track {
|
|
||||||
/// Create a new track with only the default [Sequencer].
|
|
||||||
pub fn new (
|
|
||||||
name: &impl AsRef<str>,
|
|
||||||
color: Option<ItemTheme>,
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
clock: Option<&Clock>,
|
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
||||||
midi_from: &[Connect],
|
|
||||||
midi_to: &[Connect],
|
|
||||||
) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
color: color.unwrap_or_default(),
|
|
||||||
sequencer: Sequencer::new(
|
|
||||||
format!("{}/sequencer", name.as_ref()),
|
|
||||||
jack, clock, clip, midi_from, midi_to
|
|
||||||
)?,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn new_added (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
clock: Option<&Clock>,
|
|
||||||
name: &impl AsRef<str>,
|
|
||||||
color: Option<ItemTheme>,
|
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
||||||
mins: Arc<[Connect]>,
|
|
||||||
mouts: Arc<[Connect]>,
|
|
||||||
) -> Usually<Self> {
|
|
||||||
let name = name.as_ref();
|
|
||||||
Ok(Self {
|
|
||||||
name: name.into(),
|
|
||||||
width: (name.len() + 2).max(12),
|
|
||||||
color: color.unwrap_or_else(ItemTheme::random),
|
|
||||||
sequencer: Sequencer::new(name, jack, clock, clip, mins.as_ref(), mouts.as_ref())?,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
|
||||||
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
|
||||||
}
|
|
||||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
|
||||||
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
|
||||||
}
|
|
||||||
pub fn stop (&mut self) -> Perhaps<TrackCmd> {
|
|
||||||
self.sequencer.enqueue_next(None);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasWidth for Track {
|
|
||||||
const MIN_WIDTH: usize = 9;
|
|
||||||
fn width_inc (&mut self) {
|
|
||||||
self.width += 1;
|
|
||||||
}
|
|
||||||
fn width_dec (&mut self) {
|
|
||||||
if self.width > Track::MIN_WIDTH { self.width -= 1; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def_sizes_iter!(TracksSizes => Track);
|
|
||||||
impl_has!(Vec<Track>: |self: Project| self.tracks);
|
|
||||||
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
|
|
||||||
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
|
||||||
impl_has!(Sequencer: |self: Track| self.sequencer);
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue