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

Minseok Lee

May 25, 2023 at 01:28 am

thanks for sharing! i'm now trying to follow your logic and reproduce similar effect with more than 2 separate audio output devices howeveer, i'm quite stuck in two parts First, i'm not a professional in c++ but in my humble opinion, i believe implementing those 3 functions should be under AkAudioDeivce.cpp isn't it? Second, well, actually it can be second after implementing those functions under AkAudioDevice.cpp is correct but let's pass the issue for now. Second, I could not understand the whole part 3. Such as, What is emitter and how can I add it as component in Unreal? What if I use multiple speakers that has same name in their name, if they are both produced in a same company like Sony1 and Sony2? What is the meaning of setting listener in the blueprint? Sorry for throwing bunch of questions Hope you stay well and also will wait for your reply, if possible Thanks again for sharing your hard work and knowledge with community!

Leave a Reply

Your email address will not be published.

More articles

Approaching UI Audio from a UI Design Perspective - Part 2

In this article I’d like to show you how approaching UI audio as if you yourself were the UI...

30.7.2019 - By Joseph Marchuk

Putting the Virtual back into Reality

Imagine taking a Virtual Reality experience out of its virtuality, and placing it in the real world,...

17.9.2019 - By Billy Mello

How Sound Designers Use PureData + Heavy to Develop DSP Plug-ins - Part 2

In Part 1, I explained how to create a “blueprint” using the Patch file. Now let me show you how to...

15.10.2019 - By Chenzhong Hou (侯晨钟)

Game of Thrones: Winter is Coming - How We Combined Original Soundtracks and Created Interactive Music Using Wwise

Hello. I’m Victor Liu, a sound designer and programmer at YooZoo Games in China. I was responsible...

20.11.2019 - By Victor Liu (刘子奇)

The Sound of FINAL FANTASY VII REMAKE

The music of FINAL FANTASY VII is beloved by many and has been revisited over the years for new...

23.10.2020 - By SQUARE ENIX

Developing first-person horror games solo

My Background I am Alessandro Guzzo, a sole game developer of the first-person horror games, The...

22.4.2022 - By Alessandro Guzzo

More articles

Approaching UI Audio from a UI Design Perspective - Part 2

In this article I’d like to show you how approaching UI audio as if you yourself were the UI...

Putting the Virtual back into Reality

Imagine taking a Virtual Reality experience out of its virtuality, and placing it in the real world,...

How Sound Designers Use PureData + Heavy to Develop DSP Plug-ins - Part 2

In Part 1, I explained how to create a “blueprint” using the Patch file. Now let me show you how to...