最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

關(guān)于如何在團(tuán)隊(duì)工作環(huán)境中使用 WAAPI 和 Python

2022-05-11 11:10 作者:Wwise官方  | 我要投稿

在本文中,我想說說自己很長一段時(shí)間以來是如何使用 WAAPI 的。這當(dāng)中用到了 Python、命令擴(kuò)展 (Command Add-on) 和一個(gè)小的輔助程序 (Helper) 庫。藉此,能以比?waapi-client?更快的速度編寫新的 WAAPI 腳本,而且大部分情況下跟其他團(tuán)隊(duì)成員共用都不會(huì)沖突。除此之外,我還會(huì)講到 WAAPI 的基本工作原理,并借此展示一些實(shí)用的 WAAPI 腳本。所以,這篇博文對(duì)剛開始學(xué)習(xí) WAAPI 的新手也不無裨益。對(duì)于從旁協(xié)助音頻團(tuán)隊(duì)構(gòu)建自動(dòng)化工具的人,不妨將本文看作類似“入門”一樣的指南。

注意,文中代碼示例僅為闡明本人觀點(diǎn),其中的代碼并未經(jīng)過廣泛的測(cè)試。另外,各位需要設(shè)置 PC 環(huán)境才能運(yùn)行這些代碼(詳請(qǐng)參閱附錄部分的說明)。

基本概念

首先,我覺得有必要對(duì) Wwise 和 WAAPI 有個(gè)基本的了解。

Wwise 工程被分成了不同的對(duì)象層級(jí)結(jié)構(gòu),而對(duì)象又包含很多種類型(如 Event、RandomSequenceContainer 等)。對(duì)象類型決定了對(duì)象具有怎樣的屬性(如 BusVolume、IsStreamingEnabled 等)。在層級(jí)結(jié)構(gòu)中,子對(duì)象默認(rèn)沿用其父對(duì)象的屬性值。所有這些決定了音頻引擎在運(yùn)行時(shí)的聲音播放規(guī)則和配置。

WAAPI 是一種客戶端-服務(wù)器 API,可用于操控前面所說的層級(jí)結(jié)構(gòu)。比如,重命名/移除對(duì)象、更改對(duì)象屬性等等。不過,并非所有參數(shù)都能通過 WAAPI 來操控,比如 RTPC(至少在筆者撰寫本文時(shí)還做不到)。所以,有必要了解 Wwise 工程的組織結(jié)構(gòu),弄明白不同類型的對(duì)象及其屬性等。為此,我特地將以下參考頁面做成了書簽以便各位快速查閱:

  • Wwise 對(duì)象參考(https://www.audiokinetic.com/library/edge/?source=SDK&id=wobjects_index.html)?– 所有對(duì)象類型及其屬性

  • Wwise Authoring API 參考 – 函數(shù)?(https://www.audiokinetic.com/library/edge/?source=SDK&id=waapi_functions_index.html)– 可以查詢或修改的內(nèi)容

  • Wwise Authoring API 參考 – 主題?–(https://www.audiokinetic.com/library/edge/?source=SDK&id=waapi_topics_index.html) Wwise 設(shè)計(jì)工具可向用戶發(fā)送的通知

除此之外,WAAPI 還可提供對(duì)象屬性中沒有列出的對(duì)象信息。比如,屬性 sound:convertedWemFilePath、sound:originalWavFilePath 和 maxDurationSource(提供計(jì)算得出的?AudioSource?對(duì)象的 TrimEnd 和 TrimBegin 屬性的差值)。它們并未在“Wwise 對(duì)象參考”中作為對(duì)象屬性列出,但 WAAPI 照樣可對(duì)其進(jìn)行查詢(可能 WAAPI 對(duì)工程數(shù)據(jù)有特殊訪問權(quán)限)。

對(duì)于高級(jí)用戶,%WWISEROOT%\Authoring\Data\Schemas?下設(shè)有用于 WAAPI 和 Wwise 設(shè)計(jì)工具數(shù)據(jù)(如 Work Unit)的 JSON 和 XML 架構(gòu)。在有些情況下,用起來可能會(huì)很方便。

注意事項(xiàng)

WAAPI 腳本并非寫得越長越復(fù)雜越好,尤其是在自動(dòng)處理耗時(shí)費(fèi)力的任務(wù)時(shí)。除了腳本本身,還要注意以下事項(xiàng):

  • 如何執(zhí)行腳本。

  • 如何在團(tuán)隊(duì)成員之間進(jìn)行分發(fā)。

為此,我們使用了自定義版本的 Total Commander。其存儲(chǔ)在 SVN 下,并包含大量便捷的實(shí)用工具,可以滿足各種技術(shù)音頻需求。這些工具可通過應(yīng)用程序頂部的菜單欄/子菜單欄訪問。另外,它還包含內(nèi)嵌的 Python 分發(fā)包(附有由我維護(hù)的數(shù)據(jù)包),包括工程特定腳本和 WAAPI 腳本。這樣的話,團(tuán)隊(duì)成員無需配置自己的電腦就可運(yùn)行所述工具。而且,我也可以輕松地更新整個(gè)環(huán)境并與大家同步。不得不說,Total Commander 真的太強(qiáng)悍了!

有的時(shí)候,我也會(huì)將所有 Python 腳本直接放到 Wwise 工程的 Scripts 目錄下。當(dāng)然,這會(huì)將腳本的適用范圍限定為特定的工程。

除了 Total Commander 中的按鈕,我還喜歡通過命令擴(kuò)展來調(diào)用 WAAPI 腳本。藉此在設(shè)計(jì)工具中定義相應(yīng)命令,并用工程特定參數(shù)(比如選定對(duì)象的 GUID、Wwise 工程的路徑等)來隨意地運(yùn)行外部工具。這些命令可以集成到設(shè)計(jì)工具的菜單中,非常方便使用自定義的 Wwise 相關(guān)工具。

流程概述

為了避繁就簡(jiǎn),本文中的 WAAPI 腳本全部通過命令擴(kuò)展執(zhí)行,并且 Python 文件本身將放到 Scripts 目錄下。為此,用戶需要根據(jù)情況配置自己的 PC 環(huán)境(詳請(qǐng)參閱附錄部分的說明)。

另外,我還希望將所有錯(cuò)誤顯示在 GUI 中。根據(jù)我的經(jīng)驗(yàn),這樣就可直接處理聲音設(shè)計(jì)師遇到的問題,而不必讓其在控制臺(tái)窗口中自己費(fèi)力查找。

Python 工程結(jié)構(gòu)

以下截圖展示了 Scripts 文件夾的結(jié)構(gòu):

幾個(gè)要點(diǎn):

  • 根目錄只包含由 Wwise 通過命令擴(kuò)展調(diào)用的腳本,所以其不可由其他 Python 文件導(dǎo)入。對(duì)此,只有以下兩個(gè)文件除外:

    • __init__.py?– 該文件將 Scripts 文件夾標(biāo)記為模塊。這樣方便我們?cè)诋?dāng)前工作目錄被設(shè)為 Wwise 工程文件夾時(shí)使用來自腳本的相對(duì)導(dǎo)入;

    • _template.py?– 該模板文件用于制作新的腳本;其包含樣板,用于根據(jù)自身需要進(jìn)行復(fù)制、重命名和修改。

  • 子模塊包含在腳本之間重復(fù)使用的代碼或函數(shù)。

  • 在腳本執(zhí)行時(shí)會(huì)將其路徑作為參數(shù)傳給 Python 解釋器。我原本想按照模塊名稱運(yùn)行腳本(標(biāo)記?-m),但后來發(fā)現(xiàn)在將當(dāng)前工作目錄設(shè)為?${WwiseProjectRoot}?時(shí)無法運(yùn)行命令擴(kuò)展,所以暫時(shí)沒有使用相對(duì)導(dǎo)入?1。

  • 按照約定,在腳本遇到錯(cuò)誤或檢測(cè)到數(shù)據(jù)處于無效狀態(tài)時(shí)會(huì)拋出?RuntimeError?異常。此類異常在最外層框架中捕獲,相關(guān)消息通過對(duì)話框窗口顯示。

下面列出了 Python 模板代碼。這段代碼可能看起來挺長,其實(shí)是為了實(shí)現(xiàn)多項(xiàng)操作:處理錯(cuò)誤并顯示在 GUI 中,同時(shí)提供普通導(dǎo)入。這樣使用起來會(huì)很方便。在編寫新的腳本時(shí),直接復(fù)制粘貼模板即可。

# 藉此避免導(dǎo)入此文件
if?__name__?!=?'__main__':
? ??print(f'error:?{__file__}?should not be imported, aborting script')
? ??exit(1)

# tkinter 用于顯示警告對(duì)話框和其他一些簡(jiǎn)單的 UI 內(nèi)容
import?tkinter
from?tkinter.messagebox?import?showinfo, showerror
from?waapi?import?WaapiClient, CannotConnectToWaapiException
from?waapi_helpers?import?*
from?helpers?import?*

# 可根據(jù)需要?jiǎng)h除腳本沒有使用的導(dǎo)入或添加新的導(dǎo)入

# 初始化 Tk 小組件并避免在任務(wù)欄中顯示圖標(biāo)
tk = tkinter.Tk()
tk.withdraw()

# 此 try-except 代碼塊旨在捕獲運(yùn)行時(shí)錯(cuò)誤,
# 并確保在 GUI 窗口而非控制臺(tái)中顯示給用戶
try:
? ??with?WaapiClient()?as?client:
# 我們的腳本從此處開始
? ? ? ??pass

except?CannotConnectToWaapiException:
# 在無法連接到運(yùn)行中的 Wwise 應(yīng)用程序時(shí)輸出用戶友好消息
? ? showerror('Error',?'Could not establish the WAAPI connection. Is the Wwise Authoring Tool running?')
except?RuntimeError?as?e:
# 直接由腳本拋出的預(yù)期錯(cuò)誤
? ? showerror('Error',?f'{e}')
except?Exception?as?e:
# 異常錯(cuò)誤總是列明堆棧跟蹤信息
? ??import?traceback

? ? showerror('Error',?f'{e}\n\n{traceback.format_exc()}')
finally:
# 需要調(diào)用此代碼才能停止 Tk 窗口事件循環(huán),
# 否則此時(shí)將停止運(yùn)行腳本
? ? tk.destroy()

配置命令擴(kuò)展

關(guān)于命令擴(kuò)展配置其實(shí)沒什么好說的,因?yàn)榇肆鞒桃言诠俜轿臋n中詳細(xì)說明。在此,我將使用?Wwise Project/Add-ons/Commands/ak_blog_addons.json?位置存儲(chǔ)的單個(gè) JSON 文件來保存所有示例。各位可自行下載隨附的存檔來查看此文件(參見附錄)。

注意,在修改 JSON 文件后,需要在 Wwise 設(shè)計(jì)工具中重新加載命令擴(kuò)展。這里并沒有用來執(zhí)行此操作的熱鍵。不過,可通過在“搜索”字段中依次鍵入右尖括號(hào)和 command 來進(jìn)行搜索并加以執(zhí)行2:

調(diào)試 WAAPI 腳本

Logs 窗口中設(shè)有 WAAPI 選項(xiàng)卡,不過默認(rèn)情況下并不會(huì)記錄所有內(nèi)容。各位可根據(jù)需要在 Logs Settings 中啟用附加日志記錄。另外,命令擴(kuò)展 JSON 中的 redirectOutputs 設(shè)置會(huì)強(qiáng)制 Wwise 將 Python 腳本的控制臺(tái)輸出重定向到 General 選項(xiàng)卡;默認(rèn)處于禁用狀態(tài)。

關(guān)于 waapi_helpers3

我編寫了一個(gè)小的?waapi_helpers?庫,并將其用到了本文的示例中。該庫由一些小的無狀態(tài)輔助程序組成。這些輔助程序接受將 WaapiClient 作為參數(shù),因而可與?waapi-client?代碼混合使用。所有函數(shù)均遵循有關(guān)“獲取屬性”的約定。比如,如有屬性不存在,則值應(yīng)當(dāng)為 None。簡(jiǎn)單明了。在這里我暫不細(xì)作探討,具體請(qǐng)查看下面的示例。

示例

這里展示的大多數(shù)例子甚至我自己的大部分 WAAPI 腳本都采用了大致相同的構(gòu)架:首先遍歷 Wwise 工程并收集信息,然后對(duì)信息進(jìn)行處理或轉(zhuǎn)換,最后將更改應(yīng)用于 Wwise 工程。

例 1:將選定對(duì)象的 GUID 復(fù)制到剪貼板

Wwise 中有個(gè)非常實(shí)用的命令,不需要通過 WAAPI 來實(shí)現(xiàn)。因?yàn)槭趾?jiǎn)單,所以我就完整列出了對(duì)應(yīng)的命令擴(kuò)展 JSON 及 Python 源碼。

copy_guid.py(其實(shí)沒什么特別的,模板的大部分內(nèi)容都被剝離了):

if?__name__?!=?'__main__':
? ??print(f'error:?{__file__}?should not be imported, aborting script')
? ??exit(1)

# 此為第三方 lib
import?pyperclip ?
# 這個(gè)簡(jiǎn)單的函數(shù)以列表形式在 argv 中返回參數(shù)
# 注意此處使用了相對(duì)導(dǎo)入,因?yàn)橛袀€(gè) 'helpers' 子模塊
from?helpers?import?get_selected_guids_list

guids = get_selected_guids_list()
pyperclip.copy(' '.join(guids))

JSON:

{
? ??"version":?2,
? ??"commands": [
? ? ? ? {
? ? ? ? ? ??"id":?"waapi_article.copy_guid",
? ? ? ? ? ??"displayName":?"Copy GUID",
? ? ? ? ? ??"program":?"python",
? ? ? ? ? ??"startMode":?"MultipleSelectionSingleProcessSpaceSeparated",
? ? ? ? ? ??"args":?"\"${WwiseProjectRoot}/Scripts/copy_guid.py\"?${id}",
? ? ? ? ? ??"redirectOutputs":?false,
? ? ? ? ? ??"contextMenu": {
? ? ? ? ? ? ? ??"basePath":?"WAAPI"
? ? ? ? ? ? }
? ? ? ? }
? ? ]
}

  • id?– 此新命令的唯一標(biāo)識(shí)符。

  • displayName?– 要針對(duì)此命令在菜單中顯示的用戶可讀名稱。

  • program?– 要在用戶執(zhí)行此命令時(shí)運(yùn)行的程序;注意,腳本路徑用雙引號(hào)進(jìn)行了轉(zhuǎn)義。這樣的話,Wwise 會(huì)將此路徑視為單個(gè)參數(shù),而不是把其拆分開來。

  • startMode?– Wwise 將調(diào)用上述程序一次,并傳遞采用空格分隔的參數(shù)(本例中為對(duì)象 GUID)。

  • args?– 傳給 Python 的參數(shù):

    • ${WwiseProjectRoot}/Scripts/copy_guid.py?為腳本路徑,前后帶有用來轉(zhuǎn)義的雙引號(hào),為的是在路徑包含空格時(shí)將其視為單個(gè)參數(shù);

    • ${id}?為專用參數(shù),將由 Wwise 替換為選定對(duì)象的 GUID。

  • redirectOutputs?– 用于將 stdout 重定向到 Wwise 中的 Logs 窗口以便調(diào)試腳本;默認(rèn)處于禁用狀態(tài)。

  • contextMenu?– 配置為針對(duì) Wwise 工程層級(jí)結(jié)構(gòu)下的所有對(duì)象在上下文菜單中顯示此命令。在本例中,將生成一個(gè)名為 WAAPI 的子組。

在刷新命令擴(kuò)展后,上下文菜單中將顯示如下條目:

通過單擊該條目,可將以下文本復(fù)制到系統(tǒng)剪貼板:

{2E9E3B71-C905-4BB0-9B30-06CFF26E0C5E} {3AD5C9DF-C0B5-4A78-B87A-2EE37D64BFCB} {8F7A715D-5704-4F09-9563-4172E250419B}

例 2:顯示所有 Event 的名稱

這段代碼并不實(shí)用,只是為了展示如何使用?walk_wproj?函數(shù)來遍歷層級(jí)結(jié)構(gòu)(樣板省略掉了)。

show_event_names.py:

events = []
with?WaapiClient()?as?client:
? ??for?guid, name?in?walk_wproj(client,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?start_guids_or_paths='\\Events',
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?properties=['id',?'name'],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?types=['Event']):
? ? ? ? events.append(name)
showinfo('Hi tutorial!',?'\n'.join(events))

在此,walk_project?函數(shù)將從路徑?\Events?開始順著層級(jí)結(jié)構(gòu)遍歷每個(gè)對(duì)象,并生成其查到的每個(gè) Event 對(duì)象的 id 和 name 屬性。此函數(shù)將啟動(dòng)我編寫的庫,以便使用基于 Pythonic 迭代器的簡(jiǎn)單接口遍歷 Wwise 層級(jí)結(jié)構(gòu)(跟 XML 庫提供的迭代器類似)。除此之外,我還想避免創(chuàng)建和解壓 JSON 對(duì)象。因?yàn)槠浼軜?gòu)在不同命令之間有差異,這樣會(huì)很難一直保存在工作內(nèi)存中。

Event 名稱將被匯集成數(shù)組,并在完成迭代后一并列出。代碼本身并不實(shí)用,只是為了演示一下。結(jié)果大致如下:

例 3:重置音量推子

有的時(shí)候,比如在混音過程中,我希望將層級(jí)結(jié)構(gòu)特定部分的所有推子設(shè)為零。有以下腳本將針對(duì)選定對(duì)象的所有子對(duì)象執(zhí)行這一操作(注釋中帶有?@ignore?標(biāo)記的除外)。

reset_faders.py:

with?WaapiClient()?as?client:
? ? num_reset_faders =?0
? ? selected_guid = get_selected_guid()

? ??for?obj_id, obj_type, obj_notes?in?walk_wproj(client, selected_guid,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??properties=['id',?'type',?'notes']):
? ? ? ??if?'@ignore'?in?obj_notes:
? ? ? ? ? ??continue

# 注意,我們希望根據(jù)對(duì)象是屬于 Actor-Mixer Hierarchy
# 還是 Master Mixer Hierarchy 來調(diào)節(jié)不同的屬性
? ? ? ? prop_name =?'Volume'
? ? ? ??if?obj_type ==?'Bus'?or?obj_type ==?'AuxBus':
? ? ? ? ? ? prop_name =?'BusVolume'

? ? ? ? cur_volume = get_property_value(client, obj_id, prop_name)
? ? ? ??if?cur_volume?is?not?None:
# 按照約定,若屬性不存在,
# 則 `get_property_value` 返回 None。
# 這樣在對(duì)象上沒有音量屬性時(shí),
# 將跳過對(duì) `set_property_value` 的調(diào)用
? ? ? ? ? ? set_property_value(client, obj_id, prop_name,?0)
? ? ? ? ? ? num_reset_faders +=?1

? ? showinfo('Info',?f'{num_reset_faders}?faders were reset')

結(jié)果如下:

?

?

例 4:移除無效的 Event

比方說,我們剛剛從 Actor-Mixer Hierarchy 刪除了一堆遺留對(duì)象。這樣的話可能會(huì)留下很多具有無效引用的 Event Action。其中有些 Event 會(huì)變得毫無用處,因?yàn)槠渌?Action 現(xiàn)在引用的對(duì)象都不存在了。在這種情況下,可以安全刪除這些 Event。為此,可使用 WAAPI 來遍歷所有 Event,并確認(rèn)是否其所有 Action 均指向不存在的對(duì)象。若果真如此,則標(biāo)記 Event 以供刪除。

delete_invalid_events.py:

# 一系列引用對(duì)象的 Action Type 標(biāo)識(shí)符。
# 有關(guān)詳細(xì)信息,參見 Action 對(duì)象引用。
action_types_to_check = {1,?2,?7,?9,?34,?37,?41}
events_to_delete = []

with?WaapiClient()?as?client:
? ? num_obj_visited =?0
? ??for?event_guid,?in?walk_wproj(client,?'\\Events',?properties=['id'],?types=['Event']):
? ? ? ??print(f'Visited:?{num_obj_visited}, To delete:?{len(events_to_delete)}',?end='\r')
? ? ? ? num_valid_actions =?0
? ? ? ??for?action_id, action_type, target?in?walk_wproj(client, event_guid,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?properties=['id',?'ActionType',?'Target'],
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?types=['Action']):
? ? ? ? ? ??if?action_type?in?action_types_to_check:
? ? ? ? ? ? ? ??if?does_object_exist(client, target['id']):
? ? ? ? ? ? ? ? ? ? num_valid_actions +=?1
? ? ? ? ? ??else:
? ? ? ? ? ? ? ? num_valid_actions +=?1

? ? ? ??if?num_valid_actions ==?0:
? ? ? ? ? ? events_to_delete.append(event_guid)

? ? num_events_to_delete =?len(events_to_delete)
? ??if?num_events_to_delete >?0?\
? ? ? ? ? ??and?askyesno('Confirm',?f'{num_events_to_delete}?events are going to be deleted. Proceed?'):
? ? ? ? begin_undo_group(client)
? ? ? ??for?event_guid?in?events_to_delete:
? ? ? ? ? ? delete_object(client, event_guid)
? ? ? ? end_undo_group(client,?'Delete Invalid Events') ?# capitalized as per Wwise convention

showinfo('Success',?f'{len(events_to_delete)}?were deleted')

這段代碼稍微有點(diǎn)復(fù)雜。

首先,注意這里保存了一組有可能引用對(duì)象的 Action Type(如 Play、Stop、Set RTPC 等)。所有引用不存在的對(duì)象的 Action Type 都將被視為無效。這組 Action Type 需依據(jù)?Action?對(duì)象文檔手動(dòng)填入(參見 ActionType 屬性說明)。

其次,我們請(qǐng)求了用戶允許執(zhí)行破壞性操作。此時(shí),將顯示對(duì)話框并詢問是否要?jiǎng)h除特定數(shù)量的 Event。

最后,命令會(huì)使用 WAAPI 的?Undo Group?功能。藉此,可在單次“撤銷”操作中撤銷通過多項(xiàng) WAAPI 調(diào)用做出的更改。該項(xiàng)操作在 Edit 菜單中設(shè)有專用名稱:Delete Invalid Events。

相關(guān)截圖:

?

例 5:為長 SFX 設(shè)置流播放

要想將 SFX 批量配置為使用流播放并不容易,因?yàn)橛袝r(shí)設(shè)計(jì)師會(huì)對(duì)音頻源進(jìn)行修剪,最終的 Wave 文件長度可能跟運(yùn)行時(shí)的 SFX 時(shí)長存在很大差別。截至撰寫本文的時(shí)候,無論是通過 Wwise Query 還是 WAQL 都無法獲取修剪后的 SFX 時(shí)長。

幸好,WAAPI 提供有 trimmedDuration 屬性。在此,讓我們進(jìn)一步將工具所要設(shè)置的流播放參數(shù)設(shè)為可從 Wwise 設(shè)計(jì)工具中進(jìn)行配置。在本例中,我會(huì)將配置放在?\Actor-Mixer Hierarchy\Default Work Unit?注釋分區(qū),并使用 Python 的?configparser?語法。其實(shí)非常簡(jiǎn)單,而且已經(jīng)內(nèi)置到 Python 中。比方說,我們的配置如下圖所示:

[Enable_Streaming_For_SFX]
If_Longer_Than = 10
Non_Cachable = no
Zero_Latency = no
Prefetch_Length_Ms = 400

對(duì)于較大的工程,您可能想針對(duì)不同類型的聲音甚至平臺(tái)對(duì)流播放進(jìn)行更加精細(xì)的控制。這樣做的好處在于可針對(duì)腳本中的每項(xiàng)特定任務(wù)添加配置分區(qū)并檢索自己需要的配置。

set_streaming_for_long_sfx.py:

with?WaapiClient()?as?client:
? ? dwu_notes = get_property_value(
? ? ? ? client,?'\\Actor-Mixer Hierarchy\\Default Work Unit',?'notes')
? ??if?dwu_notes?is?None:
? ? ? ??raise?RuntimeError('Could not fetch notes from Default Work Unit')

? ? config = configparser.ConfigParser()
? ? config.read_string(dwu_notes)
? ??if?'Enable_Streaming_For_SFX'?not?in?config:
? ? ? ??raise?RuntimeError('Could not find [Enable_Streaming_For_SFX] config section')

? ? stream_config = config['Enable_Streaming_For_SFX']

? ? objects_to_modify = []
? ??for?guid, name, max_dur_src?in?walk_wproj(client,?'\\Actor-Mixer Hierarchy',
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ['id',?'name',?'maxDurationSource'],?'Sound'):
? ? ? ??if?max_dur_src?is?None:
? ? ? ? ? ??continue
? ? ? ??# trimmedDuration is in seconds, not milliseconds
? ? ? ? is_long_sound = max_dur_src['trimmedDuration'] > stream_config.getfloat('If_Longer_Than')
? ? ? ??if?is_long_sound:
? ? ? ? ? ? objects_to_modify.append(guid)
? ? ? ? ? ??print(name)
? ? ? ? ? ??break

? ??if?len(objects_to_modify) >?0?and?\
? ? ? ? ? ? askyesno('Confirm',
? ? ? ? ? ? ? ? ? ? ?f'The tool is about to modify properties of?{len(objects_to_modify)}?objects. Proceed?'):
? ? ? ? begin_undo_group(client)
? ? ? ??for?guid?in?objects_to_modify:
? ? ? ? ? ? set_property_value(client, guid,?'IsStreamingEnabled',?True)
? ? ? ? ? ? set_property_value(client, guid,?'IsNonCachable',?True) ?# stream_config.getboolean('Non_Cachable'))
? ? ? ? ? ? set_property_value(client, guid,?'IsZeroLantency', stream_config.getboolean('Zero_Latency'))
? ? ? ? ? ? set_property_value(client, guid,?'PreFetchLength', stream_config.getint('Prefetch_Length_Ms'))
? ? ? ? end_undo_group(client,?'Bulk Set SFX Streaming')
? ? ? ? showinfo('Success',?f'{len(objects_to_modify)}?objects were updated')
? ??else:
? ? ? ? showinfo('Success',?f'No changes have been made')

最終的配置如下所示:

例 6:將多個(gè)容器重構(gòu)為一個(gè) Switch Container

有時(shí),我們需要將 Actor-Mixer Hierarchy 下的多個(gè)容器重構(gòu)為一個(gè) Switch Container。對(duì)此,可嘗試構(gòu)建相應(yīng)工具來自動(dòng)完成此操作:用戶選擇多個(gè)對(duì)象并單擊按鈕來創(chuàng)建新的 Switch Container,同時(shí)將選定對(duì)象指派給不同的 Switch。在本例中,我將使用 Wwise Adventure Game 工程內(nèi)的 "Surface_Type" Switch。

refactor_into_switch_surface_type.py:

with?WaapiClient()?as?client:
? ? obj_names = [get_name_of_guid(client, guid)
? ? ? ? ? ? ? ? ?for?guid?in?selected_guids]
? ??if?None?in?obj_names:
? ? ? ??raise?RuntimeError('Could not get names of all selected objects')
? ?
? ? switches = get_switches_for_group_surface_type(client)
? ??if?len(switches) ==?0:
? ? ? ??raise?RuntimeError("Could not find switches for group 'Surface_Type'")
? ? parent_obj = get_parent_guid(client, selected_guids[0])
? ??if?parent_obj?is?None:
? ? ? ??raise?RuntimeError(f'{selected_guids[0]}?has no parent')
? ?
? ? begin_undo_group(client)
? ?
? ? switch_obj = create_objects(client, parent_obj,?'RENAME_ME',?'SwitchContainer')[0]
? ??if?switch_obj?is?not?None:
? ? ? ? set_reference(client, switch_obj,?'SwitchGroupOrStateGroup',
? ? ? ? ? ? ? ? ? ? ??f'SwitchGroup:{SURFACE_TYPE_SWITCH_GROUP_NAME}')
? ??else:
# 若腳本在操作當(dāng)中失敗,則回滾更改
? ? ? ? end_undo_group(client,?'Refactor Into Surface_Type Switch')
? ? ? ? perform_undo(client)
? ? ? ??raise?RuntimeError('Could not create switch container under '?+
? ? ? ? ? ? ? ? ? ? ? ? ? ?f'{get_name_of_guid(client, parent_obj)}. '
? ? ? ? ? ? ? ? ? ? ? ? ? ?'All changes have been reverted.')
? ?
# 重新設(shè)置選定對(duì)象的父對(duì)象
? ??for?guid?in?selected_guids:
? ? ? ? res = move_object(client, guid, switch_obj)
? ? ? ??if?res?is?None:
? ? ? ? ? ? end_undo_group(client,?'Refactor Into Surface_Type Switch')
? ? ? ? ? ? perform_undo(client)
? ? ? ? ? ??raise?RuntimeError(
? ? ? ? ? ? ? ??f'Could not move object?{guid}?to parent?{switch_obj}. '
? ? ? ? ? ? ? ??'All changes have been reverted.')
? ?
? ? obj_assignments = infer_obj_assignments(selected_guids, switches)
? ?
? ??for?obj_guid, sw_guid?in?obj_assignments:
? ? ? ? client.call('ak.wwise.core.switchContainer.addAssignment',
? ? ? ? ? ? ? ? ? ? {'child': obj_guid,?'stateOrSwitch': sw_guid})
? ?
? ? end_undo_group(client,?'Refactor Into Surface_Type Switch')

這段代碼比前面的示例要復(fù)雜一點(diǎn)。為了避免列表過長,我甚至不得不將有些部分重構(gòu)為兩個(gè)函數(shù)。第一個(gè)函數(shù)?get_switches_for_group_surface_type?為輔助程序,用于獲取所有 "Surface_Type" Switch 的 GUID 和名稱。第二個(gè)函數(shù)?infer_obj_assignments?嘗試將選定對(duì)象與 Switch 進(jìn)行匹配:對(duì)兩者的名稱進(jìn)行比較并選擇最為相近的 Switch 名稱(thefuzz?庫的?partial_ratio?函數(shù))。

其他注意事項(xiàng):

  • 代碼會(huì)在執(zhí)行當(dāng)中對(duì)不同的數(shù)據(jù)進(jìn)行驗(yàn)證。若狀態(tài)無效,則拋出 RuntimeError 異常。此類異常將以錯(cuò)誤窗口形式顯示給用戶。

  • 對(duì)于有些錯(cuò)誤路徑,在拋出異常之前,腳本會(huì)執(zhí)行“撤銷”操作來回滾目前為止通過 WAAPI 所作的全部更改。

  • 另外還可看到對(duì)?waapi-client?的直接調(diào)用,因?yàn)檩o助程序庫中沒有 Switch Container 指派函數(shù)。

  • 此腳本并沒有經(jīng)過任何優(yōu)化,所以肯定有很多不完美之處,可能還存在奇怪的極端案例。只是順手寫的,還請(qǐng)各位注意。

以下截圖展示了相應(yīng)工作機(jī)制。

?

?

例 7:從 Originals 文件夾刪除未使用的 Wave 文件

隨著時(shí)間的推移,Wwise 工程可能會(huì)在 Originals 文件夾中收集 Wave 文件。這些文件沒有被任何地方引用,所以只會(huì)白白地浪費(fèi)磁盤空間。在這種情況下,用戶可按下按鈕。這時(shí)腳本會(huì)要求確認(rèn),然后告知是已經(jīng)全部刪除還是保留了部分內(nèi)容。比如,某個(gè)文件是在 Audition 中打開了還是被鎖定了。這里有個(gè)小竅門,就是在執(zhí)行此類操作后運(yùn)行 Integrity Report。

remove_unused_wavs.py:

with?WaapiClient()?as?client:
? ? default_wu_path, = get_object(client,?'\\Actor-Mixer Hierarchy\\Default Work Unit',?'filePath')
# 此函數(shù)會(huì)解析 .wproj 文件以確定 'Originals' 目錄的位置
? ? origs_dir = find_originals_dir(default_wu_path)

? ? wavs_in_origs =?set()
? ? wavs_in_wproj =?set()

# 我們不想碰 'Plugins' 目錄
? ??for?subdir?in?'SFX',?'Voices':
? ? ? ??for?wav_path?in?glob(os.path.join(origs_dir, subdir,?'**',?'*.wav'),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?recursive=True):
? ? ? ? ? ? wavs_in_origs.add(normalize_path(wav_path))

# 注意,單個(gè) walk_wproj 可從不同位置
# 多次遍歷層級(jí)結(jié)構(gòu)
? ??for?guid, wav_path?in?walk_wproj(
? ? ? ? ? ? client,
? ? ? ? ? ??start_guids_or_paths=['\\Actor-Mixer Hierarchy',?'\\Interactive Music Hierarchy'],
? ? ? ? ? ??properties=['id',?'originalWavFilePath'],
? ? ? ? ? ??types=['AudioFileSource']
? ? ):
? ? ? ? wavs_in_wproj.add(normalize_path(wav_path))

? ? wavs_to_remove = wavs_in_origs.difference(wavs_in_wproj)
? ? files_left =?len(wavs_to_remove)

? ??if?files_left >?0?and?askyesno(
? ? ? ? ? ??'Confirm',?f'You are about to delete?{files_left}?files. Proceed?'):
? ? ? ??for?wav_path?in?wavs_to_remove:
? ? ? ? ? ??try:
? ? ? ? ? ? ? ? os.remove(wav_path)
? ? ? ? ? ? ? ? files_left -=?1
? ? ? ? ? ??except?PermissionError:
? ? ? ? ? ? ? ??pass

? ? ? ??if?files_left ==?0:
? ? ? ? ? ? showinfo('Success',
? ? ? ? ? ? ? ? ? ? ?f'{len(wavs_to_remove)}?files were deleted')
? ? ? ??else:
? ? ? ? ? ? showwarning('Warning',
? ? ? ? ? ? ? ? ? ? ? ??f'{files_left}?files could not have been deleted. '
? ? ? ? ? ? ? ? ? ? ? ??f'Are they open in some apps?')

雖然這段代碼看起來比前面的示例簡(jiǎn)單,但這里遇到了對(duì) WAAPI 的一些限制,而不得不解析 Wwise 工程文件來獲取 Originals 文件夾的路徑。最初,我以為這些信息是存儲(chǔ)在?Project?對(duì)象中的,但顯然不是,最后我也沒能找到其他方法來進(jìn)行查詢。

除此之外,我還嘗試了通過?walk_wproj?來檢索所有?AudioFileSource?類型的對(duì)象。不過,Wwise 對(duì)象參考頁面并未列出此類型。最相近的類型為?AudioSource,其有可能為父類型,但對(duì)此我無法確定。之所以做此嘗試是因?yàn)槲医馕霾⑸闪?Work Unit XML,記得好像 Wave 文件加了?<AudioFileSource/>?標(biāo)記。后來我就想,這樣做有可能會(huì)比較便捷一些。另外,此類型被列在了?%WWISEROOT%\Authoring\Data\Schemas?位置的 XML 架構(gòu)文件中。

相關(guān)截圖:

?

例 8:自動(dòng)導(dǎo)入 Wave 文件

在很多情況下,游戲中的聲音會(huì)被存放到不同的層級(jí)結(jié)構(gòu),而且這些聲音可能包含多個(gè)分層。各個(gè)分層會(huì)輸出到不同的總線,并由不同的 RTPC 控制。其中有些可構(gòu)建為帶插槽的層級(jí)結(jié)構(gòu),以便關(guān)聯(lián)音頻素材并創(chuàng)建特定的聲音。其實(shí),有點(diǎn)像復(fù)制現(xiàn)有層級(jí)結(jié)構(gòu),并在相應(yīng)插槽中替換素材。

當(dāng)然,我們可以通過 WAAPI 完成這一操作。為直觀起見,我們來看看下面的用例。

  • 用戶配置模板層級(jí)結(jié)構(gòu)并為其各個(gè)部分做注釋,來指定模板名稱、插槽及其名稱等。在下圖中,Gun 模板設(shè)有 Shot、Tail_Indoor 和 Tail_Outdoor 插槽。

  • 用戶右鍵單擊模板并按下按鈕。系統(tǒng)提示選擇包含統(tǒng)一命名的 Wave 文件的目錄。在選擇之后,工具會(huì)掃描目錄并查找與模板名稱匹配的文件。?

?


?


?

  • 在用戶確認(rèn)導(dǎo)入后,將復(fù)制、重命名模板并在插槽中填入 Wave 文件。

  • 在截圖中,工具一次性導(dǎo)入了兩個(gè)聲音對(duì)象:M4 和 M1911。同時(shí),將模板對(duì)象 GUID 記錄到了槍械對(duì)應(yīng) Virtual Folder 的注釋中,以便其他腳本在需要時(shí)對(duì)其進(jìn)行掃描。


  • 有關(guān)工作示例,請(qǐng)查看?import_wavs.py?源碼。不過,這段代碼要稍微復(fù)雜一點(diǎn)。

    結(jié)語

    在本文中,我介紹了自己是如何使用 WAAPI 和 Python 的,包括代碼的組織以及腳本的共用。為了進(jìn)一步闡明觀點(diǎn),我還列出了一些用來實(shí)現(xiàn)相應(yīng)工作流程的工具示例。各位如果有什么意見或者建議,不妨聯(lián)系我或在博文下方留言。

    其中一些可以改進(jìn)的地方:

    • 在將 WaapiClient 實(shí)例化時(shí)考慮使用?allow_exception=True。這樣可以捕獲 WAAPI 特定異常,并向用戶提供更為詳細(xì)的錯(cuò)誤消息。比如,在 Wwise 中打開某些對(duì)話框窗口的時(shí)候。

    • 按照我們的 Python 工程約定,所有命令擴(kuò)展腳本會(huì)直接放到 Scripts 文件夾下。藉此,可自動(dòng)生成命令擴(kuò)展 JSON 文件。

    • 按照類似于?YAML 扉頁的方式拆分 notes 分區(qū)的配置來使其更加有條理。這種方式在很多網(wǎng)站構(gòu)建框架(如 Jekyll)中都有使用。這樣的話便可同時(shí)使用系統(tǒng)內(nèi)嵌和手動(dòng)添加的注釋。

    • 在例 4 中,除了刪除無效的 Event,也應(yīng)刪除無效的 Action。因?yàn)樗鼈儺?dāng)前在工程中根本就沒有用,Integrity Report 中可能還會(huì)報(bào)告錯(cuò)誤。

    • Switch 重構(gòu)命令可使用某些算法來賦予 Switch 以最為相近的名稱。比如,獲取所有選定對(duì)象共有的子字符串,或者…由 AI 來自動(dòng)對(duì)文本進(jìn)行匯總!

    致謝

    本文基于我在 DevGAMM Fall 20214?大會(huì)上所做的演講。其中的想法都是我在網(wǎng)易游戲技術(shù)音頻團(tuán)隊(duì)任職的時(shí)候跟 德米特里·帕特里科夫 (Dmitry Patrakov)、魯斯蘭·內(nèi)斯特魯克 (Ruslan Nesteruk) 和維克多·埃爾馬科夫 (Victor Ermakov) 一起琢磨出來的。另外,在此要特別感謝戴米安?卡斯特鮑爾 (Damian Kastbauer) 對(duì)我在 DevGAMM 上做的幻燈片以及 Wave 文件導(dǎo)入器提供反饋;感謝伯納德?羅德里格 (Bernard Rodrigue) 的同行評(píng)審和在文中加入腳本調(diào)試代碼的建議;感謝瑪莎?利特維納瓦 (Masha Litvinava) 在刊發(fā)過程中提供的校對(duì)和協(xié)助;感謝蒂奧馬?馬切夫 (Tyoma Makeev) 對(duì) Switch Container 示例提供的建議;感謝丹尼斯?茲洛賓 (Denis Zlobin) 對(duì)我在 Audiokinetic 博客版塊發(fā)布這些材料的鼓勵(lì)。


    附錄:運(yùn)行示例

    這里展示的所有示例均有存檔,各位可點(diǎn)擊此處下載(https://audiokinetic.com/media/blog/ak_files_rev3.zip)。直接將其內(nèi)容解壓到 Wwise 工程就可使用。不過,要注意這些示例是使用 Wwise Adventure Game 工程制作的。

    其他相關(guān)要求:

    • Git

    • Python 3:只要是最近的版本應(yīng)該就行。不過,在安裝過程中一定要勾選復(fù)選框來將 Python 執(zhí)行文件添加到 PATH,同時(shí)安裝 pip 和 tcl/Tk 組件

    • Wwise 2021:如果想完全依照步驟操作,請(qǐng)安裝 Wwise Adventure Game

    • 在 Wwise 工程設(shè)置中,確保啟用 WAAPI

    在系統(tǒng)中安裝 Python 后,還要安裝幾個(gè) Python 數(shù)據(jù)包:

    pip install -U pyperclip waapi-client
    pip install -U git+https://github.com/ech2/waapi_helpers.git

    另外,例 6 使用了模糊字符串匹配算法。如果想運(yùn)行它,還需安裝以下數(shù)據(jù)包:

    pip install -U thefuzz python-Levenshtein

    1. 這是一個(gè)已經(jīng)確認(rèn)的漏洞,其與 redirectOutput 選項(xiàng)跟自定義 cwd 的交互有關(guān)。?

    2. 真希望自己能早點(diǎn)學(xué)會(huì)這一小竅門,因?yàn)橹懊看胃?JSON 之后都是重新啟動(dòng) Wwise 設(shè)計(jì)工具。?

    3. 為免廣告之嫌,我對(duì)使用?waapi-client?最上面編寫的封裝器做了一些保留。說實(shí)話,我?guī)缀鯖]怎么在 Python 中用過 WAAPI,而且已經(jīng)記不太清楚常用 WAAPI 函數(shù)的 JSON 架構(gòu)。我并不是讓大家都使用我的工具,而只是建議參閱其中的示例代碼。?

    4. 演講時(shí)用的是俄語,但幻燈片是英語的。這段演講最終應(yīng)該會(huì)發(fā)布到 YouTube 上?;脽羝痛a示例可從以下 URL 下載:https://github.com/ech2/DevGAMM_2021_Fall/

    尤金?喬爾內(nèi) (EUGENE CHERNY)

    尤金?喬爾內(nèi) (Eugene Cherny) 是一名音頻程序員,目前從事于游戲行業(yè)。他對(duì)藝術(shù)和學(xué)術(shù)也頗有興趣,不過最熱愛的還是游戲音頻。



關(guān)于如何在團(tuán)隊(duì)工作環(huán)境中使用 WAAPI 和 Python的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
汶川县| 南丰县| 昌乐县| 克拉玛依市| 蓝田县| 渝北区| 博爱县| 新巴尔虎右旗| 宁武县| 沁源县| 长岭县| 博湖县| 安仁县| 交城县| 渝北区| 乌恰县| 涟水县| 油尖旺区| 通山县| 东丽区| 荔波县| 萝北县| 定西市| 宝清县| 高邮市| 江达县| 临汾市| 吉安县| 华蓥市| 综艺| 巫溪县| 读书| 沙田区| 新源县| 浠水县| 洮南市| 中山市| 类乌齐县| 沅陵县| 中西区| 乌拉特前旗|