It's already been a couple of years since the first version of the Wwise Authoring Query Language (WAQL) was released. It has been pretty much unchanged since the first version. The most notable change since the introduction was the integration of WAQL in the Project Explorer Search for Wwise 2022.1. Now with the upcoming release of 2023.1, we will discuss here how we are bringing the query language to the next level.
If you don't know what WAQL is, I invite you to read this other blog: https://blog.audiokinetic.com/en/introducing-waql/
Before we get into the details, let's summarize some of the efforts we did under the hood for Wwise Authoring 2023.1. Over the last couple of years, we have been converting some of the oldest data models of Wwise Authoring to what we refer to as generic object models. Basically, this allows us to treat all objects in Wwise the same way. For instance, each object has properties like volume, or references to other objects, like the output bus. This allows us to expose anything generic to WAQL or WAAPI without the need to create specialized functions. For example, WAAPI does not have a setVolume function. To set the volume, you can either call ak.wwise.core.object.setProperty or ak.wwise.core.object.set and pass "volume" as the property name.
Something relatively new we added to our object model is the concept of object lists. Object lists allow us to store a variable number of objects, or references to objects, inside any object. For example, each object in the Actor-Mixer Hierarchy now has a list of RTPC objects, and also a list of Effect Slots. In the same way as the volume, it is possible to set RTPC objects or effects with ak.wwise.core.object.set.
Let's talk more about effects and WAQL. In 2022.1 and earlier, if you wanted to query for all objects with an EQ on slot 0, you could do:
$ where effect0.pluginname : "EQ"
This would return all objects in the project where the first effect's plug-in name contains "EQ". Well, the query becomes less pretty if you are looking at any effect slot:
$ where effect0.pluginname : "EQ" or effect1.pluginname : "EQ" or effect2.pluginname : "EQ" or effect3.pluginname : "EQ"
And in 2023.1, we are removing these four fixed effect slots and replacing them with an object list. Now how can we query the effects in the object list with WAQL? To make this possible, we had to add proper support for object lists in WAQL.
Introducing List Functions
In WAQL, the different types of accessors that you can use inside a where statement, select statement, or inside a return expression, can be of different types. Here is a list of the types:
Accessor Type |
Return |
Examples |
Property |
Variant (integer, float, boolean, string). |
Volume, Pitch, IsLoopingEnabled |
Reference |
Wwise Object |
Output Bus, User Aux Send 0, Attenuation |
Object List (NEW!) |
An array of Wwise Objects |
RTPC, Effects, children, descendants |
JSON object |
A JSON object with key-values. |
duration.max, duration.min, loudness.integrated |
Since the object lists are now natively supported in WAQL, there are a few things we can do with them. One thing that was added in relation to lists is the concept of functions that operate on all items of a list at once.
Let's start with the count() function. The count() function returns the number of items in the list. This number can be compared with another number. For example:
$ where effects.count() > 0
This is great, and the count() function can also take an optional condition as an argument. So you can count the number of items matching a certain condition. Let's try to search for any object with an EQ in the effect list:
$ where effects.count(effect.pluginname : "eq") > 0
It's also possible to write this query with the any() function, which returns true as soon as one item meets the condition, which is more effective:
$ where effects.any(effect.pluginname : "eq")
These are the functions that can be used on lists:
Function |
Description |
Examples |
count |
Returns the number of items in the list matching the specified condition, otherwise returns false. The condition statement is optional. |
$ where children.count() > 3 |
any |
Returns true if any item is found matching the condition, otherwise returns false. Returns false if empty. The condition statement is optional. |
$ where rtpc.any() |
all |
Returns true if all items in the list are matching the specified condition, otherwise returns false. Returns false if empty. The condition statement is mandatory. |
$ where children.all(type = "Sound") |
first |
Returns the first item matching the specified condition. The condition statement is optional. |
$ where effects.first(effect.pluginname :"EQ") != null |
last |
Returns the last item matching the specified condition. The condition statement is optional. |
$ where effects.last(effect.pluginname :"EQ") != null |
take |
Takes the specified number of items, and returns them in a new list. |
$ select children.take(2) |
skip |
Skips the specified number of items, and returns the rest in a new list. |
$ select effects.skip(1) |
at |
Returns the item at the specified index in the list. |
$ select effects.at(0) |
where |
Returns the items matching the specified condition in a new list. |
$ select effects.where(effect.pluginname :"EQ") |
More on Effects
Let's talk a bit more about effects. In 2023.1, we are increasing the maximum number of effects from 4 to 255 on the Sound Engine side. On the Wwise Authoring side, however, there is no limit (apart from system resources, like memory, at some point). So we store the effects in a list. Here is a UML diagram of the data model for the effects on a Sound object:
We are also introducing a new object type called EffectSlot. The EffectSlot is a tiny object that stores the Bypass and Render properties and also stores the Effect reference to a ShareSet or Custom effect.
The Sound object is the one that stores the list of EffectSlot objects, and also stores the BypassEffects property, which bypasses all effects at once on the Sound.
You could, in fact, query all of the EffectSlot objects in the project with:
$ from type effectslot
More Power to Return Expressions
When using WAQL along with WAAPI, it is possible to query specific data for the returned objects using return expressions. For example, you could use the following WAQL query, which returns all the Sound objects from the project:
$ from type sound
with a JSON array of return expressions:
["name", "volume"]
It would return the following table, in JSON format:
name |
volume |
Silence |
0 |
Ambient_Day_Element_01_01 |
0 |
Ambient_Day_Element_01_02 |
0 |
waterdrop_05 |
-4 |
Ambient_Water_River |
-20 |
Now let's suppose that you are interested in getting the RTPC for those sounds. You could use the following expression, which was already possible in Wwise 2022.1:
["name", "volume", "rtpc"]
However, for the rtpc column, for each row, we get something like this:
[
{
"id": "{1D1905C4-18F9-4506-AD8B-A0CDEC396F4D}"
}
]
It's an array of JSON objects, and each object contains the ID of the RTPC entry. It's only useful if you already know about those GUIDs, or if you do an additional query to learn more about the GUIDs. By default, when WAQL returns Wwise objects, it returns the ID and name of the object. In this case, the RTPC objects don't have a name, so only the ID is returned.
Let's say we are interested in the property name to which the RTPC is attached, and also the control input name driving the RTPC. In 2023.1, there is a new syntax for it that allows for returning new JSON objects by composition:
["name", "volume", "rtpc.{controlinput, propertyname}"]
It returns something like this for the last column:
[
{
"controlinput": {
"id": "{CEC3FD56-5B7C-44AF-B635-5C3A0C36825E}",
"name": "Distance_to_Camera"
},
"propertyname": "Volume"
}
]
You see that, again, we get both the id and name for the ControlInput reference, which is the default. If we are not interested in the ID of the control input, we can use this array of expressions:
["name", "volume", "rtpc.{controlinput.name, propertyname}"]
And we get this, which is more compact and straight to the point:
[
{
"controlinput.name": "Distance_to_Camera",
"propertyname": "Volume"
}
]
Now the problem that we are facing is that the code needed to access this column uses the return expression as key in the JSON object, which is very verbose. For instance, to access the RTPC fields that we asked for, on the first row, we need this Python code:
myRTPCs = results[0]['rtpc.{controlinput.name, propertyname}']
To mitigate this problem, let's introduce the concept of aliases with the keyword as, which allows us to rename the expression:
["name", "volume", "rtpc.{controlinput.name, propertyname} as rtpcs"]
Now the code is shorter, and it looks like this:
myRTPCs = results[0]['rtpcs']
Concatenation of Lists
The last topic we will introduce is the concatenation of lists. For example, let's start by getting all events from the project:
$ from type event
And here is the magic expression to get all original WAV files associated with each of the individual events:
["name as event", "children.target.[this, descendants].where(type=\"Sound\").originalRelativeFilePath as originals"]
The results:
event |
originals |
Ambient_Region_PineForest |
[ |
Ambient_River |
[ |
What is happening here? There are a lot of concepts together. Let's dissect the expression:
children |
Returns the list of children of the event, which are the actions. |
.target |
For each action, it selects the target object of the action. This normally points to an object in the Actor-Mixer Hierarchy. |
.[this, descendants] |
For each target, it builds and selects a new array, which is the concatenation of the target itself (this) and its descendants. |
.where(type=\"Sound\") |
Filters out objects by keeping only the Sound objects. Note we have escaped the double quotes because we are inside a JSON value. |
.originalRelativeFilePath |
For each Sound, it selects the original WAV file path relative to the Originals folder. |
Other Lists
Since we like lists, here is the list of lists that you can use in WAQL:
Specialized lists:
- Effects in many objects (NEW!)
- RTPCs in many objects
- Playlists in Random/Sequence Containers (NEW!)
- Markers in Audio File Sources (NEW!)
- Metadata in Actor-Mixer Hierarchy objects
- Entries, Arguments in Music Switch Containers (NEW!)
- Entries, Arguments in Dialog Events (NEW!)
- Stingers in Music objects
- Cues in Music Segments
- Sequences in Music Tracks
- Clips in Music Sequences (aka subtrack)
Generic lists:
- children
- ancestors
- descendants
- referencesTo
Closing Notes
That is a lot to digest. If you wish to learn more about it and try it out, you can use the WAQL Playground (https://github.com/ak-brodrigue/waql-playground), which was updated to support the new features we presented here. Download Wwise 2023.1, and give it a try with your own project. That is the best way to learn it and make sense of it.
The idea behind WAQL is to give control and visibility over your data. And these new features push the idea even further.
Also, remember that you can use WAQL directly in the Project Explorer search, the List View search, the Schematic View, the Toolbar search, and also directly inside the Query Editor.
There is also the WAQL Reference (https://www.audiokinetic.com/en/library/edge/?source=SDK&id=waql_reference.html) and the Wwise Object Reference (https://www.audiokinetic.com/en/library/edge/?source=SDK&id=wobjects_index.html), which are always useful to discover the hidden gems of WAQL and our object model.
Comments
Mike Maksim
August 27, 2024 at 01:51 pm
Using WAQL in the Query Editor, I'm trying to return not only an effect slot in the results, but the instance of an effect selected in it. So far, it seems I can only do one or the other. Query returning the effect slot: $ from type effectslot where path = /Mixer.*/ where Effect.PluginName= /Convolution/ where Effect.Name: "city" Query returning the effect instance (so that selecting the result opens it in the Effect Editor): $ from type effectslot where path = /Mixer.*/ where Effect.PluginName= /Convolution/ where Effect.Name: "city" select effect However, in cases where the effect reference is using a ShareSet, the path listed will be the path to the ShareSet rather than the path to the effect slot referencing it in the path I have specified in the query. Is there some way I could modify the WAQL query to return both an effect slot as well as the effect loaded in it (even if it is a shareset)? If not, I would like to suggest a feature improvement for a future Wwise release wherein, similar to the way other container objects function in query results, effect slots listed in query results will be preceded by the "+" or ">" symbol, and clicking the symbol will expand the effect slot to show the effect referenced within it, which might also be expandable (as is the case with the Wwise Convolution Reverb effect result, which will expand to show the IR audio source file it uses).
Bernard Rodrigue
September 10, 2024 at 07:30 am
Thank you for the suggestion!