mittens_engine/engine/ecs/component/
audio_clip.rs1use super::Component;
2use crate::engine::ecs::ComponentId;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum AudioTriggerMode {
9 Retrigger,
11 OneShot,
13 Latched,
16}
17
18impl Default for AudioTriggerMode {
19 fn default() -> Self {
20 AudioTriggerMode::Retrigger
21 }
22}
23
24#[derive(Debug, Clone, PartialEq)]
31pub enum AudioClipLoadState {
32 Pending,
34 Loaded,
37 Failed(String),
42}
43
44impl Default for AudioClipLoadState {
45 fn default() -> Self {
46 AudioClipLoadState::Pending
47 }
48}
49
50#[derive(Debug, Clone)]
58pub struct AudioClipComponent {
59 pub uri: String,
60 pub trigger_mode: AudioTriggerMode,
61 pub load_state: AudioClipLoadState,
62 pub source_component: Option<ComponentId>,
66 pub start_beat: f64,
69 pub stop_beat: Option<f64>,
72 component: Option<ComponentId>,
73}
74
75impl AudioClipComponent {
76 pub fn new(uri: impl Into<String>) -> Self {
77 Self {
78 uri: uri.into(),
79 trigger_mode: AudioTriggerMode::Retrigger,
80 load_state: AudioClipLoadState::Pending,
81 source_component: None,
82 start_beat: 0.0,
83 stop_beat: None,
84 component: None,
85 }
86 }
87
88 pub fn instance_of(source: &AudioClipComponent) -> Self {
94 Self {
95 uri: source.uri.clone(),
96 trigger_mode: AudioTriggerMode::Retrigger,
97 load_state: AudioClipLoadState::Pending,
98 source_component: source.component,
99 start_beat: 0.0,
100 stop_beat: None,
101 component: None,
102 }
103 }
104
105 pub fn with_trigger_mode(mut self, mode: AudioTriggerMode) -> Self {
106 self.trigger_mode = mode;
107 self
108 }
109
110 pub fn with_start_beat(mut self, beat: f64) -> Self {
111 self.start_beat = beat;
112 self
113 }
114
115 pub fn with_stop_beat(mut self, beat: f64) -> Self {
116 self.stop_beat = Some(beat);
117 self
118 }
119
120 pub fn id(&self) -> Option<ComponentId> {
121 self.component
122 }
123
124 pub fn check_uri_exists(uri: &str) -> AudioClipLoadState {
127 if uri.is_empty() {
128 return AudioClipLoadState::Failed("empty uri".into());
129 }
130 match std::fs::metadata(uri) {
131 Ok(meta) if meta.is_file() => AudioClipLoadState::Loaded,
132 Ok(_) => AudioClipLoadState::Failed(format!("not a file: {uri}")),
133 Err(e) => AudioClipLoadState::Failed(format!("{uri}: {e}")),
134 }
135 }
136}
137
138impl Component for AudioClipComponent {
139 fn set_id(&mut self, component: ComponentId) {
140 self.component = Some(component);
141 }
142
143 fn name(&self) -> &'static str {
144 "audio_clip"
145 }
146
147 fn as_any(&self) -> &dyn std::any::Any {
148 self
149 }
150
151 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
152 self
153 }
154
155 fn init(&mut self, emit: &mut dyn crate::engine::ecs::SignalEmitter, component: ComponentId) {
156 emit.push_intent_now(
161 component,
162 crate::engine::ecs::IntentValue::RegisterAudioClip {
163 component_ids: vec![component],
164 },
165 );
166 }
167
168 fn to_mms_ast(
169 &self,
170 _world: &crate::engine::ecs::World,
171 ) -> crate::scripting::ast::ComponentExpression {
172 use crate::engine::ecs::component::ce_helpers::*;
173 let ctor = match self
174 .uri
175 .rsplit('.')
176 .next()
177 .map(str::to_ascii_lowercase)
178 .as_deref()
179 {
180 Some("wav") => "wav",
181 Some("opus") => "opus",
182 Some("ogg") => "ogg",
183 Some("mp3") => "mp3",
184 Some("flac") => "flac",
185 _ => "new",
186 };
187 let mut c = ce_call("AudioClip", ctor, vec![s(&self.uri)]);
188 match self.trigger_mode {
189 AudioTriggerMode::Retrigger => {}
190 AudioTriggerMode::OneShot => c = c.with_call("one_shot", vec![]),
191 AudioTriggerMode::Latched => c = c.with_call("latched", vec![]),
192 }
193 c
194 }
195}