Skip to main content

rill_core/math/
functions.rs

1//! Common mathematical functions for signal processing
2
3use super::num::Transcendental;
4use super::vector::scalar::ScalarVector4;
5use super::vector::traits::Vector as VecTrait;
6
7/// Linear interpolation
8#[inline(always)]
9pub fn lerp<T: Transcendental>(a: T, b: T, t: T) -> T {
10    a + (b - a) * t
11}
12
13/// Convert decibels to a linear coefficient
14#[inline(always)]
15pub fn db_to_linear<T: Transcendental>(db: T) -> T {
16    T::from_f32(10.0_f32.powf(db.to_f32() / 20.0))
17}
18
19/// Convert a linear coefficient to decibels
20#[inline(always)]
21pub fn linear_to_db<T: Transcendental>(linear: T) -> T {
22    T::from_f32(20.0 * linear.to_f32().log10())
23}
24/// Convert seconds to samples
25#[inline(always)]
26pub fn seconds_to_samples(seconds: f32, sample_rate: f32) -> usize {
27    (seconds * sample_rate) as usize
28}
29
30/// Convert samples to seconds
31#[inline(always)]
32pub fn samples_to_seconds(samples: usize, sample_rate: f32) -> f32 {
33    samples as f32 / sample_rate
34}
35
36/// Fast tanh approximation
37#[inline(always)]
38pub fn fast_tanh<T: Transcendental>(x: T) -> T {
39    let xf = x.to_f32();
40    T::from_f32(xf / (1.0 + xf.abs()))
41}
42
43/// Soft clipping
44#[inline(always)]
45pub fn soft_clip<T: Transcendental>(x: T, threshold: T) -> T {
46    let xf = x.to_f32();
47    let t = threshold.to_f32();
48
49    if xf > t {
50        T::from_f32(t + (xf - t) / (1.0 + ((xf - t) / (1.0 - t)).powi(2)))
51    } else if xf < -t {
52        T::from_f32(-t - (-xf - t) / (1.0 + ((-xf - t) / (1.0 - t)).powi(2)))
53    } else {
54        x
55    }
56}
57
58/// Hann window
59#[inline(always)]
60pub fn hann_window<T: Transcendental>(x: T) -> T {
61    let cos_term = (x * T::from_f32(2.0) * T::PI).cos();
62    T::from_f32(0.5) * (T::ONE - cos_term)
63}
64
65/// Convert f32 chunk to i16 with SIMD clamping and scaling.
66///
67/// Each f32 sample is clamped to `[-1.0, 1.0]`, multiplied by 32767,
68/// and truncated to `i16`. Processes 4 samples at a time.
69///
70/// # Panics
71/// Panics if `dst` is shorter than `src`.
72pub fn f32_to_i16_chunk(src: &[f32], dst: &mut [i16]) {
73    let len = src.len().min(dst.len());
74    let chunks = len / 4;
75
76    for chunk in 0..chunks {
77        let o = chunk * 4;
78        let v = ScalarVector4::load(&src[o..o + 4]);
79        let lo = ScalarVector4::splat(-1.0f32);
80        let hi = ScalarVector4::splat(1.0f32);
81        let scale = ScalarVector4::splat(32767.0f32);
82        let clamped = v.clamp(&lo, &hi);
83        let scaled = clamped.mul(&scale);
84        dst[o] = scaled.extract(0) as i16;
85        dst[o + 1] = scaled.extract(1) as i16;
86        dst[o + 2] = scaled.extract(2) as i16;
87        dst[o + 3] = scaled.extract(3) as i16;
88    }
89
90    for i in chunks * 4..len {
91        dst[i] = (src[i].clamp(-1.0, 1.0) * 32767.0) as i16;
92    }
93}
94
95/// Convert i16 chunk to f32 with SIMD scaling.
96///
97/// Each i16 sample is divided by 32768.0 to produce f32 in `[-1.0, 1.0)`.
98/// Processes 4 samples at a time.
99///
100/// # Panics
101/// Panics if `dst` is shorter than `src`.
102pub fn i16_to_f32_chunk(src: &[i16], dst: &mut [f32]) {
103    let len = src.len().min(dst.len());
104    let chunks = len / 4;
105
106    for chunk in 0..chunks {
107        let o = chunk * 4;
108        let v = ScalarVector4::load(&[
109            src[o] as f32 / 32768.0,
110            src[o + 1] as f32 / 32768.0,
111            src[o + 2] as f32 / 32768.0,
112            src[o + 3] as f32 / 32768.0,
113        ]);
114        v.store(&mut dst[o..o + 4]);
115    }
116
117    for i in chunks * 4..len {
118        dst[i] = src[i] as f32 / 32768.0;
119    }
120}
121
122/// Deinterleave stereo interleaved buffer into two mono buffers (SIMD).
123///
124/// `stereo` contains `[L0,R0, L1,R1, L2,R2, ...]`.
125/// Processes 4 stereo pairs (8 samples) per chunk.
126///
127/// # Panics
128/// Panics if `out_l` or `out_r` is shorter than `stereo.len() / 2`.
129pub fn deinterleave_stereo(stereo: &[f32], out_l: &mut [f32], out_r: &mut [f32]) {
130    let pairs = (stereo.len() / 2).min(out_l.len()).min(out_r.len());
131    let chunks = pairs / 4;
132
133    for chunk in 0..chunks {
134        let so = chunk * 8;
135        let mo = chunk * 4;
136
137        let v01 = ScalarVector4::load(&stereo[so..so + 4]);
138        let v23 = ScalarVector4::load(&stereo[so + 4..so + 8]);
139
140        let l = ScalarVector4::from_fn(|i| {
141            if i < 2 {
142                v01.extract(i * 2)
143            } else {
144                v23.extract((i - 2) * 2)
145            }
146        });
147
148        let r = ScalarVector4::from_fn(|i| {
149            if i < 2 {
150                v01.extract(i * 2 + 1)
151            } else {
152                v23.extract((i - 2) * 2 + 1)
153            }
154        });
155
156        l.store(&mut out_l[mo..mo + 4]);
157        r.store(&mut out_r[mo..mo + 4]);
158    }
159
160    for i in chunks * 4..pairs {
161        out_l[i] = stereo[i * 2];
162        out_r[i] = stereo[i * 2 + 1];
163    }
164}
165
166/// Interleave two mono buffers into a stereo interleaved buffer (SIMD).
167///
168/// Produces `[L0,R0, L1,R1, L2,R2, ...]`.
169/// Processes 4 stereo pairs (8 samples) per chunk.
170///
171/// # Panics
172/// Panics if `stereo` is shorter than `2 * in_l.len()`.
173pub fn interleave_stereo(in_l: &[f32], in_r: &[f32], stereo: &mut [f32]) {
174    let pairs = in_l.len().min(in_r.len()).min(stereo.len() / 2);
175    let chunks = pairs / 4;
176
177    for chunk in 0..chunks {
178        let mo = chunk * 4;
179        let so = chunk * 8;
180
181        let l = ScalarVector4::load(&in_l[mo..mo + 4]);
182        let r = ScalarVector4::load(&in_r[mo..mo + 4]);
183
184        let out: [f32; 8] = std::array::from_fn(|i| {
185            if i % 2 == 0 {
186                l.extract(i / 2)
187            } else {
188                r.extract(i / 2)
189            }
190        });
191
192        stereo[so..so + 8].copy_from_slice(&out);
193    }
194
195    for i in chunks * 4..pairs {
196        stereo[i * 2] = in_l[i];
197        stereo[i * 2 + 1] = in_r[i];
198    }
199}