Working on the MidiP MIDI note input node
This commit is contained in:
parent
20f41b1dfd
commit
ecd6b53c65
6 changed files with 223 additions and 19 deletions
|
@ -519,6 +519,8 @@ mod node_noise;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
mod node_out;
|
mod node_out;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
|
mod node_midip;
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
mod node_pverb;
|
mod node_pverb;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
mod node_quant;
|
mod node_quant;
|
||||||
|
@ -570,6 +572,7 @@ use crate::fa_map_clip;
|
||||||
use crate::fa_mux9_in_cnt;
|
use crate::fa_mux9_in_cnt;
|
||||||
use crate::fa_noise_mode;
|
use crate::fa_noise_mode;
|
||||||
use crate::fa_out_mono;
|
use crate::fa_out_mono;
|
||||||
|
use crate::fa_midip_chan;
|
||||||
use crate::fa_quant;
|
use crate::fa_quant;
|
||||||
use crate::fa_sampl_dclick;
|
use crate::fa_sampl_dclick;
|
||||||
use crate::fa_sampl_dir;
|
use crate::fa_sampl_dir;
|
||||||
|
@ -601,6 +604,7 @@ use node_mix3::Mix3;
|
||||||
use node_mux9::Mux9;
|
use node_mux9::Mux9;
|
||||||
use node_noise::Noise;
|
use node_noise::Noise;
|
||||||
use node_out::Out;
|
use node_out::Out;
|
||||||
|
use node_midip::MidiP;
|
||||||
use node_pverb::PVerb;
|
use node_pverb::PVerb;
|
||||||
use node_quant::Quant;
|
use node_quant::Quant;
|
||||||
use node_rndwk::RndWk;
|
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)
|
(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)
|
(4 pos n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||||
[0 sig],
|
[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
|
out => Out UIType::Generic UICategory::IOUtil
|
||||||
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
(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)
|
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
|
|
|
@ -90,13 +90,11 @@ impl DspNode for FormFM {
|
||||||
let peak_val = denorm::FormFM::peak(peak_val, frame);
|
let peak_val = denorm::FormFM::peak(peak_val, frame);
|
||||||
|
|
||||||
// make a triangle wave, with the peak at carrier center
|
// make a triangle wave, with the peak at carrier center
|
||||||
let carrier_base =
|
let carrier_base = (self.phase / side_val).min((1.0 - self.phase) / (1.0 - side_val));
|
||||||
(self.phase / side_val).min((1.0 - self.phase) / (1.0 - side_val));
|
|
||||||
|
|
||||||
// smoothstep
|
// smoothstep
|
||||||
let carrier = 1.0
|
let carrier = 1.0
|
||||||
- ((1.0 - peak_val)
|
- ((1.0 - peak_val) * (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base)));
|
||||||
* (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base)));
|
|
||||||
|
|
||||||
// multiple of the frequency the modulators are at
|
// multiple of the frequency the modulators are at
|
||||||
let multiple = formant_freq / base_freq.max(1e-6);
|
let multiple = formant_freq / base_freq.max(1e-6);
|
||||||
|
|
106
src/dsp/node_midip.rs
Normal file
106
src/dsp/node_midip.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright (c) 2022 Weird Constructor <weirdconstructor@gmail.com>
|
||||||
|
// 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<T: NodeAudioContext>(
|
||||||
|
&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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ mod node_conf;
|
||||||
mod node_exec;
|
mod node_exec;
|
||||||
mod node_graph_ordering;
|
mod node_graph_ordering;
|
||||||
mod node_prog;
|
mod node_prog;
|
||||||
|
mod note_buffer;
|
||||||
pub mod visual_sampling_filter;
|
pub mod visual_sampling_filter;
|
||||||
|
|
||||||
pub(crate) use visual_sampling_filter::*;
|
pub(crate) use visual_sampling_filter::*;
|
||||||
|
@ -30,6 +31,7 @@ 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::{NoteBuffer, NoteChannelState};
|
||||||
|
|
||||||
use crate::dsp::{Node, SAtom};
|
use crate::dsp::{Node, SAtom};
|
||||||
pub use crate::monitor::MinMaxMonitorSamples;
|
pub use crate::monitor::MinMaxMonitorSamples;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
||||||
// See README.md and COPYING for details.
|
// See README.md and COPYING for details.
|
||||||
|
|
||||||
|
use super::NoteBuffer;
|
||||||
use super::{
|
use super::{
|
||||||
DropMsg, GraphMessage, NodeProg, FB_DELAY_TIME_US, MAX_ALLOCATED_NODES, MAX_FB_DELAY_SIZE,
|
DropMsg, GraphMessage, NodeProg, FB_DELAY_TIME_US, MAX_ALLOCATED_NODES, MAX_FB_DELAY_SIZE,
|
||||||
MAX_SMOOTHERS, UNUSED_MONITOR_IDX,
|
MAX_SMOOTHERS, UNUSED_MONITOR_IDX,
|
||||||
|
@ -169,13 +170,14 @@ impl Default for FeedbackBuffer {
|
||||||
/// This is used for instance to implement the feedbackd delay nodes.
|
/// This is used for instance to implement the feedbackd delay nodes.
|
||||||
pub struct NodeExecContext {
|
pub struct NodeExecContext {
|
||||||
pub feedback_delay_buffers: Vec<FeedbackBuffer>,
|
pub feedback_delay_buffers: Vec<FeedbackBuffer>,
|
||||||
|
pub note_buffer: NoteBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 }
|
Self { feedback_delay_buffers: fbdb, note_buffer: NoteBuffer::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_sample_rate(&mut self, srate: f32) {
|
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]
|
#[inline]
|
||||||
pub fn get_nodes(&self) -> &Vec<Node> {
|
pub fn get_nodes(&self) -> &Vec<Node> {
|
||||||
&self.nodes
|
&self.nodes
|
||||||
|
|
|
@ -3,38 +3,119 @@
|
||||||
// See README.md and COPYING for details.
|
// See README.md and COPYING for details.
|
||||||
|
|
||||||
use crate::dsp::MAX_BLOCK_SIZE;
|
use crate::dsp::MAX_BLOCK_SIZE;
|
||||||
use crate::util::Smoother;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ChannelState {
|
pub struct NoteChannelState {
|
||||||
vel: f32,
|
pub vel: f32,
|
||||||
pitch: u8,
|
pub note: u8,
|
||||||
gate: 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<N={},G={},V={:5.3}>", self.note, self.gate, self.vel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteChannelState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { vel: 0.0, note: 0, gate: 0 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NoteBuffer {
|
pub struct NoteBuffer {
|
||||||
interleaved_chans: Vec<ChannelState>,
|
interleaved_chans: Vec<NoteChannelState>,
|
||||||
buf_idx: usize,
|
buf_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoteBuffer {
|
impl NoteBuffer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self { interleaved_chans: vec![NoteChannelState::new(); 16 * MAX_BLOCK_SIZE], buf_idx: 15 }
|
||||||
interleaved_chans: vec![ChannelState::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]
|
#[inline]
|
||||||
pub fn step(&mut self) {
|
pub fn step(&mut self) {
|
||||||
let cur = self.buf_idx;
|
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.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) {
|
#[inline]
|
||||||
// let mut vel = &mut self.velocity[channel % 16];
|
pub fn note_on(&mut self, channel: u8, note: u8) {
|
||||||
// vel.set(vel.current(), vel);
|
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<N=10,G=1,V=0.600>");
|
||||||
|
assert_eq!(buf.get_chan_at(0, 12).to_string(), "C<N=0,G=0,V=0.600>");
|
||||||
|
assert_eq!(buf.get_chan_at(2, 0).to_string(), "C<N=12,G=1,V=0.800>");
|
||||||
|
assert_eq!(buf.get_chan_at(2, 127).to_string(), "C<N=12,G=1,V=0.400>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue