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
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
/*
 * Copyright (c) 2022 Contributors to the Rrise project
 */

use crate::{
    bindings::root::{AK::SoundEngine::*, *},
    settings::AkInitSettings,
    *,
};
use ::std::convert::TryInto;
use ::std::ffi::CStr;
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 /*, T*/> {
    game_obj_id: AkGameObjectID,
    event_id: AkID<'a>,
    flags: AkCallbackType,
    // external_sources: Vec<...>  // TODO
    playing_id: AkPlayingID,
    marker: PhantomData<&'a u8>,
}

impl<'a> PostEvent<'a> {
    /// Select an event by name or by ID, to play on a given game object.
    pub fn new<T: Into<AkID<'a>>>(game_obj_id: AkGameObjectID, event_id: T) -> PostEvent<'a> {
        PostEvent {
            game_obj_id,
            event_id: event_id.into(),
            flags: AkCallbackType(0),
            // external_sources: ...,
            playing_id: AK_INVALID_PLAYING_ID,
            marker: PhantomData,
        }
    }

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

    /// Set flags before posting. Bitmask: see [AkCallbackType]
    ///
    /// *See also* [post_with_callback](Self::post_with_callback)
    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 AkID::Name(name) = self.event_id {
            let ak_playing_id = unsafe {
                with_cstring![name => cname {
                    PostEvent2(
                        cname.as_ptr(),
                        self.game_obj_id,
                        self.flags.0 as u32,
                        None,
                        ::std::ptr::null_mut(),
                        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 AkID::ID(id) = self.event_id {
            let ak_playing_id = unsafe {
                PostEvent(
                    id,
                    self.game_obj_id,
                    self.flags.0 as u32,
                    None,
                    ::std::ptr::null_mut(),
                    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")
        }
    }

    /// Posts the event to the sound engine, calling `callback` according to [flags](Self::flags).
    ///
    /// `callback` can be a function or a closure.
    ///
    /// **⚡ ATTENTION ⚡**
    ///
    /// `callback` will be called on the audio thread, **not** on the current thread where you called
    /// this. This means your closure or function must access shared state in a thread-safe way.
    ///
    /// This also means the closure or function must not be long to return, or audio might sutter as
    /// it prevents the audio thread from processing buffers.
    pub fn post_with_callback<F>(&self, callback: F) -> Result<AkPlayingID, AkResult>
    where
        F: FnMut(crate::AkCallbackInfo) + 'static,
    {
        // see http://blog.sagetheprogrammer.com/neat-rust-tricks-passing-rust-closures-to-c
        let data = Box::into_raw(Box::new(callback));

        if let AkID::Name(name) = self.event_id {
            let ak_playing_id = unsafe {
                with_cstring![name => cname {
                    PostEvent2(
                        cname.as_ptr(),
                        self.game_obj_id,
                        (self.flags | AkCallbackType::AK_EndOfEvent).0 as u32,
                        Some(Self::call_callback_as_closure::<F>),
                        data as *mut _,
                        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 AkID::ID(id) = self.event_id {
            let ak_playing_id = unsafe {
                PostEvent(
                    id,
                    self.game_obj_id,
                    (self.flags | AkCallbackType::AK_EndOfEvent).0 as u32,
                    Some(Self::call_callback_as_closure::<F>),
                    data as *mut _,
                    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")
        }
    }

    unsafe extern "C" fn call_callback_as_closure<F>(
        cb_type: AkCallbackType,
        cb_info: *mut bindings::root::AkCallbackInfo,
    ) where
        F: FnMut(crate::AkCallbackInfo),
    {
        // see http://blog.sagetheprogrammer.com/neat-rust-tricks-passing-rust-closures-to-c

        // TODO: do all this + callback(wrapped_cb_type) on the main thread, for ex. in render_audio?
        // TODO: behind a feature enabled by default? If disabled, just do this on the audio thread
        // TODO: and let user implement callback sync in their own way

        let callback_ptr: *mut F;
        let wrapped_cb_type: crate::AkCallbackInfo;
        if cb_type.contains(AkCallbackType::AK_MusicSyncAll) {
            let cb_info = *(cb_info as *mut AkMusicSyncCallbackInfo);
            callback_ptr = cb_info._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::MusicSync {
                game_obj_id: cb_info._base.gameObjID,
                playing_id: cb_info.playingID,
                segment_info: cb_info.segmentInfo,
                music_sync_type: cb_info.musicSyncType,

                user_cue_name: if cb_info.pszUserCueName.is_null() {
                    "".to_string()
                } else {
                    // Safety
                    // pszUserCueName will be valid until to_string(), which will copy the bytes from
                    // pszUserCueName onto the Rust-managed heap
                    CStr::from_ptr(cb_info.pszUserCueName as *const ::std::os::raw::c_char)
                        .to_str()
                        .unwrap()
                        .to_string()
                },
            };
        } else if cb_type.contains(AkCallbackType::AK_EndOfDynamicSequenceItem) {
            let cb_info = *(cb_info as *mut AkDynamicSequenceItemCallbackInfo);
            callback_ptr = cb_info._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::DynamicSequenceItem {
                game_obj_id: cb_info._base.gameObjID,
                playing_id: cb_info.playingID,
                audio_node_id: cb_info.audioNodeID,
            };
        } else if cb_type.contains(
            AkCallbackType::AK_EndOfEvent
                | AkCallbackType::AK_MusicPlayStarted
                | AkCallbackType::AK_Starvation,
        ) {
            let cb_info = *(cb_info as *mut AkEventCallbackInfo);
            callback_ptr = cb_info._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::Event {
                game_obj_id: cb_info._base.gameObjID,
                playing_id: cb_info.playingID,
                event_id: cb_info.eventID,
            };
        } else if cb_type.contains(AkCallbackType::AK_Duration) {
            let cb_info = *(cb_info as *mut AkDurationCallbackInfo);
            callback_ptr = cb_info._base._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::Duration {
                game_obj_id: cb_info._base._base.gameObjID,
                playing_id: cb_info._base.playingID,
                event_id: cb_info._base.eventID,
                duration: cb_info.fDuration,
                estimated_duration: cb_info.fEstimatedDuration,
                audio_node_id: cb_info.audioNodeID,
                media_id: cb_info.mediaID,
                streaming: cb_info.bStreaming,
            };
        } else if cb_type.contains(AkCallbackType::AK_Marker) {
            let cb_info = *(cb_info as *mut AkMarkerCallbackInfo);
            callback_ptr = cb_info._base._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::Marker {
                game_obj_id: cb_info._base._base.gameObjID,
                playing_id: cb_info._base.playingID,
                event_id: cb_info._base.eventID,
                identifier: cb_info.uIdentifier,
                position: cb_info.uPosition,
                label: if cb_info.strLabel.is_null() {
                    "".to_string()
                } else {
                    // Safety
                    // strLabel will be valid until to_string(), which will copy the bytes from
                    // strLabel onto the Rust-managed heap
                    CStr::from_ptr(cb_info.strLabel)
                        .to_str()
                        .unwrap()
                        .to_string()
                },
            }
        } else if cb_type.contains(AkCallbackType::AK_MIDIEvent) {
            let cb_info = *(cb_info as *mut AkMIDIEventCallbackInfo);
            callback_ptr = cb_info._base._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::Midi {
                game_obj_id: cb_info._base._base.gameObjID,
                playing_id: cb_info._base.playingID,
                event_id: cb_info._base.eventID,
                midi_event: cb_info.midiEvent.into(),
            }
        } else if cb_type.contains(AkCallbackType::AK_MusicPlaylistSelect) {
            let cb_info = *(cb_info as *mut AkMusicPlaylistCallbackInfo);
            callback_ptr = cb_info._base._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::MusicPlaylist {
                game_obj_id: cb_info._base._base.gameObjID,
                playing_id: cb_info._base.playingID,
                event_id: cb_info._base.eventID,
                playlist_id: cb_info.playlistID,
                num_playlist_items: cb_info.uNumPlaylistItems,
                playlist_selection: cb_info.uPlaylistSelection,
                playlist_item_done: cb_info.uPlaylistItemDone,
            }
        } else if cb_type.contains(AkCallbackType::AK_SpeakerVolumeMatrix) {
            let cb_info = *(cb_info as *mut AkSpeakerVolumeMatrixCallbackInfo);
            callback_ptr = cb_info._base._base.pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::SpeakerMatrixVolume {
                game_obj_id: cb_info._base._base.gameObjID,
                playing_id: cb_info._base.playingID,
                event_id: cb_info._base.eventID,
                input_config: cb_info.inputConfig,
                output_config: cb_info.outputConfig,
            }
        } else {
            if !cb_type.contains(AkCallbackType::AK_CallbackBits) {
                // is it safe to panic here?
                panic!("Unexpected AkCallbackType encountered: {:?}", cb_type.0);
            }

            callback_ptr = (*cb_info).pCookie as *mut F;
            wrapped_cb_type = crate::AkCallbackInfo::Default {
                game_obj_id: (*cb_info).gameObjID,
            };
        }
        let callback = &mut *callback_ptr;

        // Info needed: is this safe if the callback panics? Should we do something with
        // catch_unwind? Is this undefined behavior?
        callback(wrapped_cb_type);

        if cb_type.contains(AkCallbackType::AK_EndOfEvent) {
            // No more callbacks to process! Cleanup memory
            Box::from_raw(callback_ptr); // effectively drops callback_ptr
        }
    }
}