WAAPI in ReaScript (Lua) with ReaWwise

Programmation audio / Outils et conseils pour Wwise

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.

Commentaires

Laisser une réponse

Votre adresse électronique ne sera pas publiée.

Plus d'articles

Amélioration de l'intégration Wwise dans Unreal

L'introduction du workflow de gestion d’assets de type Event-Based Packaging (EBP) incluse dans...

29.6.2022 - Par Guillaume Renaud

Wwise 2022.1 Nouveautés

Wwise 2022.1 est en ligne et peut être installé à partir de l'Audiokinetic Launcher. Voici un résumé...

15.11.2022 - Par Audiokinetic

Travailler en équipe avec WAAPI et Python, et exemples pratiques

Dans cet article, j'aimerais décrire une approche de travail avec WAAPI un peu particulière,...

7.11.2023 - Par Eugene Cherny

Nouvelles concernant le cycle de mises à jour de Wwise | Lancement de Sim-Patchs et support de développement pour versions Preview d'Unreal Engine

Cet article a pour but de partager les changements que nous avons apportés à notre processus de...

29.5.2024 - Par Guillaume Renaud

Comment jouer des sons depuis Wwise dans Unreal Engine

Wwise possède de nombreuses fonctionnalités ; mais en début de développement, lorsque vous ne...

8.8.2024 - Par Hiroshi Goda

Comment utiliser AudioLink dans Unreal Engine

Introduction Dans cet article, nous nous concentrerons sur la fonctionnalité AudioLink d'Unreal...

16.8.2024 - Par Hiroshi Goda

Plus d'articles

Amélioration de l'intégration Wwise dans Unreal

L'introduction du workflow de gestion d’assets de type Event-Based Packaging (EBP) incluse dans...

Wwise 2022.1 Nouveautés

Wwise 2022.1 est en ligne et peut être installé à partir de l'Audiokinetic Launcher. Voici un résumé...

Travailler en équipe avec WAAPI et Python, et exemples pratiques

Dans cet article, j'aimerais décrire une approche de travail avec WAAPI un peu particulière,...