converted scope_handle to store min/max values for a proper scope

This commit is contained in:
Weird Constructor 2022-07-27 02:02:54 +02:00
parent 30284e27dc
commit db906e7c2b
4 changed files with 121 additions and 34 deletions

View file

@ -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)

View file

@ -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;
}
}
}
}

View file

@ -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<AtomicFloat>; 3],
bufs: [Vec<AtomicFloatPair>; 3],
active: [AtomicBool; 3],
}
impl ScopeHandle {
pub fn new_shared() -> Arc<Self> {
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()
}

View file

@ -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<AtomicFloat> 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<AtomicFloatPair> for (f32, f32) {
fn from(value: AtomicFloatPair) -> Self {
value.get()
}
}