[Section 3] 第一個 YAML 檔案
## 什麼是 YAML 檔案
YAML 檔是一種文字檔案,非常重視縮排的特性相當類似 python 的編輯邏輯,這個規則讓編輯跟閱讀變得相當容易,但對於新手來說很容易編寫錯誤讓程式看不懂。如果你編輯完以後發現沒反應,那大概是縮排出問題了。為了減少縮排問題帶來的影響,建議你使用比較專門的編輯器,避免使用 TextEdit、Word 或是 Pages 之類的程式編輯 YAML 檔案。使用 VS Code、Sublime Text 或是 vim、 Emacs 之類的程式會讓編寫過程比較順利一些。
範例:這兩種 YAML 檔案代表的意義並不一樣
- 代碼: 選擇全部
- devices:
 samplerate: 44100
 chunksize: 1024
 capture:
 type: File
 filename: "dummy"
 channels: 2
 format: S16LE
 playback:
 type: File
 filename: "dummy"
 channels: 2
 format: S16LE
 
- 代碼: 選擇全部
- devices:
 samplerate: 44100
 chunksize: 1024
 capture:
 type: File
 filename: "dummy"
 channels: 2
 format: S16LE
 playback:
 type: File
 filename: "dummy"
 channels: 2
 format: S16LE
 
另外,也需要注意的是看到的 YAML 檔案內容會隨著 CamillaDSP 的版本會有些許不一樣。這篇文章依據 CamillaDSP 2.0.3 撰寫,也許 CamillaDSP 更新以後會有不一樣的寫法。如果發現問題的話,請以 GitHub 裡面的說明為準。
## CamillaDSP 需要什麼樣的訊息
根據 
GitHub 的描述,CamillaDSP 會需要
* [Devices] 音樂訊號輸入與輸出
* [Resampling] 重新取樣訊號
* [Mixers] 訊號混合,有沒有需要增減數位通道
* [Filters] 訊號過濾,做各種處理。
** [Gain] 訊號增益,讓訊號變大變小
** [Volume] 整體音量控制,音控如何動作
** [Loudness] 整體響度補償,設定不同音量控制的時候如何補償高低頻響應
** [Delay] 訊號時間延遲,讓訊號晚點出現
** [FIR] 讓訊號根據載入檔案補償 FIR 曲線
** [IIR] 一般所說的 EQ,寫入各種 IIR 濾波器
** [Dither] 幫訊號增加雜訊
** [Limiter] 訊號限制器,如何處理削波 (Clipping) 的訊號
** [Difference equation] 用數學函數調整訊號響應
* [Processors] 多組數位訊號處理,目前只有壓縮器一項可以用
** [Compressor] 訊號壓縮器,可以壓縮動態範圍
* [Pipeline] 資料處理流程,照順序處理定義過的處理單元
## 一個簡單的 YAML 檔案
我用來測試 CamillaDSP 的檔案是實作一個單聲道的數位分音。將我的兩聲道 DAC 轉換為一邊是推高音,一邊推低音。
### 輸入與輸出
第一項需要的是設定輸入與輸出,這個在 devices 欄位裡面定義。要讓 CamillaDSP 處理你的音訊,你需要先將電腦的聲音輸出到安裝好的 BlackHole 裡面,換句話說就是把聲音輸出給 BlackHole,然後 CamillaDSP 從 BlackHole 截取訊號處理完丟給你的 DAC。

你會需要打開 MacOS 裡面的 "Audio MIDI Setup",裡面會有比較多的相關資訊。在這裡你可以找到 CamillaDSP 需要的內容。

你可以在裡面找到「設備名稱」、「格式」、「Sample Rate」、「channel 數量」等等的訊息。(註1)
一個好的設定會長的下面這樣:
- 代碼: 選擇全部
- devices:
 adjust_period: 10
 capture:
 change_format: false
 channels: 2
 device: BlackHole 16ch
 format: FLOAT32LE
 type: CoreAudio
 capture_samplerate: 48000
 chunksize: 4096
 enable_rate_adjust: true
 enable_resampling: true
 playback:
 change_format: false
 channels: 2
 device: 'USB Audio DAC   '
 exclusive: false
 format: S16LE
 type: CoreAudio
 queuelimit: 4
 rate_measure_interval: 1
 resampler_type: BalancedAsync
 samplerate: 48000
 silence_threshold: -100
 silence_timeout: 5
 stop_on_rate_change: false
 target_level: 0
 
在這份範例裡面,CamillaDSP 從 BlackHole 16ch 截取音樂,這個 BlackHole 16ch 使用了 FLOAT32LE 的音樂格式,BlackHole 16ch 擁有 2 個 channel 並且使用了 48000Hz 的 Sample Rate (capture_samplerate: 48000)。然後決定我的輸出是 "USB Audio DAC   " (註1),擁有兩個 channel, 48000 Hz 的 Sample Rate (samplerate: 48000)並且使用 S16LE 格式。此外 CamillaDSP 並不會獨佔這個 DAC  (exclusive: false)
註1 :如果你照著打名字還是發現 CamillaDSP 找不到 DAC,那恭喜你遇到噁心的問題了,這表示你的 DAC 製造商寫了空格在你的 DAC 名字內,所以你必須將空格也擺進 DAC 名字內。這時候 Audio MIDI Setup 程式雖然顯示了正確的名稱,但人眼無法讀取到底有幾個空格在裡面。因此你必須使用 terminal 並執行指令
- 代碼: 選擇全部
-  system_profiler SPAudioDataType 
然後複製正確的字串。
### Filter 
CamillaDSP 最重要的設定就是要對音訊做處理,每項處理都是一個 Filter 模組。每個模組都有自己的名字、種類 (type) 跟參數 (parameters)。而且你可以從範例程式碼裡面發現,定義 Filter 模組的時候並沒有定義他被用在什麼地方。這裡僅僅是定義功能,作用的範圍記載於 pipeline 區域。
這裡的定義是不分順序的,每一個 Filter 都是獨立的模組,不會互相影響。
- 代碼: 選擇全部
- filters:
 my_example_delay:
 type: Delay
 parameters:
 delay: 0.019
 unit: ms
 subsample: false
 low_pass_filter:
 parameters:
 freq: 2200.0
 order: 4
 type: ButterworthLowpass
 type: BiquadCombo
 high_pass_filter:
 parameters:
 freq: 2200.0
 order: 4
 type: ButterworthHighpass
 type: BiquadCombo
 
這裡你可以看到我寫了三個 filter,延遲、高通與低通。這裡名字可以亂取,但不能重複。
### Mixer 混音器
混音器 Mixer 定義了輸入與輸出該怎麼對應的問題。想像你的音樂是一個兩聲道音樂,而 DAC 卻是八聲道的情況,這種情況顯然就不能夠一對一對應了。混音器 Mixer 就會是你需要定義的東西。
- 代碼: 選擇全部
- mixers:
 1x2:
 channels:
 in: 2
 out: 2
 mapping:
 - dest: 0
 mute: false
 sources:
 - channel: 0
 gain: 0
 inverted: false
 mute: false
 - dest: 1
 mute: false
 sources:
 - channel: 0
 gain: 0
 inverted: false
 mute: false
 
在這裡我的例子是兩聲道音樂僅僅使用了左聲道,然而這個左聲道音樂都被輸出到 DAC 的兩個聲道裡。這樣我可以對輸出的音樂做後續處理。
### Pipeline 資料流
Pipeline 定義了資料處理的順序,在這裡是使用列表來呈現,先定義好的東西會先被執行。而你需要知道的是,一開始的時候這個音樂是兩聲道音樂,經過了你定義的功能以後,這個音樂變成什麼了?
- 代碼: 選擇全部
- pipeline:
 - type: Mixer
 description: 'let channel 0 occupies output channel 0 and 1'
 name: 1x2
 - type: Filter
 channel: 0
 description: 'output left channel as a tweeter'
 names:
 - high_pass_filter
 - my_example_delay
 - type: Filter
 description: 'output right channel as a woofer'
 channel: 1
 - low_pass_filter
 
在這個範例裡面,一開始的兩聲道音樂先經過 (1) Mixer: 1x2,在這裡他把兩聲道音樂變成單聲道音樂,而且 channel0 跟 channel1 的聲音是一樣的。然後經過 (2) Filter,這個 Filter 是作用在 channel0 上,讓 channel0 通過高通和延遲模組。最後是 (3) Filter,這個 Filter 作用在 channel1 上,讓 channel1 通過低通模組。
*********************** 錯誤範例 ***********************
- 代碼: 選擇全部
- pipeline:
 - type: Filter
 channel: 0
 description: 'output left channel as a tweeter'
 names:
 - high_pass_filter
 - my_example_delay
 - type: Filter
 description: 'output right channel as a woofer'
 channel: 1
 - low_pass_filter
 - type: Mixer
 description: 'let channel 0 occupies output channel 0 and 1'
 name: 1x2
 
如果這份檔案的順序調換了,那麼解讀的方式會變得不一樣。在這個情況下,音樂的兩聲道會在 (1) 左聲道 channel0 通過一個高通和延遲模組,然後在 (2) 右聲道 channel1 通過一個低通模組。最後在 (3) Mixer 的部分把右聲道丟掉,讓左聲道的音樂被放進 DAC 的 channel 0 和 channel 1 中
*********************** 錯誤範例 ***********************
### 完整程式碼
這邊是完整的程式碼,你可以從這裡複製上述提到的所有程式碼,好讓你的 CamillaDSP 開始動作。不過要記得,你還是需要修改輸入與輸出才能夠讓 CamillaDSP 開始動作
- 代碼: 選擇全部
- devices:
 adjust_period: 10
 capture:
 change_format: false
 channels: 2
 device: BlackHole 16ch
 format: FLOAT32LE
 type: CoreAudio
 capture_samplerate: 48000
 chunksize: 4096
 enable_rate_adjust: true
 enable_resampling: true
 playback:
 change_format: false
 channels: 2
 device: 'USB Audio DAC   '
 exclusive: false
 format: S16LE
 type: CoreAudio
 queuelimit: 4
 rate_measure_interval: 1
 resampler_type: BalancedAsync
 samplerate: 48000
 silence_threshold: -100
 silence_timeout: 5
 stop_on_rate_change: false
 target_level: 0
 filters:
 my_example_delay:
 type: Delay
 parameters:
 delay: 0.019
 unit: ms
 subsample: false
 low_pass_filter:
 parameters:
 freq: 2200.0
 order: 4
 type: ButterworthLowpass
 type: BiquadCombo
 high_pass_filter:
 parameters:
 freq: 2200.0
 order: 4
 type: ButterworthHighpass
 type: BiquadCombo
 mixers:
 1x2:
 channels:
 in: 2
 out: 2
 mapping:
 - dest: 0
 mute: false
 sources:
 - channel: 0
 gain: 0
 inverted: false
 mute: false
 - dest: 1
 mute: false
 sources:
 - channel: 0
 gain: 0
 inverted: false
 mute: false
 pipeline:
 - type: Mixer
 description: 'let channel 0 occupies output channel 0 and 1'
 name: 1x2
 - type: Filter
 channel: 0
 description: 'output left channel as a tweeter'
 names:
 - high_pass_filter
 - my_example_delay
 - type: Filter
 description: 'output right channel as a woofer'
 channel: 1
 - low_pass_filter