From ecd6b53c65efbffe298a0185d857f403b222ed6e Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Wed, 10 Aug 2022 19:15:03 +0200 Subject: [PATCH] Working on the MidiP MIDI note input node --- src/dsp/mod.rs | 10 ++++ src/dsp/node_formfm.rs | 6 +-- src/dsp/node_midip.rs | 106 +++++++++++++++++++++++++++++++++++++ src/nodes/mod.rs | 2 + src/nodes/node_exec.rs | 9 +++- src/nodes/note_buffer.rs | 109 ++++++++++++++++++++++++++++++++++----- 6 files changed, 223 insertions(+), 19 deletions(-) create mode 100644 src/dsp/node_midip.rs diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 04e3e4a..d7aa25c 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -519,6 +519,8 @@ mod node_noise; #[allow(non_upper_case_globals)] mod node_out; #[allow(non_upper_case_globals)] +mod node_midip; +#[allow(non_upper_case_globals)] mod node_pverb; #[allow(non_upper_case_globals)] mod node_quant; @@ -570,6 +572,7 @@ use crate::fa_map_clip; use crate::fa_mux9_in_cnt; use crate::fa_noise_mode; use crate::fa_out_mono; +use crate::fa_midip_chan; use crate::fa_quant; use crate::fa_sampl_dclick; use crate::fa_sampl_dir; @@ -601,6 +604,7 @@ use node_mix3::Mix3; use node_mux9::Mux9; use node_noise::Noise; use node_out::Out; +use node_midip::MidiP; use node_pverb::PVerb; use node_quant::Quant; use node_rndwk::RndWk; @@ -1423,6 +1427,12 @@ macro_rules! node_list { (3 force n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5) (4 pos n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5) [0 sig], + midip => MidiP UIType::Generic UICategory::IOUtil + (0 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0) + {1 0 chan setting(0) mode fa_midip_chan 0 16} + [0 freq] + [1 gate] + [2 vel], out => Out UIType::Generic UICategory::IOUtil (0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) (1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) diff --git a/src/dsp/node_formfm.rs b/src/dsp/node_formfm.rs index 265f736..b53ec01 100644 --- a/src/dsp/node_formfm.rs +++ b/src/dsp/node_formfm.rs @@ -90,13 +90,11 @@ impl DspNode for FormFM { let peak_val = denorm::FormFM::peak(peak_val, frame); // make a triangle wave, with the peak at carrier center - let carrier_base = - (self.phase / side_val).min((1.0 - self.phase) / (1.0 - side_val)); + let carrier_base = (self.phase / side_val).min((1.0 - self.phase) / (1.0 - side_val)); // smoothstep let carrier = 1.0 - - ((1.0 - peak_val) - * (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base))); + - ((1.0 - peak_val) * (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base))); // multiple of the frequency the modulators are at let multiple = formant_freq / base_freq.max(1e-6); diff --git a/src/dsp/node_midip.rs b/src/dsp/node_midip.rs new file mode 100644 index 0000000..8fe4c34 --- /dev/null +++ b/src/dsp/node_midip.rs @@ -0,0 +1,106 @@ +// Copyright (c) 2022 Weird Constructor +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +use crate::dsp::{ + at, denorm, inp, out_idx, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, +}; +use crate::nodes::{NodeAudioContext, NodeExecContext}; + +#[macro_export] +macro_rules! fa_midip_chan { + ($formatter: expr, $v: expr, $denorm_v: expr) => {{ + write!($formatter, "{}", $v.round() as usize) + }}; +} + +/// The (stereo) output port of the plugin +#[derive(Debug, Clone)] +pub struct MidiP { + /// - 0: signal channel 1 + /// - 1: signal channel 2 + #[allow(dead_code)] + input: [f32; 2], +} + +impl MidiP { + pub fn new(_nid: &NodeId) -> Self { + Self { input: [0.0; 2] } + } + + pub const chan: &'static str = "MidiP chan\nMIDI Channel 0 to 15\n"; + pub const det: &'static str = "MidiP det\nDetune input pitch a bit\nRange: (-1..1)"; + pub const freq: &'static str = + "MidiP freq\nMIDI note frequency, detuned by 'det'.\nRange: (-1..1)"; + pub const gate: &'static str = "MidiP gate\nMIDI note gate\nRange: (0..1)"; + pub const vel: &'static str = "MidiP vel\nMIDI note velocity\nRange: (0..1)"; + + pub const ch1: &'static str = "MidiP ch1\nAudio channel 1 (left)\nRange: (-1..1)"; + pub const ch2: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + + pub const ch3: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch4: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch5: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch6: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch7: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch8: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch9: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch10: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch11: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch12: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch13: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch14: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch15: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch16: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + pub const ch17: &'static str = "MidiP ch2\nAudio channel 2 (right)\nRange: (-1..1)"; + + pub const DESC: &'static str = "Audio Output Port\n\n\ + This output port node allows you to send audio signals \ + to audio devices or tracks in your DAW."; + pub const HELP: &'static str = r#"Audio Output Port + +This output port node allows you to send audio signals to audio devices +or tracks in your DAW. If you need a stereo output but only have a mono +signal you can use the 'mono' setting to duplicate the signal on the 'ch1' +input to the second channel 'ch2'. +"#; +} + +impl DspNode for MidiP { + fn outputs() -> usize { + 0 + } + + fn set_sample_rate(&mut self, _srate: f32) {} + fn reset(&mut self) {} + + #[inline] + fn process( + &mut self, + ctx: &mut T, + ectx: &mut NodeExecContext, + _nctx: &NodeContext, + atoms: &[SAtom], + _inputs: &[ProcBuf], + outputs: &mut [ProcBuf], + ctx_vals: LedPhaseVals, + ) { + let out_i = out_idx::MidiP::freq(); + + let (freq, r) = outputs.split_at_mut(out_i); + let (gate, vel) = r.split_at_mut(1); + let freq = &mut freq[0]; + let gate = &mut gate[0]; + let vel = &mut vel[0]; + + for frame in 0..ctx.nframes() { + let chan = ectx.note_buffer.get_chan_at(0, frame as u8); + freq.write(frame, chan.note as f32 * 1.0 / 127.0); + gate.write(frame, chan.gate as f32); + vel.write(frame, chan.vel as f32); + } + + let last_val = gate.read(ctx.nframes() - 1); + ctx_vals[0].set(last_val); + } +} diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 9888f83..309e95b 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -21,6 +21,7 @@ mod node_conf; mod node_exec; mod node_graph_ordering; mod node_prog; +mod note_buffer; pub mod visual_sampling_filter; pub(crate) use visual_sampling_filter::*; @@ -30,6 +31,7 @@ pub use node_conf::*; pub use node_exec::*; pub use node_graph_ordering::NodeGraphOrdering; pub use node_prog::*; +pub use note_buffer::{NoteBuffer, NoteChannelState}; use crate::dsp::{Node, SAtom}; pub use crate::monitor::MinMaxMonitorSamples; diff --git a/src/nodes/node_exec.rs b/src/nodes/node_exec.rs index db374c9..db16984 100644 --- a/src/nodes/node_exec.rs +++ b/src/nodes/node_exec.rs @@ -2,6 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. +use super::NoteBuffer; use super::{ DropMsg, GraphMessage, NodeProg, FB_DELAY_TIME_US, MAX_ALLOCATED_NODES, MAX_FB_DELAY_SIZE, MAX_SMOOTHERS, UNUSED_MONITOR_IDX, @@ -169,13 +170,14 @@ impl Default for FeedbackBuffer { /// This is used for instance to implement the feedbackd delay nodes. pub struct NodeExecContext { pub feedback_delay_buffers: Vec, + pub note_buffer: NoteBuffer, } impl NodeExecContext { fn new() -> Self { let mut fbdb = vec![]; fbdb.resize_with(MAX_ALLOCATED_NODES, FeedbackBuffer::new); - Self { feedback_delay_buffers: fbdb } + Self { feedback_delay_buffers: fbdb, note_buffer: NoteBuffer::new() } } fn set_sample_rate(&mut self, srate: f32) { @@ -352,6 +354,11 @@ impl NodeExecutor { } } + #[inline] + pub fn get_note_buffer(&mut self) -> &mut NoteBuffer { + &mut self.exec_ctx.note_buffer + } + #[inline] pub fn get_nodes(&self) -> &Vec { &self.nodes diff --git a/src/nodes/note_buffer.rs b/src/nodes/note_buffer.rs index 9c3e98f..d1c8038 100644 --- a/src/nodes/note_buffer.rs +++ b/src/nodes/note_buffer.rs @@ -3,38 +3,119 @@ // See README.md and COPYING for details. use crate::dsp::MAX_BLOCK_SIZE; -use crate::util::Smoother; #[derive(Debug, Clone, Copy)] -pub struct ChannelState { - vel: f32, - pitch: u8, - gate: u8, +pub struct NoteChannelState { + pub vel: f32, + pub note: u8, + pub gate: u8, +} + +impl std::fmt::Display for NoteChannelState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "C", self.note, self.gate, self.vel) + } +} + +impl NoteChannelState { + pub fn new() -> Self { + Self { vel: 0.0, note: 0, gate: 0 } + } } pub struct NoteBuffer { - interleaved_chans: Vec, + interleaved_chans: Vec, buf_idx: usize, } impl NoteBuffer { pub fn new() -> Self { - Self { - interleaved_chans: vec![ChannelState::new(); 16 * MAX_BLOCK_SIZE], - buf_idx: 15, + Self { interleaved_chans: vec![NoteChannelState::new(); 16 * MAX_BLOCK_SIZE], buf_idx: 15 } + } + + #[inline] + pub fn reset(&mut self) { + let cur = self.buf_idx; + if cur != 0 { + self.interleaved_chans.copy_within((cur * 16)..((cur + 1) * 16), 0); + self.buf_idx = 0; } } #[inline] pub fn step(&mut self) { let cur = self.buf_idx; - let next = (self.buf_idx + 1) % 16; + let next = (self.buf_idx + 1) % MAX_BLOCK_SIZE; + println!("COPY {}..{} => {}", (cur * 16), ((cur + 1) * 16), next * 16); self.interleaved_chans.copy_within((cur * 16)..((cur + 1) * 16), next * 16); + self.buf_idx = next; } -// pub fn play_velocity(&mut self, channel: u8, vel: f32) { -// let mut vel = &mut self.velocity[channel % 16]; -// vel.set(vel.current(), vel); -// } + #[inline] + pub fn note_on(&mut self, channel: u8, note: u8) { + let mut chan = &mut self.interleaved_chans[(self.buf_idx * 16) + (channel as usize % 16)]; + if chan.gate == 0 { + chan.gate = 1; + chan.note = note; + } + } + + #[inline] + pub fn note_off(&mut self, channel: u8, note: u8) { + let mut chan = &mut self.interleaved_chans[(self.buf_idx * 16) + (channel as usize % 16)]; + if chan.gate == 1 && chan.note == note { + chan.gate = 0; + chan.note = 0; + } + } + + #[inline] + pub fn set_velocity(&mut self, channel: u8, vel: f32) { + self.interleaved_chans[(self.buf_idx * 16) + (channel as usize % 16)].vel = vel; + } + + #[inline] + pub fn get_chan_at(&self, channel: u8, frame: u8) -> &NoteChannelState { + &self.interleaved_chans[frame as usize * 16 + (channel as usize % 16)] + } } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_note_buffer() { + let mut buf = NoteBuffer::new(); + + buf.reset(); + buf.note_on(0, 10); + buf.note_on(2, 12); + buf.set_velocity(0, 0.6); + buf.set_velocity(2, 0.8); + //d// println!("> {:?}", buf.get_chan_at(0, 0)); + buf.step(); + buf.note_on(0, 11); + for _ in 0..10 { + buf.step(); + } + buf.note_off(0, 11); + buf.step(); + buf.note_off(0, 10); + buf.step(); + buf.set_velocity(2, 0.4); + //d// println!("> {:?}", buf.get_chan_at(0, 0)); + for i in 0..(MAX_BLOCK_SIZE - 14) { + buf.step(); + } + + //d// for i in 0..MAX_BLOCK_SIZE { + //d// println!(">{} {}", i, buf.get_chan_at(2, i as u8)); + //d// } + + assert_eq!(buf.get_chan_at(0, 0).to_string(), "C"); + assert_eq!(buf.get_chan_at(0, 12).to_string(), "C"); + assert_eq!(buf.get_chan_at(2, 0).to_string(), "C"); + assert_eq!(buf.get_chan_at(2, 127).to_string(), "C"); + } +}