WAAPI in ReaScript (Lua) with ReaWwise

오디오 프로그래밍 / 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.

댓글

댓글 달기

이메일 주소는 공개되지 않습니다.

다른 글

Wwise / Unreal Engine 4 / Unity 3D를 사용해 발자국 소리의 표면 관리하기

사전 제작의 초기 단계에서 사운드 디자이너는 많은 시스템을 프로토타입화해야 하는데, 이를 도와줄 오디오 프로그래머가 없는 경우도 있죠. 다행히도 Wwise는 Unreal...

17.7.2019 - 작성자: 세바스티앙 겔라르 (Sébastien Gaillard)

Wwise에서 Audio Object 저작하기

미래... 미래란 항상 멀리 있고, 절대 지금으로 앞당길 수 없지만, 항상 손안에 잡힐 듯 말 듯 하지 않나요? 최신 뉴스를 꿰뚫고 있거나, 좋아하는 드라마를 바로바로 챙겨...

7.7.2021 - 작성자: 데미안 캐스트바우어 (Damian Kastbauer)

대사 | Wwise와 Unreal Engine에서의 나레이션

현대 게임의 필수 요소 중 하나인 보이스오버 대사는 플레이어가 캐릭터를 특정 목소리와 연관지을 수 있을 뿐만 아니라 전반적인 억양을 통해 캐릭터의 감정을 더 잘 이해할 수 있게...

11.4.2023 - 작성자: Jake Gamelin (제이크 겜린)

WAQL 2.0

Wwise Authoring Query Language (WAQL, 와클) 첫 번째 버전이 출시된 지 벌써 몇 년이 지났습니다. 첫 버전 이후 크게 변경된 점은 없습니다. 가장...

10.8.2023 - 작성자: 베르나르 로드리그 (Bernard Rodrigue)

Wwise 출시 주기 변경 | Sim-Patch 출시 및 언리얼 엔진 프리뷰 개발 지원

이 글에서는 지난 몇 달 동안 Audiokinetic의 개발 프로세스에 적용된 몇몇 변경 사항을 공유하려고 합니다. 이러한 변경 사항은 Wwise가 더 자주 제공되어 더 빨리 다음...

28.5.2024 - 작성자: 기욤 르노 (Guillaume Renaud)

Unreal Engine에서 AudioLink를 사용하는 방법

소개 이 글에서는 Unreal Engine의 AudioLink를 집중적으로 살펴보려고 합니다.이 작업은 다음 버전을 사용하여 검토되었습니다....

15.8.2024 - 작성자: 히로시 고다 (Hiroshi Goda)

다른 글

Wwise / Unreal Engine 4 / Unity 3D를 사용해 발자국 소리의 표면 관리하기

사전 제작의 초기 단계에서 사운드 디자이너는 많은 시스템을 프로토타입화해야 하는데, 이를 도와줄 오디오 프로그래머가 없는 경우도 있죠. 다행히도 Wwise는 Unreal...

Wwise에서 Audio Object 저작하기

미래... 미래란 항상 멀리 있고, 절대 지금으로 앞당길 수 없지만, 항상 손안에 잡힐 듯 말 듯 하지 않나요? 최신 뉴스를 꿰뚫고 있거나, 좋아하는 드라마를 바로바로 챙겨...

대사 | Wwise와 Unreal Engine에서의 나레이션

현대 게임의 필수 요소 중 하나인 보이스오버 대사는 플레이어가 캐릭터를 특정 목소리와 연관지을 수 있을 뿐만 아니라 전반적인 억양을 통해 캐릭터의 감정을 더 잘 이해할 수 있게...