UE integrated WWise: concept and code analysis

UE集成WWise:概念与代码分析

WWise is Audiokinetic’s cross-platform audio engine that interacts well with game engines, allowing audio designers to handle audio solely within WWise. This separates game logic from audio production and management, providing events and parameters for use in game engines, achieving decoupling with business logic and precision control over audio.

This article mainly introduces the integration of WWise with UE4, remote building, resource analysis, documentation collection, the control interaction between WWise and UE, and code analysis of generated Banks.

Integration into UE4

WWise supports all platforms, including Linux/Lumin/PS4/Switch/XboxOne/Windows/Android/iOS/Mac. However, most games do not require support for so many platforms, and the WWise link library is large. Therefore, I’ve trimmed down the official multi-platform version by removing support for the following platforms:

  • Linux
  • Lumin
  • PS4
  • Switch
  • XboxOne

For the Android and Windows platforms, the following adjustments were made:

  • Removed support for arm64-v8a and android_x86/x86_64 link libraries
  • Removed all link libraries for Win32/removed support for vc140/vc150

For iOS, the following adjustments were made:

  • Removed all iphonesimulator, saving 2.31G of space

For Mac support:

The current project does not require support for Mac (using iOS for remote builds), but to avoid issues with compiling the project on Mac, the Mac link library and module support have been retained, and keeping it will not affect the packaging for Android/iOS.

In the trimmed version, I support the following platforms:

  • Android_armabi_v7a
  • iOS
  • Mac
  • Win vc160

Each platform supports Debug/Profile/Release, corresponding to UE’s Debug/Development/Shipping configuration.

In the Android Development configuration, including the WWise link library, the APK size increases by approximately 30M.

WWise Version Issues

The WWise version is Wwise 2019.1.9.7221

In AkAudio_Android.build.cs, the link library support for Android has a path error under UE_4_25_OR_LATER.

Original path:

1
Path.Combine(ThirdPartyFolder, "Android", "armeabi-v7a", akConfigurationDir, "lib")

Actual path:

1
Path.Combine(ThirdPartyFolder, "Android_armeabi-v7a", akConfigurationDir, "lib")

However, this modification will result in link errors when simultaneously supporting armv7 and arm64. The solution is discussed in the next section.

Android Support for armv7 and arm64

When supporting both arm64 and armv7 in the UE project settings for Android, the above modifications result in link errors. It is necessary to change the link library paths in WWise. For specific methods, refer to my note: Simultaneously Supporting armv7 and arm64 Link Libraries.

The WWise SDK simultaneously uses UPL and RuntimeDependencies to add link libraries, causing duplication. Therefore, you can remove the moving platform entries from RuntimeDependencies:

1
2
3
4
5
6
7
8
9
10
11
12
public AkAudio(ReadOnlyTargetRules Target) : base(Target)
{
// ...
if (Target.Platform == UnrealTargetPlatform.Win64)
{
foreach(var RuntimeDependency in AkUEPlatformInstance.GetRuntimeDependencies())
{
RuntimeDependencies.Add(RuntimeDependency);
}
}
// ...
}

Remote Building for iOS

In my previous notes, I mentioned that remote building for iOS involves uploading files to Mac for compilation. However, a problem arises if the files involved in the build are not uploaded to Mac, which unfortunately occurs with WWise. The solution is to upload the files that WWise depends on to Mac.

Since UE uses RSync for file synchronization between the build machine and local files, I previously discussed this in the article UE4 Development Notes: Mac/iOS Section # Configuring Remote Builds, you can create a file <ProjectDir>/Build/Rsync/RsyncProject.txt to write RSync’s file synchronization rules, uploading the required files to Mac.

The rules for WWise are as follows:

1
2
+ /Plugins/Wwise/ThirdParty/include/**
+ /Plugins/Wwise/ThirdParty/iOS/**

This just specifies that the WWise link library and Include directories are fully uploaded to Mac.

WWise Resources

WWise has two resource formats in UE: one is UAkAudioEvent, used to execute specified Events in WWise, and the other is UAkAudioBank, which records which Events are included in the Bank, marking them to be packed together during generation.

UAkAudioEvent

UAkAudioEvent: Its main function is to designate the Bank for the current Event. Its only function is LoadBank, which loads the Bank specified for the current Event (if you see its usage, it’s just to retrieve its name). The WWise integration into UE’s plugin also binds the UAkAudioEvent object’s name to the UAkAudioBank for generating bnk files, which is why the naming of resources in UE must exactly match the naming of Events in WWise.

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
UCLASS(meta=(BlueprintSpawnableComponent))
class AKAUDIO_API UAkAudioEvent : public UObject
{
GENERATED_UCLASS_BODY()

public:
/** Bank to which this event should be added. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bank")
class UAkAudioBank * RequiredBank;

/** Maximum attenuation radius for this event */
UPROPERTY(BlueprintReadOnly, Category="AkAudioEvent")
float MaxAttenuationRadius;

/** Whether this event is infinite (looping) or finite (duration parameters are valid) */
UPROPERTY(BlueprintReadOnly, Category = "AkAudioEvent")
bool IsInfinite;

/** Minimum duration */
UPROPERTY(BlueprintReadOnly, Category = "AkAudioEvent")
float MinimumDuration;

/** Maximum duration */
UPROPERTY(BlueprintReadOnly, Category = "AkAudioEvent")
float MaximumDuration;

#if CPP
/**
* Load the required bank.
*
* @return true if the bank was loaded, otherwise false
*/
bool LoadBank();
#endif

};

By obtaining the name of the Event and passing it to the deeper PostEvent:

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
int32 UAkGameplayStatics::PostEvent(
class UAkAudioEvent* AkEvent
, class AActor* Actor
, int32 CallbackMask
, const FOnAkPostEventCallback& PostEventCallback
, const TArray<FAkExternalSourceInfo>& ExternalSources
, bool bStopWhenAttachedToDestroyed
, FString EventName
)
{
if (AkEvent == NULL && EventName.IsEmpty())
{
UE_LOG(LogScript, Warning, TEXT("UAkGameplayStatics::PostEvent: No Event specified!"));
return AK_INVALID_PLAYING_ID;
}

if (Actor == NULL)
{
UE_LOG(LogScript, Warning, TEXT("UAkGameplayStatics::PostEvent: NULL Actor specified!"));
return AK_INVALID_PLAYING_ID;
}

AkDeviceAndWorld DeviceAndWorld(Actor);
if (DeviceAndWorld.IsValid())
{
AkCallbackType AkCallbackMask = AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(CallbackMask);
if (ExternalSources.Num() > 0)
{
FAkSDKExtrernalSourceArray SDKExternalSrcInfo(ExternalSources);
return DeviceAndWorld.AkAudioDevice->PostEvent(GET_AK_EVENT_NAME(AkEvent, EventName), Actor, PostEventCallback, AkCallbackMask, false, SDKExternalSrcInfo.ExternalSourceArray);
}
else
{
return DeviceAndWorld.AkAudioDevice->PostEvent(GET_AK_EVENT_NAME(AkEvent, EventName), Actor, PostEventCallback, AkCallbackMask);
}
}

return AK_INVALID_PLAYING_ID;
}

UAkAudioBank

UAkAudioBank is used to call AkAudioDevice to load the Bank. If AutoLoad is enabled in UE, it will execute the loading in the UObject’s PostLoad.

The official introduction to SoundBanks can be found here: SoundBank

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
/**
* Called after load process is complete.
*/
void UAkAudioBank::PostLoad()
{
Super::PostLoad();
if ( AutoLoad && !HasAnyFlags(RF_ClassDefaultObject))
{
Load();
}
}

/**
* Loads an AkBank.
*
* @return Returns true if the load was successful, otherwise false
*/
bool UAkAudioBank::Load()
{
if (!IsRunningCommandlet())
{
FAkAudioDevice * AudioDevice = FAkAudioDevice::Get();
if (AudioDevice)
{
AkBankID BankID;
AKRESULT eResult = AudioDevice->LoadBank(this, AK_DEFAULT_POOL_ID, BankID);
return (eResult == AK_Success) ? true : false;
}
}

return false;
}

Moreover, LoadBank similarly uses the Event name for passing to FAkAudioDevices.

Analysis of Bank Generation

So how does AkAudioEvent associate with SoundBank? In the code, I see the need for LoadBank, but I don’t see where SoundBank and Event are associated in UE resources, and the naming of SoundBank has no relation in WWise.

The answer lies in the files generated by AkSoundBank. When generating AkSoundBank resources with Generate Selected SoundBank:

It creates the following files under the WwiseSoundBankFolder directory in project settings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
D:\WWise\WwiseDemoGame\Content\WwiseAudio\Windows>tree /a /f
D:.
| Init.bnk
| Init.json
| Init.txt
| Init.xml
| PluginInfo.json
| PluginInfo.xml
| SoundbanksInfo.json
| SoundbanksInfo.xml
| SwitchBank.bnk
| SwitchBank.json
| SwitchBank.txt
| SwitchBank.xml
| VelocityBank.bnk
| VelocityBank.json
| VelocityBank.txt
| VelocityBank.xml

Among them, the four Init.* files are essential for Init’s Bank content. Each Bank generates four files: .bnk/.json/.txt/.xml, where the .bnk file is what UE needs to load the bank, and tests show that deleting the other files does not cause any issues.

  • bnk: Data file, *.Bnk stores detailed event information, audio, and data structures required by other plugins. It can be simply understood as a container for storing data, allowing dynamic control over adding and unloading during runtime.
  • json: Descriptor file for recording what data and Events are in the current bank and the corresponding Event’s path in the WWise project.
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
{
"SoundBanksInfo": {
"Platform": "Windows",
"BasePlatform": "Windows",
"SchemaVersion": "11",
"SoundbankVersion": "134",
"RootPaths": {
"ProjectRoot": "C:\\Users\\lipengzha\\Desktop\\WWise\\WwiseDemoGame\\UnrealWwiseDemo\\",
"SourceFilesRoot": "C:\\Users\\lipengzha\\Desktop\\WWise\\WwiseDemoGame\\UnrealWwiseDemo\\.cache\\Windows\\",
"SoundBanksRoot": "C:\\Users\\lipengzha\\Desktop\\WWise\\WwiseDemoGame\\Content\\WwiseAudio\\Windows\\",
"ExternalSourcesInputFile": "",
"ExternalSourcesOutputRoot": "C:\\Users\\lipengzha\\Desktop\\WWise\\WwiseDemoGame\\UnrealWwiseDemo\\GeneratedSoundBanks\\Windows"
},
"SoundBanks": [
{
"Id": "2001541346",
"Language": "SFX",
"ObjectPath": "\\SoundBanks\\Default Work Unit\\VelocityBank",
"ShortName": "VelocityBank",
"Path": "VelocityBank.bnk",
"IncludedEvents": [
{
"Id": "2099597577",
"Name": "PlayRederenceSoundTest",
"ObjectPath": "\\Events\\Default Work Unit\\PlayRederenceSoundTest",
"DurationType": "OneShot",
"DurationMin": "1.338833",
"DurationMax": "1.338833"
},
{
"Id": "3368745218",
"Name": "VelocityLoop",
"ObjectPath": "\\Events\\Default Work Unit\\VelocityLoop",
"DurationType": "Infinite"
}
],
"IncludedMemoryFiles": [
{
"Id": "386490851",
"Language": "SFX",
"ShortName": "Shotgun_Fire_01.wav",
"Path": "SFX\\Shotgun_Fire_01_A07A4AEB.wem"
}
]
}
]
}
}
  • xml: Descriptor file containing the same information as json.
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
<?xml version="1.0" encoding="utf-8"?>
<SoundBanksInfo Platform="Windows" BasePlatform="Windows" SchemaVersion="11" SoundbankVersion="134">
<RootPaths>
<ProjectRoot>C:\Users\lipengzha\Desktop\WWise\WwiseDemoGame\UnrealWwiseDemo\</ProjectRoot>
<SourceFilesRoot>C:\Users\lipengzha\Desktop\WWise\WwiseDemoGame\UnrealWwiseDemo\.cache\Windows\</SourceFilesRoot>
<SoundBanksRoot>C:\Users\lipengzha\Desktop\WWise\WwiseDemoGame\Content\WwiseAudio\Windows\</SoundBanksRoot>
<ExternalSourcesInputFile></ExternalSourcesInputFile>
<ExternalSourcesOutputRoot>C:\Users\lipengzha\Desktop\WWise\WwiseDemoGame\UnrealWwiseDemo\GeneratedSoundBanks\Windows</ExternalSourcesOutputRoot>
</RootPaths>
<SoundBanks>
<SoundBank Id="2001541346" Language="SFX">
<ObjectPath>\SoundBanks\Default Work Unit\VelocityBank</ObjectPath>
<ShortName>VelocityBank</ShortName>
<Path>VelocityBank.bnk</Path>
<IncludedEvents>
<Event Id="2099597577" Name="PlayRederenceSoundTest" ObjectPath="\Events\Default Work Unit\PlayRederenceSoundTest" DurationType="OneShot" DurationMin="1.338833" DurationMax="1.338833"/>
<Event Id="3368745218" Name="VelocityLoop" ObjectPath="\Events\Default Work Unit\VelocityLoop" DurationType="Infinite"/>
</IncludedEvents>
<IncludedMemoryFiles>
<File Id="386490851" Language="SFX">
<ShortName>Shotgun_Fire_01.wav</ShortName>
<Path>SFX\Shotgun_Fire_01_A07A4AEB.wem</Path>
</File>
</IncludedMemoryFiles>
</SoundBank>
</SoundBanks>
</SoundBanksInfo>

  • txt: Also a descriptor file, but with fewer details compared to json and xml.
1
2
3
4
5
6
7
8
9
10
11
12
Event	ID	Name			Wwise Object Path	Notes
2099597577 PlayRederenceSoundTest \Default Work Unit\PlayRederenceSoundTest
3368745218 VelocityLoop \Default Work Unit\VelocityLoop

Game Parameter ID Name Wwise Object Path Notes
3519441192 Velocity \Default Work Unit\Velocity

Source plug-ins ID Name Type Wwise Object Path Notes
778067245 Wwise Tone Generator Wwise Tone Generator \Actor-Mixer Hierarchy\RTPCDemo\VelocityLoop\Wwise Tone Generator

In Memory Audio ID Name Audio source file Wwise Object Path Notes Data Size
386490851 RederenceSound C:\Users\lipengzha\Desktop\WWise\WwiseDemoGame\UnrealWwiseDemo\.cache\Windows\SFX\Shotgun_Fire_01_A07A4AEB.wem \Actor-Mixer Hierarchy\Default Work Unit\RederenceSound 257120

The similar Post* actions in UAkGameplayStatics where the Actor is passed function to get the World, and then retrieve AkComponent from that Actor (if it doesn’t exist, create it and attach it to the Actor’s RootComponent):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct AkDeviceAndWorld
{
FAkAudioDevice* AkAudioDevice;
UWorld* CurrentWorld;

AkDeviceAndWorld(AActor* in_pActor) :
AkAudioDevice(FAkAudioDevice::Get()),
CurrentWorld(in_pActor ? in_pActor->GetWorld() : nullptr)
{}

AkDeviceAndWorld(UObject* in_pWorldContextObject) :
AkAudioDevice(FAkAudioDevice::Get()),
#if UE_4_17_OR_LATER
CurrentWorld(GEngine->GetWorldFromContextObject(in_pWorldContextObject, EGetWorldErrorMode::ReturnNull))
#else
CurrentWorld(GEngine->GetWorldFromContextObject(in_pWorldContextObject))
#endif // UE_4_17_OR_LATER
{}

bool IsValid() const { return (CurrentWorld && CurrentWorld->AllowAudioPlayback() && AkAudioDevice); }
};

Using the Event to specify the SoundBank, executing Generate Selected Bank workflow is as follows:

  1. Get the list of names of the selected SoundBank
  2. Retrieve all UAkAudioEvent objects in the engine and analyze if the names of RequireBank from AkSoundEvent match the names obtained from the first step, thereby getting all AkAudioEvents in the SoundBank;
  3. Based on the results of the above two analyses, generate SoundBankDefinitionFile, which is stored in the UE project directory, named TempDefinitionFile.txt
1
2
3
4
5
6
7
8
9
10
11
VelocityBank	"PlayRederenceSoundTest"
VelocityBank "VelocityLoop"
ExtSrcBnk "Play_MyExtSrc"
AmbientBank "AmbientNoise_NotSpatialized"
AmbientBank "AmbientNoise_Spatialized"
ReverbBank "Fire_Weapon"
MatineeBank "Closed_Hi_Hat"
MatineeBank "Kick"
MatineeBank "Snare"
SubtitleBank "Play_Subtitles"
SwitchBank "Play_Tone"

This records the association between UE’s SoundBank objects and their associated Event as well as Bus names, passed to WwiseCLI.exe using the -ImportDefinitionFile parameter, allowing WWise to understand the correspondence between UE’s SoundBank and Event.

  1. On the WWise side, based on the ImportDefinitionFile, it matches with Events and Bus in the WWise project, creating a SoundBank that contains the specified Events and generates json/xml/txt descriptor files.

The loading process for UE using LoadBank should be:

  1. Retrieve the Bank’s name via the Bank object.
  2. Look for the corresponding bnk file in the WwiseSoundBankFolder directory based on the Bank name.
  3. Obtain the bnk file and proceed with loading.

Interaction Between UE and WWise

In UE, the use of WWise API to play and control sound is introduced.

PlaySoundAtLocation

The APIs are available in UAkGameplayStatics:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** Posts a Wwise Event at the specified location. This is a fire and forget sound, created on a temporary Wwise Game Object. Replication is also not handled at this point.
* @param AkEvent - Wwise Event to post.
* @param Location - Location from which to post the Wwise Event.
* @param Orientation - Orientation of the event.
*/
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic", meta=(WorldContext="WorldContextObject", AdvancedDisplay = "3"))
static int32 PostEventAtLocation(class UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation, const FString& EventName, UObject* WorldContextObject );

/** Posts a Wwise Event by name at the specified location. This is a fire and forget sound, created on a temporary Wwise Game Object. Replication is also not handled at this point.
* @param AkEvent - Wwise Event to post.
* @param Location - Location from which to post the Wwise Event.
* @param Orientation - Orientation of the event.
*/
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic", meta=(WorldContext="WorldContextObject", DeprecatedFunction, DeprecationMessage = "Please use the \"Event Name\" field of PostEventAtLocation"))
static void PostEventAtLocationByName(const FString& EventName, FVector Location, FRotator Orientation, UObject* WorldContextObject );

These two APIs can be used to play sounds at a specified location by passing resources of AkAudioEvent or the Event name. PostEventAtLocationByName is a wrapped version of UAkGameplatStatics::PostEventAtLocation, with AkAudioEvent being NULL.

PostEvent

Sends a specified Wwise Event bound to the root component of the given Actor. If the incoming Actor does not have an AkComponent attached, one will be created on PostEvent and will be attached by default to that Actor’s RootComponent.

After PostEvent, if subsequent control of Event values is needed, like RTPC or Switch, the corresponding Actor must be passed in (since PostEvent essentially works through AkComponent):

1
2
3
4
5
6
7
8
9
10
11
12
AkPlayingID FAkAudioDevice::PostEvent(
const FString& in_EventName,
UAkComponent* in_pComponent,
const FOnAkPostEventCallback& PostEventCallback,
AkUInt32 in_uFlags, /*= 0*/
const TArray<AkExternalSourceInfo>& in_ExternalSources /*= TArray<AkExternalSourceInfo>()*/
)
{
return PostEvent(in_EventName, in_pComponent, in_ExternalSources, [PostEventCallback, in_uFlags, this](AkGameObjectID gameObjID) {
return CallbackManager->CreateCallbackPackage(PostEventCallback, in_uFlags, gameObjID);
});
}

Currently, my understanding is that the Actor (with AkComponent) passed in serves as context for playing sounds in WWise, allowing further operations to control specific events through this context.

Furthermore, PostEvent has various types of callback functions, controlled through the mask values passed in, defined by the EAkCallbackType enum (bitmask):

1
2
3
4
5
6
7
static int32 PostEvent(	class UAkAudioEvent* AkEvent, 
class AActor* Actor,
UPARAM(meta = (Bitmask, BitmaskEnum = EAkCallbackType)) int32 CallbackMask,
const FOnAkPostEventCallback& PostEventCallback,
const TArray<FAkExternalSourceInfo>& ExternalSources,
bool bStopWhenAttachedToDestroyed = false,
FString EventName = FString(""));

Available values for EAkCallbackType:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// Type of callback. Used as a bitfield in methods AK::SoundEngine::PostEvent() and AK::SoundEngine::DynamicSequence::Open().
UENUM(BlueprintType, meta = (Bitmask))
enum class EAkCallbackType : uint8
{
EndOfEvent = 0 UMETA(ToolTip = "Callback triggered when reaching the end of an event. AkCallbackInfo can be cast to AkEventCallbackInfo."),
Marker = 2 UMETA(ToolTip = "Callback triggered when encountering a marker during playback. AkCallbackInfo can be cast to AkMarkerCallbackInfo."),
Duration = 3 UMETA(ToolTip = "Callback triggered when the duration of the sound is known by the sound engine. AkCallbackInfo can be cast to AkDurationCallbackInfo."),

Starvation = 5 UMETA(ToolTip = "Callback triggered when playback skips a frame due to stream starvation. AkCallbackInfo can be cast to AkEventCallbackInfo."),

MusicPlayStarted = 7 UMETA(ToolTip = "Callback triggered when a Play or Seek command has been executed (Seek commands are issued from AK::SoundEngine::SeekOnEvent()). Applies to objects of the Interactive-Music Hierarchy only. AkCallbackInfo can be cast to AkEventCallbackInfo."),

MusicSyncBeat = 8 UMETA(ToolTip = "Enable notifications on Music Beat. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."),
MusicSyncBar = 9 UMETA(ToolTip = "Enable notifications on Music Bar. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."),
MusicSyncEntry = 10 UMETA(ToolTip = "Enable notifications on Music Entry Cue. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."),
MusicSyncExit = 11 UMETA(ToolTip = "Enable notifications on Music Exit Cue. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."),
MusicSyncGrid = 12 UMETA(ToolTip = "Enable notifications on Music Grid. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."),
MusicSyncUserCue = 13 UMETA(ToolTip = "Enable notifications on Music Custom Cue. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."),
MusicSyncPoint = 14 UMETA(ToolTip = "Enable notifications on Music switch transition synchronization point. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."),

MIDIEvent = 16 UMETA(ToolTip = "Enable notifications for MIDI events. AkCallbackInfo can be cast to AkMIDIEventCallbackInfo."),
};
  • EndOfEvent: Triggered when the event ends; can be cast to AkEventCallbackInfo
  • Marker: Triggered when encountering a marker; can be cast to AkMarkerCallbackInfo
  • Duration: Triggered when the Sound Engine knows the current sound duration; can be cast to AkDurationCallbackInfo
  • Starvation: Triggered when playback skips a frame due to stream starvation; can be cast to AkEventCallbackInfo
  • MusicPlayStarted: Triggered when a Play or Seek command is executed (Seek commands are issued from AK::SoundEngine::SeekOnEvent); applicable to objects in the Interactive-Music Hierarchy only; can be cast to AkEventCallbackInfo
  • MusicSyncBeat: Enable notifications on Music beat; can be cast to AkMusicSyncCallbackInfo
  • MusicSyncBar: Enable notifications on Music Bar; can be cast to AkMusicSyncCallbackInfo
  • MusicSyncEntry: Enable notifications on Music Entry; can be cast to AkMusicSyncCallbackInfo
  • MusicSyncExit: Enable notifications on Music Exit; can be cast to AkMusicSyncCallbackInfo
  • MusicSyncGrid: Enable notifications on Music Grid; can be cast to AkMusicSyncCallbackInfo
  • MusicSyncPoint: Enable notifications on Music switch transition synchronization point; can be cast to AkMusicSyncCallbackInfo
  • MIDIEvent: Enable notifications for MIDI events; can be cast to AkMIDIEventCallbackInfo

Marker

Audio added in the Sound VFX in the WWise editor can be edited to add Markers, which can be received in UE by setting the CallbackMask parameter to EAkCallbackType::Marker when calling UAkGameplayStatics::PostEvent. This allows receiving the location of each Marker hit during playback for performing game logic actions, such as displaying audio subtitles.

Listening in UE:

Moreover, the Identifier also starts from 0:

Adding Markers in WWise:

In UE, when PostEvent, the value of the CallbackMask can be added along with Marker, allowing for receiving each encountered marker during playback in the associated event, useful for displaying subtitles while playing.

RTPC

Real-time Parameter Controls (RTPC) allow real-time control of specific properties of various Wwise objects (including sound objects, containers, buses, effects, etc.) based on real-time parameter changes occurring in the game.

RTPC can transfer values from the program to the WWise side, allowing WWise to control the sound based on the incoming values.

In WWise, RTPC:

Switch

Switches can be used in the engine to switch different modes, such as the sounds of players walking on different surfaces.

In PostEvent one AkEvent, the SwitchGroup and SwitchState can be passed along with the Actor passed at the time of PostEvent.

Sequencer

WWise provides support for use in Sequencer, enabling the use of AkAudioEvents and AkAudioRTPC to play and control Events within a Sequence.

Animation

And can be used in animation notifications:

Specify the Event for the notification:

## CommandLet
WWise provides Commandlet in UE for handling the batch generation of Banks.

Introduction to usage:

1
2
3
4
5
6
7
8
Commandlet allowing to generate Wwise SoundBanks.
Usage: <Editor.exe> <path_to_uproject> -run=GenerateSoundBanks [-platforms=listOfPlatforms] [-banks=listOfBanks] [-wwiseCliPath=pathToWwiseCli]
Parameters:
- platforms: (Optional) Comma separated list of platforms for which SoundBanks will be generated, as specified in the Wwise project. If not specified, SoundBanks will be generated for all platforms.
- banks: (Optional) Comma separated list of SoundBanks to generate. Bank names must correspond to a UAkAudioBank asset in the project. If not specified, all SoundBanks found in project will be generated.
- wwiseCliPath: (Optional) Full path to the Wwise command-line application to use to generate the SoundBanks. If not specified, the path found in the Wwise settings will be used.
- help: (Optional) Print this help message. This will quit the commandlet immediately.
For more information, see https://www.audiokinetic.com/library/edge/?source=UE4&id=using_features_generatecommandlet.html

The Commandlet for generating SoundBank is:

1
Engine/Binaries/Win64/UE4Editor.exe "D:/WwiseDemoGame.uproject" -run=GenerateSoundBanks -platforms=Windows,Android,iOS -wait

The -wait parameter is included in the CommandLet to control waiting for user input after execution is complete, preventing the execution window from flashing and disappearing. When no specific SoundBank is designated for generation, all defined SoundBanks in the project will be generated. If you want to specify, you can use the format -banks=aaa,bbb,ccc and so on.

The final execution command is (you can see the -ImportDefinitionFile parameter):

1
"C:\Program Files (x86)\Audiokinetic\Wwise 2019.1.9.7221\Authoring\x64\Release\bin\WwiseCLI.exe"  "D:/WwiseDemoGame/UnrealWwiseDemo/UnrealDemo.wproj" -GenerateSoundBanks -Bank ExtSrcBnk -Bank AmbientBank -Bank ReverbBank -Bank VelocityBank -Bank MatineeBank -Bank SubtitleBank -Bank SwitchBank -ImportDefinitionFile "D:/WwiseDemoGame/TempDefinitionFile.txt" -Platform Windows -SoundBankPath Windows "D:\WwiseDemoGame\Content\WwiseAudio\Windows" -Platform Android -SoundBankPath Android "D:\WwiseDemoGame\Content\WwiseAudio\Android" -Platform iOS -SoundBankPath iOS "D:\WwiseDemoGame\Content\WwiseAudio\iOS"

Breaking down the command:

  1. Path to WWiseCLI.exe
  2. -GenerateSoundBanks: Passed to WWiseCLI.exe indicating the function to generate SoundBank
  3. -Bank: Specify the name of the Bank
  4. -ImportDefinitionFile: Specify the TempDefinitionFile.txt
  5. -Platform: Specify the platform
  6. -SoundBankPath: Specify the directory where the platform’s generated content will be saved

Command Line Parameters

  • -nosound: Disable sound in WWise.
1
2
3
4
5
6
7
8
9
bool FAkAudioDevice::EnsureInitialized()
{
// We don't want sound in those cases.
if (FParse::Param(FCommandLine::Get(), TEXT("nosound")) || FApp::IsBenchmarking() || IsRunningDedicatedServer() || IsRunningCommandlet())
{
return false;
}
// ...
}

Global Pause Music

This can be achieved with the following code:

1
2
3
4
5
6
// on enter background
AK::SoundEngine::Suspend(false);

// on enter foreground
AK::SoundEngine::WakeupFromSuspend();
AK::SoundEngine::RenderAudio();

Hot Updating WWise’s Banks

From the above analysis, it can be seen that WWise generates Banks on a per-platform basis, creating corresponding folders for each platform. This requires us to include different external files based on different platforms during hot updates. HotPatcher already supports this feature, enabling specific platforms to contain special files.

Disadvantages of WWise Integration into UE

The official importation of Events and the process of specifying Banks is too cumbersome; each Event must be dragged into the Content Browser to create UE Event resources, followed by manually specifying RequireBank, which is quite troublesome. In fact, the WWise editor itself contains the functionality for Bank editing, but there is no official method provided in UE for batch generation of Event and Bank resources. This can be a point for amateur extension development, creating a batch import feature; this is a later topic, to be tackled when time permits.

Documentation

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:UE integrated WWise: concept and code analysis
Author:LIPENGZHA
Publish Date:2020/09/12 15:31
Word Count:12k Words
Link:https://en.imzlp.com/posts/9809/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!