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

355 lines
8.9 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 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);
}
}
}