數位噪音生成器的運作原理

基礎:偽隨機數生成

在我為 WhiteNoise.top 建構噪音引擎的工作中,我花了數百小時最佳化將數學隨機性轉化為逼真音訊的管線。每個數位噪音生成器都始於相同的基本組件:偽隨機數生成器,簡稱 PRNG。PRNG 是一種演算法,產生根據統計測試看似隨機的確定性數字序列。來自硬體熵源的真正隨機性對即時音訊來說太慢且不可預測,因此 PRNG 為所有數位噪音生成提供了實用的基礎。

最基本的適合音訊工作的 PRNG 是線性同餘生成器(LCG),它將每個值計算為前一個值的線性函數,對一個大常數取模。雖然速度很快,LCG 有已知的弱點:它在連續樣本之間表現出相關性,可能在噪音信號中產生可聽到的偽影。在我早期的原型中,我使用了一個簡單的 LCG,透過高品質耳機聆聽時能聽到輸出中微弱的週期性模式。這些模式在 FFT 分析中以窄頻譜峰值的形式出現,證實 PRNG 的統計弱點正在洩漏到音訊領域。

對於生產品質的噪音,我改用了 xorshift128+ 演算法,這是大多數 JavaScript 引擎內部用於 Math.random() 的 PRNG。它的週期為 2 的 128 次方減 1,具有優秀的統計特性和可忽略的計算成本。當我對其輸出運行 TestU01 隨機性測試套件時,它通過了所有的 SmallCrush 和 Crush 測試組。結果是一個沒有可聽偽影的噪音信號,在整個可聽範圍內經驗證頻譜平坦度在 0.3 dB 以內。

從隨機數到音訊樣本

PRNG 產生數字,但音訊系統需要在特定振幅和取樣率下的波形樣本。轉換過程涉及縮放、分佈塑形和緩衝區管理。在我的實作中,PRNG 輸出無符號 32 位元整數,然後我將其正規化為負一到正一範圍內的浮點值。這是 Web Audio API 和大多數其他音訊框架中數位音訊的標準振幅範圍。

隨機值的分佈對噪音的特性很重要。均勻分佈,即範圍內所有值出現的機率相等,產生的噪音振幅分佈與高斯分佈(類比電路中熱噪音的特徵)略有不同。在實務中,差異很微妙:均勻白噪音的峰值因數約為 4.8 dB,而高斯白噪音的峰值因數理論上是無限的,但在實務中對於典型的緩衝區長度約為 12 dB。對於大多數應用,均勻分佈完全可以接受且計算更簡單。

當我需要高斯分佈噪音用於要求更高的應用時,我使用 Box-Muller 轉換,它將一對均勻分佈的隨機數轉換為一對高斯分佈的值。計算開銷適中,大約使每個樣本的成本翻倍,但結果是更接近類比噪音源振幅統計特性的噪音信號。在聽力測試中,均勻白噪音和高斯白噪音之間的差異幾乎聽不出來,但高斯變體在某些振幅統計很重要的信號處理應用中表現更好。

頻譜塑形:從白色變為其他顏色

白噪音是所有其他噪音顏色創建的原料。轉換透過數位濾波來完成,即對白噪音頻譜施加頻率相關的增益曲線。在我的噪音引擎中,我使用 IIR(無限脈衝響應)和 FIR(有限脈衝響應)濾波器來實現頻譜塑形,每種都有不同的優勢。

對於粉紅噪音(每八度衰減三分貝的斜率),我使用 Voss-McCartney 演算法作為主要方法。這個演算法維護多個以不同速率更新的獨立隨機數生成器:一個每個樣本更新一次,一個每兩個樣本,一個每四個樣本,依此類推。輸出被加總以產生頻譜近似理想粉紅噪音斜率的信號。在我的實作中,我使用 16 個八度層,這在 1 Hz 以下到 20 kHz 以上提供了精確的頻譜塑形。相對於理想斜率的誤差在整個可聽範圍內小於 0.5 dB。

對於棕噪音(每八度衰減六分貝的斜率),我使用簡單的積分:每個輸出樣本是前一個輸出和一個新白噪音樣本的加總,乘以一個小係數。數學上,這是一個一階 IIR 低通濾波器,其極點在單位圓上非常接近一。這種方法的挑戰是直流漂移:累加和可能隨時間遠離零點,最終超過削波閾值。我透過添加一個在 5 Hz 的非常溫和的一階高通濾波器來解決這個問題,它限制了漂移而不會可聽地影響 20 Hz 以上的頻譜。

對於進階使用者要求的自訂頻譜形狀,我使用由串聯的二階雙二次濾波器段組成的參數等化器。每個段實現一個標準濾波器類型(峰值、低頻架式、高頻架式或陷波),具有使用者可調的頻率、增益和頻寬參數。透過串聯四到六個段,我可以近似幾乎任何使用者可能想要的平滑頻譜形狀,從溫和的傾斜到複雜的多頻帶輪廓。

使用 Web Audio API 的即時生成

Web Audio API 在所有現代瀏覽器中都可用,提供了無需任何伺服器端處理即可即時生成和播放噪音的基礎架構。在我 WhiteNoise.top 的實作中,我使用了三個主要的 Web Audio API 組件:AudioContext、ScriptProcessorNode(或其現代替代品 AudioWorkletNode)和 BiquadFilterNode。

AudioWorkletNode 是核心生成發生的地方。我註冊了一個自訂的 AudioWorkletProcessor,它在與瀏覽器主執行緒分開的執行緒上運行,確保 UI 互動不會導致音訊中斷。處理器的 process() 方法會被反覆呼叫並提供一個輸出緩衝區,在 44.1 kHz 時通常每次呼叫 128 個樣本。在這個方法內部,我使用 PRNG 生成白噪音樣本,應用任何頻譜塑形,並將結果寫入輸出緩衝區。在現代硬體上,整個管線每個緩衝區大約在 0.01 毫秒內完成,遠在約三毫秒的無中斷播放期限內。

緩衝區管理對於避免可聽偽影至關重要。如果處理器填充緩衝區的時間太長,音訊輸出就會欠載,產生喀嚓聲或爆音。在我的壓力測試中,我透過向主執行緒添加計算負載(如繁重的 DOM 操作或大型 JSON 解析)來推動系統,並驗證音訊工作執行緒繼續按時交付緩衝區。AudioWorklet API 提供的執行緒隔離對於這種穩健性至關重要;舊的 ScriptProcessorNode 在主執行緒上運行,在繁重的 JavaScript 執行期間容易出現中斷。

當所需的濾波器是標準類型時,我也使用 Web Audio API 內建的 BiquadFilterNode 進行頻譜塑形。這些節點以最佳化的原生程式碼實作,使它們比等效的 JavaScript 實作快得多。對於較簡單配置中的粉紅噪音生成,我串聯多個配置為低通濾波器的 BiquadFilterNode,使用精心選擇的頻率和 Q 值來近似理想的每八度衰減三分貝的斜率。

最佳化與實務考量

效能最佳化是即時音訊生成中的持續關注點。在我的開發過程中,我實作了幾種在不犧牲品質的情況下最大化效率的技術。第一種是預計算:對於不會動態變化的噪音顏色,我在初始化期間生成一個大的噪音緩衝區(通常為 44.1 kHz 下的 10 秒,即 441,000 個樣本),並在播放時循環它。為了防止循環被聽出來,我在循環邊界使用交叉淡化,使用升餘弦窗口將緩衝區的最後 4,096 個樣本與前 4,096 個樣本混合。得到的循環在感知上是無縫的。

第二種最佳化是 JavaScript 中的 SIMD 風格處理。雖然 JavaScript 在 AudioWorklet 上下文中不提供明確的 SIMD 指令,但我將內部迴圈結構化為一次處理四個樣本,允許 JavaScript 引擎的 JIT 編譯器應用自動向量化。在我的基準測試中,這種方法比一次處理一個樣本提速 15% 到 25%,取決於瀏覽器引擎。

第三個考量是記憶體管理。在音訊處理期間分配和釋放記憶體可能觸發垃圾回收暫停,導致可聽到的中斷。我在初始化期間預先分配所有緩衝區並在整個工作階段中重複使用它們。在 process() 方法內部不會建立任何物件,所有中間值都儲存在預先分配的型別陣列中。這種嚴格做法完全消除了與垃圾回收相關的音訊偽影。

功耗是另一個實務考量,尤其是對行動裝置。在我對智慧型手機的測試中,我發現簡單噪音生成器的 CPU 負載可以忽略不計,通常不到百分之一。然而,保持音訊輸出活躍會阻止裝置進入深度睡眠狀態,這在延長使用期間可能會耗盡電池。我透過提供計時器功能來緩解這個問題,該功能在使用者指定的持續時間後停止生成器,並使用 Page Visibility API 在瀏覽器標籤不在前景時暫停生成。

驗證生成器輸出品質

噪音生成器的品質取決於其輸出品質,而品質必須透過客觀測量來驗證。在我的品質保證流程中,我在將每個生成器配置發佈給使用者之前,都會透過一套自動化測試來運行。第一個測試是頻譜平坦度:我捕捉 60 秒的白噪音樣本,計算平均 FFT 幅度,並驗證沒有頻率區間偏離平均值超過 1 dB。對於塑形噪音,我將測量的頻譜與目標曲線進行比較,並驗證偏差小於 0.5 dB。

第二個測試是振幅分佈。我計算樣本值的直方圖,並使用 Kolmogorov-Smirnov 檢定將其與預期分佈(無論是均勻的還是高斯的)進行比較。p 值高於 0.05 表示分佈在 95% 的信心水準下與預期形狀匹配。

第三個測試是週期性偵測。我計算 10 秒樣本的自相關函數,並檢查沒有非零延遲的相關係數超過該長度隨機過程的理論最大值。PRNG 中的週期性模式即使不可聽到,也會在自相關函數中以峰值的形式出現,並指示生成器中可能在信號處理應用中造成問題的缺陷。

最後,我使用耳機進行主觀聽力測試,檢查喀嚓聲、爆音、音調偽影,以及在延長播放期間聲級或音色的變化。自動化測試能捕捉大多數問題,但人耳仍然是音訊品質的最終評判者,我堅持在每個生成器變體上線之前親自聆聽它。

參考資料

常見問題

數位噪音生成器能產生真正隨機的輸出嗎?

不能。數位噪音生成器使用產生確定性序列的偽隨機數生成器(PRNG)。然而,設計良好的 PRNG 產生的輸出在所有實際音訊應用中與真正的隨機性在統計上無法區分。

為什麼 Web Audio API 使用 AudioWorklet 而不是 ScriptProcessorNode?

AudioWorklet 在與瀏覽器主執行緒分開的執行緒上運行,防止 UI 操作導致音訊中斷。ScriptProcessorNode 在主執行緒上運行,在繁重的 JavaScript 執行期間容易出現丟幀。ScriptProcessorNode 現在已被棄用。

什麼導致噪音生成器中的喀嚓聲或爆音?

喀嚓聲和爆音通常由緩衝區欠載引起,即生成器無法及時填充輸出緩衝區,或由 JavaScript 中的垃圾回收暫停引起。正確的緩衝區管理和記憶體預先分配可消除這些偽影。

噪音生成器使用多少 CPU?

簡單的噪音生成器在現代硬體上通常使用不到百分之一的 CPU。主要的效能問題是維持一致的計時以防止音訊中斷,而非整體 CPU 負載。

可以循環播放噪音樣本而不是即時生成嗎?

可以,但你需要在循環邊界應用交叉淡化以防止可聽到的喀嚓聲。在 44.1 kHz 下約 4,096 個樣本的升餘弦交叉淡化可以創建感知上無縫的循環。

Leo Chen

Leo Chen 是一位工具開發者與音訊愛好者,專注於打造實用的線上聲音與效率工具。