use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AudioDef {
#[serde(default)]
pub kind: AudioKind,
pub source: AudioSource,
#[serde(default = "default_volume")]
pub volume: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub radius: Option<f32>,
#[serde(default)]
pub rolloff: Rolloff,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum AudioKind {
#[default]
Ambient,
Sfx,
Music,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum AudioSource {
Wind {
#[serde(default = "default_half")]
speed: f32,
#[serde(default = "default_half")]
gustiness: f32,
},
Rain {
#[serde(default = "default_half")]
intensity: f32,
},
Forest {
#[serde(default = "default_half")]
bird_density: f32,
#[serde(default = "default_half")]
wind: f32,
},
Ocean {
#[serde(default = "default_half")]
wave_size: f32,
},
Cave {
#[serde(default = "default_half")]
drip_rate: f32,
#[serde(default = "default_half")]
resonance: f32,
},
Stream {
#[serde(default = "default_half")]
flow_rate: f32,
},
Water {
#[serde(default = "default_half")]
turbulence: f32,
},
Fire {
#[serde(default = "default_half")]
intensity: f32,
#[serde(default = "default_half")]
crackle: f32,
},
Hum {
#[serde(default = "default_hum_freq")]
frequency: f32,
#[serde(default = "default_half")]
warmth: f32,
},
WindEmitter {
#[serde(default = "default_one")]
pitch: f32,
},
Custom {
#[serde(default)]
waveform: WaveformType,
#[serde(default = "default_filter_cutoff")]
filter_cutoff: f32,
#[serde(default)]
filter_type: FilterType,
},
Abc { notation: String },
File {
path: String,
#[serde(default = "default_true")]
looping: bool,
},
Silence,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum WaveformType {
#[default]
Sine,
Saw,
Square,
WhiteNoise,
PinkNoise,
BrownNoise,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum FilterType {
#[default]
Lowpass,
Highpass,
Bandpass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum Rolloff {
#[default]
InverseSquare,
Linear,
Exponential,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ReverbParams {
#[serde(default = "default_half")]
pub room_size: f32,
#[serde(default = "default_half")]
pub damping: f32,
#[serde(default = "default_third")]
pub wet: f32,
}
fn default_volume() -> f32 {
1.0
}
fn default_half() -> f32 {
0.5
}
fn default_one() -> f32 {
1.0
}
fn default_hum_freq() -> f32 {
220.0
}
fn default_filter_cutoff() -> f32 {
1000.0
}
fn default_true() -> bool {
true
}
fn default_third() -> f32 {
0.33
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn audio_source_roundtrip_all_variants() {
let sources = vec![
AudioSource::Wind {
speed: 0.7,
gustiness: 0.3,
},
AudioSource::Rain { intensity: 0.8 },
AudioSource::Forest {
bird_density: 0.5,
wind: 0.3,
},
AudioSource::Ocean { wave_size: 1.0 },
AudioSource::Cave {
drip_rate: 0.4,
resonance: 0.6,
},
AudioSource::Stream { flow_rate: 0.5 },
AudioSource::Water { turbulence: 0.6 },
AudioSource::Fire {
intensity: 0.8,
crackle: 0.5,
},
AudioSource::Hum {
frequency: 440.0,
warmth: 0.7,
},
AudioSource::WindEmitter { pitch: 1.5 },
AudioSource::Custom {
waveform: WaveformType::PinkNoise,
filter_cutoff: 800.0,
filter_type: FilterType::Lowpass,
},
AudioSource::Abc {
notation: "X:1\nT:Test\nK:C\nCDEF|".to_string(),
},
AudioSource::File {
path: "music.ogg".to_string(),
looping: true,
},
AudioSource::Silence,
];
for src in &sources {
let json = serde_json::to_string(src).unwrap();
let back: AudioSource = serde_json::from_str(&json).unwrap();
assert_eq!(*src, back);
}
}
#[test]
fn audio_def_spatial_vs_global() {
let global = AudioDef {
kind: AudioKind::Ambient,
source: AudioSource::Wind {
speed: 0.5,
gustiness: 0.3,
},
volume: 0.8,
radius: None,
rolloff: Rolloff::InverseSquare,
};
assert!(global.radius.is_none());
let spatial = AudioDef {
radius: Some(20.0),
kind: AudioKind::Sfx,
..global.clone()
};
assert_eq!(spatial.radius, Some(20.0));
for def in [&global, &spatial] {
let json = serde_json::to_string(def).unwrap();
let back: AudioDef = serde_json::from_str(&json).unwrap();
assert_eq!(*def, back);
}
}
}