Fairy Lights

by MusicBar, progress bar

A twisted wire of glass fairy lights swags along the bar, lit up to the playhead and twinkling, with a chase racing down the string on every beat.

7 downloads Download file, 4.7 KB
How it installs, its SHA-256 and its code

Click Apply: MusicBar downloads it, checks its SHA-256 and puts it on the taskbar, or just puts it on if it already has it. Needs MusicBar 1.8 or newer.

SHA-256 5a7f67c1761b6291374079361812f8e7a46457eac714d4106332972df7868b58

//! name: Fairy Lights
//! author: MusicBar
//! kind: progress
//! description: A twisted wire of glass fairy lights swags along the bar, lit up to the playhead and twinkling, with a chase racing down the string on every beat.

float hash1(float n)
{
    n = frac(n * 0.1031);
    n *= n + 33.33;
    n *= n + n;
    return frac(n);
}

// Lays a color over what is there (premultiplied light, and how much of the pixel it covers).
void layer(inout float3 acc, inout float cover, float3 rgb, float a)
{
    a = saturate(a);
    acc = rgb * a + acc * (1.0 - a);
    cover = a + cover * (1.0 - a);
}

float2 rot(float2 p, float a)
{
    float c = cos(a), s = sin(a);
    return float2(c * p.x - s * p.y, s * p.x + c * p.y);
}

float boxD(float2 p, float2 b, float r)
{
    float2 d = abs(p) - b + r;
    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
}

// Bulb colors run through the palette.
float3 tint(float i)
{
    float k = frac(i / 3.0) * 3.0;
    return k < 0.5 ? colorA.rgb : (k < 1.5 ? colorB.rgb : colorC.rgb);
}

// The wire sags in swags between hooks every P pixels; returns its height and slope.
float2 wire(float x, float P, float H)
{
    float f = frac(x / P);
    return float2(H * 0.92 - H * 1.36 * f * (1.0 - f), -H * 1.36 * (1.0 - 2.0 * f) / P);
}

// Bulb i: a plastic socket on the wire and a glass bulb hanging under it.
void bulb(inout float3 acc, inout float cover, float2 px, float i, float sp, float P, float H, float head)
{
    float xi = (i + 0.5) * sp;
    float2 w = wire(xi, P, H);
    float h = hash1(i * 3.17 + 0.5);
    float ang = atan(w.y) * 0.7 + (h - 0.5) * 0.6 + 0.05 * sin(time * 1.4 + i);
    float2 q = rot(px - float2(xi, w.x), -ang);

    // Lit behind the playhead (the newest one stutters on), twinkling, and a chase on the beat.
    float on = saturate((head - xi) / sp + 0.5);
    on *= lerp(0.45 + 0.55 * abs(sin(time * 29.0 + i) * sin(time * 43.0)), 1.0, step(0.999, on));
    float tw = 0.75 + 0.25 * sin(time * (2.0 + 3.0 * h) + h * 40.0);
    float chase = smoothstep(0.7, 1.0, frac(i * 0.25 - time * 1.7));
    float glow = on * tw * (1.0 + (0.2 + 1.8 * beat) * chase + 0.3 * beat) * (0.85 + 0.25 * level);
    float3 c = tint(i);

    // The glass: an egg that narrows to a tip.
    float2 p = q - float2(0.0, -0.27 * H);
    float2 r = float2(0.12, 0.17) * H;
    float taper = 1.0 + 0.5 * saturate(-p.y / r.y);
    float e = length(float2(p.x * taper, p.y) / r);
    float sd = (e - 1.0) * r.x;

    // Halo first, then the glass with its hot middle and a window-shaped highlight.
    float hd = length(p * float2(1.0, 0.8)) / H;
    layer(acc, cover, lerp(c, float3(1, 1, 1), 0.1), (exp(-hd * hd * 22.0) * 0.5 + exp(-hd * hd * 70.0) * 0.3) * glow);
    float core = exp(-dot(p / r, p / r) * 2.0);
    float3 litC = c * (0.55 + 0.7 * core) * glow + core * core * 0.75 * glow;
    float3 darkC = c * (0.08 + 0.32 * smoothstep(0.65, 1.0, e)) + 0.02;
    float3 glass = lerp(darkC, litC, saturate(on));
    float spec = smoothstep(1.0, 0.3, length((p - float2(-0.045, 0.05) * H) / (float2(0.022, 0.055) * H)));
    glass += spec * (0.4 + 0.3 * on);
    layer(acc, cover, glass, smoothstep(0.5, -0.5, sd) * lerp(0.85, 1.0, on));

    // The socket, catching a little of its bulb's light.
    float sk = boxD(q - float2(0.0, -0.065 * H), float2(0.055, 0.065) * H, 0.02 * H);
    float3 skC = float3(0.05, 0.1, 0.06) + c * 0.25 * on + 0.18 * smoothstep(0.03 * H, -0.04 * H, q.x);
    layer(acc, cover, skC, smoothstep(0.5, -0.5, sk));
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
    // Pixels, y up the screen, so the bulbs always hang down.
    float H = size.y;
    float2 px = float2(uv.x * size.x, (1.0 - uv.y) * H);
    float sp = 0.78 * H;
    float P = sp * 4.0;
    float head = progress * size.x;
    float played = smoothstep(head + 0.4 * H, head - 0.4 * H, px.x);

    float3 acc = float3(0, 0, 0);
    float cover = 0;

    // The wire: two strands twisted round each other, warmed by the lit bulbs.
    float2 w = wire(px.x, P, H);
    float n = sqrt(1.0 + w.y * w.y);
    float tw = sin(px.x * 6.2832 / (0.16 * H));
    float th = max(0.5, 0.014 * H);
    float s1 = abs(px.y - w.x - 0.02 * H * tw) / n;
    float s2 = abs(px.y - w.x + 0.02 * H * tw) / n;
    float3 wc = lerp(float3(0.07, 0.13, 0.08), float3(0.3, 0.26, 0.17), played);
    layer(acc, cover, wc * 0.7, smoothstep(th + 0.5, th - 0.5, s2));
    layer(acc, cover, wc * (1.0 + 0.6 * step(0.0, tw)), smoothstep(th + 0.5, th - 0.5, s1));

    // The two nearest bulbs: the neighbor first, then this one on top.
    float i = floor(px.x / sp);
    float j = i + (frac(px.x / sp) > 0.5 ? 1.0 : -1.0);
    bulb(acc, cover, px, j, sp, P, H, head);
    bulb(acc, cover, px, i, sp, P, H, head);

    return paint(acc / max(cover, 0.001), cover);
}