Skip to content

Commit 04f9ce8

Browse files
committed
add resampler for const source
1 parent 52aaf5c commit 04f9ce8

3 files changed

Lines changed: 227 additions & 0 deletions

File tree

src/const_source.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod conversions;
2626
pub use buffer::SamplesBuffer;
2727
pub use chain::SourceChain;
2828
pub use conversions::channel_count::ChannelConvertor;
29+
pub use conversions::sample_rate::SampleRateConvertor;
2930

3031
/// A source which sample rate and channel count are fixed at compile time.
3132
pub trait ConstSource<const SR: u32, const CH: u16>: Iterator<Item = Sample> {
@@ -49,6 +50,17 @@ pub trait ConstSource<const SR: u32, const CH: u16>: Iterator<Item = Sample> {
4950
})
5051
}
5152

53+
/// Convert from `SR` (the current sample rate) to `SR_OUT`.
54+
///
55+
/// Though the defaults cover most use-cases you can configure
56+
/// the resampler using [`with_config`](SampleRateConvertor::with_config).
57+
fn with_sample_rate<const SR_OUT: u32>(self) -> SampleRateConvertor<SR, SR_OUT, CH, Self>
58+
where
59+
Self: Sized,
60+
{
61+
SampleRateConvertor::new(self)
62+
}
63+
5264
/// Convert from the current channel count to `CH_OUT`.
5365
fn with_channel_count<const CH_OUT: u16>(self) -> ChannelConvertor<SR, CH, CH_OUT, Self>
5466
where

src/const_source/conversions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod channel_count;
2+
pub mod sample_rate;
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use crate::conversions::sample_rate::rubato::{ResampleInner, RubatoAsyncResample};
2+
use crate::conversions::Interpolation;
3+
use crate::math::gcd;
4+
use crate::source::ResampleConfig;
5+
use crate::{ConstSource, Sample, SampleRate, Source};
6+
7+
use crate::const_source::IntoDynamicSource;
8+
use crate::conversions::sample_rate::{InSamples, OutSamples};
9+
10+
/// Resamples an audio source to a target sample rate using Rubato.
11+
pub struct SampleRateConvertor<
12+
const SR_IN: u32,
13+
const SR_OUT: u32,
14+
const CH: u16,
15+
S: ConstSource<SR_IN, CH>,
16+
> {
17+
// Option so we can take out the source and rebuild the resampler without unsafe
18+
inner: Option<ResampleInner<IntoDynamicSource<SR_IN, CH, S>>>,
19+
}
20+
21+
#[derive(thiserror::Error)]
22+
#[error("The resampler was already running")]
23+
pub struct ResamplerRunning<
24+
const SR_IN: u32,
25+
const SR_OUT: u32,
26+
const CH: u16,
27+
S: ConstSource<SR_IN, CH>,
28+
>(SampleRateConvertor<SR_IN, SR_OUT, CH, S>);
29+
30+
impl<const SR_IN: u32, const SR_OUT: u32, const CH: u16, S: ConstSource<SR_IN, CH>>
31+
SampleRateConvertor<SR_IN, SR_OUT, CH, S>
32+
{
33+
pub(crate) fn new(source: S) -> Self {
34+
let target_rate =
35+
const { SampleRate::new(SR_OUT).expect("SampleRate (SR_OUT) may not be zero") };
36+
Self {
37+
inner: Some(Self::create_resampler(
38+
source.into_dynamic_source(),
39+
target_rate,
40+
ResampleConfig::default(),
41+
)),
42+
}
43+
}
44+
45+
/// Further configure the resampler created with [`with_sample_rate`](ConstSource::with_sample_rate).
46+
/// You usually do not need to do this unless you have a specific use-case that requires very
47+
/// fast or extremely high quality resampling. The [`ResampleConfig`] has a number of factories
48+
/// with good defaults for such use-cases.
49+
///
50+
/// # Errors
51+
/// If the resampler can no longer be reconfigured, usually after yielding
52+
/// the first sample.
53+
///
54+
/// # Example
55+
/// ```
56+
/// # use rodio::generators::const_source::Silence;
57+
/// # use rodio::SampleRate;
58+
/// # fn hi() -> Option<()> { // to enable ? in the example
59+
/// use rodio::ConstSource;
60+
/// use rodio::conversions::ResampleConfig;
61+
///
62+
/// let source: Silence<44100> = Silence::new();
63+
/// let resampled = source
64+
/// .with_sample_rate::<48000>()
65+
/// .with_config(ResampleConfig::fast());
66+
/// # Some(())
67+
/// # }
68+
/// # hi().unwrap();
69+
/// ```
70+
#[allow(clippy::result_large_err, reason = "the Ok variant is the same size")]
71+
pub fn with_config(
72+
mut self,
73+
config: ResampleConfig,
74+
) -> Result<Self, ResamplerRunning<SR_IN, SR_OUT, CH, S>> {
75+
if !self.resampler().can_reconfigure() {
76+
return Err(ResamplerRunning(self));
77+
}
78+
79+
let source = self
80+
.inner
81+
.take()
82+
.expect(
83+
"we are the only ones who set this to none and we set it to \
84+
some at the end of this fn",
85+
)
86+
.into_inner();
87+
let target_rate = SampleRate::new(SR_OUT).expect("already checked in 'new'");
88+
89+
Ok(Self {
90+
inner: Some(Self::create_resampler(source, target_rate, config)),
91+
})
92+
}
93+
94+
fn resampler(&self) -> &ResampleInner<IntoDynamicSource<SR_IN, CH, S>> {
95+
self.inner
96+
.as_ref()
97+
.expect("never none outside `with_config`")
98+
}
99+
100+
fn resampler_mut(&mut self) -> &mut ResampleInner<IntoDynamicSource<SR_IN, CH, S>> {
101+
self.inner
102+
.as_mut()
103+
.expect("never none outside `with_config`")
104+
}
105+
106+
fn create_resampler(
107+
source: IntoDynamicSource<SR_IN, CH, S>,
108+
target_rate: SampleRate,
109+
config: ResampleConfig,
110+
) -> ResampleInner<IntoDynamicSource<SR_IN, CH, S>> {
111+
if source.sample_rate() == target_rate {
112+
let channels = source.channels();
113+
ResampleInner::Passthrough {
114+
source_rate: source.sample_rate(),
115+
source,
116+
input_span_pos: InSamples::ZERO,
117+
channels,
118+
}
119+
} else {
120+
match config {
121+
ResampleConfig::Poly { degree, chunk_size } => {
122+
let resampler =
123+
RubatoAsyncResample::new_poly(source, target_rate, chunk_size, degree)
124+
.expect("Failed to create polynomial resampler");
125+
ResampleInner::Poly(resampler)
126+
}
127+
ResampleConfig::Sinc(mut sinc) => {
128+
#[cfg(feature = "rubato-fft")]
129+
if sinc.is_supported_fixed_ratio(target_rate, source_rate) {
130+
let resampler = RubatoFftResample::new(
131+
source,
132+
target_rate,
133+
sinc.chunk_size,
134+
sinc.sub_chunks,
135+
)
136+
.expect("Failed to create FFT resampler");
137+
return ResampleInner::Fft(resampler);
138+
}
139+
140+
if sinc.is_supported_fixed_ratio(target_rate, source.sample_rate()) {
141+
sinc.interpolation = Interpolation::Nearest;
142+
let g = gcd(target_rate.get(), source.sample_rate().get());
143+
let numer = target_rate.get() / g;
144+
let denom = source.sample_rate().get() / g;
145+
let ratio = numer.max(denom) as usize;
146+
sinc.oversampling_factor = ratio;
147+
}
148+
ResampleInner::Sinc(sinc.build(source, target_rate))
149+
}
150+
}
151+
}
152+
}
153+
}
154+
155+
impl<const SR_IN: u32, const SR_OUT: u32, const CH: u16, S: ConstSource<SR_IN, CH>>
156+
ConstSource<SR_OUT, CH> for SampleRateConvertor<SR_IN, SR_OUT, CH, S>
157+
{
158+
fn total_duration(&self) -> Option<std::time::Duration> {
159+
self.resampler().inner().total_duration()
160+
}
161+
}
162+
163+
impl<const SR_IN: u32, const SR_OUT: u32, const CH: u16, S> Iterator
164+
for SampleRateConvertor<SR_IN, SR_OUT, CH, S>
165+
where
166+
S: ConstSource<SR_IN, CH>,
167+
{
168+
type Item = Sample;
169+
170+
#[inline]
171+
fn next(&mut self) -> Option<Self::Item> {
172+
match self.resampler_mut() {
173+
ResampleInner::Passthrough { source, .. } => source.next(),
174+
ResampleInner::Poly(resampler) => resampler.next_sample(),
175+
ResampleInner::Sinc(resampler) => resampler.next_sample(),
176+
#[cfg(feature = "rubato-fft")]
177+
ResampleInner::Fft(resampler) => resampler.next_sample(),
178+
}
179+
}
180+
181+
#[inline]
182+
fn size_hint(&self) -> (usize, Option<usize>) {
183+
match self.resampler() {
184+
ResampleInner::Passthrough { source, .. } => source.size_hint(),
185+
ResampleInner::Poly(resampler) | ResampleInner::Sinc(resampler) => {
186+
let adjusted_for_resampling = |samples| {
187+
InSamples(samples).resampled_by(resampler.resample_ratio)
188+
+ resampler.output.len()
189+
+ resampler
190+
.frames_being_resampled
191+
.samples(resampler.output.channels)
192+
};
193+
let (lower, upper) = resampler.input.size_hint();
194+
let lower = adjusted_for_resampling(lower);
195+
let upper = upper.map(adjusted_for_resampling);
196+
(lower.raw(), upper.as_ref().map(OutSamples::raw))
197+
}
198+
#[cfg(feature = "rubato-fft")]
199+
ResampleInner::Fft(resampler) => {
200+
let adjusted_for_resampling = |samples| {
201+
InSamples(samples).resampled_by(resampler.resample_ratio)
202+
+ resampler.output.len()
203+
+ resampler
204+
.frames_being_resampled
205+
.samples(resampler.output.channels)
206+
};
207+
let (lower, upper) = resampler.input.size_hint();
208+
let lower = adjusted_for_resampling(lower);
209+
let upper = upper.map(adjusted_for_resampling);
210+
(lower.raw(), upper.as_ref().map(OutSamples::raw))
211+
}
212+
}
213+
}
214+
}

0 commit comments

Comments
 (0)