From 03ea8cef87d60b2aa2bc7b334ad2b5e4528c7415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Mon, 26 Oct 2020 00:15:13 +0100 Subject: [PATCH] wooow --- src/beep.rs | 152 ----------------- src/beep/hack.rs | 18 ++ src/beep/instrument.rs | 145 ++++++++++++++++ src/beep/mod.rs | 355 ++++++++++++++++++++++++++++++++++++++ src/beep/orchestra.rs | 86 ++++++++++ src/beep/osc.rs | 376 +++++------------------------------------ src/beep/sample.rs | 68 ++++++++ src/beep/tween.rs | 89 ++++++++++ src/main.rs | 4 +- 9 files changed, 808 insertions(+), 485 deletions(-) delete mode 100644 src/beep.rs create mode 100644 src/beep/hack.rs create mode 100644 src/beep/instrument.rs create mode 100644 src/beep/mod.rs create mode 100644 src/beep/orchestra.rs create mode 100644 src/beep/sample.rs create mode 100644 src/beep/tween.rs diff --git a/src/beep.rs b/src/beep.rs deleted file mode 100644 index 419b23c..0000000 --- a/src/beep.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::sync::Arc; - -use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use std::ops::{Add, AddAssign, Sub}; -use parking_lot::RwLock; -use std::time::Duration; -use std::thread; -use cpal::SampleFormat; -use crate::beep::osc::{Instrument, Oscillator}; - -pub mod osc; - -struct AudioContext { - device: cpal::Device, - sample_format: SampleFormat, - config: cpal::StreamConfig -} - -fn al_init() -> Result { - let host = cpal::default_host(); - - let device = host.default_output_device() - .ok_or_else(|| anyhow::anyhow!("No audio output device!"))?; - - let config = device.default_output_config()?; - - Ok(AudioContext { - device, - sample_format: config.sample_format(), - config: config.into() - }) -} - -pub fn beep() -> Result<(), anyhow::Error> { - let ac = al_init()?; - - match ac.sample_format { - cpal::SampleFormat::F32 => run::(&ac.device, &ac.config.into())?, - cpal::SampleFormat::I16 => run::(&ac.device, &ac.config.into())?, - cpal::SampleFormat::U16 => run::(&ac.device, &ac.config.into())?, - } - - Ok(()) -} - - -fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error> - where - T: cpal::Sample, -{ - let sample_rate = config.sample_rate.0 as f32; - let channels = config.channels as usize; - - println!("SR={}",sample_rate); - - // Produce a sinusoid of maximum amplitude. - - let mut ins = Instrument::new(vec![ - Oscillator::new(440.0, 1.0, sample_rate), - //Oscillator::new(441.0, 1.0, sample_rate), - ], sample_rate); - - let instrument = Arc::new(parking_lot::RwLock::new(ins)); - - let err_fn = |err| eprintln!("an error occurred on stream: {}", err); - - let m_instrument = instrument.clone(); - let stream = device.build_output_stream( - config, - move |data: &mut [T], _: &cpal::OutputCallbackInfo| { - write_data(data, channels, &m_instrument) - }, - err_fn, - )?; - stream.play()?; - - // instrument.write().normalize(false); - // instrument.write().set_amp(1.0); - // thread::sleep(Duration::from_millis(500)); - // - // instrument.write().set_amp(0.0); - // thread::sleep(Duration::from_millis(250)); - - instrument.write().set_amp(1.0); - instrument.write().normalize(true); - thread::sleep(Duration::from_millis(500)); - - instrument.write().set_amp(0.0); - thread::sleep(Duration::from_millis(250)); - - instrument.write().set_amp(0.5); - thread::sleep(Duration::from_millis(500)); - - instrument.write().set_amp(0.0); - thread::sleep(Duration::from_millis(250)); - - instrument.write().fade_amp(1.0, Duration::from_millis(10)); - instrument.write().waveforms[0].fade_balance(-1.0, Duration::from_millis(10)); - thread::sleep(Duration::from_millis(500)); - instrument.write().fade_amp(0.0, Duration::from_millis(10)); - thread::sleep(Duration::from_millis(500)); - - - /* - - //instrument.write().set_amp_fade(0.0, 4.0); - - thread::sleep(Duration::from_millis(1000)); - instrument.write().waveforms[1].fade_amp(0.0, Duration::from_millis(500)); - thread::sleep(Duration::from_millis(500)); - - instrument.write().waveforms[0].fade_balance(-1.0, Duration::from_millis(1000)); - thread::sleep(Duration::from_millis(1000)); - instrument.write().waveforms[0].fade_balance(1.0, Duration::from_millis(2000)); - instrument.write().waveforms[0].fade_freq(880.0, Duration::from_millis(1000)); - thread::sleep(Duration::from_millis(2000)); - instrument.write().waveforms[0].fade_balance(-1.0, Duration::from_millis(1000)); - instrument.write().waveforms[0].fade_amp(0.0, Duration::from_millis(1000)); - thread::sleep(Duration::from_millis(1000)); -*/ - - // for _ in 0..10 - // { - // instrument.write().harmonics[0].set_balance_fade(-1.0, 0.1); - // thread::sleep(Duration::from_millis(100)); - // instrument.write().harmonics[0].set_balance_fade(1.0, 0.1); - // thread::sleep(Duration::from_millis(100)); - // } - // - // instrument.write().harmonics[0].set_balance_fade(0.0, 0.5); - // instrument.write().harmonics[0].set_amp_fade(0.0, 0.05); - // thread::sleep(Duration::from_millis(200)); - - Ok(()) -} - -fn write_data(output: &mut [T], channels: usize, instrument: &Arc>) - where - T: cpal::Sample, -{ - let mut instrument = instrument.write(); - for frame in output.chunks_mut(channels) { - let sample = instrument.sample(); - - if channels == 1 { - frame[0] = cpal::Sample::from::(&((sample.left + sample.right) / 2.0)); - } else { - frame[0] = cpal::Sample::from::(&sample.left); - frame[1] = cpal::Sample::from::(&sample.right); - } - } -} diff --git a/src/beep/hack.rs b/src/beep/hack.rs new file mode 100644 index 0000000..332f509 --- /dev/null +++ b/src/beep/hack.rs @@ -0,0 +1,18 @@ +/// Work-around for `f32::clamp()` being unstable +pub(crate) 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 + } +} diff --git a/src/beep/instrument.rs b/src/beep/instrument.rs new file mode 100644 index 0000000..a4c7d3d --- /dev/null +++ b/src/beep/instrument.rs @@ -0,0 +1,145 @@ +use crate::beep::osc::HarmonicOscillator; +use crate::beep::tween::{Tween, GainScale}; +use std::time::Duration; +use crate::beep::sample::StereoSample; +use crate::beep::hack::StableClamp; + +/// Waveform generator with multiple frequencies / waveforms +#[derive(Clone,Debug)] +pub struct Instrument { + /// Waveforms to combine to produce the final sound + pub waveforms: Vec, + /// Balance fader -1..1 + balance: Tween, + /// 0-1 left gain + pub(crate) ratio_left : f32, + /// 0-1 right gain + pub(crate) ratio_right : f32, + /// Master gain + gain: Tween, +} + +impl Instrument { + pub fn new(mut waveforms: Vec, sample_rate: f32) -> Self { + for (n, o) in waveforms.iter_mut().enumerate() { + o.set_sample_rate(sample_rate); + o.set_freq_imm(sample_rate * (n as f32 + 1.0)); + } + + let mut o = Self { + waveforms, + balance: Tween { + actual: 0.0, + target: 0.0, + min: -1.0, + max: 1.0, + step: None, + sample_rate + }, + ratio_left: 0.7, + ratio_right: 0.7, + gain: Tween { + actual: 0.0, + target: 0.0, + step: None, + min: 0.0, + max: 1.0, + sample_rate + } + }; + o.update_side_ratios(); + o + } + + /// Update the pre-computed channel gain variables + pub fn update_side_ratios(&mut self) { + let angle = ((1.0 + self.balance.actual) / 2.0) * std::f32::consts::FRAC_PI_2; + self.ratio_left = angle.sin(); + self.ratio_right = angle.cos(); + } + + /// Get amplitude + 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); + } + + /// Set amplitude (0..1) immediate + pub fn set_amp_imm(&mut self, amp : f32) { + self.gain.set_immediate(amp); + } + + /// Fade amplitude over a given time + pub fn fade_amp(&mut self, amp : f32, time: Duration) { + self.gain.fade(amp, time); + } + + /// 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); + } + + /// Set balance (-1..1) immediate + pub fn set_balance_imm(&mut self, balance : f32) { + self.balance.set_immediate(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); + } + + pub fn get_peak_amplitude(&self) -> f32 { + self.waveforms.iter() + .fold((0.0), |acc, item| acc + item.gain.actual) + } + + /// Get base frequency value (1..22050) + pub fn get_freq(&self) -> f32 { + self.waveforms[0].get_freq() // TODO error checking + } + + /// Set base and harmonics frequency (1..22050), change at the end of the current waveform to reduce popping + pub fn set_freq(&mut self, freq : f32) { + let mut f = freq; + self.waveforms.iter_mut().for_each(|w| { + w.set_freq(f); + }); + } + + /// Set base and harmonics frequency (1..22050) immediate + pub fn set_freq_imm(&mut self, freq : f32) { + let mut f = freq; + self.waveforms.iter_mut().for_each(|w| { + w.set_freq_imm(f); + }); + } + + /// Fade base and harmonics to frequency (1..22050) over a given time + pub fn fade_freq(&mut self, freq : f32, time: Duration) { + let mut f = freq; + self.waveforms.iter_mut().for_each(|w| { + w.fade_freq(f, time); + }); + } + + /// Get a raw sample (with panning) + pub fn sample(&mut self) -> f32 { + self.balance.tick(); + self.gain.tick(); + + self.waveforms.iter_mut() + .fold(0.0, |mut acc, item| { + acc + item.sample() + }) * self.gain.actual + } +} diff --git a/src/beep/mod.rs b/src/beep/mod.rs new file mode 100644 index 0000000..3e0d570 --- /dev/null +++ b/src/beep/mod.rs @@ -0,0 +1,355 @@ +use std::sync::Arc; + +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use std::ops::{Add, AddAssign, Sub}; +use parking_lot::RwLock; +use std::time::Duration; +use std::{thread, panic}; +use cpal::SampleFormat; +use crate::beep::instrument::Instrument; +use crate::beep::orchestra::Orchestra; +use crate::beep::osc::HarmonicOscillator; +use std::panic::UnwindSafe; + +mod hack; +pub mod sample; +pub mod tween; +pub mod osc; +pub mod instrument; +pub mod orchestra; + +struct AudioContext { + device: cpal::Device, + sample_format: SampleFormat, + config: cpal::StreamConfig +} + +fn al_init() -> Result { + let host = cpal::default_host(); + + let device = host.default_output_device() + .ok_or_else(|| anyhow::anyhow!("No audio output device!"))?; + + let config = device.default_output_config()?; + + Ok(AudioContext { + device, + sample_format: config.sample_format(), + config: config.into() + }) +} + +pub fn beep() -> Result<(), anyhow::Error> { + let ac = al_init()?; + + match ac.sample_format { + cpal::SampleFormat::F32 => run::(&ac.device, &ac.config.into())?, + cpal::SampleFormat::I16 => run::(&ac.device, &ac.config.into())?, + cpal::SampleFormat::U16 => run::(&ac.device, &ac.config.into())?, + } + + Ok(()) +} + + +fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error> + where + T: cpal::Sample, +{ + let sr = config.sample_rate.0 as f32; + let channels = config.channels as usize; + + println!("SR={}", sr); + + // Produce a sinusoid of maximum amplitude. + + let ins1 = Instrument::new(vec![ + HarmonicOscillator::new(1.0, 1.0), + HarmonicOscillator::new(0.5, 5.0), + ], sr); + + let ins2 = Instrument::new(vec![ + HarmonicOscillator::new(1.0, 1.0), + HarmonicOscillator::new(0.5, 5.0), + ], sr); + + let mut orch = Orchestra::new(vec![ins1, ins2], sr); + orch.normalize(true); + + let handle = Arc::new(parking_lot::RwLock::new(orch)); + + let err_fn = |err| eprintln!("an error occurred on stream: {}", err); + + let m_handle = handle.clone(); + let stream = device.build_output_stream( + config, + move |data: &mut [T], _: &cpal::OutputCallbackInfo| { + write_data(data, channels, &m_handle) + }, + err_fn, + )?; + stream.play()?; + + const C3 : i32 = 28; + const C3X : i32 = 29; + const D3 : i32 = 30; + const D3X : i32 = 31; + const E3 : i32 = 32; + const F3 : i32 = 33; + const F3X : i32 = 34; + const G3 : i32 = 35; + const G3X : i32 = 36; + const A3 : i32 = 37; + const A3X : i32 = 38; + const B3 : i32 = 39; + + const C4 : i32 = 40; + const C4X : i32 = 41; + const D4 : i32 = 42; + const D4X : i32 = 43; + const E4 : i32 = 44; + const F4 : i32 = 45; + const F4X : i32 = 46; + const G4 : i32 = 47; + const G4X : i32 = 48; + const A4 : i32 = 49; + const A4X : i32 = 50; + const B4 : i32 = 51; + + const C5 : i32 = 52; + const C5X : i32 = 53; + const D5 : i32 = 54; + const D5X : i32 = 55; + const E5 : i32 = 56; + const F5 : i32 = 57; + const F5X : i32 = 58; + const G5 : i32 = 59; + const G5X : i32 = 60; + const A5 : i32 = 61; + const A5X : i32 = 62; + const B5 : i32 = 63; + + let key2freq = |key| { + match key { + // C0 => 16.35, + // C0X => 17.32, + // D0 => 18.35, + // D0X => 19.45, + // E0 => 20.60, + // F0 => 21.83, + // F0X => 23.12, + // G0 => 24.50, + // G0X => 25.96, + // A0 => 27.50, + // A0X => 29.14, + // B0 => 30.87, + // C1 => 32.70, + // C1X => 34.65, + // D1 => 36.71, + // D1X => 38.89, + // E1 => 41.20, + // F1 => 43.65, + // F1X => 46.25, + // G1 => 49.00, + // G1X => 51.91, + // A1 => 55.00, + // A1X => 58.27, + // B1 => 61.74, + // C2 => 65.41, + // C2X => 69.30, + // D2 => 73.42, + // D2X => 77.78, + // E2 => 82.41, + // F2 => 87.31, + // F2X => 92.50, + // G2 => 98.00, + // G2X => 103.83, + // A2 => 110.00, + // A2X => 116.54, + // B2 => 123.47, + C3 => 130.81, + C3X => 138.59, + D3 => 146.83, + D3X => 155.56, + E3 => 164.81, + F3 => 174.61, + F3X => 185.00, + G3 => 196.00, + G3X => 207.65, + A3 => 220.00, + A3X => 233.08, + B3 => 246.94, + C4 => 261.63, + C4X => 277.18, + D4 => 293.66, + D4X => 311.13, + E4 => 329.63, + F4 => 349.23, + F4X => 369.99, + G4 => 392.00, + G4X => 415.30, + A4 => 440.00, + A4X => 466.16, + B4 => 493.88, + C5 => 523.25, + C5X => 554.37, + D5 => 587.33, + D5X => 622.25, + E5 => 659.25, + F5 => 698.46, + F5X => 739.99, + G5 => 783.99, + G5X => 830.61, + A5 => 880.00, + A5X => 932.33, + B5 => 987.77, + // C6 => 1046.50, + // C6X => 1108.73, + // D6 => 1174.66, + // D6X => 1244.51, + // E6 => 1318.51, + // F6 => 1396.91, + // F6X => 1479.98, + // G6 => 1567.98, + // G6X => 1661.22, + // A6 => 1760.00, + // A6X => 1864.66, + // B6 => 1975.53, + // C7 => 2093.00, + // C7X => 2217.46, + // D7 => 2349.32, + // D7X => 2489.02, + // E7 => 2637.02, + // F7 => 2793.83, + // F7X => 2959.96, + // G7 => 3135.96, + // G7X => 3322.44, + // A7 => 3520.00, + // A7X => 3729.31, + // B7 => 3951.07, + // C8 => 4186.01, + // C8X => 4434.92, + // D8 => 4698.63, + // D8X => 4978.03, + // E8 => 5274.04, + // F8 => 5587.65, + // F8X => 5919.91, + // G8 => 6271.93, + // G8X => 6644.88, + // A8 => 7040.00, + // A8X => 7458.62, + // B8 => 7902.13, + + _ => 440.0 + } + }; + + // this is greensleeves painfully transcribed from the sheet here: + // https://www.guitarcommand.com/greensleeves-guitar-tab/ + + let alt = vec![ + (2, A4), + + (4, C5), + (2, D5), + (3, E5), (1, F5X), (2, E5), + + (4, D5), + (2, B4), + (3, G4), (1, A4), (2, B4), + + (4, C5), + (2, A4), + (3, A4), (1, G4X), (2, A4), + + (4, B4), + (2, G4X), + (4, E4), + ]; + + // Bass line, must be kept in sync with alt. Insert -1 notes for padding where needed. + let bass = vec![ + (2, -1), + + (6, A3), + (6, C4), + + (6, G3), + (6, G4), + + (6, A3), + (6, A4), + + (6, E3), + (6, E4), + ]; + + const D_ONOFF: Duration = Duration::from_millis(10); + + let bpm = 25.0; + + let mut ticks_alt = 0; + let mut ticks_bass = 0; + + let mut alt_iter = alt.iter(); + let mut bass_iter = bass.iter(); + loop { + let mut wg = handle.write(); + if ticks_alt == 0 { + if let Some((len, key)) = alt_iter.next() { + ticks_alt = *len; + if *key >= 0 { + let f = key2freq(*key); + wg.instruments[0].fade_amp(1.0, D_ONOFF); + wg.instruments[0].set_freq_imm(f); + } else { + wg.instruments[0].fade_amp(0.0, D_ONOFF); + } + } + } + if ticks_bass == 0 { + if let Some((len, key)) = bass_iter.next() { + ticks_bass = len - 1; + if *key >= 0 { + let f = key2freq(*key); + wg.instruments[1].fade_amp(0.5, D_ONOFF); + wg.instruments[1].set_freq_imm(f); + } else { + wg.instruments[1].fade_amp(0.0, D_ONOFF); + } + } + } + drop(wg); + if ticks_alt == 0 && ticks_bass == 0 { + break; + } + + thread::sleep(Duration::from_secs_f32((60.0/bpm) / 16.0)); + + if ticks_alt > 0 { + ticks_alt -= 1; + } + if ticks_bass > 0 { + ticks_bass -= 1; + } + } + + Ok(()) +} + +fn write_data(output: &mut [T], channels: usize, handle: &Arc>) + where + T: cpal::Sample, +{ + let mut wg = handle.write(); + for frame in output.chunks_mut(channels) { + if channels == 1 { + let sample = wg.sample_mono(); + frame[0] = cpal::Sample::from::(&sample); + } else { + let sample = wg.sample_stereo(); + frame[0] = cpal::Sample::from::(&sample.left); + frame[1] = cpal::Sample::from::(&sample.right); + } + } +} diff --git a/src/beep/orchestra.rs b/src/beep/orchestra.rs new file mode 100644 index 0000000..004b374 --- /dev/null +++ b/src/beep/orchestra.rs @@ -0,0 +1,86 @@ +use crate::beep::instrument::Instrument; +use crate::beep::tween::Tween; +use std::time::Duration; +use crate::beep::sample::StereoSample; +use crate::beep::hack::StableClamp; + +/// A set of instruments +pub struct Orchestra { + pub instruments: Vec, + /// Normalize the output to produce constant amplitude of 1.0 (instead of clipping) + normalize: bool, + /// Sample rate, used to calculate time based effects + sample_rate : f32, + /// Master gain + gain: Tween, +} + +impl Orchestra { + pub fn new(instruments: Vec, sample_rate: f32) -> Self { + let mut o = Self { + instruments, + normalize: false, + sample_rate, + gain: Tween { + actual: 1.0, + target: 1.0, + step: None, + min: 0.0, + max: 1.0, + sample_rate + } + }; + o + } + + pub fn normalize(&mut self, normalize: bool) { + self.normalize = normalize; + } + + pub fn get_amp(&self) -> f32 { + self.gain.actual() + } + + pub fn set_amp(&mut self, amp : f32) { + self.gain.set_immediate(amp); + } + + pub fn fade_amp(&mut self, amp : f32, time: Duration) { + self.gain.fade(amp, time); + } + + fn calc_gain(&self) -> f32 { + (if self.normalize { + 1.0 / ( + self.instruments.iter() + .fold(0.0, |acc, item| acc + item.get_peak_amplitude()) + ) + } else { 1.0 }) * self.gain.actual + } + + pub fn sample_stereo(&mut self) -> StereoSample { + let gain = self.calc_gain(); + + self.instruments + .iter_mut() + .fold(StereoSample::default(), |mut acc, item| { + let s = item.sample() * gain; + acc.left += s * item.ratio_left; + acc.right += s * item.ratio_left; + acc + }) + .clip() + } + + pub fn sample_mono(&mut self) -> f32 { + let gain = self.calc_gain(); + + self.instruments + .iter_mut() + .fold(0.0, |mut acc, item| { + acc + item.sample() * gain + }) + .clamp_(0.0, 1.0) + } +} + diff --git a/src/beep/osc.rs b/src/beep/osc.rs index 15c3371..5cc7e8a 100644 --- a/src/beep/osc.rs +++ b/src/beep/osc.rs @@ -1,184 +1,69 @@ -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, - // 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; - } -} +use crate::beep::tween::{Tween, GainScale}; +use crate::beep::sample::StereoSample; #[derive(Clone,Debug)] -pub struct Oscillator { +pub struct HarmonicOscillator { /// Amplitude fader 0..1 - gain: Tween, + pub(crate) 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, + /// Harmonic multiple + pub harmonic: f32, } -impl Oscillator { +impl HarmonicOscillator { /// New oscillator with a frequency and amplitude - pub fn new(freq: f32, amp: f32, sample_rate : f32) -> Self { + pub fn new(amp: f32, n: f32) -> Self { let mut o = Self { gain: Tween { actual: amp, target: amp, min: 0.0, max: 1.0, - step: None + step: None, + sample_rate: 0.0, }, freq: Tween { - actual: freq, - target: freq, + actual: 440.0, + target: 440.0, 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 + max: 20000.0, + step: None, + sample_rate: 0.0, }, phase_step: 0.0, - gain_left: 0.0, - gain_right: 0.0, - sample_rate, - phase: 0.0 + sample_rate: 0.0, + phase: 0.0, + harmonic: n, }; o.update_step(); - o.update_gains(1.0); o } + pub(crate) fn set_sample_rate(&mut self, sample_rate : f32) { + self.sample_rate = sample_rate; + self.gain.sample_rate = sample_rate; + self.freq.sample_rate = sample_rate; + self.freq.max = sample_rate / 2.0; + self.update_step(); + } + /// 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 @@ -189,24 +74,15 @@ impl Oscillator { 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 amplitude (0..1) immediate + pub fn set_amp_imm(&mut self, amp : f32) { + self.gain.set_immediate(amp); } - /// 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); + /// Fade to amplitude (0..1) over a given time + pub fn fade_amp(&mut self, amp : f32, time: Duration) { + self.gain.fade(amp, time); } /// Get frequency value (1..22050) @@ -216,27 +92,29 @@ impl Oscillator { /// 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); + self.freq.set_at_crossover(freq * self.harmonic); + self.update_step(); + } + + /// Set frequency (1..22050) immediate + pub fn set_freq_imm(&mut self, freq : f32) { + self.freq.set_immediate(freq * self.harmonic); + self.update_step(); } /// 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); + self.freq.fade(freq * self.harmonic, time); + self.update_step(); } /// Take a sample. Mutable to update faders. - pub fn sample(&mut self, gain_scale: f32) -> StereoSample { + pub fn sample(&mut self) -> f32 { 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; @@ -244,12 +122,6 @@ impl Oscillator { 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() { @@ -257,168 +129,8 @@ impl Oscillator { } } - 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, - /// 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, 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 for StereoSample { - type Output = Self; - - fn div(self, rhs: f32) -> Self::Output { - Self { - left: self.left / rhs, - right: self.right / rhs, - } - } -} - -impl Mul for StereoSample { - type Output = Self; - - fn mul(self, rhs: f32) -> Self::Output { - Self { - left: self.left * rhs, - right: self.right * rhs, - } + self.phase.sin() * self.gain.actual } } -impl AddAssign for StereoSample { - fn add_assign(&mut self, rhs: Self) { - *self = *self + rhs - } -} -impl DivAssign for StereoSample { - fn div_assign(&mut self, rhs: f32) { - *self = *self / rhs - } -} - -impl MulAssign for StereoSample { - fn mul_assign(&mut self, rhs: f32) { - *self = *self * rhs - } -} diff --git a/src/beep/sample.rs b/src/beep/sample.rs new file mode 100644 index 0000000..044fb2e --- /dev/null +++ b/src/beep/sample.rs @@ -0,0 +1,68 @@ +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign}; +use crate::beep::hack::StableClamp; + +#[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 for StereoSample { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self { + left: self.left / rhs, + right: self.right / rhs, + } + } +} + +impl Mul for StereoSample { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self { + left: self.left * rhs, + right: self.right * rhs, + } + } +} + +impl AddAssign for StereoSample { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs + } +} + +impl DivAssign for StereoSample { + fn div_assign(&mut self, rhs: f32) { + *self = *self / rhs + } +} + +impl MulAssign for StereoSample { + fn mul_assign(&mut self, rhs: f32) { + *self = *self * rhs + } +} diff --git a/src/beep/tween.rs b/src/beep/tween.rs new file mode 100644 index 0000000..2b1ffa4 --- /dev/null +++ b/src/beep/tween.rs @@ -0,0 +1,89 @@ +use crate::beep::hack::StableClamp; +use std::time::Duration; + +/// Scale function amplitude by a gain +pub 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 + } +} + + +/// Value fader +#[derive(Clone,Debug)] +pub(crate) struct Tween { + /// Actual value + pub actual: f32, + /// Target value, approached by adding "step" to :actual" every sample + pub target: f32, + /// Value added to "actual" per sample. None = set to target at the end of a cycle + pub step: Option, + // Min value + pub min: f32, + // Max value + pub max: f32, + /// Sample rate, used to calculate time based effects + pub sample_rate : f32, +} + + +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 + pub(crate) 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 + pub(crate) fn fade(&mut self, val: f32, time: Duration) { + self.target = val.clamp_(self.min, self.max); + let nsamples = (time.as_secs_f32() * self.sample_rate).ceil(); + self.step = Some((self.target - self.actual) / nsamples); + } + + /// Set value immediately + pub(crate) fn set_immediate(&mut self, val: f32) { + self.target = val.clamp_(self.min, self.max); + self.actual = self.target; + self.step = None; + } +} diff --git a/src/main.rs b/src/main.rs index e7d94cd..02decf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ -mod beep; + extern crate anyhow; extern crate cpal; +mod beep; + fn main() -> Result<(), anyhow::Error> { beep::beep() }