use serde::{Deserialize, Serialize};
use crate::dsl::{Adsr, KitStyle, SeqWave};
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct VoiceParams {
pub duty: Option<f32>,
pub fm_ratio: Option<f32>,
pub fm_index: Option<f32>,
pub fm_strike: Option<f32>,
pub pluck_decay: Option<f32>,
pub pluck_body: Option<f32>,
pub pluck_pick: Option<f32>,
pub pluck_tone: Option<f32>,
pub piano_hammer: Option<f32>,
pub piano_strike: Option<f32>,
pub piano_inharm: Option<f32>,
pub piano_detune: Option<f32>,
pub piano_decay: Option<f32>,
pub kit: Option<KitStyle>,
pub bass_cutoff: Option<f32>,
pub bass_env: Option<f32>,
pub bass_env_vel: Option<f32>,
pub bass_decay: Option<f32>,
pub bass_click: Option<f32>,
pub bass_body: Option<f32>,
pub bass_sub: Option<f32>,
pub bass_sub_ratio: Option<f32>,
pub bass_drive: Option<f32>,
pub bass_body_decay: Option<f32>,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Voice {
pub name: String,
pub wave: SeqWave,
pub env: Adsr,
pub gain: f32,
pub pan: f32,
pub reverb: f32,
pub swing: Option<f32>,
pub humanize: Option<f32>,
pub voice: VoiceParams,
}
#[deprecated(since = "1.6.0", note = "renamed to `catalog::Voice`")]
pub type Instrument = Voice;
impl Voice {
pub fn named(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn gain(mut self, gain: f32) -> Self {
self.gain = gain;
self
}
pub fn pan(mut self, pan: f32) -> Self {
self.pan = pan;
self
}
pub fn reverb(mut self, amount: f32) -> Self {
self.reverb = amount;
self
}
pub fn swing(mut self, swing: f32) -> Self {
self.swing = Some(swing);
self
}
pub fn humanize(mut self, humanize: f32) -> Self {
self.humanize = Some(humanize);
self
}
}
fn voice(name: &str, wave: SeqWave, env: Adsr) -> Voice {
Voice {
name: name.to_string(),
wave,
env,
gain: 1.0,
pan: 0.0,
reverb: 0.0,
swing: None,
humanize: None,
voice: VoiceParams::default(),
}
}
fn piano_env(release: f32) -> Adsr {
Adsr {
a: 0.002,
d: 0.0,
s: 1.0,
r: release,
punch: 0.0,
}
}
pub struct GrandPiano;
impl GrandPiano {
pub fn grand() -> Voice {
voice("grand piano", SeqWave::Piano, piano_env(0.35))
}
pub fn bright() -> Voice {
Voice {
gain: 1.05,
voice: piano_tone(&[
(Tone::Hammer, 1.6),
(Tone::Strike, 0.11),
(Tone::Decay, 1.05),
]),
..voice(
"bright piano",
SeqWave::Piano,
Adsr {
punch: 0.15,
r: 0.22,
..piano_env(0.22)
},
)
}
}
pub fn mellow() -> Voice {
Voice {
gain: 0.9,
voice: piano_tone(&[
(Tone::Hammer, 0.65),
(Tone::Strike, 0.14),
(Tone::Decay, 1.1),
]),
..voice("mellow piano", SeqWave::Piano, piano_env(0.6))
}
}
pub fn felt() -> Voice {
Voice {
gain: 0.85,
voice: piano_tone(&[
(Tone::Hammer, 0.35),
(Tone::Strike, 0.16),
(Tone::Decay, 0.8),
]),
..voice("felt piano", SeqWave::Piano, piano_env(0.5))
}
}
pub fn upright() -> Voice {
Voice {
voice: piano_tone(&[
(Tone::Hammer, 1.15),
(Tone::Strike, 0.115),
(Tone::Inharm, 1.6),
(Tone::Detune, 1.3),
(Tone::Decay, 0.7),
]),
..voice("upright piano", SeqWave::Piano, piano_env(0.12))
}
}
pub fn honky_tonk() -> Voice {
Voice {
voice: piano_tone(&[
(Tone::Hammer, 1.5),
(Tone::Strike, 0.11),
(Tone::Inharm, 1.7),
(Tone::Detune, 12.0),
(Tone::Decay, 0.65),
]),
..voice("honky-tonk piano", SeqWave::Piano, piano_env(0.12))
}
}
}
enum Tone {
Hammer,
Strike,
Inharm,
Detune,
Decay,
}
fn piano_tone(knobs: &[(Tone, f32)]) -> VoiceParams {
let mut v = VoiceParams::default();
for (knob, value) in knobs {
match knob {
Tone::Hammer => v.piano_hammer = Some(*value),
Tone::Strike => v.piano_strike = Some(*value),
Tone::Inharm => v.piano_inharm = Some(*value),
Tone::Detune => v.piano_detune = Some(*value),
Tone::Decay => v.piano_decay = Some(*value),
}
}
v
}
pub struct ElectricPiano;
impl ElectricPiano {
pub fn rhodes() -> Voice {
voice(
"electric piano",
SeqWave::Epiano,
Adsr {
a: 0.002,
d: 0.0,
s: 0.7,
r: 0.3,
punch: 0.0,
},
)
}
pub fn wurli() -> Voice {
voice(
"wurli",
SeqWave::Epiano,
Adsr {
a: 0.002,
d: 0.0,
s: 0.55,
r: 0.18,
punch: 0.1,
},
)
}
pub fn dx() -> Voice {
Voice {
voice: VoiceParams {
fm_ratio: Some(1.0),
fm_index: Some(3.5),
fm_strike: Some(0.4),
..VoiceParams::default()
},
..voice(
"dx piano",
SeqWave::Fm,
Adsr {
a: 0.002,
d: 0.0,
s: 0.6,
r: 0.3,
punch: 0.0,
},
)
}
}
}
pub struct Organ;
impl Organ {
pub fn tonewheel() -> Voice {
voice(
"organ",
SeqWave::Organ,
Adsr {
a: 0.005,
d: 0.0,
s: 1.0,
r: 0.08,
punch: 0.0,
},
)
}
pub fn rock() -> Voice {
voice(
"rock organ",
SeqWave::Organ,
Adsr {
a: 0.002,
d: 0.0,
s: 1.0,
r: 0.05,
punch: 0.2,
},
)
.gain(1.05)
}
}
pub struct Strings;
impl Strings {
pub fn ensemble() -> Voice {
voice(
"strings",
SeqWave::Strings,
Adsr {
a: 0.15,
d: 0.0,
s: 1.0,
r: 0.4,
punch: 0.0,
},
)
}
pub fn warm() -> Voice {
voice(
"warm strings",
SeqWave::Strings,
Adsr {
a: 0.3,
d: 0.0,
s: 1.0,
r: 0.7,
punch: 0.0,
},
)
.gain(0.95)
}
}
pub struct Bass;
impl Bass {
pub fn finger() -> Voice {
voice(
"bass",
SeqWave::Bass,
Adsr {
a: 0.005,
d: 0.1,
s: 0.9,
r: 0.12,
punch: 0.0,
},
)
}
pub fn pick() -> Voice {
Voice {
voice: bass_tone(
300.0, 800.0, 1200.0, 0.08, 2500.0, 0.75, 0.40, 1.0, 0.05, 1.6,
),
..voice(
"pick bass",
SeqWave::Bass,
Adsr {
a: 0.002,
d: 0.08,
s: 0.85,
r: 0.08,
punch: 0.25,
},
)
}
}
pub fn sub() -> Voice {
Voice {
voice: bass_tone(100.0, 150.0, 200.0, 0.25, 0.0, 0.22, 0.95, 1.0, 0.0, 4.0),
..voice(
"sub bass",
SeqWave::Bass,
Adsr {
a: 0.005,
d: 0.0,
s: 1.0,
r: 0.1,
punch: 0.0,
},
)
}
}
pub fn synth() -> Voice {
Voice {
voice: bass_tone(600.0, 1500.0, 800.0, 0.25, 0.0, 0.85, 0.35, 0.5, 0.35, 6.0),
..voice(
"synth bass",
SeqWave::Bass,
Adsr {
a: 0.003,
d: 0.06,
s: 0.8,
r: 0.08,
punch: 0.1,
},
)
}
}
}
#[allow(clippy::too_many_arguments)]
fn bass_tone(
cutoff: f32,
env: f32,
env_vel: f32,
decay: f32,
click: f32,
body: f32,
sub: f32,
sub_ratio: f32,
drive: f32,
body_decay: f32,
) -> VoiceParams {
VoiceParams {
bass_cutoff: Some(cutoff),
bass_env: Some(env),
bass_env_vel: Some(env_vel),
bass_decay: Some(decay),
bass_click: Some(click),
bass_body: Some(body),
bass_sub: Some(sub),
bass_sub_ratio: Some(sub_ratio),
bass_drive: Some(drive),
bass_body_decay: Some(body_decay),
..VoiceParams::default()
}
}
pub struct Guitar;
impl Guitar {
pub fn nylon() -> Voice {
Voice {
voice: VoiceParams {
pluck_decay: Some(0.90),
pluck_tone: Some(-0.35),
pluck_body: Some(0.55),
pluck_pick: Some(0.05),
..VoiceParams::default()
},
..voice("nylon guitar", SeqWave::Pluck, pluck_env(0.25))
}
}
pub fn steel() -> Voice {
Voice {
voice: VoiceParams {
pluck_decay: Some(0.965),
pluck_tone: Some(0.30),
pluck_body: Some(0.45),
pluck_pick: Some(0.30),
..VoiceParams::default()
},
..voice("steel guitar", SeqWave::Pluck, pluck_env(0.35))
}
}
pub fn electric() -> Voice {
Voice {
voice: VoiceParams {
pluck_decay: Some(0.99),
pluck_tone: Some(0.45),
pluck_pick: Some(0.25),
..VoiceParams::default() },
..voice("electric guitar", SeqWave::Pluck, pluck_env(0.5))
}
}
}
fn pluck_env(release: f32) -> Adsr {
Adsr {
a: 0.001,
d: 0.0,
s: 1.0,
r: release,
punch: 0.0,
}
}
pub struct Drums;
impl Drums {
pub fn acoustic() -> Voice {
drum_kit("drums", KitStyle::Acoustic)
}
pub fn classic() -> Voice {
Voice {
voice: VoiceParams::default(), ..drum_kit("classic drums", KitStyle::Classic)
}
}
pub fn electronic() -> Voice {
drum_kit("electronic drums", KitStyle::Electronic)
}
pub fn tr808() -> Voice {
drum_kit("808 drums", KitStyle::Eight08)
}
}
fn drum_kit(name: &str, style: KitStyle) -> Voice {
Voice {
voice: VoiceParams {
kit: Some(style),
..VoiceParams::default()
},
..voice(
name,
SeqWave::Kit,
Adsr {
a: 0.001,
d: 0.0,
s: 1.0,
r: 0.05,
punch: 0.0,
},
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_constructor_builds_a_distinct_named_instrument() {
let all = [
GrandPiano::grand(),
GrandPiano::bright(),
GrandPiano::mellow(),
GrandPiano::felt(),
GrandPiano::upright(),
GrandPiano::honky_tonk(),
ElectricPiano::rhodes(),
ElectricPiano::wurli(),
ElectricPiano::dx(),
Organ::tonewheel(),
Organ::rock(),
Strings::ensemble(),
Strings::warm(),
Bass::finger(),
Bass::pick(),
Bass::sub(),
Bass::synth(),
Guitar::nylon(),
Guitar::steel(),
Guitar::electric(),
Drums::acoustic(),
Drums::classic(),
Drums::electronic(),
Drums::tr808(),
];
for i in &all {
assert!(!i.name.is_empty());
assert!(i.gain > 0.0);
}
}
#[test]
fn grand_is_the_default_voice_and_variants_set_tone_knobs() {
assert_eq!(GrandPiano::grand().voice, VoiceParams::default());
assert_eq!(GrandPiano::bright().voice.piano_hammer, Some(1.6));
assert!(
GrandPiano::felt().voice.piano_hammer.unwrap() < 0.5,
"felt is soft"
);
assert_eq!(GrandPiano::honky_tonk().voice.piano_detune, Some(12.0));
assert_eq!(GrandPiano::upright().voice.piano_inharm, Some(1.6));
}
#[test]
fn bass_variants_set_tone_and_finger_is_the_default_voice() {
assert_eq!(Bass::finger().voice, VoiceParams::default());
assert_eq!(Bass::pick().voice.bass_click, Some(2500.0)); assert_eq!(Bass::synth().voice.bass_sub_ratio, Some(0.5)); assert_eq!(Bass::sub().wave, SeqWave::Bass); }
#[test]
fn guitar_variants_set_body_and_tone() {
assert_eq!(Guitar::nylon().voice.pluck_body, Some(0.55));
assert!(
Guitar::nylon().voice.pluck_tone.unwrap() < 0.0,
"nylon is dark"
);
assert!(
Guitar::steel().voice.pluck_tone.unwrap() > 0.0,
"steel is bright"
);
assert_eq!(
Guitar::electric().voice.pluck_body,
None,
"electric = solid body"
);
}
#[test]
fn guitar_variants_differ_in_ring_time() {
let n = Guitar::nylon().voice.pluck_decay.unwrap();
let s = Guitar::steel().voice.pluck_decay.unwrap();
let e = Guitar::electric().voice.pluck_decay.unwrap();
assert!(n < s && s < e, "nylon rings shortest, electric longest");
}
#[test]
fn builder_methods_override_defaults() {
let i = Bass::finger().named("low end").gain(0.8).pan(-0.3);
assert_eq!(i.name, "low end");
assert_eq!(i.gain, 0.8);
assert_eq!(i.pan, -0.3);
}
#[test]
fn round_trips_through_serde() {
let i = Guitar::steel();
let json = serde_json::to_string(&i).unwrap();
let back: Voice = serde_json::from_str(&json).unwrap();
assert_eq!(i, back);
}
}