Blog homepage

WAAPI in ReaScript (Lua) with ReaWwise

Audio Programming / Wwise Tips & Tools

A lesser-known feature of ReaWwise is that it exposes raw WAAPI functions to REAPER, which you can use in your own custom ReaScripts. In this blog article, we’ll look at implementing some basic Wwise-related functionality using WAAPI, all in the comfort of the ReaScript Development Environment.

Prerequisites

WAAPI & Lua

Before continuing, I recommend that you have a basic understanding of WAAPI, Lua, and ReaScript. Some resources to learn those are here:

ReaWwise

To run WAAPI commands from within a ReaScript, you must install ReaWwise

Getting Started

The ReaScript Development Environment

To get to the ReaScript Development Environment, open REAPER. In the REAPER menu, select Actions > Show action list. In the Actions dialog, in the lower-right corner, click New action > New ReaScript. At this point, you will be prompted to save a file. Name your ReaScript file and click Save. After saving, the ReaScript Development Environment opens. This is where we will write our code.

Hello Wworld

-- Waapi Hello World.lua

if(reaper.AK_Waapi_Connect("127.0.0.1", 8080)) then
    reaper.ShowConsoleMsg("Successfully connected to Wwise!")

    reaper.AK_Waapi_Disconnect()
end

In the preceding code snippet, we first make a call to AK_Waapi_Connect. This function takes in the IP and PORT on which WAAPI should communicate with Wwise. The function returns a boolean value: true if the connection was successful and false if otherwise. At the end of the if statement, we make sure to terminate the connection by calling AK_Waapi_Disconnect. If the script runs successfully, the REAPER console output window pops up a message like so:

ReaScript console output reads "Successfully connected to Wwise!"

Basic Example (Get Selected Objects)

In this next example, we look at querying the currently selected objects in Wwise.

-- Get Selected Objects.lua

if(reaper.AK_Waapi_Connect("127.0.0.1", 8080)) then
    local fieldsToReturn = reaper.AK_AkJson_Array()

    reaper.AK_AkJson_Array_Add(fieldsToReturn, reaper.AK_AkVariant_String("path"))

    local options = reaper.AK_AkJson_Map()
    reaper.AK_AkJson_Map_Set(options, "return", fieldsToReturn)

    local result = reaper.AK_Waapi_Call("ak.wwise.ui.getSelectedObjects",
      reaper.AK_AkJson_Map(), options)

    local status = reaper.AK_AkJson_GetStatus(result)

    if(status) then
      local objects = reaper.AK_AkJson_Map_Get(result, "objects")
      local numObjects = reaper.AK_AkJson_Array_Size(objects)

      for i=0, numObjects - 1 do
        local item = reaper.AK_AkJson_Array_Get(objects, i)
        local path = reaper.AK_AkJson_Map_Get(item, "path")
        local pathStr = reaper.AK_AkVariant_GetString(path)
        reaper.ShowConsoleMsg(pathStr .. "\n")
      end
    end

    reaper.AK_AkJson_ClearAll()
    reaper.AK_Waapi_Disconnect()
  end

AkJson

To facilitate the creation of JSON objects, ReaWwise exports various helper functions. These allow ReaScripts to dynamically create JSON objects based on the scripter's needs.

Calls to WAAPI require three elements: arguments, options, and a command string. For this example, the command string is ak.wwise.ui.getSelectedObjects. An exhaustive list of WAAPI commands can be found in the WAAPI Reference

For this specific command, WAAPI expects an empty map as the arguments. All we need to do then is set up the options map.

The options map we want to create will look like this:

{
    "return": [
        "path"
    ]
}

Due to the fact that only value types and pointers can be passed between REAPER’s Lua context and the ReaWwise back end, the options map needs to be created in multiple steps.

We start by creating the fieldsToReturn array:

local fieldsToReturn = reaper.AK_AkJson_Array()

What gets returned in the fieldsToReturn variable is a pointer to an array in the ReaWwise back end. With this pointer, we can operate on the array. 

We then add the "path" string as an element to the fieldsToReturn:

reaper.AK_AkJson_Array_Add(fieldsToReturn, reaper
    .AK_AkVariant_String("path"))

Finally, we create the options map and add the fieldsToReturn array as a value with key "return":

    local options = reaper.AK_AkJson_Map()
    reaper.AK_AkJson_Map_Set(options, "return", fieldsToReturn)

Call to WAAPI

Once we’ve created all the required parameters, we use them as inputs in the call to WAAPI:

local result = reaper.AK_Waapi_Call("ak.wwise.ui.getSelectedObjects",
    reaper.AK_AkJson_Map(), options)

We can then inspect the result to see if the call to WAAPI succeeded. Remember, complex data structures cannot be passed directly from the ReaWwise back end to the Lua context. The result variable is a pointer. 

We can query the status:

local status = reaper.AK_AkJson_GetStatus(result)

If the status is true, we can proceed with querying the actual data returned in the call to WAAPI:

local objects = reaper.AK_AkJson_Map_Get(result, "objects")
local numObjects = reaper.AK_AkJson_Array_Size(objects)

Then, we iterate over numObjects and extract the data we need:

for i=0, numObjects - 1 do
    ...
end

Each object is represented as a map. To access a specific attribute of the object, we use the helper functions offered by the ReaWwise back end. 

To get a single object from the objects array, we use the objects pointer and an index value: 

local item = reaper.AK_AkJson_Array_Get(objects, i)

The variable item is a pointer to a map object that represents a single object in the objects array. We can then extract the path variable:

local path = reaper.AK_AkJson_Map_Get(item, "path")

Map and array values are stored internally as variant objects. To extract the final value, we need to know what data the value represents. In our case, we know we should be getting a string. We use the following helper function to get a string from the variant data type:

local pathStr = reaper.AK_AkVariant_GetString(path)

The variable pathStr is a regular Lua string that could be used throughout the rest of the script. The script simply outputs pathStr to the REAPER console.

reaper.ShowConsoleMsg(pathStr .. "\n")

Since the ReaWwise back end needs to keep track of reference type objects used throughout the Lua code, it’s important to clear them when they are not needed anymore:

reaper.AK_AkJson_ClearAll()

Advanced Example (Import)

In the next example, we will do something a little more complex. We will be transferring audio files into Wwise from the REAPER render directory. Each audio file will be imported as a Sound SFX under the import destination that has been hardcoded in the script. The underlying WAAPI command that will be used is ak.wwise.core.audio.import.

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
-- Import audio files in render directory.lua --
 
if(reaper.AK_Waapi_Connect("127.0.0.1", 8080)) then
    local windows = string.find(reaper.GetOS(), "Win") ~= nil
    local separator = windows and '\\' or '/'
 
    local importDestination = "\\Actor-Mixer Hierarchy\\Default Work Unit"
 
    -- render directory --
    local state, projectPath = reaper.EnumProjects(-1)
 
    local directory = projectPath:sub(1,string.len(projectPath) -
        string.reverse(projectPath):find(separator))
 
    -- import command --
    local importCommand = "ak.wwise.core.audio.import"
 
    -- importOperation --
    local importOperation = reaper.AK_AkVariant_String("replaceExisting")
 
    -- default --
    local default = reaper.AK_AkJson_Map()
    local importLanguage = reaper.AK_AkVariant_String("SFX")
    reaper.AK_AkJson_Map_Set(default, "importLanguage", importLanguage)
 
    -- imports --
    local imports = reaper.AK_AkJson_Array()
 
    -- autoAddToSourceControl --
    local autoAddToSourceControl = reaper.AK_AkVariant_Bool(true)
 
    -- arguments --
    local arguments = reaper.AK_AkJson_Map()
    reaper.AK_AkJson_Map_Set(arguments, "importOperation", importOperation)
    reaper.AK_AkJson_Map_Set(arguments, "default", default)
    reaper.AK_AkJson_Map_Set(arguments, "imports", imports)
    reaper.AK_AkJson_Map_Set(arguments, "autoAddToSourceControl",
        autoAddToSourceControl)
 
    -- options --
    local options = reaper.AK_AkJson_Map()
 
    -- build import request --
    local currentFilePath = ""
    local fileIndex = 0;
 
    while(currentFilePath ~= nil) do
      currentFilePath = reaper.EnumerateFiles(directory, fileIndex)
 
      if currentFilePath ~= nil then
        local isWav = currentFilePath:find(".wav")
 
        if isWav then
          local importItem = reaper.AK_AkJson_Map()
          reaper.AK_AkJson_Map_Set(importItem, "audioFile",
            reaper.AK_AkVariant_String(directory .. separator .. currentFilePath))
 
          reaper.AK_AkJson_Map_Set(importItem, "objectPath",
            reaper.AK_AkVariant_String(importDestination .. "\\<Sound SFX>" ..
            currentFilePath))
 
          reaper.AK_AkJson_Map_Set(importItem, "originalsSubFolder",
            reaper.AK_AkVariant_String(""))
 
          reaper.AK_AkJson_Array_Add(imports, importItem)
        end
      end
 
      fileIndex = fileIndex + 1
    end
 
    local numFilesToImport = reaper.AK_AkJson_Array_Size(imports)
 
    if numFilesToImport > 0 then
      local result = reaper.AK_Waapi_Call(importCommand, arguments, options)
      local status = reaper.AK_AkJson_GetStatus(result)
 
      if status then
        reaper.ShowConsoleMsg("Successfully imported " .. numFilesToImport .. " audio
          files\n")
      else
        local errorMessage = reaper.AK_AkJson_Map_Get(result, "message")
        local errorMessageStr = reaper.AK_AkVariant_GetString(errorMessage)
 
        reaper.ShowConsoleMsg("Import failed: " .. errorMessageStr .. "\n")
 
        local details = reaper.AK_AkJson_Map_Get(result, "details")
        local log = reaper.AK_AkJson_Map_Get(details, "log")
        local logSize = reaper.AK_AkJson_Array_Size(log)
 
        for i=0, logSize - 1 do
          local logItem = reaper.AK_AkJson_Array_Get(log, i)
          local logItemMessage = reaper.AK_AkJson_Map_Get(logItem, "message")
          local logItemMessageStr = reaper.AK_AkVariant_GetString(logItemMessage)
          reaper.ShowConsoleMsg("["..i.."]" .. logItemMessageStr .. "\n")
        end
 
      end
    else
      reaper.ShowConsoleMsg("No audio files detected in render directory ...")
    end
 
  reaper.AK_AkJson_ClearAll()
  reaper.AK_Waapi_Disconnect()
end

For this example, I won’t explain what each line of code does since most of the concepts regarding the API were explained in the previous example. I will, however, give a general description of what each section of the code is doing.

Lines 3-13: We connect to WAAPI, set up the importDestination, and then deduce the render directory. In this script, we assume that the render directory is the same as the parent directory of the project file.

Lines 15-41: We build all the AkJson structures to be passed as inputs to the AK_Waapi_Call function. 

Lines 44-70: We iterate through the render directory and add any WAV file encountered to the list of audio files to be imported.

Lines 74-101: We execute the AK_WAAPI_Call function and display the results in the REAPER console. These lines also contain logic that extracts error information from the result in the case that an error is encountered.

Conclusion

In this article, we got to see several examples of WAAPI being used directly in a Lua ReaScript. From a basic connection to Wwise to querying Wwise, and then doing something quite complex such as importing audio files. We hope that this API will be beneficial to you and your workflows. We are excited to see how creative you get with WAAPI in your ReaScripts.

Andrew Costa

Software Developer, R&D

Audiokinetic

Andrew Costa

Software Developer, R&D

Audiokinetic

Andrew has been working as software developer building tools for content creators for the last 8 years. He is passionate about software and music production. He has been a developer at Audiokinetic since 2021, working as one of the main developers on ReaWwise.

Comments

Leave a Reply

Your email address will not be published.

More articles

Using Templates in Wwise

Like many tools in a game developer's toolbox, Wwise is a deep, complex program with documentation...

6.9.2016 - By Bradley Meyer

Image Source Approach to Dynamic Early Reflections

In our previous blog, Simulating dynamic and geometry-informed early reflections with Wwise Reflect...

14.11.2017 - By Thalie Keklikian

Out With the Old, in With the New: Positioning Revamped in Wwise 2018.1

One of the new features included in Wwise 2018.1.0 is the enhancement of the Positioning tab. The...

21.8.2018 - By Guillaume Renaud

WAAPI is for Everyone | Part 1: Overview

Hello. I’m Thomas Wang (also known as Xi Ye). I discovered WAAPI (Wwise Authoring API) in the second...

13.11.2020 - By Thomas Wang (汪洋)

Authoring Plug-ins for Wwise 2021.1 | Part 1: History and Goals

One of the least known characteristics of the Wwise ecosystem is its extensibility. Companies create...

5.2.2021 - By Michel Donais

Dialogue | Narration in Wwise and Unreal Engine

Dialogue with voiceovers is one of the staples of modern video games, allowing the player to not...

1.9.2021 - By Jake Gamelin

More articles

Using Templates in Wwise

Like many tools in a game developer's toolbox, Wwise is a deep, complex program with documentation...

Image Source Approach to Dynamic Early Reflections

In our previous blog, Simulating dynamic and geometry-informed early reflections with Wwise Reflect...

Out With the Old, in With the New: Positioning Revamped in Wwise 2018.1

One of the new features included in Wwise 2018.1.0 is the enhancement of the Positioning tab. The...