Files
SoundFlix/FractalVisualizer/Rendering/FractalRenderer.cs
T
Michal Vrba 6fd9e52bf0 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.
2026-02-13 20:11:21 +01:00

1394 lines
51 KiB
C#

using System;
using System.Collections.Generic;
using FractalVisualizer.Models;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace FractalVisualizer.Rendering;
public sealed class FractalRenderer : IDisposable
{
private readonly GraphicsDevice _graphicsDevice;
private readonly SpriteBatch _spriteBatch;
private readonly int _internalWidth;
private readonly int _internalHeight;
private readonly Texture2D _fractalTexture;
private readonly Color[] _pixelBuffer;
private readonly Color[] _postBuffer;
private readonly Color[] _previousFrame;
private readonly List<NoteParticle> _notes = [];
private readonly List<FireworkRocket> _fireworkRockets = [];
private readonly List<FireworkSpark> _fireworkSparks = [];
private readonly Random _rng = new();
private float _noteSpawnAccumulator;
private float _fireworkSpawnAccumulator;
private bool _disposed;
public FractalRenderer(GraphicsDevice graphicsDevice, int internalWidth = 320, int internalHeight = 180)
{
_graphicsDevice = graphicsDevice ?? throw new ArgumentNullException(nameof(graphicsDevice));
_spriteBatch = new SpriteBatch(_graphicsDevice);
_internalWidth = internalWidth;
_internalHeight = internalHeight;
_fractalTexture = new Texture2D(_graphicsDevice, _internalWidth, _internalHeight, false, SurfaceFormat.Color);
_pixelBuffer = new Color[_internalWidth * _internalHeight];
_postBuffer = new Color[_internalWidth * _internalHeight];
_previousFrame = new Color[_internalWidth * _internalHeight];
}
public void Draw(AudioFeatures features, float timeSeconds, int presetIndex)
{
ThrowIfDisposed();
switch (presetIndex)
{
case 0:
RenderJulia(features, timeSeconds);
break;
case 1:
RenderMandelbulbStyle(features, timeSeconds);
break;
case 2:
RenderKaleidoscopeTunnel(features, timeSeconds);
break;
case 3:
RenderFlame(features, timeSeconds);
break;
case 4:
RenderPlasma(features, timeSeconds);
break;
case 5:
RenderNebula(features, timeSeconds);
break;
case 6:
RenderFlameGrill(features, timeSeconds);
break;
case 7:
RenderFloatingNotes(features, timeSeconds);
break;
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;
}
ApplyFeedbackAndCrt(features, timeSeconds);
_fractalTexture.SetData(_postBuffer);
Viewport viewport = _graphicsDevice.Viewport;
Rectangle destination = new(0, 0, viewport.Width, viewport.Height);
_spriteBatch.Begin(samplerState: SamplerState.LinearClamp, blendState: BlendState.Opaque);
_spriteBatch.Draw(_fractalTexture, destination, Color.White);
_spriteBatch.End();
}
private void ApplyFeedbackAndCrt(AudioFeatures features, float timeSeconds)
{
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) + (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++)
{
float ny = (y / (_internalHeight - 1f)) * 2f - 1f;
float scanline = 0.90f + (0.10f * MathF.Sin(((y * 0.42f) + (timeSeconds * 22f)) * MathF.PI));
for (int x = 0; x < _internalWidth; x++)
{
int index = (y * _internalWidth) + x;
Vector3 raw = _pixelBuffer[index].ToVector3();
Vector3 previous = _previousFrame[index].ToVector3();
Vector3 trailed = Vector3.Lerp(raw, previous, trailStrength);
int xr = Math.Min(_internalWidth - 1, x + shiftPixels);
int xb = Math.Max(0, x - shiftPixels);
int rightIndex = (y * _internalWidth) + xr;
int leftIndex = (y * _internalWidth) + xb;
float r = _pixelBuffer[rightIndex].R / 255f;
float g = trailed.Y;
float b = _pixelBuffer[leftIndex].B / 255f;
Vector3 crt = new(r, g, b);
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);
crt.Z = Clamp01(crt.Z);
Color finalColor = new(crt.X, crt.Y, crt.Z, 1f);
_postBuffer[index] = finalColor;
_previousFrame[index] = finalColor;
}
}
}
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);
float mid = Clamp01(features.Mid * 0.003f);
float high = Clamp01(features.High * 0.0025f);
float beat = Clamp01(features.Beat);
float power = 6f + (bass * 4f);
float detail = 28f + (high * 22f);
float rotation = timeSeconds * (0.25f + mid * 0.8f);
float aspect = _internalWidth / (float)_internalHeight;
float cosR = MathF.Cos(rotation);
float sinR = MathF.Sin(rotation);
for (int y = 0; y < _internalHeight; y++)
{
float sy = (y / (float)_internalHeight) * 2f - 1f;
for (int x = 0; x < _internalWidth; x++)
{
float sx = ((x / (float)_internalWidth) * 2f - 1f) * aspect;
float rayX = sx;
float rayY = sy;
float rayZ = 1.65f - beat * 0.25f;
float march = 0f;
float glow = 0f;
float minDist = 10f;
int steps = (int)detail;
for (int step = 0; step < steps; step++)
{
float px = rayX * march;
float py = rayY * march;
float pz = rayZ - march;
float rpx = (px * cosR) - (pz * sinR);
float rpz = (px * sinR) + (pz * cosR);
float d = MandelbulbDistance(rpx, py, rpz, power);
minDist = MathF.Min(minDist, d);
glow += MathF.Exp(-20f * d) * (0.015f + high * 0.03f);
march += d * 0.95f;
if (d < 0.002f || march > 3.2f)
{
break;
}
}
float hit = Clamp01(1f - (minDist * 110f));
float fog = Clamp01(1f - (march / 3.2f));
float pulse = beat * 0.45f;
float r = Clamp01(hit * (0.35f + bass * 1.2f + pulse) + glow * 1.3f);
float g = Clamp01(hit * (0.22f + mid * 1.1f) + glow * 0.6f);
float b = Clamp01(hit * (0.45f + high * 1.5f) + glow * 1.5f + fog * 0.08f);
_pixelBuffer[(y * _internalWidth) + x] = new Color(r, g, b, 1f);
}
}
}
private void RenderKaleidoscopeTunnel(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.01f);
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);
float aspect = _internalWidth / (float)_internalHeight;
float tunnelSpeed = 0.45f + bass * 1.35f;
float spin = timeSeconds * (0.25f + mid * 1.1f);
float slices = 6f + MathF.Floor(high * 8f);
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 radius = MathF.Sqrt((px * px) + (py * py)) + 1e-5f;
float angle = MathF.Atan2(py, px) + spin;
float wedge = MathF.PI / slices;
angle = MathF.Abs((angle % (2f * wedge)) - wedge);
float spectrumDrive = GetSpectrumSample(features, radius * 8f + beat * 10f);
float distortion = spectrumDrive * (0.55f + high * 0.7f);
float tunnel = MathF.Sin((1f / radius) * 9f - timeSeconds * tunnelSpeed * 7f + distortion * 8f);
float rings = MathF.Sin(radius * 35f - timeSeconds * (2f + mid * 4f));
float edge = MathF.Exp(-10f * MathF.Abs(angle - wedge * 0.5f));
float pulse = beat * 0.6f;
float brightness = Clamp01((tunnel * 0.5f + 0.5f) * (0.4f + level) + edge * 0.5f + pulse * 0.3f);
float hueShift = (timeSeconds * 0.15f) + (beat * 0.25f);
float r = Clamp01(brightness * (0.5f + 0.5f * MathF.Sin(hueShift + 0.0f)) + rings * 0.08f + bass * 0.25f);
float g = Clamp01(brightness * (0.5f + 0.5f * MathF.Sin(hueShift + 2.09f)) + rings * 0.06f + mid * 0.25f);
float b = Clamp01(brightness * (0.5f + 0.5f * MathF.Sin(hueShift + 4.18f)) + rings * 0.10f + high * 0.35f);
_pixelBuffer[(y * _internalWidth) + x] = new Color(r, g, b, 1f);
}
}
}
private float GetSpectrumSample(AudioFeatures features, float virtualIndex)
{
if (features.Spectrum is null || features.Spectrum.Length == 0)
{
return 0f;
}
int i0 = ((int)virtualIndex) % features.Spectrum.Length;
if (i0 < 0)
{
i0 += features.Spectrum.Length;
}
int i1 = (i0 + 1) % features.Spectrum.Length;
float t = virtualIndex - MathF.Floor(virtualIndex);
float sample = features.Spectrum[i0] + ((features.Spectrum[i1] - features.Spectrum[i0]) * t);
return Clamp01(sample * 0.06f);
}
private static float MandelbulbDistance(float x, float y, float z, float power)
{
float zx = x;
float zy = y;
float zz = z;
float dr = 1f;
float r = 0f;
for (int i = 0; i < 7; i++)
{
r = MathF.Sqrt((zx * zx) + (zy * zy) + (zz * zz));
if (r > 2f)
{
break;
}
float theta = MathF.Acos(zz / (r + 1e-6f));
float phi = MathF.Atan2(zy, zx);
float zr = MathF.Pow(r, power);
dr = MathF.Pow(r, power - 1f) * power * dr + 1f;
float sinTheta = MathF.Sin(theta * power);
zx = zr * sinTheta * MathF.Cos(phi * power) + x;
zy = zr * sinTheta * MathF.Sin(phi * power) + y;
zz = zr * MathF.Cos(theta * power) + z;
}
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);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float level = Clamp01(features.Level * 3f);
float aspect = _internalWidth / (float)_internalHeight;
float flowSpeed = 0.65f + (bass * 1.8f);
float turbulence = 1.2f + (high * 3.0f);
float pulse = 0.2f + (beat * 0.8f);
for (int y = 0; y < _internalHeight; y++)
{
float y01 = y / (float)(_internalHeight - 1);
float fy = 1f - y01;
for (int x = 0; x < _internalWidth; x++)
{
float x01 = x / (float)(_internalWidth - 1);
float fx = (x01 * 2f - 1f) * aspect;
float t = timeSeconds * flowSpeed;
float n1 = MathF.Sin((fx * 6.0f) + (fy * 7.5f) - t * 5.0f);
float n2 = MathF.Sin((fx * 12.0f - t * 2.1f) + MathF.Cos(fy * 10.0f + t * 3.3f));
float n3 = MathF.Sin((fx * 22.0f + t * 1.4f) + MathF.Sin(fy * 18.0f - t * 2.7f));
float noise = ((n1 * 0.55f) + (n2 * 0.30f) + (n3 * 0.15f)) * turbulence;
noise = noise * 0.5f + 0.5f;
float cone = 1f - MathF.Abs(fx) * (0.55f + fy * 0.65f);
cone = Clamp01(cone);
float baseIntensity = MathF.Pow(fy, 1.8f);
float flame = Clamp01(baseIntensity * cone * (0.6f + noise * 0.9f + pulse * 0.25f));
flame *= 0.65f + level * 0.75f;
float ember = MathF.Sin((fx * 45f) + (fy * 14f) - t * 18f);
float sparks = ember > 0.985f ? 0.35f + beat * 0.45f : 0f;
float r = Clamp01(flame * 2.1f + sparks);
float g = Clamp01(MathF.Pow(flame, 1.35f) * (0.95f + high * 0.45f) + sparks * 0.7f);
float b = Clamp01(MathF.Pow(flame, 3.2f) * (0.25f + beat * 0.25f));
_pixelBuffer[(y * _internalWidth) + x] = new Color(r, g, b, 1f);
}
}
}
private void RenderPlasma(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.01f);
float mid = Clamp01(features.Mid * 0.003f);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float level = Clamp01(features.Level * 3f);
float aspect = _internalWidth / (float)_internalHeight;
float speed = 0.8f + bass * 1.6f;
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 t = timeSeconds * speed;
float v1 = MathF.Sin((px * 4.5f) + t * 1.9f);
float v2 = MathF.Sin((py * 5.0f) - t * 1.3f);
float v3 = MathF.Sin((px + py) * 4.0f + t * (2.2f + mid * 2f));
float v4 = MathF.Sin(MathF.Sqrt(px * px + py * py) * 10f - t * (3f + high * 3f));
float plasma = (v1 + v2 + v3 + v4) * 0.25f;
float p = plasma * 0.5f + 0.5f;
float pulse = beat * 0.35f;
float spec = GetSpectrumSample(features, (x * 0.08f) + (y * 0.03f));
float r = Clamp01((0.35f + 0.65f * MathF.Sin((p + pulse) * 6.28f)) * (0.6f + level * 0.6f));
float g = Clamp01((0.35f + 0.65f * MathF.Sin((p + 0.33f) * 6.28f + spec * 2f)) * (0.55f + mid * 0.8f));
float b = Clamp01((0.35f + 0.65f * MathF.Sin((p + 0.66f) * 6.28f + spec * 3f)) * (0.55f + high * 0.9f + pulse));
_pixelBuffer[(y * _internalWidth) + x] = new Color(r, g, b, 1f);
}
}
}
private void RenderNebula(AudioFeatures features, float timeSeconds)
{
float bass = Clamp01(features.Bass * 0.01f);
float mid = Clamp01(features.Mid * 0.003f);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float level = Clamp01(features.Level * 3f);
float aspect = _internalWidth / (float)_internalHeight;
float zoom = 1.6f - bass * 0.5f;
float driftX = MathF.Sin(timeSeconds * (0.16f + mid * 0.4f)) * 0.45f;
float driftY = MathF.Cos(timeSeconds * (0.12f + high * 0.5f)) * 0.45f;
for (int y = 0; y < _internalHeight; y++)
{
float py = ((y / (float)_internalHeight) * 2f - 1f) * zoom;
for (int x = 0; x < _internalWidth; x++)
{
float px = (((x / (float)_internalWidth) * 2f - 1f) * aspect) * zoom;
float fx = px + driftX;
float fy = py + driftY;
float density = 0f;
float amp = 0.55f;
float freq = 1.1f;
for (int o = 0; o < 5; o++)
{
float n = MathF.Sin((fx * freq + timeSeconds * 0.15f) * 2.1f)
* MathF.Cos((fy * freq - timeSeconds * 0.19f) * 2.3f);
density += (n * 0.5f + 0.5f) * amp;
freq *= 1.9f;
amp *= 0.55f;
}
float rad = MathF.Sqrt(fx * fx + fy * fy);
float core = MathF.Exp(-rad * (1.6f + bass * 1.2f));
float stars = GetSpectrumSample(features, rad * 24f + timeSeconds * 8f);
float pulse = beat * 0.45f;
float cloud = Clamp01(density * (0.6f + level * 0.5f));
float r = Clamp01(cloud * (0.25f + mid * 0.8f) + core * (0.8f + pulse));
float g = Clamp01(cloud * (0.35f + high * 0.9f) + core * 0.55f + stars * 0.35f);
float b = Clamp01(cloud * (0.55f + bass * 0.8f) + core * 0.75f + stars * 0.5f + pulse * 0.2f);
_pixelBuffer[(y * _internalWidth) + x] = new Color(r, g, b, 1f);
}
}
}
private void RenderFlameGrill(AudioFeatures features, float timeSeconds)
{
float level = Clamp01(features.Level * 3f);
float bass = Clamp01(features.Bass * 0.012f);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float intensity = Clamp01(0.25f + level * 0.45f + bass * 0.35f + beat * 0.35f);
float flameHeight = 0.22f + intensity * 0.68f;
float flickerSpeed = 2.2f + bass * 4.0f;
int grillTop = (int)(_internalHeight * 0.86f);
float barSpacing = 14f;
float barThickness = 3.5f;
for (int y = 0; y < _internalHeight; y++)
{
float y01 = y / (float)(_internalHeight - 1);
float fromBottom = 1f - y01;
for (int x = 0; x < _internalWidth; x++)
{
int idx = (y * _internalWidth) + x;
float x01 = x / (float)(_internalWidth - 1);
float wave1 = MathF.Sin((x01 * 18f) + timeSeconds * flickerSpeed);
float wave2 = MathF.Sin((x01 * 36f) - timeSeconds * (flickerSpeed * 0.65f));
float wave3 = MathF.Sin((x01 * 72f) + timeSeconds * (flickerSpeed * 1.35f));
float turbulence = (wave1 * 0.45f + wave2 * 0.35f + wave3 * 0.20f) * (0.22f + high * 0.55f);
float localHeight = flameHeight + turbulence;
float flameMask = Clamp01((fromBottom - 0.02f) / MathF.Max(0.05f, localHeight));
float taper = Clamp01(1f - MathF.Pow(fromBottom / MathF.Max(0.05f, localHeight), 1.35f));
float flameCore = flameMask * taper;
float tongues = MathF.Sin((x01 * 110f) + timeSeconds * (6f + high * 8f) - fromBottom * 22f);
float tongueMask = Clamp01(tongues * 0.5f + 0.5f);
float flame = flameCore * (0.72f + tongueMask * 0.45f);
flame *= 0.65f + intensity * 0.7f;
float emberNoise = MathF.Sin((x01 * 180f) + (fromBottom * 90f) - timeSeconds * 18f);
float sparks = emberNoise > 0.992f && fromBottom < localHeight ? 0.55f + beat * 0.35f : 0f;
float r = Clamp01(flame * 2.4f + sparks);
float g = Clamp01(MathF.Pow(flame, 1.4f) * (1.05f + high * 0.35f) + sparks * 0.55f);
float b = Clamp01(MathF.Pow(flame, 3.1f) * (0.22f + beat * 0.18f));
if (y >= grillTop)
{
float line = MathF.Abs(((x % barSpacing) - barSpacing * 0.5f));
float barMask = Clamp01(1f - (line / barThickness));
float hot = 0.25f + intensity * 0.55f;
float metal = 0.08f;
float grillR = Clamp01(metal + barMask * hot);
float grillG = Clamp01(metal * 0.7f + barMask * hot * 0.45f);
float grillB = Clamp01(metal * 0.6f + barMask * hot * 0.18f);
r = MathF.Max(r * 0.45f, grillR);
g = MathF.Max(g * 0.45f, grillG);
b = MathF.Max(b * 0.45f, grillB);
}
_pixelBuffer[idx] = new Color(r, g, b, 1f);
}
}
}
private void RenderFloatingNotes(AudioFeatures features, float timeSeconds)
{
float level = Clamp01(features.Level * 3f);
float bass = Clamp01(features.Bass * 0.012f);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float intensity = Clamp01(level * 0.6f + bass * 0.25f + beat * 0.35f);
for (int y = 0; y < _internalHeight; y++)
{
float t = y / (float)(_internalHeight - 1);
float r = 0.03f + 0.08f * (1f - t);
float g = 0.02f + 0.05f * (1f - t);
float b = 0.08f + 0.20f * (1f - t);
Color bg = new(r, g, b, 1f);
int row = y * _internalWidth;
for (int x = 0; x < _internalWidth; x++)
{
_pixelBuffer[row + x] = bg;
}
}
float spawnRate = 0.08f + intensity * 1.8f + high * 0.7f;
_noteSpawnAccumulator += spawnRate;
while (_noteSpawnAccumulator >= 1f)
{
SpawnNote(intensity);
_noteSpawnAccumulator -= 1f;
}
for (int i = _notes.Count - 1; i >= 0; i--)
{
NoteParticle note = _notes[i];
note.Y += note.Vy;
note.X += note.Vx + MathF.Sin(timeSeconds * 2.8f + note.Phase) * 0.0012f;
note.Life -= 0.012f + (1f - intensity) * 0.004f;
if (note.Life <= 0f || note.Y < -0.12f || note.X < -0.2f || note.X > 1.2f)
{
_notes.RemoveAt(i);
continue;
}
_notes[i] = note;
DrawNote(note);
}
}
private void RenderFireworks(AudioFeatures features, float timeSeconds)
{
float level = Clamp01(features.Level * 3f);
float bass = Clamp01(features.Bass * 0.012f);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float intensity = Clamp01(level * 0.55f + bass * 0.30f + beat * 0.45f);
for (int y = 0; y < _internalHeight; y++)
{
float t = y / (float)(_internalHeight - 1);
float r = 0.01f + 0.05f * (1f - t);
float g = 0.01f + 0.04f * (1f - t);
float b = 0.04f + 0.16f * (1f - t);
Color bg = new(r, g, b, 1f);
int row = y * _internalWidth;
for (int x = 0; x < _internalWidth; x++)
{
_pixelBuffer[row + x] = bg;
}
}
float spawnRate = 0.02f + intensity * 0.45f + high * 0.15f;
_fireworkSpawnAccumulator += spawnRate;
while (_fireworkSpawnAccumulator >= 1f)
{
SpawnFireworkRocket(intensity);
_fireworkSpawnAccumulator -= 1f;
}
for (int i = _fireworkRockets.Count - 1; i >= 0; i--)
{
FireworkRocket rocket = _fireworkRockets[i];
rocket.Y += rocket.Vy;
rocket.X += rocket.Vx;
rocket.Age += 0.016f;
DrawFilledCircle(
(int)(rocket.X * _internalWidth),
(int)(rocket.Y * _internalHeight),
1 + (int)(rocket.Size * 0.6f),
rocket.Color,
0.95f);
bool explode = rocket.Y <= rocket.TargetY || rocket.Age >= rocket.MaxAge;
if (explode)
{
ExplodeFirework(rocket, intensity);
_fireworkRockets.RemoveAt(i);
}
else
{
_fireworkRockets[i] = rocket;
}
}
for (int i = _fireworkSparks.Count - 1; i >= 0; i--)
{
FireworkSpark spark = _fireworkSparks[i];
spark.X += spark.Vx;
spark.Y += spark.Vy;
spark.Vy += 0.00022f;
spark.Vx *= 0.995f;
spark.Life -= 0.012f;
if (spark.Life <= 0f || spark.Y > 1.1f || spark.X < -0.2f || spark.X > 1.2f)
{
_fireworkSparks.RemoveAt(i);
continue;
}
_fireworkSparks[i] = spark;
int sx = (int)(spark.X * _internalWidth);
int sy = (int)(spark.Y * _internalHeight);
DrawFilledCircle(sx, sy, spark.Size, spark.Color, spark.Life);
}
}
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;
float y = 1.02f;
float vy = -(0.0065f + (float)_rng.NextDouble() * (0.0025f + intensity * 0.0025f));
float vx = ((float)_rng.NextDouble() - 0.5f) * 0.0018f;
float targetY = 0.2f + (float)_rng.NextDouble() * 0.42f;
float maxAge = 0.7f + (float)_rng.NextDouble() * 0.45f;
Vector3 color = new(
0.55f + (float)_rng.NextDouble() * 0.45f,
0.45f + (float)_rng.NextDouble() * 0.55f,
0.45f + (float)_rng.NextDouble() * 0.55f);
_fireworkRockets.Add(new FireworkRocket
{
X = x,
Y = y,
Vx = vx,
Vy = vy,
TargetY = targetY,
Age = 0f,
MaxAge = maxAge,
Size = 1.4f + (float)_rng.NextDouble() * 1.4f,
Color = color
});
}
private void ExplodeFirework(FireworkRocket rocket, float intensity)
{
int count = 22 + (int)(intensity * 38f);
float speedBase = 0.0018f + intensity * 0.0034f;
for (int i = 0; i < count; i++)
{
float a = (float)(i * (Math.PI * 2.0 / count));
float speed = speedBase + (float)_rng.NextDouble() * speedBase;
float vx = MathF.Cos(a) * speed;
float vy = MathF.Sin(a) * speed;
_fireworkSparks.Add(new FireworkSpark
{
X = rocket.X,
Y = rocket.Y,
Vx = vx,
Vy = vy,
Life = 0.65f + (float)_rng.NextDouble() * 0.35f,
Size = 1 + _rng.Next(0, 2),
Color = rocket.Color
});
}
}
private void SpawnNote(float intensity)
{
float x = 0.08f + (float)_rng.NextDouble() * 0.84f;
float y = 0.94f + (float)_rng.NextDouble() * 0.08f;
float vy = -(0.0025f + (float)_rng.NextDouble() * (0.0035f + intensity * 0.004f));
float vx = ((float)_rng.NextDouble() - 0.5f) * (0.0018f + intensity * 0.0015f);
float size = 0.75f + (float)_rng.NextDouble() * (1.6f + intensity * 1.2f);
bool flagged = _rng.NextDouble() > 0.42;
Vector3 color = new(
0.7f + (float)_rng.NextDouble() * 0.3f,
0.55f + (float)_rng.NextDouble() * 0.45f,
0.4f + (float)_rng.NextDouble() * 0.6f);
_notes.Add(new NoteParticle
{
X = x,
Y = y,
Vx = vx,
Vy = vy,
Life = 1f,
Size = size,
Phase = (float)_rng.NextDouble() * MathF.PI * 2f,
Color = color,
HasFlag = flagged
});
}
private void DrawNote(NoteParticle note)
{
int px = (int)(note.X * _internalWidth);
int py = (int)(note.Y * _internalHeight);
int s = Math.Max(1, (int)MathF.Round(note.Size));
float alpha = Clamp01(note.Life);
Vector3 color = note.Color;
int headX = px;
int headY = py;
DrawFilledCircle(headX, headY, s + 1, color, alpha * 0.75f);
DrawFilledCircle(headX, headY, s, color, alpha);
int stemHeight = 6 + s * 4;
DrawRect(headX + s, headY - stemHeight, 2, stemHeight, color, alpha);
if (note.HasFlag)
{
for (int i = 0; i < 6 + s; i++)
{
int fx = headX + s + 1 + i;
int fy = headY - stemHeight + (int)(MathF.Sin(i * 0.55f) * (2f + s * 0.25f));
DrawRect(fx, fy, 2, 2, color, alpha * 0.95f);
}
}
}
private void DrawFilledCircle(int cx, int cy, int radius, Vector3 color, float alpha)
{
int r2 = radius * radius;
for (int oy = -radius; oy <= radius; oy++)
{
int yy = cy + oy;
if (yy < 0 || yy >= _internalHeight)
{
continue;
}
int oy2 = oy * oy;
for (int ox = -radius; ox <= radius; ox++)
{
if (ox * ox + oy2 > r2)
{
continue;
}
int xx = cx + ox;
if (xx < 0 || xx >= _internalWidth)
{
continue;
}
BlendPixel(xx, yy, color, alpha);
}
}
}
private void DrawRect(int x, int y, int width, int height, Vector3 color, float alpha)
{
for (int yy = y; yy < y + height; yy++)
{
if (yy < 0 || yy >= _internalHeight)
{
continue;
}
for (int xx = x; xx < x + width; xx++)
{
if (xx < 0 || xx >= _internalWidth)
{
continue;
}
BlendPixel(xx, yy, color, alpha);
}
}
}
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;
Vector3 baseColor = _pixelBuffer[idx].ToVector3();
Vector3 mixed = Vector3.Lerp(baseColor, color, Clamp01(alpha));
_pixelBuffer[idx] = new Color(Clamp01(mixed.X), Clamp01(mixed.Y), Clamp01(mixed.Z), 1f);
}
private void RenderJulia(AudioFeatures features, float timeSeconds)
{
float level = Clamp01(features.Level * 3f);
float bass = Clamp01(features.Bass * 0.015f);
float mid = Clamp01(features.Mid * 0.004f);
float high = Clamp01(features.High * 0.003f);
float beat = Clamp01(features.Beat);
float zoom = 1.1f - (bass * 0.45f) - (beat * 0.15f);
if (zoom < 0.45f)
{
zoom = 0.45f;
}
float cRe = -0.8f + (mid * 1.6f) + (MathF.Sin(timeSeconds * 0.31f) * 0.06f);
float cIm = 0.156f + ((high - 0.5f) * 1.0f) + (MathF.Cos(timeSeconds * 0.23f) * 0.04f);
int maxIterations = 42 + (int)(level * 54f);
float aspect = _internalWidth / (float)_internalHeight;
for (int y = 0; y < _internalHeight; y++)
{
float ny = ((y / (float)_internalHeight) * 2f - 1f) * zoom;
for (int x = 0; x < _internalWidth; x++)
{
float nx = (((x / (float)_internalWidth) * 2f - 1f) * aspect) * zoom;
float zr = nx;
float zi = ny;
int iter = 0;
while (iter < maxIterations)
{
float zr2 = zr * zr;
float zi2 = zi * zi;
if (zr2 + zi2 > 4f)
{
break;
}
zi = (2f * zr * zi) + cIm;
zr = zr2 - zi2 + cRe;
iter++;
}
float t = iter / (float)maxIterations;
float pulse = beat * 0.35f;
float r = MathF.Pow(t, 0.65f) * (0.35f + high * 1.2f + pulse);
float g = MathF.Pow(t, 1.2f) * (0.28f + mid * 1.1f);
float b = MathF.Pow(t, 0.85f) * (0.45f + bass * 1.5f + pulse);
_pixelBuffer[(y * _internalWidth) + x] = new Color(
Clamp01(r),
Clamp01(g),
Clamp01(b),
1f);
}
}
}
private static float Clamp01(float value)
{
if (value < 0f)
{
return 0f;
}
if (value > 1f)
{
return 1f;
}
return value;
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(FractalRenderer));
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_fractalTexture.Dispose();
_spriteBatch.Dispose();
_disposed = true;
}
private struct NoteParticle
{
public float X;
public float Y;
public float Vx;
public float Vy;
public float Life;
public float Size;
public float Phase;
public Vector3 Color;
public bool HasFlag;
}
private struct FireworkRocket
{
public float X;
public float Y;
public float Vx;
public float Vy;
public float TargetY;
public float Age;
public float MaxAge;
public float Size;
public Vector3 Color;
}
private struct FireworkSpark
{
public float X;
public float Y;
public float Vx;
public float Vy;
public float Life;
public int Size;
public Vector3 Color;
}
}