wip: f64 timebase (sloooo)

This commit is contained in:
🪞👃🪞 2024-07-01 17:48:16 +03:00
parent c4d8692b71
commit 4055662bbd
12 changed files with 206 additions and 142 deletions

82
Cargo.lock generated
View file

@ -84,6 +84,12 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "atomic_float"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c4b08ed8a30ff7320117c190eb4d73d47f0ac0c930ab853b8224cef7cd9a5e7"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.3.0" version = "1.3.0"
@ -299,6 +305,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fraction"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7"
dependencies = [
"lazy_static",
"num",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.29.0" version = "0.29.0"
@ -534,6 +550,70 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -893,10 +973,12 @@ dependencies = [
name = "tek" name = "tek"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"atomic_float",
"backtrace", "backtrace",
"better-panic", "better-panic",
"clap", "clap",
"crossterm", "crossterm",
"fraction",
"jack", "jack",
"livi", "livi",
"microxdg", "microxdg",

View file

@ -19,3 +19,5 @@ livi = "0.7.4"
#atomic_enum = "0.3.0" #atomic_enum = "0.3.0"
wavers = "1.4.3" wavers = "1.4.3"
music-math = "0.1.1" music-math = "0.1.1"
atomic_float = "1.0.0"
fraction = "0.15.3"

View file

@ -2,101 +2,96 @@ use crate::core::*;
pub struct Timebase { pub struct Timebase {
/// Frames per second /// Frames per second
pub rate: AtomicUsize, pub rate: ::atomic_float::AtomicF64,
/// Beats per minute /// Beats per minute
pub bpm: AtomicUsize, pub bpm: ::atomic_float::AtomicF64,
/// Ticks per beat /// Ticks per beat
pub ppq: AtomicUsize, pub ppq: ::atomic_float::AtomicF64,
}
/// NoteDuration in musical terms. Has definite usec value
/// for given bpm and sample rate.
pub enum NoteDuration {
Nth(usize, usize),
Dotted(Box<Self>),
Tuplet(usize, Box<Self>),
} }
impl Timebase { impl Timebase {
#[inline] fn rate (&self) -> usize { pub fn new (rate: f64, bpm: f64, ppq: f64) -> Self {
Self { rate: rate.into(), bpm: bpm.into(), ppq: ppq.into() }
}
#[inline] fn rate (&self) -> f64 {
self.rate.load(Ordering::Relaxed) self.rate.load(Ordering::Relaxed)
} }
#[inline] fn frame_usec (&self) -> usize { #[inline] fn frame_usec (&self) -> f64 {
1_000_000 / self.rate() 1_000_000 as f64 / self.rate() as f64
} }
#[inline] pub fn frame_to_usec (&self, frame: usize) -> usize { #[inline] pub fn frames_usecs (&self, frame: f64) -> f64 {
frame * 1000000 / self.rate() frame * self.frame_usec()
} }
#[inline] pub fn bpm (&self) -> usize { #[inline] pub fn bpm (&self) -> f64 {
self.bpm.load(Ordering::Relaxed) self.bpm.load(Ordering::Relaxed)
} }
#[inline] fn beat_usec (&self) -> usize { #[inline] fn beat_usec (&self) -> f64 {
60_000_000_000_000 / self.bpm() 60_000_000_000_000f64 / self.bpm() as f64
}
#[inline] fn beats_per_second (&self) -> f64 {
self.bpm() as f64 / 60000000.0
} }
#[inline] pub fn ppq (&self) -> usize { #[inline] pub fn ppq (&self) -> f64 {
self.ppq.load(Ordering::Relaxed) self.ppq.load(Ordering::Relaxed)
} }
#[inline] fn pulse_usec (&self) -> usize { #[inline] fn pulse_usec (&self) -> f64 {
self.beat_usec() / self.ppq() self.beat_usec() / self.ppq() as f64
} }
#[inline] fn pulse_frame (&self) -> usize { #[inline] fn pulse_frame (&self) -> f64 {
self.pulse_usec() / self.frame_usec() self.pulse_usec() / self.frame_usec() as f64
} }
#[inline] pub fn pulses_frames (&self, pulses: usize) -> usize { #[inline] pub fn pulses_frames (&self, pulses: f64) -> f64 {
self.pulse_frame() * pulses self.pulse_frame() * pulses
} }
#[inline] pub fn frames_pulses (&self, frames: usize) -> usize { #[inline] pub fn frames_pulses (&self, frames: f64) -> f64 {
frames / self.pulse_frame() frames / self.pulse_frame()
} }
#[inline] pub fn frames_per_tick (&self) -> f64 { #[inline] pub fn usec_per_step (&self, divisor: f64) -> f64 {
self.rate() as f64 / self.ticks_per_second()
}
#[inline] fn ticks_per_second (&self) -> f64 {
self.beats_per_second() * self.ppq() as f64
}
#[inline] fn usec_to_frame (&self, usec: usize) -> usize {
usec * self.rate() / 1000
}
#[inline] pub fn usec_per_step (&self, divisor: usize) -> usize {
self.beat_usec() / divisor self.beat_usec() / divisor
} }
#[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 {
#[inline] pub fn note_to_usec (&self, note: &NoteDuration) -> usize { 4.0 * self.beat_usec() * num / den
match note {
NoteDuration::Nth(time, flies) =>
self.beat_usec() * *time / *flies,
NoteDuration::Dotted(note) =>
self.note_to_usec(note) * 3 / 2,
NoteDuration::Tuplet(n, note) =>
self.note_to_usec(note) * 2 / *n,
}
} }
#[inline] pub fn note_to_frame (&self, note: &NoteDuration) -> usize { #[inline] fn usec_to_frame (&self, usec: f64) -> f64 {
usec * self.rate() / 1000.0
}
#[inline] pub fn note_to_frame (&self, note: (f64, f64)) -> f64 {
self.usec_to_frame(self.note_to_usec(note)) self.usec_to_frame(self.note_to_usec(note))
} }
#[inline] pub fn quantize (&self, step: &NoteDuration, time: usize) -> (usize, usize) { #[inline] pub fn quantize (
&self, step: (f64, f64), time: f64
) -> (f64, f64) {
let step = self.note_to_usec(step); let step = self.note_to_usec(step);
let time = time / step; (time / step, time % step)
let offset = time % step;
(time, offset)
} }
#[inline] pub fn quantize_into <T, U> (&self, step: &NoteDuration, events: U) -> Vec<(usize, T)> #[inline] pub fn quantize_into <E, T> (
where U: std::iter::Iterator<Item=(usize, T)> + Sized &self, step: (f64, f64), events: E
) -> Vec<(f64, T)>
where E: std::iter::Iterator<Item=(f64, T)> + Sized
{ {
let step = (step.0.into(), step.1.into());
events events
.map(|(time, event)|(self.quantize(step, time).0, event)) .map(|(time, event)|(self.quantize(step, time).0, event))
.collect() .collect()
} }
pub fn frames_to_ticks (&self, start: usize, end: usize, quant: usize) -> Vec<(usize, usize)> { #[inline] fn beats_per_second (&self) -> f64 {
self.bpm() as f64 / 60000000.0
}
#[inline] fn ticks_per_second (&self) -> f64 {
self.beats_per_second() * self.ppq() as f64
}
#[inline] pub fn frames_per_tick (&self) -> f64 {
self.rate() as f64 / self.ticks_per_second()
}
pub fn frames_to_ticks (
&self,
start: f64,
end: f64,
quant: f64,
) -> Vec<(usize, usize)> {
let start_frame = start % quant; let start_frame = start % quant;
let end_frame = end % quant; let end_frame = end % quant;
let fpt = self.frames_per_tick(); let fpt = self.frames_per_tick();
@ -106,24 +101,24 @@ impl Timebase {
let last_jitter = (frame - 1.0).max(0.0) % fpt; let last_jitter = (frame - 1.0).max(0.0) % fpt;
let next_jitter = frame + 1.0 % fpt; let next_jitter = frame + 1.0 % fpt;
if jitter <= last_jitter && jitter <= next_jitter { if jitter <= last_jitter && jitter <= next_jitter {
ticks.push((frame as usize % (end-start), (frame / fpt) as usize)); ticks.push((frame as usize % (end as usize-start as usize), (frame / fpt) as usize));
}; };
}; };
if start_frame < end_frame { if start_frame < end_frame {
for frame in start_frame..end_frame { for frame in start_frame as usize..end_frame as usize {
add_frame(frame as f64); add_frame(frame as f64);
} }
} else { } else {
let mut frame = start_frame; let mut frame = start_frame as usize;
loop { loop {
add_frame(frame as f64); add_frame(frame as f64);
frame = frame + 1; frame = frame + 1;
if frame >= quant { if frame >= quant as usize {
frame = 0; frame = 0;
loop { loop {
add_frame(frame as f64); add_frame(frame as f64);
frame = frame + 1; frame = frame + 1;
if frame >= end_frame.saturating_sub(1) { if frame >= (end_frame as usize).saturating_sub(1) {
break break
} }
} }

View file

@ -46,7 +46,7 @@ impl Launcher {
) -> Result<DynamicDevice<Self>, Box<dyn Error>> { ) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let transport = client.transport(); let transport = client.transport();
let ppq = timebase.ppq() as u32; let ppq = timebase.ppq() as usize;
DynamicDevice::new(render, handle, process, Self { DynamicDevice::new(render, handle, process, Self {
name: name.into(), name: name.into(),
view: LauncherView::Chains, view: LauncherView::Chains,
@ -187,7 +187,7 @@ pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually<Re
draw_rec(buf, x + 12, y, state.recording); draw_rec(buf, x + 12, y, state.recording);
draw_mon(buf, x + 19, y, state.monitoring); draw_mon(buf, x + 19, y, state.monitoring);
draw_dub(buf, x + 26, y, state.overdub); draw_dub(buf, x + 26, y, state.overdub);
draw_bpm(buf, x + 33, y, state.timebase.bpm()); draw_bpm(buf, x + 33, y, state.timebase.bpm() as usize);
draw_timer(buf, x + width - 1, y, &state.timebase, state.position); draw_timer(buf, x + width - 1, y, &state.timebase, state.position);
} }
let mut y = y + 1; let mut y = y + 1;
@ -226,12 +226,12 @@ fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Us
timer(buf, x+5, y, sequencer.time_start, sequencer.time_start + area.width as usize, 0); timer(buf, x+5, y, sequencer.time_start, sequencer.time_start + area.width as usize, 0);
keys(buf, Rect { x, y: y + 1, width, height }, sequencer.note_start)?; keys(buf, Rect { x, y: y + 1, width, height }, sequencer.note_start)?;
if let Some(Some(phrase)) = state.phrase_id().map(|id|sequencer.phrases.get(id)) { if let Some(Some(phrase)) = state.phrase_id().map(|id|sequencer.phrases.get(id)) {
let ppq = sequencer.timebase.ppq() as u32; let ppq = sequencer.timebase.ppq() as usize;
let zoom = sequencer.time_zoom as u32; let zoom = sequencer.time_zoom;
let t0 = sequencer.time_start as u32; let t0 = sequencer.time_start;
let t1 = t0 + area.width as u32; let t1 = t0 + area.width as usize;
let n0 = sequencer.note_start as u32; let n0 = sequencer.note_start;
let n1 = n0 + area.height as u32; let n1 = n0 + area.height as usize;
lanes(buf, x, y + 1, &phrase, ppq, zoom, t0, t1, n0, n1); lanes(buf, x, y + 1, &phrase, ppq, zoom, t0, t1, n0, n1);
} }
let cursor_style = match view { let cursor_style = match view {

View file

@ -50,9 +50,10 @@ fn note_add (s: &mut Sequencer) -> Usually<bool> {
if s.sequence.is_none() { if s.sequence.is_none() {
return Ok(false) return Ok(false)
} }
let step = (s.time_start + s.time_cursor) as u32; let ppq = s.timebase.ppq() as usize;
let start = (step as usize * s.timebase.ppq() / s.time_zoom) as u32; let step = s.time_start + s.time_cursor;
let end = ((step + 1) as usize * s.timebase.ppq() / s.time_zoom) as u32; let start = step as usize * ppq / s.time_zoom;
let end = (step + 1) as usize * ppq / s.time_zoom;
let key = u7::from_int_lossy((s.note_cursor + s.note_start) as u8); let key = u7::from_int_lossy((s.note_cursor + s.note_start) as u8);
let note_on = MidiMessage::NoteOn { key, vel: 100.into() }; let note_on = MidiMessage::NoteOn { key, vel: 100.into() };
let note_off = MidiMessage::NoteOff { key, vel: 100.into() }; let note_off = MidiMessage::NoteOff { key, vel: 100.into() };

View file

@ -13,12 +13,12 @@ pub fn draw (
if let Some(phrase) = s.phrase() { if let Some(phrase) = s.phrase() {
lanes(buf, x, y, lanes(buf, x, y,
phrase, phrase,
s.timebase.ppq() as u32, s.timebase.ppq() as usize,
s.time_zoom as u32, s.time_zoom,
s.time_start as u32, s.time_start,
s.time_start as u32 + area.width as u32, s.time_start + area.width as usize,
s.note_start as u32, s.note_start,
s.note_start as u32 + area.height as u32, s.note_start + area.height as usize,
); );
} }
cursor( cursor(
@ -76,17 +76,17 @@ pub fn lanes (
x: u16, x: u16,
y: u16, y: u16,
phrase: &Phrase, phrase: &Phrase,
ppq: u32, ppq: usize,
time_zoom: u32, time_zoom: usize,
time0: u32, time0: usize,
time1: u32, time1: usize,
note0: u32, note0: usize,
note1: u32, note1: usize,
) { ) {
let bg = Style::default(); let bg = Style::default();
let (bw, wh) = (bg.dim(), bg.white()); let (bw, wh) = (bg.dim(), bg.white());
for step in time0..time1 { for step in time0..time1 {
let x = x as u32 + 5 + step; let x = x as usize + 5 + step;
let (a, b) = ((step + 0) * ppq / time_zoom, (step + 1) * ppq / time_zoom,); let (a, b) = ((step + 0) * ppq / time_zoom, (step + 1) * ppq / time_zoom,);
if step % 4 == 0 { if step % 4 == 0 {
"|".blit(buf, x as u16, y - 1, Some(Style::default().dim())); "|".blit(buf, x as u16, y - 1, Some(Style::default().dim()));
@ -95,7 +95,7 @@ pub fn lanes (
format!("{}", step / time_zoom / 4 + 1) format!("{}", step / time_zoom / 4 + 1)
.blit(buf, x as u16, y - 1, Some(Style::default().bold().not_dim())); .blit(buf, x as u16, y - 1, Some(Style::default().bold().not_dim()));
} }
let h = ((note1-note0)/2).saturating_sub(y as u32); let h = ((note1-note0)/2).saturating_sub(y as usize);
for k in 0..h { for k in 0..h {
let (character, style) = match ( let (character, style) = match (
contains_note_on(phrase, u7::from_int_lossy((note0 + k * 2 + 0) as u8), a, b), contains_note_on(phrase, u7::from_int_lossy((note0 + k * 2 + 0) as u8), a, b),
@ -106,7 +106,7 @@ pub fn lanes (
(false, true) => ("", wh), (false, true) => ("", wh),
(false, false) => ("·", bw), (false, false) => ("·", bw),
}; };
let y = y as u32 + k; let y = y as usize + k;
character.blit(buf, x as u16, y as u16, Some(style)); character.blit(buf, x as u16, y as u16, Some(style));
} }
} }

View file

@ -66,7 +66,7 @@ impl Sequencer {
timebase: timebase.clone(), timebase: timebase.clone(),
sequence: Some(0), sequence: Some(0),
phrases: phrases.unwrap_or_else(||vec![ phrases: phrases.unwrap_or_else(||vec![
Phrase::new("Phrase0", 4*timebase.ppq() as u32, None) Phrase::new("Phrase0", 4 * timebase.ppq() as usize, None)
]), ]),
transport, transport,
@ -95,18 +95,13 @@ impl PortList for Sequencer {
} }
fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, height } = area; let Rect { x, y, width, height } = area;
let pos = s.transport.query().unwrap().pos; let header = draw_header(s, buf, area)?;
let frame = pos.frame();
let usecs = s.timebase.frame_to_usec(frame as usize);
let ustep = s.timebase.usec_per_step(s.time_zoom as usize);
let steps = usecs / ustep;
let header = draw_header(s, buf, area, steps)?;
let piano = match s.view { let piano = match s.view {
SequencerView::Tiny => Rect { x, y, width, height: 0 }, SequencerView::Tiny => Rect { x, y, width, height: 0 },
SequencerView::Compact => Rect { x, y, width, height: 0 }, SequencerView::Compact => Rect { x, y, width, height: 0 },
SequencerView::Vertical => self::vertical::draw(s, buf, Rect { SequencerView::Vertical => self::vertical::draw(s, buf, Rect {
x, y: y + header.height, width, height, x, y: y + header.height, width, height,
}, steps)?, })?,
SequencerView::Horizontal => self::horizontal::draw(s, buf, Rect { SequencerView::Horizontal => self::horizontal::draw(s, buf, Rect {
x, y: y + header.height, width, height, x, y: y + header.height, width, height,
})?, })?,
@ -117,7 +112,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
height: header.height + piano.height height: header.height + piano.height
})) }))
} }
pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, _: usize) -> Usually<Rect> { pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, .. } = area; let Rect { x, y, width, .. } = area;
let style = Style::default().gray(); let style = Style::default().gray();
crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing); crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing);
@ -149,7 +144,7 @@ pub fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect>
} }
Ok(Rect { x, y, width: 14, height: 14 }) Ok(Rect { x, y, width: 14, height: 14 })
} }
pub fn contains_note_on (sequence: &Phrase, k: u7, start: u32, end: u32) -> bool { pub fn contains_note_on (sequence: &Phrase, k: u7, start: usize, end: usize) -> bool {
for (_, (_, events)) in sequence.notes.range(start..end).enumerate() { for (_, (_, events)) in sequence.notes.range(start..end).enumerate() {
for event in events.iter() { for event in events.iter() {
match event { match event {

View file

@ -1,25 +1,21 @@
use crate::core::*; use crate::core::*;
pub type PhraseData = BTreeMap<u32, Vec<MidiMessage>>; pub type PhraseData = BTreeMap<usize, Vec<MidiMessage>>;
pub type MIDIChunk = [Option<Vec<Vec<u8>>>]; pub type MIDIMessage = Vec<u8>;
pub type MIDIChunk = [Option<Vec<MIDIMessage>>];
pub struct Phrase { pub struct Phrase {
pub name: String, pub name: String,
pub length: u32, pub length: usize,
pub notes: PhraseData, pub notes: PhraseData,
} }
impl Phrase { impl Phrase {
pub fn new (name: &str, length: u32, notes: Option<PhraseData>) -> Self { pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) } Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) }
} }
pub fn frames (&self, timebase: &Arc<Timebase>) -> usize {
timebase.pulses_frames(self.length as usize)
}
pub fn frame_to_pulse (&self, timebase: &Arc<Timebase>, frame: usize) -> usize {
timebase.frames_pulses(frame) % self.length as usize
}
/** Write a chunk of MIDI events to an output port. */ /** Write a chunk of MIDI events to an output port. */
pub fn chunk_out ( pub fn chunk_out (
&self, &self,
@ -29,9 +25,13 @@ impl Phrase {
frame0: usize, frame0: usize,
frames: usize, frames: usize,
) { ) {
let ticks = timebase.frames_to_ticks(frame0, frame0 + frames, self.frames(timebase)); let ticks = timebase.frames_to_ticks(
frame0 as f64,
(frame0 + frames) as f64,
timebase.pulses_frames(self.length as f64)
);
for (time, tick) in ticks.iter() { for (time, tick) in ticks.iter() {
let events = self.notes.get(&(*tick as u32)); let events = self.notes.get(&(*tick as usize));
if events.is_none() { if events.is_none() {
continue continue
} }
@ -67,7 +67,7 @@ impl Phrase {
) { ) {
for RawMidi { time, bytes } in input { for RawMidi { time, bytes } in input {
let time = time as usize; let time = time as usize;
let pulse = timebase.frames_pulses(frame0 + time) as u32; let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize;
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
if let MidiMessage::NoteOn { key, vel: _ } = message { if let MidiMessage::NoteOn { key, vel: _ } = message {
notes_on[key.as_int() as usize] = true; notes_on[key.as_int() as usize] = true;

View file

@ -32,7 +32,7 @@ pub fn process (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Cont
); );
} }
// Play from input to monitor, and record into phrase. // Play from input to monitor, and record into phrase.
//let usecs = state.timebase.frame_to_usec(frame); //let usecs = state.timebase.frames_usecs(frame);
//let steps = usecs / state.timebase.usec_per_step(state.time_zoom as usize); //let steps = usecs / state.timebase.usec_per_step(state.time_zoom as usize);
//let step = steps % state.steps; //let step = steps % state.steps;
//let tick = (step * state.timebase.ppq() / state.time_zoom) as u32; //let tick = (step * state.timebase.ppq() / state.time_zoom) as u32;

View file

@ -5,11 +5,10 @@ pub fn draw (
s: &Sequencer, s: &Sequencer,
buf: &mut Buffer, buf: &mut Buffer,
mut area: Rect, mut area: Rect,
beat: usize
) -> Usually<Rect> { ) -> Usually<Rect> {
area.x = area.x + 13; area.x = area.x + 13;
keys(s, buf, area, beat); keys(s, buf, area, 0);
steps(s, buf, area, beat); steps(s, buf, area, 0);
playhead(s, buf, area.x, area.y); playhead(s, buf, area.x, area.y);
Ok(area) Ok(area)
} }
@ -18,7 +17,7 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
if s.sequence.is_none() { if s.sequence.is_none() {
return return
} }
let ppq = s.timebase.ppq() as u32; let ppq = s.timebase.ppq() as usize;
let bg = Style::default(); let bg = Style::default();
let bw = bg.dim(); let bw = bg.dim();
let wh = bg.white(); let wh = bg.white();
@ -34,9 +33,9 @@ pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
let key = ::midly::num::u7::from_int_lossy(k as u8); let key = ::midly::num::u7::from_int_lossy(k as u8);
if step % 2 == 0 { if step % 2 == 0 {
let (a, b, c) = ( let (a, b, c) = (
(step + 0) as u32 * ppq / s.time_zoom as u32, (step + 0) as usize * ppq / s.time_zoom as usize,
(step + 1) as u32 * ppq / s.time_zoom as u32, (step + 1) as usize * ppq / s.time_zoom as usize,
(step + 2) as u32 * ppq / s.time_zoom as u32, (step + 2) as usize * ppq / s.time_zoom as usize,
); );
let (character, style) = match ( let (character, style) = match (
contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b), contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b),
@ -71,7 +70,7 @@ pub fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
} }
pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) { pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
let ppq = s.timebase.ppq() as u32; let ppq = s.timebase.ppq() as usize;
let Rect { x, y, .. } = area; let Rect { x, y, .. } = area;
for key in s.note_start..s.note_start+area.width as usize { for key in s.note_start..s.note_start+area.width as usize {
let x = x + (5 + key - s.note_start) as u16; let x = x + (5 + key - s.note_start) as u16;
@ -83,9 +82,9 @@ pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
let mut is_on = s.notes_on[key as usize]; let mut is_on = s.notes_on[key as usize];
let step = beat; let step = beat;
let (a, b, c) = ( let (a, b, c) = (
(step + 0) as u32 * ppq / s.time_zoom as u32, (step + 0) as usize * ppq / s.time_zoom as usize,
(step + 1) as u32 * ppq / s.time_zoom as u32, (step + 1) as usize * ppq / s.time_zoom as usize,
(step + 2) as u32 * ppq / s.time_zoom as u32, (step + 2) as usize * ppq / s.time_zoom as usize,
); );
let key = ::midly::num::u7::from(key as u8); let key = ::midly::num::u7::from(key as u8);
is_on = is_on || contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b); is_on = is_on || contains_note_on(&s.phrases[s.sequence.unwrap()], key, a, b);

View file

@ -2,10 +2,10 @@ use crate::core::*;
use crate::layout::*; use crate::layout::*;
pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) { pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) {
let tick = (frame as f64 / timebase.frames_per_tick()) as usize; let tick = (frame as f64 / timebase.frames_per_tick());
let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq()); let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq());
let (bars, beats) = (beats / 4, beats % 4); let (bars, beats) = (beats / 4.0, beats % 4.0);
let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1); let timer = format!("{}.{}.{ticks:02}", bars as usize + 1, beats as usize + 1);
timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim())); timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim()));
} }
pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) { pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) {
@ -66,17 +66,11 @@ pub struct Transport {
} }
impl Transport { impl Transport {
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> { pub fn new (name: &str, timebase: &Arc<Timebase>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let transport = client.transport(); let transport = client.transport();
DynamicDevice::new(render, handle, process, Self { DynamicDevice::new(render, handle, process, Self {
name: name.into(), name: name.into(), timebase: timebase.clone(), transport
timebase: Arc::new(Timebase {
rate: AtomicUsize::new(client.sample_rate()),
bpm: AtomicUsize::new(113000),
ppq: AtomicUsize::new(96),
}),
transport
}).activate(client) }).activate(client)
} }
@ -128,8 +122,8 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
"REC", "REC",
"DUB", "DUB",
&format!("BPM {:03}.{:03}", &format!("BPM {:03}.{:03}",
state.timebase.bpm() / 1000, state.timebase.bpm() / 1000.0,
state.timebase.bpm() % 1000, state.timebase.bpm() % 1000.0,
), ),
"0.0+00", "0.0+00",
"0:00.000", "0:00.000",

View file

@ -43,12 +43,8 @@ fn main () -> Result<(), Box<dyn Error>> {
let input = ".*nanoKEY.*"; let input = ".*nanoKEY.*";
let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"]; let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"];
let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?; let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?;
let timebase = Arc::new(Timebase { let timebase = Arc::new(Timebase::new(client.sample_rate() as f64, 125000.0, 96.0));
rate: AtomicUsize::new(client.sample_rate()), let ppq = timebase.ppq() as usize;
bpm: AtomicUsize::new(125000000),
ppq: AtomicUsize::new(96),
});
let ppq = timebase.ppq() as u32;
macro_rules! play { macro_rules! play {
($t1:expr => [ $($msg:expr),* $(,)? ]) => { ($t1:expr => [ $($msg:expr),* $(,)? ]) => {
( $t1 * ppq / 4, vec![ $($msg),* ] ) ( $t1 * ppq / 4, vec![ $($msg),* ] )