Audio and Physics
January 30, 2022
Audio
After working on Chronicle in silence for quite a while I finally decided it was time to get some basic audio up and running. I wasn't looking to write an audio system from scratch, so I turned to GitHub searching for an open source solution. I landed on SoLoud for a few reasons. It has good documentation, supports multiple back-ends, 3D audio support, and a single entry point for most interactions with it.
Besides the documentation the biggest reason I decided on SoLoud was the relative ease of setting up and interacting with it at runtime. To get the audio engine started all I had to do was create a SoLoud::Soloud object and initialize it. That same object is also how you play any sound and set any sound settings, which made it very easy to create a wrapper around it to keep SoLoud out of the rest of the engine in-case I ever decide to try out a different audio library.
The above is the wrapper around SoLoud that it took to get basic audio running in Chronicle. Pretty straight forward right? Also a quick note in Chronicle there are a handful of singletons like this that all follow this Instance() type of interface to access/create the singleton which are all called for the first time in the owning libraries initialize functions to avoid order and threading issues.
Update: Called once a frame to update the listeners positions for 3d audio
Currently the listener is just the active camera.
Play*: Straight forward methods for playing different types of audio. Background, 3d, and non-3d sounds.
StopSound: If you can play a sound you need a way to stop it as well.
IsSoundHandleValid: Really just a way to check if a sound is still playing.
GetLoopCount: Returns the number of times the provided sound has looped.
Disable: One of the first things you add after getting audio working is a way to easily turn it off as you get tired of your background music and sound effects.
That covers the wrapper around the SoLoud engine, but the other required bit is the ability to load sound files(.wav) to be played. SoLoud has a built-in functionality to load wav files which was another point in its favor. So again to keep SoLoud from spreading throughout Chronicle I created a wrapper around SoLoud::Wav.
Again a pretty simple wrapper around SoLoud::Wav to keep it contained as well as integrate it with Chronicles Resource Manager(I'll cover this in my next post). To keep SoLoud::Wav extra hidden I decided to just make the AudioEngine class a friend so it could access the SoLoud::Wav when a Play is requested with this resource.
Now I have everything I need to play a sound effect in Chronicle, except a component that actually holds an audio resource and requests it be played by the AudioEngine. The simplest test was to add a BackgroundMusicComponent that holds a list of AudioClips and randomly selects one on load and as the active one finishes. I also added support for playing sound effects on collision events, but I'll cover some of that in the physics section of this post.
Physics Collisions and Trigger Volumes
While Chronicle has had physics for a quite a while it was a very basic integration that didn't catch or handle any of the collision events from PhysX. The only thing it really did other than apply basic physics to objects was the character controller would add impulses to any dynamic bodies they touched. While working on adding audio of course I wanted to have some sound effects on collision events which led me expand the integration of PhysX including adding TriggerVolumes which have a bunch of uses in any type of engine/game.
First thing to do was add collision groups/filters to the rigid bodies being created.
The only filter really used right now is generic. Long term I'd like to make this enum expandable by the game, but for now this exist in the Physics Library, but it is exposed via reflection to the tooling for easily setting a filter on a physics component. Now that I have a filter enum and a way to set on the data being used to create rigid bodies I need to add some callbacks and handlers to the physics simulation wrapper. For PhysX this means deriving from PxSimulationEventCallback and overriding any callback functions you want to handle yourself.
I overrode all of the callbacks, but only really handle 2 of them(onContact and onTrigger). I probably should have at least commented them out, but I figured I'd leave it like this for future me when I expand the physics integration more. So now the physics simulation is listening for simulation events, but there's still one missing piece to get basic collision event handling going. In PhysX that's a FilterShader. This "shader" is responsible for checking filter information on bodies and determining the type of contact event they represent.
As you can see it's pretty basic, just checking the attributes on pairs of bodies and if and what type of event that represents. In the simple case right now it's if one of the objects is a Trigger Volume or do the objects collide based on their filters and masks. Set this FilterShader on the scene description used to create the physic scene and Chronicle starts getting callbacks for events from the simulation it cares about.
Now I just leverage the event system built into CrObjects to send out the resulting collision event information to the colliding object. Note: the owning object is set to a rigid body's userData void* to easily map back to the owning CrObject for things like this. Now any component can register for the PhysicsCollisionMessage and if it's on an object that collides it will be notified and can do any work it wants such as play a sound effect.
The onTrigger handling is bit more involved because you want to handle a body entering and leaving a trigger volume. I also wanted to send messages to both the objects owning the volumes and the objects interacting with the volumes. Just like the collision event any component and register for these messages and then do any work in response to them. For example as you'll see in the video below the DynamicWaterComponent listens for the PhysicsTriggerVolumeEnteredMessage and when receiving it starts a splash at the entering objects location and plays a sound effect that has been set on the component. There is also a new VolumeTriggerComponent that contains all the data for creating the trigger volume, but the DynamicWaterComponent doesn't actually know anything about it. The DynamicWaterComponent will still work on an object without a trigger volume it just won't do anything real interesting other than be a runtime generated plane mesh.
The above video is just showing the things I talked about in this post and some of the Imgui menus put in place to interact with audio.
My next post will be about the Chronicle Resource Manager and some of the recent work I've done to make it asynchronous and some of the fallout from that. One day I hope to get back to the terrain and streaming work I've mentioned in previous posts. The Resource Manager work was a precursor to that, but the audio and physics changes were just something that caught my interest so I tackled that first.