javascript - surfer - ¿Cómo funcionan los shaders de audio de Shadertoy?
shadertoy tutorial (1)
Para empezar, realmente no pude encontrar una comunidad apropiada para publicar esta pregunta, así que elegí esta. Me preguntaba cómo funcionaban los sombreadores de audio de la popular herramienta de sombreado basada en webGL, porque cuando, aunque obviamente había oído hablar de los sombreadores GLSL "normales", escuché por primera vez los sombreadores para generar audio de manera procesal, me sorprendió. ¿Alguna pista?
Básicamente son una función que, dado el time
devuelve 2 valores para un solo de audio (canal izquierdo y derecho). Los valores van de -1 a 1.
pegar en este shader y tal vez lo conseguirás
vec2 mainSound( float time )
{
return vec2( sin(time * 1000.0), sin(time * 1000.0) );
}
Puedes ver un ejemplo más vivo de un estilo similar de hacer sonidos aquí .
Puedes imaginarlo así.
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);
}
Ahora pasa audioData a la Web Audio API
Para estéreo podría ser
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];
}
Realmente no hay una buena razón para que estén en WebGL (suponiendo que lo estén). En WebGL, los utilizarías para generar datos en una textura adjunta a un framebuffer. Luego, los datos que generan deberían ser copiados de nuevo desde la GPU a la memoria principal usando gl.readPixels
y luego pasados a la API de Web Audio, que sería lento y, al menos en WebGL, bloquearía el procesamiento ya que no hay manera de leer de forma asíncrona. datos de vuelta en WebGL. Además, no puedes leer fácilmente los datos flotantes en WebGL. Por supuesto, si shadertoy realmente está utilizando WebGL, podría volver a escribir el sombreador de audio para codificar los datos en texturas RGBA de 8 bits y luego convertirlos de nuevo a flotadores en JavaScript. Aún más razón para no usar WebGL para esto. La razón principal para usar WebGL es que lo hace simétrico. Todos los shaders usando el mismo idioma.
El ejemplo de bytebeat vinculado anteriormente se ejecuta completamente en JavaScript. El valor predeterminado es bytebeat, lo que significa que el valor que se espera que la función devuelva es de 0 a 255 int sin signo, pero hay una configuración para floatbeat en cuyo caso se espera un valor de -1 a 1 al igual que los shaders de shadertoy.
Actualizar
Así que revisé Shadertoy y está usando sombreadores WebGL y está codificando los valores en texturas de 8 bits
Aquí hay un sombreador real (usé el editor de sombreadores de cromo para mirar fácilmente el sombreador).
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);
}
Esto indica que una ventaja de usar WebGL en este caso particular es que obtiene todas las mismas entradas al sombreador de audio que los sombreadores de fragmentos (ya que es un sombreado de fragmentos). Eso significa que, por ejemplo, el sombreador de audio podría hacer referencia a hasta 4 texturas
En JavaScript, a continuación, gl.readPixels
la textura con gl.readPixels
luego convertirías la muestra en flotadores con algo como
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;
}
Además, aunque dije que no era una buena idea, asumí que shadertoy estaba constantemente llamando al sombreador de audio y, por lo tanto, el problema que planteé sobre el procesamiento del bloqueo sería cierto, pero ... N segundos de audio usando el shader cuando presionas play, donde N es aparentemente 60 segundos. Entonces, no hay bloqueo, pero de nuevo el sonido dura solo 60 segundos.