# week 3
contributed by < `robertlin0401` >
###### tags: `數位音樂訊號分析`
> [GitHub](https://github.com/robertlin0401/Digital-Music-Signal-week3)
---
## 重現範例
> 參考 [JUCE Framework 開發環境設定](https://hackmd.io/@datuiji/JUCEFrameWorkSetting)
### slider UI
結果如下圖

### 使用 AudioPluginHost debug 之設定
* 使用 windows 系統
* 於範例文件中提供的[影片連結](https://www.youtube.com/watch?v=8G7MvRdljLc)與文字說明內容不同
* 影片:將「專案名稱_StandalonePlugin -> 屬性 -> 偵錯 -> 命令」設定為 AudioPluginHost.exe 之檔案路徑
* 文字:右鍵點選 專案名稱_VST3 -> 屬性 -> 偵錯 -> 命令 -> 瀏覽 -> 找到 AudioPluginHost.app 路徑
* 實際測試後,發現影片之方法可得到正確結果,如下圖


### 測試 plugin
* 建置方法:右鍵點擊「專案名稱_VST3」,並點選建置,便會生成 `.vst3` 檔案
* 欲測試時發現問題:在 AudioPluginHost 中右鍵開啟的選單裡,找不到建置出來的 `.vst3` plugin

* 解決方法
1. 點選 Options -> Edit the List of Available Plug-ins...(或使用熱鍵 `ctrl+P`),開啟 Available Plugins 視窗

2. 在 Available Plugins 視窗中,點選 Options... -> Scan for new or updated VST3 plug-ins,開啟選擇視窗

3. 點選左下角「+」鍵,選擇 專案資料夾 -> Builds -> ... -> VST3 資料夾,最後點選 Scan

4. 如此一來,即可在右鍵選單中的 yourcompany 內找到欲測試的 plugin

### 測試後發現之問題
以下問題目前尚未知曉發生原因
1. 自製的 sine wave synth 在 tailOff 未開啟時,聲音結束後會出現不固定之雜音,但 JUCE 內建的 sine wave synth 並不會有此情況,以下錄製兩者各發出三個音作比較
* 自製的 sine wave synth 在第三個音的結尾處有明顯的雜音,而第二個音的結尾處也有較小聲的雜音存在,代表這個雜音訊號的強度並不固定
<iframe
frameborder="0" width="100%" height="100"
src="https://drive.google.com/file/d/1XYKbAjWzAsx9saXwEB7vQd2aYQMTbCAl/preview?usp=sharing">
</iframe>
* 內建的 sine wave synth 就不存在此情況
<iframe
frameborder="0" width="100%" height="100"
src="https://drive.google.com/file/d/1BUj_qqjx0bhI-qFGmpoO87GZHX481oN7/preview?usp=sharing">
</iframe>
2. 自製的 sine wave synth,若移動(包含插上或拔斷)filter 端到 Audio Output 端的接線,有機會發出「炸音箱」的聲音,似乎是在長時間使用後較容易發生,但此行為容易傷害音箱,因此我不太想實驗這件事:cry:
:::info
**補充:炸音箱**
在我以前待的熱音社裡,所謂「炸音箱」的說法是指在訊號可以輸入音箱的情況下,插/拔音源端的接線,在插/拔的瞬間會產生一個較大的訊號,輸入音箱後就會發出「碰」的聲音,但由於功率過高導致音箱有可能被「炸」壞
:::
---
## 實作作業要求
* 作業要求:將 sine wave 合成器改成方波、三角波、鋸齒波
* 四種波形可參考下圖

> 圖源:[維基百科](https://zh.wikipedia.org/wiki/%E6%96%B9%E6%B3%A2)
### 正弦波
```cpp=
if (tailOff > 0.0) {
for (int i = startSample; i < (startSample + numSamples); i++) {
float value = std::sin(currentAngle) * level * tailOff;
outputBuffer.addSample(0, i, value);
outputBuffer.addSample(1, i, value);
currentAngle += angleIncrement;
tailOff *= 0.99;
if (tailOff <= 0.05) {
clearCurrentNote();
angleIncrement = 0.0;
level = 0.0;
break;
}
}
} else {
for (int i = startSample; i < (startSample + numSamples); i++) {
float value = std::sin(currentAngle) * level;
outputBuffer.addSample(0, i, value);
outputBuffer.addSample(1, i, value);
currentAngle += angleIncrement;
}
}
```
* 範例中已經實作完成,以下簡要說明運作原理
* 正弦波的計算:見第 18-24 行的 `for` 迴圈
* 針對一個 block 中的取樣點,依序計算在當前取樣點的角度上,正弦函數 `std::sin()` 的值
* 使用 `addSample()` 分別將計算出來的訊號,放到左、右聲道上
* 延音的計算:見第 2-16 行的程式區塊
* 當 `tailOff > 0.0` 成立時,表示琴鍵已經鬆開,進入延音的部分,且為形成漸弱的效果,在計算訊號時乘上呈指數遞減的變數 `tailOff`
* 當 `tailOff` 值遞減到一定程度(此處設定為 `0.05`),便將該訊號清除
### 方波
```cpp=3
float value = (std::sin(currentAngle) >= 0 ? 1 : -1) * level * tailOff;
```
```cpp=19
float value = (std::sin(currentAngle) >= 0 ? 1 : -1) * level;
```
* 方波之計算,即為正弦函數之值代入符號函數計算之結果,公式如下
$f(\theta)=sgn(sin(\theta))$
* 此處使用三元運算子代替函式實作,可避免因多出一倍的函式呼叫而花費過多時間之可能性
### 三角波
```cpp=3
float value = std::asin(std::sin(currentAngle)) * level * tailOff;
```
```cpp=19
float value = std::asin(std::sin(currentAngle)) * level;
```
* 三角波之計算,即為正弦函數之值代入反正弦函數計算之結果,公式如下
$f(\theta)=arcsin(sin(\theta))$
### 鋸齒波
```cpp=3
float value = std::atan(std::tan(currentAngle)) * level * tailOff;
```
```cpp=19
float value = std::atan(std::tan(currentAngle)) * level;
```
* 鋸齒波之計算,即為正切函數之值代入反正切函數計算之結果,公式如下
$f(\theta)=arctan(tan(\theta))$
:::warning
在設計三角波與鋸齒波時,起初我不知道兩者的公式,並打算根據圖形自行實作,但在過程中遇到困難,而我在參考同學們的共筆時,學到了直接套用公式的做法
:::
### UI
* 使用 ComboBox 作為波形選擇器,並重新排版後如下圖所示

* ComboBox 實作方法
* 宣告
```cpp
juce::ComboBox comboBox;
```
* 加入選項
```cpp
comboBox.addItem("sine", 1);
comboBox.addItem("square", 2);
comboBox.addItem("triangle", 3);
comboBox.addItem("sawtooth", 4);
```
* addItem 的兩個參數分別為:選項的「顯示文字」以及「ID」,其中,ID 不可為 0,且僅作為編號的用途
* 與 SynthVoice 溝通
* 透過 `juce::AudioProcessorValueTreeState` 來傳遞數值,與範例中 slider 之做法相同
* 在 PluginEditor 中宣告並設定 ComboBoxAttachment
```cpp
std::unique_ptr<juce::AudioProcessorValueTreeState::ComboBoxAttachment> modeComboBoxAttachment;
modeComboBoxAttachment.reset(new juce::AudioProcessorValueTreeState::ComboBoxAttachment(audioProcessor.tree, "mode", comboBox));
```
* 在 PluginProcessor 中的 tree 傳入相關參數
```cpp
tree(*this, nullptr, "PARAM", {
std::make_unique<juce::AudioParameterFloat>(
"level",
"Level",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.1f), 0.5f,
juce::String(),
juce::AudioProcessorParameter::genericParameter,
[](float value, int){ return juce::String(value); },
[](juce::String text){ return text.getFloatValue(); }),
std::make_unique<juce::AudioParameterChoice>(
"mode",
"Mode",
juce::StringArray({ "sine", "square", "triangle", "sawtooth" }), 0)
})
```
* StringArray 中的內容即為選項列表,其 index 編號從 0 起算,後方的 0 則代表預設值,預設選項在開啟時會直接顯示在 UI 上
* 傳遞之數值為此處的 index,而非前述 addItem 中使用的 ID
* 在 SynthVoice 中定義 `setMode()` 方法,並將取得之數值傳入即可,取得數值之方法如下
```cpp
tree.getRawParameterValue("mode")->load()
```
### 發現問題
透過 UI 切入或切出 square wave mode 時,會出現「炸音箱」的情形,原因暫時不得而知