Skip to content

Commit 2da7b64

Browse files
authored
Merge pull request #132 from HEnquist/capability-traits
Split Resampler into capability traits Adjustable and Resizable
2 parents f9f5de5 + c02f3b4 commit 2da7b64

7 files changed

Lines changed: 264 additions & 90 deletions

File tree

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,9 @@ RUST_LOG=trace cargo test --features log
273273
## Example
274274

275275
Resample a dummy audio file from 44100 to 48000 Hz.
276+
This uses the `Fft` resampler, which requires the `fft_resampler` feature (enabled by default).
276277
See also the "process_f64" example that can be used to process a file from disk.
277-
```rust
278+
```rust,ignore
278279
use rubato::{
279280
Resampler, Fft, FixedSync, Indexing
280281
};
@@ -408,6 +409,28 @@ let r = Fft::<f64>::new_custom(rate_in, rate_out, chunk_size, sub_chunks, channe
408409
WindowFunction::BlackmanHarris2, fixed)?;
409410
```
410411

412+
**Ratio and chunk-size changes moved to capability traits.** `set_resample_ratio`,
413+
`set_resample_ratio_relative` are now on the `Adjustable` trait, and `set_chunk_size` is on
414+
`Resizable`. On a concrete resampler, just bring the trait into scope. On a `dyn Resampler`,
415+
recover the capability with `as_adjustable()` / `as_resizable()` (this replaces the old
416+
`SyncNotAdjustable` / `ChunkSizeNotAdjustable` errors, which are removed). To query the
417+
capability through a shared `&dyn Resampler`, use `is_adjustable()` / `is_resizable()`.
418+
419+
```rust,ignore
420+
// before: on a Box<dyn Resampler>, with a runtime error for synchronous resamplers
421+
resampler.set_resample_ratio(new_ratio, true)?;
422+
// after: None means "synchronous, nothing to adjust"
423+
if let Some(adjustable) = resampler.as_adjustable() {
424+
adjustable.set_resample_ratio(new_ratio, true)?;
425+
}
426+
427+
// before: on a concrete Async resampler
428+
async_resampler.set_resample_ratio_relative(0.95, true)?;
429+
// after: same call, but the Adjustable trait must be in scope
430+
use rubato::Adjustable;
431+
async_resampler.set_resample_ratio_relative(0.95, true)?;
432+
```
433+
411434
**The error enums are now `#[non_exhaustive]`.** If you `match` on `ResampleError` or
412435
`ResamplerConstructionError`, add a `_ => ...` arm.
413436

@@ -440,6 +463,12 @@ let r = Fft::<f64>::new_custom(rate_in, rate_out, chunk_size, sub_chunks, channe
440463
`SincInterpolationType`, `PolynomialDegree`, `FixedSync` and `FixedAsync`.
441464
- Return `WrongNumberOfMaskChannels` instead of panicking when the
442465
`active_channels_mask` passed to a process method has the wrong length.
466+
- Split the capability-specific methods out of `Resampler` into the `Adjustable` trait
467+
(`set_resample_ratio`, `set_resample_ratio_relative`) and the `Resizable` trait
468+
(`set_chunk_size`). `Resampler` gains `as_adjustable()` and `as_resizable()` to recover
469+
these capabilities from a trait object, and `is_adjustable()` / `is_resizable()` to query
470+
them through a shared reference. The `SyncNotAdjustable` and `ChunkSizeNotAdjustable`
471+
error variants are removed, since calling these methods is now a compile-time capability.
443472
- v3.0.0
444473
- Use separate lifetimes for `buffer_in` and `buffer_out` in `process_into_buffer`.
445474
- Improve sinc resampler performance with smarter dot product calculation.

examples/fixedout_ramp64.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
extern crate rubato;
22
use audioadapter_buffers::direct::InterleavedSlice;
33
use rubato::{
4-
Async, FixedAsync, Indexing, Resampler, SincInterpolationParameters, SincInterpolationType,
5-
WindowFunction,
4+
Adjustable, Async, FixedAsync, Indexing, Resampler, SincInterpolationParameters,
5+
SincInterpolationType, WindowFunction,
66
};
77
use std::convert::TryInto;
88
use std::env;

examples/polyfixedin_ramp64.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
extern crate rubato;
22
use audioadapter_buffers::direct::InterleavedSlice;
3-
use rubato::{Async, FixedAsync, Indexing, PolynomialDegree, Resampler};
3+
use rubato::{Adjustable, Async, FixedAsync, Indexing, PolynomialDegree, Resampler};
44
use std::convert::TryInto;
55
use std::env;
66
use std::fs::File;

src/asynchro.rs

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::sinc_interpolator::{
1111
AnyInterpolator, AvxSample, NeonSample, SincInterpolator, SseSample,
1212
};
1313
use crate::{get_offsets, get_partial_len, update_mask, Indexing};
14-
use crate::{validate_buffers, Resampler, Sample};
14+
use crate::{validate_buffers, Adjustable, Resampler, Resizable, Sample};
1515

1616
/// An enum for specifying which side of an asynchronous resampler should be fixed size.
1717
/// This is similar to [FixedSync](crate::FixedSync) that is used for the synchronous resamplers.
@@ -55,8 +55,8 @@ pub trait InnerResampler<T>: Send {
5555
/// and when output size is fixed, the input size varies.
5656
///
5757
/// The number of frames on the fixed side is determined by the chunk size argument to the constructor.
58-
/// This value can be changed by the `set_chunk_size()` method,
59-
/// to let the resampler process smaller chunks of audio data.
58+
/// This value can be changed by the [set_chunk_size](Resizable::set_chunk_size) method of the
59+
/// [Resizable] trait (which must be in scope), to let the resampler process smaller chunks of audio data.
6060
/// Note that the chunk size cannot exceed the value given at creation time.
6161
///
6262
/// When the input size is fixed, the maximum value can be retrieved using the `input_size_max()` method,
@@ -155,7 +155,7 @@ where
155155
///
156156
/// Parameters are:
157157
/// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0.
158-
/// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`.
158+
/// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Adjustable::set_resample_ratio](crate::Adjustable::set_resample_ratio) relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`.
159159
/// - `interpolation_type`: Degree of polynomial used for interpolation, see [PolynomialDegree].
160160
/// - `chunk_size`: Size of input data in frames.
161161
/// - `nbr_channels`: Number of channels in input/output.
@@ -237,7 +237,7 @@ where
237237
///
238238
/// Parameters are:
239239
/// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0.
240-
/// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`.
240+
/// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Adjustable::set_resample_ratio](crate::Adjustable::set_resample_ratio) relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`.
241241
/// - `parameters`: Parameters for interpolation, see [SincInterpolationParameters].
242242
/// - `chunk_size`: Size of input data in frames.
243243
/// - `nbr_channels`: Number of channels in input/output.
@@ -277,7 +277,7 @@ where
277277
///
278278
/// Parameters are:
279279
/// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0.
280-
/// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio` * 10.0 and `resample_ratio` / 10.0.
280+
/// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Adjustable::set_resample_ratio](crate::Adjustable::set_resample_ratio) relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio` * 10.0 and `resample_ratio` / 10.0.
281281
/// - `interpolation_type`: Parameters for interpolation, see `SincInterpolationParameters`.
282282
/// - `interpolator`: The interpolator to use.
283283
/// - `chunk_size`: Size of output data in frames.
@@ -441,6 +441,23 @@ where
441441
self.needed_output_size
442442
);
443443
}
444+
445+
/// Check whether a ratio, expressed relative to the original ratio, is within the
446+
/// allowed `1 / max` to `max` range. Checking the relative ratio directly avoids the
447+
/// rounding error that a `(original * rel) / original` round-trip would introduce at
448+
/// the exact bounds.
449+
fn relative_ratio_in_bounds(&self, rel_ratio: f64) -> bool {
450+
rel_ratio >= 1.0 / self.max_relative_ratio && rel_ratio <= self.max_relative_ratio
451+
}
452+
453+
/// Apply an already validated resample ratio to the internal state.
454+
fn apply_ratio(&mut self, new_ratio: f64, ramp: bool) {
455+
if !ramp {
456+
self.resample_ratio = new_ratio;
457+
}
458+
self.target_ratio = new_ratio;
459+
self.update_lengths();
460+
}
444461
}
445462

446463
impl<T> Resampler<T> for Async<T>
@@ -573,35 +590,10 @@ where
573590
self.needed_input_size
574591
}
575592

576-
fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> ResampleResult<()> {
577-
trace!("Change resample ratio to {}", new_ratio);
578-
if (new_ratio / self.resample_ratio_original >= 1.0 / self.max_relative_ratio)
579-
&& (new_ratio / self.resample_ratio_original <= self.max_relative_ratio)
580-
{
581-
if !ramp {
582-
self.resample_ratio = new_ratio;
583-
}
584-
self.target_ratio = new_ratio;
585-
self.update_lengths();
586-
Ok(())
587-
} else {
588-
Err(ResampleError::RatioOutOfBounds {
589-
provided: new_ratio,
590-
original: self.resample_ratio_original,
591-
max_relative_ratio: self.max_relative_ratio,
592-
})
593-
}
594-
}
595-
596593
fn resample_ratio(&self) -> f64 {
597594
self.resample_ratio
598595
}
599596

600-
fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()> {
601-
let new_ratio = self.resample_ratio_original * rel_ratio;
602-
self.set_resample_ratio(new_ratio, ramp)
603-
}
604-
605597
fn reset(&mut self) {
606598
self.buffer
607599
.iter_mut()
@@ -614,6 +606,60 @@ where
614606
self.update_lengths();
615607
}
616608

609+
fn as_adjustable(&mut self) -> Option<&mut dyn Adjustable<T>> {
610+
Some(self)
611+
}
612+
613+
fn is_adjustable(&self) -> bool {
614+
true
615+
}
616+
617+
fn as_resizable(&mut self) -> Option<&mut dyn Resizable<T>> {
618+
Some(self)
619+
}
620+
621+
fn is_resizable(&self) -> bool {
622+
true
623+
}
624+
}
625+
626+
impl<T> Adjustable<T> for Async<T>
627+
where
628+
T: Sample,
629+
{
630+
fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> ResampleResult<()> {
631+
trace!("Change resample ratio to {}", new_ratio);
632+
if self.relative_ratio_in_bounds(new_ratio / self.resample_ratio_original) {
633+
self.apply_ratio(new_ratio, ramp);
634+
Ok(())
635+
} else {
636+
Err(ResampleError::RatioOutOfBounds {
637+
provided: new_ratio,
638+
original: self.resample_ratio_original,
639+
max_relative_ratio: self.max_relative_ratio,
640+
})
641+
}
642+
}
643+
644+
fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()> {
645+
let new_ratio = self.resample_ratio_original * rel_ratio;
646+
if self.relative_ratio_in_bounds(rel_ratio) {
647+
self.apply_ratio(new_ratio, ramp);
648+
Ok(())
649+
} else {
650+
Err(ResampleError::RatioOutOfBounds {
651+
provided: new_ratio,
652+
original: self.resample_ratio_original,
653+
max_relative_ratio: self.max_relative_ratio,
654+
})
655+
}
656+
}
657+
}
658+
659+
impl<T> Resizable<T> for Async<T>
660+
where
661+
T: Sample,
662+
{
617663
fn set_chunk_size(&mut self, chunksize: usize) -> ResampleResult<()> {
618664
if chunksize > self.max_chunk_size || chunksize == 0 {
619665
return Err(ResampleError::InvalidChunkSize {
@@ -632,14 +678,14 @@ mod tests {
632678
use crate::tests::expected_output_value;
633679
use crate::Indexing;
634680
use crate::PolynomialDegree;
635-
use crate::Resampler;
636681
use crate::SincInterpolationParameters;
637682
use crate::SincInterpolationType;
638683
use crate::WindowFunction;
639684
use crate::{
640685
assert_fi_len, assert_fo_len, check_input_offset, check_masked, check_output,
641686
check_output_offset, check_ratio, check_reset,
642687
};
688+
use crate::{Adjustable, Resampler, Resizable};
643689
use crate::{Async, FixedAsync};
644690
use audioadapter_buffers::direct::SequentialSliceOfVecs;
645691
use test_case::test_matrix;
@@ -816,6 +862,37 @@ mod tests {
816862
check_output!(resampler, f64);
817863
}
818864

865+
// The exact relative bounds `1 / max` and `max` must be accepted. The pair below is chosen
866+
// so that the old `(original * rel) / original` round-trip rounds the lower bound just outside
867+
// the range and wrongly rejected it; checking the relative ratio directly must not.
868+
#[test_log::test]
869+
fn async_relative_ratio_exact_bounds() {
870+
let params = basic_params();
871+
let original_ratio = 44100.0 / 48000.0;
872+
let max = 1.0161;
873+
let mut resampler =
874+
Async::<f64>::new_sinc(original_ratio, max, &params, 1024, 2, FixedAsync::Input)
875+
.unwrap();
876+
877+
assert!(
878+
resampler.set_resample_ratio_relative(max, false).is_ok(),
879+
"exact upper bound must be accepted"
880+
);
881+
assert!(
882+
resampler
883+
.set_resample_ratio_relative(1.0 / max, false)
884+
.is_ok(),
885+
"exact lower bound must be accepted"
886+
);
887+
// Just outside the range must still be rejected.
888+
assert!(resampler
889+
.set_resample_ratio_relative(max * 1.0001, false)
890+
.is_err());
891+
assert!(resampler
892+
.set_resample_ratio_relative(1.0 / max * 0.9999, false)
893+
.is_err());
894+
}
895+
819896
fn process_and_get_frame_counts(resampler: &mut Async<f64>) -> (usize, usize) {
820897
let input_frames = resampler.input_frames_next();
821898
let output_frames_max = resampler.output_frames_max();

src/error.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,14 @@ impl error::Error for ResamplerConstructionError {}
119119
#[derive(Clone, Copy, PartialEq)]
120120
#[non_exhaustive]
121121
pub enum ResampleError {
122-
/// Error raised when [Resampler::set_resample_ratio](crate::Resampler::set_resample_ratio)
122+
/// Error raised when [Adjustable::set_resample_ratio](crate::Adjustable::set_resample_ratio)
123123
/// is called with a ratio outside the maximum range specified when
124124
/// the resampler was constructed.
125125
RatioOutOfBounds {
126126
provided: f64,
127127
original: f64,
128128
max_relative_ratio: f64,
129129
},
130-
/// Error raised when calling [Resampler::set_resample_ratio](crate::Resampler::set_resample_ratio)
131-
/// on a synchronous resampler.
132-
SyncNotAdjustable,
133130
/// Error raised when the number of channels in the input buffer doesn't match the value expected.
134131
WrongNumberOfInputChannels {
135132
expected: usize,
@@ -161,7 +158,6 @@ pub enum ResampleError {
161158
max: usize,
162159
requested: usize,
163160
},
164-
ChunkSizeNotAdjustable,
165161
}
166162

167163
impl fmt::Display for ResampleError {
@@ -175,9 +171,6 @@ impl fmt::Display for ResampleError {
175171
write!(f, "New resample ratio out of bounds. Provided ratio {}, original resample ratio {}, maximum relative ratio {}, allowed absolute range {} to {}",
176172
provided, original, max_relative_ratio, original / max_relative_ratio, original * max_relative_ratio)
177173
}
178-
Self::SyncNotAdjustable { .. } => {
179-
write!(f, "Not possible to adjust a synchronous resampler")
180-
}
181174
Self::WrongNumberOfInputChannels { expected, actual } => {
182175
write!(
183176
f,
@@ -220,9 +213,6 @@ impl fmt::Display for ResampleError {
220213
requested, max
221214
)
222215
}
223-
Self::ChunkSizeNotAdjustable { .. } => {
224-
write!(f, "This resampler does not support changing the chunk size")
225-
}
226216
}
227217
}
228218
}

0 commit comments

Comments
 (0)