concinnity_core/components/
audio_emitter.rs1use crate::components::AudioBus;
4use crate::ecs::AudioClipHandle;
5use crate::ecs::asset_id::AssetId;
6use crate::ecs::asset_id::de_opt_asset_ref;
7use crate::ecs::de_opt_audio_clip_handle;
8
9#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
28#[serde(default)]
29pub struct AudioEmitter {
30 #[serde(deserialize_with = "de_opt_audio_clip_handle")]
32 pub clip: Option<AudioClipHandle>,
33 pub position: [f32; 3],
35 pub volume: f32,
37 pub looping: bool,
39 #[serde(deserialize_with = "de_opt_asset_ref")]
41 pub prop: Option<AssetId>,
42 pub min_distance: f32,
44 pub max_distance: f32,
47 pub rolloff: Rolloff,
49 pub bus: Option<AudioBus>,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
55#[serde(rename_all = "lowercase")]
56pub enum Rolloff {
57 #[default]
59 Logarithmic,
60 Linear,
62 None,
64}
65
66impl Default for AudioEmitter {
67 fn default() -> Self {
68 Self {
69 clip: None,
70 position: [0.0; 3],
71 volume: 1.0,
72 looping: true,
73 prop: None,
74 min_distance: 1.0,
75 max_distance: 50.0,
76 rolloff: Rolloff::Logarithmic,
77 bus: None,
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn a_blank_emitter_loops_at_the_origin() {
88 let e = AudioEmitter::default();
90 assert!(e.looping);
91 assert_eq!(e.volume, 1.0);
92 assert_eq!(e.position, [0.0, 0.0, 0.0]);
93 assert!(e.clip.is_none());
94 assert!(e.prop.is_none());
95 assert_eq!(e.min_distance, 1.0);
96 assert_eq!(e.max_distance, 50.0);
97 assert_eq!(e.rolloff, Rolloff::Logarithmic);
98 assert!(e.bus.is_none());
99 }
100
101 #[test]
102 fn an_emitter_attached_to_a_prop_parses_and_round_trips_through_postcard() {
103 crate::test_support::install_resolvers();
104 let e: AudioEmitter = serde_json::from_str(
105 r#"{"clip":"hum","prop":"lamp","position":[1,2,3],"volume":0.5,"looping":false}"#,
106 )
107 .unwrap();
108 assert_eq!(e.clip, Some(AudioClipHandle(3)));
109 assert_eq!(e.prop, Some(AssetId(4)));
110 assert_eq!(e.position, [1.0, 2.0, 3.0]);
111 assert!(!e.looping);
112
113 let bytes = postcard::to_allocvec(&e).unwrap();
114 let back: AudioEmitter = postcard::from_bytes(&bytes).unwrap();
115 assert_eq!(back.clip, Some(AudioClipHandle(3)));
116 assert_eq!(back.prop, Some(AssetId(4)));
117 assert_eq!(back.volume, 0.5);
118 }
119
120 #[test]
121 fn authored_rolloff_and_bus_parse_and_round_trip() {
122 crate::test_support::install_resolvers();
123 let e: AudioEmitter = serde_json::from_str(
124 r#"{"clip":"hum","min_distance":2.5,"max_distance":80.0,"rolloff":"linear","bus":"voice"}"#,
125 )
126 .unwrap();
127 assert_eq!(e.min_distance, 2.5);
128 assert_eq!(e.max_distance, 80.0);
129 assert_eq!(e.rolloff, Rolloff::Linear);
130 assert_eq!(e.bus, Some(crate::components::AudioBus::Voice));
131
132 let bytes = postcard::to_allocvec(&e).unwrap();
133 let back: AudioEmitter = postcard::from_bytes(&bytes).unwrap();
134 assert_eq!(back.max_distance, 80.0);
135 assert_eq!(back.rolloff, Rolloff::Linear);
136 assert_eq!(back.bus, Some(crate::components::AudioBus::Voice));
137
138 let none: AudioEmitter = serde_json::from_str(r#"{"rolloff":"none"}"#).unwrap();
139 assert_eq!(none.rolloff, Rolloff::None);
140 }
141}