You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
424 lines
11 KiB
424 lines
11 KiB
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign};
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use parking_lot::RwLock;
|
|
|
|
use hack::StableClamp;
|
|
|
|
mod hack {
|
|
/// Work-around for `f32::clamp()` being unstable
|
|
pub(super) trait StableClamp {
|
|
fn clamp_(self, min: f32, max: f32) -> f32;
|
|
}
|
|
|
|
impl StableClamp for f32 {
|
|
fn clamp_(self, min: f32, max: f32) -> f32 {
|
|
assert!(min <= max);
|
|
let mut x = self;
|
|
if x < min {
|
|
x = min;
|
|
}
|
|
if x > max {
|
|
x = max;
|
|
}
|
|
x
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Value fader
|
|
#[derive(Clone,Debug)]
|
|
struct Tween {
|
|
/// Actual value
|
|
actual: f32,
|
|
/// Target value, approached by adding "step" to :actual" every sample
|
|
target: f32,
|
|
/// Value added to "actual" per sample. None = set to target at the end of a cycle
|
|
step: Option<f32>,
|
|
// Min value
|
|
min: f32,
|
|
// Max value
|
|
max: f32,
|
|
}
|
|
|
|
/// Scale function amplitude by a gain
|
|
trait GainScale {
|
|
fn scale_gain(self, gain: f32) -> Self;
|
|
}
|
|
|
|
impl GainScale for f32 {
|
|
/// Scale by gain 0-1
|
|
fn scale_gain(self, gain: f32) -> Self {
|
|
// TODO what scaling to use?
|
|
// self * (1.0 + gain * 9.0).log10()
|
|
// self * gain.sqrt()
|
|
self * gain
|
|
}
|
|
}
|
|
|
|
impl Tween {
|
|
/// Update values for the next sample
|
|
pub fn tick(&mut self) {
|
|
if let Some(step) = self.step {
|
|
let actual0 = self.actual;
|
|
self.actual += step;
|
|
if (self.target > actual0 && self.target <= self.actual)
|
|
|| (self.target <= actual0 && self.target > self.actual)
|
|
{
|
|
self.step = None;
|
|
self.actual = self.target;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get actual value
|
|
pub fn actual(&self) -> f32 {
|
|
self.actual
|
|
}
|
|
|
|
/// Check if the change from actual to target is scheduled to happen instantly
|
|
/// at the end of a cycle.
|
|
pub fn is_change_scheduled_at_crossover(&self) -> bool {
|
|
self.target != self.actual
|
|
&& self.step.is_none()
|
|
}
|
|
|
|
/// Check if the fading is in progress
|
|
pub fn is_fading(&self) -> bool {
|
|
self.step.is_some()
|
|
}
|
|
|
|
/// Schedule change at crossover
|
|
fn set_at_crossover(&mut self, val: f32) {
|
|
self.target = val.clamp_(self.min, self.max);
|
|
self.step = None; // change at the earliest convenience
|
|
}
|
|
|
|
/// Start fading
|
|
fn fade(&mut self, val: f32, time: Duration, sample_rate: f32) {
|
|
self.target = val.clamp_(self.min, self.max);
|
|
let nsamples = (time.as_secs_f32() * sample_rate).ceil();
|
|
self.step = Some((self.target - self.actual) / nsamples);
|
|
}
|
|
|
|
/// Set value immediately
|
|
fn set_immediate(&mut self, val: f32) {
|
|
self.target = val;
|
|
self.actual = val;
|
|
self.step = None;
|
|
}
|
|
}
|
|
|
|
#[derive(Clone,Debug)]
|
|
pub struct Oscillator {
|
|
/// Amplitude fader 0..1
|
|
gain: Tween,
|
|
/// Frequency fader 1..22050
|
|
freq: Tween,
|
|
/// Balance fader -1..1
|
|
balance: Tween,
|
|
/// Left channel multiplier
|
|
gain_left: f32,
|
|
/// Right channel multiplier
|
|
gain_right: f32,
|
|
/// Sample rate (Hz)
|
|
sample_rate: f32,
|
|
/// Phase increment per sample
|
|
phase_step: f32,
|
|
/// Phase 0-TAU
|
|
phase: f32,
|
|
}
|
|
|
|
impl Oscillator {
|
|
/// New oscillator with a frequency and amplitude
|
|
pub fn new(freq: f32, amp: f32, sample_rate : f32) -> Self {
|
|
let mut o = Self {
|
|
gain: Tween {
|
|
actual: amp,
|
|
target: amp,
|
|
min: 0.0,
|
|
max: 1.0,
|
|
step: None
|
|
},
|
|
freq: Tween {
|
|
actual: freq,
|
|
target: freq,
|
|
min: 1.0,
|
|
max: sample_rate / 2.0,
|
|
step: None
|
|
},
|
|
balance: Tween {
|
|
actual: 0.0,
|
|
target: 0.0,
|
|
min: -1.0,
|
|
max: 1.0,
|
|
step: None
|
|
},
|
|
phase_step: 0.0,
|
|
gain_left: 0.0,
|
|
gain_right: 0.0,
|
|
sample_rate,
|
|
phase: 0.0
|
|
};
|
|
o.update_step();
|
|
o.update_gains(1.0);
|
|
o
|
|
}
|
|
|
|
/// Update the step variable
|
|
fn update_step(&mut self) {
|
|
self.phase_step = (self.freq.actual / self.sample_rate) * std::f32::consts::TAU;
|
|
}
|
|
|
|
/// Update the pre-computed channel gain variables
|
|
fn update_gains(&mut self, gain_scale: f32) {
|
|
let angle = ((1.0 + self.balance.actual) / 2.0) * std::f32::consts::FRAC_PI_2;
|
|
let act = self.gain.actual * gain_scale;
|
|
self.gain_left = act.scale_gain(angle.sin());
|
|
self.gain_right = act.scale_gain(angle.cos());
|
|
}
|
|
|
|
/// Get actual amplitude (0..1)
|
|
pub fn get_amp(&self) -> f32 {
|
|
self.gain.actual
|
|
}
|
|
|
|
/// Set amplitude (0..1), change at the end of the current waveform to reduce popping
|
|
pub fn set_amp(&mut self, amp : f32) {
|
|
self.gain.set_at_crossover(amp);
|
|
}
|
|
|
|
/// Fade to amplitude (0..1) over a given time
|
|
pub fn fade_amp(&mut self, amp : f32, time: Duration) {
|
|
self.gain.fade(amp, time, self.sample_rate);
|
|
}
|
|
|
|
/// Get balance value (-1..1)
|
|
pub fn get_balance(&self) -> f32 {
|
|
self.balance.actual
|
|
}
|
|
|
|
/// Set balance (-1..1), change at the end of the current waveform to reduce popping
|
|
pub fn set_balance(&mut self, balance : f32) {
|
|
self.balance.set_at_crossover(balance);
|
|
}
|
|
|
|
/// Fade to balance (-1..1) over a given time
|
|
pub fn fade_balance(&mut self, balance : f32, time: Duration) {
|
|
self.balance.fade(balance, time, self.sample_rate);
|
|
}
|
|
|
|
/// Get frequency value (1..22050)
|
|
pub fn get_freq(&self) -> f32 {
|
|
self.freq.actual
|
|
}
|
|
|
|
/// Set frequency (1..22050), change at the end of the current waveform to reduce popping
|
|
pub fn set_freq(&mut self, freq : f32) {
|
|
self.freq.set_at_crossover(freq);
|
|
}
|
|
|
|
/// Fade to frequency (1..22050) over a given time
|
|
pub fn fade_freq(&mut self, freq : f32, time: Duration) {
|
|
self.freq.fade(freq, time, self.sample_rate);
|
|
}
|
|
|
|
/// Take a sample. Mutable to update faders.
|
|
pub fn sample(&mut self, gain_scale: f32) -> StereoSample {
|
|
if self.freq.is_fading() {
|
|
self.freq.tick();
|
|
self.update_step();
|
|
}
|
|
|
|
if self.balance.is_fading() || self.gain.is_fading() {
|
|
self.balance.tick();
|
|
self.gain.tick();
|
|
self.update_gains(gain_scale);
|
|
}
|
|
|
|
let ph0 = self.phase;
|
|
self.phase = (self.phase + self.phase_step) % std::f32::consts::TAU;
|
|
|
|
// "zero crossing detection"
|
|
if self.phase < ph0 {
|
|
if self.gain.is_change_scheduled_at_crossover() {
|
|
self.gain.actual = self.gain.target;
|
|
self.update_gains(gain_scale);
|
|
}
|
|
|
|
if self.balance.is_change_scheduled_at_crossover() {
|
|
self.balance.actual = self.balance.target;
|
|
self.update_gains(gain_scale);
|
|
}
|
|
|
|
if self.freq.is_change_scheduled_at_crossover() {
|
|
self.freq.actual = self.freq.target;
|
|
}
|
|
}
|
|
|
|
let y = self.phase.sin() * self.gain.actual;
|
|
|
|
StereoSample {
|
|
left: self.gain_left * y,
|
|
right: self.gain_right * y,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Waveform generator with multiple frequencies / waveforms
|
|
#[derive(Clone,Debug)]
|
|
pub struct Instrument {
|
|
/// Waveforms to combine to produce the final sound
|
|
pub waveforms: Vec<Oscillator>,
|
|
/// Normalize the output to produce constant amplitude of 1.0 (instead of clipping)
|
|
normalize: bool,
|
|
sample_rate : f32,
|
|
master: Tween,
|
|
}
|
|
|
|
impl Instrument {
|
|
pub fn new(waveforms: Vec<Oscillator>, sample_rate: f32) -> Self {
|
|
let mut o = Self {
|
|
waveforms,
|
|
normalize: false,
|
|
sample_rate,
|
|
master: Tween {
|
|
actual: 1.0,
|
|
target: 1.0,
|
|
step: None,
|
|
min: 0.0,
|
|
max: 1.0
|
|
}
|
|
};
|
|
o
|
|
}
|
|
|
|
pub fn normalize(&mut self, normalize: bool) {
|
|
self.normalize = normalize;
|
|
|
|
// This is an artifact of how Oscillator works internally:
|
|
// The left/right gains are pre-computed and updated only when they change
|
|
if normalize {
|
|
self.force_normalize();
|
|
} else {
|
|
self.waveforms.iter_mut()
|
|
.for_each(|item| item.update_gains(1.0));
|
|
}
|
|
}
|
|
|
|
pub fn get_amp(&self) -> f32 {
|
|
self.master.actual()
|
|
}
|
|
|
|
pub fn set_amp(&mut self, amp : f32) {
|
|
self.master.set_immediate(amp);
|
|
}
|
|
|
|
pub fn fade_amp(&mut self, amp : f32, time: Duration) {
|
|
self.master.fade(amp, time, self.sample_rate);
|
|
}
|
|
|
|
fn force_normalize(&mut self) {
|
|
let gain_scale = 1.0 / self.waveforms.iter()
|
|
.fold((0.0), |acc, item| acc + item.gain.actual);
|
|
|
|
println!("Gain scale {}", gain_scale);
|
|
self.waveforms.iter_mut()
|
|
.for_each(|item| item.update_gains(gain_scale));
|
|
}
|
|
|
|
/// Get a sample (left, right)
|
|
pub fn sample(&mut self) -> StereoSample {
|
|
self.master.tick();
|
|
|
|
let gain_scaling = if self.normalize {
|
|
1.0 / self.waveforms.iter()
|
|
.fold((0.0), |acc, item| acc + item.gain.actual)
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
let (mut value, gains)= self.waveforms.iter_mut()
|
|
.fold((StereoSample::default(), 0.0), |mut acc, item| {
|
|
acc.0 += item.sample(gain_scaling);
|
|
acc.1 += item.gain.actual;
|
|
acc
|
|
});
|
|
|
|
if self.normalize {
|
|
value.left /= gains;
|
|
value.right /= gains;
|
|
}
|
|
|
|
value.left = value.left.scale_gain(self.master.actual).clamp_(-1.0, 1.0);
|
|
value.right = value.right.scale_gain(self.master.actual).clamp_(-1.0, 1.0);
|
|
value.clip()
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Clone, Copy)]
|
|
pub struct StereoSample {
|
|
pub left: f32,
|
|
pub right: f32,
|
|
}
|
|
|
|
impl StereoSample {
|
|
pub fn clip(self) -> Self {
|
|
Self {
|
|
left: self.left.clamp_(-1.0, 1.0),
|
|
right: self.right.clamp_(-1.0, 1.0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Add for StereoSample {
|
|
type Output = Self;
|
|
|
|
fn add(self, rhs: Self) -> Self::Output {
|
|
Self {
|
|
left: self.left + rhs.left,
|
|
right: self.right + rhs.right,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Div<f32> for StereoSample {
|
|
type Output = Self;
|
|
|
|
fn div(self, rhs: f32) -> Self::Output {
|
|
Self {
|
|
left: self.left / rhs,
|
|
right: self.right / rhs,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Mul<f32> for StereoSample {
|
|
type Output = Self;
|
|
|
|
fn mul(self, rhs: f32) -> Self::Output {
|
|
Self {
|
|
left: self.left * rhs,
|
|
right: self.right * rhs,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AddAssign<Self> for StereoSample {
|
|
fn add_assign(&mut self, rhs: Self) {
|
|
*self = *self + rhs
|
|
}
|
|
}
|
|
|
|
impl DivAssign<f32> for StereoSample {
|
|
fn div_assign(&mut self, rhs: f32) {
|
|
*self = *self / rhs
|
|
}
|
|
}
|
|
|
|
impl MulAssign<f32> for StereoSample {
|
|
fn mul_assign(&mut self, rhs: f32) {
|
|
*self = *self * rhs
|
|
}
|
|
}
|
|
|