From db906e7c2be184fbb3106db2fc4d70ccd6c8782c Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Wed, 27 Jul 2022 02:02:54 +0200 Subject: [PATCH] converted scope_handle to store min/max values for a proper scope --- src/dsp/mod.rs | 2 +- src/dsp/node_scope.rs | 71 +++++++++++++++++++++++++++++-------------- src/scope_handle.rs | 18 +++++------ src/util.rs | 64 +++++++++++++++++++++++++++++++++++++- 4 files changed, 121 insertions(+), 34 deletions(-) diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index f1c4fb4..6972f2a 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -1437,7 +1437,7 @@ macro_rules! node_list { (9 gain1 n_xgin d_xgin r_id f_def stp_d 0.0, 1.0, 1.0) (10 gain2 n_xgin d_xgin r_id f_def stp_d 0.0, 1.0, 1.0) (11 gain3 n_xgin d_xgin r_id f_def stp_d 0.0, 1.0, 1.0) - {12 0 tsrc setting(0) mode fa_scope_tsrc 0 1}, + {12 0 tsrc setting(0) mode fa_scope_tsrc 0 2}, 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 3dc0788..ea6bce1 100644 --- a/src/dsp/node_scope.rs +++ b/src/dsp/node_scope.rs @@ -35,6 +35,7 @@ pub struct Scope { idx: usize, frame_time: f32, srate_ms: f32, + cur_mm: Box<[(f32, f32); 3]>, trig: CustomTrigger, } @@ -45,6 +46,7 @@ impl Scope { idx: 0, srate_ms: 44.1, frame_time: 0.0, + cur_mm: Box::new([(0.0, 0.0); 3]), trig: CustomTrigger::new(0.0, 0.0001), } } @@ -64,7 +66,7 @@ impl Scope { "Scope gain2\nVisual amplification/attenuation of the signal input 2.\nRange: (0..1)\n"; pub const gain3: &'static str = "Scope gain3\nVisual amplification/attenuation of the signal input 3.\nRange: (0..1)\n"; - pub const tsrc: &'static str = "Scope tsrc\nTriggering allows you to capture fast signals or pinning fast waveforms into the scope view for better inspection.\nRange: (-1..1)\n"; + pub const tsrc: &'static str = "Scope tsrc\nTriggering allows you to capture fast signals or pinning fast waveforms into the scope view for better inspection. You can let the scope freeze and manually recapture waveforms by setting 'tsrc' to 'Extern' and hitting the 'trig' button manually.\nRange: (-1..1)\n"; pub const DESC: &'static str = r#"Signal Oscilloscope Probe This is a signal oscilloscope probe node, you can capture up to 3 signals. You can enable internal or external triggering for capturing signals or pinning fast waveforms. @@ -76,13 +78,15 @@ in record up to 24 signals for displaying them in the scope view. The received signal will be forwarded to the GUI and you can inspect the waveform there. -You can enable an internal trigger with the 'tsrc'. The 'thrsh' parameter -is the trigger detection parameter. That means, if your signal passes that -trigger from negative to positive, the signal recording will be -reset to that point. +You can enable an internal trigger with the 'tsrc' setting set to 'Intern'. +'Intern' here means that the signal input 1 'in1' is used as trigger signal. +The 'thrsh' parameter is the trigger detection parameter. That means, if your +signal passes that threshold in negative to positive direction, the signal +recording will be reset to that point. You can also route in an external trigger to capture signals with the 'trig' -input and 'tsrc' set to 'Extern'. +input and 'tsrc' set to 'Extern'. Of course you can also hit the 'trig' button +manually to recapture a waveform. The inputs 'off1', 'off2' and 'off3' define a vertical offset of the signal waveform in the scope view. Use 'gain1', 'gain2' and 'gain3' for scaling @@ -111,30 +115,34 @@ impl DspNode for Scope { ctx: &mut T, _ectx: &mut NodeExecContext, nctx: &NodeContext, - _atoms: &[SAtom], + atoms: &[SAtom], inputs: &[ProcBuf], _outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals, ) { - use crate::dsp::{denorm, inp}; + use crate::dsp::{at, denorm, inp}; let in1 = inp::Scope::in1(inputs); let in2 = inp::Scope::in2(inputs); let in3 = inp::Scope::in3(inputs); let time = inp::Scope::time(inputs); let thrsh = inp::Scope::thrsh(inputs); + let trig = inp::Scope::trig(inputs); + let tsrc = at::Scope::tsrc(atoms); let inputs = [in1, in2, in3]; self.handle.set_active_from_mask(nctx.in_connected); - let time = denorm::Scope::time(time, 0); + let time = denorm::Scope::time(time, 0).clamp(0.1, 1000.0 * 300.0); let samples_per_block = (time * self.srate_ms) / SCOPE_SAMPLES as f32; let time_per_block = time / SCOPE_SAMPLES as f32; let sample_time = 1.0 / self.srate_ms; let threshold = denorm::Scope::thrsh(thrsh, 0); + self.trig.set_threshold(threshold, threshold + 0.001); - let trigger_input = in1; + let trigger_input = if tsrc.i() == 2 { trig } else { in1 }; + let trigger_disabled = tsrc.i() == 0; //d// println!("TIME time={}; st={}; tpb={}; frame_time={}", time, sample_time, time_per_block, self.frame_time); if samples_per_block < 1.0 { @@ -144,28 +152,39 @@ impl DspNode for Scope { if self.idx < SCOPE_SAMPLES { for (i, input) in inputs.iter().enumerate() { let in_val = input.read(frame); - self.handle.write_copies(i, self.idx, copy_count, in_val); + self.handle.write_oversampled(i, self.idx, copy_count, in_val); } self.idx = self.idx.saturating_add(copy_count); } - if self.idx >= SCOPE_SAMPLES && self.trig.check_trigger(trigger_input.read(frame)) { - self.frame_time = 0.0; - self.idx = 0; + if self.idx >= SCOPE_SAMPLES { + if self.trig.check_trigger(trigger_input.read(frame)) { + self.frame_time = 0.0; + self.idx = 0; + } else if trigger_disabled { + self.frame_time = 0.0; + self.idx = 0; + } } } } else { -// let samples_per_block = samples_per_block as usize; + let cur_mm = self.cur_mm.as_mut(); + // let samples_per_block = samples_per_block as usize; for frame in 0..ctx.nframes() { if self.idx < SCOPE_SAMPLES { - if self.frame_time >= time_per_block { - for (i, input) in inputs.iter().enumerate() { - let in_val = input.read(frame); - self.handle.write(i, self.idx, in_val); - } + for (i, input) in inputs.iter().enumerate() { + let in_val = input.read(frame); + cur_mm[i].0 = cur_mm[i].0.max(in_val); + cur_mm[i].1 = cur_mm[i].1.min(in_val); + } + if self.frame_time >= time_per_block { + for i in 0..inputs.len() { + self.handle.write(i, self.idx, cur_mm[i]); + } + *cur_mm = [(-99999.0, 99999.0); 3]; self.idx = self.idx.saturating_add(1); self.frame_time -= time_per_block; } @@ -173,9 +192,15 @@ impl DspNode for Scope { self.frame_time += sample_time; } - if self.idx >= SCOPE_SAMPLES && self.trig.check_trigger(trigger_input.read(frame)) { - self.frame_time = 0.0; - self.idx = 0; + if self.idx >= SCOPE_SAMPLES { + if self.trig.check_trigger(trigger_input.read(frame)) { + *cur_mm = [(-99999.0, 99999.0); 3]; + self.frame_time = 0.0; + self.idx = 0; + } else if trigger_disabled { + *cur_mm = [(-99999.0, 99999.0); 3]; + self.idx = 0; + } } } } diff --git a/src/scope_handle.rs b/src/scope_handle.rs index 3f8cb75..6c1e99a 100644 --- a/src/scope_handle.rs +++ b/src/scope_handle.rs @@ -3,42 +3,42 @@ // See README.md and COPYING for details. use crate::nodes::SCOPE_SAMPLES; -use crate::util::AtomicFloat; +use crate::util::AtomicFloatPair; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; #[derive(Debug)] pub struct ScopeHandle { - bufs: [Vec; 3], + bufs: [Vec; 3], active: [AtomicBool; 3], } impl ScopeHandle { pub fn new_shared() -> Arc { let mut v1 = vec![]; - v1.resize_with(SCOPE_SAMPLES, || AtomicFloat::new(0.0)); + v1.resize_with(SCOPE_SAMPLES, || AtomicFloatPair::default()); let mut v2 = vec![]; - v2.resize_with(SCOPE_SAMPLES, || AtomicFloat::new(0.0)); + v2.resize_with(SCOPE_SAMPLES, || AtomicFloatPair::default()); let mut v3 = vec![]; - v3.resize_with(SCOPE_SAMPLES, || AtomicFloat::new(0.0)); + v3.resize_with(SCOPE_SAMPLES, || AtomicFloatPair::default()); Arc::new(Self { bufs: [v1, v2, v3], active: [AtomicBool::new(false), AtomicBool::new(false), AtomicBool::new(false)], }) } - pub fn write_copies(&self, buf_idx: usize, idx: usize, copies: usize, v: f32) { + pub fn write_oversampled(&self, buf_idx: usize, idx: usize, copies: usize, v: f32) { let end = (idx + copies).min(SCOPE_SAMPLES); for i in idx..end { - self.bufs[buf_idx % 3][i % SCOPE_SAMPLES].set(v); + self.bufs[buf_idx % 3][i % SCOPE_SAMPLES].set((v, v)); } } - pub fn write(&self, buf_idx: usize, idx: usize, v: f32) { + pub fn write(&self, buf_idx: usize, idx: usize, v: (f32, f32)) { self.bufs[buf_idx % 3][idx % SCOPE_SAMPLES].set(v); } - pub fn read(&self, buf_idx: usize, idx: usize) -> f32 { + pub fn read(&self, buf_idx: usize, idx: usize) -> (f32, f32) { self.bufs[buf_idx % 3][idx % SCOPE_SAMPLES].get() } diff --git a/src/util.rs b/src/util.rs index 4ae966d..6ccb4fc 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; const SMOOTHING_TIME_MS: f32 = 10.0; @@ -152,3 +152,65 @@ impl From for f32 { value.get() } } + +/// The AtomicFloatPair can store two `f32` numbers atomically. +/// +/// This is useful for storing eg. min and max values of a sampled signal. +pub struct AtomicFloatPair { + atomic: AtomicU64, +} + +impl AtomicFloatPair { + /// New atomic float with initial value `value`. + pub fn new(v: (f32, f32)) -> AtomicFloatPair { + AtomicFloatPair { + atomic: AtomicU64::new(((v.0.to_bits() as u64) << 32) | (v.1.to_bits() as u64)), + } + } + + /// Get the current value of the atomic float. + #[inline] + pub fn get(&self) -> (f32, f32) { + let v = self.atomic.load(Ordering::Relaxed); + (f32::from_bits((v >> 32 & 0xFFFFFFFF) as u32), f32::from_bits((v & 0xFFFFFFFF) as u32)) + } + + /// Set the value of the atomic float to `value`. + #[inline] + pub fn set(&self, v: (f32, f32)) { + let v = ((v.0.to_bits() as u64) << 32) | (v.1.to_bits()) as u64; + self.atomic.store(v, Ordering::Relaxed) + } +} + +impl Default for AtomicFloatPair { + fn default() -> Self { + AtomicFloatPair::new((0.0, 0.0)) + } +} + +impl std::fmt::Debug for AtomicFloatPair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let v = self.get(); + write!(f, "({}, {})", v.0, v.1) + } +} + +impl std::fmt::Display for AtomicFloatPair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let v = self.get(); + write!(f, "({}, {})", v.0, v.1) + } +} + +impl From<(f32, f32)> for AtomicFloatPair { + fn from(value: (f32, f32)) -> Self { + AtomicFloatPair::new((value.0, value.1)) + } +} + +impl From for (f32, f32) { + fn from(value: AtomicFloatPair) -> Self { + value.get() + } +}