menu
 

通过 ReaWwise 在 ReaScript (Lua) 中调用 WAAPI

音频编程 / Wwise 技巧和工具

ReaWwise 有个大家可能不太知道的功能,就是将原始 WAAPI 函数暴露给 REAPER。藉此,用户可在自己的 ReaScript 中使用这些函数。在本文中,我们将探讨如何使用 WAAPI 在 ReaScript Development Environment 中轻松实现一些与 Wwise 相关的基本功能。

前提要求

WAAPI 和 Lua

在阅读接下来的章节之前,建议先对 WAAPI、Lua 和 ReaScript 做一个基本的了解。这里有一些学习资源,感兴趣的话不妨看下:

ReaWwise

要在 ReaScript 内运行 WAAPI 命令,必须安装 ReaWwise。 

快速入门

The ReaScript Development Environment

要进入 ReaScript Development Environment,请先打开 REAPER。然后,在 REAPER 菜单中依次选择 Actions > Show action list...。接着,在 Actions 对话框右下角依次单击 New action... > New ReaScript...。这时会提示保存文件。对 ReaScript 文件进行命名,然后单击 Save。在保存之后,便会打开 ReaScript Development Environment。我们将在此编写代码。

Hello World

-- 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

在上面的代码段中,我们首先调用 AK_Waapi_Connect。此函数会获取 WAAPI 与 Wwise 通信所用的 IPPORT。它会返回一个布尔值:若连接成功,则返回 true;否则,返回 false。在 if 语句末尾,确保通过调用 AK_Waapi_Disconnect 来终止连接。若脚本运行成功,REAPER console output 窗口会弹出以下消息:

ReaScript console output 显示 "Successfully connected to Wwise!"

基本示例 (Get Selected Objects)

在以下示例中,我们来看看如何在 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

为了方便创建 JSON 对象,ReaWwise 导出了各种辅助函数。藉此,ReaScript 可根据脚本编写者的需求动态创建 JSON 对象。

调用 WAAPI 需要三个元素:参数选项命令字符串。在本例中,命名字符串为 ak.wwise.ui.getSelectedObjects。有关 WAAPI 命令的详尽列表,请参阅 WAAPI 参考

对于这个命令,WAAPI 会将空映射作为参数。在此,我们只需设置 options 映射即可。

我们要创建的 options 映射就跟下面一样:

{
    "return": [
        "path"
    ]
}

由于 REAPER 的 Lua 上下文和 ReaWwise 后端之间只能传递值类型和指针,所以需要分多个步骤来创建 options 映射。

首先,创建 fieldsToReturn 数组:

local fieldsToReturn = reaper.AK_AkJson_Array()

fieldsToReturn 变量会返回指向 ReaWwise 后端中数组的指针。利用该指针,我们可以对数组进行操作。

然后,将 path 字符串作为元素添加到 fieldsToReturn

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

最后,创建 options 映射并将 fieldsToReturn 数组添加为键值为 return 的值:

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

调用 WAAPI

在创建全部所需参数后,将其用作调用 WAAPI 时的输入:

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

然后,便可察看结果以确认对 WAAPI 的调用是否成功。注意,复杂的数据结构无法直接从 ReaWwise 后端传递到 Lua 上下文。在这里,结果变量是一个指针。

我们可以查询 status:

local status = reaper.AK_AkJson_GetStatus(result)

若 status 为 true,就可继续查询调用 WAAPI 时返回的实际数据:

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

然后,遍历 numObjects 并提取所需的数据:

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

每个对象都表示为映射。要访问对象的特定属性,可使用 ReaWwise 后端提供的辅助函数。

要从 objects 数组获取单个对象,需要使用 objects 指针和索引值: 

local item = reaper.AK_AkJson_Array_Get(objects, i)

变量 item 是指向映射对象的指针。该对象代表 objects 数组中的单个对象。然后,便可提取 path 变量:

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

映射和数组值在内部存储为变体对象。要提取最终值,得知道该值代表什么数据。在本例中,我们知道应该获取字符串。为此,使用以下辅助函数从变体数据类型获取字符串:

local pathStr = reaper.AK_AkVariant_GetString(path)

变量 pathStr 是个常规 Lua 字符串,在后续的脚本代码中都可以使用。脚本会直接将 pathStr 输出到 REAPER 控制台。

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

由于 ReaWwise 后端需要追踪整个 Lua 代码中使用的引用类型对象,所以在不再需要这些对象时必须予以清除:

reaper.AK_AkJson_ClearAll()

Advanced Example (Import)

在下一示例中,我们会做一些更复杂的操作。比如,把音频文件从 REAPER 渲染目录传输到 Wwise 中。在此,每个音频文件都将作为 Sound SFX 导入到已在脚本中硬编码的导入目标下。此处要使用的底层 WAAPI 命令为 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"
 
    -- 渲染目录 --
    local state, projectPath = reaper.EnumProjects(-1)
 
    local directory = projectPath:sub(1,string.len(projectPath) -
        string.reverse(projectPath):find(separator))
 
    -- import 命令 --
    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()
 
    -- 构建 import 请求 --
    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

对于这个示例,我不会详细阐释每行代码的作用,因为有关 API 的大部分概念已经在上一示例中说过了。不过,我会大致介绍一下各段代码的作用。

第 3-13 行:连接到 WAAPI,设置 importDestination,然后得出渲染目录。在此脚本中,我们假定渲染目录与工程文件的父目录相同。

第 15-41 行:构建所有要作为输入传给 AK_Waapi_Call 函数的 AkJson 结构。

第 44-70 行:遍历渲染目录并将遇到的所有 WAV 文件添加到要导入的音频文件列表。

第 74-101 行:执行 AK_WAAPI_Call 函数并在 REAPER 控制台中显示结果。这些代码行还包含在遇到错误时从结果中提取错误信息的逻辑。

结语

在本文中,我们查看了几个直接在 Lua ReaScript 中使用 WAAPI 的示例。从与 Wwise 的基本连接到查询 Wwise,再到导入音频文件等较为复杂的操作。希望此 API 能在工作流程当中给大家带来帮助。同时,期待看到各位在 ReaScript 中灵活运用 WAAPI。

安德鲁·科斯塔 (Andrew Costa)

软件研发

安德鲁·科斯塔 (Andrew Costa)

软件研发

在过去的8年中,Andrew一直作为软件开发人员为内容创作者建立工具。他对软件和音乐制作充满热情。他2021年加入Audiokinetic的研发团队,主要从事ReaWwise的开发工作。

评论

留下回复

您的电子邮件地址将不会被公布。

更多文章

SoundSeed Grain 的开发历程

很长时间以来,由于粒子合成器在声音设计上的广泛应用,我们一直都想为 Wwise 开发一款通用的粒子合成器插件。这样就可以利用它生成各种各样的声音,来为游戏设计提供丰富灵活的支持。 我记得几年前曾在...

12.8.2019 - 作者:路易斯-扎维尔·布法尼 (Louis-Xavier Buffoni)

人人都能用 WAAPI(一)概述

大家好,我是溪夜。 去年下半年我接触到了 WAAPI(Wwise Authoring API),作为头脑不怎么灵光的非专业程序员,看到 WAMP、JSON...

29.9.2020 - 作者:汪洋

Wwise Audio Lab (WAL) 配套更新

Wwise Audio Lab (WAL) 是一个采用 Unreal Engine 4 开发的类似游戏的 3D 开源环境,其可通过 Wwise Launcher 进行下载。在 WAL 中,用户可对...

27.4.2022 - 作者:戴米安·卡斯特鲍尔(Damian Kastbauer)

关于如何在团队工作环境中使用 WAAPI 和 Python

在本文中,我想说说自己很长一段时间以来是如何使用 WAAPI 的。这当中用到了 Python、命令扩展 (Command Add-on) 和一个小的辅助程序 (Helper) 库。藉此,能以比...

11.5.2022 - 作者:尤金•乔尔内 (Eugene Cherny)

Wwise 开发流程改进 | 针对 Unreal Engine Preview 的 Sim-Patch 发布和开发支持

这篇博文旨在跟各位分享我们在过去几个月里对开发流程所做的一些改进。这些改进是根据 Wwise 社区用户的反馈做出的,目的是更频繁地发布 Wwise 以缩短获取下一小版本的间隔。除此之外,我们还改变了对...

28.5.2024 - 作者:纪尧姆·雷诺 (GUILLAUME RENAUD)

Wwise 新手经常遇到的 10 个问题

12.10.2024 - 作者:麦斯·麦雷蒂·桑德鲁普 (Mads Maretty Sønderup)

更多文章

SoundSeed Grain 的开发历程

很长时间以来,由于粒子合成器在声音设计上的广泛应用,我们一直都想为 Wwise 开发一款通用的粒子合成器插件。这样就可以利用它生成各种各样的声音,来为游戏设计提供丰富灵活的支持。 我记得几年前曾在...

人人都能用 WAAPI(一)概述

大家好,我是溪夜。 去年下半年我接触到了 WAAPI(Wwise Authoring API),作为头脑不怎么灵光的非专业程序员,看到 WAMP、JSON...

Wwise Audio Lab (WAL) 配套更新

Wwise Audio Lab (WAL) 是一个采用 Unreal Engine 4 开发的类似游戏的 3D 开源环境,其可通过 Wwise Launcher 进行下载。在 WAL 中,用户可对...