1use std::sync::Arc;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4use slotmap::Key;
5
6use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
7
8use crate::engine::ecs::component::AudioBufferSizeComponent;
9use crate::engine::ecs::component::AudioOscillator;
10use crate::engine::ecs::component::AudioOscillatorComponent;
11use crate::engine::ecs::component::AudioOutputComponent;
12use crate::engine::ecs::system::System;
13use crate::engine::ecs::system::clock_system::ClockDriver;
14use crate::engine::ecs::{ComponentId, World};
15use crate::engine::graphics::VisualWorld;
16use crate::engine::user_input::InputState;
17
18use crate::engine::ecs::system::audio_system_fundsp::AudioClockState;
19use crate::engine::ecs::system::audio_system_fundsp::AudioQueueItem;
20use crate::engine::ecs::system::audio_system_fundsp::AudioRtLocalState;
21use crate::engine::ecs::system::audio_system_fundsp::MAX_AUDIO_GRAPH_CHILDREN_PER_NODE;
22use crate::engine::ecs::system::audio_system_fundsp::MAX_AUDIO_GRAPH_NODES;
23use crate::engine::ecs::system::audio_system_fundsp::MAX_OSCS_PER_COMPONENT;
24use crate::engine::ecs::system::audio_system_fundsp::RtAudioGraph;
25use crate::engine::ecs::system::audio_system_fundsp::RtAudioGraphChild;
26use crate::engine::ecs::system::audio_system_fundsp::RtAudioGraphNode;
27use crate::engine::ecs::system::audio_system_fundsp::ScheduledGraphOp;
28use crate::engine::ecs::system::audio_system_fundsp::SynthRtState;
29use crate::engine::ecs::system::audio_system_fundsp::{
30 RtAudioGraphNodeKind, RtAudioGraphNodeState,
31};
32
33use heapless::Vec as HVec;
34use rtrb::Producer;
35
36use crate::engine::ecs::system::audio_graph_compiler::{AudioGraphCompiler, CompiledAudioGraph};
37
38pub use crate::engine::ecs::system::audio_system_fundsp::ScheduledGraphOp as ScheduledGraphOperation;
39pub use crate::engine::ecs::system::audio_system_fundsp::{AudioOp, ScheduledAudioOp};
40
41#[derive(Debug)]
44pub struct AudioClockDriver {
45 state: Arc<AudioClockState>,
46}
47
48impl AudioClockDriver {
49 fn new(state: Arc<AudioClockState>) -> Self {
50 Self { state }
51 }
52}
53
54impl ClockDriver for AudioClockDriver {
55 fn name(&self) -> &'static str {
56 "audio"
57 }
58
59 fn time_now_sec(&self) -> f64 {
60 let frames = self.state.frames_played.load(Ordering::Relaxed) as f64;
61 frames / (self.state.sample_rate_hz as f64).max(1.0)
62 }
63}
64
65pub struct AudioSystem {
71 stream: Option<cpal::Stream>,
72 driver: Option<Arc<dyn ClockDriver>>,
73
74 clock_state: Option<Arc<AudioClockState>>,
75
76 output_component: Option<ComponentId>,
77 desired_buffer_size_frames: Option<u32>,
78
79 pending_buffer_sizes: Vec<(ComponentId, u32)>,
80
81 pub oscillators: std::collections::HashMap<ComponentId, Vec<AudioOscillator>>,
83
84 dirty_audio_outputs: std::collections::BTreeSet<ComponentId>,
86
87 compiled_graphs_by_output: std::collections::HashMap<ComponentId, Vec<CompiledAudioGraph>>,
89
90 last_transport: Option<(f64, f64)>,
92
93 audio_tx: Option<Producer<AudioQueueItem>>,
94
95 decode_thread: Option<super::audio_decode_thread::DecodeThreadHandle>,
97 decode_complete_rx:
99 Option<std::sync::mpsc::Receiver<super::audio_decode_thread::LoadedClipMessage>>,
100 decode_complete_tx:
102 Option<std::sync::mpsc::Sender<super::audio_decode_thread::LoadedClipMessage>>,
103 playback_format: Option<super::audio_sample_format_convert::PlaybackFormat>,
106 clip_uris: std::collections::HashMap<ComponentId, String>,
110 asset_states: std::collections::HashMap<u64, AssetDecodeState>,
115 asset_subscribers: std::collections::HashMap<u64, Vec<ComponentId>>,
118 pending_clip_uploads: Vec<AudioQueueItem>,
121 pending_clip_instance_registrations: Vec<AudioQueueItem>,
124}
125
126#[derive(Debug, Clone)]
128enum AssetDecodeState {
129 Pending { uri: String },
130 Loaded { uri: String },
131 Failed { uri: String, reason: String },
132}
133
134impl Default for AudioSystem {
135 fn default() -> Self {
136 Self {
137 stream: None,
138 driver: None,
139 clock_state: None,
140 output_component: None,
141 desired_buffer_size_frames: None,
142 pending_buffer_sizes: Vec::new(),
143 oscillators: std::collections::HashMap::new(),
144 audio_tx: None,
145
146 dirty_audio_outputs: std::collections::BTreeSet::new(),
147 compiled_graphs_by_output: std::collections::HashMap::new(),
148
149 last_transport: None,
150
151 decode_thread: None,
152 decode_complete_rx: None,
153 decode_complete_tx: None,
154 playback_format: None,
155 clip_uris: std::collections::HashMap::new(),
156 asset_states: std::collections::HashMap::new(),
157 asset_subscribers: std::collections::HashMap::new(),
158 pending_clip_uploads: Vec::new(),
159 pending_clip_instance_registrations: Vec::new(),
160 }
161 }
162}
163
164impl std::fmt::Debug for AudioSystem {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 let driver_name = self.driver.as_ref().map(|d| d.name()).unwrap_or("<none>");
167 f.debug_struct("AudioSystem")
168 .field("active", &self.is_active())
169 .field("driver", &driver_name)
170 .finish()
171 }
172}
173
174impl AudioSystem {
175 pub fn new() -> Self {
176 Self::default()
177 }
178
179 pub fn is_active(&self) -> bool {
180 self.stream.is_some() && self.driver.is_some()
181 }
182
183 pub fn driver(&self) -> Option<Arc<dyn ClockDriver>> {
184 self.driver.clone()
185 }
186
187 pub fn update_transport_from_clock(&mut self, beat_now: f64, bpm: f64) {
188 if !beat_now.is_finite() || !bpm.is_finite() || bpm <= 0.0 {
189 return;
190 }
191
192 self.last_transport = Some((beat_now, bpm));
193
194 let Some(clock) = self.clock_state.as_ref() else {
197 return;
198 };
199
200 let frames = clock.frames_played.load(Ordering::Relaxed) as f64;
201 let sample_rate_hz = (clock.sample_rate_hz as f64).max(1.0);
202 let time_sec = frames / sample_rate_hz;
203 let beats_per_sec = bpm / 60.0;
204 let beat_offset = beat_now - time_sec * beats_per_sec;
205
206 let Some(tx) = self.audio_tx.as_mut() else {
207 return;
208 };
209 let _ = tx.push(AudioQueueItem::SetTransport { bpm, beat_offset });
210 }
211
212 pub fn schedule_graph_swap(&mut self, world: &World, source_root: ComponentId, beat: f64) {
213 if !beat.is_finite() {
214 return;
215 }
216
217 let Ok(compiled) = AudioGraphCompiler::compile(world, source_root) else {
218 return;
219 };
220
221 let Some(graph) = rt_graph_from_compiled(source_root, &compiled) else {
222 return;
223 };
224
225 let Some(tx) = self.audio_tx.as_mut() else {
226 return;
227 };
228
229 let target_component_ffi = source_root.data().as_ffi();
230 let msg = ScheduledGraphOp {
231 beat,
232 target_component_ffi,
233 graph,
234 };
235
236 let _ = tx.push(AudioQueueItem::GraphMessage(msg));
237 }
238
239 pub fn schedule_audio_op(&mut self, target_component: ComponentId, beat: f64, op: AudioOp) {
240 if !beat.is_finite() {
241 return;
242 }
243 let target_component_ffi = target_component.data().as_ffi();
244
245 let Some(tx) = self.audio_tx.as_mut() else {
246 return;
247 };
248
249 let event = ScheduledAudioOp {
250 beat,
251 target_component_ffi,
252 op,
253 };
254
255 let _ = tx.push(AudioQueueItem::Message(event));
257 }
258
259 pub fn register_audio_output(&mut self, world: &mut World, component: ComponentId) {
260 if world
261 .get_component_by_id_as::<AudioOutputComponent>(component)
262 .is_none()
263 {
264 return;
265 }
266
267 if self.stream.is_some() {
268 return;
269 }
270
271 self.output_component = Some(component);
272
273 let host = cpal::default_host();
274 let Some(device) = host.default_output_device() else {
275 return;
276 };
277
278 let Ok(supported_config) = device.default_output_config() else {
279 return;
280 };
281
282 self.desired_buffer_size_frames =
285 self.pending_buffer_sizes
286 .iter()
287 .rev()
288 .find_map(|(cid, frames)| {
289 if *frames == 0 {
290 return None;
291 }
292
293 let mut cur = Some(*cid);
295 while let Some(c) = cur {
296 if c == component {
297 return Some(*frames);
298 }
299 cur = world.parent_of(c);
300 }
301 None
302 });
303
304 let sample_rate_hz = supported_config.sample_rate().0;
305 let channels = supported_config.channels() as u64;
306 let sample_format = supported_config.sample_format();
307
308 let mut stream_config: cpal::StreamConfig = supported_config.into();
309 if let Some(frames) = self.desired_buffer_size_frames {
310 if frames > 0 {
311 stream_config.buffer_size = cpal::BufferSize::Fixed(frames);
312 }
313 }
314 let state = Arc::new(AudioClockState {
315 sample_rate_hz,
316 frames_played: AtomicU64::new(0),
317 });
318 let mut synth_state = SynthRtState::default();
319 let err_fn = |err| eprintln!("[AudioSystem] stream error: {err}");
320
321 let sample_rate_hz_f32 = (sample_rate_hz as f32).max(1.0);
322
323 fn f32_to_f32(s: f32) -> f32 {
324 s
325 }
326
327 fn f32_to_i16(s: f32) -> i16 {
328 (s * i16::MAX as f32)
329 .round()
330 .clamp(i16::MIN as f32, i16::MAX as f32) as i16
331 }
332
333 fn f32_to_u16(s: f32) -> u16 {
334 ((s * 0.5 + 0.5) * u16::MAX as f32)
335 .round()
336 .clamp(0.0, u16::MAX as f32) as u16
337 }
338
339 use crate::engine::ecs::system::audio_system_fundsp::AUDIO_QUEUE_CAP;
340 use crate::engine::ecs::system::audio_system_fundsp::render_buffer;
341
342 let (tx, rx) = rtrb::RingBuffer::<AudioQueueItem>::new(AUDIO_QUEUE_CAP);
344 self.audio_tx = Some(tx);
345
346 self.playback_format = Some(super::audio_sample_format_convert::PlaybackFormat {
349 sample_rate: sample_rate_hz,
350 channels: channels as u16,
351 });
352
353 if let Some(tx) = self.audio_tx.as_mut() {
355 for (cid, list) in self.oscillators.iter() {
356 let component_ffi = cid.data().as_ffi();
357 let mut hv = heapless::Vec::<AudioOscillator, MAX_OSCS_PER_COMPONENT>::new();
358 for osc in list.iter().take(MAX_OSCS_PER_COMPONENT) {
359 let _ = hv.push(osc.clone());
360 }
361 let _ = tx.push(AudioQueueItem::ReplaceOscillators {
362 target_component_ffi: component_ffi,
363 oscillators: hv,
364 });
365 }
366 for item in self.pending_clip_uploads.drain(..) {
368 let _ = tx.push(item);
369 }
370 for item in self.pending_clip_instance_registrations.drain(..) {
373 let _ = tx.push(item);
374 }
375 }
376
377 let stream = match sample_format {
378 cpal::SampleFormat::F32 => {
379 let state_for_cb = state.clone();
380 let mut rx = rx;
381 let mut rt = AudioRtLocalState::default();
382 device
383 .build_output_stream(
384 &stream_config,
385 move |data: &mut [f32], _info| {
386 render_buffer(
387 data,
388 channels,
389 sample_rate_hz,
390 sample_rate_hz_f32,
391 &state_for_cb,
392 &mut rx,
393 &mut rt,
394 &mut synth_state,
395 f32_to_f32,
396 );
397 },
398 err_fn,
399 None,
400 )
401 .ok()
402 }
403 cpal::SampleFormat::I16 => {
404 let state_for_cb = state.clone();
405 let mut rx = rx;
406 let mut rt = AudioRtLocalState::default();
407 device
408 .build_output_stream(
409 &stream_config,
410 move |data: &mut [i16], _info| {
411 render_buffer(
412 data,
413 channels,
414 sample_rate_hz,
415 sample_rate_hz_f32,
416 &state_for_cb,
417 &mut rx,
418 &mut rt,
419 &mut synth_state,
420 f32_to_i16,
421 );
422 },
423 err_fn,
424 None,
425 )
426 .ok()
427 }
428 cpal::SampleFormat::U16 => {
429 let state_for_cb = state.clone();
430 let mut rx = rx;
431 let mut rt = AudioRtLocalState::default();
432 device
433 .build_output_stream(
434 &stream_config,
435 move |data: &mut [u16], _info| {
436 render_buffer(
437 data,
438 channels,
439 sample_rate_hz,
440 sample_rate_hz_f32,
441 &state_for_cb,
442 &mut rx,
443 &mut rt,
444 &mut synth_state,
445 f32_to_u16,
446 );
447 },
448 err_fn,
449 None,
450 )
451 .ok()
452 }
453 _ => None,
454 };
455
456 let Some(stream) = stream else {
457 return;
458 };
459
460 if let Err(e) = stream.play() {
461 eprintln!("[AudioSystem] failed to play stream: {e}");
462 return;
463 }
464
465 let state_for_driver = state.clone();
466 self.driver = Some(Arc::new(AudioClockDriver::new(state_for_driver)));
467 self.clock_state = Some(state);
468 self.stream = Some(stream);
469
470 self.dirty_audio_outputs.insert(component);
472
473 let sources = collect_audio_oscillator_roots(world, component);
476 if !sources.is_empty() {
477 let (beat_now, bpm) = self.last_transport.unwrap_or((0.0, 120.0));
478 let beats_per_sec = bpm / 60.0;
479 let beat_epsilon = (beats_per_sec * 0.010).max(0.0); let beat = beat_now + beat_epsilon;
481 for src in sources {
482 self.schedule_graph_swap(world, src, beat);
483 }
484 }
485 }
486
487 pub fn register_audio_oscillator(&mut self, world: &mut World, component: ComponentId) {
488 let Some(c) = world.get_component_by_id_as::<AudioOscillatorComponent>(component) else {
489 return;
490 };
491
492 let list = c.oscillators.clone();
493 self.oscillators.insert(component, list.clone());
494
495 let Some(tx) = self.audio_tx.as_mut() else {
496 return;
497 };
498 let component_ffi = component.data().as_ffi();
499
500 let mut hv = heapless::Vec::<AudioOscillator, MAX_OSCS_PER_COMPONENT>::new();
501 for osc in list.iter().take(MAX_OSCS_PER_COMPONENT) {
502 let _ = hv.push(osc.clone());
503 }
504
505 let _ = tx.push(AudioQueueItem::ReplaceOscillators {
506 target_component_ffi: component_ffi,
507 oscillators: hv,
508 });
509
510 self.mark_audio_graph_dirty(world, component);
512 }
513
514 pub fn register_audio_buffer_size(&mut self, world: &mut World, component: ComponentId) {
515 let Some(c) = world.get_component_by_id_as::<AudioBufferSizeComponent>(component) else {
516 return;
517 };
518 if c.frames == 0 {
519 return;
520 }
521
522 self.pending_buffer_sizes.push((component, c.frames));
523
524 let Some(output) = self.output_component else {
525 return;
526 };
527
528 let mut cur = Some(component);
530 let mut attached = false;
531 while let Some(cid) = cur {
532 if cid == output {
533 attached = true;
534 break;
535 }
536 cur = world.parent_of(cid);
537 }
538 if !attached {
539 return;
540 }
541
542 self.desired_buffer_size_frames = Some(c.frames);
543
544 if self.stream.is_some() {
546 self.stream = None;
547 self.driver = None;
548 self.clock_state = None;
549 self.audio_tx = None;
550 self.register_audio_output(world, output);
551 }
552 }
553}
554
555impl AudioSystem {
556 pub fn mark_audio_graph_dirty(&mut self, world: &World, component: ComponentId) {
560 let mut cur = Some(component);
562 while let Some(cid) = cur {
563 if world
564 .get_component_by_id_as::<AudioOutputComponent>(cid)
565 .is_some()
566 {
567 self.dirty_audio_outputs.insert(cid);
568 return;
569 }
570 cur = world.parent_of(cid);
571 }
572 }
573
574 pub fn rebuild_dirty_audio_graphs(&mut self, world: &World) {
577 if self.dirty_audio_outputs.is_empty() {
578 return;
579 }
580
581 let outputs: Vec<ComponentId> = self.dirty_audio_outputs.iter().copied().collect();
582 self.dirty_audio_outputs.clear();
583
584 for output in outputs {
585 let sources = collect_audio_oscillator_roots(world, output);
586
587 let mut compiled = Vec::new();
588 for &src in sources.iter() {
589 if let Ok(g) = AudioGraphCompiler::compile(world, src) {
590 compiled.push(g);
591 }
592 }
593
594 self.compiled_graphs_by_output.insert(output, compiled);
596
597 if self.audio_tx.is_some() {
601 let Some((beat_now, bpm)) = self.last_transport else {
602 continue;
604 };
605 let beats_per_sec = bpm / 60.0;
606 let beat_epsilon = (beats_per_sec * 0.001).max(0.0); let beat = beat_now + beat_epsilon;
608
609 for &src in sources.iter() {
610 self.schedule_graph_swap(world, src, beat);
611 }
612 }
613 }
614 }
615}
616
617fn rt_graph_from_compiled(
618 source_root: ComponentId,
619 compiled: &CompiledAudioGraph,
620) -> Option<RtAudioGraph> {
621 fn kind_and_state(
622 k: &crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind,
623 ) -> (RtAudioGraphNodeKind, RtAudioGraphNodeState) {
624 match *k {
625 crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind::OscillatorSource { .. } => {
626 (RtAudioGraphNodeKind::OscillatorSource, Default::default())
627 }
628 crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind::ClipSource => {
629 (RtAudioGraphNodeKind::ClipSource, Default::default())
630 }
631 crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind::Gain { gain } => {
632 (RtAudioGraphNodeKind::Gain { gain }, Default::default())
633 }
634 crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind::LowPass {
635 cutoff_hz,
636 resonance,
637 } => (
638 RtAudioGraphNodeKind::LowPass {
639 cutoff_hz,
640 resonance,
641 },
642 Default::default(),
643 ),
644 crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind::BandPass {
645 center_hz,
646 bandwidth_octaves,
647 resonance,
648 } => (
649 RtAudioGraphNodeKind::BandPass {
650 center_hz,
651 bandwidth_octaves,
652 resonance,
653 },
654 Default::default(),
655 ),
656 crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind::HighPass {
657 cutoff_hz,
658 resonance,
659 } => (
660 RtAudioGraphNodeKind::HighPass {
661 cutoff_hz,
662 resonance,
663 },
664 Default::default(),
665 ),
666 crate::engine::ecs::system::audio_graph_compiler::AudioGraphNodeKind::Limiter {
667 attack_ms,
668 release_ms,
669 threshold,
670 } => {
671 let mut st: RtAudioGraphNodeState = Default::default();
672 st.limiter_attack_ms = attack_ms;
673 st.limiter_release_ms = release_ms;
674 st.limiter_threshold = threshold;
675 (RtAudioGraphNodeKind::Limiter, st)
676 }
677 }
678 }
679
680 fn build(
681 node: &crate::engine::ecs::system::audio_graph_compiler::AudioGraphNode,
682 nodes: &mut HVec<RtAudioGraphNode, MAX_AUDIO_GRAPH_NODES>,
683 ) -> Option<u8> {
684 let idx = nodes.len();
685 if idx >= MAX_AUDIO_GRAPH_NODES {
686 return None;
687 }
688
689 let (kind, state) = kind_and_state(&node.kind);
690
691 nodes
692 .push(RtAudioGraphNode {
693 component_ffi: node.component.data().as_ffi(),
694 kind,
695 state,
696 children: HVec::<RtAudioGraphChild, MAX_AUDIO_GRAPH_CHILDREN_PER_NODE>::new(),
697 })
698 .ok()?;
699
700 let idx_u8 = idx as u8;
701 for (i, ch) in node.children.iter().enumerate() {
702 let child_idx = build(ch, nodes)?;
703 let w = node
704 .mix
705 .as_ref()
706 .and_then(|m| m.weights.get(i))
707 .copied()
708 .unwrap_or(1.0);
709
710 let parent = nodes.get_mut(idx).expect("just pushed");
712 if parent.children.len() >= MAX_AUDIO_GRAPH_CHILDREN_PER_NODE {
713 continue;
714 }
715 let _ = parent.children.push(RtAudioGraphChild {
716 idx: child_idx,
717 weight: w,
718 });
719 }
720
721 Some(idx_u8)
722 }
723
724 let mut nodes = HVec::<RtAudioGraphNode, MAX_AUDIO_GRAPH_NODES>::new();
725 let _root_idx = build(&compiled.root, &mut nodes)?;
726
727 Some(RtAudioGraph {
728 root_component_ffi: source_root.data().as_ffi(),
729 nodes,
730 })
731}
732
733fn collect_audio_oscillator_roots(world: &World, output: ComponentId) -> Vec<ComponentId> {
734 use crate::engine::ecs::component::AudioClipComponent;
735
736 let mut all = Vec::new();
739 let mut stack = vec![output];
740 while let Some(node) = stack.pop() {
741 for &ch in world.children_of(node) {
742 stack.push(ch);
743 }
744
745 if node != output
746 && (world
747 .get_component_by_id_as::<AudioOscillatorComponent>(node)
748 .is_some()
749 || world
750 .get_component_by_id_as::<AudioClipComponent>(node)
751 .is_some())
752 {
753 all.push(node);
754 }
755 }
756
757 all.sort();
759 all.dedup();
760
761 all.into_iter()
762 .filter(|&cid| {
763 let mut cur = world.parent_of(cid);
764 while let Some(p) = cur {
765 if p == output {
766 return true;
767 }
768 let is_source = world
769 .get_component_by_id_as::<AudioOscillatorComponent>(p)
770 .is_some()
771 || world
772 .get_component_by_id_as::<AudioClipComponent>(p)
773 .is_some();
774 if is_source {
775 return false;
776 }
777 cur = world.parent_of(p);
778 }
779 false
780 })
781 .collect()
782}
783
784impl System for AudioSystem {
785 fn tick(
786 &mut self,
787 world: &mut World,
788 _visuals: &mut VisualWorld,
789 _input: &InputState,
790 _dt_sec: f32,
791 ) {
792 self.drain_decode_completions(world);
793 }
794}
795
796impl AudioSystem {
797 pub fn drain_decode_completions_public(&mut self, world: &mut World) {
800 self.drain_decode_completions(world);
801 }
802
803 fn drain_decode_completions(&mut self, world: &mut World) {
807 use super::audio_decode_thread::LoadedClipMessage;
808 use crate::engine::ecs::component::{AudioClipComponent, AudioClipLoadState};
809
810 let Some(rx) = self.decode_complete_rx.as_ref() else {
811 return;
812 };
813
814 loop {
815 let msg = match rx.try_recv() {
816 Ok(m) => m,
817 Err(_) => break,
818 };
819 match msg {
823 LoadedClipMessage::Loaded {
824 clip_id: asset_key,
825 samples,
826 channels,
827 sample_rate,
828 } => {
829 let upload = AudioQueueItem::UploadClip {
830 asset_key,
831 samples: samples.clone(),
832 channels,
833 sample_rate,
834 };
835 if let Some(tx) = self.audio_tx.as_mut() {
836 let _ = tx.push(upload);
837 } else {
838 self.pending_clip_uploads.push(upload);
839 }
840 let uri = match self.asset_states.get(&asset_key) {
841 Some(AssetDecodeState::Pending { uri })
842 | Some(AssetDecodeState::Loaded { uri })
843 | Some(AssetDecodeState::Failed { uri, .. }) => uri.clone(),
844 None => String::new(),
845 };
846 self.asset_states
847 .insert(asset_key, AssetDecodeState::Loaded { uri });
848 if let Some(subs) = self.asset_subscribers.remove(&asset_key) {
850 for cid in subs {
851 if let Some(c) =
852 world.get_component_by_id_as_mut::<AudioClipComponent>(cid)
853 {
854 c.load_state = AudioClipLoadState::Loaded;
855 }
856 }
857 }
858 }
859 LoadedClipMessage::Failed {
860 clip_id: asset_key,
861 reason,
862 } => {
863 let uri = match self.asset_states.get(&asset_key) {
864 Some(AssetDecodeState::Pending { uri })
865 | Some(AssetDecodeState::Loaded { uri })
866 | Some(AssetDecodeState::Failed { uri, .. }) => uri.clone(),
867 None => String::new(),
868 };
869 eprintln!("[AudioClip] {uri}: {reason}");
870 self.asset_states.insert(
871 asset_key,
872 AssetDecodeState::Failed {
873 uri,
874 reason: reason.clone(),
875 },
876 );
877 if let Some(subs) = self.asset_subscribers.remove(&asset_key) {
878 for cid in subs {
879 if let Some(c) =
880 world.get_component_by_id_as_mut::<AudioClipComponent>(cid)
881 {
882 c.load_state = AudioClipLoadState::Failed(reason.clone());
883 }
884 }
885 }
886 }
887 }
888 }
889 }
890
891 pub fn register_audio_clip(&mut self, world: &mut World, component: ComponentId) {
899 use super::audio_decode_thread::{LoadClipRequest, spawn_decode_thread};
900 use super::audio_sample_format_convert::PlaybackFormat;
901 use crate::engine::ecs::component::{AudioClipComponent, AudioClipLoadState};
902 use slotmap::Key;
903 use std::collections::hash_map::DefaultHasher;
904 use std::hash::{Hash, Hasher};
905
906 let (uri, start_beat, stop_beat) = {
910 let Some(clip) = world.get_component_by_id_as::<AudioClipComponent>(component) else {
911 return;
912 };
913 (clip.uri.clone(), clip.start_beat, clip.stop_beat)
914 };
915
916 if uri.is_empty() {
917 if let Some(c) = world.get_component_by_id_as_mut::<AudioClipComponent>(component) {
918 c.load_state = AudioClipLoadState::Failed("clip has no uri".into());
919 }
920 return;
921 }
922
923 self.clip_uris.insert(component, uri.clone());
924
925 let asset_key = {
929 let mut h = DefaultHasher::new();
930 uri.hash(&mut h);
931 h.finish()
932 };
933
934 if self.decode_thread.is_none() {
936 let (tx, rx) = std::sync::mpsc::channel();
937 self.decode_complete_tx = Some(tx.clone());
938 self.decode_complete_rx = Some(rx);
939 self.decode_thread = Some(spawn_decode_thread(tx));
940 }
941
942 let target = self.playback_format.unwrap_or(PlaybackFormat {
943 sample_rate: 48_000,
944 channels: 1,
945 });
946
947 let needs_decode = !self.asset_states.contains_key(&asset_key);
949 if needs_decode {
950 self.asset_states
951 .insert(asset_key, AssetDecodeState::Pending { uri: uri.clone() });
952 if let Some(handle) = self.decode_thread.as_ref() {
953 let _ = handle.tx.send(LoadClipRequest {
954 clip_id: asset_key,
955 uri: uri.clone(),
956 target,
957 });
958 }
959 }
960
961 match self.asset_states.get(&asset_key).cloned() {
964 Some(AssetDecodeState::Loaded { .. }) => {
965 if let Some(c) = world.get_component_by_id_as_mut::<AudioClipComponent>(component) {
966 c.load_state = AudioClipLoadState::Loaded;
967 }
968 }
969 Some(AssetDecodeState::Failed { reason, .. }) => {
970 if let Some(c) = world.get_component_by_id_as_mut::<AudioClipComponent>(component) {
971 c.load_state = AudioClipLoadState::Failed(reason);
972 }
973 }
974 _ => {
975 self.asset_subscribers
976 .entry(asset_key)
977 .or_default()
978 .push(component);
979 }
980 }
981
982 let register = AudioQueueItem::RegisterClipInstance {
985 component_ffi: component.data().as_ffi(),
986 asset_key,
987 start_beat,
988 stop_beat,
989 };
990 if let Some(tx) = self.audio_tx.as_mut() {
991 let _ = tx.push(register);
992 } else {
993 self.pending_clip_instance_registrations.push(register);
994 }
995
996 self.mark_audio_graph_dirty(world, component);
999 }
1000}