Add 11 new fractal presets and enhance audio visuals

Expanded fractal preset range to 20 with unique names and rendering functions for each. Added new visual effects including equalizer bars, metaballs, galaxy, aurora, vortex, laser grid, rain ripple, ink smoke, mosaic, ribbon, and grain bars. Improved CRT feedback with audio spectrum integration. Introduced utility and ring drawing functions. Updated preset selection and naming logic.
This commit is contained in:
Michal Vrba
2026-02-13 20:11:21 +01:00
parent 5a9c290816
commit 6fd9e52bf0
2 changed files with 484 additions and 4 deletions
+472 -3
View File
@@ -72,6 +72,39 @@ public sealed class FractalRenderer : IDisposable
case 8:
RenderFireworks(features, timeSeconds);
break;
case 9:
RenderEqualizerCity(features, timeSeconds);
break;
case 10:
RenderLiquidMetaballs(features, timeSeconds);
break;
case 11:
RenderParticleGalaxy(features, timeSeconds);
break;
case 12:
RenderAuroraCurtains(features, timeSeconds);
break;
case 13:
RenderVortexPortal(features, timeSeconds);
break;
case 14:
RenderLaserGrid(features, timeSeconds);
break;
case 15:
RenderRainRipple(features, timeSeconds);
break;
case 16:
RenderInkSmoke(features, timeSeconds);
break;
case 17:
RenderMirrorMosaic(features, timeSeconds);
break;
case 18:
RenderWaveformRibbon(features, timeSeconds);
break;
case 19:
RenderGrainBars(features, timeSeconds);
break;
default:
RenderJulia(features, timeSeconds);
break;
@@ -92,10 +125,14 @@ public sealed class FractalRenderer : IDisposable
float bass = Clamp01(features.Bass * 0.012f);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float lowSpec = GetSpectrumBand(features, 0.00f, 0.20f);
float midSpec = GetSpectrumBand(features, 0.20f, 0.60f);
float highSpec = GetSpectrumBand(features, 0.60f, 1.00f);
float fullSpec = Clamp01((lowSpec + midSpec + highSpec) / 3f);
int shiftPixels = 1 + (int)MathF.Round((high * 2f) + (beat * 2f));
float trailStrength = Clamp01(0.16f + (bass * 0.28f) + (beat * 0.34f));
float glowBoost = (high * 0.08f) + (beat * 0.06f);
int shiftPixels = 1 + (int)MathF.Round((high * 2f) + (highSpec * 3f) + (beat * 2f));
float trailStrength = Clamp01(0.14f + (bass * 0.20f) + (lowSpec * 0.20f) + (beat * 0.26f));
float glowBoost = (high * 0.05f) + (highSpec * 0.08f) + (midSpec * 0.05f) + (beat * 0.05f);
for (int y = 0; y < _internalHeight; y++)
{
@@ -122,9 +159,18 @@ public sealed class FractalRenderer : IDisposable
float nx = (x / (_internalWidth - 1f)) * 2f - 1f;
float vignette = Clamp01(1f - ((nx * nx + ny * ny) * 0.42f));
float spectralIndex = ((x / (float)_internalWidth) * 63f) + (timeSeconds * 6f) + (y * 0.03f);
float spectralPulse = GetSpectrumSample(features, spectralIndex);
float hueWarpR = 1f + (lowSpec * 0.20f) + spectralPulse * 0.12f;
float hueWarpG = 1f + (midSpec * 0.24f) + spectralPulse * 0.08f;
float hueWarpB = 1f + (highSpec * 0.28f) + spectralPulse * 0.14f;
crt *= scanline;
crt *= 0.5f + (vignette * 0.5f);
crt += raw * glowBoost;
crt.X *= hueWarpR;
crt.Y *= hueWarpG;
crt.Z *= hueWarpB;
crt += new Vector3(spectralPulse * 0.03f, spectralPulse * 0.02f, spectralPulse * 0.035f) * (0.3f + fullSpec);
crt.X = Clamp01(crt.X);
crt.Y = Clamp01(crt.Y);
@@ -137,6 +183,32 @@ public sealed class FractalRenderer : IDisposable
}
}
private float GetSpectrumBand(AudioFeatures features, float normalizedStart, float normalizedEnd)
{
if (features.Spectrum is null || features.Spectrum.Length == 0)
{
return 0f;
}
int length = features.Spectrum.Length;
int start = Math.Clamp((int)(normalizedStart * length), 0, length - 1);
int end = Math.Clamp((int)(normalizedEnd * length), start + 1, length);
float sum = 0f;
int count = 0;
for (int i = start; i < end; i++)
{
sum += features.Spectrum[i];
count++;
}
if (count == 0)
{
return 0f;
}
return Clamp01((sum / count) * 0.08f);
}
private void RenderMandelbulbStyle(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.012f);
@@ -295,6 +367,22 @@ public sealed class FractalRenderer : IDisposable
return 0.5f * MathF.Log(r + 1e-6f) * r / dr;
}
private static float Frac(float v)
{
return v - MathF.Floor(v);
}
private static float Hash01(int x, int y, int seed)
{
unchecked
{
int h = x * 374761393 + y * 668265263 + seed * 2147483647;
h = (h ^ (h >> 13)) * 1274126177;
h ^= h >> 16;
return (h & 0x7fffffff) / (float)int.MaxValue;
}
}
private void RenderFlame(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.012f);
@@ -622,6 +710,358 @@ public sealed class FractalRenderer : IDisposable
}
}
private void RenderEqualizerCity(AudioFeatures features, float timeSeconds)
{
for (int y = 0; y < _internalHeight; y++)
{
float t = y / (float)(_internalHeight - 1);
Color bg = new(0.01f, 0.02f + 0.08f * (1f - t), 0.08f + 0.18f * (1f - t), 1f);
int row = y * _internalWidth;
for (int x = 0; x < _internalWidth; x++)
{
_pixelBuffer[row + x] = bg;
}
}
int buildings = 48;
float beat = Clamp01(features.Beat);
for (int i = 0; i < buildings; i++)
{
float bin = GetSpectrumSample(features, i * (64f / buildings));
int bw = Math.Max(2, _internalWidth / buildings);
int x0 = i * bw;
int h = (int)((_internalHeight * 0.15f) + bin * _internalHeight * 0.75f);
int y0 = _internalHeight - h;
Vector3 c = new(0.08f + bin * 0.9f, 0.2f + bin * 0.6f + beat * 0.2f, 0.4f + bin * 0.5f);
DrawRect(x0, y0, bw - 1, h, c, 0.95f);
for (int wy = y0 + 4; wy < _internalHeight - 2; wy += 6)
{
for (int wx = x0 + 1; wx < x0 + bw - 1; wx += 4)
{
if (Hash01(wx, wy, i) > 0.55f - beat * 0.25f)
{
DrawRect(wx, wy, 2, 2, new Vector3(1f, 0.85f, 0.4f), 0.75f);
}
}
}
}
}
private void RenderLiquidMetaballs(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.012f);
float high = Clamp01(features.High * 0.003f);
for (int y = 0; y < _internalHeight; y++)
{
float py = y / (float)_internalHeight;
for (int x = 0; x < _internalWidth; x++)
{
float px = x / (float)_internalWidth;
float field = 0f;
for (int i = 0; i < 6; i++)
{
float ax = 0.5f + MathF.Sin(timeSeconds * (0.4f + i * 0.13f) + i) * (0.2f + bass * 0.1f);
float ay = 0.5f + MathF.Cos(timeSeconds * (0.35f + i * 0.11f) + i * 1.7f) * (0.25f + bass * 0.12f);
float dx = px - ax;
float dy = py - ay;
field += 0.02f / (dx * dx + dy * dy + 0.002f);
}
float m = Clamp01((field - 0.35f) * 0.8f);
float edge = Clamp01((field - 0.5f) * 2.5f);
float r = Clamp01(m * 0.2f + edge * (0.5f + high));
float g = Clamp01(m * 0.45f + edge * 0.3f);
float b = Clamp01(m * 0.75f + edge * (0.2f + high));
_pixelBuffer[y * _internalWidth + x] = new Color(r, g, b, 1f);
}
}
}
private void RenderParticleGalaxy(AudioFeatures features, float timeSeconds)
{
for (int i = 0; i < _pixelBuffer.Length; i++)
{
_pixelBuffer[i] = new Color(0.01f, 0.01f, 0.04f, 1f);
}
float beat = Clamp01(features.Beat);
int particles = 2200;
float arms = 4f + Clamp01(features.Mid * 0.002f) * 3f;
for (int i = 0; i < particles; i++)
{
float fi = i / (float)particles;
float radius = MathF.Pow(fi, 0.65f) * 0.48f * (1f + beat * 0.2f);
float arm = (i % (int)arms) / arms;
float angle = arm * MathF.Tau + radius * 14f + timeSeconds * (0.15f + beat * 0.8f);
float x = 0.5f + MathF.Cos(angle) * radius;
float y = 0.5f + MathF.Sin(angle) * radius;
int px = (int)(x * _internalWidth);
int py = (int)(y * _internalHeight);
if (px < 0 || py < 0 || px >= _internalWidth || py >= _internalHeight)
{
continue;
}
float hue = fi * 0.7f + beat * 0.2f;
Vector3 c = new(
0.5f + 0.5f * MathF.Sin(hue * 6.28f),
0.5f + 0.5f * MathF.Sin(hue * 6.28f + 2.1f),
0.5f + 0.5f * MathF.Sin(hue * 6.28f + 4.2f));
DrawFilledCircle(px, py, 1, c, 0.85f);
}
DrawFilledCircle(_internalWidth / 2, _internalHeight / 2, 6, new Vector3(1f, 0.9f, 0.7f), 0.9f);
}
private void RenderAuroraCurtains(AudioFeatures features, float timeSeconds)
{
float mid = Clamp01(features.Mid * 0.003f);
float high = Clamp01(features.High * 0.003f);
for (int y = 0; y < _internalHeight; y++)
{
float ny = y / (float)(_internalHeight - 1);
for (int x = 0; x < _internalWidth; x++)
{
float nx = x / (float)(_internalWidth - 1);
float wave = MathF.Sin(nx * 8f + timeSeconds * (0.3f + mid * 1.2f) + MathF.Sin(nx * 3f + timeSeconds));
float layer = MathF.Exp(-MathF.Abs(ny - (0.35f + wave * 0.18f)) * (6f + high * 12f));
float layer2 = MathF.Exp(-MathF.Abs(ny - (0.55f + MathF.Sin(nx * 5f - timeSeconds * 0.4f) * 0.12f)) * 9f);
float a = Clamp01(layer + layer2 * 0.7f);
float r = a * 0.15f;
float g = a * (0.6f + high * 0.5f);
float b = a * (0.45f + mid * 0.5f);
_pixelBuffer[y * _internalWidth + x] = new Color(r, g, b, 1f);
}
}
}
private void RenderVortexPortal(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.012f);
float aspect = _internalWidth / (float)_internalHeight;
for (int y = 0; y < _internalHeight; y++)
{
float py = (y / (float)_internalHeight) * 2f - 1f;
for (int x = 0; x < _internalWidth; x++)
{
float px = ((x / (float)_internalWidth) * 2f - 1f) * aspect;
float r = MathF.Sqrt(px * px + py * py) + 1e-5f;
float a = MathF.Atan2(py, px) + timeSeconds * (0.5f + bass * 2f);
float spiral = MathF.Sin(14f * r - a * 5f - timeSeconds * 3f);
float glow = MathF.Exp(-r * (2.5f - bass));
float v = Clamp01((spiral * 0.5f + 0.5f) * glow * 2f);
_pixelBuffer[y * _internalWidth + x] = new Color(v * 0.7f, v * 0.4f, v, 1f);
}
}
}
private void RenderLaserGrid(AudioFeatures features, float timeSeconds)
{
float beat = Clamp01(features.Beat);
float bass = Clamp01(features.Bass * 0.012f);
for (int y = 0; y < _internalHeight; y++)
{
float fy = y / (float)(_internalHeight - 1);
for (int x = 0; x < _internalWidth; x++)
{
float fx = x / (float)(_internalWidth - 1);
float perspective = 1f / (0.2f + fy * (1.8f - bass * 0.6f));
float gx = MathF.Abs(((fx * perspective * 20f + timeSeconds) % 1f) - 0.5f);
float gy = MathF.Abs(((fy * perspective * 20f) % 1f) - 0.5f);
float line = Clamp01(0.08f / (gx + 0.02f) + 0.08f / (gy + 0.02f));
float sweep = MathF.Exp(-MathF.Abs(fy - ((timeSeconds * 0.25f + beat * 0.2f) % 1f)) * 60f);
float v = Clamp01(line * 0.6f + sweep);
_pixelBuffer[y * _internalWidth + x] = new Color(v * 0.1f, v * 0.9f, v, 1f);
}
}
}
private void RenderRainRipple(AudioFeatures features, float timeSeconds)
{
float level = Clamp01(features.Level * 3f);
float density = 70f + level * 220f;
for (int y = 0; y < _internalHeight; y++)
{
for (int x = 0; x < _internalWidth; x++)
{
_pixelBuffer[y * _internalWidth + x] = new Color(0.02f, 0.04f, 0.08f, 1f);
}
}
int drops = (int)density;
for (int i = 0; i < drops; i++)
{
float seed = i * 13.37f;
float x = Frac(seed * 0.731f);
float phase = Frac(seed * 0.379f + timeSeconds * (0.4f + level * 1.2f));
float y = phase;
int px = (int)(x * _internalWidth);
int py = (int)(y * _internalHeight);
DrawRect(px, py, 1, 6, new Vector3(0.6f, 0.8f, 1f), 0.7f);
if (py > _internalHeight * 0.8f)
{
float rr = (phase - 0.8f) * 80f;
DrawRing(px, (int)(_internalHeight * 0.82f), rr, new Vector3(0.7f, 0.9f, 1f), 0.35f);
}
}
}
private void RenderInkSmoke(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.012f);
float high = Clamp01(features.High * 0.003f);
for (int y = 0; y < _internalHeight; y++)
{
float ny = y / (float)(_internalHeight - 1);
for (int x = 0; x < _internalWidth; x++)
{
float nx = (x / (float)(_internalWidth - 1)) * 2f - 1f;
float basePlume = MathF.Exp(-MathF.Abs(nx) * (2.5f - bass));
float turbulence = MathF.Sin(nx * 12f + ny * 9f - timeSeconds * (1.5f + high * 5f))
+ MathF.Sin(nx * 21f - ny * 14f + timeSeconds * 2.2f);
turbulence = turbulence * 0.25f + 0.5f;
float rise = Clamp01((1f - ny) * (0.7f + bass * 0.7f));
float d = Clamp01(basePlume * rise * turbulence);
_pixelBuffer[y * _internalWidth + x] = new Color(d * 0.18f, d * 0.2f, d * 0.24f, 1f);
}
}
}
private void RenderMirrorMosaic(AudioFeatures features, float timeSeconds)
{
int tiles = 3 + (int)(Clamp01(features.Beat + features.Level * 2f) * 5f);
float t = timeSeconds * 0.6f;
for (int y = 0; y < _internalHeight; y++)
{
float v = y / (float)_internalHeight;
for (int x = 0; x < _internalWidth; x++)
{
float u = x / (float)_internalWidth;
float mu = Frac(u * tiles);
float mv = Frac(v * tiles);
if (((int)(u * tiles) & 1) == 1) mu = 1f - mu;
if (((int)(v * tiles) & 1) == 1) mv = 1f - mv;
float pattern = MathF.Sin((mu * 10f + t) * MathF.Tau) * MathF.Cos((mv * 10f - t) * MathF.Tau);
float p = pattern * 0.5f + 0.5f;
_pixelBuffer[y * _internalWidth + x] = new Color(
Clamp01(0.2f + p * 0.9f),
Clamp01(0.2f + (1f - p) * 0.8f),
Clamp01(0.35f + p * 0.7f),
1f);
}
}
}
private void RenderWaveformRibbon(AudioFeatures features, float timeSeconds)
{
for (int i = 0; i < _pixelBuffer.Length; i++)
{
_pixelBuffer[i] = new Color(0.01f, 0.01f, 0.03f, 1f);
}
if (features.Spectrum is null || features.Spectrum.Length == 0)
{
return;
}
int points = _internalWidth;
float beat = Clamp01(features.Beat);
for (int x = 0; x < points; x++)
{
float tx = x / (float)(points - 1);
float idx = tx * (features.Spectrum.Length - 1);
int i0 = (int)idx;
int i1 = Math.Min(features.Spectrum.Length - 1, i0 + 1);
float f = idx - i0;
float amp = (features.Spectrum[i0] + (features.Spectrum[i1] - features.Spectrum[i0]) * f) * 0.18f;
amp = Clamp01(amp);
float center = 0.5f + MathF.Sin(tx * 8f + timeSeconds * 0.9f) * 0.08f;
float yNorm = center - amp * (0.35f + beat * 0.2f);
int y = (int)(yNorm * _internalHeight);
int thickness = 2 + (int)(amp * 9f);
for (int k = -thickness; k <= thickness; k++)
{
int yy = y + k;
if (yy < 0 || yy >= _internalHeight)
{
continue;
}
float w = 1f - MathF.Abs(k) / (float)(thickness + 1);
Vector3 c = new(0.2f + amp * 1.0f, 0.3f + w * 0.7f, 0.7f + beat * 0.3f);
BlendPixel(x, yy, c, w * 0.8f);
}
}
}
private void RenderGrainBars(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.012f);
float mid = Clamp01(features.Mid * 0.0035f);
float high = Clamp01(features.High * 0.0035f);
float beat = Clamp01(features.Beat);
float level = Clamp01(features.Level * 3f);
for (int y = 0; y < _internalHeight; y++)
{
float t = y / (float)(_internalHeight - 1);
Color bg = new(0.015f, 0.02f + 0.04f * (1f - t), 0.03f + 0.07f * (1f - t), 1f);
int row = y * _internalWidth;
for (int x = 0; x < _internalWidth; x++)
{
_pixelBuffer[row + x] = bg;
}
}
int totalBars = Math.Max(120, _internalWidth / 2);
float bandWidth = _internalWidth / 3f;
float maxHeight = _internalHeight * (0.22f + 0.62f * (0.35f + level * 0.65f));
for (int i = 0; i < totalBars; i++)
{
float xNorm = i / (float)(totalBars - 1);
int x = (int)(xNorm * (_internalWidth - 1));
int region = x < bandWidth ? 0 : (x < bandWidth * 2f ? 1 : 2);
float band = region switch
{
0 => bass,
1 => mid,
_ => high
};
float grain = Hash01(i, (int)(timeSeconds * 60f), region) * 2f - 1f;
float wobble = MathF.Sin(timeSeconds * (6.0f + region * 2.0f) + i * 0.21f) * 0.22f;
float spectrum = GetSpectrumSample(features, i * 0.43f + region * 7f) * 1.8f;
float amp = Clamp01(band * 0.8f + spectrum * 0.45f + beat * 0.3f + grain * 0.13f + wobble * 0.1f);
int h = Math.Max(2, (int)(amp * maxHeight));
Vector3 color = region switch
{
0 => new Vector3(1f, 0.35f + amp * 0.4f, 0.2f),
1 => new Vector3(0.2f, 0.9f, 0.35f + amp * 0.5f),
_ => new Vector3(0.25f + amp * 0.4f, 0.5f, 1f)
};
int barW = 1;
int y0 = _internalHeight - h;
DrawRect(x, y0, barW, h, color, 0.95f);
if (Hash01(i, region, (int)(timeSeconds * 100f)) > 0.68f)
{
int gritCount = 2 + (int)(amp * 8f);
for (int g = 0; g < gritCount; g++)
{
int gy = _internalHeight - 1 - (int)(Hash01(g, i, region) * h);
DrawRect(x, gy, 1, 1, Vector3.One, 0.3f + amp * 0.5f);
}
}
}
}
private void SpawnFireworkRocket(float intensity)
{
float x = 0.1f + (float)_rng.NextDouble() * 0.8f;
@@ -782,6 +1222,35 @@ public sealed class FractalRenderer : IDisposable
}
}
private void DrawRing(int cx, int cy, float radius, Vector3 color, float alpha)
{
int r = Math.Max(1, (int)radius);
int minX = Math.Max(0, cx - r - 1);
int maxX = Math.Min(_internalWidth - 1, cx + r + 1);
int minY = Math.Max(0, cy - r - 1);
int maxY = Math.Min(_internalHeight - 1, cy + r + 1);
float r0 = MathF.Max(0f, radius - 1.25f);
float r1 = radius + 1.25f;
float r0s = r0 * r0;
float r1s = r1 * r1;
for (int y = minY; y <= maxY; y++)
{
float dy = y - cy;
for (int x = minX; x <= maxX; x++)
{
float dx = x - cx;
float d2 = dx * dx + dy * dy;
if (d2 < r0s || d2 > r1s)
{
continue;
}
BlendPixel(x, y, color, alpha);
}
}
}
private void BlendPixel(int x, int y, Vector3 color, float alpha)
{
int idx = y * _internalWidth + x;