capture 3 signals at once

This commit is contained in:
Weird Constructor 2022-07-25 06:15:48 +02:00
parent 64ece501ad
commit 8d0dbe797c
7 changed files with 82 additions and 38 deletions

View file

@ -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 atv n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
[0 sig], [0 sig],
scope => Scope UIType::Generic UICategory::IOUtil 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 in1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
[0 sig], (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 ad => Ad UIType::Generic UICategory::Mod
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0) (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) (1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)

View file

@ -3,38 +3,42 @@
// See README.md and COPYING for details. // See README.md and COPYING for details.
//use super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger}; //use super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger};
use crate::dsp::{ use crate::nodes::SCOPE_SAMPLES;
DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom};
};
use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::nodes::{NodeAudioContext, NodeExecContext};
use crate::UnsyncFloatBuf; use crate::UnsyncFloatBuf;
/// A simple signal scope /// A simple signal scope
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Scope { pub struct Scope {
buf: UnsyncFloatBuf, buf: [UnsyncFloatBuf; 3],
idx: usize, idx: usize,
} }
impl Scope { impl Scope {
pub fn new(_nid: &NodeId) -> Self { 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 in1: &'static str = "Scope in1\nSignal input 1.\nRange: (-1..1)\n";
pub const sig: &'static str = pub const in2: &'static str = "Scope in2\nSignal input 2.\nRange: (-1..1)\n";
"Scope sig\nSignal output. The exact same signal that was received on 'inp'!\n"; pub const in3: &'static str = "Scope in3\nSignal input 3.\nRange: (-1..1)\n";
pub const DESC: &'static str = r#"Signal Oscilloscope Probe pub const DESC: &'static str = r#"Signal Oscilloscope Probe
This is a signal oscilloscope probe node. You can have up to 16 of these, This is a signal oscilloscope probe node, you can capture up to 3 signals.
which will be displayed in the GUI.
"#; "#;
pub const HELP: &'static str = r#"Scope - Signal Oscilloscope Probe 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 You can have up to 8 different scopes in your patch. That means you can in theory
forwarded to the GUI and you can inspect the waveform there. 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; self.buf = buf;
} }
} }
@ -61,17 +65,23 @@ impl DspNode for Scope {
) { ) {
use crate::dsp::{inp, out}; use crate::dsp::{inp, out};
let inp = inp::Ad::inp(inputs); let in1 = inp::Scope::in1(inputs);
let out = out::Scope::sig(outputs); let in2 = inp::Scope::in2(inputs);
let in3 = inp::Scope::in3(inputs);
let inputs = [in1, in2, in3];
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
let in_val = inp.read(frame); for (i, input) in inputs.iter().enumerate() {
self.buf.write(self.idx, in_val); let in_val = input.read(frame);
self.idx = (self.idx + 1) % self.buf.len(); self.buf[i].write(self.idx, in_val);
out.write(frame, in_val); }
self.idx = (self.idx + 1) % SCOPE_SAMPLES;
} }
let last_frame = ctx.nframes() - 1; 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),
);
} }
} }

View file

@ -582,8 +582,8 @@ impl Matrix {
self.config.get_pattern_data(tracker_id) self.config.get_pattern_data(tracker_id)
} }
pub fn get_scope_buffer(&self, scope: usize) -> Option<UnsyncFloatBuf> { pub fn get_scope_buffers(&self, scope: usize) -> Option<[UnsyncFloatBuf; 3]> {
self.config.get_scope_buffer(scope) self.config.get_scope_buffers(scope)
} }
/// Checks if pattern data updates need to be sent to the /// Checks if pattern data updates need to be sent to the

View file

@ -3,7 +3,7 @@
// See README.md and COPYING for details. // See README.md and COPYING for details.
pub const MAX_ALLOCATED_NODES: usize = 256; 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 SCOPE_SAMPLES: usize = 512;
pub const MAX_INPUTS: usize = 32; pub const MAX_INPUTS: usize = 32;
pub const MAX_SMOOTHERS: usize = 36 + 4; // 6 * 6 modulator inputs + 4 UI Knobs pub const MAX_SMOOTHERS: usize = 36 + 4; // 6 * 6 modulator inputs + 4 UI Knobs

View file

@ -3,15 +3,15 @@
// See README.md and COPYING for details. // See README.md and COPYING for details.
use super::{ use super::{
FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, MAX_AVAIL_TRACKERS,
MAX_AVAIL_TRACKERS, MAX_INPUTS, UNUSED_MONITOR_IDX, MAX_SCOPES, SCOPE_SAMPLES MAX_INPUTS, MAX_SCOPES, SCOPE_SAMPLES, UNUSED_MONITOR_IDX,
}; };
use crate::dsp::tracker::{PatternData, Tracker}; use crate::dsp::tracker::{PatternData, Tracker};
use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom}; use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom};
use crate::monitor::{new_monitor_processor, MinMaxMonitorSamples, Monitor, MON_SIG_CNT}; use crate::monitor::{new_monitor_processor, MinMaxMonitorSamples, Monitor, MON_SIG_CNT};
use crate::nodes::drop_thread::DropThread; use crate::nodes::drop_thread::DropThread;
use crate::util::AtomicFloat;
use crate::unsync_float_buf::UnsyncFloatBuf; use crate::unsync_float_buf::UnsyncFloatBuf;
use crate::util::AtomicFloat;
use crate::SampleLibrary; use crate::SampleLibrary;
use ringbuf::{Producer, RingBuffer}; use ringbuf::{Producer, RingBuffer};
@ -179,7 +179,7 @@ pub struct NodeConfigurator {
/// Holding the tracker sequencers /// Holding the tracker sequencers
pub(crate) trackers: Vec<Tracker>, pub(crate) trackers: Vec<Tracker>,
/// Holding the scope buffers: /// Holding the scope buffers:
pub(crate) scopes: Vec<UnsyncFloatBuf>, pub(crate) scopes: Vec<[UnsyncFloatBuf; 3]>,
/// The shared parts of the [NodeConfigurator] /// The shared parts of the [NodeConfigurator]
/// and the [crate::nodes::NodeExecutor]. /// and the [crate::nodes::NodeExecutor].
pub(crate) shared: SharedNodeConf, pub(crate) shared: SharedNodeConf,
@ -282,7 +282,14 @@ impl NodeConfigurator {
atom_values: std::collections::HashMap::new(), atom_values: std::collections::HashMap::new(),
node2idx: HashMap::new(), node2idx: HashMap::new(),
trackers: vec![Tracker::new(); MAX_AVAIL_TRACKERS], 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, shared_exec,
) )
@ -642,7 +649,7 @@ impl NodeConfigurator {
} }
} }
pub fn get_scope_buffer(&self, scope: usize) -> Option<UnsyncFloatBuf> { pub fn get_scope_buffers(&self, scope: usize) -> Option<[UnsyncFloatBuf; 3]> {
self.scopes.get(scope).cloned() self.scopes.get(scope).cloned()
} }
@ -687,7 +694,7 @@ impl NodeConfigurator {
if let Node::Scope { node } = &mut node { if let Node::Scope { node } = &mut node {
if let Some(buf) = self.scopes.get(ni.instance()) { if let Some(buf) = self.scopes.get(ni.instance()) {
node.set_scope_buffer(buf.clone()); node.set_scope_buffers(buf.clone());
} }
} }

View file

@ -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)] #[derive(Debug)]
struct UnsyncFloatBufImpl { struct UnsyncFloatBufImpl {
data_store: Vec<AtomicFloat>, data_store: Vec<AtomicFloat>,
@ -65,18 +69,23 @@ unsafe impl Sync for UnsyncFloatBuf {}
unsafe impl Send for UnsyncFloatBuf {} unsafe impl Send for UnsyncFloatBuf {}
impl UnsyncFloatBufImpl { 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<Self> { fn new_shared(len: usize) -> Arc<Self> {
let mut rc = Arc::new(Self { data_store: Vec::new(), len, ptr: std::ptr::null_mut() }); 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"); 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)); 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. // because it will not be moved when inside the Arc.
unsync_buf.ptr = unsync_buf.data_store.as_mut_ptr(); unsync_buf.ptr = unsync_buf.data_store.as_mut_ptr();
rc rc
} }
/// Write a sample.
fn write(&self, idx: usize, v: f32) { fn write(&self, idx: usize, v: f32) {
if idx < self.len { if idx < self.len {
unsafe { unsafe {
@ -85,6 +94,7 @@ impl UnsyncFloatBufImpl {
} }
} }
/// Read a sample.
fn read(&self, idx: usize) -> f32 { fn read(&self, idx: usize) -> f32 {
if idx < self.len { if idx < self.len {
unsafe { (*self.ptr.add(idx)).get() } unsafe { (*self.ptr.add(idx)).get() }
@ -93,6 +103,7 @@ impl UnsyncFloatBufImpl {
} }
} }
/// Return the length of this buffer.
fn len(&self) -> usize { fn len(&self) -> usize {
self.len self.len
} }

View file

@ -13,20 +13,35 @@ fn check_node_scope_1() {
let mut matrix = Matrix::new(node_conf, 3, 3); let mut matrix = Matrix::new(node_conf, 3, 3);
let mut chain = MatrixCellChain::new(CellDir::B); 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(); matrix.sync().unwrap();
let scope = NodeId::Scope(0); 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 _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![]; let mut v = vec![];
for x in 0..SCOPE_SAMPLES { 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]); assert_decimated_feq!(v, 80, vec![0.0022, 0.1836, 0.3650, 0.5464, 0.7278, 0.9093, 1.0]);
} }