Improved documentation of the helpers

This commit is contained in:
Weird Constructor 2022-07-20 05:40:41 +02:00
parent 2d1b989880
commit 2febe4a7e8

View file

@ -395,10 +395,21 @@ pub fn f_fold_distort(gain: f32, threshold: f32, i: f32) -> f32 {
} }
} }
/// Apply linear interpolation between the value a and b.
///
/// * `a` - value at x=0.0
/// * `b` - value at x=1.0
/// * `x` - value between 0.0 and 1.0 to blend between `a` and `b`.
#[inline]
pub fn lerp(x: f32, a: f32, b: f32) -> f32 { pub fn lerp(x: f32, a: f32, b: f32) -> f32 {
(a * (1.0 - x)) + (b * x) (a * (1.0 - x)) + (b * x)
} }
/// Apply 64bit linear interpolation between the value a and b.
///
/// * `a` - value at x=0.0
/// * `b` - value at x=1.0
/// * `x` - value between 0.0 and 1.0 to blend between `a` and `b`.
pub fn lerp64(x: f64, a: f64, b: f64) -> f64 { pub fn lerp64(x: f64, a: f64, b: f64) -> f64 {
(a * (1.0 - x)) + (b * x) (a * (1.0 - x)) + (b * x)
} }
@ -558,6 +569,10 @@ pub const TRIG_LOW_THRES: f32 = 0.25;
/// a logical '1'. Anything below this is a logical '0'. /// a logical '1'. Anything below this is a logical '0'.
pub const TRIG_HIGH_THRES: f32 = 0.5; pub const TRIG_HIGH_THRES: f32 = 0.5;
/// Trigger signal generator for HexoDSP nodes.
///
/// A trigger in HexoSynth and HexoDSP is commonly 2.0 milliseconds.
/// This generator generates a trigger signal when [TrigSignal::trigger] is called.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct TrigSignal { pub struct TrigSignal {
length: u32, length: u32,
@ -565,24 +580,29 @@ pub struct TrigSignal {
} }
impl TrigSignal { impl TrigSignal {
/// Create a new trigger generator
pub fn new() -> Self { pub fn new() -> Self {
Self { length: ((44100.0 * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32, scount: 0 } Self { length: ((44100.0 * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32, scount: 0 }
} }
/// Reset the trigger generator.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.scount = 0; self.scount = 0;
} }
/// Set the sample rate to calculate the amount of samples for the trigger signal.
pub fn set_sample_rate(&mut self, srate: f32) { pub fn set_sample_rate(&mut self, srate: f32) {
self.length = ((srate * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32; self.length = ((srate * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32;
self.scount = 0; self.scount = 0;
} }
/// Enable sending a trigger impulse the next time [TrigSignal::next] is called.
#[inline] #[inline]
pub fn trigger(&mut self) { pub fn trigger(&mut self) {
self.scount = self.length; self.scount = self.length;
} }
/// Trigger signal output.
#[inline] #[inline]
pub fn next(&mut self) -> f32 { pub fn next(&mut self) -> f32 {
if self.scount > 0 { if self.scount > 0 {
@ -600,6 +620,9 @@ impl Default for TrigSignal {
} }
} }
/// Signal change detector that emits a trigger when the input signal changed.
///
/// This is commonly used for control signals. It has not much use for audio signals.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct ChangeTrig { pub struct ChangeTrig {
ts: TrigSignal, ts: TrigSignal,
@ -607,6 +630,7 @@ pub struct ChangeTrig {
} }
impl ChangeTrig { impl ChangeTrig {
/// Create a new change detector
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
ts: TrigSignal::new(), ts: TrigSignal::new(),
@ -614,15 +638,20 @@ impl ChangeTrig {
} }
} }
/// Reset internal state.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.ts.reset(); self.ts.reset();
self.last = -100.0; self.last = -100.0;
} }
/// Set the sample rate for the trigger signal generator
pub fn set_sample_rate(&mut self, srate: f32) { pub fn set_sample_rate(&mut self, srate: f32) {
self.ts.set_sample_rate(srate); self.ts.set_sample_rate(srate);
} }
/// Feed a new input signal sample.
///
/// The return value is the trigger signal.
#[inline] #[inline]
pub fn next(&mut self, inp: f32) -> f32 { pub fn next(&mut self, inp: f32) -> f32 {
if (inp - self.last).abs() > std::f32::EPSILON { if (inp - self.last).abs() > std::f32::EPSILON {
@ -640,21 +669,30 @@ impl Default for ChangeTrig {
} }
} }
/// Trigger signal detector for HexoDSP.
///
/// Whenever you need to detect a trigger on an input you can use this component.
/// A trigger in HexoDSP is any signal over [TRIG_HIGH_THRES]. The internal state is
/// resetted when the signal drops below [TRIG_LOW_THRES].
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Trigger { pub struct Trigger {
triggered: bool, triggered: bool,
} }
impl Trigger { impl Trigger {
/// Create a new trigger detector.
pub fn new() -> Self { pub fn new() -> Self {
Self { triggered: false } Self { triggered: false }
} }
/// Reset the internal state of the trigger detector.
#[inline] #[inline]
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.triggered = false; self.triggered = false;
} }
/// Checks the input signal for a trigger and returns true when the signal
/// surpassed [TRIG_HIGH_THRES] and has not fallen below [TRIG_LOW_THRES] yet.
#[inline] #[inline]
pub fn check_trigger(&mut self, input: f32) -> bool { pub fn check_trigger(&mut self, input: f32) -> bool {
if self.triggered { if self.triggered {
@ -672,6 +710,10 @@ impl Trigger {
} }
} }
/// Generates a phase signal from a trigger/gate input signal.
///
/// This helper allows you to measure the distance between trigger or gate pulses
/// and generates a phase signal for you that increases from 0.0 to 1.0.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct TriggerPhaseClock { pub struct TriggerPhaseClock {
clock_phase: f64, clock_phase: f64,
@ -681,10 +723,12 @@ pub struct TriggerPhaseClock {
} }
impl TriggerPhaseClock { impl TriggerPhaseClock {
/// Create a new phase clock.
pub fn new() -> Self { pub fn new() -> Self {
Self { clock_phase: 0.0, clock_inc: 0.0, prev_trigger: true, clock_samples: 0 } Self { clock_phase: 0.0, clock_inc: 0.0, prev_trigger: true, clock_samples: 0 }
} }
/// Reset the phase clock.
#[inline] #[inline]
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.clock_samples = 0; self.clock_samples = 0;
@ -693,11 +737,16 @@ impl TriggerPhaseClock {
self.clock_samples = 0; self.clock_samples = 0;
} }
/// Restart the phase clock. It will count up from 0.0 again on [TriggerPhaseClock::next_phase].
#[inline] #[inline]
pub fn sync(&mut self) { pub fn sync(&mut self) {
self.clock_phase = 0.0; self.clock_phase = 0.0;
} }
/// Generate the phase signal of this clock.
///
/// * `clock_limit` - The maximum number of samples to detect two trigger signals in.
/// * `trigger_in` - Trigger signal input.
#[inline] #[inline]
pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 { pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 {
if self.prev_trigger { if self.prev_trigger {
@ -896,6 +945,8 @@ fn fclampc<F: Flt>(x: F, mi: f64, mx: f64) -> F {
/// _len_ is the buffer length to consider and wrap the index into. And _fract_ is the /// _len_ is the buffer length to consider and wrap the index into. And _fract_ is the
/// fractional part of the index. /// fractional part of the index.
/// ///
/// This function is generic over f32 and f64. That means you can use your preferred float size.
///
/// Commonly used like this: /// Commonly used like this:
/// ///
///``` ///```
@ -958,6 +1009,11 @@ pub fn cubic_interpolate<F: Flt>(data: &[F], len: usize, index: usize, fract: F)
res res
} }
/// This is a delay buffer/line with linear and cubic interpolation.
///
/// It's the basic building block underneath the all-pass filter, comb filters and delay effects.
/// You can use linear and cubic and no interpolation to access samples in the past. Either
/// by sample offset or time (millisecond) based.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct DelayBuffer<F: Flt> { pub struct DelayBuffer<F: Flt> {
data: Vec<F>, data: Vec<F>,
@ -966,18 +1022,22 @@ pub struct DelayBuffer<F: Flt> {
} }
impl<F: Flt> DelayBuffer<F> { impl<F: Flt> DelayBuffer<F> {
/// Creates a delay buffer with about 5 seconds of capacity at 8*48000Hz sample rate.
pub fn new() -> Self { pub fn new() -> Self {
Self { data: vec![f(0.0); DEFAULT_DELAY_BUFFER_SAMPLES], wr: 0, srate: f(44100.0) } Self { data: vec![f(0.0); DEFAULT_DELAY_BUFFER_SAMPLES], wr: 0, srate: f(44100.0) }
} }
/// Creates a delay buffer with the given amount of samples capacity.
pub fn new_with_size(size: usize) -> Self { pub fn new_with_size(size: usize) -> Self {
Self { data: vec![f(0.0); size], wr: 0, srate: f(44100.0) } Self { data: vec![f(0.0); size], wr: 0, srate: f(44100.0) }
} }
/// Sets the sample rate that is used for milliseconds => sample conversion.
pub fn set_sample_rate(&mut self, srate: F) { pub fn set_sample_rate(&mut self, srate: F) {
self.srate = srate; self.srate = srate;
} }
/// Reset the delay buffer contents and write position.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.data.fill(f(0.0)); self.data.fill(f(0.0));
self.wr = 0; self.wr = 0;
@ -1037,7 +1097,7 @@ impl<F: Flt> DelayBuffer<F> {
self.linear_interpolate_at(delay_time_ms) self.linear_interpolate_at(delay_time_ms)
} }
/// Fetch a sample from the delay buffer at the given time. /// Fetch a sample from the delay buffer at the given tim with linear interpolation.
/// ///
/// * `delay_time_ms` - Delay time in milliseconds. /// * `delay_time_ms` - Delay time in milliseconds.
#[inline] #[inline]
@ -1045,7 +1105,7 @@ impl<F: Flt> DelayBuffer<F> {
self.linear_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0)) self.linear_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0))
} }
/// Fetch a sample from the delay buffer at the given offset. /// Fetch a sample from the delay buffer at the given offset with linear interpolation.
/// ///
/// * `s_offs` - Sample offset in samples. /// * `s_offs` - Sample offset in samples.
#[inline] #[inline]
@ -1072,7 +1132,7 @@ impl<F: Flt> DelayBuffer<F> {
res res
} }
/// Fetch a sample from the delay buffer at the given time. /// Fetch a sample from the delay buffer at the given time with cubic interpolation.
/// ///
/// * `delay_time_ms` - Delay time in milliseconds. /// * `delay_time_ms` - Delay time in milliseconds.
#[inline] #[inline]
@ -1080,7 +1140,7 @@ impl<F: Flt> DelayBuffer<F> {
self.cubic_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0)) self.cubic_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0))
} }
/// Fetch a sample from the delay buffer at the given offset. /// Fetch a sample from the delay buffer at the given offset with cubic interpolation.
/// ///
/// * `s_offs` - Sample offset in samples into the past of the [DelayBuffer] /// * `s_offs` - Sample offset in samples into the past of the [DelayBuffer]
/// from the current write (or the "now") position. /// from the current write (or the "now") position.
@ -1106,6 +1166,9 @@ impl<F: Flt> DelayBuffer<F> {
res res
} }
/// Fetch a sample from the delay buffer at the given time without any interpolation.
///
/// * `delay_time_ms` - Delay time in milliseconds.
#[inline] #[inline]
pub fn nearest_at(&self, delay_time_ms: F) -> F { pub fn nearest_at(&self, delay_time_ms: F) -> F {
let len = self.data.len(); let len = self.data.len();
@ -1116,6 +1179,7 @@ impl<F: Flt> DelayBuffer<F> {
self.data[idx] self.data[idx]
} }
/// Fetch a sample from the delay buffer at the given number of samples in the past.
#[inline] #[inline]
pub fn at(&self, delay_sample_count: usize) -> F { pub fn at(&self, delay_sample_count: usize) -> F {
let len = self.data.len(); let len = self.data.len();
@ -1129,29 +1193,39 @@ impl<F: Flt> DelayBuffer<F> {
/// Default size of the delay buffer: 1 seconds at 8 times 48kHz /// Default size of the delay buffer: 1 seconds at 8 times 48kHz
const DEFAULT_ALLPASS_COMB_SAMPLES: usize = 8 * 48000; const DEFAULT_ALLPASS_COMB_SAMPLES: usize = 8 * 48000;
/// An all-pass filter based on a delay line.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct AllPass<F: Flt> { pub struct AllPass<F: Flt> {
delay: DelayBuffer<F>, delay: DelayBuffer<F>,
} }
impl<F: Flt> AllPass<F> { impl<F: Flt> AllPass<F> {
/// Creates a new all-pass filter with about 1 seconds space for samples.
pub fn new() -> Self { pub fn new() -> Self {
Self { delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES) } Self { delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES) }
} }
/// Set the sample rate for millisecond based access.
pub fn set_sample_rate(&mut self, srate: F) { pub fn set_sample_rate(&mut self, srate: F) {
self.delay.set_sample_rate(srate); self.delay.set_sample_rate(srate);
} }
/// Reset the internal delay buffer.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.delay.reset(); self.delay.reset();
} }
/// Access the internal delay at the given amount of milliseconds in the past.
#[inline] #[inline]
pub fn delay_tap_n(&self, time_ms: F) -> F { pub fn delay_tap_n(&self, time_ms: F) -> F {
self.delay.tap_n(time_ms) self.delay.tap_n(time_ms)
} }
/// Retrieve the next sample from the all-pass filter while feeding in the next.
///
/// * `time_ms` - Delay time in milliseconds.
/// * `g` - Feedback factor (usually something around 0.7 is common)
/// * `v` - The new input sample to feed the filter.
#[inline] #[inline]
pub fn next(&mut self, time_ms: F, g: F, v: F) -> F { pub fn next(&mut self, time_ms: F, g: F, v: F) -> F {
let s = self.delay.cubic_interpolate_at(time_ms); let s = self.delay.cubic_interpolate_at(time_ms);
@ -1266,6 +1340,7 @@ impl<F: Flt> OnePoleLPF<F> {
self.a = f::<F>(1.0) - self.b; self.a = f::<F>(1.0) - self.b;
} }
#[inline]
pub fn set_sample_rate(&mut self, srate: F) { pub fn set_sample_rate(&mut self, srate: F) {
self.israte = f::<F>(1.0) / srate; self.israte = f::<F>(1.0) / srate;
self.recalc(); self.recalc();