The Code Therapy

Sound Shader Time

A simple audio visualizer to demonstrate GPU audio generation with Sound Shader, combined with the audiotime uniform to enable audio and graphics synchronization.

Created by marcogomez on Sat, 06 Nov 2021 20:09:32 GMT.


// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm3Texture;
uniform vec2 resolution;
uniform float time;
uniform float audiotime;

// All the fft uniforms are obtained from the AnalyserNode on the browser Audio API,
// and the Fast Fourier transform is obtained from 512 bins (fftSize).
// On PRGM1 you can find the fftTexture sampler2D uniform, created from a 512x1 px
// grayscale pseudo-texture (data texture) drawn in real-time using the AnalyserNode data.

uniform float fft; //          The average of all 512 values
uniform float fftLow; //       The average of the first (lower) third of the fft values Array
uniform float fftMid; //       The average of the second (mid) third of the fft values Array
uniform float fftHigh; //      The average of the third (high) third of the fft values Array
uniform float fftLowerHalf; // The average of the lower half of the fft values Array
uniform float fftUpperHalf; // The average of the upper half of the fft values Array

// The following values are the same as above, but normalized (0 to 1) according to...
// the ongoing registration of the maximum values ever achieved for each frequency range.

uniform float fftNormalized;
uniform float fftLowNormalized;
uniform float fftMidNormalized;
uniform float fftHighNormalized;
uniform float fftLowerHalfNormalized;
uniform float fftUpperHalfNormalized;

// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode

vec3 clamps(vec3 x) { return clamp(x, 0.042, 1.0); }

vec3 getColor(float val, vec2 uv, float mul) {
  return clamps(vec3(val * 1.0 - uv.y) * mul);
}

void main(void) {
  float ratio = 0.2;
  float ratioDiff = 1.0 - ratio;
  float invRatioDiff = 1.0 / ratioDiff;
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float uvy = (uv.y / ratioDiff) - ratio * invRatioDiff;
  vec4 prgm3 = texture2D(prgm3Texture, vec2(uv.x, uvy));
  if (uv.y > ratio + 0.001) {
    gl_FragColor = prgm3;
  } else if (uv.y < ratio - 0.001) {
    float huvy = uv.y / ((ratio * 2.0) - ratio);
    vec2 huv = vec2(uv.x, huvy);
    vec2 grid = fract(huv * 12.0) - 0.5;
    vec3 col = vec3(1.0, 0.8, 0.8);
    vec3 normCol = vec3(0.8, 1.0, 0.8);
    vec3 color;
    float mul = 100.0;
         if (uv.x <  1.0 / 12.0) { color = getColor(fft, huv, mul) * col; }
    else if (uv.x <  2.0 / 12.0) { color = getColor(fftLow, huv, mul) * col; }
    else if (uv.x <  3.0 / 12.0) { color = getColor(fftMid, huv, mul) * col; }
    else if (uv.x <  4.0 / 12.0) { color = getColor(fftHigh, huv, mul) * col; }
    else if (uv.x <  5.0 / 12.0) { color = getColor(fftLowerHalf, huv, mul) * col; }
    else if (uv.x <  6.0 / 12.0) { color = getColor(fftUpperHalf, huv, mul) * col; }
    else if (uv.x <  7.0 / 12.0) { color = getColor(fftNormalized, huv, mul) * normCol; }
    else if (uv.x <  8.0 / 12.0) { color = getColor(fftLowNormalized, huv, mul) * normCol; }
    else if (uv.x <  9.0 / 12.0) { color = getColor(fftMidNormalized, huv, mul) * normCol; }
    else if (uv.x < 10.0 / 12.0) { color = getColor(fftHighNormalized, huv, mul) * normCol; }
    else if (uv.x < 11.0 / 12.0) { color = getColor(fftLowerHalfNormalized, huv, mul) * normCol; }
    else if (uv.x < 12.0 / 12.0) { color = getColor(fftUpperHalfNormalized, huv, mul) * normCol; }
    if (grid.x > 0.45 || uv.x < 0.005) color = vec3(0.0);
    gl_FragColor = vec4(clamps(color), 1.0);
  }
  if (uv.y > ratio - 0.002 && uv.y < ratio + 0.002) {
    float pattern = clamp(0.5 + (sin(-time * 5.0 + uv.x * (resolution.x / 3.0)) * 0.5 + 0.5), 0.0, 1.0);
    float progress = step(uv.x * (180.0 / audiotime), 1.0);
    vec3 baseColor = vec3(0.3);
    vec3 progressColor = vec3(0.3, progress, 0.4) * pattern;
    gl_FragColor = vec4(mix(baseColor, progressColor, progress), 1.0);
  }
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D fftTexture;
uniform vec2 resolution;
uniform float time;
uniform int frame;

vec3 hash33(vec3 p3) {
  p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
  p3 += dot(p3, p3.yxz + 19.19);
  return fract((p3.xxy + p3.yxx) * p3.zyx);
}

vec3 mixColor(vec3 col1, vec3 col2, float v) {
  v = clamp(v, 0.0, 1.0);
  return clamp(col1 + v * (col2 - col1 * 1.5), 0.0, 1.0);
}

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  uv.x = (uv.x - 0.5 > 0.0) ? uv.x - 0.521 : 0.479 - uv.x;
  vec2 p = uv * 2.0 - 1.0;
  p *= vec2(resolution.x / resolution.y, 1.0);
  p.y += 0.3;
  vec3 col = vec3(0.0);
  vec3 ref = vec3(0.0);
  float nBands = 64.0;
  float i = floor((uv.x * 1.3 + 0.3) * nBands);
  float f = fract((uv.x * 1.3 + 0.3) * nBands);
  float band = i/nBands;
  band *= band * band;
  band = band * 0.995;
  band += 0.005;
  float s = texture2D(fftTexture, vec2(band, 0.25)).x;
  const int nColors = 4;
  vec3 colors[nColors];
  colors[0] = vec3(0.0, 0.0, 1.0);
  colors[1] = vec3(0.0, 1.0, 1.0);
  colors[2] = vec3(1.0, 1.0, 0.0);
  colors[3] = vec3(1.0, 0.0, 0.0);
  vec3 gradCol = colors[0];
  float n = float(nColors) - 1.0;
  for(int i = 1; i < nColors; i++) {
    gradCol = mixColor(gradCol, colors[i], (s - float(i - 1) / n) * n);
  }
  col += vec3(1.0 - smoothstep(0.0, 0.001, p.y - s * 1.25));
  col *= gradCol;
  ref += vec3(1.0 - smoothstep(0.0, -0.001, p.y + s * 1.25));
  ref *= gradCol * smoothstep(-0.5, 0.5, p.y);
  col = mix(ref, col, smoothstep(-0.005, 0.005, p.y));
  col *= smoothstep(0.125, 0.375, f);
  col *= smoothstep(0.875, 0.625, f);
  col = clamp(col, 0.0, 1.0);
  vec3 h = hash33(vec3(gl_FragCoord.xy, float(frame)));
  float dither = h.x * h.y;
  col += dither * 0.04;
  gl_FragColor = vec4(col,1.0);
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm1Texture;
uniform vec2 resolution;
uniform float time;
uniform int frame;

vec3 hash33(vec3 p3) {
  p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
  p3 += dot(p3, p3.yxz + 19.19);
  return fract((p3.xxy + p3.yxx) * p3.zyx);
}

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec4 texCol = texture2D(prgm1Texture, uv);
  vec4 col = texCol;
  float w = 0.1;
  vec2 a = vec2(uv.x - 0.5, uv.y - 0.66);
  vec2 b = a * 0.15 / float(10.0);
  vec3 h = hash33(vec3(gl_FragCoord.xy, frame));
  uv += b * h.x;
  for (float i = 1.0; i > 0.9; i-= 0.000625) {
    uv -= 0.5;
    uv *= i;
    uv += 0.5;
    col += texture2D(prgm1Texture, uv) * w * 1.5;
    w *= 0.95;
  }
  col *= 0.9;
  gl_FragColor = mix(texCol, col, 0.5);
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm2Texture;
uniform vec2 resolution;
uniform float time;
uniform float fft;
uniform float fftHighNormalized;

const float PI = acos(-1.0);
const float TAU = PI * 2.0;
const float hardscan = -16.0; // -8.0 = soft | -16.0 = medium
const float hardPix = -4.0; // -2.0 = soft | -4.0 = hard
const float maskDark = 0.5;
const float maskLight = 2.5;
const float hk = 1.0 / sqrt(3.0);

vec3 hueShift(vec3 col, float a) {
  const vec3 k = vec3(hk);
  float ca = cos(a);
  return vec3(col * ca + cross(k, col) * sin(a) + k * dot(k, col) * (1.0 - ca));
}

float toLinear(float c) {
  return (c <= 0.04045) ? c / 12.92 : pow(abs((c + 0.055) / 1.055), 2.4);
}

vec3 toLinear(vec3 c) {
  return vec3(toLinear(c.r), toLinear(c.g), toLinear(c.b));
}

float toSRGB(float c) {
  return(c < 0.0031308 ? c * 12.92 : 1.055 * pow(abs(c), 0.41666) - 0.055);
}

vec3 toSRGB(vec3 c) {
  return vec3(toSRGB(c.r), toSRGB(c.g), toSRGB(c.b));
}

vec3 fetch(vec2 pos, vec2 off, vec2 res) {
  pos = floor(pos * res + off) / res;
  if (max(abs(pos.x - 0.5), abs(pos.y - 0.5)) > 0.5) {
    return vec3(0.0);
  }
  vec3 fetch = texture2D(prgm2Texture, pos.xy, -16.0).xyz;
  fetch = hueShift(fetch, 1.5 * fftHighNormalized + time * 0.75);
  return toLinear(mix(fetch, fetch * fetch, 0.5));
}

vec2 dist(vec2 pos, vec2 res) {
  pos = pos * res;
  return -((pos - floor(pos)) - vec2(0.5));
}

float gauss(float pos, float scale) {
  return exp2(scale * pos * pos);
}

vec3 horz3(vec2 pos, float off, vec2 res) {
  vec3 b = fetch(pos, vec2(-1.0, off), res);
  vec3 c = fetch(pos, vec2(+0.0, off), res);
  vec3 d = fetch(pos, vec2(+1.0, off), res);
  float dst = dist(pos, res).x;
  float scale = hardPix;
  float wb = gauss(dst - 1.0, scale);
  float wc = gauss(dst + 0.0, scale);
  float wd = gauss(dst + 1.0, scale);
  return (b * wb + c * wc + d * wd) / (wb + wc + wd);
}

vec3 horz5(vec2 pos, float off, vec2 res) {
  vec3 a = fetch(pos, vec2(-2.0, off), res);
  vec3 b = fetch(pos, vec2(-1.0, off), res);
  vec3 c = fetch(pos, vec2(+0.0, off), res);
  vec3 d = fetch(pos, vec2(+1.0, off), res);
  vec3 e = fetch(pos, vec2(+2.0, off), res);
  float dst = dist(pos, res).x;
  float scale = hardPix;
  float wa = gauss(dst - 2.0, scale);
  float wb = gauss(dst - 1.0, scale);
  float wc = gauss(dst + 0.0, scale);
  float wd = gauss(dst + 1.0, scale);
  float we = gauss(dst + 2.0, scale);
  return (a * wa + b * wb + c * wc + d * wd + e * we) / (wa + wb + wc + wd + we);
}

float scan(vec2 pos, float off, vec2 res) {
  float dst = dist(pos, res).y;
  return gauss(dst + off, hardscan);
}

vec3 tri(vec2 pos, vec2 res) {
  vec3 a = horz3(pos, -1.0, res);
  vec3 b = horz5(pos, +0.0, res);
  vec3 c = horz3(pos, +1.0, res);
  float wa = scan(pos, -1.0, res);
  float wb = scan(pos, +0.0, res);
  float wc = scan(pos, +1.0, res);
  return a * wa + b * wb + c * wc;
}

vec3 mask(vec2 pos) {
  pos.x += pos.y * 3.0;
  vec3 m = vec3(maskDark, maskDark, maskDark);
  pos.x = fract(pos.x / 6.0);
  if (pos.x < 0.333) {
    m.r = maskLight;
  } else if (pos.x < 0.666) {
    m.g = maskLight;
  } else {
    m.b = maskLight;
  }
  return m;
}

float bar(float pos, float bar) {
  pos -= bar;
  return pos * pos < 4.0 ? 0.0 : 1.0;
}

vec2 warp(vec2 uv, vec2 warpAmount) {
  uv = uv * 2.0 - 1.0;
  vec2 offset = abs(uv.yx) / vec2(warpAmount.x, warpAmount.y);
  uv = uv + uv * offset * offset;
  uv = uv * 0.5 + 0.5;
  return uv;
}

void drawVig(inout vec3 color, vec2 uv) {
  float vignette = uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y);
  vignette = clamp(pow(abs(16.0 * vignette), 0.1), 0.0, 1.0);
  color *= vignette;
}

void main(void) {
  vec2 warpAmount = vec2(7.0, 5.0);
  vec2 res = vec2(resolution.x / 4.0, resolution.y / 3.0);
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float vig = (0.0 + 1.0 * 21.0 * uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y));
  float v = exp(-0.01 * length(uv)) * vig;
  vec2 pos = mix(uv, warp(uv, warpAmount), 0.75);
  vec4 color = vec4(tri(pos, res) * mask(gl_FragCoord.xy), 1.0);
  color.xyz = toSRGB(color.xyz * 2.0);
  color = mix(color, color * v, 0.7);
  gl_FragColor = vec4(color.xyz, 1.0);
}











// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

// mandatory declaration for the audio buffer generation
// it will determine the audio size in seconds.
#define duration 180 // duration of the song in seconds
// you can also use it in your main function, as I did in
// this example, to have a nice time-based fadeOut effect =)

uniform vec2 resolution;
uniform float sampleRate;
uniform float blockOffset;

const float PI = acos(-1.0);
const float TAU = PI * 2.0;
float tempo = 2.125;

float hashA(float x) {
  vec2 n = vec2(x * 0.99, x * 1.01);
  return fract(52.982919 * fract(dot(vec2(0.06711, 0.00584), n)));
}

float hashB(float x) {
  vec2 n = vec2(x * 1.01, x * 0.99);
  n = fract(n * vec2(5.3987, 5.4421));
  n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
  float xy = n.x * n.y;
  return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
}

float rnd(float x, float mn, float mx) {
  return mn + floor((mx - mn) * hashA(floor(x)) * hashB(floor(x * 2.0)));
}

float nse(float x) {
  float y = floor(x);
  x -= y;
  x = x * x * (3.0 - 2.0 * x);
  return mix(hashA(y), hashA(y + 1.0), x) - 0.5;
}

vec2 voice1(float t, float f) {
  vec2 v;
  v.x = (
    sin(t * f * TAU + sin(t * f * 80.0) * exp(t * -120.0)) * min(t * 400.0, 1.0) * exp(t * -2.0) * 0.25
  );
  v.y = (
    cos(t * f * TAU + sin(t * f * 80.0) * exp(t * -50.0)) * min(t * 800.0, 1.0) * exp(t * -5.0) * 0.5
  );
  return v;
}

float voice2(float t, float f, float fmt) {
  float x = fract(t * f) / f;
  return (
    (sin(x * 6.0 * fmt) * 0.4 + sin(x * 12.0 * fmt) + sin(x * 26.0 * fmt) * 0.2) *
    min(x * 1000.0, 1.0) * exp(x * -200.0)
  );
}

vec2 voice3(float t, float f) {
  vec2 v = vec2(0.0, 0.0);
  float fmt = 300.0 * exp2(sin(t * 0.1));
  for (int i = 0; i < 16; ++i) {
    float h = float(i);
    float m = voice2(t + h / 3.0, f + pow(abs(2.01), (h - 8.0) * 0.2), fmt);
    float pan = hashA(h);
    v.x += m * pan;
    v.y += m * (1.0 - pan);
  }
  return v * 0.1;
}

vec2 voice4(float t, float f) {
  vec2 v = vec2(0.0, 0.0);
  for (int i = 0; i < 8; ++i) {
    float h = float(i) * 2.0 + 1.0;
    float x = h;
    float m = sin(t * f * TAU * x) * (1.0 + nse(h * 41.0 + t * 10.0)) / h;
    float pan = hashA(h);
    v.x += m * pan;
    v.y += m * (1.0 - pan);
  }
  return v * 0.25;
}

vec2 voice5(float t, float f) {
  vec2 v = vec2(0.0, 0.0);
  float fmt = 12.5 * exp2(sin(t * 2.0));
  for (int i = 0; i < 4; ++i) {
    float h = float(i);
    float m = voice2(t + h / 1.5, f + pow(abs(2.01), (h - 8.0) * 0.2), fmt);
    float pan = hashA(h);
    v.x += m * pan;
    v.y += m * (1.0 - pan);
  }
  return v * 0.2;
}

float scale(float x) {
  x /= 5.0;
  float y = floor(x);
  x = fract(x);
  x *= 5.0;
  return (
    step(1.0, x) * 2.0 +
    step(2.0, x) * 3.0 +
    step(3.0, x) * 2.0 +
    step(4.0, x) * 5.0 +
    y * 12.0
  );
}

vec2 choir(float time, float n) {
  #define nt(n) pow(abs(2.0), (n + 12.0) / 12.0)
  #define ss(f) smoothstep(0.0, 0.1, f)
  vec2 v = voice3(time, 140.0 * pow(abs(2.0), n / 12.0));
  v += voice4(-time, 140.0 * pow(abs(2.0), n / 12.0 - 1.0));
  float rate = tempo * 0.25;
  if (time <= 82.0 / tempo) {
    float fadeIn = min(1.0, time * 0.125 * 0.75);
    v += voice1(
      fract(time * rate) / rate, 140.0 * 2.0 *
      pow(abs(2.0), scale(rnd(time * rate, 0.0, 10.0) * 4.0) / 12.0)
    ) * 0.25 * fadeIn * ss(nse(time * 0.1));
    v += voice5(time, 280.0 * pow(abs(2.0), n / 12.0)) * ss(nse(time * 0.1)) * 0.25;
    v += voice5(time, 140.0 * pow(abs(2.0), n / 12.0)) * ss(nse(time * 0.1)) * 0.50;
    v += voice5(time, 035.0 * pow(abs(2.0), n / 12.0)) * ss(nse(time * 0.1)) * 1.50;
  }
  if (time > 128.0 / tempo) {
    v += voice3(time, 140.0 * 5.0 * nt(n)) * 0.2 * ss(nse(time * 0.44 + 10.0));
    v += voice4(time, 140.0 * 4.0 * nt(n)) * 0.2 * ss(nse(time * 0.33));
    v += voice3(time, 140.0 * 3.0 * nt(n)) * 0.2 * ss(nse(time * 0.12));
    v += voice4(time, 140.0 * 5.0 * nt(n)) * 0.2 * ss(nse(time * 0.32 + 4.0));
  }
  return v;
}

vec2 snare(float t) {
  if (t < 0.0) { return vec2(0.0); }
  t *= 4.0;
  float env = exp(t * -10.0) + exp(t * -1.0) * 0.07;
  vec2 w = vec2(hashB(t), hashB(t + 0.1)) * env * 0.4;
  w += sin(t * 200.0) * exp(t * -10.0) * min(1.0, t * 2500.0) * 0.1;
  vec2 s = (
    w *
    (
      max(exp(-1.0 * fract(t * 20.0)), min(t * 9.0, 1.0)) +
      3.0 * clamp(1.0 - abs(t - 0.4) * 10.0, 0.0, 1.0)
    )
  );
  return s;
}

vec2 eMix(float time) {
  vec2 v = vec2(0.0);
  float x = mod(time, 8.0 / tempo);
  v += vec2(0.5) * sin(pow(abs(x), 0.9) * 300.0) * min(1.0, x * 200.0) * exp(x * -1.0) * 1.2;
  v += vec2(0.5) * sin(pow(abs(x), 0.1) * 300.0) * min(1.0, x * 200.0) * exp(x * -5.0) * 0.7;
  float rate = tempo * 2.0;
  if(time > 32.0 / tempo) {
    v += voice1(fract(time * rate) / rate, 140.0 * 2.0 * pow(2.0, scale(rnd(time * rate, 0.0, 10.0)) / 12.0)) * 0.4;
  }
  return v;
}

vec2 echo(float t) {
  vec2 s = vec2(0.0);
  float k = 3.0 / 4.0 / tempo;
  float a = 1.0;
  float damp = 0.5;
  vec2 sep = vec2(0.3, 1.0);
  s += eMix(t); a *= damp; sep = sep.yx;
  s += eMix(t - k).yx * a * sep; a *= damp; sep = sep.yx;
  s += eMix(t - k * 2.0) * a * sep; a *= damp; sep = sep.yx;
  s += eMix(t - k * 3.0).yx * a * sep;
  return s;
}

vec2 mainSound(float time) {
  vec2 v = echo(time);
  float x;
  x = mod(time, 32.0 / tempo);
  v += choir(time, -5.0) * clamp(x * tempo * 0.5, 0.0, 1.0) * smoothstep(16.0 + 4.0, 15.0, x * tempo);
  v += choir(time, -7.0) * (clamp((x * tempo - 16.0) * 0.5, 0.0, 1.0) * smoothstep(32.0 + 4.0, 31.0, x * tempo + 1.0) + smoothstep(5.0, 0.0, x * tempo));
  if(time >= 128.0 / tempo) {
    v += snare(mod(time - 1.0 / tempo, 2.0 / tempo));
    x = mod(time, 16.0 / tempo);
    v += snare(x - tempo * (7.0 + 16.0) / 16.0);
  }
  vec2 ms = vec2(v.x + v.y, v.y - v.x);
  v = vec2(ms.x * 0.5 + ms.y, ms.x * 0.5 - ms.y);
  return v;
}

void main(void) {
  vec2 coord = floor(gl_FragCoord.xy);
  float time = blockOffset + (coord.x + coord.y * resolution.x) / sampleRate;
  float d = float(duration);
  float fadeIn = smoothstep(0.0, 2.0 * 2.125, time);
  float fadeOut = clamp(abs(max(d - (2.0 * 2.125), min(d, time)) - d), 0.0, 1.0);
  gl_FragColor = vec4(mainSound(time) * fadeIn * fadeOut, 0.0, 0.0);
}