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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
 * Copyright (c) 2022 Contributors to the Rrise project
 */

use crate::{
    bindings::root::{AK::SoundEngine::*, *},
    settings::AkInitSettings,
    *,
};
use ::std::convert::TryInto;
use ::std::fmt::Debug;
use ::std::marker::PhantomData;

macro_rules! link_static_plugin {
    ($feature:ident) => {
        link_static_plugin![$feature, $feature]
    };
    ($feature:ident, $global_var_name:ident) => {
        paste::paste! {
            #[cfg(feature = "" $feature)]
            {
                // If max log level doesn't include debug, need to explicitly reference this variable
                // or it won't be statically linked and the plugin won't be able to be loaded.
                #[cfg(any(
                    all(
                        debug_assertions,
                        all(not(feature = "max_level_debug"), not(feature = "max_level_trace"))
                    ),
                    all(
                        not(debug_assertions),
                        all(
                            not(feature = "release_max_level_debug"),
                            not(feature = "release_max_level_trace")
                        )
                    ),
                ))]
                ::std::convert::identity(unsafe {
                    crate::bindings_static_plugins::[<$global_var_name Registration>]
                });
                log::debug!(
                    "{} has been statically loaded successfully",
                    stringify!($feature)
                )
            }
        }
    };
}

/// Initialize the sound engine.
///
/// *Warning* This function is not thread-safe.
///
/// *Remark* The initial settings should be initialized using [AkInitSettings::default]
/// and [AkPlatformInitSettings::default] to fill the structures with their
/// default settings. This is not mandatory, but it helps avoid backward compatibility problems.
///
/// *Return*
/// > - [AK_Success](AkResult::AK_Success) if the initialization was successful
/// > - [AK_MemManagerNotInitialized](AkResult::AK_MemManagerNotInitialized) if the memory manager is not available or not properly initialized
/// > - [AK_StreamMgrNotInitialized](AkResult::AK_StreamMgrNotInitialized) if the stream manager is not available or not properly initialized
/// > - [AK_SSEInstructionsNotSupported](AkResult::AK_SSEInstructionsNotSupported) if the machine does not support SSE instruction (only on the PC)
/// > - [AK_InsufficientMemory](AkResult::AK_InsufficientMemory) or [AK_Fail](AkResult::AK_Fail) if there is not enough memory available to initialize the sound engine properly
/// > - [AK_InvalidParameter](AkResult::AK_InvalidParameter) if some parameters are invalid
/// > - [AK_Fail](AkResult::AK_Fail) if the sound engine is already initialized, or if the provided settings result in insufficient resources for the initialization.
///
/// *See also*
/// > - [term]
/// > - [AkInitSettings::default]
/// > - [AkPlatformInitSettings::default]
pub fn init(
    mut init_settings: AkInitSettings,
    mut platform_init_settings: AkPlatformInitSettings,
) -> Result<(), AkResult> {
    ak_call_result![Init(init_settings.as_ak(), &mut platform_init_settings)]?;

    link_static_plugin![AkVorbisDecoder];
    link_static_plugin![AkOggOpusDecoder]; // see Ak/Plugin/AkOpusDecoderFactory.h
    link_static_plugin![AkWemOpusDecoder]; // see Ak/Plugin/AkOpusDecoderFactory.h
    link_static_plugin![AkMeterFX];
    link_static_plugin![AkAudioInputSource];
    link_static_plugin![AkCompressorFX];
    link_static_plugin![AkDelayFX];
    link_static_plugin![AkExpanderFX];
    link_static_plugin![AkFlangerFX];
    link_static_plugin![AkGainFX];
    link_static_plugin![AkGuitarDistortionFX];
    link_static_plugin![AkHarmonizerFX];
    link_static_plugin![AkMatrixReverbFX];
    link_static_plugin![AkParametricEQFX];
    link_static_plugin![AkPeakLimiterFX];
    link_static_plugin![AkPitchShifterFX];
    link_static_plugin![AkRecorderFX];
    link_static_plugin![AkRoomVerbFX];
    link_static_plugin![AkSilenceSource];
    link_static_plugin![AkSineSource, SineSource];
    link_static_plugin![AkStereoDelayFX];
    link_static_plugin![AkSynthOneSource, AkSynthOne];
    link_static_plugin![AkTimeStretchFX];
    link_static_plugin![AkToneSource];
    link_static_plugin![AkTremoloFX];

    Ok(())
}

/// Query whether or not the sound engine has been successfully initialized.
///
/// *Warning* This function is not thread-safe. It should not be called at the same time as [init()] or [term()].
///
/// *Return* `True` if the sound engine has been initialized, `False` otherwise.
///
/// *See also*
/// > - [init]
/// > - [term]
pub fn is_initialized() -> bool {
    unsafe { IsInitialized() }
}

/// Terminates the sound engine.
///
/// If some sounds are still playing or events are still being processed when this function is
/// called, they will be stopped.
///
/// *Warning* This function is not thread-safe.
///
/// *Warning* Before calling `Term`, you must ensure that no other thread is accessing the sound engine.
///
/// *See also*
/// > - [init]
pub fn term() {
    unsafe {
        AK::SoundEngine::Term();
    }
}

/// Processes all commands in the sound engine's command queue.
///
/// This method has to be called periodically (usually once per game frame).
///
/// `allow_sync_render`: When AkInitSettings::bUseLEngineThread is false, `RenderAudio` may generate
/// an audio buffer -- unless in_bAllowSyncRender is set to false. Use in_bAllowSyncRender=false
/// when calling RenderAudio from a Sound Engine callback.
///
/// *Return* Always returns [AK_Success](AkResult::AK_Success)
///
/// *See also*
/// > - [PostEvent](struct@PostEvent)
pub fn render_audio(allow_sync_render: bool) -> Result<(), AkResult> {
    ak_call_result![RenderAudio(allow_sync_render)]
}

/// Unregister all game objects, or all game objects with a particular matching set of property flags.
///
/// This function to can be used to unregister all game objects.
///
/// *Return* AK_Success if successful
///
/// *Remark* Registering a game object twice does nothing. Unregistering it once unregisters it no
/// matter how many times it has been registered. Unregistering a game object while it is
/// in use is allowed, but the control over the parameters of this game object is lost.
/// For example, if a sound associated with this game object is a 3D moving sound, it will
/// stop moving once the game object is unregistered, and there will be no way to recover
/// the control over this game object.
///
/// *See also*
/// - [register_game_obj]
/// - [unregister_game_obj]
pub fn unregister_all_game_obj() -> Result<(), AkResult> {
    ak_call_result![UnregisterAllGameObj()]
}

/// Unregisters a game object.
///
/// *Return*
/// > - AK_Success if successful
/// > - AK_Fail if the specified AkGameObjectID is invalid (0 is an invalid ID)
///
/// *Remark* Registering a game object twice does nothing. Unregistering it once unregisters it no
/// matter how many times it has been registered. Unregistering a game object while it is
/// in use is allowed, but the control over the parameters of this game object is lost.
/// For example, say a sound associated with this game object is a 3D moving sound. This sound will
/// stop moving when the game object is unregistered, and there will be no way to regain control over the game object.
///
/// *See also*
/// > - [register_game_obj]
/// > - [unregister_all_game_obj]
pub fn register_game_obj(game_object_id: AkGameObjectID) -> Result<(), AkResult> {
    ak_call_result![RegisterGameObj(game_object_id)]
}

/// Sets the position of a game object.
///
/// *Warning* `position`'s orientation vectors must be normalized.
///
/// *Return*
/// > - [AK_Success](AkResult::AK_Success) when successful
/// > - [AK_InvalidParameter](AkResult::AK_InvalidParameter) if parameters are not valid.
pub fn set_position<T: Into<AkSoundPosition>>(
    game_object_id: AkGameObjectID,
    position: T,
) -> Result<(), AkResult> {
    ak_call_result![SetPosition(game_object_id, &position.into())]
}

/// Sets the default set of associated listeners for game objects that have not explicitly overridden their listener sets. Upon registration, all game objects reference the default listener set, until
/// a call to [add_listener], [remove_listener], [set_listeners] or [set_game_object_output_bus_volume] is made on that game object.
///
/// All default listeners that have previously been added via AddDefaultListener or set via SetDefaultListeners will be removed and replaced with the listeners in the array in_pListenerGameObjs.
///
/// *Return* Always returns [AK_Success](AkResult::AK_Success)
pub fn set_default_listeners(listener_ids: &[AkGameObjectID]) -> Result<(), AkResult> {
    ak_call_result![SetDefaultListeners(
        listener_ids.as_ptr(),
        listener_ids.len().try_into().unwrap()
    )]
}

/// Stops the current content playing associated to the specified game object ID.
///
/// If no game object is specified, all sounds will be stopped.
pub fn stop_all(game_object_id: Option<AkGameObjectID>) {
    unsafe {
        StopAll(game_object_id.unwrap_or(AK_INVALID_GAME_OBJECT));
    }
}

/// Load a bank synchronously (by Unicode string).
///
/// The bank name is passed to the Stream Manager.
///
/// A bank load request will be posted, and consumed by the Bank Manager thread.
///
/// The function returns when the request has been completely processed.
///
/// *Return*
/// The bank ID (see [get_id_from_string]). You may use this ID with [unload_bank].
/// > - [AK_Success](AkResult::AK_Success): Load or unload successful.
/// > - [AK_InsufficientMemory](AkResult::AK_InsufficientMemory): Insufficient memory to store bank data.
/// > - [AK_BankReadError](AkResult::AK_BankReadError): I/O error.
/// > - [AK_WrongBankVersion](AkResult::AK_WrongBankVersion): Invalid bank version: make sure the version of Wwise that you used to generate the SoundBanks matches that of the SDK you are currently using.
/// > - [AK_InvalidFile](AkResult::AK_InvalidFile): File specified could not be opened.
/// > - [AK_InvalidParameter](AkResult::AK_InvalidParameter): Invalid parameter, invalid memory alignment.
/// > - [AK_Fail](AkResult::AK_Fail): Load or unload failed for any other reason. (Most likely small allocation failure)
///
/// *Remarks*
/// > - The initialization bank must be loaded first.
/// > - All SoundBanks subsequently loaded must come from the same Wwise project as the
///   initialization bank. If you need to load SoundBanks from a different project, you
///   must first unload ALL banks, including the initialization bank, then load the
///   initialization bank from the other project, and finally load banks from that project.
/// > - Codecs and plug-ins must be registered before loading banks that use them.
/// > - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success,
/// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in
/// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects,
/// posting the event will fail.
/// > - The sound engine internally calls get_id_from_string(name) to return the correct bank ID.
/// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally),
/// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in
/// your implementation of the Stream Manager, or in the Low-Level I/O module if you use the default Stream Manager's implementation.
///
/// *See also*
/// > - [unload_bank_by_name]
/// > - [unload_bank_by_id]
/// > - [clear_banks]
/// > - [get_id_from_string]
pub fn load_bank_by_name<T: AsRef<str>>(name: T) -> Result<AkBankID, AkResult> {
    let mut bank_id = 0;
    with_cstring![name.as_ref() => cname {
        ak_call_result![LoadBank1(cname.as_ptr(), &mut bank_id) => bank_id]
    }]
}

#[derive(Debug, Copy, Clone)]
/// Helper to post events to the sound engine.
///
/// Use [PostEvent::post] to post your event to the sound engine.
///
/// The callback function can be used to be noticed when markers are reached or when the event is finished.
///
/// An array of Wave file sources can be provided to resolve External Sources triggered by the event.
///
/// *Return* The playing ID of the event launched, or [AK_INVALID_PLAYING_ID] if posting the event failed
///
/// *Remarks*
/// > - If used, the array of external sources should contain the information for each external source triggered by the
/// event. When triggering an Event with multiple external sources, you need to differentiate each source
/// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo.
/// > - If an event triggers the playback of more than one external source, they must be named uniquely in the project
/// (therefore have a unique cookie) in order to tell them apart when filling the AkExternalSourceInfo structures.
///
/// *See also*
/// > - [render_audio]
/// > - [get_source_play_position]
pub struct PostEvent<'a> {
    game_obj_id: AkGameObjectID,
    event_id: Option<AkUniqueID>,
    event_name: Option<&'a str>,
    flags: AkCallbackType,
    // callback: Option<Box<dyn Fn(AkCallbackType, CallbackInfo)>>, // TODO
    // cookie: Option<Cookie>,                                      // TODO
    // external_sources: Vec<...>                                   // TODO
    playing_id: AkPlayingID,
    marker: PhantomData<&'a u8>,
}

impl<'a> PostEvent<'a> {
    fn new(
        game_obj_id: AkGameObjectID,
        event_id: Option<AkUniqueID>,
        event_name: Option<&'a str>,
    ) -> PostEvent<'a> {
        PostEvent {
            game_obj_id,
            event_id,
            event_name,
            flags: AkCallbackType(0),
            // callback: None,
            // cookie: None,
            // external_sources: ...,
            playing_id: AK_INVALID_PLAYING_ID,
            marker: PhantomData,
        }
    }

    /// Select an event by name, to play on a given game object.
    pub fn from_event_name(game_obj_id: AkGameObjectID, name: &'a str) -> PostEvent {
        Self::new(game_obj_id, None, Some(name))
    }

    /// Select an event by id, to play on a given game object.
    pub fn from_event_id(game_obj_id: AkGameObjectID, id: AkUniqueID) -> PostEvent<'a> {
        Self::new(game_obj_id, Some(id), None)
    }

    /// Add flags before posting. Bitmask: see [AkCallbackType].
    pub fn add_flags(&mut self, flags: AkCallbackType) -> &mut Self {
        self.flags |= flags;
        self
    }

    /// Set flags before posting. Bitmask: see [AkCallbackType]
    pub fn flags(&mut self, flags: AkCallbackType) -> &mut Self {
        self.flags = flags;
        self
    }

    /// Advanced users only. Specify the playing ID to target with the event. Will Cause active
    /// actions in this event to target an existing Playing ID. Let it be [AK_INVALID_PLAYING_ID]
    /// or do not specify any for normal playback.
    pub fn playing_id(&mut self, id: AkPlayingID) -> &mut Self {
        self.playing_id = id;
        self
    }

    /// Posts the event to the sound engine.
    pub fn post(&self) -> Result<AkPlayingID, AkResult> {
        if let Some(name) = self.event_name {
            let ak_playing_id = unsafe {
                with_cstring![name => cname {
                    PostEvent2(
                        cname.as_ptr(),
                        self.game_obj_id,
                        self.flags.0 as u32,
                        None,                   // TODO
                        ::std::ptr::null_mut(), // TODO
                        0,                      // TODO
                        ::std::ptr::null_mut(), // TODO
                        self.playing_id,
                    )
                }]
            };
            if ak_playing_id == AK_INVALID_PLAYING_ID {
                Err(AkResult::AK_Fail)
            } else {
                Ok(ak_playing_id)
            }
        } else if let Some(id) = self.event_id {
            let ak_playing_id = unsafe {
                PostEvent(
                    id,
                    self.game_obj_id,
                    self.flags.0 as u32,
                    None,                   // TODO
                    ::std::ptr::null_mut(), // TODO
                    0,                      // TODO
                    ::std::ptr::null_mut(), // TODO
                    self.playing_id,
                )
            };
            if ak_playing_id == AK_INVALID_PLAYING_ID {
                Err(AkResult::AK_Fail)
            } else {
                Ok(ak_playing_id)
            }
        } else {
            panic!("need at least an event ID or and an event name to post")
        }
    }
}