Added a scope probe node, to provide direct signal views from the frontend
This commit is contained in:
parent
c73199185b
commit
64ece501ad
7 changed files with 147 additions and 2 deletions
|
@ -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)
|
||||
|
|
77
src/dsp/node_scope.rs
Normal file
77
src/dsp/node_scope.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
// 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 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<T: NodeAudioContext>(
|
||||
&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));
|
||||
}
|
||||
}
|
|
@ -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<UnsyncFloatBuf> {
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<NodeId, usize>,
|
||||
/// Holding the tracker sequencers
|
||||
pub(crate) trackers: Vec<Tracker>,
|
||||
/// Holding the scope buffers:
|
||||
pub(crate) scopes: Vec<UnsyncFloatBuf>,
|
||||
/// 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<UnsyncFloatBuf> {
|
||||
self.scopes.get(scope).cloned()
|
||||
}
|
||||
|
||||
pub fn get_pattern_data(&self, tracker_id: usize) -> Option<Arc<Mutex<PatternData>>> {
|
||||
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);
|
||||
|
|
|
@ -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)]
|
||||
|
|
32
tests/node_scope.rs
Normal file
32
tests/node_scope.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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.
|
||||
|
||||
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]);
|
||||
}
|
Loading…
Reference in a new issue