| 1 |
1 |
|
//! Audio conversion pipeline: channel count and sample rate conversion for export.
|
| 2 |
2 |
|
|
|
3 |
+ |
use rubato::audioadapter_buffers::direct::SequentialSliceOfVecs;
|
| 3 |
4 |
|
use rubato::{
|
| 4 |
|
- |
Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
|
|
5 |
+ |
Async, FixedAsync, Indexing, Resampler, SincInterpolationParameters, SincInterpolationType,
|
|
6 |
+ |
WindowFunction,
|
| 5 |
7 |
|
};
|
| 6 |
8 |
|
|
| 7 |
9 |
|
use super::ExportChannels;
|
| 86 |
88 |
|
src_rate: u32,
|
| 87 |
89 |
|
dst_rate: u32,
|
| 88 |
90 |
|
channels: usize,
|
| 89 |
|
- |
) -> Result<SincFixedIn<f32>, CoreError> {
|
|
91 |
+ |
) -> Result<ExportResampler, CoreError> {
|
| 90 |
92 |
|
let params = SincInterpolationParameters {
|
| 91 |
93 |
|
sinc_len: 256,
|
| 92 |
|
- |
f_cutoff: 0.95,
|
|
94 |
+ |
f_cutoff: Some(0.95),
|
| 93 |
95 |
|
interpolation: SincInterpolationType::Linear,
|
| 94 |
96 |
|
oversampling_factor: 256,
|
| 95 |
97 |
|
window: WindowFunction::BlackmanHarris2,
|
| 96 |
98 |
|
};
|
| 97 |
99 |
|
let ratio = dst_rate as f64 / src_rate as f64;
|
| 98 |
|
- |
SincFixedIn::<f32>::new(ratio, 2.0, params, RESAMPLE_CHUNK, channels)
|
| 99 |
|
- |
.map_err(|e| CoreError::Export(format!("resampler init: {e}")))
|
|
100 |
+ |
// `FixedAsync::Input` is rubato 4's spelling of the old `SincFixedIn`: a
|
|
101 |
+ |
// fixed-size input chunk with a variable-length output block.
|
|
102 |
+ |
let inner = Async::<f32>::new_sinc(
|
|
103 |
+ |
ratio,
|
|
104 |
+ |
2.0,
|
|
105 |
+ |
¶ms,
|
|
106 |
+ |
RESAMPLE_CHUNK,
|
|
107 |
+ |
channels,
|
|
108 |
+ |
FixedAsync::Input,
|
|
109 |
+ |
)
|
|
110 |
+ |
.map_err(|e| CoreError::Export(format!("resampler init: {e}")))?;
|
|
111 |
+ |
let delay_remaining = inner.output_delay();
|
|
112 |
+ |
Ok(ExportResampler {
|
|
113 |
+ |
inner,
|
|
114 |
+ |
channels,
|
|
115 |
+ |
delay_remaining,
|
|
116 |
+ |
input: vec![Vec::new(); channels],
|
|
117 |
+ |
output: vec![Vec::new(); channels],
|
|
118 |
+ |
})
|
|
119 |
+ |
}
|
|
120 |
+ |
|
|
121 |
+ |
/// The export resampler, wrapping rubato's `Async` in the per-channel
|
|
122 |
+ |
/// `Vec<Vec<f32>>` interface both export paths are written against.
|
|
123 |
+ |
///
|
|
124 |
+ |
/// rubato 4 replaced `process`/`process_partial` with a single
|
|
125 |
+ |
/// `process_into_buffer` over `audioadapter` views, and moved short-chunk
|
|
126 |
+ |
/// handling into `Indexing::partial_len`. Keeping that behind one method is what
|
|
127 |
+ |
/// lets the buffered and streaming paths stay byte-identical: they hand the same
|
|
128 |
+ |
/// chunks to the same call, and neither has to reason about padding or output
|
|
129 |
+ |
/// sizing.
|
|
130 |
+ |
pub(crate) struct ExportResampler {
|
|
131 |
+ |
inner: Async<f32>,
|
|
132 |
+ |
channels: usize,
|
|
133 |
+ |
/// Leading output frames still to be discarded to undo the sinc filter's
|
|
134 |
+ |
/// group delay. rubato 0.14 time-aligned output to input for us; rubato 4
|
|
135 |
+ |
/// reports the delay through `output_delay` and leaves the trim to the
|
|
136 |
+ |
/// caller. Without it every rate-converted export is shifted late by half
|
|
137 |
+ |
/// the sinc length (scaled by the ratio) and runs long by the same amount.
|
|
138 |
+ |
delay_remaining: usize,
|
|
139 |
+ |
/// Scratch input, always padded out to the resampler's required chunk size.
|
|
140 |
+ |
input: Vec<Vec<f32>>,
|
|
141 |
+ |
/// Scratch output, always sized to the resampler's maximum output block.
|
|
142 |
+ |
output: Vec<Vec<f32>>,
|
|
143 |
+ |
}
|
|
144 |
+ |
|
|
145 |
+ |
impl ExportResampler {
|
|
146 |
+ |
/// Push one chunk of per-channel input and return the frames it produced.
|
|
147 |
+ |
///
|
|
148 |
+ |
/// `chunk` may be shorter than [`RESAMPLE_CHUNK`] (the final partial chunk)
|
|
149 |
+ |
/// or empty (flushing the latency tail); in both cases the remainder is
|
|
150 |
+ |
/// treated as silence, which is what `process_partial` did in rubato 0.14.
|
|
151 |
+ |
fn push(&mut self, chunk: &[Vec<f32>]) -> Result<&[Vec<f32>], CoreError> {
|
|
152 |
+ |
let needed = self.inner.input_frames_next();
|
|
153 |
+ |
let valid = chunk.first().map_or(0, |c| c.len()).min(needed);
|
|
154 |
+ |
|
|
155 |
+ |
// rubato requires a full-size input buffer even for a partial chunk: the
|
|
156 |
+ |
// frames past `valid` are declared silent via `partial_len`, but must
|
|
157 |
+ |
// still exist for the adapter to wrap.
|
|
158 |
+ |
for (c, buf) in self.input.iter_mut().enumerate() {
|
|
159 |
+ |
buf.clear();
|
|
160 |
+ |
if let Some(src) = chunk.get(c) {
|
|
161 |
+ |
buf.extend_from_slice(&src[..valid.min(src.len())]);
|
|
162 |
+ |
}
|
|
163 |
+ |
buf.resize(needed, 0.0);
|
|
164 |
+ |
}
|
|
165 |
+ |
|
|
166 |
+ |
let out_frames = self.inner.output_frames_next();
|
|
167 |
+ |
for buf in self.output.iter_mut() {
|
|
168 |
+ |
buf.clear();
|
|
169 |
+ |
buf.resize(out_frames, 0.0);
|
|
170 |
+ |
}
|
|
171 |
+ |
|
|
172 |
+ |
let indexing = Indexing {
|
|
173 |
+ |
partial_len: (valid < needed).then_some(valid),
|
|
174 |
+ |
..Default::default()
|
|
175 |
+ |
};
|
|
176 |
+ |
|
|
177 |
+ |
let input = SequentialSliceOfVecs::new(&self.input[..], self.channels, needed)
|
|
178 |
+ |
.map_err(|e| CoreError::Export(format!("resample input buffer: {e}")))?;
|
|
179 |
+ |
let mut output =
|
|
180 |
+ |
SequentialSliceOfVecs::new_mut(&mut self.output[..], self.channels, out_frames)
|
|
181 |
+ |
.map_err(|e| CoreError::Export(format!("resample output buffer: {e}")))?;
|
|
182 |
+ |
|
|
183 |
+ |
let (_, produced) = self
|
|
184 |
+ |
.inner
|
|
185 |
+ |
.process_into_buffer(&input, &mut output, Some(&indexing))
|
|
186 |
+ |
.map_err(|e| CoreError::Export(format!("resample: {e}")))?;
|
|
187 |
+ |
|
|
188 |
+ |
// Trim the group delay off the head of the stream, spending it across
|
|
189 |
+ |
// however many blocks it takes.
|
|
190 |
+ |
let skip = self.delay_remaining.min(produced);
|
|
191 |
+ |
self.delay_remaining -= skip;
|
|
192 |
+ |
for buf in self.output.iter_mut() {
|
|
193 |
+ |
buf.truncate(produced);
|
|
194 |
+ |
buf.drain(..skip);
|
|
195 |
+ |
}
|
|
196 |
+ |
Ok(&self.output)
|
|
197 |
+ |
}
|
|
198 |
+ |
|
|
199 |
+ |
/// Process one full or short chunk of real input.
|
|
200 |
+ |
pub(crate) fn process(&mut self, chunk: &[Vec<f32>]) -> Result<&[Vec<f32>], CoreError> {
|
|
201 |
+ |
self.push(chunk)
|
|
202 |
+ |
}
|
|
203 |
+ |
|
|
204 |
+ |
/// Drive the resampler with silence to drain its latency tail.
|
|
205 |
+ |
pub(crate) fn flush(&mut self) -> Result<&[Vec<f32>], CoreError> {
|
|
206 |
+ |
self.push(&[])
|
|
207 |
+ |
}
|
| 100 |
208 |
|
}
|
| 101 |
209 |
|
|
| 102 |
210 |
|
/// Resample interleaved audio from src_rate to dst_rate using rubato.
|
| 147 |
255 |
|
.map(|buf| buf[pos..end].to_vec())
|
| 148 |
256 |
|
.collect();
|
| 149 |
257 |
|
|
| 150 |
|
- |
// Full chunks go through `process`; the short final chunk through
|
| 151 |
|
- |
// `process_partial`, which zero-pads internally to a full chunk.
|
| 152 |
|
- |
let output_chunk = if actual_len == chunk_size {
|
| 153 |
|
- |
resampler.process(&input_chunk, None)
|
| 154 |
|
- |
} else {
|
| 155 |
|
- |
resampler.process_partial(Some(input_chunk.as_slice()), None)
|
| 156 |
|
- |
}
|
| 157 |
|
- |
.map_err(|e| CoreError::Export(format!("resample: {e}")))?;
|
|
258 |
+ |
// Full and short chunks take the same call; the resampler treats
|
|
259 |
+ |
// anything past the chunk's real length as silence.
|
|
260 |
+ |
let _ = actual_len;
|
|
261 |
+ |
let output_chunk = resampler.process(&input_chunk)?;
|
| 158 |
262 |
|
|
| 159 |
|
- |
for (c, chunk) in output_chunk.into_iter().enumerate() {
|
| 160 |
|
- |
output_channels[c].extend_from_slice(&chunk);
|
|
263 |
+ |
for (c, chunk) in output_chunk.iter().enumerate() {
|
|
264 |
+ |
output_channels[c].extend_from_slice(chunk);
|
| 161 |
265 |
|
}
|
| 162 |
266 |
|
|
| 163 |
267 |
|
pos += chunk_size;
|
| 169 |
273 |
|
// its tail. (The resampler time-aligns output to input internally, so no
|
| 170 |
274 |
|
// leading-delay trim is needed.)
|
| 171 |
275 |
|
while output_channels[0].len() < target_total {
|
| 172 |
|
- |
let flushed = resampler
|
| 173 |
|
- |
.process_partial::<Vec<f32>>(None, None)
|
| 174 |
|
- |
.map_err(|e| CoreError::Export(format!("resample flush: {e}")))?;
|
|
276 |
+ |
let flushed = resampler.flush()?;
|
| 175 |
277 |
|
let produced = flushed.first().map_or(0, |c| c.len());
|
| 176 |
|
- |
for (c, chunk) in flushed.into_iter().enumerate() {
|
| 177 |
|
- |
output_channels[c].extend_from_slice(&chunk);
|
|
278 |
+ |
for (c, chunk) in flushed.iter().enumerate() {
|
|
279 |
+ |
output_channels[c].extend_from_slice(chunk);
|
| 178 |
280 |
|
}
|
| 179 |
281 |
|
if produced == 0 {
|
| 180 |
282 |
|
break; // fully drained
|