# week 3 contributed by < `robertlin0401` > ###### tags: `數位音樂訊號分析` > [GitHub](https://github.com/robertlin0401/Digital-Music-Signal-week3) --- ## 重現範例 > 參考 [JUCE Framework 開發環境設定](https://hackmd.io/@datuiji/JUCEFrameWorkSetting) ### slider UI 結果如下圖 ![](https://i.imgur.com/6t3Ak4k.png) ### 使用 AudioPluginHost debug 之設定 * 使用 windows 系統 * 於範例文件中提供的[影片連結](https://www.youtube.com/watch?v=8G7MvRdljLc)與文字說明內容不同 * 影片:將「專案名稱_StandalonePlugin -> 屬性 -> 偵錯 -> 命令」設定為 AudioPluginHost.exe 之檔案路徑 * 文字:右鍵點選 專案名稱_VST3 -> 屬性 -> 偵錯 -> 命令 -> 瀏覽 -> 找到 AudioPluginHost.app 路徑 * 實際測試後,發現影片之方法可得到正確結果,如下圖 ![](https://i.imgur.com/lOqA4De.png) ![](https://i.imgur.com/RlfwQvb.png) ### 測試 plugin * 建置方法:右鍵點擊「專案名稱_VST3」,並點選建置,便會生成 `.vst3` 檔案 * 欲測試時發現問題:在 AudioPluginHost 中右鍵開啟的選單裡,找不到建置出來的 `.vst3` plugin ![](https://i.imgur.com/QjSGnxy.png) * 解決方法 1. 點選 Options -> Edit the List of Available Plug-ins...(或使用熱鍵 `ctrl+P`),開啟 Available Plugins 視窗 ![](https://i.imgur.com/GU5T3dn.png) 2. 在 Available Plugins 視窗中,點選 Options... -> Scan for new or updated VST3 plug-ins,開啟選擇視窗 ![](https://i.imgur.com/aK6VGUb.png) 3. 點選左下角「+」鍵,選擇 專案資料夾 -> Builds -> ... -> VST3 資料夾,最後點選 Scan ![](https://i.imgur.com/UVqRNcI.png) 4. 如此一來,即可在右鍵選單中的 yourcompany 內找到欲測試的 plugin ![](https://i.imgur.com/Qh9n7VS.png) ### 測試後發現之問題 以下問題目前尚未知曉發生原因 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://i.imgur.com/zVqSm0I.png) > 圖源:[維基百科](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 作為波形選擇器,並重新排版後如下圖所示 ![](https://i.imgur.com/r5uwmFP.png) * 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 時,會出現‌「炸音箱」的情形,原因暫時不得而知