Blog homepage

Implementing Two Audio Devices to your UE Game Using Wwise

Game Audio / Sound Design / VR Experiences

1

First, let me introduce myself. My name is Ed Kashinsky and I am a sound designer and musician from Saint-Petersburg, Russia. As for now, I’m working on a very interesting and unique sound-wise project. It’s a local multiplayer VR game called Priest vs. Poltergeist VR. Iit turned out to be a challenge sound-wise. That’s why.

So, Priest vs. Poltergeist is a duel game between a VR player who plays as a Poltergeist and a PC player who plays as a Priest and controls the character with mouse and keyboard. The challenging part is they play on one PC. Not only both should hear sound from different output devices (VR headphones and PC headphones/speakers), but each sound should have its own positioning, attenuation, occlusion, timing, etc. as well. 

Imagine a chair has fallen on the floor somewhere in the level. If one player is a couple of meters away from it the sound sounds clear. But if the other player is at the same distance away but there’s a wall between the chair and the player then the hit should sound drier and lower. So, both players must hear the hit sound from their output device but modified to the current location and settings of each. 

After testing several solutions including Unreal’s native audio library we stopped on Wwise. It’s a standalone audio engine for games that lets you do whatever you want with sound in its studio app. Even send the sound to different audio devices that is our ultimate goal.

To integrate this engine’s features into Unreal Wwise provides a plugin. In theory, it should pass all the Wwise engine functionality to the game engine so you could use its features there. And this is where we quickly hit the roadblock. Turns out its Unreal plugin doesn’t cover the functionality we need to play sound on secondary audio device outputs. 

We had no choice but to roll up our sleeves and add this feature in C++ ourselves.

The Goal

The main component of the plugin - AkComponent - exists on the level and works as both a listener and emitter. The emitter part plays a sound at any location in the level. A listener is like a pair of ears that listens to all the sounds and outputs the sound to the audio device according to the character's location (it usually attaches to the camera component). 

So, we have two characters. Each has an AkComponent as a listener that outputs to two audio devices simultaneously. In general, it looks like this:

2

  • Emitter fires a blueprint Post Ak Event function, that in turn fires an event in Wwise
  • This event plays the sound that goes to two audio buses
  • Audio buses send sound to their own Wwise audio device. In our case, it’s System or System_VR
  • Each listener is connected to its own output (Output Device 1Output Device 2) and listens to the sound from Wwise. When the sound reaches the audio bus it plays on its own output. 

This is what we aim for: we get two text fields in the AkComponent settings in which we can type the audio device name to route our sounds to. 

3

Step one. Setting up Wwise

First, let’s create Wwise audio devices and audio buses. Check the original docs (Bus Routing section) for more info.

4

  • Add a device in Wwise for a VR player. Set “System” as the type and name it “System_VR”. This is the name of a Wwise audio device that you should eventually put in our "Wwise Device Name" field of the AkComponent settings.
  • Create an Audio Bus for VR and set System_VR as an Audio Device.

5

  • In Wwise the sound can be sent to one output bus only. However, we need the sound to be played for two characters so let's use an Auxilary Bus for that.

6

To play one sound on two devices we should create a nested Auxiliary Bus for VR audio bus. It should look like this:

7

Now we can send a sound to each character or to both. 

***

So, the main part is done and all is left is to set buses for every sound. For example:

  • Priest sound (PC): set Master Audio Bus in the Output Bus menu, Auxiliary Buses is blank
  • Poltergeist sound (VR): set Master Audio VR Bus in the Output Bus menu, Auxiliary Buses is blank
  • Sounds for both: set Master Audio Bus in the Output Bus menu and Master_VR_Aux_Bus in the Auxiliary Buses list 

8

Now Wwise is all set up.

Step two. Setting up the AkComponent

Here we are going to deal with a compiler, otherwise, the changes we do to the plugin won’t work. If you don’t know how to convert your Blueprints project into C++ project and how to compile it check these docs:

https://docs.unrealengine.com/en-US/Programming/Development/CompilingProjects/index.html

https://docs.unrealengine.com/en-US/Programming/Introduction/index.html

Files we are going to work on you can find here

{Project Folder}/Plugins/Wwise/Source/AkAudio

AkAudioDevice.h

Open /Public/AkAudioDevice.h and add after “public:”

// connecting audio device with device from wwise 
AkOutputDeviceID AddCustomOutput(FString AudioDevice, FString WwiseDevice, UAkComponent* in_pComponent);
// removing connection
AKRESULT RemoveCustomOutput(AkOutputDeviceID deviceId);
//  searching audio device by substring
TTuple <AkUInt32, FString> SearchAudioDeviceIdByName(FString deviceName);

9

AKComponent.h 

Now go to /Classes/AkComponent.h and add this code after “public:”:

TTuple <AkUInt32, FString> FAkAudioDevice::SearchAudioDeviceIdByName(FString deviceName)
{
TTuple <AkUInt32, FString> result;
AkUInt32 deviceId = AK_INVALID_DEVICE_ID;

if (deviceName.Len() == 0) {
// getting default device
AK::GetWindowsDevice(-1, deviceId, NULL, AkDeviceState_Active);
auto deviceNameWstr = AK::GetWindowsDeviceName(-1, deviceId, AkDeviceState_Active);
result.Key = deviceId;
result.Value = FString(deviceNameWstr);
} else {
AkUInt32 immDeviceCount = AK::GetWindowsDeviceCount(AkDeviceState_Active);
for (AkUInt32 i = 0; i < immDeviceCount; ++i) {
AK::GetWindowsDevice(i, deviceId, NULL, AkDeviceState_Active);
auto deviceNameWstr = AK::GetWindowsDeviceName(i, deviceId, AkDeviceState_Active);
if (FString(deviceNameWstr).Contains(deviceName)) {
result.Key = deviceId;
result.Value = FString(deviceNameWstr);
break;
}
}
}
return result;
}
AkOutputDeviceID FAkAudioDevice::AddCustomOutput(FString AudioDevice, FString WwiseDevice, UAkComponent* in_pComponent)
{
TTuple <AkUInt32, FString> Device;
AkOutputDeviceID deviceId = AK_INVALID_DEVICE_ID;
FString WwiseDeviceName = "System";
AKRESULT res = AK_Fail;
if (AudioDevice.Len() == 0 && WwiseDevice.Len() == 0) {
return deviceId;
}
if (WwiseDevice.Len() > 0) {
WwiseDeviceName = WwiseDevice;
}
Device = SearchAudioDeviceIdByName(*AudioDevice);
if (Device.Key) {
AkOutputSettings outputSettings(*WwiseDeviceName, Device.Key);
auto gameObjID = in_pComponent->GetAkGameObjectID();
res = AK::SoundEngine::AddOutput(outputSettings, &deviceId, &gameObjID, 1);
}
FString componentName = in_pComponent->GetName();
if (res != AK_Success) {
UE_LOG(LogAkAudio, Error, TEXT("Error attaching of AkComponent \"%s\" to \"%s\" <-> \"%s\". Error \"%d"), *componentName, *AudioDevice, *WwiseDeviceName, res);
} else {
UE_LOG(LogAkAudio, Warning, TEXT("AkComponent \"%s\" attached to \"%s\" <-> \"%s\" "), *componentName, *Device.Value, *WwiseDeviceName);
}
return deviceId;
}
AKRESULT FAkAudioDevice::RemoveCustomOutput(AkOutputDeviceID deviceId)
{
return AK::SoundEngine::RemoveOutput(deviceId);
}

AKComponent.h 

Now go to /Classes/AkComponent.h and add this code after “public:”:

/**
* Name of Audio Device in Wwise. If empty, "System" is using
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = "AkComponent")
FString WwiseDeviceName;
/**
* Name of Audio Device in OS. If empty, default device is using
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = "AkComponent")
FString AudioDeviceName;
AkOutputDeviceID OutputID;

10

AkComponent.cpp

And finally go to /Private/AkComponent.cpp and search for two empty functions PostRegisterGameObject and PostUnregisterGameObject and replace them:

void UAkComponent::PostRegisterGameObject()
{
FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
if (AudioDeviceName.Len() > 0 || WwiseDeviceName.Len() > 0) {
OutputID = AkAudioDevice->AddCustomOutput(AudioDeviceName, WwiseDeviceName, this);
}
}
void UAkComponent::PostUnregisterGameObject()
{
FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
if (AkAudioDevice && OutputID != AK_INVALID_DEVICE_ID) {
AkAudioDevice->RemoveCustomOutput(OutputID);
}
}

11

Congratulations if you are reading this! All you have to do is to compile the project in Visual Studio and run Unreal editor. 

If all done right two new text fields appear in Unreal Engine editor’s AkComponent settings. These are the fields that connect your audio output device with Wwise audio device.

  • Wwise Device Name - “System” or “System_VR” in our case
  • Audio Device Name - your hardware device name like “Sony Headphones”, for example. Only “Headphones” should be enough to find the actual device.

Whether the device found or not you’ll see the result in the Output Log in the Unreal Editor:

12

Step 3. Finishing touches

The final step is to set characters Listeners. Create an AkComponent for every Pawn/Character. Then type in audio devices in AkComponent’s settings for each one of them.

13

This is important: you should set listeners for EVERY emitter. Otherwise the whole setup won’t work. There’s a “Set Listeners” blueprint function for that.

Do this on Begin Play, for example:

14

That’s it. Play in editor and check both audio devices. This is what you should see in the Output log as well as hear the sound from both output devices.

15

Check my Soundcloud or message me if you have any questions.

 

Ed Kashinsky

Composer and Sound Designer

Ed Kashinsky

Composer and Sound Designer

Ed is a Saint-Petersburg-based sound designer and musician with a background in sound production and software development. He is interested in interactive music and sound engines.

Comments

Leave a Reply

Your email address will not be published.

More articles

Is Hybrid Interactive Music the Future? PART I - How I used Get Even as an R&D platform for Interactive Music

When I write music for video games, I always wonder about how I can make it more meaningful for the...

27.3.2018 - By Olivier Derivière

Behind the Sounds of Another Sight

London, end of the Victorian Era. The British Empire is at the apex of its power. The Royal Navy...

24.7.2018 - By Luca Piccina

Jurassic World: VR Expedition

Jurassic World: VR Expedition, an interactive cinematic virtual reality (VR) location-based...

9.10.2018 - By Hexany Audio

Dialogue and Conversation Design in Murderous Pursuits - Pt. 2

Jaime here again with the second part on Murderous Pursuits’ conversation and dialogue systems. If...

30.10.2018 - By Jaime Cross

Sound Design of Immortal Legacy: The Jade Cipher - Part 1

Hello. I’m Eddy Liu, the audio designer of the recently released Immortal Legacy: The Jade Cipher. I...

3.7.2019 - By Eddy Liu

Clocker: Designing Game Audio Logic (Part 1)

In this two-part blog series, we'll be going over the way we designed the sound system for Clocker. ...

16.1.2020 - By YU TAN

More articles

Is Hybrid Interactive Music the Future? PART I - How I used Get Even as an R&D platform for Interactive Music

When I write music for video games, I always wonder about how I can make it more meaningful for the...

Behind the Sounds of Another Sight

London, end of the Victorian Era. The British Empire is at the apex of its power. The Royal Navy...

Jurassic World: VR Expedition

Jurassic World: VR Expedition, an interactive cinematic virtual reality (VR) location-based...