pub mod io;
pub mod sound_builders;
pub mod sounds;
use fundsp::hacker::{midi_hz, shared, var, An, AudioUnit64, FrameMul, Net64, Shared, Var};
use std::fmt::Debug;
use std::sync::Arc;
use std::time::{Duration, Instant};
pub const MAX_MIDI_VALUE: u8 = 127;
pub const CONTROL_ON: f64 = 1.0;
pub const CONTROL_OFF: f64 = -1.0;
pub type SynthFunc = Arc<dyn Fn(&SharedMidiState) -> Box<dyn AudioUnit64> + Send + Sync>;
#[derive(Clone)]
pub struct SharedMidiState {
pitch: Shared<f64>,
velocity: Shared<f64>,
control: Shared<f64>,
pitch_bend: Shared<f64>,
}
impl Default for SharedMidiState {
fn default() -> Self {
Self {
pitch: Default::default(),
velocity: Default::default(),
control: shared(CONTROL_OFF),
pitch_bend: shared(1.0),
}
}
}
impl Debug for SharedMidiState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SharedMidiState")
.field("pitch", &self.pitch.value())
.field("velocity", &self.velocity.value())
.field("control", &self.control.value())
.field("pitch_bend", &self.pitch_bend.value())
.finish()
}
}
impl SharedMidiState {
pub fn bent_pitch(&self) -> Net64 {
Net64::wrap(Box::new(var(&self.pitch_bend) * var(&self.pitch)))
}
pub fn control_var(&self) -> An<Var<f64>> {
var(&self.control)
}
pub fn volume(&self, adjuster: Box<dyn AudioUnit64>) -> Net64 {
Net64::bin_op(
Net64::wrap(Box::new(var(&self.velocity))),
Net64::wrap(adjuster),
FrameMul::new(),
)
}
pub fn assemble_unpitched_sound(
&self,
synth: Box<dyn AudioUnit64>,
adjuster: Box<dyn AudioUnit64>,
) -> Box<dyn AudioUnit64> {
self.assemble_pitched_sound(
Box::new(Net64::pipe_op(self.bent_pitch(), Net64::wrap(synth))),
adjuster,
)
}
pub fn assemble_pitched_sound(
&self,
pitched_sound: Box<dyn AudioUnit64>,
adjuster: Box<dyn AudioUnit64>,
) -> Box<dyn AudioUnit64> {
Box::new(Net64::bin_op(
Net64::wrap(pitched_sound),
self.volume(adjuster),
FrameMul::new(),
))
}
pub fn on(&self, pitch: u8, velocity: u8) {
self.pitch.set_value(midi_hz(pitch as f64));
self.velocity
.set_value(velocity as f64 / MAX_MIDI_VALUE as f64);
self.control.set_value(CONTROL_ON);
}
pub fn off(&self) {
self.control.set_value(CONTROL_OFF);
}
pub fn bend(&self, bend: u16) {
self.pitch_bend.set_value(pitch_bend_factor(bend));
}
}
pub fn pitch_bend_factor(bend: u16) -> f64 {
2.0_f64.powf(semitone_from(bend) / 12.0)
}
pub fn semitone_from(bend: u16) -> f64 {
(bend as f64 - 8192.0) / 8192.0
}
#[derive(Debug)]
pub struct SoundTestResult {
total: f64,
count: usize,
min: f64,
max: f64,
}
impl SoundTestResult {
pub fn add_value(&mut self, value: f64) {
self.count += 1;
self.total += value;
if value < self.min {
self.min = value;
}
if value > self.max {
self.max = value;
}
}
pub fn report(&self) {
println!(
"{} ({}..{})",
self.total / self.count as f64,
self.min,
self.max
);
}
}
impl Default for SoundTestResult {
fn default() -> Self {
Self {
total: Default::default(),
count: Default::default(),
min: f64::MAX,
max: f64::MIN,
}
}
}
pub const SAMPLE_RATE: f64 = 44100.0;
pub const DURATION: f64 = 5.0;
const SLEEP_TIME: f64 = 1.0 / SAMPLE_RATE;
impl SoundTestResult {
pub fn test(
sound: Arc<dyn Fn(&SharedMidiState) -> Box<dyn AudioUnit64> + Send + Sync>,
) -> Self {
let mut result = Self::default();
let state = SharedMidiState::default();
let mut sound = sound(&state);
sound.reset(Some(SAMPLE_RATE));
let mut next_value = move || sound.get_mono();
let start = Instant::now();
state.on(60, 127);
while start.elapsed().as_secs_f64() < DURATION {
result.add_value(next_value());
std::thread::sleep(Duration::from_secs_f64(SLEEP_TIME));
}
result
}
}