diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index c5b0975..7a2270c 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -535,6 +535,8 @@ mod node_tseq; mod node_tslfo; #[allow(non_upper_case_globals)] mod node_vosc; +#[allow(non_upper_case_globals)] +mod node_scope; pub mod biquad; pub mod dattorro; @@ -605,6 +607,7 @@ use node_test::Test; use node_tseq::TSeq; use node_tslfo::TsLFO; use node_vosc::VOsc; +use node_scope::Scope; pub const MIDI_MAX_FREQ: f32 = 13289.75; @@ -1403,6 +1406,9 @@ macro_rules! node_list { fbrd => FbRd UIType::Generic UICategory::IOUtil (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], 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 new file mode 100644 index 0000000..d6d5268 --- /dev/null +++ b/src/dsp/node_scope.rs @@ -0,0 +1,77 @@ +// 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 super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger}; +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, + idx: usize, +} + +impl Scope { + pub fn new(_nid: &NodeId) -> Self { + Self { buf: UnsyncFloatBuf::new_with_len(1), 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 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. +"#; + 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. +"#; + + pub fn set_scope_buffer(&mut self, buf: UnsyncFloatBuf) { + self.buf = buf; + } +} + +impl DspNode for Scope { + fn outputs() -> usize { + 1 + } + + 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, + ) { + use crate::dsp::{inp, out}; + + let inp = inp::Ad::inp(inputs); + let out = out::Scope::sig(outputs); + + 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); + } + + let last_frame = ctx.nframes() - 1; + ctx_vals[0].set(out.read(last_frame)); + } +} diff --git a/src/matrix.rs b/src/matrix.rs index 3585bad..f6a3d80 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -9,6 +9,7 @@ pub use crate::monitor::MON_SIG_CNT; pub use crate::nodes::MinMaxMonitorSamples; use crate::nodes::{NodeConfigurator, NodeGraphOrdering, NodeProg, MAX_ALLOCATED_NODES}; pub use crate::CellDir; +use crate::UnsyncFloatBuf; use std::collections::{HashMap, HashSet}; @@ -581,6 +582,10 @@ impl Matrix { self.config.get_pattern_data(tracker_id) } + pub fn get_scope_buffer(&self, scope: usize) -> Option { + self.config.get_scope_buffer(scope) + } + /// Checks if pattern data updates need to be sent to the /// DSP thread. pub fn check_pattern_data(&mut self, tracker_id: usize) { diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index cd450c5..bdbf419 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -3,6 +3,8 @@ // See README.md and COPYING for details. pub const MAX_ALLOCATED_NODES: usize = 256; +pub const MAX_SCOPES: usize = 16; +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 pub const MAX_AVAIL_TRACKERS: usize = 128; diff --git a/src/nodes/node_conf.rs b/src/nodes/node_conf.rs index ba9f313..7b091fa 100644 --- a/src/nodes/node_conf.rs +++ b/src/nodes/node_conf.rs @@ -3,14 +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, + FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, + MAX_AVAIL_TRACKERS, MAX_INPUTS, UNUSED_MONITOR_IDX, MAX_SCOPES, SCOPE_SAMPLES }; 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::SampleLibrary; use ringbuf::{Producer, RingBuffer}; @@ -177,6 +178,8 @@ pub struct NodeConfigurator { pub(crate) node2idx: HashMap, /// Holding the tracker sequencers pub(crate) trackers: Vec, + /// Holding the scope buffers: + pub(crate) scopes: Vec, /// The shared parts of the [NodeConfigurator] /// and the [crate::nodes::NodeExecutor]. pub(crate) shared: SharedNodeConf, @@ -279,6 +282,7 @@ 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], }, shared_exec, ) @@ -638,6 +642,10 @@ impl NodeConfigurator { } } + pub fn get_scope_buffer(&self, scope: usize) -> Option { + self.scopes.get(scope).cloned() + } + pub fn get_pattern_data(&self, tracker_id: usize) -> Option>> { if tracker_id >= self.trackers.len() { return None; @@ -677,6 +685,12 @@ impl NodeConfigurator { } } + if let Node::Scope { node } = &mut node { + if let Some(buf) = self.scopes.get(ni.instance()) { + node.set_scope_buffer(buf.clone()); + } + } + for i in 0..self.nodes.len() { if let NodeId::Nop = self.nodes[i].0.to_id() { index = Some(i); diff --git a/src/unsync_float_buf.rs b/src/unsync_float_buf.rs index 5103c57..d9f7f6f 100644 --- a/src/unsync_float_buf.rs +++ b/src/unsync_float_buf.rs @@ -47,6 +47,11 @@ impl UnsyncFloatBuf { pub fn read(&self, idx: usize) -> f32 { self.0.read(idx) } + + /// Length of the buffer. + pub fn len(&self) -> usize { + self.0.len() + } } #[derive(Debug)] @@ -87,6 +92,10 @@ impl UnsyncFloatBufImpl { 0.0 } } + + fn len(&self) -> usize { + self.len + } } #[cfg(test)] diff --git a/tests/node_scope.rs b/tests/node_scope.rs new file mode 100644 index 0000000..36bfa4a --- /dev/null +++ b/tests/node_scope.rs @@ -0,0 +1,32 @@ +// 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. + +mod common; +use common::*; + +use hexodsp::nodes::SCOPE_SAMPLES; + +#[test] +fn check_node_scope_1() { + let (node_conf, mut node_exec) = new_node_engine(); + 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(); + matrix.sync().unwrap(); + + let scope = NodeId::Scope(0); + let inp_p = scope.inp_param("inp").unwrap(); + + matrix.set_param(inp_p, SAtom::param(1.0)); + let _res = run_for_ms(&mut node_exec, 11.0); + + let scope = matrix.get_scope_buffer(0).unwrap(); + let mut v = vec![]; + for x in 0..SCOPE_SAMPLES { + v.push(scope.read(x)); + } + + assert_decimated_feq!(v, 80, vec![0.0022, 0.1836, 0.3650, 0.5464, 0.7278, 0.9093, 1.0]); +}