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/mod.rs

455 lines
11 KiB

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 mut alt_synth = Instrument::new(vec![
HarmonicOscillator::new(1.0, 1.0),
HarmonicOscillator::new(0.5, 5.0),
], sr);
alt_synth.set_balance_imm(0.3);
let mut bas_synth = Instrument::new(vec![
HarmonicOscillator::new(1.0, 1.0),
HarmonicOscillator::new(0.5, 5.0),
], sr);
bas_synth.set_intrinsic_gain(0.5);
bas_synth.set_balance_imm(-0.3);
let mut orch = Orchestra::new(vec![alt_synth, bas_synth], 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),
(2, A4),// end of 1st line
(4, C5),
(2, D5),
(3, E5), (1, F5X), (2, E5),
(4, D5),
(2, B4),
(3, G4), (1, A4), (2, B4),
(3, C5), (1, B4), (2, A4),
(3, G4X), (1, F4X), (2, G4X),
(6, A4),
(6, A4),// end of 2nd line
(6, G5),
(3, G5), (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),
(6, E4),// end of 3rd line
(6, G5),
(3, G5), (1, F5X), (2, E5),
(4, D5),
(2, B4),
(3, G4), (1, A4), (2, B4),
(3, C5), (1, B4), (2, A4),
(2, G4X), (2, F4X), (2, G4X),
(6, A4),
(4, A4),// end of 4th line
(2, -1),
];
// 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, G3),
(6, A3),
(6, A3),
(6, E3),
(6, E3),// end of 1st line
(6, A3),
(6, C4),
(6, G3),
(6, E3),
(6, A3),
(6, E3),
(6, A3),
(6, A3),// end of 2nd line
(6, C4),
(6, C4),
(6, G3),
(6, G3),
(6, A3),
(6, A3),
(6, E3),
(6, E3), // end of line 3
(6, C4),
(6, C4),
(6, G3),
(6, E3),
(6, A3),
(6, E3),
(6, A3),
(4, A3), // end of line 4
(2, -1),
];
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_amp2_imm(0.0);
wg.instruments[0].fade_amp2(1.0, D_ONOFF);
wg.instruments[0].set_amp_imm(1.0);
wg.instruments[0].set_freq_imm(f);
wg.instruments[0].fade_amp(0.0, Duration::from_secs_f32((60.0/bpm) / 16.0) * *len);
} else {
wg.instruments[0].fade_amp(0.0, D_ONOFF);
wg.instruments[0].fade_amp2(0.0, D_ONOFF);
}
}
}
if ticks_bass == 0 {
if let Some((len, key)) = bass_iter.next() {
ticks_bass = *len;
if *key >= 0 {
let f = key2freq(*key);
//wg.instruments[1].fade_amp(1.0, D_ONOFF);
// wg.instruments[1].set_amp_imm(1.0);
// wg.instruments[1].fade_amp(0.0, Duration::from_secs_f32((60.0/bpm) / 16.0) * *len);
// wg.instruments[1].set_freq_imm(f);
wg.instruments[1].set_amp2_imm(0.0);
wg.instruments[1].fade_amp2(1.0, D_ONOFF);
wg.instruments[1].set_amp_imm(1.0);
wg.instruments[1].set_freq_imm(f);
wg.instruments[1].fade_amp(0.0, Duration::from_secs_f32((60.0/bpm) / 16.0) * *len);
} else {
wg.instruments[1].fade_amp(0.0, D_ONOFF);
wg.instruments[1].fade_amp2(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);
}
}
}