parent
4b697f6664
commit
03ea8cef87
@ -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<AudioContext, anyhow::Error> { |
||||
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::<f32>(&ac.device, &ac.config.into())?, |
||||
cpal::SampleFormat::I16 => run::<i16>(&ac.device, &ac.config.into())?, |
||||
cpal::SampleFormat::U16 => run::<u16>(&ac.device, &ac.config.into())?, |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
|
||||
fn run<T>(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<T>(output: &mut [T], channels: usize, instrument: &Arc<RwLock<Instrument>>) |
||||
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::<f32>(&((sample.left + sample.right) / 2.0)); |
||||
} else { |
||||
frame[0] = cpal::Sample::from::<f32>(&sample.left); |
||||
frame[1] = cpal::Sample::from::<f32>(&sample.right); |
||||
} |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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<HarmonicOscillator>, |
||||
/// 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<HarmonicOscillator>, 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 |
||||
} |
||||
} |
@ -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<AudioContext, anyhow::Error> { |
||||
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::<f32>(&ac.device, &ac.config.into())?, |
||||
cpal::SampleFormat::I16 => run::<i16>(&ac.device, &ac.config.into())?, |
||||
cpal::SampleFormat::U16 => run::<u16>(&ac.device, &ac.config.into())?, |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
|
||||
fn run<T>(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<T>(output: &mut [T], channels: usize, handle: &Arc<RwLock<Orchestra>>) |
||||
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::<f32>(&sample); |
||||
} else { |
||||
let sample = wg.sample_stereo(); |
||||
frame[0] = cpal::Sample::from::<f32>(&sample.left); |
||||
frame[1] = cpal::Sample::from::<f32>(&sample.right); |
||||
} |
||||
} |
||||
} |
@ -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<Instrument>, |
||||
/// 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<Instrument>, 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) |
||||
} |
||||
} |
||||
|
@ -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<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 |
||||
} |
||||
} |
@ -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<f32>, |
||||
// 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; |
||||
} |
||||
} |
@ -1,7 +1,9 @@ |
||||
mod beep; |
||||
|
||||
extern crate anyhow; |
||||
extern crate cpal; |
||||
|
||||
mod beep; |
||||
|
||||
fn main() -> Result<(), anyhow::Error> { |
||||
beep::beep() |
||||
} |
||||
|
Loading…
Reference in new issue