Wildfire
by MusicBar, progress bar
Real flames burn along the played part, roaring tallest at the playhead and settling into glowing coals behind, with the bass driving them higher and embers flying on the beat.
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 651d2bae84c19ab2e6efd027d3c78264b1debe4558b2fa04626f0e757794d1af
//! name: Wildfire
//! author: MusicBar
//! kind: progress
//! description: Real flames burn along the played part, roaring tallest at the playhead, and settle into glowing coals and drifting embers behind it.
// A steady random number for a cell.
float hash2(float2 p)
{
float3 q = frac(float3(p.x, p.y, p.x) * 0.1031);
q += dot(q, q.yzx + 33.33);
return frac((q.x + q.y) * q.z);
}
// Smooth value noise.
float vnoise(float2 p)
{
float2 i = floor(p);
float2 f = frac(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash2(i);
float b = hash2(i + float2(1, 0));
float c = hash2(i + float2(0, 1));
float d = hash2(i + float2(1, 1));
return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y);
}
// Four octaves of turbulence, each one turned a little so no grid shows.
float fbm(float2 p)
{
float v = 0;
float a = 0.5;
[unroll] for (int i = 0; i < 4; i++)
{
v += a * vnoise(p);
p = float2(1.6 * p.x + 1.2 * p.y, -1.2 * p.x + 1.6 * p.y) + float2(3.1, 7.7);
a *= 0.5;
}
return v / 0.9375;
}
// Blackbody heat: dull red, red, orange, yellow, near white. The palette tints the cool tips, and the middle a little.
float3 heat(float t)
{
float3 c = lerp(float3(0.30, 0.02, 0.00), float3(0.80, 0.09, 0.02), smoothstep(0.0, 0.3, t));
c = lerp(c, float3(1.00, 0.36, 0.04), smoothstep(0.25, 0.6, t));
c = lerp(c, float3(1.00, 0.72, 0.22), smoothstep(0.55, 0.88, t));
c = lerp(c, float3(1.00, 0.96, 0.84), smoothstep(0.9, 1.2, t));
float tips = smoothstep(0.5, 0.1, t);
float mids = smoothstep(0.2, 0.45, t) * smoothstep(0.9, 0.6, t);
return lerp(c, max(c, colorA.rgb * (0.35 + 0.9 * t)), 0.5 * tips + 0.15 * mids);
}
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);
}
float4 main(float2 uv : TEXCOORD) : COLOR
{
float H = size.y;
float2 px = float2(uv.x * size.x, rise(uv) * size.y); // pixels: along the bar, up from the screen edge
float h = px.y / H;
float head = progress * size.x;
float d = (head - px.x) / H; // bar heights behind the playhead
float near = exp(-max(d, 0.0) / 2.5);
// Bass feeds the flames at the playhead, the treble the ones far back.
float s = spectrum(saturate(d / 14.0));
float bass = spectrum(0.03);
// Flames lean forward at the front and sway a little; db is where this flame stands on the ground.
float lean = h * (0.1 + 0.5 * near * (0.45 + 0.75 * h) + 0.08 * sin(time * 1.3 + px.x / H * 0.7));
float db = d + lean;
float lit = smoothstep(-0.25, 0.35, db); // 0 where the fire has not arrived yet
// How tall the fire stands: roaring behind the playhead, then patchy low flames over the coals.
float patches = smoothstep(0.35, 0.75, vnoise(float2(px.x / H * 0.9 - time * 0.15, time * 0.35)));
float tall = exp(-max(db - 0.5, 0.0) / 3.5) * (0.8 + 0.4 * bass + 0.35 * beat * near);
float calm = exp(-max(d, 0.0) / 14.0); // far back the flames die down to coals
float env = 0.03 + 0.05 * calm + 0.22 * patches * (0.6 + 0.6 * s) * (0.3 + 0.7 * calm) + tall * (0.5 + 0.6 * s);
// Turbulence scrolling up fast, warped so the tongues twist and split.
float t = time * 2.8;
float2 p = float2((px.x / H - lean) * 3.4, h * 1.7 - t);
float2 w = float2(vnoise(p * 0.5 + float2(0.0, t * 0.3)), vnoise(p * 0.5 + float2(5.2, 1.3)));
float n = fbm(p + (w - 0.5) * 2.0);
// The flame field: hot at the base, broken into tongues higher up.
float hn = h / env;
float I = (1.0 - hn) * 0.9 + (n - 0.5) * (0.8 + 1.8 * hn);
I = I * lerp(0.75, 1.15, near) * (1.0 + 0.15 * beat) - (1.0 - lit) * 3.0;
float3 acc = float3(0, 0, 0);
float cover = 0;
// Dry grass waiting ahead of the fire in uneven tufts; the blades nearest the flames catch and glow.
float gw = H * 0.075;
float gi = floor(px.x / gw);
float gr = hash2(float2(gi, 3.7));
float tuft = vnoise(float2(gi * 0.23, 5.0));
float gh = H * (0.06 + 0.3 * gr * gr + 0.18 * tuft) * step(0.18, hash2(float2(gi, 1.3)));
float bx = (gi + 0.25 + 0.5 * hash2(float2(gi, 9.1))) * gw + px.y * (0.5 * gr - 0.22 + 0.06 * sin(time * 1.8 + gi * 0.6));
float gy = saturate(px.y / max(gh, 0.5));
float half0 = 0.3 + (1.0 - gy) * H * 0.024;
float blade = smoothstep(half0 + 0.5, half0 - 0.5, abs(px.x - bx)) * step(px.y, gh);
float ahead = smoothstep(0.05, -0.25, d);
float scorch = exp(min(d, 0.0) * 1.3);
float tipBurn = scorch * scorch * smoothstep(0.25, 0.9, gy) * 1.3 * (0.7 + 0.3 * sin(time * 23.0 + gi * 3.0));
float3 straw = lerp(float3(0.50, 0.40, 0.24), float3(0.78, 0.66, 0.40), gy);
straw = lerp(straw, float3(1.0, 0.42, 0.08), scorch * 0.8);
straw = lerp(straw, float3(1.0, 0.85, 0.5), saturate(tipBurn));
layer(acc, cover, straw, blade * ahead * (0.4 + 0.6 * scorch));
// Smoke curling off the older burn: thin wisps above the flame tips, lit warm from below.
float wisp = 1.0 - abs(w.y * 1.6 + n * 0.6 - 1.1) * 5.0;
float smoke = saturate(wisp) * smoothstep(env, env + 0.45, h) * smoothstep(0.8, 3.0, d) * smoothstep(1.0, 0.45, h);
layer(acc, cover, lerp(float3(0.62, 0.56, 0.52), float3(0.9, 0.5, 0.3), smoothstep(env + 0.5, env, h)), smoke * 0.12);
// Soft heat glow around the fire, flaring at the playhead on the beat.
float glow = pow(saturate(I + 0.45), 2.0) * lit;
layer(acc, cover, heat(0.4), glow * (0.3 + 0.3 * beat * near));
float bloom = exp(-length(float2(px.x - head + 0.4 * H, px.y - 0.15 * H)) / (H * (0.45 + 0.3 * beat)));
layer(acc, cover, heat(0.55), bloom * (0.16 + 0.3 * beat));
// Glowing coals on the ground where the fire has passed: lumps with dark cracks, breathing.
float2 cq = float2(px.x / H * 6.0, h * 9.0);
float coal = vnoise(cq) * 0.6 + vnoise(cq * 2.4 + 4.0) * 0.4;
float top = 0.1 + 0.07 * vnoise(float2(px.x / H * 4.0, 1.0));
float bed = smoothstep(top + 0.5 / H, top - 0.5 / H, h) * smoothstep(0.0, 0.8, d);
float glowCoal = smoothstep(0.45, 0.75, coal) * (0.7 + 0.3 * sin(time * 1.4 + coal * 11.0)) * (0.85 + 0.3 * level);
layer(acc, cover, heat(0.2 + 0.7 * glowCoal), bed * (0.45 + 0.55 * smoothstep(0.3, 0.55, coal)));
// The flames.
layer(acc, cover, heat(I), smoothstep(0.0, 0.16, I));
// Embers drifting up and back, flickering; more of them when it is loud and on the beat.
float2 ep = px / H * 3.0 + float2(time * 0.8, -time * 2.4);
float2 cell = floor(ep);
float r = hash2(cell);
float2 at = 0.2 + 0.6 * float2(hash2(cell + 3.1), hash2(cell + 7.3)) + float2(0.15 * sin(time * 3.0 + r * 30.0), 0.0);
float2 rel = (frac(ep) - at) * H / 3.0;
float dot0 = length(float2(rel.x + rel.y * 0.3, rel.y * 0.45)); // a short streak along the way it flies
float er = max(0.9, H * 0.022);
float live = (0.5 + 0.5 * sin(time * 19.0 + r * 60.0)) * step(r, 0.18 + 0.2 * level + 0.35 * beat)
* smoothstep(-0.8, 0.2, d) * exp(-max(d, 0.0) / 5.0) * smoothstep(-0.05, 0.25, h);
layer(acc, cover, lerp(float3(1.0, 0.45, 0.1), colorA.rgb, 0.5), 0.55 * exp(-dot0 / (er * 1.8)) * live);
layer(acc, cover, lerp(float3(1.0, 0.85, 0.5), colorA.rgb, 0.25), smoothstep(er + 0.6, er - 0.4, dot0) * live);
return paint(acc / max(cover, 0.001), cover);
}