#include <metal_stdlib>
using namespace metal;

struct Uniforms {
    float2 resolution;
    float  time;
    float  timeDelta;
    float2 mouse;
    uint   frame;
    uint   pad;
};

struct Params {
    float speed;
    float scale;
    float warp;
    float heat;
    float volume;
};

struct RKVertexOut {
    float4 position [[position]];
    float2 uv;
};

vertex RKVertexOut rk_vertex(uint vid [[vertex_id]]) {
    float2 p = float2((vid << 1) & 2, vid & 2);
    RKVertexOut o;
    o.position = float4(p * 2.0 - 1.0, 0.0, 1.0);
    o.uv = p;
    return o;
}

#line 1 "shader.metal"
// Ray Kiln · Flowing Color Field
//
// @episode Flowing Color Field
// @length 60
// @variation 30
// @variations 2,3,4,6,7,9,12
// @still 14.5
// @teaches Coordinates, time, noise, color palettes
// @category Fragment
// @tags noise, domain warping, procedural sound
// @final The kiln palette | Turn a number between 0 and 1 into a colour: near-black, ember, orange, gold, white-hot.
//
// A field of noise, folded over itself, pushed through the kiln palette.
// Each @param line below becomes a slider in the host app and a row in the page's parameters table:
//   // @param name default min max description

// @param speed 0.35 0.0 2.0 How fast the field drifts
// @param scale 2.2 0.5 6.0 Zoom of the noise: higher means finer detail
// @param warp 1.1 0.0 3.0 How far the field folds over itself
// @param heat 0.5 0.0 1.0 Slides the palette from deep ember to white-hot
// @param volume 0.8 0.0 1.0 Loudness of the sound

float hash21(float2 p) {
    p = fract(p * float2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return fract(p.x * p.y);
}

// Value noise: random values on a grid, blended smoothly between the grid points.
float vnoise(float2 p) {
    float2 i = floor(p), f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash21(i), b = hash21(i + float2(1, 0));
    float c = hash21(i + float2(0, 1)), d = hash21(i + float2(1, 1));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

// Fractal noise: several octaves of value noise, each finer and fainter than the last.
float fbm(float2 p) {
    float v = 0.0, a = 0.5;
    for (int i = 0; i < 5; i++) {
        v += a * vnoise(p);
        p = p * 2.03 + float2(17.1, 9.2);
        a *= 0.5;
    }
    return v;
}

// The Ray Kiln palette: near-black, ember, orange, gold, white-hot.
float3 kiln(float t) {
    float3 c0 = float3(0.020, 0.012, 0.020);
    float3 c1 = float3(0.280, 0.040, 0.030);
    float3 c2 = float3(0.880, 0.260, 0.050);
    float3 c3 = float3(1.000, 0.680, 0.200);
    float3 c4 = float3(1.000, 0.970, 0.840);
    t = clamp(t, 0.0, 1.0);
    float3 c = mix(c0, c1, smoothstep(0.00, 0.22, t));
    c = mix(c, c2, smoothstep(0.22, 0.50, t));
    c = mix(c, c3, smoothstep(0.50, 0.76, t));
    return mix(c, c4, smoothstep(0.76, 1.00, t));
}

float4 shade(float2 uv, constant Uniforms& u, constant Params& p) {
    float aspect = u.resolution.x / u.resolution.y;
    float2 q = (uv - 0.5) * float2(aspect, 1.0) * p.scale;
    float t = u.time * p.speed;

    // Warp the coordinates by noise, then sample noise at the warped spot.
    float2 w = float2(fbm(q + t), fbm(q + float2(5.2, 1.3) - t));
    float f = fbm(q + p.warp * w + t * 0.3);

    float3 col = kiln(f * 1.35 - 0.12 + (p.heat - 0.5) * 0.7);
    return float4(col, 1.0);
}

// ---- Sound -----------------------------------------------------------------------------------------------------
// The sound is part of the shader: a function of time, run on the GPU 48,000 times a second and put in the video.
// It listens to the same sliders as the picture: `speed` and `warp` shape the wind in it.

float sh11(float x) { return fract(sin(x * 127.1) * 43758.5453); }
float snoise(float x) {
    float i = floor(x), f = fract(x);
    f = f * f * (3.0 - 2.0 * f);
    return mix(sh11(i), sh11(i + 1.0), f) * 2.0 - 1.0;
}

float2 sound(float t, constant Params& p) {
    const float tau = 6.2831853;

    // A slow pad in D minor (D2 A2 D3 F3 A3). Each note drifts a few cents against its neighbours, a different
    // amount in each ear, so the chord slowly beats and breathes.
    const float freq[5] = {73.42, 110.0, 146.83, 174.61, 220.0};
    const float amp[5]  = {1.0, 0.8, 0.6, 0.4, 0.3};
    float2 pad = float2(0.0);
    for (int i = 0; i < 5; i++) {
        float drift = 0.0012 * (float(i) - 2.0);
        pad.x += amp[i] * sin(tau * freq[i] * (1.0 + drift) * t);
        pad.y += amp[i] * sin(tau * freq[i] * (1.0 - drift) * t + 1.0);
    }
    pad *= (0.65 + 0.35 * sin(tau * t / 11.0)) * 0.16;

    // Wind: slow noise that swells and falls, moving faster as the field is folded harder.
    float gust = 0.5 + 0.5 * snoise(t * 0.45);
    float wind = (snoise(t * (38.0 + 50.0 * p.warp)) + 0.5 * snoise(t * 23.0 + 4.0)) * gust * (0.25 + 0.5 * p.speed) * 0.1;

    // Sparks: now and then a soft high note from the D minor pentatonic, from a different place in the stereo field.
    float2 sparks = float2(0.0);
    const float note[4] = {587.33, 698.46, 880.0, 1174.66};
    for (int k = 0; k < 4; k++) {
        float period = 3.1 + 0.83 * float(k);
        float cell = floor(t / period + float(k) * 0.37);
        float since = t - (cell - float(k) * 0.37) * period;
        float on = step(0.45, sh11(cell * 2.7 + float(k) * 9.0));
        float e = on * exp(-since * 1.8) * (1.0 - exp(-since * 120.0));
        float tone = sin(tau * note[k] * t) + 0.3 * sin(tau * note[k] * 2.0 * t);
        float pan = 0.5 + 0.4 * sin(cell * 1.9 + float(k));
        sparks += float2(tone * (1.0 - pan), tone * pan) * e * 0.05;
    }

    float fadeIn = smoothstep(0.0, 2.0, t);
    return tanh((pad + wind + sparks) * 1.1) * fadeIn * p.volume;
}

#line 1 "rk_host"
fragment float4 rk_fragment(RKVertexOut in [[stage_in]],
                                constant Uniforms& u [[buffer(0)]],
                                constant Params& p [[buffer(1)]]) {
    return shade(in.uv, u, p);
}

struct SoundUniforms { float start; float rate; uint count; uint pad; };
kernel void rk_sound(device float2* out [[buffer(0)]],
                         constant SoundUniforms& su [[buffer(1)]],
                         constant Params& p [[buffer(2)]],
                         uint i [[thread_position_in_grid]]) {
    if (i >= su.count) return;
    out[i] = clamp(sound(su.start + float(i) / su.rate, p), -1.0, 1.0);
}