Restructured project for separate plugins
This commit is contained in:
74
dsp/src/amplitude.rs
Normal file
74
dsp/src/amplitude.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use crate::{decibel::Decibel, math_ops_impl};
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Amplitude(pub f64);
|
||||
impl From<Amplitude> for Decibel {
|
||||
fn from(value: Amplitude) -> Self {
|
||||
Decibel::from(20.0 * value.0.abs().log10())
|
||||
}
|
||||
}
|
||||
impl Amplitude {
|
||||
pub fn value(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Amplitude {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!("{:.2}", self.0))
|
||||
}
|
||||
}
|
||||
impl Debug for Amplitude {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!("{}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
math_ops_impl!(Amplitude);
|
||||
|
||||
impl std::ops::Add for Amplitude {
|
||||
type Output = Amplitude;
|
||||
fn add(self, rhs: Amplitude) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub for Amplitude {
|
||||
type Output = Amplitude;
|
||||
fn sub(self, rhs: Amplitude) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul for Amplitude {
|
||||
type Output = Amplitude;
|
||||
fn mul(self, rhs: Amplitude) -> Self::Output {
|
||||
Self(self.0 * rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Div for Amplitude {
|
||||
type Output = Amplitude;
|
||||
fn div(self, rhs: Amplitude) -> Self::Output {
|
||||
Self(self.0 / rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::AddAssign for Amplitude {
|
||||
fn add_assign(&mut self, rhs: Amplitude) {
|
||||
self.0 += rhs.0;
|
||||
}
|
||||
}
|
||||
impl std::ops::SubAssign for Amplitude {
|
||||
fn sub_assign(&mut self, rhs: Amplitude) {
|
||||
self.0 -= rhs.0;
|
||||
}
|
||||
}
|
||||
impl std::ops::MulAssign for Amplitude {
|
||||
fn mul_assign(&mut self, rhs: Amplitude) {
|
||||
self.0 *= rhs.0;
|
||||
}
|
||||
}
|
||||
impl std::ops::DivAssign for Amplitude {
|
||||
fn div_assign(&mut self, rhs: Amplitude) {
|
||||
self.0 /= rhs.0;
|
||||
}
|
||||
}
|
||||
348
dsp/src/biquad.rs
Normal file
348
dsp/src/biquad.rs
Normal file
@@ -0,0 +1,348 @@
|
||||
use std::{
|
||||
f64::{self, consts::SQRT_2},
|
||||
fmt::Display,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
decibel::Decibel, SampleRate,
|
||||
traits::{IntFormatter, Processor},
|
||||
};
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Slope {
|
||||
#[default]
|
||||
DB12,
|
||||
DB24,
|
||||
DB36,
|
||||
DB48,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum FilterMode {
|
||||
Peak,
|
||||
HighPass,
|
||||
HighShelf,
|
||||
LowPass,
|
||||
LowShelf,
|
||||
AllPass,
|
||||
BandPass,
|
||||
BandReject,
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
impl TryFrom<i32> for FilterMode {
|
||||
type Error = ();
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(FilterMode::Peak),
|
||||
1 => Ok(FilterMode::HighPass),
|
||||
2 => Ok(FilterMode::HighShelf),
|
||||
3 => Ok(FilterMode::LowPass),
|
||||
4 => Ok(FilterMode::LowShelf),
|
||||
5 => Ok(FilterMode::AllPass),
|
||||
6 => Ok(FilterMode::BandPass),
|
||||
7 => Ok(FilterMode::BandReject),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<FilterMode> for i32 {
|
||||
fn from(value: FilterMode) -> Self {
|
||||
match value {
|
||||
FilterMode::Peak => 0,
|
||||
FilterMode::HighPass => 1,
|
||||
FilterMode::HighShelf => 2,
|
||||
FilterMode::LowPass => 3,
|
||||
FilterMode::LowShelf => 4,
|
||||
FilterMode::AllPass => 5,
|
||||
FilterMode::BandPass => 6,
|
||||
FilterMode::BandReject => 7,
|
||||
FilterMode::None => 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<&str> for FilterMode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"Peak" => Ok(FilterMode::Peak),
|
||||
"Highpass" => Ok(FilterMode::HighPass),
|
||||
"High Shelf" => Ok(FilterMode::HighShelf),
|
||||
"Lowpass" => Ok(FilterMode::LowPass),
|
||||
"Low Shelf" => Ok(FilterMode::LowShelf),
|
||||
"Allpass" => Ok(FilterMode::AllPass),
|
||||
"Bandpass" => Ok(FilterMode::BandPass),
|
||||
"Notch" => Ok(FilterMode::BandReject),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for FilterMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
FilterMode::Peak => "Peak",
|
||||
FilterMode::HighPass => "Highpass",
|
||||
FilterMode::HighShelf => "High Shelf",
|
||||
FilterMode::LowPass => "Lowpass",
|
||||
FilterMode::LowShelf => "Low Shelf",
|
||||
FilterMode::AllPass => "Allpass",
|
||||
FilterMode::BandPass => "Bandpass",
|
||||
FilterMode::BandReject => "Notch",
|
||||
FilterMode::None => "None",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntFormatter for FilterMode {
|
||||
fn v2s() -> Arc<dyn Fn(i32) -> String + Send + Sync>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Arc::new(move |value| FilterMode::try_from(value).unwrap_or_default().to_string())
|
||||
}
|
||||
|
||||
fn s2v() -> Arc<dyn Fn(&str) -> Option<i32> + Send + Sync>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Arc::new(move |value| FilterMode::try_from(value).map(i32::from).ok())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct FilterCoefficients {
|
||||
a1: f64,
|
||||
a2: f64,
|
||||
b0: f64,
|
||||
b1: f64,
|
||||
b2: f64,
|
||||
}
|
||||
impl FilterCoefficients {
|
||||
fn calculate(
|
||||
&mut self,
|
||||
mode: FilterMode,
|
||||
sample_rate: SampleRate,
|
||||
freq: f64,
|
||||
q: f64,
|
||||
gain_db: Decibel,
|
||||
) {
|
||||
let v = 10f64.powf(gain_db.value().abs() / 20.0);
|
||||
let k = (f64::consts::PI * freq / sample_rate.0).tan();
|
||||
|
||||
match mode {
|
||||
FilterMode::Peak => {
|
||||
if gain_db.value() >= 0.0 {
|
||||
let norm = 1.0 / (1.0 + 1.0 / q * k + k * k);
|
||||
self.b0 = (1.0 + v / q * k + k * k) * norm;
|
||||
self.b1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.b2 = (1.0 - v / q * k + k * k) * norm;
|
||||
self.a1 = self.b1;
|
||||
self.a2 = (1.0 - 1.0 / q * k + k * k) * norm;
|
||||
} else {
|
||||
let norm = 1.0 / (1.0 + v / q * k + k * k);
|
||||
self.b0 = (1.0 + 1.0 / q * k + k * k) * norm;
|
||||
self.b1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.b2 = (1.0 - 1.0 / q * k + k * k) * norm;
|
||||
self.a1 = self.b1;
|
||||
self.a2 = (1.0 - v / q * k + k * k) * norm;
|
||||
}
|
||||
}
|
||||
FilterMode::HighPass => {
|
||||
let norm = 1.0 / (1.0 + k / q + k * k);
|
||||
self.b0 = 1.0 * norm;
|
||||
self.b1 = -2.0 * self.b0;
|
||||
self.b2 = self.b0;
|
||||
self.a1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.a2 = (1.0 - k / q + k * k) * norm;
|
||||
}
|
||||
FilterMode::HighShelf => {
|
||||
if gain_db.value() >= 0.0 {
|
||||
let norm = 1.0 / (1.0 + SQRT_2 * k + k * k);
|
||||
self.b0 = (v + (2.0 * v).sqrt() * k + k * k) * norm;
|
||||
self.b1 = 2.0 * (k * k - v) * norm;
|
||||
self.b2 = (v - (2.0 * v).sqrt() * k + k * k) * norm;
|
||||
self.a1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.a2 = (1.0 - SQRT_2 * k + k * k) * norm;
|
||||
} else {
|
||||
let norm = 1.0 / (v + (2.0 * v).sqrt() * k + k * k);
|
||||
self.b0 = (1.0 + SQRT_2 * k + k * k) * norm;
|
||||
self.b1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.b2 = (1.0 - SQRT_2 * k + k * k) * norm;
|
||||
self.a1 = 2.0 * (k * k - v) * norm;
|
||||
self.a2 = (v - (2.0 * v).sqrt() * k + k * k) * norm;
|
||||
}
|
||||
}
|
||||
FilterMode::LowPass => {
|
||||
let norm = 1.0 / (1.0 + k / q + k * k);
|
||||
self.b0 = k * k * norm;
|
||||
self.b1 = 2.0 * self.b0;
|
||||
self.b2 = self.b0;
|
||||
self.a1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.a2 = (1.0 - k / q + k * k) * norm;
|
||||
}
|
||||
FilterMode::LowShelf => {
|
||||
if gain_db.value() >= 0.0 {
|
||||
let norm = 1.0 / (1.0 + SQRT_2 * k + k * k);
|
||||
self.b0 = (1.0 + (2.0 * v).sqrt() * k + v * k * k) * norm;
|
||||
self.b1 = 2.0 * (v * k * k - 1.0) * norm;
|
||||
self.b2 = (1.0 - (2.0 * v).sqrt() * k + v * k * k) * norm;
|
||||
self.a1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.a2 = (1.0 - SQRT_2 * k + k * k) * norm;
|
||||
} else {
|
||||
let norm = 1.0 / (1.0 + (2.0 * v).sqrt() * k + v * k * k);
|
||||
self.b0 = (1.0 + SQRT_2 * k + k * k) * norm;
|
||||
self.b1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.b2 = (1.0 - SQRT_2 * k + k * k) * norm;
|
||||
self.a1 = 2.0 * (v * k * k - 1.0) * norm;
|
||||
self.a2 = (1.0 - (2.0 * v).sqrt() * k + v * k * k) * norm;
|
||||
}
|
||||
}
|
||||
FilterMode::AllPass => {
|
||||
let norm = 1.0 / (1.0 + k / q + k * k);
|
||||
self.b0 = (1.0 - k / q + k * k) * norm;
|
||||
self.b1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.b2 = 1.0;
|
||||
self.a1 = self.b1;
|
||||
self.a2 = self.b0;
|
||||
}
|
||||
FilterMode::BandPass => {
|
||||
let norm = 1.0 / (1.0 + k / q + k * k);
|
||||
self.b0 = k / q * norm;
|
||||
self.b1 = 0.0;
|
||||
self.b2 = -self.b0;
|
||||
self.a1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.a2 = (1.0 - k / q + k * k) * norm;
|
||||
}
|
||||
FilterMode::BandReject => {
|
||||
let norm = 1.0 / (1.0 + k / q + k * k);
|
||||
self.b0 = (1.0 + k * k) * norm;
|
||||
self.b1 = 2.0 * (k * k - 1.0) * norm;
|
||||
self.b2 = self.b0;
|
||||
self.a1 = self.b1;
|
||||
self.a2 = (1.0 - k / q + k * k) * norm;
|
||||
}
|
||||
FilterMode::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
struct State {
|
||||
s1: f64,
|
||||
s2: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BiquadFilterState {
|
||||
pub mode: FilterMode,
|
||||
pub cutoff: f64,
|
||||
pub q: f64,
|
||||
pub gain_db: Decibel,
|
||||
pub slope: Slope,
|
||||
}
|
||||
impl Default for BiquadFilterState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: FilterMode::None,
|
||||
cutoff: 2000.0,
|
||||
q: 0.707,
|
||||
gain_db: Decibel::from(0.0),
|
||||
slope: Slope::DB12,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BiquadFilter {
|
||||
pub state: BiquadFilterState,
|
||||
sample_rate: SampleRate,
|
||||
coefficients: FilterCoefficients,
|
||||
slope_states: [State; 4],
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl BiquadFilter {
|
||||
pub fn new(sample_rate: SampleRate) -> Self {
|
||||
Self::from_state(sample_rate, BiquadFilterState::default())
|
||||
}
|
||||
pub fn from_state(sample_rate: SampleRate, state: BiquadFilterState) -> Self {
|
||||
Self {
|
||||
sample_rate,
|
||||
coefficients: FilterCoefficients::default(),
|
||||
slope_states: [State::default(); 4],
|
||||
dirty: true,
|
||||
state
|
||||
}
|
||||
}
|
||||
pub fn set_mode(&mut self, mode: FilterMode) {
|
||||
self.dirty = true;
|
||||
self.state.mode = mode;
|
||||
}
|
||||
pub fn set_cutoff(&mut self, cutoff: f64) {
|
||||
self.dirty = true;
|
||||
self.state.cutoff = cutoff;
|
||||
}
|
||||
pub fn set_slope(&mut self, slope: Slope) {
|
||||
self.dirty = true;
|
||||
self.state.slope = slope;
|
||||
}
|
||||
pub fn set_q(&mut self, q: f64) {
|
||||
self.dirty = true;
|
||||
self.state.q = q;
|
||||
}
|
||||
pub fn set_gain<T>(&mut self, gain_db: T)
|
||||
where
|
||||
T: Into<Decibel>,
|
||||
{
|
||||
self.dirty = true;
|
||||
self.state.gain_db = gain_db.into();
|
||||
}
|
||||
|
||||
fn update_coefficients(&mut self) {
|
||||
if !self.dirty {
|
||||
return;
|
||||
}
|
||||
self.dirty = false;
|
||||
|
||||
self.coefficients.calculate(
|
||||
self.state.mode,
|
||||
self.sample_rate,
|
||||
self.state.cutoff,
|
||||
self.state.q,
|
||||
self.state.gain_db,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Processor for BiquadFilter {
|
||||
fn process(&mut self, sample: f64) -> f64 {
|
||||
self.update_coefficients();
|
||||
|
||||
let iterations = match self.state.slope {
|
||||
Slope::DB12 => 1,
|
||||
Slope::DB24 => 2,
|
||||
Slope::DB36 => 3,
|
||||
Slope::DB48 => 4,
|
||||
};
|
||||
let mut current = sample;
|
||||
|
||||
for i in 0..iterations {
|
||||
let result = self.coefficients.b0 * current + self.slope_states[i].s1;
|
||||
let s1 = self.coefficients.b1 * current - self.coefficients.a1 * result
|
||||
+ self.slope_states[i].s2;
|
||||
let s2 = self.coefficients.b2 * current - self.coefficients.a2 * result;
|
||||
|
||||
self.slope_states[i].s1 = s1;
|
||||
self.slope_states[i].s2 = s2;
|
||||
current = result;
|
||||
}
|
||||
|
||||
if current.is_nan() || current.is_infinite() {
|
||||
0.0
|
||||
} else {
|
||||
current
|
||||
}
|
||||
}
|
||||
}
|
||||
127
dsp/src/comp.rs
Normal file
127
dsp/src/comp.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use crate::{
|
||||
Ratio, SampleRate, amplitude::Amplitude, decibel::Decibel, ring_buffer::RingBuffer, rms, smoother::Smoother, traits::Processor
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub enum CompressorPeakMode {
|
||||
#[default]
|
||||
Sample,
|
||||
RMS,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct CompressorState {
|
||||
pub peak_mode: CompressorPeakMode,
|
||||
pub threshold: Decibel,
|
||||
pub ratio: Ratio,
|
||||
pub gain: Decibel,
|
||||
pub attack: Duration,
|
||||
pub release: Duration,
|
||||
pub last_rms: f64,
|
||||
pub input_db: Decibel,
|
||||
pub output_static_db: Decibel,
|
||||
pub gain_db: Decibel,
|
||||
pub gain_smoothed_db: Decibel,
|
||||
pub final_gain_db: Decibel,
|
||||
}
|
||||
impl CompressorState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
peak_mode: CompressorPeakMode::Sample,
|
||||
threshold: Decibel::from(-24.0),
|
||||
ratio: Ratio(1.0, 3.0),
|
||||
gain: Decibel::from(0.0),
|
||||
attack: Duration::from_millis(25),
|
||||
release: Duration::from_millis(100),
|
||||
last_rms: 0.0,
|
||||
input_db: Decibel::from(0.0),
|
||||
output_static_db: Decibel::from(0.0),
|
||||
gain_db: Decibel::from(0.0),
|
||||
gain_smoothed_db: Decibel::from(0.0),
|
||||
final_gain_db: Decibel::from(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Compressor {
|
||||
pub state: CompressorState,
|
||||
sample_buffer: RingBuffer<f64, 2048>,
|
||||
smoother: Smoother,
|
||||
}
|
||||
|
||||
impl Compressor {
|
||||
pub fn new(sample_rate: SampleRate) -> Self {
|
||||
Self::from_state(sample_rate, CompressorState::default())
|
||||
}
|
||||
pub fn from_state(sample_rate: SampleRate, state: CompressorState) -> Self {
|
||||
Self {
|
||||
sample_buffer: RingBuffer::new(0.0),
|
||||
smoother: Smoother::new(sample_rate),
|
||||
state,
|
||||
}
|
||||
}
|
||||
pub fn set_threshold<T>(&mut self, threshold: T)
|
||||
where
|
||||
T: Into<Decibel>,
|
||||
{
|
||||
self.state.threshold = threshold.into();
|
||||
}
|
||||
pub fn set_attack<T>(&mut self, attack: T)
|
||||
where
|
||||
T: Into<Duration>,
|
||||
{
|
||||
self.smoother.set_attack(attack);
|
||||
}
|
||||
pub fn set_release<T>(&mut self, release: T)
|
||||
where
|
||||
T: Into<Duration>,
|
||||
{
|
||||
self.smoother.set_release(release);
|
||||
}
|
||||
pub fn set_ratio<R>(&mut self, ratio: R)
|
||||
where
|
||||
R: Into<Ratio>,
|
||||
{
|
||||
self.state.ratio = ratio.into();
|
||||
}
|
||||
pub fn set_gain<T>(&mut self, gain: T)
|
||||
where
|
||||
T: Into<Decibel>,
|
||||
{
|
||||
self.state.gain = gain.into();
|
||||
}
|
||||
}
|
||||
|
||||
impl Processor for Compressor {
|
||||
fn process(&mut self, sample: f64) -> f64 {
|
||||
self.sample_buffer[0] = sample;
|
||||
|
||||
self.state.input_db = Decibel::from(Amplitude(match self.state.peak_mode {
|
||||
CompressorPeakMode::Sample => sample.abs() + f64::EPSILON,
|
||||
CompressorPeakMode::RMS => {
|
||||
self.state.last_rms = rms(&self.sample_buffer, self.state.last_rms);
|
||||
self.state.last_rms.abs().sqrt() + f64::EPSILON
|
||||
}
|
||||
}));
|
||||
|
||||
self.state.output_static_db = if self.state.input_db < self.state.threshold {
|
||||
self.state.input_db
|
||||
} else {
|
||||
self.state.threshold
|
||||
+ (self.state.input_db - self.state.threshold) * self.state.ratio.multiplier()
|
||||
};
|
||||
self.state.gain_db = self.state.output_static_db - self.state.input_db;
|
||||
|
||||
self.state.gain_smoothed_db = if self.state.gain_db < self.state.gain_smoothed_db {
|
||||
self.smoother.attack(self.state.gain_db)
|
||||
} else {
|
||||
self.smoother.release(self.state.gain_db)
|
||||
};
|
||||
|
||||
self.state.final_gain_db = self.state.gain_smoothed_db + self.state.gain;
|
||||
self.sample_buffer.shift();
|
||||
|
||||
sample * self.state.final_gain_db
|
||||
}
|
||||
}
|
||||
141
dsp/src/decibel.rs
Normal file
141
dsp/src/decibel.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use crate::{amplitude::Amplitude, math_ops_impl};
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Decibel(f64);
|
||||
impl Decibel {
|
||||
pub fn value(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl From<Decibel> for Amplitude {
|
||||
fn from(value: Decibel) -> Self {
|
||||
Amplitude(10f64.powf(value.0 / 20.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Decibel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.0 >= 0.0 {
|
||||
f.write_str(&format!("+{:.2}dB", self.0))
|
||||
} else {
|
||||
f.write_str(&format!("{:.2}dB", self.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Debug for Decibel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.0 >= 0.0 {
|
||||
f.write_str(&format!("+{}dB", self.0))
|
||||
} else {
|
||||
f.write_str(&format!("{}dB", self.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Decibel {
|
||||
fn from(value: f32) -> Self {
|
||||
Decibel(value as f64)
|
||||
}
|
||||
}
|
||||
impl From<Decibel> for f32 {
|
||||
fn from(value: Decibel) -> Self {
|
||||
value.0 as f32
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Decibel {
|
||||
fn from(value: f64) -> Self {
|
||||
let mut value = value;
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use nih_plug::nih_warn;
|
||||
if value.is_nan() {
|
||||
nih_warn!("Tried making dB from NaN");
|
||||
value = 0.0;
|
||||
} else if value.is_infinite() {
|
||||
nih_warn!("Tried making dB from Inf");
|
||||
value = 0.0;
|
||||
} else if value >= 120.0 {
|
||||
nih_warn!("Impossibly large dB {value}");
|
||||
}
|
||||
}
|
||||
Decibel(value)
|
||||
}
|
||||
}
|
||||
impl From<Decibel> for f64 {
|
||||
fn from(value: Decibel) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
math_ops_impl!(Decibel);
|
||||
|
||||
impl std::ops::Add for Decibel {
|
||||
type Output = Decibel;
|
||||
fn add(self, rhs: Decibel) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub for Decibel {
|
||||
type Output = Decibel;
|
||||
fn sub(self, rhs: Decibel) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::AddAssign for Decibel {
|
||||
fn add_assign(&mut self, rhs: Decibel) {
|
||||
self.0 += rhs.0;
|
||||
}
|
||||
}
|
||||
impl std::ops::SubAssign for Decibel {
|
||||
fn sub_assign(&mut self, rhs: Decibel) {
|
||||
self.0 -= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Decibel> for f32 {
|
||||
type Output = f32;
|
||||
fn mul(self, rhs: Decibel) -> Self::Output {
|
||||
((self as f64) * rhs) as f32
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Decibel> for f64 {
|
||||
type Output = f64;
|
||||
fn mul(self, rhs: Decibel) -> Self::Output {
|
||||
self * Amplitude::from(rhs).0
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<Decibel> for f32 {
|
||||
type Output = f32;
|
||||
fn div(self, rhs: Decibel) -> Self::Output {
|
||||
((self as f64) / rhs) as f32
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<Decibel> for f64 {
|
||||
type Output = f64;
|
||||
fn div(self, rhs: Decibel) -> Self::Output {
|
||||
self / Amplitude::from(rhs).0
|
||||
}
|
||||
}
|
||||
impl std::ops::MulAssign<Decibel> for f32 {
|
||||
fn mul_assign(&mut self, rhs: Decibel) {
|
||||
*self = ((*self as f64) * rhs) as f32
|
||||
}
|
||||
}
|
||||
impl std::ops::MulAssign<Decibel> for f64 {
|
||||
fn mul_assign(&mut self, rhs: Decibel) {
|
||||
*self *= Amplitude::from(rhs).0
|
||||
}
|
||||
}
|
||||
impl std::ops::DivAssign<Decibel> for f32 {
|
||||
fn div_assign(&mut self, rhs: Decibel) {
|
||||
*self = ((*self as f64) / rhs) as f32
|
||||
}
|
||||
}
|
||||
impl std::ops::DivAssign<Decibel> for f64 {
|
||||
fn div_assign(&mut self, rhs: Decibel) {
|
||||
*self /= Amplitude::from(rhs).0
|
||||
}
|
||||
}
|
||||
31
dsp/src/eq.rs
Normal file
31
dsp/src/eq.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::{BiquadFilter, Processor};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct EqualizerState {
|
||||
pub nodes: [Option<BiquadFilter>; 6],
|
||||
}
|
||||
|
||||
pub struct Equalizer {
|
||||
pub state: EqualizerState,
|
||||
}
|
||||
|
||||
impl Equalizer {
|
||||
pub fn new() -> Self {
|
||||
Self::from_state(EqualizerState::default())
|
||||
}
|
||||
pub fn from_state(state: EqualizerState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Processor for Equalizer {
|
||||
fn process(&mut self, sample: f64) -> f64 {
|
||||
let mut sample = sample;
|
||||
for node in &mut self.state.nodes {
|
||||
if let Some(filter) = node {
|
||||
sample = filter.process(sample);
|
||||
}
|
||||
}
|
||||
sample
|
||||
}
|
||||
}
|
||||
56
dsp/src/freq_split.rs
Normal file
56
dsp/src/freq_split.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use crate::{BiquadFilter, FilterMode, SampleRate, biquad::Slope, traits::Processor};
|
||||
|
||||
pub struct FreqSplitterState {
|
||||
pub split_frequency: f64,
|
||||
pub slope: Slope,
|
||||
}
|
||||
impl Default for FreqSplitterState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
split_frequency: 2000.0,
|
||||
slope: Slope::DB24,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FreqSplitter {
|
||||
pub state: FreqSplitterState,
|
||||
low_band: BiquadFilter,
|
||||
high_band: BiquadFilter,
|
||||
}
|
||||
|
||||
impl FreqSplitter {
|
||||
pub fn new(sample_rate: SampleRate) -> Self {
|
||||
Self::from_state(sample_rate, FreqSplitterState::default())
|
||||
}
|
||||
pub fn from_state(sample_rate: SampleRate, state: FreqSplitterState) -> Self {
|
||||
let mut this = Self {
|
||||
state,
|
||||
low_band: BiquadFilter::new(sample_rate),
|
||||
high_band: BiquadFilter::new(sample_rate),
|
||||
};
|
||||
this.low_band.set_mode(FilterMode::LowPass);
|
||||
this.high_band.set_mode(FilterMode::HighPass);
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl Processor<f64, (f64, f64)> for FreqSplitter {
|
||||
fn process(&mut self, sample: f64) -> (f64, f64) {
|
||||
self.low_band.set_cutoff(self.state.split_frequency);
|
||||
self.low_band.set_slope(self.state.slope);
|
||||
self.high_band.set_cutoff(self.state.split_frequency);
|
||||
self.high_band.set_slope(self.state.slope);
|
||||
(
|
||||
self.low_band.process(sample),
|
||||
self.high_band.process(sample),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Processor<f32, (f32, f32)> for FreqSplitter {
|
||||
fn process(&mut self, sample: f32) -> (f32, f32) {
|
||||
let (low, high) = self.process(sample as f64);
|
||||
(low as f32, high as f32)
|
||||
}
|
||||
}
|
||||
186
dsp/src/gui/mod.rs
Normal file
186
dsp/src/gui/mod.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use std::sync::{Arc, atomic::Ordering};
|
||||
|
||||
use femtovg::{Canvas, ImageFlags, ImageId, Paint, Path, renderer::OpenGl};
|
||||
use nih_plug::prelude::AtomicF32;
|
||||
|
||||
use crate::{Rect, ScaledRect};
|
||||
|
||||
pub struct SpriteSheet {
|
||||
scale_factor: Arc<AtomicF32>,
|
||||
image: Result<ImageId, String>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
frame_width: usize,
|
||||
frame_height: usize,
|
||||
frames_x: usize,
|
||||
}
|
||||
|
||||
impl SpriteSheet {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
scale_factor: Arc::new(AtomicF32::new(1.0)),
|
||||
image: Err("No image loaded".to_owned()),
|
||||
width: 0,
|
||||
height: 0,
|
||||
frame_width: 0,
|
||||
frame_height: 0,
|
||||
frames_x: 0,
|
||||
}
|
||||
}
|
||||
pub fn new(
|
||||
canvas: &mut Canvas<OpenGl>,
|
||||
data: &[u8],
|
||||
scale_factor: Arc<AtomicF32>,
|
||||
frame_width: usize,
|
||||
frame_height: usize,
|
||||
) -> Self {
|
||||
let image = canvas
|
||||
.load_image_mem(data, ImageFlags::empty())
|
||||
.map_err(|e| format!("{e:?}"));
|
||||
if let Ok(image) = image {
|
||||
let (width, height) = canvas.image_size(image).unwrap_or_default();
|
||||
Self {
|
||||
scale_factor,
|
||||
image: Ok(image),
|
||||
width,
|
||||
height,
|
||||
frame_width,
|
||||
frame_height,
|
||||
frames_x: width / frame_width,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
scale_factor,
|
||||
image,
|
||||
width: 0,
|
||||
height: 0,
|
||||
frame_width,
|
||||
frame_height,
|
||||
frames_x: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn draw(&self, canvas: &mut Canvas<OpenGl>, x: f32, y: f32, frame: usize) {
|
||||
let factor = self.scale_factor.load(Ordering::Relaxed);
|
||||
|
||||
let frame_x = frame % self.frames_x;
|
||||
let frame_y = frame / self.frames_x;
|
||||
|
||||
let screen_rect = Rect {
|
||||
x: x * factor,
|
||||
y: y * factor,
|
||||
width: self.frame_width as f32 * factor,
|
||||
height: self.frame_height as f32 * factor,
|
||||
};
|
||||
let image_rect = Rect {
|
||||
x: screen_rect.x - (frame_x * self.frame_width) as f32 * factor,
|
||||
y: screen_rect.y - (frame_y * self.frame_height) as f32 * factor,
|
||||
width: self.width as f32 * factor,
|
||||
height: self.height as f32 * factor,
|
||||
};
|
||||
|
||||
let mut screen_path = Path::new();
|
||||
screen_path.rect(
|
||||
screen_rect.x,
|
||||
screen_rect.y,
|
||||
screen_rect.width,
|
||||
screen_rect.height,
|
||||
);
|
||||
if let Ok(image) = self.image {
|
||||
canvas.fill_path(
|
||||
&screen_path,
|
||||
&Paint::image(
|
||||
image,
|
||||
image_rect.x,
|
||||
image_rect.y,
|
||||
image_rect.width,
|
||||
image_rect.height,
|
||||
0.0,
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn screen_bounds(&self, x: f32, y: f32) -> ScaledRect {
|
||||
ScaledRect::new_from(
|
||||
self.scale_factor.clone(),
|
||||
(x, y, self.frame_width as f32, self.frame_height as f32),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sprite {
|
||||
scale_factor: Arc<AtomicF32>,
|
||||
image: Result<ImageId, String>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
impl Sprite {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
scale_factor: Arc::new(AtomicF32::new(1.0)),
|
||||
image: Err("No image loaded".to_owned()),
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
pub fn new(canvas: &mut Canvas<OpenGl>, data: &[u8], scale_factor: Arc<AtomicF32>) -> Self {
|
||||
let image = canvas
|
||||
.load_image_mem(data, ImageFlags::empty())
|
||||
.map_err(|e| format!("{e:?}"));
|
||||
if let Ok(image) = image {
|
||||
let (width, height) = canvas.image_size(image).unwrap_or_default();
|
||||
Self {
|
||||
scale_factor,
|
||||
image: Ok(image),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
scale_factor,
|
||||
image,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn draw(&self, canvas: &mut Canvas<OpenGl>, x: f32, y: f32, alpha: f32) {
|
||||
let factor = self.scale_factor.load(Ordering::Relaxed);
|
||||
|
||||
let screen_rect = Rect {
|
||||
x: x * factor,
|
||||
y: y * factor,
|
||||
width: self.width as f32 * factor,
|
||||
height: self.height as f32 * factor,
|
||||
};
|
||||
|
||||
let mut screen_path = Path::new();
|
||||
screen_path.rect(
|
||||
screen_rect.x,
|
||||
screen_rect.y,
|
||||
screen_rect.width,
|
||||
screen_rect.height,
|
||||
);
|
||||
if let Ok(image) = self.image {
|
||||
canvas.fill_path(
|
||||
&screen_path,
|
||||
&Paint::image(
|
||||
image,
|
||||
screen_rect.x,
|
||||
screen_rect.y,
|
||||
screen_rect.width,
|
||||
screen_rect.height,
|
||||
0.0,
|
||||
alpha,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn screen_bounds(&self, x: f32, y: f32) -> ScaledRect {
|
||||
ScaledRect::new_from(
|
||||
self.scale_factor.clone(),
|
||||
(x, y, self.width as f32, self.height as f32),
|
||||
)
|
||||
}
|
||||
}
|
||||
316
dsp/src/lib.rs
Normal file
316
dsp/src/lib.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
mod amplitude;
|
||||
mod biquad;
|
||||
mod comp;
|
||||
mod decibel;
|
||||
mod eq;
|
||||
mod freq_split;
|
||||
mod gui;
|
||||
mod meter;
|
||||
mod ring_buffer;
|
||||
mod smoother;
|
||||
mod traits;
|
||||
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, atomic::Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub use amplitude::Amplitude;
|
||||
pub use biquad::{BiquadFilter, BiquadFilterState, FilterMode, Slope};
|
||||
pub use comp::{Compressor, CompressorState};
|
||||
pub use decibel::Decibel;
|
||||
pub use eq::{Equalizer, EqualizerState};
|
||||
pub use freq_split::{FreqSplitter, FreqSplitterState};
|
||||
pub use gui::{SpriteSheet, Sprite};
|
||||
use nih_plug::prelude::AtomicF32;
|
||||
pub use traits::{FloatFormatter, IntFormatter, Lerp, Processor};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct Point {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
impl Point {
|
||||
pub const fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
impl From<(f32, f32)> for Point {
|
||||
fn from(value: (f32, f32)) -> Self {
|
||||
Point {
|
||||
x: value.0,
|
||||
y: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScaledPoint {
|
||||
pub factor: Arc<AtomicF32>,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
impl PartialEq for ScaledPoint {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.factor.load(Ordering::Acquire) == other.factor.load(Ordering::Acquire)
|
||||
&& self.x == other.x
|
||||
&& self.y == other.y
|
||||
}
|
||||
}
|
||||
impl ScaledPoint {
|
||||
pub fn new(factor: Arc<AtomicF32>) -> Self {
|
||||
Self {
|
||||
factor,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn new_from<P>(factor: Arc<AtomicF32>, point: P) -> Self
|
||||
where
|
||||
P: Into<Point>,
|
||||
{
|
||||
let point = point.into();
|
||||
Self {
|
||||
factor,
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
}
|
||||
}
|
||||
pub fn set<P>(&mut self, point: P)
|
||||
where
|
||||
P: Into<Point>,
|
||||
{
|
||||
let point = point.into();
|
||||
self.x = point.x;
|
||||
self.y = point.y;
|
||||
}
|
||||
pub fn set_scaled<P>(&mut self, point: P)
|
||||
where
|
||||
P: Into<Point>,
|
||||
{
|
||||
let factor = self.factor.load(Ordering::Acquire);
|
||||
let point = point.into();
|
||||
self.x = point.x / factor;
|
||||
self.y = point.y / factor;
|
||||
}
|
||||
pub fn as_point(&self) -> Point {
|
||||
let factor = self.factor.load(Ordering::Acquire);
|
||||
Point {
|
||||
x: self.x * factor,
|
||||
y: self.y * factor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct Rect {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
impl Rect {
|
||||
pub fn contains<P>(&self, value: P) -> bool
|
||||
where
|
||||
P: Into<Point>,
|
||||
{
|
||||
let value = value.into();
|
||||
value.x >= self.x
|
||||
&& value.y >= self.y
|
||||
&& value.x < self.x + self.width
|
||||
&& value.y < self.y + self.height
|
||||
}
|
||||
}
|
||||
impl From<(f32, f32, f32, f32)> for Rect {
|
||||
fn from(value: (f32, f32, f32, f32)) -> Self {
|
||||
Rect {
|
||||
x: value.0,
|
||||
y: value.1,
|
||||
width: value.2,
|
||||
height: value.3,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScaledRect {
|
||||
pub factor: Arc<AtomicF32>,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
impl PartialEq for ScaledRect {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.factor.load(Ordering::Acquire) == other.factor.load(Ordering::Acquire)
|
||||
&& self.x == other.x
|
||||
&& self.y == other.y
|
||||
&& self.width == other.width
|
||||
&& self.height == other.height
|
||||
}
|
||||
}
|
||||
impl ScaledRect {
|
||||
pub fn new(factor: Arc<AtomicF32>) -> Self {
|
||||
Self {
|
||||
factor,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn new_from<R>(factor: Arc<AtomicF32>, rect: R) -> Self
|
||||
where
|
||||
R: Into<Rect>,
|
||||
{
|
||||
let rect = rect.into();
|
||||
Self {
|
||||
factor,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
}
|
||||
pub fn as_rect(&self) -> Rect {
|
||||
let factor = self.factor.load(Ordering::Acquire);
|
||||
Rect {
|
||||
x: self.x * factor,
|
||||
y: self.y * factor,
|
||||
width: self.width * factor,
|
||||
height: self.height * factor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::ring_buffer::RingBuffer;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! math_ops_impl {
|
||||
($T:ty) => {
|
||||
/* impls for f32 */
|
||||
impl std::ops::Add<f32> for $T {
|
||||
type Output = $T;
|
||||
fn add(self, rhs: f32) -> Self::Output {
|
||||
Self(self.0 + rhs as f64)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<f32> for $T {
|
||||
type Output = $T;
|
||||
fn sub(self, rhs: f32) -> Self::Output {
|
||||
Self(self.0 - rhs as f64)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<f32> for $T {
|
||||
type Output = $T;
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Self(self.0 * rhs as f64)
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<f32> for $T {
|
||||
type Output = $T;
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
Self(self.0 / rhs as f64)
|
||||
}
|
||||
}
|
||||
impl std::ops::AddAssign<f32> for $T {
|
||||
fn add_assign(&mut self, rhs: f32) {
|
||||
self.0 += rhs as f64;
|
||||
}
|
||||
}
|
||||
impl std::ops::SubAssign<f32> for $T {
|
||||
fn sub_assign(&mut self, rhs: f32) {
|
||||
self.0 -= rhs as f64;
|
||||
}
|
||||
}
|
||||
impl std::ops::MulAssign<f32> for $T {
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
self.0 *= rhs as f64;
|
||||
}
|
||||
}
|
||||
impl std::ops::DivAssign<f32> for $T {
|
||||
fn div_assign(&mut self, rhs: f32) {
|
||||
self.0 /= rhs as f64;
|
||||
}
|
||||
}
|
||||
|
||||
/* impls for f64 */
|
||||
impl std::ops::Add<f64> for $T {
|
||||
type Output = $T;
|
||||
fn add(self, rhs: f64) -> Self::Output {
|
||||
Self(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<f64> for $T {
|
||||
type Output = $T;
|
||||
fn sub(self, rhs: f64) -> Self::Output {
|
||||
Self(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<f64> for $T {
|
||||
type Output = $T;
|
||||
fn mul(self, rhs: f64) -> Self::Output {
|
||||
Self(self.0 * rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<f64> for $T {
|
||||
type Output = $T;
|
||||
fn div(self, rhs: f64) -> Self::Output {
|
||||
Self(self.0 / rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::AddAssign<f64> for $T {
|
||||
fn add_assign(&mut self, rhs: f64) {
|
||||
self.0 += rhs;
|
||||
}
|
||||
}
|
||||
impl std::ops::SubAssign<f64> for $T {
|
||||
fn sub_assign(&mut self, rhs: f64) {
|
||||
self.0 -= rhs;
|
||||
}
|
||||
}
|
||||
impl std::ops::MulAssign<f64> for $T {
|
||||
fn mul_assign(&mut self, rhs: f64) {
|
||||
self.0 *= rhs;
|
||||
}
|
||||
}
|
||||
impl std::ops::DivAssign<f64> for $T {
|
||||
fn div_assign(&mut self, rhs: f64) {
|
||||
self.0 /= rhs;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct SampleRate(pub f64);
|
||||
impl SampleRate {
|
||||
pub fn as_samples<D>(&self, time: D) -> f64
|
||||
where
|
||||
D: Into<Duration>,
|
||||
{
|
||||
self.0 * time.into().as_secs_f64()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Ratio(pub f64, pub f64);
|
||||
impl Ratio {
|
||||
pub fn multiplier(&self) -> f64 {
|
||||
self.0 / self.1
|
||||
}
|
||||
}
|
||||
impl Display for Ratio {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!("{}:{} ({})", self.0, self.1, self.multiplier()))
|
||||
}
|
||||
}
|
||||
impl Debug for Ratio {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!("{}:{} ({})", self.0, self.1, self.multiplier()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rms<const N: usize>(sample_buffer: &RingBuffer<f64, N>, last_rms: f64) -> f64 {
|
||||
// RMS_0 = sqrt((1/n) * (x0^2 + x1^2 + x2^2 + ... + xN^2))
|
||||
// RMS_-1 = sqrt((1/n) * ( x1^2 + x2^2 + ... + xN^2 + x(N+1)^2))
|
||||
last_rms.powf(2.0)
|
||||
+ (1.0 / N as f64) * (sample_buffer[0].powf(2.0) - sample_buffer[N - 1].powf(2.0))
|
||||
}
|
||||
0
dsp/src/meter.rs
Normal file
0
dsp/src/meter.rs
Normal file
38
dsp/src/ring_buffer.rs
Normal file
38
dsp/src/ring_buffer.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
pub struct RingBuffer<T, const N: usize> {
|
||||
buffer: [T; N],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<T, const N: usize> RingBuffer<T, N>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self {
|
||||
buffer: [value; N],
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
pub const fn len(&self) -> usize {
|
||||
N
|
||||
}
|
||||
pub fn shift(&mut self) {
|
||||
self.index = (self.index + 1) % N;
|
||||
}
|
||||
|
||||
}
|
||||
impl<T, const N: usize> Index<usize> for RingBuffer<T, N> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.buffer[(self.index + index) % N]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> IndexMut<usize> for RingBuffer<T, N> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.buffer[(self.index + index) % N]
|
||||
}
|
||||
}
|
||||
50
dsp/src/smoother.rs
Normal file
50
dsp/src/smoother.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::SampleRate;
|
||||
|
||||
pub struct Smoother {
|
||||
sample_rate: SampleRate,
|
||||
attack: Duration,
|
||||
release: Duration,
|
||||
last_value: f64,
|
||||
}
|
||||
impl Smoother {
|
||||
pub const fn new(sample_rate: SampleRate) -> Self {
|
||||
Self {
|
||||
sample_rate,
|
||||
attack: Duration::ZERO,
|
||||
release: Duration::ZERO,
|
||||
last_value: 0.0,
|
||||
}
|
||||
}
|
||||
pub fn set_attack<D>(&mut self, duration: D)
|
||||
where
|
||||
D: Into<Duration>,
|
||||
{
|
||||
self.attack = duration.into();
|
||||
}
|
||||
pub fn attack<T>(&mut self, value: T) -> T
|
||||
where
|
||||
T: From<f64>,
|
||||
T: Into<f64>,
|
||||
{
|
||||
let a = (-2.2 / (self.attack.as_secs_f64() * self.sample_rate.0) + f64::EPSILON).exp();
|
||||
self.last_value = a * self.last_value + (1.0 - a) * value.into();
|
||||
(self.last_value + f64::EPSILON).into()
|
||||
}
|
||||
pub fn set_release<D>(&mut self, duration: D)
|
||||
where
|
||||
D: Into<Duration>,
|
||||
{
|
||||
self.release = duration.into();
|
||||
}
|
||||
pub fn release<T>(&mut self, value: T) -> T
|
||||
where
|
||||
T: From<f64>,
|
||||
T: Into<f64>,
|
||||
{
|
||||
let a = (-2.2 / (self.release.as_secs_f64() * self.sample_rate.0) + f64::EPSILON).exp();
|
||||
self.last_value = a * self.last_value + (1.0 - a) * value.into();
|
||||
(self.last_value + f64::EPSILON).into()
|
||||
}
|
||||
}
|
||||
68
dsp/src/traits.rs
Normal file
68
dsp/src/traits.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::{
|
||||
ops::{Add, Mul},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub trait FloatFormatter {
|
||||
fn v2s(digits: usize) -> Arc<dyn Fn(f32) -> String + Send + Sync>
|
||||
where
|
||||
Self: Sized;
|
||||
fn s2v() -> Arc<dyn Fn(&str) -> Option<f32> + Send + Sync>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
pub trait IntFormatter {
|
||||
fn v2s() -> Arc<dyn Fn(i32) -> String + Send + Sync>
|
||||
where
|
||||
Self: Sized;
|
||||
fn s2v() -> Arc<dyn Fn(&str) -> Option<i32> + Send + Sync>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Processor<I = f64, O = f64> {
|
||||
fn process(&mut self, sample: I) -> O;
|
||||
}
|
||||
|
||||
impl<I> Processor<f32, f32> for I
|
||||
where
|
||||
I: Processor<f64, f64>,
|
||||
{
|
||||
fn process(&mut self, sample: f32) -> f32 {
|
||||
self.process(sample as f64) as f32
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Lerp<F> {
|
||||
type Output;
|
||||
fn lerp(&self, to: Self, t: F) -> Self::Output;
|
||||
fn lerp_unbounded(&self, to: Self, t: F) -> Self::Output;
|
||||
}
|
||||
impl<T> Lerp<f64> for T
|
||||
where
|
||||
T: Mul<f64, Output = T>,
|
||||
<T as Mul<f64>>::Output: Add,
|
||||
Self: Copy,
|
||||
{
|
||||
type Output = <T as Add<Self>>::Output;
|
||||
fn lerp(&self, to: Self, t: f64) -> Self::Output {
|
||||
self.lerp_unbounded(to, t.clamp(0.0, 1.0))
|
||||
}
|
||||
fn lerp_unbounded(&self, to: Self, t: f64) -> Self::Output {
|
||||
*self * (1.0 - t) + to * t
|
||||
}
|
||||
}
|
||||
impl<T> Lerp<f32> for T
|
||||
where
|
||||
T: Mul<f32, Output = T>,
|
||||
<T as Mul<f32>>::Output: Add,
|
||||
Self: Copy,
|
||||
{
|
||||
type Output = <T as Add<Self>>::Output;
|
||||
fn lerp(&self, to: Self, t: f32) -> Self::Output {
|
||||
self.lerp_unbounded(to, t.clamp(0.0, 1.0))
|
||||
}
|
||||
fn lerp_unbounded(&self, to: Self, t: f32) -> Self::Output {
|
||||
*self * (1.0 - t) + to * t
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user