summaryrefslogtreecommitdiff
path: root/rust/crates/audio/src/buckets.rs
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 20:14:51 +0200
committercashmere <cashmere@autistici.org>2026-07-28 20:14:51 +0200
commit32438ce25f69de99d2523b98237f62cfa90890d7 (patch)
tree3a712c347dec0889bdea3328b4c817c2d9392b27 /rust/crates/audio/src/buckets.rs
parentf5a82152394fde4d8f740f664ab7737b82861618 (diff)
downloadopencut-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.rs105
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)
+}