mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
launch countdown/switchover, pt.3
This commit is contained in:
parent
97a7bf5b1d
commit
5f112cc203
2 changed files with 75 additions and 50 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue