mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
refactor PhrasePlayer::process callback
This commit is contained in:
parent
0820c10f8b
commit
3df8e87840
4 changed files with 118 additions and 87 deletions
|
|
@ -22,6 +22,8 @@ pub struct Arranger<E: Engine> {
|
||||||
pub arrangement_split: u16,
|
pub arrangement_split: u16,
|
||||||
/// Width of phrase pool
|
/// Width of phrase pool
|
||||||
pub phrases_split: u16,
|
pub phrases_split: u16,
|
||||||
|
/// Width and height of app at last render
|
||||||
|
pub size: Measure<E>,
|
||||||
}
|
}
|
||||||
/// Sections in the arranger app that may be focused
|
/// Sections in the arranger app that may be focused
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
|
@ -121,9 +123,9 @@ impl<E: Engine> Arranger<E> {
|
||||||
phrases: Arc<RwLock<PhrasePool<E>>>,
|
phrases: Arc<RwLock<PhrasePool<E>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut app = Self {
|
let mut app = Self {
|
||||||
jack: None,
|
jack: None,
|
||||||
focus_cursor: (0, 1),
|
focus_cursor: (0, 1),
|
||||||
phrases_split: 20,
|
phrases_split: 20,
|
||||||
arrangement_split: 21,
|
arrangement_split: 21,
|
||||||
editor: PhraseEditor::new(),
|
editor: PhraseEditor::new(),
|
||||||
status: ArrangerStatusBar::ArrangementClip,
|
status: ArrangerStatusBar::ArrangementClip,
|
||||||
|
|
@ -135,6 +137,7 @@ impl<E: Engine> Arranger<E> {
|
||||||
transport,
|
transport,
|
||||||
arrangement,
|
arrangement,
|
||||||
phrases,
|
phrases,
|
||||||
|
size: Measure::new(),
|
||||||
};
|
};
|
||||||
app.update_focus();
|
app.update_focus();
|
||||||
app
|
app
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ impl Content for Arrangement<Tui> {
|
||||||
ArrangementViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)),
|
ArrangementViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)),
|
||||||
}?;
|
}?;
|
||||||
let color = if self.focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
|
let color = if self.focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
|
||||||
add(&TuiStyle::fg("Session", color).push_x(1))?;
|
add(&TuiStyle::fg("Session", color).push_x(2))?;
|
||||||
add(&self.size)
|
add(&self.size)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +137,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
|
||||||
let border_hi = Color::Rgb(100, 110, 40);
|
let border_hi = Color::Rgb(100, 110, 40);
|
||||||
let border_lo = Color::Rgb(70, 80, 50);
|
let border_lo = Color::Rgb(70, 80, 50);
|
||||||
let border_fg = if self.0.focused { border_hi } else { border_lo };
|
let border_fg = if self.0.focused { border_hi } else { border_lo };
|
||||||
let border = Lozenge(Style::default().bg(border_bg).fg(border_fg));
|
//let border = Lozenge(Style::default().bg(border_bg).fg(border_fg));
|
||||||
let track_title_h = 2u16;
|
let track_title_h = 2u16;
|
||||||
let tracks_footer = 3u16;
|
let tracks_footer = 3u16;
|
||||||
let scene_title_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track
|
let scene_title_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track
|
||||||
|
|
|
||||||
|
|
@ -403,15 +403,6 @@ impl PhrasePlayer {
|
||||||
.map(|(started,_)|started.sample().get())
|
.map(|(started,_)|started.sample().get())
|
||||||
.map(|started|(started - self.clock.instant.sample().get()) as usize)
|
.map(|started|(started - self.clock.instant.sample().get()) as usize)
|
||||||
}
|
}
|
||||||
pub fn playing_phrase (&self) -> Option<(usize, Arc<RwLock<Phrase>>)> {
|
|
||||||
if let (
|
|
||||||
Some(TransportState::Rolling), Some((started, Some(ref phrase)))
|
|
||||||
) = (*self.clock.playing.read().unwrap(), &self.phrase) {
|
|
||||||
Some((started.sample().get() as usize, phrase.clone()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Displays and edits phrase length
|
/// Displays and edits phrase length
|
||||||
pub struct PhraseLength<E: Engine> {
|
pub struct PhraseLength<E: Engine> {
|
||||||
|
|
|
||||||
|
|
@ -10,100 +10,137 @@ impl<E: Engine> Audio for Sequencer<E> {
|
||||||
}
|
}
|
||||||
impl Audio for PhrasePlayer {
|
impl Audio for PhrasePlayer {
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
let frame0 = scope.last_frame_time() as usize;
|
|
||||||
let frames = scope.n_frames() as usize;
|
|
||||||
let has_midi_outputs = self.has_midi_outputs();
|
let has_midi_outputs = self.has_midi_outputs();
|
||||||
|
let has_midi_inputs = self.has_midi_inputs();
|
||||||
if has_midi_outputs {
|
if has_midi_outputs {
|
||||||
self.clear_midi_out_buf(frames);
|
// Clear output buffer(s)
|
||||||
self.reset_midi_out_buf(false /* FIXME where did force-reset come from? */);
|
self.clear(scope, false);
|
||||||
}
|
|
||||||
let has_midi_inputs = self.has_midi_inputs();
|
|
||||||
let quant = self.clock.quant().get();
|
|
||||||
if let Some((start_frame, phrase)) = self.playing_phrase() {
|
|
||||||
// Write chunk of phrase to output
|
// Write chunk of phrase to output
|
||||||
phrase.read().map(|phrase|{
|
self.play(scope);
|
||||||
if has_midi_outputs {
|
}
|
||||||
let output = &mut self.midi_out_buf;
|
if has_midi_inputs {
|
||||||
let notes_on = &mut self.notes_out.write().unwrap();
|
if self.recording || self.monitoring {
|
||||||
let frame0 = frame0.saturating_sub(start_frame);
|
// Record and/or monitor input
|
||||||
let frame_n = frame0 + frames;
|
self.record(scope)
|
||||||
let ticks = self.clock.timebase().pulses_between_samples(frame0, frame_n);
|
} else if has_midi_outputs && self.monitoring {
|
||||||
let mut buf = Vec::with_capacity(8);
|
// Monitor input to output
|
||||||
for (sample, tick) in ticks {
|
self.monitor(scope)
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).unwrap();
|
|
||||||
let mut phrase = phrase.write().unwrap();
|
|
||||||
let length = phrase.length;
|
|
||||||
// Monitor and record input
|
|
||||||
if has_midi_inputs && (self.recording || self.monitoring) {
|
|
||||||
// For highlighting keys and note repeat
|
|
||||||
let mut notes_in = self.notes_in.write().unwrap();
|
|
||||||
// Record from each input
|
|
||||||
for input in self.midi_inputs.iter() {
|
|
||||||
for (frame, event, bytes) in parse_midi_input(input.iter(scope)) {
|
|
||||||
if let LiveEvent::Midi { message, .. } = event {
|
|
||||||
if self.monitoring { self.midi_out_buf[frame].push(bytes.to_vec()) }
|
|
||||||
if self.recording {
|
|
||||||
phrase.record_event({
|
|
||||||
let pulse = self.clock.timebase().samples_to_pulse(
|
|
||||||
(frame0 + frame - start_frame) as f64
|
|
||||||
);
|
|
||||||
let quantized = (pulse / quant).round() * quant;
|
|
||||||
let looped = quantized as usize % length;
|
|
||||||
looped
|
|
||||||
}, message);
|
|
||||||
}
|
|
||||||
update_keys(&mut notes_in, &message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if has_midi_inputs && has_midi_outputs && self.monitoring {
|
}
|
||||||
|
// Write to output port(s)
|
||||||
|
self.write(scope);
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PhrasePlayer {
|
||||||
|
fn has_midi_inputs (&self) -> bool { self.midi_inputs.len() > 0 }
|
||||||
|
fn has_midi_outputs (&self) -> bool { self.midi_outputs.len() > 0 }
|
||||||
|
/// Clear the section of the output buffer that we will be using,
|
||||||
|
/// emitting "all notes off" at start of buffer if requested.
|
||||||
|
fn clear (&mut self, scope: &ProcessScope, force_reset: bool) {
|
||||||
|
for frame in &mut self.midi_out_buf[0..scope.n_frames() as usize] {
|
||||||
|
frame.clear();
|
||||||
|
}
|
||||||
|
if self.reset || force_reset {
|
||||||
|
all_notes_off(&mut self.midi_out_buf); self.reset = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Return playing phrase with starting point
|
||||||
|
fn playing (&self) -> Option<(usize, Arc<RwLock<Phrase>>)> {
|
||||||
|
if let (
|
||||||
|
Some(TransportState::Rolling), Some((started, Some(ref phrase)))
|
||||||
|
) = (*self.clock.playing.read().unwrap(), &self.phrase) {
|
||||||
|
Some((started.sample().get() as usize, phrase.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Return next phrase with starting point
|
||||||
|
fn enqueued (&self) -> Option<(usize, Arc<RwLock<Phrase>>)> {
|
||||||
|
if let (
|
||||||
|
Some(TransportState::Rolling), Some((start_at, Some(ref phrase)))
|
||||||
|
) = (*self.clock.playing.read().unwrap(), &self.next_phrase) {
|
||||||
|
Some((start_at.sample().get() as usize, phrase.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn play (&mut self, scope: &ProcessScope) {
|
||||||
|
let sample0 = scope.last_frame_time() as usize;
|
||||||
|
let samples = scope.n_frames() as usize;
|
||||||
|
if let Some((start, ref phrase)) = self.playing() {
|
||||||
|
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 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn record (&mut self, scope: &ProcessScope) {
|
||||||
|
let sample0 = scope.last_frame_time() as usize;
|
||||||
|
if let Some((start, ref phrase)) = self.playing() {
|
||||||
|
let quant = self.clock.quant().get();
|
||||||
|
let mut phrase = phrase.write().unwrap();
|
||||||
|
let length = phrase.length;
|
||||||
|
// For highlighting keys and note repeat
|
||||||
let mut notes_in = self.notes_in.write().unwrap();
|
let mut notes_in = self.notes_in.write().unwrap();
|
||||||
// Monitor each input
|
// Record from each input
|
||||||
for input in self.midi_inputs.iter() {
|
for input in self.midi_inputs.iter() {
|
||||||
for (frame, event, bytes) in parse_midi_input(input.iter(scope)) {
|
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
||||||
if let LiveEvent::Midi { message, .. } = event {
|
if let LiveEvent::Midi { message, .. } = event {
|
||||||
self.midi_out_buf[frame].push(bytes.to_vec());
|
if self.monitoring { self.midi_out_buf[sample].push(bytes.to_vec()) }
|
||||||
|
if self.recording {
|
||||||
|
phrase.record_event({
|
||||||
|
let pulse = self.clock.timebase().samples_to_pulse(
|
||||||
|
(sample0 + sample - start) as f64
|
||||||
|
);
|
||||||
|
let quantized = (pulse / quant).round() * quant;
|
||||||
|
let looped = quantized as usize % length;
|
||||||
|
looped
|
||||||
|
}, message);
|
||||||
|
}
|
||||||
update_keys(&mut notes_in, &message);
|
update_keys(&mut notes_in, &message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Write to midi output
|
}
|
||||||
|
fn monitor (&mut self, scope: &ProcessScope) {
|
||||||
|
let mut notes_in = self.notes_in.write().unwrap();
|
||||||
|
for input in self.midi_inputs.iter() {
|
||||||
|
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
||||||
|
if let LiveEvent::Midi { message, .. } = event {
|
||||||
|
self.midi_out_buf[sample].push(bytes.to_vec());
|
||||||
|
update_keys(&mut notes_in, &message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn write (&mut self, scope: &ProcessScope) {
|
||||||
|
let samples = scope.n_frames() as usize;
|
||||||
for port in self.midi_outputs.iter_mut() {
|
for port in self.midi_outputs.iter_mut() {
|
||||||
let writer = &mut port.writer(scope);
|
let writer = &mut port.writer(scope);
|
||||||
let output = &self.midi_out_buf;
|
let output = &self.midi_out_buf;
|
||||||
for time in 0..frames {
|
for time in 0..samples {
|
||||||
for event in output[time].iter() {
|
for event in output[time].iter() {
|
||||||
writer.write(&RawMidi { time: time as u32, bytes: &event })
|
writer.write(&RawMidi { time: time as u32, bytes: &event })
|
||||||
.expect(&format!("{event:?}"));
|
.expect(&format!("{event:?}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PhrasePlayer {
|
|
||||||
pub fn has_midi_inputs (&self) -> bool { self.midi_inputs.len() > 0 }
|
|
||||||
pub fn has_midi_outputs (&self) -> bool { self.midi_outputs.len() > 0 }
|
|
||||||
/// Clear the section of the output buffer that we will be using
|
|
||||||
pub fn clear_midi_out_buf (&mut self, frames: usize) {
|
|
||||||
for frame in &mut self.midi_out_buf[0..frames] { frame.clear(); }
|
|
||||||
}
|
|
||||||
/// Emit "all notes off" at start of buffer if requested
|
|
||||||
pub fn reset_midi_out_buf (&mut self, force_reset: bool) {
|
|
||||||
if self.reset || force_reset { all_notes_off(&mut self.midi_out_buf); self.reset = false; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Add "all notes off" to the start of a buffer.
|
/// Add "all notes off" to the start of a buffer.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue