1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! 🎵 Audio module
//!
//! TODO: Add description how they work and how to use it

#[doc(hidden)]
pub use ark_api::Mat4;
pub use ark_api_ffi::entrypoints::audio::AudioRenderInfo;
pub use ark_api_ffi::entrypoints::audio::AudioSourceFlags;
pub use ark_api_ffi::entrypoints::audio::AudioSourceInfo;

/// Audio module creation & update trait
pub trait AudioInstance {
    /// Creation of a new module
    ///
    /// This is called once on startup of the module
    fn new() -> Self;

    /// Implement the update of the audio module here
    ///
    /// TODO: Describe more in detail how it works and what the parameters are
    fn update(
        &mut self,
        frame_info: &AudioRenderInfo,
        object_to_world: ark_api::Mat4,
        static_data: &[u8],
        dynamic_data: &[u8],
        stereo_buffer: &mut [f32],
    );
}

/// Implement an audio source module.
///
/// The API is instance based so you don't need to keep track of instances yourself.
#[macro_export]
macro_rules! impl_audio_instance {
    ($instance: ty) => {
        use std::collections::HashMap;
        use $crate::audio::AudioInstance as _;

        struct Module {
            instances: HashMap<u64, $instance>,
        }

        static mut MODULE: Option<Module> = None;

        #[no_mangle]
        pub fn ark_initialize() {
            $crate::init();
            // SAFETY: Sound to access static as we do not use threads in WASM and this is guaranteed to be called first in the module
            unsafe {
                MODULE = Some(Module {
                    instances: Default::default(),
                });
            }
        }

        #[no_mangle]
        pub unsafe fn ark_audio_render(
            render_info_ptr: *const $crate::audio::AudioRenderInfo,
            source_info_ptr: *const $crate::audio::AudioSourceInfo,
            source_info_len: u32,
        ) {
            assert!(!render_info_ptr.is_null());
            assert!(!source_info_ptr.is_null());

            // SAFETY: Sound to access static as we do not use threads in WASM and this is guaranteed to be initialized on startup with `ark_initialize` first
            unsafe {
                let module = MODULE.as_mut().unwrap();

                let slice = std::slice::from_raw_parts::<$crate::audio::AudioSourceInfo>(
                    source_info_ptr,
                    source_info_len as usize,
                );

                for entry in slice {
                    assert!(entry.static_data_ptr != 0);
                    assert!(entry.dynamic_data_ptr != 0);

                    let static_data_slice = std::slice::from_raw_parts::<u8>(
                        entry.static_data_ptr as *const u8,
                        entry.static_data_len as usize,
                    );

                    let dynamic_data_slice = std::slice::from_raw_parts::<u8>(
                        entry.dynamic_data_ptr as *const u8,
                        entry.dynamic_data_len as usize,
                    );

                    let buffer_slice = std::slice::from_raw_parts_mut::<f32>(
                        entry.out_buffer_ptr as *mut f32,
                        entry.out_buffer_len as usize,
                    );

                    let instance = module
                        .instances
                        .entry(entry.cache_id)
                        .or_insert_with(|| <$instance>::new());

                    // using rate as a flag for removal
                    if !entry.flags.contains(AudioSourceFlags::REMOVED) {
                        instance.update(
                            render_info_ptr.as_ref().unwrap(),
                            $crate::audio::Mat4::from_cols_array(&entry.object_to_world),
                            static_data_slice,
                            dynamic_data_slice,
                            buffer_slice,
                        );
                    } else {
                        let _ = module.instances.remove(&entry.cache_id);
                    }
                }
            }
        }
    };
}