まず、この質問を投稿するのに適切なコミュニティが本当に見つからなかったので、これを選びました。人気のあるwebGLベースのシェーダーツールのオーディオシェーダーがどのように機能するのか疑問に思っていました。「通常の」GLSLシェーダーについては明らかに聞いたことがありますが、手続き型オーディオを生成するためのシェーダーについて最初に聞いたとき、私は驚きました。手がかりはありますか?
これらは基本的に、time
を指定すると、オーディオシングル(左チャネルと右チャネル)に対して2つの値を返す関数です。値は-1から1になります。
このシェーダーに貼り付けると、多分あなたはそれを手に入れるでしょう
vec2 mainSound( float time )
{
return vec2( sin(time * 1000.0), sin(time * 1000.0) );
}
ここで音を出す同様のスタイル のよりライブな例を見ることができます。
あなたはそれをこのように想像することができます
function generateAudioSignal(time) {
return Math.sin(time * 4000); // generate a 4khz sign wave.
}
var audioData = new Float32Array(44100 * 4); // 4 seconds of audio at 44.1khz
for (var sample = 0; sample < audioData.length; ++sample) {
var time = sample / 44100;
audioData[sample] = generateAudioSignal(time);
}
次に、audioDataをWeb AudioAPIに渡します
ステレオの場合は
function generateStereoAudioSignal(time) {
return [Math.sin(time * 4000), Math.sin(time * 4000)]; // generate a 4khz stereo sign wave.
}
var audioData = new Float32Array(44100 * 4 * 2); // 4 seconds of stereo audio at 44.1khz
for (var sample = 0; sample < audioData.length; sample += 2) {
var time = sample / 44100 / 2;
var stereoData = generateAudioSignal(time);
audioData[sample + 0] = stereoData[0];
audioData[sample + 1] = stereoData[1];
}
それらがWebGLに含まれる正当な理由は実際にはありません(それらが含まれていると仮定します)。 WebGLでは、これらを使用して、フレームバッファーにアタッチされたテクスチャにデータを生成します。次に、生成されたデータをgl.readPixels
を使用してGPUからメインメモリにコピーして戻し、Web Audio APIに渡す必要があります。これは低速であり、少なくともWebGLでは、処理をブロックする方法がありません。非同期でデータをWebGLに読み戻します。その上、WebGLでfloatデータを簡単に読み戻すことはできません。もちろん、shadertoyが実際にWebGLを使用している場合は、オーディオシェーダーを書き直して、データを8ビットRGBAテクスチャにエンコードし、JavaScriptでfloatに戻すことができます。このためにWebGLを使用しないさらに多くの理由。 WebGLを使用する主な理由は、WebGLを対称にするだけです。同じ言語を使用するすべてのシェーダー。
上にリンクされているバイトビートの例は、JavaScriptで完全に実行されています。デフォルトはbytebeatで、関数が返すと予想される値は0〜255 unsigned intですが、の設定があります。 )floatbeatこの場合、shadertoyのシェーダーと同様に、-1から1までの値が必要です。
そこで、Shadertoyをチェックしました。これは、WebGLシェーダーを使用しており、値を8ビットテクスチャにエンコードしています。
これが実際のシェーダーです(私は クロームシェーダーエディター を使用してシェーダーを簡単に確認しました)。
precision highp float;
uniform float iChannelTime[4];
uniform float iBlockOffset;
uniform vec4 iDate;
uniform float iSampleRate;
uniform vec3 iChannelResolution[4];
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
vec2 mainSound( float time )
{
return vec2( sin(time * 1000.0), sin(time * 1000.0) );
}
void main() {
// compute time `t` based on the pixel we're about to write
// the 512.0 means the texture is 512 pixels across so it's
// using a 2 dimensional texture, 512 samples per row
float t = iBlockOffset + ((gl_FragCoord.x-0.5) + (gl_FragCoord.y-0.5)*512.0)/iSampleRate;
// Get the 2 values for left and right channels
vec2 y = mainSound( t );
// convert them from -1 to 1 to 0 to 65536
vec2 v = floor((0.5+0.5*y)*65536.0);
// separate them into low and high bytes
vec2 vl = mod(v,256.0)/255.0;
vec2 vh = floor(v/256.0)/255.0;
// write them out where
// RED = channel 0 low byte
// GREEN = channel 0 high byte
// BLUE = channel 1 low byte
// ALPHA = channel 2 high byte
gl_FragColor = vec4(vl.x,vh.x,vl.y,vh.y);
}
これは、この特定のケースでWebGLを使用する利点の1つは、オーディオシェーダーにフラグメントシェーダーと同じ入力をすべて取得できることです(フラグメントシェーダーであるため)。つまり、たとえば、オーディオシェーダーは最大4つのテクスチャを参照できます。
JavaScriptでは、gl.readPixels
を使用してテクスチャを読み取り、サンプルを次のようなフロートに変換し直します。
var pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
for (var sample = 0; sample < numSamples; ++sample) {
var offset = sample * 4; // RGBA
audioData[sample * 2 ] = backToFloat(pixels[offset + 0], pixels[offset + 1]);
audioData[sample * 2 + 1] = backToFloat(pixels[offset + 2], pixels[offset + 3]);
}
float backToFloat(low, high) {
// convert back to 0 to 65536
var value = low + high * 256;
// convert from 0 to 65536 to -1 to 1
return value / 32768 - 1;
}
また、上で言ったが、シェーダーが常にオーディオシェーダーを呼び出していると思ったのは良い考えではなかったので、ブロック処理に関して私が提起した問題は本当だろうが、...どうやらシェーダーは事前に生成されただけだNが明らかに60秒である再生を押したときに、シェーダーを使用したN秒のオーディオ。したがって、ブロッキングはありませんが、サウンドは60秒しか持続しません。