pub mod io;
pub mod sound_builders;
pub mod sounds;
pub mod tunings;
use std::fmt::Debug;
use std::sync::Arc;
use std::time::{Duration, Instant};
use fundsp::math::midi_hz;
use fundsp::net::Net;
use fundsp::prelude::{An, AudioUnit, FrameMul};
use fundsp::prelude64::{shared, var};
use fundsp::shared::{Shared, Var};
pub const MAX_MIDI_VALUE: u8 = 127;
pub const NUM_MIDI_VALUES: usize = MAX_MIDI_VALUE as usize + 1;
pub const CONTROL_ON: f32 = 1.0;
pub const CONTROL_OFF: f32 = -1.0;
pub type SynthFunc = Arc<dyn Fn(&SharedMidiState) -> Box<dyn AudioUnit> + Send + Sync>;
#[derive(Clone)]
pub struct SharedMidiState {
pitch: Shared,
velocity: Shared,
control: Shared,
pitch_bend: Shared,
midi_to_hz: fn(f32) -> f32,
}
impl Default for SharedMidiState {
fn default() -> Self {
Self {
pitch: Default::default(),
velocity: Default::default(),
control: shared(CONTROL_OFF),
pitch_bend: shared(1.0),
midi_to_hz: midi_hz,
}
}
}
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 set_midi_to_hz(&mut self, midi_to_hz: fn(f32) -> f32) {
self.midi_to_hz = midi_to_hz;
}
pub fn bent_pitch(&self) -> Net {
Net::wrap(Box::new(var(&self.pitch_bend) * var(&self.pitch)))
}
pub fn control_var(&self) -> An<Var> {
var(&self.control)
}
pub fn volume(&self, adjuster: Box<dyn AudioUnit>) -> Net {
Net::binary(
Net::wrap(Box::new(var(&self.velocity))),
Net::wrap(adjuster),
FrameMul::new(),
)
}
pub fn assemble_unpitched_sound(
&self,
synth: Box<dyn AudioUnit>,
adjuster: Box<dyn AudioUnit>,
) -> Box<dyn AudioUnit> {
self.assemble_pitched_sound(
Box::new(Net::pipe(self.bent_pitch(), Net::wrap(synth))),
adjuster,
)
}
pub fn assemble_pitched_sound(
&self,
pitched_sound: Box<dyn AudioUnit>,
adjuster: Box<dyn AudioUnit>,
) -> Box<dyn AudioUnit> {
Box::new(Net::binary(
Net::wrap(pitched_sound),
self.volume(adjuster),
FrameMul::new(),
))
}
pub fn on(&self, pitch: u8, velocity: u8) {
self.pitch.set_value((self.midi_to_hz)(pitch as f32));
self.velocity
.set_value(velocity as f32 / MAX_MIDI_VALUE as f32);
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) -> f32 {
2.0_f32.powf(semitone_from(bend) / 12.0)
}
pub fn semitone_from(bend: u16) -> f32 {
(bend as f32 - 8192.0) / 8192.0
}
#[derive(Debug)]
pub struct SoundTestResult {
total: f32,
count: usize,
min: f32,
max: f32,
}
impl SoundTestResult {
pub fn add_value(&mut self, value: f32) {
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 f32,
self.min,
self.max
);
}
}
impl Default for SoundTestResult {
fn default() -> Self {
Self {
total: Default::default(),
count: Default::default(),
min: f32::MAX,
max: f32::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 AudioUnit> + Send + Sync>) -> Self {
let mut result = Self::default();
let state = SharedMidiState::default();
let mut sound = sound(&state);
sound.reset();
sound.set_sample_rate(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
}
}