diff options
| author | cashmere <cashmere@autistici.org> | 2026-07-28 20:14:51 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-07-28 20:14:51 +0200 |
| commit | 32438ce25f69de99d2523b98237f62cfa90890d7 (patch) | |
| tree | 3a712c347dec0889bdea3328b4c817c2d9392b27 /rust/crates/audio/src/buckets.rs | |
| parent | f5a82152394fde4d8f740f664ab7737b82861618 (diff) | |
| download | opencut-32438ce25f69de99d2523b98237f62cfa90890d7.tar.gz | |
feat: port domain crates to Rust (session 1 complete)
All green: cargo test --workspace (~300 tests, 0 failures).
- timeline (45): placement, resize/ripple, split, snap, retime,
audio separation, scene helpers
- animation (73): bezier solver, channel sampling, keyframe ops,
split/clamp/clone, param definitions
- commands (9): Command trait, BatchCommand, history, 19 concrete
commands, freeze-frame port 1:1
- audio (22): audible collection, range mixing, mastering limiter,
rms/peak buckets
- text (19): cosmic-text layout/measure/raster, backgrounds,
decorations
- graphics (52): full CSS gradient parser, tiny-skia raster,
4 shape definitions
- effect-defs (16): blur definition, registry, pass resolution
- mask-defs (19): all 9 mask renderers via tiny-skia
- renderer (6): node model + scene builder port
- desktop: GPUI projects screen wired to ProjectStore
- scene: factory (web defaults: v32, Main Track, 1920x1080, 30fps)
Diffstat (limited to 'rust/crates/audio/src/buckets.rs')
| -rw-r--r-- | rust/crates/audio/src/buckets.rs | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/rust/crates/audio/src/buckets.rs b/rust/crates/audio/src/buckets.rs new file mode 100644 index 00000000..261c2373 --- /dev/null +++ b/rust/crates/audio/src/buckets.rs @@ -0,0 +1,105 @@ +pub const RMS_ANALYSIS_WINDOW_SECONDS: f64 = 0.02; + +pub fn sample_bucket_range( + start_sample: usize, + end_sample: usize, + bucket_index: usize, + bucket_count: usize, +) -> (usize, usize) { + let range_length = end_sample.saturating_sub(start_sample); + let bucket_start = start_sample + bucket_index * range_length / bucket_count; + let bucket_end = start_sample + (bucket_index + 1) * range_length / bucket_count; + (bucket_start, bucket_end.max(bucket_start)) +} + +pub fn extract_peak_range( + samples: &[f32], + channels: usize, + count: usize, + start_sample: usize, + end_sample: usize, +) -> Vec<f32> { + let channels = channels.max(1); + let frames = samples.len() / channels; + let end = end_sample.min(frames); + let start = start_sample.min(end); + let mut peaks = vec![0.0_f32; count]; + for (index, peak) in peaks.iter_mut().enumerate() { + let (bucket_start, bucket_end) = sample_bucket_range(start, end, index, count); + let mut bucket_peak = 0.0_f32; + for frame in bucket_start..bucket_end { + for channel in 0..channels { + let magnitude = samples[frame * channels + channel].abs(); + if magnitude > bucket_peak { + bucket_peak = magnitude; + } + } + } + *peak = bucket_peak; + } + peaks +} + +pub fn extract_rms_windowed( + samples: &[f32], + channels: usize, + sample_rate: u32, + buckets: &[(usize, usize)], +) -> Vec<f32> { + let channels = channels.max(1); + let max_window = + (f64::from(sample_rate) * RMS_ANALYSIS_WINDOW_SECONDS).floor().max(1.0) as usize; + buckets + .iter() + .map(|&(bucket_start, bucket_end)| { + let bucket_length = bucket_end.saturating_sub(bucket_start); + if bucket_length == 0 { + return 0.0; + } + let window_length = bucket_length.min(max_window).max(1); + let mut max_mean_square = 0.0_f64; + let mut window_start = bucket_start; + while window_start < bucket_end { + let window_end = (window_start + window_length).min(bucket_end); + let n = window_end - window_start; + let mut sum = 0.0_f64; + for frame in window_start..window_end { + for channel in 0..channels { + let value = f64::from(samples[frame * channels + channel]); + sum += value * value; + } + } + let mean_square = sum / (n * channels) as f64; + if mean_square > max_mean_square { + max_mean_square = mean_square; + } + window_start = window_end; + } + max_mean_square.sqrt() as f32 + }) + .collect() +} + +pub fn extract_rms_range( + samples: &[f32], + channels: usize, + sample_rate: u32, + count: usize, + start_sample: usize, + end_sample: usize, +) -> Vec<f32> { + let buckets: Vec<(usize, usize)> = (0..count) + .map(|index| sample_bucket_range(start_sample, end_sample, index, count)) + .collect(); + extract_rms_windowed(samples, channels, sample_rate, &buckets) +} + +pub fn extract_rms_buckets( + samples: &[f32], + channels: usize, + sample_rate: u32, + bucket_count: usize, +) -> Vec<f32> { + let frames = samples.len() / channels.max(1); + extract_rms_range(samples, channels, sample_rate, bucket_count, 0, frames) +} |
