#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Waveform {
Pulse,
Sawtooth,
Triangle,
Noise,
PulseSaw,
}
impl Waveform {
pub const ALL: &'static [Waveform] = &[
Waveform::Pulse,
Waveform::Sawtooth,
Waveform::Triangle,
Waveform::Noise,
Waveform::PulseSaw,
];
#[must_use]
pub fn name(self) -> &'static str {
match self {
Waveform::Pulse => "Pulse",
Waveform::Sawtooth => "Saw",
Waveform::Triangle => "Tri",
Waveform::Noise => "Noise",
Waveform::PulseSaw => "Pls+Saw",
}
}
#[must_use]
pub fn next(self) -> Self {
let idx = Self::ALL.iter().position(|&w| w == self).unwrap_or(0);
Self::ALL[(idx + 1) % Self::ALL.len()]
}
#[must_use]
pub fn prev(self) -> Self {
let idx = Self::ALL.iter().position(|&w| w == self).unwrap_or(0);
let len = Self::ALL.len();
Self::ALL[(idx + len - 1) % len]
}
}
#[allow(clippy::enum_variant_names)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FilterMode {
LowPass,
BandPass,
HighPass,
}
impl FilterMode {
pub const ALL: &'static [FilterMode] = &[
FilterMode::LowPass,
FilterMode::BandPass,
FilterMode::HighPass,
];
#[must_use]
pub fn name(self) -> &'static str {
match self {
FilterMode::LowPass => "LP",
FilterMode::BandPass => "BP",
FilterMode::HighPass => "HP",
}
}
#[must_use]
pub fn next(self) -> Self {
let idx = Self::ALL.iter().position(|&m| m == self).unwrap_or(0);
Self::ALL[(idx + 1) % Self::ALL.len()]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LfoTarget {
Pitch,
PulseWidth,
Cutoff,
Volume,
}
impl LfoTarget {
pub const ALL: &'static [LfoTarget] = &[
LfoTarget::Pitch,
LfoTarget::PulseWidth,
LfoTarget::Cutoff,
LfoTarget::Volume,
];
#[must_use]
pub fn name(self) -> &'static str {
match self {
LfoTarget::Pitch => "Pitch",
LfoTarget::PulseWidth => "PW",
LfoTarget::Cutoff => "Cutoff",
LfoTarget::Volume => "Volume",
}
}
#[must_use]
pub fn next(self) -> Self {
let idx = Self::ALL.iter().position(|&t| t == self).unwrap_or(0);
Self::ALL[(idx + 1) % Self::ALL.len()]
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OscParams {
pub waveform: Waveform,
pub pulse_width: f32,
pub detune: f32,
pub noise_mix: f32,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EnvParams {
pub attack: f32,
pub decay: f32,
pub sustain: f32,
pub release: f32,
pub env_reverse: bool,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FilterParams {
pub filter_mode: FilterMode,
pub cutoff: f32,
pub resonance: f32,
pub drive: f32,
}
#[allow(clippy::struct_field_names)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LfoParams {
pub lfo_rate: f32,
pub lfo_depth: f32,
pub lfo_target: LfoTarget,
}
#[allow(clippy::struct_field_names)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FxParams {
pub reverb_mix: f32,
pub reverb_size: f32,
pub reverb_damping: f32,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GlobalParams {
pub volume: f32,
pub glide_time: f32,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SynthParams {
pub osc: OscParams,
pub env: EnvParams,
pub filter: FilterParams,
pub lfo: LfoParams,
pub fx: FxParams,
pub global: GlobalParams,
}
impl Default for SynthParams {
fn default() -> Self {
Self {
osc: OscParams {
waveform: Waveform::Pulse,
pulse_width: 0.5,
detune: 0.0,
noise_mix: 0.0,
},
env: EnvParams {
attack: 0.01,
decay: 0.1,
sustain: 0.8,
release: 0.3,
env_reverse: false,
},
filter: FilterParams {
filter_mode: FilterMode::LowPass,
cutoff: 4000.0,
resonance: 0.3,
drive: 0.0,
},
lfo: LfoParams {
lfo_rate: 3.0,
lfo_depth: 0.0,
lfo_target: LfoTarget::Pitch,
},
fx: FxParams {
reverb_mix: 0.15,
reverb_size: 0.5,
reverb_damping: 0.5,
},
global: GlobalParams {
volume: 0.7,
glide_time: 0.05,
},
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Patch {
pub name: String,
pub params: SynthParams,
}
impl Patch {
pub fn new(name: impl Into<String>, params: SynthParams) -> Self {
Self {
name: name.into(),
params,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MidiNote(pub u8);
impl MidiNote {
pub const MIDDLE_C: Self = Self(60);
pub const A4: Self = Self(69);
#[must_use]
pub const fn new_clamped(v: u8) -> Self {
Self(if v > 127 { 127 } else { v })
}
#[must_use]
pub const fn as_u8(self) -> u8 {
self.0
}
}
impl From<u8> for MidiNote {
fn from(v: u8) -> Self {
Self(v)
}
}
impl Default for MidiNote {
fn default() -> Self {
Self::MIDDLE_C
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ChannelNo(pub u8);
impl ChannelNo {
pub const DEFAULT: Self = Self(0);
#[must_use]
pub const fn as_usize(self) -> usize {
self.0 as usize
}
}
impl From<u8> for ChannelNo {
fn from(v: u8) -> Self {
Self(v)
}
}
impl Default for ChannelNo {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DrumHit {
Kick,
HiHatClosed,
HiHatOpen,
}
#[derive(Default, Debug, Clone)]
pub enum AudioEvent {
#[default]
Panic,
NoteOn(MidiNote),
NoteOff(MidiNote),
LoadPatch(Box<SynthParams>),
Drum(DrumHit),
NoteOnChannel(ChannelNo, MidiNote),
NoteOffChannel(ChannelNo, MidiNote),
LoadPatchChannel(ChannelNo, Box<SynthParams>),
}