Post MIDI On Event in Wwise and Unreal Engine
Si prefieres leer una versión en Español, da click en este botón =>
Unreal Engine Version: 5.3
Wwise Version: 2023.1.1.8417
In this blog post, I show how to use Wwise MIDI functionality to trigger sample accurate fire weapon sounds. I also show how to use the AK::SoundEngine::PostMIDIOnEvent function in C++ to create a sample-accurate MIDI sequence. Additionally, I show how to set up a callback function from the audio engine to drive non-audio gameplay actions.
Although this functionality is included in the Wwise - Unreal SDK, it is not fully integrated into the Game Engine’s framework, so it’s only accessible through C++. Despite this, there is the possibility of integrating Blueprints after the C++ setup is done.
Here is the list of essential tools and keywords used for this project:
Important:
Please note that this guide does not teach creative sound design. Instead, it exclusively focuses on the implementation and programming aspects of game audio.
I used the Paragon: Lt. Belica Character assets on the Unreal Engine Marketplace.
This system is not officially supported in Unreal. Its documentation is poor, and it requires some multithreading manipulation. Be careful if using this in production code!
Download the project here:
IMPORTANT!
〰️
IMPORTANT! 〰️
Please consider reading and referencing my previous post about UPROPERTY & UFUNCTION:
IMPORTANT!
〰️
IMPORTANT! 〰️
Wwise Setup
This system uses three events: a shot, an impact, and a shot tail. The shot and the impact trigger a blend container with several random containers. Each sound has its own attenuation setting. These sounds are routed to the “Weapons” bus and sent to a reverb aux bus for some audio coloring.
The “Gunshot_MIDI” event is NOT A LOOP!
To correctly compile and use the Wwise plugin, I added its dependencies in the Build.cs project file:
Ranged Character Class - Lt. Belica
I created a C++ class derived from ACharacter called HRangedCharacter. Then I imported the Paragon: Lt. Belica and use its Blueprint character class. I made sure that this Blueprint inherits from HRangedCharacter
In the class constructor I setup a relative location and rotation, and created an AkWwise Component attached to the Weapon Fire Socket.
HRangedCharacter.h:
HRangedCharacter.cpp:
Post MIDI On Event
Parameters
These are parameters and assets required for this system. These are editable or visible in Blueprint by using the UPROPERTY macro.
HRangedCharacter.h:
Function #1: Fire Burst On MIDI
This function gets the Wwise Audio engine and prepares an array of MIDI posts to be staged by the AK::SoundEngine::PostMIDIOnEvent(). I use two distinct MIDI velocities to differentiate the last shot from the rest. This allows me to trigger the shot tail sound when the last shot of the burst plays.
The AK_MIDIEvent and AK_EndOfEvent flags tell the Callback to respond to these specific events.
This function doesn’t require all its parameters to be filled, but In this case, I provided a static Callback function (OnPostMidiCallback) and a function delegate (OnFireWeapon) as a “Cookie.”
You can provide any pointer and data as a Cookie. The callback carries this pointer, which you can use later.
HRangedCharacter.cpp:
Function #2: On Post MIDI Callback
This function must be static and have this signature as required by PostMIDIOnEvent.
Unfortunately, its parameters are not defined as UOBJECT, so there is no way to expose it to Blueprint or bind it to an Unreal Delegate type at this stage.
It's important to understand that this process is running on the audio thread so far. Some game systems (animations, particles) will crash the game if invoked from outside the game thread. To solve this issue, inside this function, I am using an AsyncTask() to move from the audio thread to the game thread.
Finally, I used native C++ static_cast<> to get the OnFireWeapon delegate I included as a Cookie. Finally I invoked it with BroadCast()
HRangedCharacter.h:
HRangedCharacter.cpp:
Function #3: Handle On Fire Weapon and On Fire Delegate
I created a new Unreal Delegate called OnFireWeapon that is triggered by the OnPostMidiCallback. This delegate is now running on the game thread, so it’s possible to bind any gameplay functionality to it.
HRangedCharacter.h:
I used the PostInitializeComponents() included in every AActor type to bind this delegate to the next function on this chain: HandleOnFireWeapon()
HRangedCharacter.cpp:
HandleOnFireWeapon fires a line trace, spawning the muzzle flash and bullet impact particle effects synced to every MIDI event. If bLastShot is true, it also calls the FireShotTail() function
HRangedCharacter.h:
Function #4: Fire Shot Tail
This one is simple. Post the shot tail audio event when the Midi velocity is lower than 127. This is the system I defined to identify the last shot.
HRangedCharacter.cpp:
End Play
The audio engine persists between the game and the editor, so it is important to stop all Midi events when the actor is destroyed or the game ends while the MIDI sequence is still playing.
HRangedCharacter.cpp:
BP_RangedCharacter and Entry Point
In the Blueprint Character class, I set up a Timer By Function Name to trigger all the systems at a consistent rate. I also called the Play Animation Montage function with the “Primary_Fire_Med_Montage” included in the Lt. Belica asset pack. The Cooldown Timer and Start Delay are parameters created in Blueprint and exposed to the editor for easy manipulation.
Multiple Instances of BP_RangedCharacter?
I created a second level with three instances of the Ranged Character, each with different parameters. You will see that each instance responds to the static callback without any issues.