The Matrix (GPU Sound)

A Tribute to The Matrix, created to demonstrate my first tests with GPU synthesized sound.

Created by marcogomez on Mon, 11 Oct 2021 23:59:01 GMT.


#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm7Texture;
uniform vec2 resolution;
uniform float time;
uniform float fft;

out vec4 fragColor;

const float PI = acos(-1.0);
const float TAU = PI * 2.0;
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));
}

void main() {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec4 color = texture(prgm7Texture, uv);
  color.xyz = mix(
    color.xyz,
    hueShift(color.xyz, TAU * 2.0 * fft + time * 0.75) * vec3(0.6, 0.8, 1.0),
    0.1 + fft * 0.35
  );
  float pb = 0.4;
  vec4 lcdColor = vec4(pb, pb, pb, 1.0);
  int px = int(mod(gl_FragCoord.x, 3.0));
  if (px == 1) lcdColor.r = 1.0;
  else if (px == 2) lcdColor.g = 1.0;
  else lcdColor.b = 1.0;
  float sclV = 0.25;
  if (int(mod(gl_FragCoord.y, 3.0)) == 0) lcdColor.rgb = vec3(sclV, sclV, sclV);
  fragColor = mix(color, color * lcdColor, 0.5);
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D eTexture0; // https://i.imgur.com/q9Rv95v.png
uniform sampler2D noiseTexture;
uniform vec2 resolution;
uniform float time;
uniform float fft;

out vec4 fragColor;

const float PI = acos(-1.0);

float osc(float s, float e, float t, float ts) {
  return (e - s) * 0.5 + s + sin(t * ts) * (e - s) * 0.5;
}

mat2 rot(float a) {
  return mat2(
    cos(a), -sin(a), sin(a), cos(a)
  );
}

float noise(vec2 x) {
  vec2 p = floor(x);
  vec2 f = fract(x);
  f = f * f * (3.0 - 2.0 * f);
  float a = texture(noiseTexture, (p + vec2(0.5, 0.5 + fft * 0.25)) / 256.0, 0.0).x;
  float b = texture(noiseTexture, (p + vec2(1.5, 0.5 + fft * 0.25)) / 256.0, 0.0).x;
  float c = texture(noiseTexture, (p + vec2(0.5, 1.5 + fft * 0.25)) / 256.0, 0.0).x;
  float d = texture(noiseTexture, (p + vec2(1.5, 1.5 + fft * 0.25)) / 256.0, 0.0).x;
  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

float text(vec2 coord, float t) {
  vec2 uv = mod(coord.xy, 16.0) * 0.0625;
  vec2 block = coord * 0.0625 - uv;
  float oa = osc(0.0, 0.1, t, 3.0);
  float ob = clamp(osc(-4.0, 2.0, t, 0.5), 0.0, 1.0);
  uv = uv * (0.8 - oa) + (0.1 + (oa * 0.5));
  vec2 r = floor(texture(noiseTexture, block / resolution.xy + t * 0.001).xy * 16.0);
  uv += r;
  uv *= 0.0625;
  float rn = noise(r);
  float rt = 1.0 - texture(eTexture0, uv).r;
  rt = mix(rt, rt * rn, ob);
  return rt;
}

vec3 rain(vec2 coord, float t) {
  coord.x -= mod(coord.x, 16.0);
  coord.y -= mod(coord.y, 16.0);
  float offset = sin(coord.x * 15.0);
  float speed = cos(coord.x * 3.0) * 0.3 + 0.7;
  float y = fract(coord.y / resolution.y + (t * 0.5) * speed + offset);
  return vec3(0.1, 1.0, 0.35) / (y * 20.0);
}

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float t = (time * 0.5) + fft * 1.5;
  vec2 coord = gl_FragCoord.xy;
  fragColor = vec4(text(coord.xy, t) * rain(coord.xy, t), 1.0);
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D noiseTexture; // https://i.imgur.com/N9yUvGf.png
uniform sampler2D textTexture;
uniform vec2 resolution;
uniform float time;
uniform float fft;

out vec4 fragColor;

const float PI = acos(-1.0);

float osc(float s, float e, float t, float ts) {
  return (e - s) * 0.5 + s + sin(t * ts) * (e - s) * 0.5;
}

mat2 rot(float a) {
  return mat2(
    cos(a), -sin(a), sin(a), cos(a)
  );
}

float noise(vec2 x) {
  vec2 p = floor(x);
  vec2 f = fract(x);
  f = f * f * (3.0 - 2.0 * f);
  float a = texture(noiseTexture, (p + vec2(0.5, 0.5 + fft * 0.25)) / 256.0, 0.0).x;
  float b = texture(noiseTexture, (p + vec2(1.5, 0.5 + fft * 0.25)) / 256.0, 0.0).x;
  float c = texture(noiseTexture, (p + vec2(0.5, 1.5 + fft * 0.25)) / 256.0, 0.0).x;
  float d = texture(noiseTexture, (p + vec2(1.5, 1.5 + fft * 0.25)) / 256.0, 0.0).x;
  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

float text(vec2 coord, float t) {
  vec2 uv = mod(coord.xy, 16.0) * 0.0625;
  vec2 block = coord * 0.0625 - uv;
  float oa = osc(0.0, 0.2, t, 3.0);
  float ob = clamp(osc(-4.0, 2.0, t, 0.5), 0.0, 1.0);
  uv = uv * (0.8 - oa) + (0.1 + (oa * 0.5));
  vec2 r = floor(texture(noiseTexture, block / resolution.xy + t * 0.001).xy * 16.0);
  uv += r;
  uv *= 0.0625;
  float rn = noise(r);
  if (rn < 0.5) { uv.x = 1.0 - uv.x; }
  if (rn < 0.6) { uv.y = 1.0 - uv.y; }
  if (rn < 0.2 && ob > 0.5) {
    uv *= rot(PI * 0.5);
  } else if (rn < 0.2 && ob < 0.5) {
    uv *= rot(PI * -0.5);
  }
  float rt = texture(textTexture, uv).r;
  rt = mix(rt, rt * rn, ob);
  return rt;
}

vec3 rain(vec2 coord, float t) {
  coord.x -= mod(coord.x, 16.0);
  coord.y -= mod(coord.y, 16.0);
  float offset = sin(coord.x * 15.0);
  float speed = cos(coord.x * 3.0) * 0.3 + 0.7;
  float y = fract(coord.y / resolution.y + (t * 0.5) * speed + offset);
  return vec3(0.1, 1.0, 0.35) / (y * 20.0);
}

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float t = (time * 0.5) + fft;
  vec2 coord = gl_FragCoord.xy * 0.8;
  fragColor = vec4(text(coord.xy, t) * rain(coord.xy, t), 1.0);
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D noiseTexture;
uniform sampler2D prgm1Texture;
uniform sampler2D prgm2Texture;
uniform vec2 resolution;
uniform float time;

out vec4 fragColor;

const vec3 tWhite = vec3(1.0);
const float tPixelSize = 16.0;
const float transitionSpread = 0.4;
const float transitionSpeed = 0.2;
const float transitionIntensity = 32.0;

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec2 noiseCell = floor(gl_FragCoord.xy / tPixelSize);
  vec2 noiseUV = noiseCell / resolution.xy;
  float noise = texture(noiseTexture, noiseUV * 4.0).x * transitionSpread;
  float progress = (time * transitionSpeed) + noiseUV.y + noise;
  float peak = cos(progress) * transitionIntensity;
  float transition = clamp(peak, 0.0, 1.0);
  vec4 prgm1 = texture(prgm1Texture, uv);
  vec4 prgm2 = texture(prgm2Texture, uv);
  vec4 tColor = (
    vec4(1.0 - clamp(abs(peak), 0.0, 1.0)) *
    vec4(0.5, 2.0, 0.7, 1.0) * 0.07
  );
  vec4 col = mix(prgm2, prgm1, transition);
  //col = col + tColor;
  col.xyz *= 1.5;
  fragColor = col;
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm4Texture;
uniform sampler2D prgm5Texture;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;
uniform int frame;

out vec4 fragColor;

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec2 mp = ((mouse + 1.0) * 0.5) * resolution;
  vec3 e = vec3(vec2(1.0) / resolution.xy, 0.0);
  vec2 q = uv;
  vec4 c = texture(prgm4Texture, q);
  float p11 = c.x;
  float p10 = texture(prgm5Texture, q - e.zy).x;
  float p01 = texture(prgm5Texture, q - e.xz).x;
  float p21 = texture(prgm5Texture, q + e.xz).x;
  float p12 = texture(prgm5Texture, q + e.zy).x;
  float d = 0.0;
  d = smoothstep(86.0, 0.5, length(mp.xy - gl_FragCoord.xy) * 1.5);
  d += -(p11 - 0.5) * 2.0 + (p10 + p01 + p21 + p12 - 2.0);
  d *= 0.995;
  d *= max(min(1.0, float(frame)), 0.0) * clamp(time - 1.0, 0.0, 1.0);
  d = d * 0.5 + 0.5;
  fragColor = vec4(d, 0.0, 0.0, 1.0);
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm4Texture;
uniform sampler2D prgm5Texture;
uniform vec2 resolution;
uniform vec2 mouselerp;
uniform float time;
uniform int frame;

out vec4 fragColor;

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec2 mp = ((mouselerp + 1.0) * 0.5) * resolution;
  vec3 e = vec3(vec2(1.0) / resolution.xy, 0.0);
  vec2 q = uv;
  vec4 c = texture(prgm5Texture, q);
  float p11 = c.x;
  float p10 = texture(prgm4Texture, q - e.zy).x;
  float p01 = texture(prgm4Texture, q - e.xz).x;
  float p21 = texture(prgm4Texture, q + e.xz).x;
  float p12 = texture(prgm4Texture, q + e.zy).x;
  float d = 0.0;
  d = smoothstep(212.0, 0.0, length(mp.xy - gl_FragCoord.xy) * resolution.x);
  d += -(p11 - 0.5) * 2.0 + (p10 + p01 + p21 + p12 - 2.0);
  d *= 0.998;
  d *= max(min(1.0, float(frame)), 0.0) * clamp(time - 1.0, 0.0, 1.0);
  d = d * 0.5 + 0.5;
  fragColor = vec4(d, 0.0, 0.0, 1.0);
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm3Texture;
uniform sampler2D prgm5Texture;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;

out vec4 fragColor;

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec2 mp = (( mouse + 1.0) * 0.5) * resolution;
  vec3 e = vec3(vec2(1.0) / resolution.xy, 0.0) * (0.06125 - 0.0125);
  float p10 = texture(prgm5Texture, uv - e.zy).x;
  float p01 = texture(prgm5Texture, uv - e.xz).x;
  float p21 = texture(prgm5Texture, uv + e.xz).x;
  float p12 = texture(prgm5Texture, uv + e.zy).x;
  vec3 grad = normalize(vec3(p21 - p01, p12 - p10, 1.0));
  vec4 c = texture(prgm3Texture, uv + grad.xy * 0.35);
  vec3 light = normalize(vec3(0.2, -0.5, 0.7));
  float diffuse = dot(grad, light);
  float spec = pow(abs(max(0.0, -reflect(light, grad).z)), 16.0);
  vec4 col = c;
  col = mix(col, col * col, 0.7);
  col = clamp(col, 0.0, 1.0);
  fragColor = vec4(col.xyz, 1.0);
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ https://mgz.me ║
// ╚═════════════╩═════════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm5Texture;
uniform sampler2D prgm6Texture;
uniform vec2 resolution;
uniform vec2 mouselerp;
uniform float time;
uniform float fft;
uniform float fftLow;
uniform float fftMid;
uniform float fftHigh;

out vec4 fragColor;

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

const int samples = 48;
const float density = 0.5;
const float weight = 0.5;
const float exposure = 1.2;

float osc(float s, float e, float t, float ts) {
  return (e - s) * 0.5 + s + sin(t * ts) * (e - s) * 0.5;
}

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

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

float gaussian(float z, float u, float o) {
  return (
    (1.0 / (o * sqrt(TAU))) *
    (exp(-(((z - u) * (z - u)) / (2.0 * (o * o)))))
  );
}

vec3 gaussgrain(float t) {
  vec2 ps = vec2(1.0) / resolution.xy;
  vec2 uv = gl_FragCoord.xy * ps;
  float seed = dot(uv, vec2(12.9898, 78.233));
  float noise = fract(sin(seed) * 43758.5453123 + t);
  noise = gaussian(noise, 0.0, 0.5);
  return vec3(noise);
}

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec3 e = vec3(vec2(1.0) / resolution.xy, 0.0) * (0.06125 - 0.0125);
  float p10 = texture(prgm5Texture, uv - e.zy).x;
  float p01 = texture(prgm5Texture, uv - e.xz).x;
  float p21 = texture(prgm5Texture, uv + e.xz).x;
  float p12 = texture(prgm5Texture, uv + e.zy).x;
  vec3 grad = normalize(vec3(p21 - p01, p12 - p10, 1.0));
  vec2 dist = (uv - (16.0 * grad.xy + 0.5));
  float density = 0.75 + 0.2 * sin(2.0 * radians(360.0));
  dist *= 1.0 / (float(samples) * (3.0 - (fft * 0.125))) * density;
  vec3 g = gaussgrain(time) * 0.03;
  vec3 color = texture(prgm6Texture, uv).rgb;
  vec3 oColor = color;
  float illuminationdecay = 1.0;
  for (int i = 0; i < samples; i++)   {
    uv -= dist;
    float ra = hashA(uv);
    float rb = hashB(uv);
    float r = (rb * ra) + smoothstep(ra, rb, 0.5);
    vec3 sample_ = texture(prgm6Texture, uv + dist * r).rgb;
    sample_ *= illuminationdecay * weight;
    color += sample_;
    float decay = 0.6 + fft * 0.6;
    illuminationdecay *= decay;
  }
  float o = osc(0.4, 0.6, time, 3.0);
  vec3 fftCol = vec3(
    sin(fftHigh * 2.0) * 0.1 + 0.1,
    sin(fftMid * 2.0) * 0.5 + 0.5,
    cos(fftLow * 2.0) * 0.5 + 0.5
  ) * vec3(1.0, 1.2, 1.5) - g;
  fragColor = mix(vec4(oColor, 1.0), vec4(color * exposure, 1.0), o);
  fragColor.xyz = clamp(mix(fragColor.xyz, fragColor.xyz * fftCol, fft) + fftCol * 0.03, 0.0, 1.0);
}

#version 300 es
// ╔═════════════╦═════════════════╦════════════════╗
// ║ Marco Gomez ║ @TheCodeTherapy ║ 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;

out vec2 fragColor;

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);
  fragColor = mainSound(time) * fadeIn * fadeOut;
}