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

Audio Programmer, C++

Ubisoft

Andrew Costa

Audio Programmer, C++

Ubisoft

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 was a developer at Audiokinetic in 2021-2023, working as one of the main developers on ReaWwise.

Comments

Leave a Reply

Your email address will not be published.

More articles

The Witcher 3: Wild Hunt - Game Audio (part 1/2)

CD Projekt Red released The Witcher 3: Wild Hunt in May of 2015, and critics have been raving about...

29.11.2016 - By Audiokinetic

Keeping it Steady with Wwise Motion Source

Sweet vibration, remember your first time? Maybe you expected it because you bought a Nintendo 64...

23.10.2018 - By Maximilien Simard Poirier

Impacter and Unreal | Controlling the Impacter Plug-in Using Game Physics

Introduction Impacter is a new impact modeling plug-in prototype for Wwise, read this article for a...

26.3.2021 - By Sean Soraghan

A Guide for Choosing the Right Codec

Game audio has always had the need to compress audio files. The fact remains that there is still not...

5.10.2023 - By Mathieu Jean

New in Wwise Spatial Audio 2023.1 | Reverb Zones

An Intro To Reverb Zones Wwise 23.1 adds a new tool to Wwise Spatial Audio called Reverb Zones. A...

10.1.2024 - By Thomas Hansen

Realistic Haptics Effects Achieved With Wwise

Introduction My name is Natsuo Koda from Miraisens. I am the CEO of Miraisens, engaged in R&D...

3.4.2024 - By Natsuo Koda

More articles

The Witcher 3: Wild Hunt - Game Audio (part 1/2)

CD Projekt Red released The Witcher 3: Wild Hunt in May of 2015, and critics have been raving about...

Keeping it Steady with Wwise Motion Source

Sweet vibration, remember your first time? Maybe you expected it because you bought a Nintendo 64...

Impacter and Unreal | Controlling the Impacter Plug-in Using Game Physics

Introduction Impacter is a new impact modeling plug-in prototype for Wwise, read this article for a...