ReaWwiseを使用してWAAPIをReaScript(Lua)で実行する

オーディオプログラミング / Wwiseの使い方やツール

あまり知られていない機能ですが、ReaWwiseはカスタムReaScriptsで使える生のWAAPI関数をREAPERに公開します。このブログ記事ではReaScript開発環境の中でWAAPIを使い、基本的なWwise関連の機能を実装する方法について考えます。

前提条件

WAAPIとLua

先にすすむ前に、WAAPI、Lua、ReaScriptの基本を理解しておくことが大切です。以下は勉強するためにおすすめのリソースです:

ReaWwise

WAAPIコマンドをReaScript内から実行するためには、 ReaWwiseをインストールする 必要があります。 

はじめてみよう

ReaScript開発環境

ReaScript開発環境を使うためにはREAPERを開きます。REAPERメニューで Actions > Show action list を選択します。Actions ダイアログ右下の New action > New ReaScript をクリックします。この時点でファイルの保存を促すプロンプトが表示されます。ReaScriptファイルに名前を付け、 Save をクリックします。保存した後にReaScript開発環境が開きます。ここにコードを書いてゆきましょう。

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

上記のコードスニペットでは、最初に AK_Waapi_Connect を呼び出しています。この関数がWAAPIとWwiseがやりとりをする IPPORT を取り込みます。関数がブール値を返します。接続に成功した場合はtrueを、成功しなかった場合はfalseを返します。if文の終わりで AK_Waapi_Disconnect を呼び出して接続を終了させます。スクリプトの実行がうまくいくとREAPERコンソールの出力ウィンドウに以下のようなメッセージが出ます:

ReaScriptコンソール出力に"Successfully connected to Wwise!"(Wwiseへの接続に成功!)と表示されます。

基本的な例(選択したオブジェクトの取得)

次の例は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を呼び出すためには 引数オプションコマンド文字列 の3つの要素が必要です。この例のコマンドストリングは ak.wwise.ui.getSelectedObjects です。WAAPIリファレンス にすべてのWAAPIコマンドの一覧があります。

WAAPIはこの特定コマンド用の引数として、空のマップを想定しています。私たちはオプションズマップを設定するだけです。

私たちが作成しようとしているオプションズマップは以下のようなものです:

{
    "return": [
        "path"
    ]
}

REAPERのLuaコンテキストとReaWwiseのバックエンドの間で渡すことができるのは、値タイプとポインタに限られているため、オプションズマップの作成はいくつかのステップに分かれます。

最初に、以下のように fieldsToReturn 配列を作成します。

local fieldsToReturn = reaper.AK_AkJson_Array()

fieldsToReturn 変数の中で返されるのが、ReaWwiseバックエンドの配列へのポインタです。このポインタを使い配列に対して操作を行うことができます。

次に、以下のように"path"文字列をfieldsToReturnの要素として追加します:

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

最後に、以下のようにオプションズマップを作成し、キー"return"を持つ値としてfieldsToReturn配列を追加します:

    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コンテキストに直接渡すことができないことを、忘れないでください。結果の変数がポインタです。

以下のようにステータスをクエリできます:

local status = reaper.AK_AkJson_GetStatus(result)

ステータスが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バックエンドが提供するヘルパー関数を使います。

オブジェクト配列から1つのオブジェクトを取得するためには、以下のようにオブジェクトポインタとインデックス値を使用します: 

local item = reaper.AK_AkJson_Array_Get(objects, i)

変数 itemobjects 配列にある1つのオブジェクトを表すマップオブジェクトへのポインタです。以下のように path 変数を抽出します:

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

マップや配列値はvariantオブジェクトとして内部に保存されます。最終値を抽出するためには、値が何のデータを表しているのかを知る必要があります。今回は文字列を取得できることが分かっています。以下のヘルパー関数を使いvariantデータタイプから文字列を取得します:

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に転送します。各オーディオファイルを、スクリプトでハードコードしたインポート先にサウンド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"
 
    -- 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

この例のAPIに関連するコンセプトの大半は最初の例で説明済みのため、コードを1行ずつ説明することはしません。その代わりにコードの各セクションで何を行っているのかを大まかに説明します。
行3-13: WAAPIに接続し、importDestinationを設定し、レンダーディレクトリを判断します。今回のスクリプトではレンダーディレクトリがプロジェクトファイルの親ディレクトリと同じであることを前提としています。

行15-41: AK_Waapi_Call関数に入力として渡すAkJson構造をすべて構築します。

行44-70:レンダーディレクトリのイテレーションを進め、WAVファイルに遭遇した場合は、インポートするオーディオファイル一覧にそれを追加します。

行74-101: AK_WAAPI_Call関数を実行してREAPERコンソールに結果を表示します。エラーが発生した場合に結果からエラー情報を抽出するロジックも、これらの行に含まれています。

まとめ

この記事ではWAAPIをLua ReaScriptで直接使う例をいくつか見てきました。Wwiseへの基本的な接続から、Wwiseのクエリ、そしてオーディオファイルのインポートというかなり複雑な処理に至るまでをご紹介しました。私たちはこのAPIがみなさんのワークフローにとって有意義となることを願っています。みなさんがWAAPIをReaScriptsでどのように使いこなすのか、見るのを楽しみにしています。

アンドリュー・コスタ

ソフトウェアデベロッパ、R&D

Audiokinetic

アンドリュー・コスタ

ソフトウェアデベロッパ、R&D

Audiokinetic

8年前からソフトウェアデベロッパとしてコンテンツクリエイターのためのツールを開発。ソフトウェアと音楽制作に情熱を注いでいる。2021年よりReaWwiseの主要デベロッパのうちの1人として、Audiokineticで開発に携わっている。

コメント

Replyを残す

メールアドレスが公開されることはありません。

ほかの記事

コマンドアドオンで実現できる、ワークフローの改善

継続的なワークフロー改善の努力を...

20.8.2019 - 作者 ベルナール・ロドリグ(Bernard Rodrigue)

Wwise 2021.1向けAuthoringプラグイン | パート 1: 経緯と目標

14.4.2021 - 作者 ミシェル・ドネイ(MICHEL DONAIS)

ダイアログ|WwiseとUnityのナレーション

9.2.2022 - 作者 ジェイク・ガムリン(Jake Gamelin)

ReaWwise:REAPERとWwiseをつなげる新しい拡張機能

はじめに...

27.9.2022 - 作者 アンドリュー・コスタ

WAQL 2.0

WAQL(Wwise Authoring Query...

22.8.2023 - 作者 ベルナール・ロドリグ(Bernard Rodrigue)

コーデックの選択ガイド

13.3.2024 - 作者 マチュー・ジャン(MATHIEU JEAN)