max / audiofiles
8 files changed,
+34 insertions,
-24 deletions
| @@ -130,7 +130,7 @@ pub(crate) fn fill_preview( | |||
| 130 | 130 | return; // GUI thread holds lock — leave buffer unchanged (already zeroed) | |
| 131 | 131 | }; | |
| 132 | 132 | ||
| 133 | - | if !guard.playing { | |
| 133 | + | if !guard.playing || device_sample_rate == 0 { | |
| 134 | 134 | return; | |
| 135 | 135 | } | |
| 136 | 136 |
| @@ -234,7 +234,7 @@ pub fn render_voices( | |||
| 234 | 234 | channels: usize, | |
| 235 | 235 | device_sr: u32, | |
| 236 | 236 | ) { | |
| 237 | - | if !inst.active || inst.zone_buffers.is_empty() || channels == 0 { | |
| 237 | + | if !inst.active || inst.zone_buffers.is_empty() || channels == 0 || device_sr == 0 { | |
| 238 | 238 | return; | |
| 239 | 239 | } | |
| 240 | 240 |
| @@ -153,7 +153,7 @@ fn compute_spectral_inner( | |||
| 153 | 153 | let arithmetic_mean = mag_sum / n; | |
| 154 | 154 | let log_sum: f64 = magnitudes | |
| 155 | 155 | .iter() | |
| 156 | - | .map(|&m| if m > 1e-10 { m.ln() } else { (-10.0f64).ln() * 2.3 }) | |
| 156 | + | .map(|&m| if m > 1e-10 { m.ln() } else { (1e-10_f64).ln() }) | |
| 157 | 157 | .sum(); | |
| 158 | 158 | let geometric_mean = (log_sum / n).exp(); | |
| 159 | 159 | let flat = if arithmetic_mean > 0.0 { |
| @@ -37,8 +37,9 @@ pub fn apply_fade_in(samples: &mut [f32], channels: u16, frames: usize, curve: F | |||
| 37 | 37 | let total_frames = samples.len() / ch; | |
| 38 | 38 | let fade_frames = frames.min(total_frames); | |
| 39 | 39 | ||
| 40 | + | let denom = (fade_frames - 1).max(1) as f32; | |
| 40 | 41 | for i in 0..fade_frames { | |
| 41 | - | let t = i as f32 / fade_frames as f32; | |
| 42 | + | let t = i as f32 / denom; | |
| 42 | 43 | let gain = curve.gain(t); | |
| 43 | 44 | for c in 0..ch { | |
| 44 | 45 | samples[i * ch + c] *= gain; | |
| @@ -57,8 +58,9 @@ pub fn apply_fade_out(samples: &mut [f32], channels: u16, frames: usize, curve: | |||
| 57 | 58 | let fade_frames = frames.min(total_frames); | |
| 58 | 59 | let start_frame = total_frames - fade_frames; | |
| 59 | 60 | ||
| 61 | + | let denom = (fade_frames - 1).max(1) as f32; | |
| 60 | 62 | for i in 0..fade_frames { | |
| 61 | - | let t = 1.0 - (i as f32 / fade_frames as f32); | |
| 63 | + | let t = 1.0 - (i as f32 / denom); | |
| 62 | 64 | let gain = curve.gain(t); | |
| 63 | 65 | let frame_idx = start_frame + i; | |
| 64 | 66 | for c in 0..ch { | |
| @@ -75,22 +77,22 @@ mod tests { | |||
| 75 | 77 | fn fade_in_linear_mono() { | |
| 76 | 78 | let mut samples = vec![1.0; 4]; | |
| 77 | 79 | apply_fade_in(&mut samples, 1, 4, FadeCurve::Linear); | |
| 78 | - | // Frame 0: gain=0.0, Frame 1: gain=0.25, Frame 2: gain=0.5, Frame 3: gain=0.75 | |
| 80 | + | // Frame 0: gain=0/3, Frame 1: gain=1/3, Frame 2: gain=2/3, Frame 3: gain=3/3 | |
| 79 | 81 | assert!((samples[0] - 0.0).abs() < 0.001); | |
| 80 | - | assert!((samples[1] - 0.25).abs() < 0.001); | |
| 81 | - | assert!((samples[2] - 0.5).abs() < 0.001); | |
| 82 | - | assert!((samples[3] - 0.75).abs() < 0.001); | |
| 82 | + | assert!((samples[1] - 1.0 / 3.0).abs() < 0.001); | |
| 83 | + | assert!((samples[2] - 2.0 / 3.0).abs() < 0.001); | |
| 84 | + | assert!((samples[3] - 1.0).abs() < 0.001); | |
| 83 | 85 | } | |
| 84 | 86 | ||
| 85 | 87 | #[test] | |
| 86 | 88 | fn fade_out_linear_mono() { | |
| 87 | 89 | let mut samples = vec![1.0; 4]; | |
| 88 | 90 | apply_fade_out(&mut samples, 1, 4, FadeCurve::Linear); | |
| 89 | - | // Frame 0: gain=1.0, Frame 1: gain=0.75, Frame 2: gain=0.5, Frame 3: gain=0.25 | |
| 91 | + | // Frame 0: gain=3/3, Frame 1: gain=2/3, Frame 2: gain=1/3, Frame 3: gain=0/3 | |
| 90 | 92 | assert!((samples[0] - 1.0).abs() < 0.001); | |
| 91 | - | assert!((samples[1] - 0.75).abs() < 0.001); | |
| 92 | - | assert!((samples[2] - 0.5).abs() < 0.001); | |
| 93 | - | assert!((samples[3] - 0.25).abs() < 0.001); | |
| 93 | + | assert!((samples[1] - 2.0 / 3.0).abs() < 0.001); | |
| 94 | + | assert!((samples[2] - 1.0 / 3.0).abs() < 0.001); | |
| 95 | + | assert!((samples[3] - 0.0).abs() < 0.001); | |
| 94 | 96 | } | |
| 95 | 97 | ||
| 96 | 98 | #[test] | |
| @@ -98,11 +100,13 @@ mod tests { | |||
| 98 | 100 | // 3 frames of stereo, all 1.0 | |
| 99 | 101 | let mut samples = vec![1.0; 6]; | |
| 100 | 102 | apply_fade_in(&mut samples, 2, 3, FadeCurve::Linear); | |
| 101 | - | // Frame 0: gain=0.0 → [0,0], Frame 1: gain=0.333 → [0.333,0.333], Frame 2: gain=0.667 | |
| 103 | + | // Frame 0: gain=0/2, Frame 1: gain=1/2, Frame 2: gain=2/2 | |
| 102 | 104 | assert!((samples[0]).abs() < 0.001); // L0 | |
| 103 | 105 | assert!((samples[1]).abs() < 0.001); // R0 | |
| 104 | - | assert!((samples[2] - 1.0 / 3.0).abs() < 0.01); | |
| 105 | - | assert!((samples[3] - 1.0 / 3.0).abs() < 0.01); | |
| 106 | + | assert!((samples[2] - 0.5).abs() < 0.01); | |
| 107 | + | assert!((samples[3] - 0.5).abs() < 0.01); | |
| 108 | + | assert!((samples[4] - 1.0).abs() < 0.01); | |
| 109 | + | assert!((samples[5] - 1.0).abs() < 0.01); | |
| 106 | 110 | } | |
| 107 | 111 | ||
| 108 | 112 | #[test] |
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | /// Apply a gain offset in dB to all samples, clamping to [-1.0, 1.0]. | |
| 4 | 4 | pub fn apply_gain(samples: &mut [f32], db: f64) { | |
| 5 | - | if samples.is_empty() || db == 0.0 { | |
| 5 | + | if samples.is_empty() || db == 0.0 || !db.is_finite() { | |
| 6 | 6 | return; | |
| 7 | 7 | } | |
| 8 | 8 |
| @@ -152,8 +152,8 @@ mod tests { | |||
| 152 | 152 | curve: FadeCurve::Linear, | |
| 153 | 153 | }; | |
| 154 | 154 | apply_edit(&mut samples, 1, 44100, &op).unwrap(); | |
| 155 | - | // Last frame should be near zero | |
| 156 | - | assert!((samples[3] - 0.25).abs() < 0.001); | |
| 155 | + | // Last frame should be zero (fade reaches silence) | |
| 156 | + | assert!((samples[3]).abs() < 0.001); | |
| 157 | 157 | } | |
| 158 | 158 | ||
| 159 | 159 | #[test] |
| @@ -58,7 +58,7 @@ pub fn apply_normalize_lufs( | |||
| 58 | 58 | let scale = 10.0_f64.powf(gain_db / 20.0) as f32; | |
| 59 | 59 | ||
| 60 | 60 | for sample in samples.iter_mut() { | |
| 61 | - | *sample *= scale; | |
| 61 | + | *sample = (*sample * scale).clamp(-1.0, 1.0); | |
| 62 | 62 | } | |
| 63 | 63 | ||
| 64 | 64 | Ok(()) |
| @@ -51,7 +51,7 @@ impl<T> VpTree<T> { | |||
| 51 | 51 | } | |
| 52 | 52 | let mut indices: Vec<usize> = (0..n).collect(); | |
| 53 | 53 | let mut nodes = Vec::with_capacity(n); | |
| 54 | - | let root = build_recursive(&items, &mut indices, &dist, &mut nodes); | |
| 54 | + | let root = build_recursive(&items, &mut indices, &dist, &mut nodes, 0); | |
| 55 | 55 | Self { | |
| 56 | 56 | items, | |
| 57 | 57 | root: Some(root), | |
| @@ -138,16 +138,22 @@ impl<T> VpTree<T> { | |||
| 138 | 138 | ||
| 139 | 139 | // --- Build --- | |
| 140 | 140 | ||
| 141 | + | /// Maximum recursion depth to prevent stack overflow on degenerate input | |
| 142 | + | /// (e.g. all-identical feature vectors causing O(n)-deep recursion). | |
| 143 | + | const MAX_BUILD_DEPTH: usize = 64; | |
| 144 | + | ||
| 141 | 145 | fn build_recursive<T>( | |
| 142 | 146 | items: &[T], | |
| 143 | 147 | indices: &mut [usize], | |
| 144 | 148 | dist: &impl Fn(&T, &T) -> f64, | |
| 145 | 149 | nodes: &mut Vec<VpNode>, | |
| 150 | + | depth: usize, | |
| 146 | 151 | ) -> usize { | |
| 147 | 152 | debug_assert!(!indices.is_empty()); | |
| 148 | 153 | ||
| 149 | 154 | let vp_idx = indices[0]; | |
| 150 | - | if indices.len() == 1 { | |
| 155 | + | // Leaf node: single item or depth cap reached | |
| 156 | + | if indices.len() == 1 || depth >= MAX_BUILD_DEPTH { | |
| 151 | 157 | let node_idx = nodes.len(); | |
| 152 | 158 | nodes.push(VpNode { | |
| 153 | 159 | item_idx: vp_idx, | |
| @@ -192,12 +198,12 @@ fn build_recursive<T>( | |||
| 192 | 198 | let left = if inside.is_empty() { | |
| 193 | 199 | None | |
| 194 | 200 | } else { | |
| 195 | - | Some(build_recursive(items, &mut inside, dist, nodes)) | |
| 201 | + | Some(build_recursive(items, &mut inside, dist, nodes, depth + 1)) | |
| 196 | 202 | }; | |
| 197 | 203 | let right = if outside.is_empty() { | |
| 198 | 204 | None | |
| 199 | 205 | } else { | |
| 200 | - | Some(build_recursive(items, &mut outside, dist, nodes)) | |
| 206 | + | Some(build_recursive(items, &mut outside, dist, nodes, depth + 1)) | |
| 201 | 207 | }; | |
| 202 | 208 | ||
| 203 | 209 | nodes[node_idx].left = left; |