new buffer less and simpler style of midi event processing
This commit is contained in:
parent
1d595fe52f
commit
0f5b44141d
4 changed files with 119 additions and 44 deletions
|
@ -5,7 +5,7 @@
|
||||||
use crate::dsp::{
|
use crate::dsp::{
|
||||||
at, denorm, inp, out_idx, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom,
|
at, denorm, inp, out_idx, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom,
|
||||||
};
|
};
|
||||||
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
use crate::nodes::{HxMidiEvent, MidiEventPointer, NodeAudioContext, NodeExecContext};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! fa_midip_chan {
|
macro_rules! fa_midip_chan {
|
||||||
|
@ -30,12 +30,15 @@ macro_rules! fa_midip_gmode {
|
||||||
/// The (stereo) output port of the plugin
|
/// The (stereo) output port of the plugin
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MidiP {
|
pub struct MidiP {
|
||||||
prev_gate: u8,
|
next_gate: i8,
|
||||||
|
cur_note: u8,
|
||||||
|
cur_gate: u8,
|
||||||
|
cur_vel: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiP {
|
impl MidiP {
|
||||||
pub fn new(_nid: &NodeId) -> Self {
|
pub fn new(_nid: &NodeId) -> Self {
|
||||||
Self { prev_gate: 0 }
|
Self { next_gate: 0, cur_note: 0, cur_gate: 0, cur_vel: 0.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const chan: &'static str = "MidiP chan\nMIDI Channel 0 to 15\n";
|
pub const chan: &'static str = "MidiP chan\nMIDI Channel 0 to 15\n";
|
||||||
|
@ -106,29 +109,54 @@ impl DspNode for MidiP {
|
||||||
let gate = &mut gate[0];
|
let gate = &mut gate[0];
|
||||||
let vel = &mut vel[0];
|
let vel = &mut vel[0];
|
||||||
|
|
||||||
let channel = (chan.i() as usize % 16) as u8;
|
let midip_channel = (chan.i() as usize % 16) as u8;
|
||||||
|
|
||||||
|
let mut ptr = MidiEventPointer::new(&ectx.midi_notes[..]);
|
||||||
|
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
let chan = ectx.note_buffer.get_chan_at(channel, frame as u8);
|
if self.next_gate > 0 {
|
||||||
|
self.cur_gate = 1;
|
||||||
|
} else if self.next_gate < 0 {
|
||||||
|
self.cur_gate = 0;
|
||||||
|
}
|
||||||
|
self.next_gate = 0;
|
||||||
|
|
||||||
let note = (chan.note as f32 - 69.0) / 120.0;
|
while let Some(ev) = ptr.next_at(frame) {
|
||||||
let note = note + det.read(frame);
|
println!("MIDIP EV: {} {:?}", frame, ev);
|
||||||
freq.write(frame, note);
|
match ev {
|
||||||
|
HxMidiEvent::NoteOn { channel, note, vel } => {
|
||||||
|
if channel != midip_channel {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if chan.gate & 0x10 > 0 {
|
if self.cur_gate > 0 {
|
||||||
// insert a single sample of silence, for retriggering
|
self.next_gate = 1;
|
||||||
// any envelopes if the note changed but no note-off came.
|
self.cur_gate = 0;
|
||||||
if self.prev_gate > 0 && self.prev_gate != chan.gate {
|
} else {
|
||||||
gate.write(frame, 0.0);
|
self.cur_gate = 1;
|
||||||
} else {
|
}
|
||||||
gate.write(frame, 1.0);
|
self.cur_note = note;
|
||||||
|
self.cur_vel = vel;
|
||||||
|
}
|
||||||
|
HxMidiEvent::NoteOff { channel, note } => {
|
||||||
|
if channel != midip_channel {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.cur_note == note {
|
||||||
|
self.next_gate = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
gate.write(frame, 0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.prev_gate = chan.gate;
|
let note = (self.cur_note as f32 - 69.0) / 120.0;
|
||||||
vel.write(frame, chan.vel as f32);
|
let note = note + det.read(frame);
|
||||||
|
println!("FRAME: {} => gate={}, freq={}, next_gate={}", frame, self.cur_gate, note, self.next_gate);
|
||||||
|
freq.write(frame, note);
|
||||||
|
gate.write(frame, if self.cur_gate > 0 { 1.0 } else { 0.0 });
|
||||||
|
vel.write(frame, self.cur_vel as f32);
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_val = gate.read(ctx.nframes() - 1);
|
let last_val = gate.read(ctx.nframes() - 1);
|
||||||
|
|
|
@ -31,7 +31,9 @@ pub use node_conf::*;
|
||||||
pub use node_exec::*;
|
pub use node_exec::*;
|
||||||
pub use node_graph_ordering::NodeGraphOrdering;
|
pub use node_graph_ordering::NodeGraphOrdering;
|
||||||
pub use node_prog::*;
|
pub use node_prog::*;
|
||||||
pub use note_buffer::{EventWindowing, HxMidiEvent, HxTimedEvent, NoteBuffer, NoteChannelState};
|
pub use note_buffer::{
|
||||||
|
EventWindowing, HxMidiEvent, HxTimedEvent, MidiEventPointer, NoteBuffer, NoteChannelState,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::dsp::{Node, SAtom};
|
use crate::dsp::{Node, SAtom};
|
||||||
pub use crate::monitor::MinMaxMonitorSamples;
|
pub use crate::monitor::MinMaxMonitorSamples;
|
||||||
|
|
|
@ -25,6 +25,9 @@ use core::arch::x86_64::{
|
||||||
// _MM_GET_FLUSH_ZERO_MODE
|
// _MM_GET_FLUSH_ZERO_MODE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const MAX_MIDI_NOTES_PER_BLOCK: usize = 512;
|
||||||
|
pub const MAX_MIDI_CC_PER_BLOCK: usize = 1024;
|
||||||
|
|
||||||
/// Holds the complete allocation of nodes and
|
/// Holds the complete allocation of nodes and
|
||||||
/// the program. New Nodes or the program is
|
/// the program. New Nodes or the program is
|
||||||
/// not newly allocated in the audio backend, but it is
|
/// not newly allocated in the audio backend, but it is
|
||||||
|
@ -171,13 +174,17 @@ impl Default for FeedbackBuffer {
|
||||||
pub struct NodeExecContext {
|
pub struct NodeExecContext {
|
||||||
pub feedback_delay_buffers: Vec<FeedbackBuffer>,
|
pub feedback_delay_buffers: Vec<FeedbackBuffer>,
|
||||||
pub note_buffer: NoteBuffer,
|
pub note_buffer: NoteBuffer,
|
||||||
|
pub midi_notes: Vec<HxTimedEvent>,
|
||||||
|
pub midi_ccs: Vec<HxTimedEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeExecContext {
|
impl NodeExecContext {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let mut fbdb = vec![];
|
let mut fbdb = vec![];
|
||||||
fbdb.resize_with(MAX_ALLOCATED_NODES, FeedbackBuffer::new);
|
fbdb.resize_with(MAX_ALLOCATED_NODES, FeedbackBuffer::new);
|
||||||
Self { feedback_delay_buffers: fbdb, note_buffer: NoteBuffer::new() }
|
let midi_notes = Vec::with_capacity(MAX_MIDI_NOTES_PER_BLOCK);
|
||||||
|
let midi_ccs = Vec::with_capacity(MAX_MIDI_CC_PER_BLOCK);
|
||||||
|
Self { feedback_delay_buffers: fbdb, note_buffer: NoteBuffer::new(), midi_notes, midi_ccs }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_sample_rate(&mut self, srate: f32) {
|
fn set_sample_rate(&mut self, srate: f32) {
|
||||||
|
@ -354,6 +361,27 @@ impl NodeExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn feed_midi_events_from<F: FnMut() -> Option<HxTimedEvent>>(&mut self, mut f: F) {
|
||||||
|
self.exec_ctx.midi_notes.clear();
|
||||||
|
self.exec_ctx.midi_ccs.clear();
|
||||||
|
|
||||||
|
while let Some(ev) = f() {
|
||||||
|
if ev.is_cc() {
|
||||||
|
self.exec_ctx.midi_ccs.push(ev);
|
||||||
|
} else {
|
||||||
|
self.exec_ctx.midi_notes.push(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.exec_ctx.midi_ccs.len() == MAX_MIDI_CC_PER_BLOCK {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if self.exec_ctx.midi_notes.len() == MAX_MIDI_NOTES_PER_BLOCK {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_note_buffer(&mut self) -> &mut NoteBuffer {
|
pub fn get_note_buffer(&mut self) -> &mut NoteBuffer {
|
||||||
&mut self.exec_ctx.note_buffer
|
&mut self.exec_ctx.note_buffer
|
||||||
|
@ -548,33 +576,17 @@ impl NodeExecutor {
|
||||||
let cur_nframes = if nframes >= MAX_BLOCK_SIZE { MAX_BLOCK_SIZE } else { nframes };
|
let cur_nframes = if nframes >= MAX_BLOCK_SIZE { MAX_BLOCK_SIZE } else { nframes };
|
||||||
nframes -= cur_nframes;
|
nframes -= cur_nframes;
|
||||||
|
|
||||||
let buf = self.get_note_buffer();
|
self.feed_midi_events_from(|| {
|
||||||
buf.reset();
|
|
||||||
loop {
|
|
||||||
if ev_win.feed_me() {
|
if ev_win.feed_me() {
|
||||||
if events.is_empty() {
|
if events.is_empty() {
|
||||||
break;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
ev_win.feed(events.remove(0));
|
ev_win.feed(events.remove(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((timing, event)) = ev_win.next_event_in_range(offs + cur_nframes) {
|
ev_win.next_event_in_range(offs, cur_nframes)
|
||||||
buf.step_to(timing);
|
});
|
||||||
match event {
|
|
||||||
HxMidiEvent::NoteOn { channel, note, vel } => {
|
|
||||||
buf.note_on(channel, note);
|
|
||||||
buf.set_velocity(channel, vel);
|
|
||||||
}
|
|
||||||
HxMidiEvent::NoteOff { channel, note } => {
|
|
||||||
buf.note_off(channel, note);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.get_note_buffer().step_to(cur_nframes - 1);
|
|
||||||
|
|
||||||
let mut context = crate::Context {
|
let mut context = crate::Context {
|
||||||
nframes: cur_nframes,
|
nframes: cur_nframes,
|
||||||
|
|
|
@ -12,6 +12,14 @@ pub struct HxTimedEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HxTimedEvent {
|
impl HxTimedEvent {
|
||||||
|
pub fn is_cc(&self) -> bool {
|
||||||
|
if let HxMidiEvent::CC { .. } = self.kind {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn note_on(timing: usize, channel: u8, note: u8, vel: f32) -> Self {
|
pub fn note_on(timing: usize, channel: u8, note: u8, vel: f32) -> Self {
|
||||||
Self { timing, kind: HxMidiEvent::NoteOn { channel, note, vel } }
|
Self { timing, kind: HxMidiEvent::NoteOn { channel, note, vel } }
|
||||||
}
|
}
|
||||||
|
@ -21,10 +29,31 @@ impl HxTimedEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MidiEventPointer<'a> {
|
||||||
|
buf: &'a [HxTimedEvent],
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MidiEventPointer<'a> {
|
||||||
|
pub fn new(buf: &'a [HxTimedEvent]) -> Self {
|
||||||
|
Self { buf, idx: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_at(&mut self, time: usize) -> Option<HxMidiEvent> {
|
||||||
|
if self.idx < self.buf.len() && self.buf[self.idx].timing <= time {
|
||||||
|
self.idx += 1;
|
||||||
|
Some(self.buf[self.idx - 1].kind)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum HxMidiEvent {
|
pub enum HxMidiEvent {
|
||||||
NoteOn { channel: u8, note: u8, vel: f32 },
|
NoteOn { channel: u8, note: u8, vel: f32 },
|
||||||
NoteOff { channel: u8, note: u8 },
|
NoteOff { channel: u8, note: u8 },
|
||||||
|
CC { channel: u8, cc: u8, value: f32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -128,10 +157,14 @@ impl EventWindowing {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn next_event_in_range(&mut self, to_time: usize) -> Option<(usize, HxMidiEvent)> {
|
pub fn next_event_in_range(
|
||||||
|
&mut self,
|
||||||
|
to_time: usize,
|
||||||
|
block_size: usize,
|
||||||
|
) -> Option<HxTimedEvent> {
|
||||||
if let Some(event) = self.event.take() {
|
if let Some(event) = self.event.take() {
|
||||||
if event.timing < to_time {
|
if event.timing < (to_time + block_size) {
|
||||||
return Some((event.timing, event.kind));
|
return Some(HxTimedEvent { timing: event.timing - to_time, kind: event.kind });
|
||||||
} else {
|
} else {
|
||||||
self.event = Some(event);
|
self.event = Some(event);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue