launch pt.9: fix beats_per_second

This commit is contained in:
🪞👃🪞 2024-11-01 23:59:39 +02:00
parent e149d777ed
commit e7dce0f84b
4 changed files with 87 additions and 85 deletions

View file

@ -74,7 +74,7 @@ impl Timebase {
} }
/// Iterate over ticks between start and end. /// Iterate over ticks between start and end.
pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator { pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator {
TicksIterator { fpt: self.samples_per_pulse(), sample: start, start, end } TicksIterator { spp: self.samples_per_pulse(), sample: start, start, end }
} }
} }
impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } } impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } }
@ -126,23 +126,23 @@ impl Instant {
} }
} }
/// Iterator that emits subsequent ticks within a range. /// Iterator that emits subsequent ticks within a range.
pub struct TicksIterator { fpt: f64, sample: usize, start: usize, end: usize, } pub struct TicksIterator { spp: f64, sample: usize, start: usize, end: usize, }
impl Iterator for TicksIterator { impl Iterator for TicksIterator {
type Item = (usize, usize); type Item = (usize, usize);
fn next (&mut self) -> Option<Self::Item> { fn next (&mut self) -> Option<Self::Item> {
loop { loop {
if self.sample > self.end { return None } if self.sample > self.end { return None }
let fpt = self.fpt; let spp = self.spp;
let sample = self.sample as f64; let sample = self.sample as f64;
let start = self.start; let start = self.start;
let end = self.end; let end = self.end;
self.sample += 1; self.sample += 1;
//println!("{fpt} {sample} {start} {end}"); //println!("{spp} {sample} {start} {end}");
let jitter = sample.rem_euclid(fpt); // ramps let jitter = sample.rem_euclid(spp); // ramps
let next_jitter = (sample + 1.0).rem_euclid(fpt); let next_jitter = (sample + 1.0).rem_euclid(spp);
if jitter > next_jitter { // at crossing: if jitter > next_jitter { // at crossing:
let time = (sample as usize) % (end as usize-start as usize); let time = (sample as usize) % (end as usize-start as usize);
let tick = (sample / fpt) as usize; let tick = (sample / spp) as usize;
return Some((time, tick)) return Some((time, tick))
} }
} }
@ -173,7 +173,7 @@ pub trait MIDITime {
/// Return the duration fo a beat in microseconds /// Return the duration fo a beat in microseconds
#[inline] fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm().get() } #[inline] fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm().get() }
/// Return the number of beats in a second /// Return the number of beats in a second
#[inline] fn beat_per_second (&self) -> f64 { self.bpm().get() / 60_000_000f64 } #[inline] fn beat_per_second (&self) -> f64 { self.bpm().get() / 60f64 }
/// Return the number of microseconds corresponding to a note of the given duration /// Return the number of microseconds corresponding to a note of the given duration
#[inline] fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { #[inline] fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 {
4.0 * self.usec_per_beat() * num / den 4.0 * self.usec_per_beat() * num / den

View file

@ -170,15 +170,16 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
}))?; }))?;
// track titles // track titles
let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{
// name and width of track
let name = track.name.read().unwrap(); let name = track.name.read().unwrap();
let max_w = w.saturating_sub(1).min(name.len()).max(2); let max_w = w.saturating_sub(1).min(name.len()).max(2);
let name = format!("{}", &name[0..max_w]); let name = format!("{}", &name[0..max_w]);
let name = TuiStyle::bold(name, true); let name = TuiStyle::bold(name, true);
let input_name = track.player.midi_inputs.get(0) // name of active MIDI input
let input = format!("▎>{}", track.player.midi_inputs.get(0)
.map(|port|port.short_name()) .map(|port|port.short_name())
.transpose()? .transpose()?
.unwrap_or("(none)".into()); .unwrap_or("(none)".into()));
let input = format!("▎>{}", input_name);
col!(name, input) col!(name, input)
.min_xy(w as u16, track_title_h) .min_xy(w as u16, track_title_h)
.bg(track.color) .bg(track.color)
@ -187,6 +188,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
// track controls // track controls
let footer = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ let footer = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{
let player = &track.player; let player = &track.player;
// beats elapsed
let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() { let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() {
let length = phrase.read().unwrap().length; let length = phrase.read().unwrap().length;
let elapsed = player.pulses_since_start().unwrap(); let elapsed = player.pulses_since_start().unwrap();
@ -197,23 +199,24 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
} else { } else {
String::from("") String::from("")
}; };
// beats until switchover
let until_next = player.next_phrase.as_ref() let until_next = player.next_phrase.as_ref()
.map(|(t, _)|{ .map(|(t, _)|{
let target = t.pulse().get(); let target = t.pulse().get();
let current = clock.instant.pulse().get(); let current = clock.instant.pulse().get();
if target > current { if target > current {
let remaining = clock.timebase().format_beats_0_short(target - current); let remaining = target - current;
format!("▎-{remaining:>}") format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining))
} else { } else {
String::new() String::new()
} }
}) })
.unwrap_or(String::from("")); .unwrap_or(String::from(""));
let output_name = track.player.midi_outputs.get(0) // name of active MIDI output
let output = format!("▎<{}", track.player.midi_outputs.get(0)
.map(|port|port.short_name()) .map(|port|port.short_name())
.transpose()? .transpose()?
.unwrap_or("(none)".into()); .unwrap_or("(none)".into()));
let output = format!("▎<{}", output_name);
col!(until_next, elapsed, output) col!(until_next, elapsed, output)
.min_xy(w as u16, tracks_footer) .min_xy(w as u16, tracks_footer)
.bg(track.color) .bg(track.color)

View file

@ -416,15 +416,12 @@ impl PhrasePlayer {
)); ));
self.reset = true; self.reset = true;
} }
pub fn samples_since_start (&self) -> Option<usize> {
self.phrase.as_ref()
.map(|(started,_)|started.sample().get())
.map(|started|(self.clock.instant.sample().get() - started) as usize)
}
pub fn pulses_since_start (&self) -> Option<f64> { pub fn pulses_since_start (&self) -> Option<f64> {
self.phrase.as_ref() if let Some((started, Some(_))) = self.phrase.as_ref() {
.map(|(started,_)|started.pulse().get()) Some(self.clock.instant.pulse().get() - started.pulse().get())
.map(|started|self.clock.instant.pulse().get() - started) } else {
None
}
} }
} }
impl<E: Engine> PhraseLength<E> { impl<E: Engine> PhraseLength<E> {

View file

@ -52,10 +52,11 @@ impl PhrasePlayer {
fn play (&mut self, scope: &ProcessScope) { fn play (&mut self, scope: &ProcessScope) {
let sample0 = scope.last_frame_time() as usize; let sample0 = scope.last_frame_time() as usize;
let samples = scope.n_frames() as usize; let samples = scope.n_frames() as usize;
// Write MIDI events from currently playing phrase (if any) to MIDI output buffer
if self.is_rolling() {
// If no phrase is playing, prepare for switchover immediately // If no phrase is playing, prepare for switchover immediately
let mut next = self.phrase.is_none(); let mut next = self.phrase.is_none();
// Write MIDI events from currently playing phrase (if any) to MIDI output buffer if let Some((started, phrase)) = &self.phrase {
if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) {
// First sample to populate. Greater than 0 means that the first // First sample to populate. Greater than 0 means that the first
// pulse of the phrase falls somewhere in the middle of the chunk. // pulse of the phrase falls somewhere in the middle of the chunk.
let sample = sample0.saturating_sub(started.sample.get() as usize); let sample = sample0.saturating_sub(started.sample.get() as usize);
@ -104,7 +105,7 @@ impl PhrasePlayer {
} }
// If it's time for the next enqueued phrase, handle it here: // If it's time for the next enqueued phrase, handle it here:
if next { if next {
if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase) { if let Some((start_at, phrase)) = &self.next_phrase {
let start = start_at.sample().get() as usize; let start = start_at.sample().get() as usize;
// If it's time to switch to the next phrase: // If it's time to switch to the next phrase:
if start <= sample0 { if start <= sample0 {
@ -121,6 +122,7 @@ impl PhrasePlayer {
} }
} }
} }
}
fn record (&mut self, scope: &ProcessScope) { fn record (&mut self, scope: &ProcessScope) {
let sample0 = scope.last_frame_time() as usize; let sample0 = scope.last_frame_time() as usize;
if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) {