diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index 4a89cfd7..84c1a2af 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -74,7 +74,7 @@ impl Timebase { } /// Iterate over ticks between start and end. 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) } } @@ -126,23 +126,23 @@ impl Instant { } } /// 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 { type Item = (usize, usize); fn next (&mut self) -> Option { loop { if self.sample > self.end { return None } - let fpt = self.fpt; + let spp = self.spp; let sample = self.sample as f64; let start = self.start; let end = self.end; self.sample += 1; - //println!("{fpt} {sample} {start} {end}"); - let jitter = sample.rem_euclid(fpt); // ramps - let next_jitter = (sample + 1.0).rem_euclid(fpt); + //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 / fpt) as usize; + let tick = (sample / spp) as usize; return Some((time, tick)) } } @@ -173,7 +173,7 @@ pub trait MIDITime { /// Return the duration fo a beat in microseconds #[inline] fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm().get() } /// 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 #[inline] fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { 4.0 * self.usec_per_beat() * num / den diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index bd507155..6372c12c 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -170,15 +170,16 @@ impl<'a> Content for VerticalArranger<'a, Tui> { }))?; // track titles 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 max_w = w.saturating_sub(1).min(name.len()).max(2); let name = format!("▎{}", &name[0..max_w]); 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()) .transpose()? - .unwrap_or("(none)".into()); - let input = format!("▎>{}", input_name); + .unwrap_or("(none)".into())); col!(name, input) .min_xy(w as u16, track_title_h) .bg(track.color) @@ -187,6 +188,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { // track controls let footer = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ let player = &track.player; + // beats elapsed let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() { let length = phrase.read().unwrap().length; let elapsed = player.pulses_since_start().unwrap(); @@ -197,23 +199,24 @@ impl<'a> Content for VerticalArranger<'a, Tui> { } else { String::from("▎") }; + // beats until switchover let until_next = player.next_phrase.as_ref() .map(|(t, _)|{ let target = t.pulse().get(); let current = clock.instant.pulse().get(); if target > current { - let remaining = clock.timebase().format_beats_0_short(target - current); - format!("▎-{remaining:>}") + let remaining = target - current; + format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining)) } else { String::new() } }) .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()) .transpose()? - .unwrap_or("(none)".into()); - let output = format!("▎<{}", output_name); + .unwrap_or("(none)".into())); col!(until_next, elapsed, output) .min_xy(w as u16, tracks_footer) .bg(track.color) diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 30c1da80..1482e739 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -416,15 +416,12 @@ impl PhrasePlayer { )); self.reset = true; } - pub fn samples_since_start (&self) -> Option { - 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 { - self.phrase.as_ref() - .map(|(started,_)|started.pulse().get()) - .map(|started|self.clock.instant.pulse().get() - started) + if let Some((started, Some(_))) = self.phrase.as_ref() { + Some(self.clock.instant.pulse().get() - started.pulse().get()) + } else { + None + } } } impl PhraseLength { diff --git a/crates/tek_sequencer/src/sequencer_snd.rs b/crates/tek_sequencer/src/sequencer_snd.rs index bfbc55d5..11ea8b60 100644 --- a/crates/tek_sequencer/src/sequencer_snd.rs +++ b/crates/tek_sequencer/src/sequencer_snd.rs @@ -52,72 +52,74 @@ impl PhrasePlayer { fn play (&mut self, scope: &ProcessScope) { let sample0 = scope.last_frame_time() as usize; let samples = scope.n_frames() as usize; - // If no phrase is playing, prepare for switchover immediately - let mut next = self.phrase.is_none(); // Write MIDI events from currently playing phrase (if any) to MIDI output buffer - if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { - // First sample to populate. Greater than 0 means that the first - // pulse of the phrase falls somewhere in the middle of the chunk. - let sample = sample0.saturating_sub(started.sample.get() as usize); - // Iterator that emits sample (index into output buffer at which to write MIDI event) - // paired with pulse (index into phrase from which to take the MIDI event) for each - // sample of the output buffer that corresponds to a MIDI pulse. - let pulses = self.clock.timebase().pulses_between_samples(sample, sample + samples); - // MIDI output buffer that will be copied to the JACK MIDI output ports. - let output = &mut self.midi_out_buf; - // Buffer for bytes of a MIDI event. - let mut mm = Vec::with_capacity(8); - // Notes active during current chunk. - let notes = &mut self.notes_out.write().unwrap(); - for (sample, pulse) in pulses { - // If a next phrase is enqueued, and we're past the end of the current one, - // break the loop here (FIXME count pulse correctly) - next = self.next_phrase.is_some() && if let Some(ref phrase) = phrase { - pulse >= phrase.read().unwrap().length - } else { - true - }; - if next { - break - } - // If there's a currently playing phrase, output notes from it to buffer: - if let Some(ref phrase) = phrase { - // Source phrase from which the MIDI events will be taken. - let phrase = phrase.read().unwrap(); - // Current pulse index in source phrase - let pulse = pulse % phrase.length; - // Output each MIDI event from phrase at appropriate frames of output buffer: - for message in phrase.notes[pulse].iter() { - // Clear output buffer for this MIDI event. - mm.clear(); - // TODO: support MIDI channels other than CH1. - let channel = 0.into(); - // Serialize MIDI event into message buffer. - LiveEvent::Midi { channel, message: *message }.write(&mut mm).unwrap(); - // Append serialized message to output buffer. - output[sample].push(mm.clone()); - // Update the list of currently held notes. - update_keys(notes, &message); + if self.is_rolling() { + // If no phrase is playing, prepare for switchover immediately + let mut next = self.phrase.is_none(); + if let Some((started, phrase)) = &self.phrase { + // First sample to populate. Greater than 0 means that the first + // pulse of the phrase falls somewhere in the middle of the chunk. + let sample = sample0.saturating_sub(started.sample.get() as usize); + // Iterator that emits sample (index into output buffer at which to write MIDI event) + // paired with pulse (index into phrase from which to take the MIDI event) for each + // sample of the output buffer that corresponds to a MIDI pulse. + let pulses = self.clock.timebase().pulses_between_samples(sample, sample + samples); + // MIDI output buffer that will be copied to the JACK MIDI output ports. + let output = &mut self.midi_out_buf; + // Buffer for bytes of a MIDI event. + let mut mm = Vec::with_capacity(8); + // Notes active during current chunk. + let notes = &mut self.notes_out.write().unwrap(); + for (sample, pulse) in pulses { + // If a next phrase is enqueued, and we're past the end of the current one, + // break the loop here (FIXME count pulse correctly) + next = self.next_phrase.is_some() && if let Some(ref phrase) = phrase { + pulse >= phrase.read().unwrap().length + } else { + true + }; + if next { + break + } + // If there's a currently playing phrase, output notes from it to buffer: + if let Some(ref phrase) = phrase { + // Source phrase from which the MIDI events will be taken. + let phrase = phrase.read().unwrap(); + // Current pulse index in source phrase + let pulse = pulse % phrase.length; + // Output each MIDI event from phrase at appropriate frames of output buffer: + for message in phrase.notes[pulse].iter() { + // Clear output buffer for this MIDI event. + mm.clear(); + // TODO: support MIDI channels other than CH1. + let channel = 0.into(); + // Serialize MIDI event into message buffer. + LiveEvent::Midi { channel, message: *message }.write(&mut mm).unwrap(); + // Append serialized message to output buffer. + output[sample].push(mm.clone()); + // Update the list of currently held notes. + update_keys(notes, &message); + } } } } - } - // If it's time for the next enqueued phrase, handle it here: - if next { - if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase) { - let start = start_at.sample().get() as usize; - // If it's time to switch to the next phrase: - if start <= sample0 { - // Samples elapsed since phrase was supposed to start - let skipped = sample0 - start; - // Switch over to enqueued phrase - let started = Instant::from_sample(&self.clock.timebase(), start as f64); - self.phrase = Some((started, phrase.clone())); - // Unset enqueuement (TODO: where to implement looping?) - self.next_phrase = None + // If it's time for the next enqueued phrase, handle it here: + if next { + if let Some((start_at, phrase)) = &self.next_phrase { + let start = start_at.sample().get() as usize; + // If it's time to switch to the next phrase: + if start <= sample0 { + // Samples elapsed since phrase was supposed to start + let skipped = sample0 - start; + // Switch over to enqueued phrase + let started = Instant::from_sample(&self.clock.timebase(), start as f64); + self.phrase = Some((started, phrase.clone())); + // Unset enqueuement (TODO: where to implement looping?) + self.next_phrase = None + } + // TODO fill in remaining ticks of chunk from next phrase. + // ?? just call self.play(scope) again, since enqueuement is off ??? } - // TODO fill in remaining ticks of chunk from next phrase. - // ?? just call self.play(scope) again, since enqueuement is off ??? } } }