wip audio synthesizer based on the rust crate cpal
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.
beeper/src/beep/osc.rs

425 lines
11 KiB

4 years ago
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
}
}