diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 7a2270c..579030a 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -1407,8 +1407,9 @@ macro_rules! node_list { (0 atv n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0) [0 sig], scope => Scope UIType::Generic UICategory::IOUtil - (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) - [0 sig], + (0 in1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) + (0 in2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) + (0 in3 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0), ad => Ad UIType::Generic UICategory::Mod (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0) (1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) diff --git a/src/dsp/node_scope.rs b/src/dsp/node_scope.rs index d6d5268..0a31119 100644 --- a/src/dsp/node_scope.rs +++ b/src/dsp/node_scope.rs @@ -3,38 +3,42 @@ // See README.md and COPYING for details. //use super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger}; -use crate::dsp::{ - DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, -}; +use crate::nodes::SCOPE_SAMPLES; +use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::UnsyncFloatBuf; /// A simple signal scope #[derive(Debug, Clone)] pub struct Scope { - buf: UnsyncFloatBuf, + buf: [UnsyncFloatBuf; 3], idx: usize, } impl Scope { pub fn new(_nid: &NodeId) -> Self { - Self { buf: UnsyncFloatBuf::new_with_len(1), idx: 0 } + let buf = [ + UnsyncFloatBuf::new_with_len(1), + UnsyncFloatBuf::new_with_len(1), + UnsyncFloatBuf::new_with_len(1), + ]; + Self { buf, idx: 0 } } - pub const inp: &'static str = "Scope inp\nSignal input.\nRange: (-1..1)\n"; - pub const sig: &'static str = - "Scope sig\nSignal output. The exact same signal that was received on 'inp'!\n"; + pub const in1: &'static str = "Scope in1\nSignal input 1.\nRange: (-1..1)\n"; + pub const in2: &'static str = "Scope in2\nSignal input 2.\nRange: (-1..1)\n"; + pub const in3: &'static str = "Scope in3\nSignal input 3.\nRange: (-1..1)\n"; pub const DESC: &'static str = r#"Signal Oscilloscope Probe -This is a signal oscilloscope probe node. You can have up to 16 of these, -which will be displayed in the GUI. +This is a signal oscilloscope probe node, you can capture up to 3 signals. "#; pub const HELP: &'static str = r#"Scope - Signal Oscilloscope Probe -You can have up to 16 of these probes in your patch. The received signal will be -forwarded to the GUI and you can inspect the waveform there. +You can have up to 8 different scopes in your patch. That means you can in theory +record up to 24 signals. The received signal will be forwarded to the GUI and +you can inspect the waveform there. "#; - pub fn set_scope_buffer(&mut self, buf: UnsyncFloatBuf) { + pub fn set_scope_buffers(&mut self, buf: [UnsyncFloatBuf; 3]) { self.buf = buf; } } @@ -61,17 +65,23 @@ impl DspNode for Scope { ) { use crate::dsp::{inp, out}; - let inp = inp::Ad::inp(inputs); - let out = out::Scope::sig(outputs); + let in1 = inp::Scope::in1(inputs); + let in2 = inp::Scope::in2(inputs); + let in3 = inp::Scope::in3(inputs); + let inputs = [in1, in2, in3]; for frame in 0..ctx.nframes() { - let in_val = inp.read(frame); - self.buf.write(self.idx, in_val); - self.idx = (self.idx + 1) % self.buf.len(); - out.write(frame, in_val); + for (i, input) in inputs.iter().enumerate() { + let in_val = input.read(frame); + self.buf[i].write(self.idx, in_val); + } + + self.idx = (self.idx + 1) % SCOPE_SAMPLES; } let last_frame = ctx.nframes() - 1; - ctx_vals[0].set(out.read(last_frame)); + ctx_vals[0].set( + (in1.read(last_frame) + in2.read(last_frame) + in3.read(last_frame)).clamp(-1.0, 1.0), + ); } } diff --git a/src/matrix.rs b/src/matrix.rs index f6a3d80..6e30476 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -582,8 +582,8 @@ impl Matrix { self.config.get_pattern_data(tracker_id) } - pub fn get_scope_buffer(&self, scope: usize) -> Option { - self.config.get_scope_buffer(scope) + pub fn get_scope_buffers(&self, scope: usize) -> Option<[UnsyncFloatBuf; 3]> { + self.config.get_scope_buffers(scope) } /// Checks if pattern data updates need to be sent to the diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index bdbf419..06e6e55 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -3,7 +3,7 @@ // See README.md and COPYING for details. pub const MAX_ALLOCATED_NODES: usize = 256; -pub const MAX_SCOPES: usize = 16; +pub const MAX_SCOPES: usize = 8; pub const SCOPE_SAMPLES: usize = 512; pub const MAX_INPUTS: usize = 32; pub const MAX_SMOOTHERS: usize = 36 + 4; // 6 * 6 modulator inputs + 4 UI Knobs diff --git a/src/nodes/node_conf.rs b/src/nodes/node_conf.rs index 7b091fa..25bfff6 100644 --- a/src/nodes/node_conf.rs +++ b/src/nodes/node_conf.rs @@ -3,15 +3,15 @@ // See README.md and COPYING for details. use super::{ - FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, - MAX_AVAIL_TRACKERS, MAX_INPUTS, UNUSED_MONITOR_IDX, MAX_SCOPES, SCOPE_SAMPLES + FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, MAX_AVAIL_TRACKERS, + MAX_INPUTS, MAX_SCOPES, SCOPE_SAMPLES, UNUSED_MONITOR_IDX, }; use crate::dsp::tracker::{PatternData, Tracker}; use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom}; use crate::monitor::{new_monitor_processor, MinMaxMonitorSamples, Monitor, MON_SIG_CNT}; use crate::nodes::drop_thread::DropThread; -use crate::util::AtomicFloat; use crate::unsync_float_buf::UnsyncFloatBuf; +use crate::util::AtomicFloat; use crate::SampleLibrary; use ringbuf::{Producer, RingBuffer}; @@ -179,7 +179,7 @@ pub struct NodeConfigurator { /// Holding the tracker sequencers pub(crate) trackers: Vec, /// Holding the scope buffers: - pub(crate) scopes: Vec, + pub(crate) scopes: Vec<[UnsyncFloatBuf; 3]>, /// The shared parts of the [NodeConfigurator] /// and the [crate::nodes::NodeExecutor]. pub(crate) shared: SharedNodeConf, @@ -282,7 +282,14 @@ impl NodeConfigurator { atom_values: std::collections::HashMap::new(), node2idx: HashMap::new(), trackers: vec![Tracker::new(); MAX_AVAIL_TRACKERS], - scopes: vec![UnsyncFloatBuf::new_with_len(SCOPE_SAMPLES); MAX_SCOPES], + scopes: vec![ + [ + UnsyncFloatBuf::new_with_len(SCOPE_SAMPLES), + UnsyncFloatBuf::new_with_len(SCOPE_SAMPLES), + UnsyncFloatBuf::new_with_len(SCOPE_SAMPLES) + ]; + MAX_SCOPES + ], }, shared_exec, ) @@ -642,7 +649,7 @@ impl NodeConfigurator { } } - pub fn get_scope_buffer(&self, scope: usize) -> Option { + pub fn get_scope_buffers(&self, scope: usize) -> Option<[UnsyncFloatBuf; 3]> { self.scopes.get(scope).cloned() } @@ -687,7 +694,7 @@ impl NodeConfigurator { if let Node::Scope { node } = &mut node { if let Some(buf) = self.scopes.get(ni.instance()) { - node.set_scope_buffer(buf.clone()); + node.set_scope_buffers(buf.clone()); } } diff --git a/src/unsync_float_buf.rs b/src/unsync_float_buf.rs index d9f7f6f..8449921 100644 --- a/src/unsync_float_buf.rs +++ b/src/unsync_float_buf.rs @@ -54,6 +54,10 @@ impl UnsyncFloatBuf { } } +/// Private implementation detail for [UnsyncFloatBuf]. +/// +/// This mostly allows [UnsyncFloatBuf] to wrap UnsyncFloatBufImpl into an [std::sync::Arc], +/// to make sure the `data_store` Vector is not moved accidentally. #[derive(Debug)] struct UnsyncFloatBufImpl { data_store: Vec, @@ -65,18 +69,23 @@ unsafe impl Sync for UnsyncFloatBuf {} unsafe impl Send for UnsyncFloatBuf {} impl UnsyncFloatBufImpl { + /// Create a new shared reference of this. You must not create + /// an UnsyncFloatBufImpl that can move! Otherwise the internal pointer + /// would be invalidated. fn new_shared(len: usize) -> Arc { let mut rc = Arc::new(Self { data_store: Vec::new(), len, ptr: std::ptr::null_mut() }); let mut unsync_buf = Arc::get_mut(&mut rc).expect("No other reference to this Arc"); unsync_buf.data_store.resize_with(len, || AtomicFloat::new(0.0)); - // Taking the pointer to the Vec data buffer is fine, + + // XXX: Taking the pointer to the Vec data buffer is fine, // because it will not be moved when inside the Arc. unsync_buf.ptr = unsync_buf.data_store.as_mut_ptr(); rc } + /// Write a sample. fn write(&self, idx: usize, v: f32) { if idx < self.len { unsafe { @@ -85,6 +94,7 @@ impl UnsyncFloatBufImpl { } } + /// Read a sample. fn read(&self, idx: usize) -> f32 { if idx < self.len { unsafe { (*self.ptr.add(idx)).get() } @@ -93,6 +103,7 @@ impl UnsyncFloatBufImpl { } } + /// Return the length of this buffer. fn len(&self) -> usize { self.len } diff --git a/tests/node_scope.rs b/tests/node_scope.rs index 36bfa4a..6523050 100644 --- a/tests/node_scope.rs +++ b/tests/node_scope.rs @@ -13,20 +13,35 @@ fn check_node_scope_1() { let mut matrix = Matrix::new(node_conf, 3, 3); let mut chain = MatrixCellChain::new(CellDir::B); - chain.node_inp("scope", "inp").place(&mut matrix, 0, 0).unwrap(); + chain.node_inp("scope", "in1").place(&mut matrix, 0, 0).unwrap(); matrix.sync().unwrap(); let scope = NodeId::Scope(0); - let inp_p = scope.inp_param("inp").unwrap(); + let in1_p = scope.inp_param("in1").unwrap(); + let in2_p = scope.inp_param("in2").unwrap(); + let in3_p = scope.inp_param("in3").unwrap(); - matrix.set_param(inp_p, SAtom::param(1.0)); + matrix.set_param(in1_p, SAtom::param(1.0)); + matrix.set_param(in2_p, SAtom::param(1.0)); + matrix.set_param(in3_p, SAtom::param(1.0)); let _res = run_for_ms(&mut node_exec, 11.0); - let scope = matrix.get_scope_buffer(0).unwrap(); + let scope = matrix.get_scope_buffers(0).unwrap(); let mut v = vec![]; for x in 0..SCOPE_SAMPLES { - v.push(scope.read(x)); + v.push(scope[0].read(x)); } + assert_decimated_feq!(v, 80, vec![0.0022, 0.1836, 0.3650, 0.5464, 0.7278, 0.9093, 1.0]); + let mut v = vec![]; + for x in 0..SCOPE_SAMPLES { + v.push(scope[1].read(x)); + } + assert_decimated_feq!(v, 80, vec![0.0022, 0.1836, 0.3650, 0.5464, 0.7278, 0.9093, 1.0]); + + let mut v = vec![]; + for x in 0..SCOPE_SAMPLES { + v.push(scope[2].read(x)); + } assert_decimated_feq!(v, 80, vec![0.0022, 0.1836, 0.3650, 0.5464, 0.7278, 0.9093, 1.0]); }