ReaWwise를 사용한 ReaScript(Lua)에서의 WAAPI

오디오 프로그래밍 / Wwise에 대한 팁과 도구

ReaWwise에서 잘 알려지지 않은 기능 중 하나는 원시적 WAAPI 함수를 REAPER에 노출하여 사용자 정의 ReaScript에서 사용할 수 있다는 것입니다. 이 블로그 글에서는 ReaScript Development Environment를 통해 WAAPI를 사용하여 몇 가지 기본적인 Wwise 관련 기능을 구현하는 방법을 살펴보겠습니다

사전 요구 사항

WAAPI & Lua

시작하기 전에 먼저 WAAPI, Lua, ReaScript에 대한 기본적인 이해를 갖추는 것이 좋습니다. 다음은 이를 위한 몇몇 자료입니다.

ReaWwise

ReaScript 안에서 WAAPI 명령을 실행하려면 ReaWwise를 설치해야 합니다

시작하기

ReaScript Development Environment (ReaScript 개발 환경)

ReaScript Development Environment로 가기 위해서는 먼저 REAPER를 여세요. REAPER 메뉴에서 Actions > Show action list를 선택하세요. Actions 대화창의 오른쪽 아래 모서리에서 New action > New ReaScript를 클릭하세요. 여기서 파일을 저장하라는 메시지가 뜹니다. ReaScript 파일의 이름을 지정하고 Save를 클릭하세요. 저장 후 ReaScript Development Environment가 열립니다. 바로 여기에서 코드를 작성하게 되죠.

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에 성공적으로 연결되었습니다!)'라고 표시됩니다.

기본 예제 (Get Selected Objects)

다음 예제를 통해 Wwise에서 현재 선택된 오브젝트를 쿼리하는 방법을 살펴봅시다.

-- 선택한 오브젝트 가져오기.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 Reference에서 찾을 수 있습니다. 

이 특정 명령에서 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"))

마지막으로 옵션 맵을 생성하고 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 콘텍스트로 바로 전달할 수 없습니다. 결과 변수는 포인터입니다. 

상태를 쿼리할 수 있습니다.

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 백엔드가 제공하는 헬퍼 함수를 사용합니다. 

객체 배열에서 단일 객체를 얻기 위해서는 객체 포인터와 색인 값을 사용합니다. 

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)

pathStr 변수는 나머지 스크립트 부분에서 사용할 수 있는 일반 Lua 문자열입니다. 이 스크립트는 단순히 REAPER 콘솔에 pathStr을 출력합니다.

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
-- 렌더 디렉터리로 오디오 파일 가져오기.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)
 
    -- 인자 --
    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)
 
    -- 옵션 --
    local options = reaper.AK_AkJson_Map()
 
    -- 가져오기 요청 빌드하기 --
    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)

소프트웨어 엔지니어, R&D

앤드류 코스타 (Andrew Costa)

소프트웨어 엔지니어, R&D

앤드류는 지난 8년 동안 콘텐츠 제작자를 위한 소프트웨어 개발자로서 일해왔습니다. 그는 소프트웨어와 음악 제작에 대한 큰 열정을 가지고 있습니다. 2021년부터 Audiokinetic의 개발자가 된 앤드류는 ReaWwise의 주요 개발자 중 한 명으로 작업해왔습니다.

댓글

댓글 달기

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

다른 글

이미지 기반 파라미터를 이용한 오픈 월드 앰비언트 디자인

Blend Container는 강력한 시퀀싱 및 믹싱 도구가 될 수 있습니다. 단순히 그것의 기능을 배우는 것이 게임 사운드 디자이너의 생각에 온갖 종류의 새로운 아이디어를...

13.3.2020 - 작성자: 톰 토디아 (TOM TODIA)

Wwise State-based Mixing의 새로운 기능 - 온갖 파라미터들!

2017.1 이전에는 States 탭에서는 볼륨, 피치, 저역-통과 필터, 하이패스 필터 및 메이크업 게인과 같은 비교적 적은 수의 파라미터에만 액세스할 수...

9.6.2020 - 작성자: Bradley Meyer

Wwise를 사용하여 보다 복잡한 MIDI로 제어되는 샘플 악기만들기

MIDI 기반 음악으로 되돌아가는 데에 관심이 있는 사람들은, PS4의 내장 신디사이저를 이용할 수 없으며(존재하지 않음) PC 사운드카드의 GENERAL MIDI에 의존할 수도...

28.7.2020 - 작성자: 다니엘 벡 (DANIEL BECK)

ReaWwise 개발 | 제 2부 - 구현

이 글은 2부작으로 제작된 블로그 시리즈의 제 2부입니다. 제 1부에서는 ReaWwise의 사전 제작에 대해 알아보았고, 제 2부에서는 이 확장의 개발에 대해 알아보게 됩니다.거의...

3.11.2022 - 작성자: 앤드류 코스타 (Andrew Costa)

올바른 코덱 선택에 대한 안내

게임 오디오에서는 항상 오디오 파일을 압축해야 했습니다. 우리가 꿈꾸는 그대로의 오디오 환경을 모두 압축되지 않은 오디오 샘플로 두기에는 여전히 디스크 공간이나 메모리가 부족하다는...

14.5.2024 - 작성자: 마튜 장 (Mathieu Jean)

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

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

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

다른 글

이미지 기반 파라미터를 이용한 오픈 월드 앰비언트 디자인

Blend Container는 강력한 시퀀싱 및 믹싱 도구가 될 수 있습니다. 단순히 그것의 기능을 배우는 것이 게임 사운드 디자이너의 생각에 온갖 종류의 새로운 아이디어를...

Wwise State-based Mixing의 새로운 기능 - 온갖 파라미터들!

2017.1 이전에는 States 탭에서는 볼륨, 피치, 저역-통과 필터, 하이패스 필터 및 메이크업 게인과 같은 비교적 적은 수의 파라미터에만 액세스할 수...

Wwise를 사용하여 보다 복잡한 MIDI로 제어되는 샘플 악기만들기

MIDI 기반 음악으로 되돌아가는 데에 관심이 있는 사람들은, PS4의 내장 신디사이저를 이용할 수 없으며(존재하지 않음) PC 사운드카드의 GENERAL MIDI에 의존할 수도...