標籤:

Shadertoy 網站的聲音紋理

Shadertoy 網站的聲音紋理

來自專欄 黃二少碎碎念

Shadertoy 是個分享和測試 glsl shader 的網站。Shadertoy 可以選擇聲音 channel,將當前幀的聲音信息轉變成紋理(Texture),傳入 shader 當中,從而根據聲音信息來控制圖形。如這些例子。使用 Chrome 瀏覽器打開,兼容性會好一些。

  • Kaleidoscope Visualizer
  • Sound sinus wave
  • Père Truchets Psychedelic Maze

正弦波

在 channel 中選擇聲音時,有些跳動的柱狀圖,猜測是頻譜信息。紅色應該是低頻,藍色應該是高頻。

頻譜

那接下來很自然的問題就是,

  1. Shadertoy 是怎麼獲取到這些頻譜信息?
  2. 這些頻譜信息怎麼轉變為紋理,傳遞到 shader 當中?

打開 Chrome 調試,Shadertoy 使用了 h5 的 AudioContext,創建了 AnalyserNode,另外也創建了一個 512 * 2 的紋理。

texture.globject = renderer.CreateTexture(renderer.TEXTYPE.T2D, 512, 2, renderer.TEXFMT.C1I8, renderer.FILTER.LINEAR, renderer.TEXWRP.CLAMP, null)texture.audio.mSound.mSource = wa.createMediaElementSource( texture.audio );texture.audio.mSound.mAnalyser = wa.createAnalyser();texture.audio.mSound.mGain = wa.createGain();.....texture.audio.mSound.mFreqData = new Uint8Array( texture.audio.mSound.mAnalyser.frequencyBinCount );texture.audio.mSound.mWaveData = new Uint8Array( texture.audio.mSound.mAnalyser.frequencyBinCount );

調試發現,這裡 mAnalyser.frequencyBinCount 為 1024,也就是說 mFreqData 和 mWaveData 數組長度為 1024。

接下來,從 AnalyserNode 從 getByteFrequencyData 和 getByteTimeDomainData 獲取頻譜信息和波形(時域)信息。

inp.audio.mSound.mAnalyser.getByteFrequencyData( inp.audio.mSound.mFreqData );inp.audio.mSound.mAnalyser.getByteTimeDomainData( inp.audio.mSound.mWaveData );

最終使用頻譜信息(mFreqData)更新了聲音紋理的第 0 行,使用波形信息更新了第 1 行。

this.mRenderer.UpdateTexture(inp.globject, 0, 0, 512, 1, inp.audio.mSound.mFreqData);this.mRenderer.UpdateTexture(inp.globject, 0, 1, 512, 1, inp.audio.mSound.mWaveData);

UpdateTexture 最後使用了 WebGL 的 texSubImage2D。

在這裡還殘留一個疑問,聲音紋理的大小是 512 * 2,每一行應該是 512 像素,1 byte 的灰度圖。但調試知道 mFreqData 和 mWaveData 是 1024 byte 的數組。長度為 1024 byte 的數組,是如何更新 512 像素的灰度圖的呢?這裡需查查 texSubImage2D 的語意,可能是採樣縮放,也可能是直接裁掉後面的數據。

PS: texSubImage2D 和 texImage2D 的行為,假如數組過長,是直接裁掉後面的數據。而假如數組不夠長,會創建失敗,報錯 ArrayBufferView not big enough for request。

------------------------------

Shadertoy 的聲音紋理是 512 * 2 的灰度圖,每像素有 8 位,其中分為兩行,第 0 行表示頻譜信息,第 1 行表示波形信息。紋理傳入到 shader 後,坐標會轉換成 [0, 1] 之間。當使用聲音紋理時,y 坐標為 [0, 0.5] 的數值,就是在取頻譜;當 y 坐標為 [0.5, 1] 時,就是在取波形;0.5 在中間,或者是不固定的。Shadertoy 中的代碼,取頻譜很多 y 都是 0 或者 0.25。

至於具體的數值範圍,沒有深究了,可以查查 AnalyserNode 的 getByteFrequencyData 和 getByteTimeDomainData 的具體語意。

實際上 Shadertoy 上也有個例子 Input - Sound, 將頻譜和波形數據都可視化了。

從聲音中分析頻譜,用到了快速傅里葉演算法(FFT),我不懂這個演算法。另外 Shadertoy 在處理麥克風(mic)是跑的是這段代碼, 猜測跟 FFT 演算法有關(甚至就是這個演算法),但我不確定。

var num = inp.mFreqData.length;for( var j=0; j<num; j++ ){ var x = j / num; var f = (0.75 + 0.25*Math.sin( 10.0*j + 13.0*time )) * Math.exp( -3.0*x ); if( j<3 ) f = Math.pow( 0.50 + 0.5*Math.sin( 6.2831*time ), 4.0 ) * (1.0-j/3.0); inp.mFreqData[j] = Math.floor(255.0*f) | 0;}var num = inp.mFreqData.length;for( var j=0; j<num; j++ ){ var f = 0.5 + 0.15*Math.sin( 17.0*time + 10.0*6.2831*j/num ) * Math.sin( 23.0*time + 1.9*j/num ); inp.mWaveData[j] = Math.floor(255.0*f) | 0;}

PS:

家裡網路中打開 Shadertoy 很慢,後調試發現 connect.soundcloud.com/ 這個文件等很長時間超時了,soundcloud 被牆了。預先下載 sdk-3.1.2.js 到本地(讓別人幫忙下載),使用 Chrome 瀏覽器插件 ReRes 將這個被牆的這個鏈接引導到本地文件。這樣不用翻牆也可以打開 Shadertoy,雖然有某些資源載入不了,但影響不大。


推薦閱讀:

如何選擇正確的走刀方式的呢?你都用了什麼技巧?一起學習吧
吐槽一篇講 Flutter/Dart 的文章
【futaba】三、沒有類型是什麼類型
如何精通C語言

TAG:編程 |