converted scope_handle to store min/max values for a proper scope
This commit is contained in:
parent
30284e27dc
commit
db906e7c2b
4 changed files with 121 additions and 34 deletions
|
@ -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)
|
(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)
|
(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)
|
(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
|
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)
|
||||||
|
|
|
@ -35,6 +35,7 @@ pub struct Scope {
|
||||||
idx: usize,
|
idx: usize,
|
||||||
frame_time: f32,
|
frame_time: f32,
|
||||||
srate_ms: f32,
|
srate_ms: f32,
|
||||||
|
cur_mm: Box<[(f32, f32); 3]>,
|
||||||
trig: CustomTrigger,
|
trig: CustomTrigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ impl Scope {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
srate_ms: 44.1,
|
srate_ms: 44.1,
|
||||||
frame_time: 0.0,
|
frame_time: 0.0,
|
||||||
|
cur_mm: Box::new([(0.0, 0.0); 3]),
|
||||||
trig: CustomTrigger::new(0.0, 0.0001),
|
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";
|
"Scope gain2\nVisual amplification/attenuation of the signal input 2.\nRange: (0..1)\n";
|
||||||
pub const gain3: &'static str =
|
pub const gain3: &'static str =
|
||||||
"Scope gain3\nVisual amplification/attenuation of the signal input 3.\nRange: (0..1)\n";
|
"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
|
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.
|
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 received signal will be forwarded to the GUI and you can inspect
|
||||||
the waveform there.
|
the waveform there.
|
||||||
|
|
||||||
You can enable an internal trigger with the 'tsrc'. The 'thrsh' parameter
|
You can enable an internal trigger with the 'tsrc' setting set to 'Intern'.
|
||||||
is the trigger detection parameter. That means, if your signal passes that
|
'Intern' here means that the signal input 1 'in1' is used as trigger signal.
|
||||||
trigger from negative to positive, the signal recording will be
|
The 'thrsh' parameter is the trigger detection parameter. That means, if your
|
||||||
reset to that point.
|
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'
|
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
|
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
|
waveform in the scope view. Use 'gain1', 'gain2' and 'gain3' for scaling
|
||||||
|
@ -111,30 +115,34 @@ impl DspNode for Scope {
|
||||||
ctx: &mut T,
|
ctx: &mut T,
|
||||||
_ectx: &mut NodeExecContext,
|
_ectx: &mut NodeExecContext,
|
||||||
nctx: &NodeContext,
|
nctx: &NodeContext,
|
||||||
_atoms: &[SAtom],
|
atoms: &[SAtom],
|
||||||
inputs: &[ProcBuf],
|
inputs: &[ProcBuf],
|
||||||
_outputs: &mut [ProcBuf],
|
_outputs: &mut [ProcBuf],
|
||||||
ctx_vals: LedPhaseVals,
|
ctx_vals: LedPhaseVals,
|
||||||
) {
|
) {
|
||||||
use crate::dsp::{denorm, inp};
|
use crate::dsp::{at, denorm, inp};
|
||||||
|
|
||||||
let in1 = inp::Scope::in1(inputs);
|
let in1 = inp::Scope::in1(inputs);
|
||||||
let in2 = inp::Scope::in2(inputs);
|
let in2 = inp::Scope::in2(inputs);
|
||||||
let in3 = inp::Scope::in3(inputs);
|
let in3 = inp::Scope::in3(inputs);
|
||||||
let time = inp::Scope::time(inputs);
|
let time = inp::Scope::time(inputs);
|
||||||
let thrsh = inp::Scope::thrsh(inputs);
|
let thrsh = inp::Scope::thrsh(inputs);
|
||||||
|
let trig = inp::Scope::trig(inputs);
|
||||||
|
let tsrc = at::Scope::tsrc(atoms);
|
||||||
let inputs = [in1, in2, in3];
|
let inputs = [in1, in2, in3];
|
||||||
|
|
||||||
self.handle.set_active_from_mask(nctx.in_connected);
|
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 samples_per_block = (time * self.srate_ms) / SCOPE_SAMPLES as f32;
|
||||||
let time_per_block = time / SCOPE_SAMPLES as f32;
|
let time_per_block = time / SCOPE_SAMPLES as f32;
|
||||||
let sample_time = 1.0 / self.srate_ms;
|
let sample_time = 1.0 / self.srate_ms;
|
||||||
let threshold = denorm::Scope::thrsh(thrsh, 0);
|
let threshold = denorm::Scope::thrsh(thrsh, 0);
|
||||||
|
|
||||||
self.trig.set_threshold(threshold, threshold + 0.001);
|
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);
|
//d// println!("TIME time={}; st={}; tpb={}; frame_time={}", time, sample_time, time_per_block, self.frame_time);
|
||||||
if samples_per_block < 1.0 {
|
if samples_per_block < 1.0 {
|
||||||
|
@ -144,28 +152,39 @@ impl DspNode for Scope {
|
||||||
if self.idx < SCOPE_SAMPLES {
|
if self.idx < SCOPE_SAMPLES {
|
||||||
for (i, input) in inputs.iter().enumerate() {
|
for (i, input) in inputs.iter().enumerate() {
|
||||||
let in_val = input.read(frame);
|
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);
|
self.idx = self.idx.saturating_add(copy_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.idx >= SCOPE_SAMPLES && self.trig.check_trigger(trigger_input.read(frame)) {
|
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.frame_time = 0.0;
|
||||||
self.idx = 0;
|
self.idx = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let cur_mm = self.cur_mm.as_mut();
|
||||||
// let samples_per_block = samples_per_block as usize;
|
// let samples_per_block = samples_per_block as usize;
|
||||||
|
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
if self.idx < SCOPE_SAMPLES {
|
if self.idx < SCOPE_SAMPLES {
|
||||||
if self.frame_time >= time_per_block {
|
|
||||||
for (i, input) in inputs.iter().enumerate() {
|
for (i, input) in inputs.iter().enumerate() {
|
||||||
let in_val = input.read(frame);
|
let in_val = input.read(frame);
|
||||||
self.handle.write(i, self.idx, in_val);
|
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.idx = self.idx.saturating_add(1);
|
||||||
self.frame_time -= time_per_block;
|
self.frame_time -= time_per_block;
|
||||||
}
|
}
|
||||||
|
@ -173,9 +192,15 @@ impl DspNode for Scope {
|
||||||
self.frame_time += sample_time;
|
self.frame_time += sample_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.idx >= SCOPE_SAMPLES && self.trig.check_trigger(trigger_input.read(frame)) {
|
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.frame_time = 0.0;
|
||||||
self.idx = 0;
|
self.idx = 0;
|
||||||
|
} else if trigger_disabled {
|
||||||
|
*cur_mm = [(-99999.0, 99999.0); 3];
|
||||||
|
self.idx = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,42 +3,42 @@
|
||||||
// See README.md and COPYING for details.
|
// See README.md and COPYING for details.
|
||||||
|
|
||||||
use crate::nodes::SCOPE_SAMPLES;
|
use crate::nodes::SCOPE_SAMPLES;
|
||||||
use crate::util::AtomicFloat;
|
use crate::util::AtomicFloatPair;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ScopeHandle {
|
pub struct ScopeHandle {
|
||||||
bufs: [Vec<AtomicFloat>; 3],
|
bufs: [Vec<AtomicFloatPair>; 3],
|
||||||
active: [AtomicBool; 3],
|
active: [AtomicBool; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScopeHandle {
|
impl ScopeHandle {
|
||||||
pub fn new_shared() -> Arc<Self> {
|
pub fn new_shared() -> Arc<Self> {
|
||||||
let mut v1 = vec![];
|
let mut v1 = vec![];
|
||||||
v1.resize_with(SCOPE_SAMPLES, || AtomicFloat::new(0.0));
|
v1.resize_with(SCOPE_SAMPLES, || AtomicFloatPair::default());
|
||||||
let mut v2 = vec![];
|
let mut v2 = vec![];
|
||||||
v2.resize_with(SCOPE_SAMPLES, || AtomicFloat::new(0.0));
|
v2.resize_with(SCOPE_SAMPLES, || AtomicFloatPair::default());
|
||||||
let mut v3 = vec![];
|
let mut v3 = vec![];
|
||||||
v3.resize_with(SCOPE_SAMPLES, || AtomicFloat::new(0.0));
|
v3.resize_with(SCOPE_SAMPLES, || AtomicFloatPair::default());
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
bufs: [v1, v2, v3],
|
bufs: [v1, v2, v3],
|
||||||
active: [AtomicBool::new(false), AtomicBool::new(false), AtomicBool::new(false)],
|
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);
|
let end = (idx + copies).min(SCOPE_SAMPLES);
|
||||||
for i in idx..end {
|
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);
|
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()
|
self.bufs[buf_idx % 3][idx % SCOPE_SAMPLES].get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
64
src/util.rs
64
src/util.rs
|
@ -2,7 +2,7 @@
|
||||||
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
||||||
// See README.md and COPYING for details.
|
// 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;
|
const SMOOTHING_TIME_MS: f32 = 10.0;
|
||||||
|
|
||||||
|
@ -152,3 +152,65 @@ impl From<AtomicFloat> for f32 {
|
||||||
value.get()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue