1use std::path::Path;
21use std::process::{Command, Stdio};
22
23use crate::audioop;
24use crate::ffmpeg::{self, AudioError};
25
26pub mod libm {
33 extern "C" {
34 #[link_name = "pow"]
35 fn c_pow(x: f64, y: f64) -> f64;
36 #[link_name = "log"]
37 fn c_log(x: f64) -> f64;
38 #[link_name = "log10"]
39 fn c_log10(x: f64) -> f64;
40 }
41
42 pub fn ln(x: f64) -> f64 {
44 unsafe { c_log(std::hint::black_box(x)) }
46 }
47
48 pub fn pow(x: f64, y: f64) -> f64 {
52 unsafe { c_pow(std::hint::black_box(x), std::hint::black_box(y)) }
54 }
55
56 pub fn log10(x: f64) -> f64 {
57 unsafe { c_log10(std::hint::black_box(x)) }
59 }
60}
61
62pub fn db_to_float(db: f64) -> f64 {
64 libm::pow(10.0, db / 20.0)
65}
66
67pub fn ratio_to_db(ratio: f64) -> f64 {
71 if ratio == 0.0 {
72 return f64::NEG_INFINITY;
73 }
74 20.0 * (libm::ln(ratio) / libm::ln(10.0))
75}
76
77pub fn py_round(x: f64) -> i64 {
79 let r = x.round();
80 if (x - x.trunc()).abs() == 0.5 {
81 let t = x.trunc();
82 if (t as i64) % 2 == 0 {
83 t as i64
84 } else {
85 r as i64
86 }
87 } else {
88 r as i64
89 }
90}
91
92fn py_slice(data: &[u8], a: i64, b: i64) -> &[u8] {
95 let n = data.len() as i64;
96 let fix = |i: i64| {
97 let i = if i < 0 { i + n } else { i };
98 i.clamp(0, n) as usize
99 };
100 let (a, b) = (fix(a), fix(b));
101 if b <= a {
102 &[]
103 } else {
104 &data[a..b]
105 }
106}
107
108fn floor_div(a: i64, b: i64) -> i64 {
110 a.div_euclid(b) - if a.rem_euclid(b) != 0 && b < 0 { 1 } else { 0 }
111}
112
113#[derive(Clone, Debug, PartialEq)]
116pub struct Segment {
117 pub data: Vec<u8>,
118 pub sample_width: usize,
119 pub frame_rate: u32,
120 pub channels: usize,
121}
122
123impl Segment {
124 pub fn new(data: Vec<u8>, sample_width: usize, frame_rate: u32, channels: usize) -> Segment {
125 Segment {
126 data,
127 sample_width,
128 frame_rate,
129 channels,
130 }
131 }
132
133 fn spawn(&self, data: Vec<u8>) -> Segment {
134 Segment::new(data, self.sample_width, self.frame_rate, self.channels)
135 }
136
137 pub fn frame_width(&self) -> usize {
138 self.channels * self.sample_width
139 }
140
141 pub fn empty() -> Segment {
143 Segment::new(Vec::new(), 1, 1, 1)
144 }
145
146 pub fn silent_at(duration_ms: f64, frame_rate: u32) -> Segment {
148 let frames = (frame_rate as f64 * (duration_ms / 1000.0)) as i64;
149 Segment::new(vec![0u8; 2 * frames.max(0) as usize], 2, frame_rate, 1)
150 }
151
152 pub fn silent(duration_ms: f64) -> Segment {
154 Segment::silent_at(duration_ms, 11025)
155 }
156
157 pub fn frame_count(&self) -> f64 {
159 (self.data.len() / self.frame_width()) as f64
160 }
161
162 pub fn frame_count_ms(&self, ms: f64) -> f64 {
164 ms * (self.frame_rate as f64 / 1000.0)
165 }
166
167 pub fn len_ms(&self) -> i64 {
169 py_round(1000.0 * (self.frame_count() / self.frame_rate as f64))
170 }
171
172 fn parse_position(&self, val: f64) -> i64 {
173 let len = self.len_ms() as f64;
174 let val = if val < 0.0 { len - val.abs() } else { val };
175 let frames = if val == f64::INFINITY {
176 self.frame_count_ms(len)
177 } else {
178 self.frame_count_ms(val)
179 };
180 frames as i64
181 }
182
183 fn slice_frames(&self, start: f64, end: f64) -> Segment {
184 let fw = self.frame_width() as i64;
185 let s = self.parse_position(start) * fw;
186 let e = self.parse_position(end) * fw;
187 let mut data = py_slice(&self.data, s, e).to_vec();
188 let expected = e - s;
189 let missing = floor_div(expected - data.len() as i64, fw);
190 if missing != 0 {
191 if missing as f64 > self.frame_count_ms(2.0) {
192 panic!(
193 "TooManyMissingFrames: You should never be filling in more than 2 ms with silence here, missing frames: {missing}"
194 );
195 }
196 let head = &data[..data.len().min(fw as usize)];
197 let silence = audioop::mul(head, self.sample_width, 0.0);
198 if missing > 0 {
199 data.extend(silence.repeat(missing as usize));
200 }
201 }
202 self.spawn(data)
203 }
204
205 pub fn slice(&self, start: Option<f64>, end: Option<f64>) -> Segment {
207 let len = self.len_ms() as f64;
208 let start = start.unwrap_or(0.0).min(len);
209 let end = end.unwrap_or(len).min(len);
210 self.slice_frames(start, end)
211 }
212
213 pub fn at(&self, ms: f64) -> Segment {
215 self.slice_frames(ms, ms + 1.0)
216 }
217
218 fn get_frame(&self, index: i64) -> &[u8] {
220 let fw = self.frame_width() as i64;
221 py_slice(&self.data, index * fw, index * fw + fw)
222 }
223
224 pub fn repeat(&self, n: i64) -> Segment {
226 self.spawn(self.data.repeat(n.max(0) as usize))
227 }
228
229 pub fn gain(&self, db: f64) -> Segment {
231 self.spawn(audioop::mul(&self.data, self.sample_width, db_to_float(db)))
232 }
233
234 pub fn set_sample_width(self, sample_width: usize) -> Segment {
235 if sample_width == self.sample_width {
236 return self;
237 }
238 let data = audioop::lin2lin(&self.data, self.sample_width, sample_width);
239 Segment::new(data, sample_width, self.frame_rate, self.channels)
240 }
241
242 pub fn set_frame_rate(self, frame_rate: u32) -> Segment {
243 if frame_rate == self.frame_rate {
244 return self;
245 }
246 let data = if self.data.is_empty() {
247 self.data
248 } else {
249 audioop::ratecv(
250 &self.data,
251 self.sample_width,
252 self.channels,
253 self.frame_rate,
254 frame_rate,
255 )
256 };
257 Segment::new(data, self.sample_width, frame_rate, self.channels)
258 }
259
260 pub fn set_channels(self, channels: usize) -> Segment {
261 if channels == self.channels {
262 return self;
263 }
264 let data = match (self.channels, channels) {
265 (1, 2) => audioop::tostereo(&self.data, self.sample_width, 1.0, 1.0),
266 (2, 1) => audioop::tomono(&self.data, self.sample_width, 0.5, 0.5),
267 (from, to) => panic!("set_channels: {from} -> {to} is not used by the pipeline"),
268 };
269 Segment::new(data, self.sample_width, self.frame_rate, channels)
270 }
271
272 pub fn sync(a: Segment, b: Segment) -> (Segment, Segment) {
274 let channels = a.channels.max(b.channels);
275 let frame_rate = a.frame_rate.max(b.frame_rate);
276 let width = a.sample_width.max(b.sample_width);
277 let conv = |s: Segment| {
278 s.set_channels(channels)
279 .set_frame_rate(frame_rate)
280 .set_sample_width(width)
281 };
282 (conv(a), conv(b))
283 }
284
285 pub fn append(self, other: Segment) -> Segment {
287 let (mut a, b) = Segment::sync(self, other);
288 a.data.extend_from_slice(&b.data);
289 a
290 }
291
292 pub fn overlay(self, seg: Segment, position: i64) -> Segment {
294 let (seg1, seg2) = Segment::sync(self, seg);
295 let width = seg1.sample_width;
296 let pos_ms = position as f64;
297
298 let fw = seg1.frame_width() as i64;
302 let len = seg1.len_ms() as f64;
303 let cut = seg1.parse_position(pos_ms.min(len)) * fw;
304 let tail_end = seg1.parse_position(len) * fw;
305 if cut >= 0 && tail_end == seg1.data.len() as i64 && cut <= tail_end {
306 let mut out = seg1;
307 let cut = cut as usize;
308 let n = seg2.data.len().min(out.data.len() - cut);
309 audioop::add_into(&mut out.data[cut..cut + n], &seg2.data[..n], width);
310 return out;
311 }
312
313 let mut output = seg1.slice(None, Some(pos_ms)).data;
314 let base = seg1.slice(Some(pos_ms), None).data;
315 let mut overlay = seg2.data;
316 let remaining = base.len();
317 if overlay.len() >= remaining {
318 overlay.truncate(remaining);
319 }
320 let n = overlay.len();
321 output.extend(audioop::add(&base[..n], &overlay, width));
322 output.extend_from_slice(&base[n..]);
323 seg1.spawn(output)
324 }
325
326 pub fn fade(
328 &self,
329 to_gain: f64,
330 from_gain: f64,
331 start: Option<f64>,
332 end: Option<f64>,
333 duration: Option<f64>,
334 ) -> Segment {
335 if to_gain == 0.0 && from_gain == 0.0 {
336 return self.clone();
337 }
338 let len = self.len_ms() as f64;
339 let mut start = start.map(|s| s.min(len));
340 let mut end = end.map(|e| e.min(len));
341 if let Some(s) = start {
342 if s < 0.0 {
343 start = Some(s + len);
344 }
345 }
346 if let Some(e) = end {
347 if e < 0.0 {
348 end = Some(e + len);
349 }
350 }
351 let duration = match duration {
352 Some(d) if d != 0.0 => {
353 if let Some(s) = start {
354 end = Some(s + d);
355 } else if let Some(e) = end {
356 start = Some(e - d);
357 }
358 d
359 }
360 _ => end.unwrap() - start.unwrap(),
361 };
362 let (start, end) = (start.unwrap(), end.unwrap());
363
364 let from_power = db_to_float(from_gain);
365 let mut output = Vec::new();
366
367 let mut before = self.slice(None, Some(start)).data;
368 if from_gain != 0.0 {
369 before = audioop::mul(&before, self.sample_width, from_power);
370 }
371 output.extend(before);
372
373 let gain_delta = db_to_float(to_gain) - from_power;
374 if duration > 100.0 {
375 let scale_step = gain_delta / duration;
376 for i in 0..duration as i64 {
377 let volume_change = from_power + scale_step * i as f64;
378 let chunk = self.at(start + i as f64);
379 output.extend(audioop::mul(&chunk.data, self.sample_width, volume_change));
380 }
381 } else {
382 let start_frame = self.frame_count_ms(start);
383 let end_frame = self.frame_count_ms(end);
384 let fade_frames = end_frame - start_frame;
385 let scale_step = gain_delta / fade_frames;
386 for i in 0..fade_frames as i64 {
387 let volume_change = from_power + scale_step * i as f64;
388 let sample = self.get_frame((start_frame + i as f64) as i64);
389 output.extend(audioop::mul(sample, self.sample_width, volume_change));
390 }
391 }
392
393 let mut after = self.slice(Some(end), None).data;
394 if to_gain != 0.0 {
395 after = audioop::mul(&after, self.sample_width, db_to_float(to_gain));
396 }
397 output.extend(after);
398 self.spawn(output)
399 }
400
401 pub fn fade_in(&self, duration_ms: f64) -> Segment {
402 self.fade(0.0, -120.0, Some(0.0), None, Some(duration_ms))
403 }
404
405 pub fn fade_out(&self, duration_ms: f64) -> Segment {
406 self.fade(-120.0, 0.0, None, Some(f64::INFINITY), Some(duration_ms))
407 }
408
409 pub fn split_to_mono(&self) -> Vec<Segment> {
411 if self.channels == 1 {
412 return vec![self.clone()];
413 }
414 let w = self.sample_width;
415 (0..self.channels)
416 .map(|c| {
417 let data: Vec<u8> = self
418 .data
419 .chunks_exact(self.frame_width())
420 .flat_map(|f| f[c * w..(c + 1) * w].to_vec())
421 .collect();
422 Segment::new(data, w, self.frame_rate, 1)
423 })
424 .collect()
425 }
426
427 pub fn apply_gain_stereo(&self, left_gain: f64, right_gain: f64) -> Segment {
429 let (left, right) = if self.channels == 1 {
430 (self.clone(), self.clone())
431 } else {
432 let mut parts = self.split_to_mono();
433 let r = parts.pop().unwrap();
434 (parts.pop().unwrap(), r)
435 };
436 let w = self.sample_width;
437 let l = audioop::mul(&left.data, w, db_to_float(left_gain));
438 let l = audioop::tostereo(&l, w, 1.0, 0.0);
439 let r = audioop::mul(&right.data, w, db_to_float(right_gain));
440 let r = audioop::tostereo(&r, w, 0.0, 1.0);
441 Segment::new(audioop::add(&l, &r, w), w, self.frame_rate, 2)
442 }
443
444 pub fn pan(&self, pan_amount: f64) -> Segment {
446 assert!(
447 (-1.0..=1.0).contains(&pan_amount),
448 "pan_amount should be between -1.0 (100% left) and +1.0 (100% right)"
449 );
450 let max_boost_db = ratio_to_db(2.0);
451 let boost_db = pan_amount.abs() * max_boost_db;
452 let boost_factor = db_to_float(boost_db);
453 let reduce_factor = db_to_float(max_boost_db) - boost_factor;
454 let reduce_db = ratio_to_db(reduce_factor);
455 let boost_db = boost_db / 2.0;
456 if pan_amount < 0.0 {
457 self.apply_gain_stereo(boost_db, reduce_db)
458 } else {
459 self.apply_gain_stereo(reduce_db, boost_db)
460 }
461 }
462
463 fn samples_i16(&self) -> Vec<i16> {
464 assert_eq!(
465 self.sample_width, 2,
466 "filters are only used on 16-bit audio"
467 );
468 self.data
469 .chunks_exact(2)
470 .map(|c| i16::from_le_bytes([c[0], c[1]]))
471 .collect()
472 }
473
474 fn spawn_i16(&self, s: &[i16]) -> Segment {
475 self.spawn(s.iter().flat_map(|v| v.to_le_bytes()).collect())
476 }
477
478 #[allow(clippy::needless_range_loop)]
481 pub fn low_pass_filter(&self, cutoff: f64) -> Segment {
482 let rc = 1.0 / (cutoff * 2.0 * std::f64::consts::PI);
483 let dt = 1.0 / self.frame_rate as f64;
484 let alpha = dt / (rc + dt);
485 let original = self.samples_i16();
486 let mut filtered = original.clone();
487 let frames = self.frame_count() as usize;
488 let ch = self.channels;
489 let mut last: Vec<f64> = vec![0.0; ch];
490 for i in 0..ch.min(original.len()) {
491 last[i] = original[i] as f64;
492 filtered[i] = original[i];
493 }
494 for i in 1..frames {
495 for j in 0..ch {
496 let off = i * ch + j;
497 last[j] += alpha * (original[off] as f64 - last[j]);
498 filtered[off] = py_array_int(last[j]);
499 }
500 }
501 self.spawn_i16(&filtered)
502 }
503
504 #[allow(clippy::needless_range_loop)]
506 pub fn high_pass_filter(&self, cutoff: f64) -> Segment {
507 let rc = 1.0 / (cutoff * 2.0 * std::f64::consts::PI);
508 let dt = 1.0 / self.frame_rate as f64;
509 let alpha = rc / (rc + dt);
510 let (minval, maxval) = (-32768.0f64, 32767.0f64);
511 let original = self.samples_i16();
512 let mut filtered = original.clone();
513 let frames = self.frame_count() as usize;
514 let ch = self.channels;
515 let mut last: Vec<f64> = vec![0.0; ch];
516 for i in 0..ch.min(original.len()) {
517 last[i] = original[i] as f64;
518 filtered[i] = original[i];
519 }
520 for i in 1..frames {
521 for j in 0..ch {
522 let off = i * ch + j;
523 let prev = (i - 1) * ch + j;
524 last[j] = alpha * (last[j] + original[off] as f64 - original[prev] as f64);
525 filtered[off] = py_array_int(last[j].max(minval).min(maxval));
526 }
527 }
528 self.spawn_i16(&filtered)
529 }
530
531 pub fn from_file(path: &Path) -> Result<Segment, AudioError> {
534 let is_wav = path.to_string_lossy().to_lowercase().ends_with(".wav");
535 if is_wav {
536 if let Some(seg) = std::fs::read(path).ok().and_then(|b| read_wav(&b)) {
537 return Ok(seg);
538 }
539 }
540 let pcm = ffmpeg::decode(path)?;
541 let data = pcm.samples.iter().flat_map(|v| v.to_le_bytes()).collect();
542 Ok(Segment::new(data, 2, pcm.frame_rate, pcm.channels as usize))
543 }
544
545 pub fn export_wav(&self, path: &Path) -> std::io::Result<()> {
548 std::fs::write(path, self.wav_bytes())
549 }
550
551 pub fn wav_bytes(&self) -> Vec<u8> {
553 let nframes = self.frame_count() as u32;
554 let datalength = nframes * self.channels as u32 * self.sample_width as u32;
555 let mut out = Vec::with_capacity(44 + self.data.len());
556 out.extend_from_slice(b"RIFF");
557 out.extend_from_slice(&(36 + datalength).to_le_bytes());
558 out.extend_from_slice(b"WAVEfmt ");
559 out.extend_from_slice(&16u32.to_le_bytes());
560 out.extend_from_slice(&1u16.to_le_bytes());
561 out.extend_from_slice(&(self.channels as u16).to_le_bytes());
562 out.extend_from_slice(&self.frame_rate.to_le_bytes());
563 let block_align = (self.channels * self.sample_width) as u32;
564 out.extend_from_slice(&(self.frame_rate * block_align).to_le_bytes());
565 out.extend_from_slice(&(block_align as u16).to_le_bytes());
566 out.extend_from_slice(&((self.sample_width * 8) as u16).to_le_bytes());
567 out.extend_from_slice(b"data");
568 out.extend_from_slice(&datalength.to_le_bytes());
569 if self.sample_width == 1 {
570 out.extend(self.data.iter().map(|b| b.wrapping_add(128)));
571 } else {
572 out.extend_from_slice(&self.data);
573 }
574 out
575 }
576
577 pub fn export_ffmpeg(
581 &self,
582 path: &Path,
583 format: &str,
584 parameters: &[&str],
585 ) -> Result<(), AudioError> {
586 let tmp_in = tempfile::Builder::new()
589 .suffix(".wav")
590 .tempfile()
591 .map_err(|e| io_err(path, e))?
592 .into_temp_path();
593 std::fs::write(&tmp_in, self.wav_bytes()).map_err(|e| io_err(path, e))?;
594 let tmp_out = tempfile::NamedTempFile::new()
595 .map_err(|e| io_err(path, e))?
596 .into_temp_path();
597 let mut cmd = Command::new("ffmpeg");
598 cmd.arg("-y").args(["-f", "wav", "-i"]).arg(&tmp_in);
599 cmd.args(parameters);
600 cmd.args(["-f", format]).arg(&tmp_out);
601 let out = cmd
602 .stdin(Stdio::null())
603 .output()
604 .map_err(|_| AudioError::FfmpegMissing)?;
605 if !out.status.success() {
606 return Err(AudioError::CouldntEncode {
607 code: out.status.code().unwrap_or(-1),
608 stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
609 });
610 }
611 let bytes = std::fs::read(&tmp_out).map_err(|e| io_err(path, e))?;
612 std::fs::write(path, bytes).map_err(|e| io_err(path, e))
613 }
614}
615
616fn io_err(path: &Path, e: std::io::Error) -> AudioError {
617 AudioError::Malformed {
618 path: path.display().to_string(),
619 what: e.to_string(),
620 }
621}
622
623fn py_array_int(x: f64) -> i16 {
626 let v = x as i64;
627 assert!(
628 (-32768..=32767).contains(&v),
629 "OverflowError: signed short integer is greater than maximum"
630 );
631 v as i16
632}
633
634pub fn read_wav(data: &[u8]) -> Option<Segment> {
637 if data.len() < 12 {
638 return None;
639 }
640 let u16at = |i: usize| u16::from_le_bytes([data[i], data[i + 1]]);
641 let u32at = |i: usize| u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
642 let mut pos = 12usize;
643 let mut chunks: Vec<(&[u8], usize, usize)> = Vec::new();
644 while pos + 8 <= data.len() && chunks.len() < 10 {
645 let id = &data[pos..pos + 4];
646 let size = u32at(pos + 4) as usize;
647 chunks.push((id, pos, size));
648 if id == b"data" {
649 break;
650 }
651 pos += size + 8;
652 }
653 let (_, fpos, fsize) = *chunks.iter().find(|c| c.0 == b"fmt ")?;
654 if fsize < 16 || fpos + 24 > data.len() {
655 return None;
656 }
657 let p = fpos + 8;
658 let audio_format = u16at(p);
659 if audio_format != 1 && audio_format != 0xFFFE {
660 return None;
661 }
662 let channels = u16at(p + 2) as usize;
663 let rate = u32at(p + 4);
664 let bits = u16at(p + 14) as usize;
665 let &(id, dpos, dsize) = chunks.last()?;
666 if id != b"data" {
667 return None;
668 }
669 let start = dpos + 8;
670 let end = (start + dsize).min(data.len());
671 let width = bits / 8;
672 if !(width == 1 || width == 2 || width == 4) || channels == 0 {
673 return None;
674 }
675 let mut raw = data[start.min(end)..end].to_vec();
676 if width == 1 {
677 raw.iter_mut().for_each(|b| *b = b.wrapping_sub(128));
678 }
679 Some(Segment::new(raw, width, rate, channels))
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685
686 fn seg(v: &[i16], rate: u32, ch: usize) -> Segment {
687 Segment::new(
688 v.iter().flat_map(|x| x.to_le_bytes()).collect(),
689 2,
690 rate,
691 ch,
692 )
693 }
694 fn vals(s: &Segment) -> Vec<i16> {
695 s.samples_i16()
696 }
697
698 #[test]
699 fn silent_and_empty_have_pydubs_defaults() {
700 let s = Segment::silent(600.0);
701 assert_eq!((s.frame_rate, s.channels, s.sample_width), (11025, 1, 2));
702 assert_eq!(s.frame_count(), 6615.0);
703 assert_eq!(s.len_ms(), 600);
704 let e = Segment::empty();
705 assert_eq!((e.frame_rate, e.channels, e.sample_width), (1, 1, 1));
706 }
707
708 #[test]
709 fn appending_to_empty_syncs_up() {
710 let a = seg(&[1, 2, 3, 4], 1000, 2);
711 let out = Segment::empty().append(a.clone());
712 assert_eq!(out, a);
713 }
714
715 #[test]
716 fn python_slicing_rules() {
717 assert_eq!(py_slice(b"abcdef", -2, 100), b"ef");
718 assert_eq!(py_slice(b"abcdef", 4, 2), b"");
719 assert_eq!(floor_div(-3, 2), -2);
720 assert_eq!(floor_div(3, 2), 1);
721 assert_eq!(py_round(2.5), 2);
722 assert_eq!(py_round(3.5), 4);
723 assert_eq!(py_round(1234.4), 1234);
724 }
725
726 #[test]
727 fn overlay_truncates_at_base_end() {
728 let base = seg(&[10, 10, 10, 10], 1000, 1);
729 let top = seg(&[1, 2, 3], 1000, 1);
730 assert_eq!(
731 vals(&base.clone().overlay(top.clone(), 2)),
732 [10, 10, 11, 12]
733 );
734 assert_eq!(vals(&base.overlay(top, 0)), [11, 12, 13, 10]);
735 }
736
737 #[test]
738 fn pan_centre_keeps_levels_close() {
739 let s = seg(&[1000, -1000], 44100, 1);
740 let p = s.pan(0.0);
741 assert_eq!(p.channels, 2);
742 let v = vals(&p);
743 assert!(v.iter().all(|x| (x.abs() - 1000).abs() <= 1), "{v:?}");
744 }
745
746 #[test]
747 fn wav_round_trip() {
748 let s = seg(&[1, -2, 3, -4], 8000, 2);
749 let bytes = s.wav_bytes();
750 assert_eq!(bytes.len(), 44 + 8);
751 assert_eq!(read_wav(&bytes).unwrap(), s);
752 }
753}