audio_overlay/lib.rs
1//! Overlay audio samples from one array onto another. You can optionally expand the destination array.
2//!
3//! The overlay function can be used for i8, i16, i32, i64, and f32.
4//!
5//! # Example
6//!
7//! ```rust
8//!use audio_overlay::overlay;
9//!use hound::WavReader;
10//!use rodio::buffer::SamplesBuffer;
11//!use rodio::{OutputStream, Sink};
12//!
13//!// Set the framerate.
14//!let framerate: u32 = 44100;
15//!// Load the audio clips.
16//!// Source: https://archive.org/download/NasaApollo11OnboardRecordings/11_highlight_2.ogg
17//!let src: Vec<i16> = WavReader::open("src.wav")
18//!.unwrap()
19//!.samples::<i16>()
20//!.map(|s| s.unwrap())
21//!.collect::<Vec<i16>>();
22//!// Source: https://archive.org/download/airship1904/airship1904.ogg
23//!let mut dst: Vec<i16> = WavReader::open("dst.wav")
24//!.unwrap()
25//!.samples::<i16>()
26//!.map(|s| s.unwrap())
27//!.collect::<Vec<i16>>();
28//!
29//!// // Overlay the audio clips. The src clip will start 1.0 seconds after dst begins.
30//!overlay(src.as_slice(), &mut dst, 1.0, framerate, true);
31//!
32//!// Play the audio clips. Source: https://docs.rs/rodio/latest/rodio/
33//!let (_stream, stream_handle) = OutputStream::try_default().unwrap();
34//!let source = SamplesBuffer::new(1, framerate, dst);
35//!let sink = Sink::try_new(&stream_handle).unwrap();
36//!sink.append(source);
37//!sink.sleep_until_end();
38//! ```
39
40use std::cmp::PartialOrd;
41
42const I8_MAX: i16 = i8::MAX as i16;
43const I8_MIN: i16 = i8::MIN as i16;
44const I16_MAX: i32 = i16::MAX as i32;
45const I16_MIN: i32 = i16::MIN as i32;
46const I32_MAX: i64 = i32::MAX as i64;
47const I32_MIN: i64 = i32::MIN as i64;
48const I64_MAX: i128 = i64::MAX as i128;
49const I64_MIN: i128 = i64::MIN as i128;
50
51/// Overlay audio samples from one array onto another. You can optionally expand the destination array.
52///
53/// This function can be used for i8, i16, i32, i64, f32, and f64.
54///
55/// This function assumes that both the source and destination arrays are a single channel of audio and have the same framerate and sample width.
56///
57/// For multi-channel audio, run `overlay()` for each channel.
58///
59/// Audio mixing algorithm source: <https://github.com/python/cpython/blob/main/Modules/audioop.c#L1083>
60///
61/// # Arguments
62///
63/// * `src` - A slice of type T. This array will be overlaid into `dst`.
64/// * `dst` - A mutable vec of type T. This will be modified, with `src` being overlaid into `dst`.
65/// * `time` - The start time in seconds at which `src` should be overlaid into `dst`.
66/// * `framerate` - The framerate of `src` and `dst`, e.g. 44100. This will be used to convert `time` into an index value.
67/// * `add` - Often, the end time of `src` will exceed the end time of `dst`. If `add == true`, samples from `src` past the original end time of `dst` will be pushed to `dst`, lengthening the waveform. If `add == false`, this function will end at the current length of `dst` and won't modify its length.
68///
69/// # Panics
70///
71/// It is technically possible for this function to panic if the source arrays are of type f32 or f64 because an overlaid value could exceed f32::MIN or f32::MAX, or f64::MIN or f64::MAX, respectively. But this would be a very unusual audio array in the first place. We're assuming that all values in `src` and `dst` are between -1.0 and 1.0.
72///
73/// For integer types such as i16, the function *won't* panic due to overflow errors because summed values will be cast to a type with a higher bit count, added, and recast as the original type (see [Overlayable]).
74pub fn overlay<T, U>(src: &[T], dst: &mut Vec<T>, time: f64, framerate: u32, add: bool)
75where
76 T: Copy + PartialOrd + Overlayable<T, U> + From<u8>,
77 U: Copy + PartialOrd + ValueBounds<U>,
78{
79 // Get the start index.
80 let mut index: usize = (time * framerate as f64) as usize;
81 // The current length of dst.
82 let len: usize = dst.len();
83 // This will be used to fill dst with zeros if needed.
84 let zero: T = T::from(0);
85 // The start time is after the end of dst.
86 if index >= len {
87 if add {
88 // Add zeros up to the start time.
89 dst.extend(vec![zero; index - len]);
90 // Add src.
91 dst.extend(src.iter().cloned());
92 }
93 return;
94 }
95 // Get the minimum and maximum values.
96 let min: U = U::min();
97 let max: U = U::max();
98 for (i, &v) in src.iter().enumerate() {
99 // If the index is greater than the length of dst, then we need to either stop here or append.
100 if index >= len {
101 if add {
102 dst.extend(src[i..].iter().cloned());
103 }
104 return;
105 }
106 // If there is no data at this index, set it to v rather than doing a lot of casting.
107 if dst[index] == zero {
108 dst[index] = v;
109 }
110 // Overlay the sample.
111 else {
112 dst[index] = T::overlay(dst[index], v, min, max);
113 }
114 // Increment the index.
115 index += 1;
116 }
117}
118
119// Clamp the value between a min and max.
120fn clamp<T>(value: T, min: T, max: T) -> T
121where
122 T: PartialOrd,
123{
124 if value > max {
125 max
126 } else if value < min {
127 min
128 } else {
129 value
130 }
131}
132
133/// Overlay values onto each other. The values are are added together and clamped to min/max values.
134pub trait Overlayable<T, U>
135where
136 T: Copy + PartialOrd,
137 U: Copy + PartialOrd,
138{
139 /// Add two values together, clamped between min/max values.
140 ///
141 /// For integer types, we need to cast the values to a higher bit count, e.g. i16 to i32, before adding them, to prevent an overflow error. Values are clamped to the min/max values of the original type, e.g. i16::MAX.
142 ///
143 /// For float types, it's assumed that the values are between -1.0 and 1.0. They are added and the sum is clamped to be between -1.0 and 1.0.
144 fn overlay(a: T, b: T, min: U, max: U) -> T;
145}
146
147impl Overlayable<i8, i16> for i8 {
148 fn overlay(a: i8, b: i8, min: i16, max: i16) -> i8 {
149 clamp((a + b) as i16, min, max) as i8
150 }
151}
152
153impl Overlayable<i16, i32> for i16 {
154 fn overlay(a: i16, b: i16, min: i32, max: i32) -> i16 {
155 clamp((a + b) as i32, min, max) as i16
156 }
157}
158
159impl Overlayable<i32, i64> for i32 {
160 fn overlay(a: i32, b: i32, min: i64, max: i64) -> i32 {
161 clamp((a + b) as i64, min, max) as i32
162 }
163}
164
165impl Overlayable<i64, i128> for i64 {
166 fn overlay(a: i64, b: i64, min: i128, max: i128) -> i64 {
167 clamp((a + b) as i128, min, max) as i64
168 }
169}
170
171impl Overlayable<f32, f32> for f32 {
172 fn overlay(a: f32, b: f32, min: f32, max: f32) -> f32 {
173 clamp(a + b, min, max)
174 }
175}
176
177impl Overlayable<f64, f64> for f64 {
178 fn overlay(a: f64, b: f64, min: f64, max: f64) -> f64 {
179 clamp(a + b, min, max)
180 }
181}
182
183/// This is used by `overlay()` to get the minimum and maximum values of a given type for the purposes of overlaying data.
184///
185/// For integer types, this is the min/max value of the type one bit count less than this one. For example, `i16::min()` returns `i8::MIN as i16`.
186///
187/// For float types, the min/max value is -1.0 and 1.0.
188pub trait ValueBounds<T>
189where
190 T: Copy + PartialOrd,
191{
192 /// Returns the minimum value of type T.
193 fn min() -> T;
194 /// Returns the maximum value of type T.
195 fn max() -> T;
196}
197
198impl ValueBounds<i16> for i16 {
199 fn min() -> i16 {
200 I8_MIN
201 }
202
203 fn max() -> i16 {
204 I8_MAX
205 }
206}
207
208impl ValueBounds<i32> for i32 {
209 fn min() -> i32 {
210 I16_MIN
211 }
212
213 fn max() -> i32 {
214 I16_MAX
215 }
216}
217
218impl ValueBounds<i64> for i64 {
219 fn min() -> i64 {
220 I32_MIN
221 }
222
223 fn max() -> i64 {
224 I32_MAX
225 }
226}
227
228impl ValueBounds<i128> for i128 {
229 fn min() -> i128 {
230 I64_MIN
231 }
232
233 fn max() -> i128 {
234 I64_MAX
235 }
236}
237
238impl ValueBounds<f32> for f32 {
239 fn min() -> f32 {
240 -1.0
241 }
242
243 fn max() -> f32 {
244 1.0
245 }
246}
247
248impl ValueBounds<f64> for f64 {
249 fn min() -> f64 {
250 -1.0
251 }
252
253 fn max() -> f64 {
254 1.0
255 }
256}