launch countdown/switchover, pt.3

This commit is contained in:
🪞👃🪞 2024-11-01 21:28:04 +02:00
parent 97a7bf5b1d
commit 5f112cc203
2 changed files with 75 additions and 50 deletions

View file

@ -1,4 +1,5 @@
use crate::*;
use std::cmp::PartialEq;
/// MIDI message structural
pub type PhraseData = Vec<Vec<MidiMessage>>;
/// MIDI message serialized
@ -23,8 +24,7 @@ pub struct Sequencer<E: Engine> {
pub player: PhrasePlayer,
}
/// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum SequencerFocus {
#[derive(Copy, Clone, PartialEq, Eq)] pub enum SequencerFocus {
/// The transport (toolbar) is focused
Transport,
/// The phrase list (pool) is focused
@ -60,8 +60,7 @@ pub enum PhrasePoolMode {
Length(usize, usize, PhraseLengthFocus),
}
/// A MIDI sequence.
#[derive(Debug, Clone)]
pub struct Phrase {
#[derive(Debug, Clone)] pub struct Phrase {
pub uuid: uuid::Uuid,
/// Name of phrase
pub name: String,
@ -141,6 +140,27 @@ pub struct PhrasePlayer {
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
}
/// Displays and edits phrase length.
pub struct PhraseLength<E: Engine> {
_engine: PhantomData<E>,
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of phrase in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<PhraseLengthFocus>,
}
/// Focused field of `PhraseLength`
#[derive(Copy, Clone)] pub enum PhraseLengthFocus {
/// Editing the number of bars
Bar,
/// Editing the number of beats
Beat,
/// Editing the number of ticks
Tick,
}
/// Focus layout of sequencer app
impl<E: Engine> FocusGrid<SequencerFocus> for Sequencer<E> {
fn cursor (&self) -> (usize, usize) { self.focus_cursor }
@ -366,9 +386,7 @@ impl Phrase {
impl Default for Phrase {
fn default () -> Self { Self::new("(empty)", false, 0, None, Some(Color::Rgb(0, 0, 0))) }
}
impl std::cmp::PartialEq for Phrase {
fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid }
}
impl PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } }
impl Eq for Phrase {}
impl PhrasePlayer {
pub fn new (clock: &Arc<TransportTime>) -> Self {
@ -404,18 +422,6 @@ impl PhrasePlayer {
.map(|started|(started - self.clock.instant.sample().get()) as usize)
}
}
/// Displays and edits phrase length
pub struct PhraseLength<E: Engine> {
_engine: PhantomData<E>,
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of phrase in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<PhraseLengthFocus>,
}
impl<E: Engine> PhraseLength<E> {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus }
@ -427,15 +433,6 @@ impl<E: Engine> PhraseLength<E> {
pub fn beats_string (&self) -> String { format!("{}", self.beats()) }
pub fn ticks_string (&self) -> String { format!("{:>02}", self.ticks()) }
}
#[derive(Copy,Clone)]
pub enum PhraseLengthFocus {
/// Editing the number of bars
Bar,
/// Editing the number of beats
Beat,
/// Editing the number of ticks
Tick,
}
impl PhraseLengthFocus {
pub fn next (&mut self) {
*self = match self {

View file

@ -1,4 +1,5 @@
use crate::*;
/// JACK process callback for sequencer app
impl<E: Engine> Audio for Sequencer<E> {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if let Some(ref transport) = self.transport {
@ -8,6 +9,7 @@ impl<E: Engine> Audio for Sequencer<E> {
Control::Continue
}
}
/// JACK process callback for a sequencer's phrase player/recorder.
impl Audio for PhrasePlayer {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let has_midi_outputs = self.has_midi_outputs();
@ -32,6 +34,7 @@ impl Audio for PhrasePlayer {
Control::Continue
}
}
/// Methods used primarily by the process callback
impl PhrasePlayer {
fn has_midi_inputs (&self) -> bool { self.midi_inputs.len() > 0 }
fn has_midi_outputs (&self) -> bool { self.midi_outputs.len() > 0 }
@ -67,32 +70,57 @@ impl PhrasePlayer {
fn play (&mut self, scope: &ProcessScope) {
let sample0 = scope.last_frame_time() as usize;
let samples = scope.n_frames() as usize;
// Write MIDI events from currently playing phrase (if any) to MIDI output buffer
if let Some((start, ref phrase)) = self.playing() {
// 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(start);
let ticks = self.clock.timebase().pulses_between_samples(sample, sample + samples);
phrase.read().map(|phrase|{
let output = &mut self.midi_out_buf;
let notes_on = &mut self.notes_out.write().unwrap();
let mut buf = Vec::with_capacity(8);
for (sample, tick) in ticks {
// If a next phrase is enqueued, and we're past the end of the current one,
// break the loop here (FIXME count tick correctly)
if self.next_phrase.is_some() && tick >= phrase.length { break }
// Output events from phrase at appropriate frames of output buffer
let tick = tick % phrase.length;
for message in phrase.notes[tick].iter() {
buf.clear();
let channel = 0.into();
let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[sample].push(buf.clone());
update_keys(notes_on, &message);
}
// 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);
// Source phrase from which the MIDI events will be taken.
let phrase = phrase.read().unwrap();
// 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)
if self.next_phrase.is_some() && pulse >= phrase.length { break }
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);
}
}).unwrap()
}
}
if let Some((start, ref phrase)) = self.enqueued() {
// TODO switch to next phrase and fill in remaining ticks from it
// Handle next enqueued phrase, if any:
if let Some((start, phrase)) = self.enqueued() {
// 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, Some(phrase)));
// 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 ???
}
}
fn record (&mut self, scope: &ProcessScope) {