Skip to content

Commit 3e41342

Browse files
committed
Fix visualizer rendering artifacts
1 parent 66532f0 commit 3e41342

6 files changed

Lines changed: 44 additions & 22 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/audio.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,12 +1780,14 @@ where
17801780
for i in 0..frames_read {
17811781
let l_val = chunk.samples[i * hardware_channels];
17821782
left_peak = left_peak.max(l_val.abs());
1783-
if l_val.abs() >= 1.0 { clips += 1; }
1783+
// Use a small epsilon above 1.0 to prevent 0dBFS peaks in losslessly
1784+
// mastered tracks from falsely triggering clipping events
1785+
if l_val.abs() > 1.0001 { clips += 1; }
17841786

17851787
if hardware_channels > 1 {
17861788
let r_val = chunk.samples[i * hardware_channels + 1];
17871789
right_peak = right_peak.max(r_val.abs());
1788-
if r_val.abs() >= 1.0 { clips += 1; }
1790+
if r_val.abs() > 1.0001 { clips += 1; }
17891791
} else {
17901792
right_peak = left_peak;
17911793
}

src/shaders/fire_compute.wgsl

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,23 +114,26 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
114114
}
115115
}
116116

117-
let coal_target = min(params.bass * 0.25 + activity * 2.5, 1.0);
117+
// Use exponential soft-clipping for AGC (Automatic Gain Control)
118+
// This ensures quiet tracks still generate nice flames, while brickwalled tracks smoothly approach 1.0 without hard clipping.
119+
let coal_target = 1.0 - exp(-(params.bass * 0.5 + activity * 3.0));
118120
let current = coal_bed[x];
119121
if (coal_target > current) {
120122
coal_bed[x] = current + (coal_target - current) * 0.18;
121123
} else {
122124
coal_bed[x] = current + (coal_target - current) * 0.008;
123125
}
124126

125-
let jitter = (perlin_noise(vec2<f32>(f32(x) * 0.5, params.time * 10.0)) + 1.0) * 0.5;
127+
// Use lower frequency noise to create wide, organic hotspots rather than single-pixel static
128+
let jitter = (perlin_noise(vec2<f32>(f32(x) * 0.05, params.time * 10.0)) + 1.0) * 0.5;
126129
output_grid[idx] = min(coal_bed[x] * (0.7 + 0.3 * jitter), 1.0);
127130
return;
128131
}
129132

130133
// === Hearth rows: inject coal heat ===
131134
if (y >= bottom - 2u) {
132135
let coal_heat = min(coal_bed[x], 1.0);
133-
let jitter = (perlin_noise(vec2<f32>(f32(x) * 0.5, params.time * 10.0)) + 1.0) * 0.5;
136+
let jitter = (perlin_noise(vec2<f32>(f32(x) * 0.05, params.time * 10.0)) + 1.0) * 0.5;
134137

135138
if (y == bottom - 1u) {
136139
output_grid[idx] = coal_heat * (0.85 + 0.15 * jitter);
@@ -152,16 +155,31 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
152155
// Global wind swaying the fire
153156
let wind = i32(sin(params.time * 1.5 + f32(x) * 0.003) * params.highs * 2.0);
154157

155-
// Cellular Automaton: read from exactly one pixel in the row below
158+
// Classic DOS fire averaging: blend adjacent pixels to allow heat to diffuse into organic shapes
156159
let src_x = clamp(i32(x) + spread + wind, 0i, i32(w) - 1);
157-
let heat = input_grid[(y + 1u) * w + u32(src_x)];
160+
let xl = max(src_x - 1, 0i);
161+
let xr = min(src_x + 1, i32(w) - 1);
162+
163+
let h1 = input_grid[(y + 1u) * w + u32(xl)];
164+
let h2 = input_grid[(y + 1u) * w + u32(src_x)];
165+
let h3 = input_grid[(y + 1u) * w + u32(xr)];
166+
167+
// Weighted horizontal blur favors the center pixel
168+
let heat = (h1 + h2 * 2.0 + h3) / 4.0;
158169

159170
// Cooling varies spatially to create uneven tips and licks
160-
let cool_noise = perlin_noise(p1 * 3.0 - vec2<f32>(params.time * 2.0, 0.0));
161-
let base_cooling = 0.001 + (cool_noise + 1.0) * 0.0008;
171+
// Stretch the noise vertically and move it upwards to simulate rising flame tongues
172+
let p_cool = vec2<f32>(f32(x) * 0.015, f32(y) * 0.03);
173+
let cool_noise = perlin_noise(p_cool + vec2<f32>(0.0, params.time * 6.0));
174+
175+
// Higher variance in cooling forces the flame to tear into distinct licks
176+
let base_cooling = 0.0015 + (cool_noise + 1.0) * 0.0015;
162177

163178
// Occasional sparks (holes in the flame) for realism, clumped by noise
164179
let spark = select(0.0, 0.06, cool_noise > 0.8 && y > 100u);
165180

166-
output_grid[idx] = max(heat - base_cooling * params.cooling_factor - spark, 0.0);
181+
// Normalize the cooling factor so heavily compressed tracks don't turn off cooling entirely
182+
let dynamic_cooling = mix(0.6, 1.0, params.cooling_factor);
183+
184+
output_grid[idx] = max(heat - base_cooling * dynamic_cooling - spark, 0.0);
167185
}

src/shaders/vis_3doscilloscope.wgsl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
111111
var x_prev = -9.6 + f32(j_u) / (res - 1.0) * 19.2;
112112
var mask_prev = smoothstep(9.6, 6.6, abs(x_prev));
113113
var v_prev = get_waveform(hist_idx, j_u);
114-
var p_prev = abs(v_prev) * mask_prev * 6.0;
114+
var p_prev = v_prev * mask_prev * 1.2;
115115
var p3_prev = vec3<f32>(x_prev, y_line, p_prev);
116116
var proj_prev = project_3d(p3_prev, ro, u, v_cam, w);
117117

@@ -120,7 +120,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
120120
let x_curr = -9.6 + f32(j_u) / (res - 1.0) * 19.2;
121121
let mask_curr = smoothstep(9.6, 6.6, abs(x_curr));
122122
let v_curr = get_waveform(hist_idx, j_u);
123-
let p_curr = abs(v_curr) * mask_curr * 6.0;
123+
let p_curr = v_curr * mask_curr * 1.2;
124124
let p3_curr = vec3<f32>(x_curr, y_line, p_curr);
125125
let proj_curr = project_3d(p3_curr, ro, u, v_cam, w);
126126

src/shaders/vis_3doscilloscope_freq.wgsl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
114114
let v1 = get_resynthesized_wave(i, x_norm1);
115115

116116
// Allow true negative waveforms, just apply mask
117-
let p0 = v0 * mask0 * 4.0;
118-
let p1 = v1 * mask1 * 4.0;
117+
let p0 = v0 * mask0 * 1.2;
118+
let p1 = v1 * mask1 * 1.2;
119119

120120
let p3_0 = vec3<f32>(x0, y_line, p0);
121121
let p3_1 = vec3<f32>(x1, y_line, p1);

src/shaders/vis_oscilloscope.wgsl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ fn get_wave_dist(hist_idx: u32, uv: vec2<f32>, aspect: f32) -> f32 {
4747
var min_dist = 1000.0;
4848

4949
// Check local neighborhood for proper line segment coverage
50-
let start_idx = max(0i, i32(idx) - 1);
51-
let end_idx = min(i32(res) - 2i, i32(idx) + 1);
50+
// Since lines are thin, a small search radius is sufficient to prevent horizontal clipping
51+
let search_radius = clamp(i32(res * 0.003), 2, 8);
52+
let start_idx = max(0i, i32(idx) - search_radius);
53+
let end_idx = min(i32(res) - 2i, i32(idx) + search_radius);
5254

5355
let p = vec2<f32>(uv.x * aspect, uv.y);
5456

@@ -109,15 +111,15 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
109111
// 0.05 gives a long 1-second smooth trail.
110112
let age = exp(-frames_old * 0.06);
111113

112-
// Beam thickness scales down with dynamic resolution to prevent huge blobs
113-
let thickness = 0.002 + edge_blur * 0.006;
114-
let core = smoothstep(thickness, 0.0, true_dist) * 0.4;
114+
// Much thinner beam for crisp, high-resolution lines instead of thick noodles
115+
let thickness = 0.0005 + edge_blur * 0.0015;
116+
let core = smoothstep(thickness, 0.0, true_dist) * 0.6;
115117

116118
// Tighter bloom (reduced spread and intensity)
117-
let bloom = 0.0002 / (true_dist * true_dist + 0.001) * 0.03;
119+
let bloom = 0.00005 / (true_dist * true_dist + 0.0002) * 0.02;
118120

119121
// Tighter halation (faster falloff)
120-
let halation = exp(-true_dist * 80.0) * 0.015;
122+
let halation = exp(-true_dist * 120.0) * 0.01;
121123

122124
let frame_intensity = (core + bloom + halation) * age * step_scale;
123125

0 commit comments

Comments
 (0)