Skip to content

Commit

Permalink
Merge pull request #78 from HEnquist/optional_fft
Browse files Browse the repository at this point in the history
Optional fft resamplers via Cargo feature
  • Loading branch information
HEnquist authored Mar 5, 2024
2 parents 97ca02b + 4217271 commit acca880
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 173 deletions.
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rubato"
version = "0.14.1"
version = "0.15.0"
rust-version = "1.61"
authors = ["HEnquist <[email protected]>"]
description = "Asynchronous resampling library intended for audio data"
Expand All @@ -13,10 +13,14 @@ edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["fft_resampler"]
fft_resampler = ["realfft", "num-complex"]

[dependencies]
log = { version = "0.4.18", optional = true }
realfft = "3.3.0"
num-complex = "0.4"
realfft = { version = "3.3.0", optional = true }
num-complex = { version = "0.4", optional = true }
num-integer = "0.1.45"
num-traits = "0.2"

Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ The synchronous resamplers benefit from the SIMD support of the RustFFT library.

## Cargo features

### `fft_resampler`: Enable the FFT based synchronous resamplers

This feature is enabled by default. Disable it if the FFT resamplers are not needed,
to save compile time and reduce the resulting binary size.

### `log`: Enable logging

This feature enables logging via the `log` crate. This is intended for debugging purposes.
Expand Down Expand Up @@ -118,6 +123,10 @@ The `rubato` crate requires rustc version 1.61 or newer.

## Changelog

- v0.15.0
- Make FFT resamplers optional via `fft_resampler` feature.
- Fix calculation of input and output sizes when creating FftFixedInOut resampler.
- Fix panic when using very small chunksizes (less than 5).
- v0.14.1
- More bugfixes for buffer allocation and max output length calculation.
- Fix building with `log` feature.
Expand Down
2 changes: 1 addition & 1 deletion examples/fastfixedin_ramp64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use log::LevelFilter;
///! To resample the file `sine_f64_2ch.raw` from 44.1kHz to 192kHz, and assuming the file has two channels,
/// and that the resampling ratio should be ramped to 150% during 3 seconds, the command is:
///! ```
///! cargo run --release --example fixedin_ramp64 sine_f64_2ch.raw test.raw 44100 192000 2 150 3
///! cargo run --release --example fastfixedin_ramp64 sine_f64_2ch.raw test.raw 44100 192000 2 150 3
///! ```
///! There are two helper python scripts for testing. `makesineraw.py` simply writes a stereo file
///! with a 1 second long 1kHz tone (at 44.1kHz). This script takes no aruments. Modify as needed to create other test files.
Expand Down
14 changes: 6 additions & 8 deletions examples/makesineraw.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# Make a simple spike for testing purposes
# Make a sine for testing purposes
# Store as interleaved stereo
import numpy as np


#wave64 = np.zeros((2,44100), dtype="float64")
#wave32 = np.zeros((2,44100), dtype="float32")
t = np.linspace(0, 1.0, num=int(1.0*44100), endpoint=False)
wave = np.sin(10000*2*np.pi*t)
t = np.linspace(0, 10.0, num=int(10.0*44100), endpoint=False)
wave = np.sin(1000*2*np.pi*t)
wave= np.reshape(wave,(-1,1))
wave = np.concatenate((wave, wave), axis=1)

Expand All @@ -14,7 +12,7 @@


#print(wave64)
wave64.tofile("sine_44.1_10000_f64_2ch_1.0s.raw")
wave32.tofile("sine_44.1_10000_f32_2ch_1.0s.raw")
wave64.tofile("sine_44.1_1000_f64_2ch_10.0s.raw")
wave32.tofile("sine_44.1_1000_f32_2ch_10.0s.raw")


12 changes: 8 additions & 4 deletions examples/process_f64.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
extern crate rubato;
use rubato::{
calculate_cutoff, implement_resampler, FastFixedIn, FastFixedOut, FftFixedIn, FftFixedInOut,
FftFixedOut, PolynomialDegree, SincFixedIn, SincFixedOut, SincInterpolationParameters,
SincInterpolationType, WindowFunction,
calculate_cutoff, implement_resampler, FastFixedIn, FastFixedOut, PolynomialDegree,
SincFixedIn, SincFixedOut, SincInterpolationParameters, SincInterpolationType, WindowFunction,
};
#[cfg(feature = "fft_resampler")]
use rubato::{FftFixedIn, FftFixedInOut, FftFixedOut};
use std::convert::TryInto;
use std::env;
use std::fs::File;
Expand Down Expand Up @@ -63,7 +64,7 @@ fn write_frames<W: Write + Seek>(
frames_to_write: usize,
) {
let channels = waves.len();
let end = frames_to_skip + frames_to_write;
let end = (frames_to_skip + frames_to_write).min(waves[0].len() - 1);
for frame in frames_to_skip..end {
for wave in waves.iter().take(channels) {
let value64 = wave[frame];
Expand Down Expand Up @@ -164,12 +165,15 @@ fn main() {
"FastFixedOut" => {
Box::new(FastFixedOut::<f64>::new(f_ratio, 1.1, PolynomialDegree::Septic, 1024, channels).unwrap())
}
#[cfg(feature = "fft_resampler")]
"FftFixedIn" => {
Box::new(FftFixedIn::<f64>::new(fs_in, fs_out, 1024, 2, channels).unwrap())
}
#[cfg(feature = "fft_resampler")]
"FftFixedOut" => {
Box::new(FftFixedOut::<f64>::new(fs_in, fs_out, 1024, 2, channels).unwrap())
}
#[cfg(feature = "fft_resampler")]
"FftFixedInOut" => {
Box::new(FftFixedInOut::<f64>::new(fs_in, fs_out, 1024, channels).unwrap())
}
Expand Down
119 changes: 97 additions & 22 deletions src/asynchro_fast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ where
validate_ratios(resample_ratio, max_resample_ratio_relative)?;

let needed_input_size =
(chunk_size as f64 / resample_ratio).ceil() as usize + 2 + POLYNOMIAL_LEN_U / 2;
(chunk_size as f64 / resample_ratio).ceil() as usize + POLYNOMIAL_LEN_U / 2;
let buffer_channel_length = ((max_resample_ratio_relative + 1.0) * needed_input_size as f64)
as usize
+ 2 * POLYNOMIAL_LEN_U;
Expand Down Expand Up @@ -601,7 +601,7 @@ where

match self.interpolation {
PolynomialDegree::Septic => {
for n in 0..self.chunk_size {
for frame in 0..self.chunk_size {
t_ratio += t_ratio_increment;
idx += t_ratio;
let idx_floor = idx.floor();
Expand All @@ -618,14 +618,14 @@ where
*wave_out
.get_unchecked_mut(chan)
.as_mut()
.get_unchecked_mut(n) = interp_septic(frac_offset, buf);
.get_unchecked_mut(frame) = interp_septic(frac_offset, buf);
}
}
}
}
}
PolynomialDegree::Quintic => {
for n in 0..self.chunk_size {
for frame in 0..self.chunk_size {
t_ratio += t_ratio_increment;
idx += t_ratio;
let idx_floor = idx.floor();
Expand All @@ -642,14 +642,14 @@ where
*wave_out
.get_unchecked_mut(chan)
.as_mut()
.get_unchecked_mut(n) = interp_quintic(frac_offset, buf);
.get_unchecked_mut(frame) = interp_quintic(frac_offset, buf);
}
}
}
}
}
PolynomialDegree::Cubic => {
for n in 0..self.chunk_size {
for frame in 0..self.chunk_size {
t_ratio += t_ratio_increment;
idx += t_ratio;
let idx_floor = idx.floor();
Expand All @@ -666,14 +666,14 @@ where
*wave_out
.get_unchecked_mut(chan)
.as_mut()
.get_unchecked_mut(n) = interp_cubic(frac_offset, buf);
.get_unchecked_mut(frame) = interp_cubic(frac_offset, buf);
}
}
}
}
}
PolynomialDegree::Linear => {
for n in 0..self.chunk_size {
for frame in 0..self.chunk_size {
t_ratio += t_ratio_increment;
idx += t_ratio;
let idx_floor = idx.floor();
Expand All @@ -690,14 +690,14 @@ where
*wave_out
.get_unchecked_mut(chan)
.as_mut()
.get_unchecked_mut(n) = interp_lin(frac_offset, buf);
.get_unchecked_mut(frame) = interp_lin(frac_offset, buf);
}
}
}
}
}
PolynomialDegree::Nearest => {
for n in 0..self.chunk_size {
for frame in 0..self.chunk_size {
t_ratio += t_ratio_increment;
idx += t_ratio;
let start_idx = idx.floor() as isize;
Expand All @@ -711,7 +711,7 @@ where
*wave_out
.get_unchecked_mut(chan)
.as_mut()
.get_unchecked_mut(n) = *point;
.get_unchecked_mut(frame) = *point;
}
}
}
Expand All @@ -726,8 +726,7 @@ where
self.needed_input_size = (self.last_index as f32
+ self.chunk_size as f32 / self.resample_ratio as f32
+ POLYNOMIAL_LEN_U as f32)
.ceil() as usize
+ 2;
.ceil() as usize;
trace!(
"Resampling channels {:?}, {} frames in, {} frames out. Next needed length: {} frames, last index {}",
active_channels_mask,
Expand All @@ -740,7 +739,7 @@ where
}

fn input_frames_max(&self) -> usize {
(self.chunk_size as f64 * self.resample_ratio_original * self.max_relative_ratio).ceil()
(self.chunk_size as f64 / self.resample_ratio_original * self.max_relative_ratio).ceil()
as usize
+ 2
+ POLYNOMIAL_LEN_U / 2
Expand Down Expand Up @@ -779,8 +778,7 @@ where
+ self.chunk_size as f32
/ (0.5 * self.resample_ratio as f32 + 0.5 * self.target_ratio as f32))
.ceil() as usize
+ POLYNOMIAL_LEN_U
+ 2;
+ POLYNOMIAL_LEN_U;
Ok(())
} else {
Err(ResampleError::RatioOutOfBounds {
Expand All @@ -802,7 +800,6 @@ where
.for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero()));
self.needed_input_size = (self.chunk_size as f64 / self.resample_ratio_original).ceil()
as usize
+ 2
+ POLYNOMIAL_LEN_U / 2;
self.current_buffer_fill = self.needed_input_size;
self.last_index = -(POLYNOMIAL_LEN_I / 2) as f64;
Expand All @@ -814,9 +811,9 @@ where

#[cfg(test)]
mod tests {
use crate::check_output;
use crate::PolynomialDegree;
use crate::Resampler;
use crate::{check_output, check_ratio};
use crate::{FastFixedIn, FastFixedOut};
use rand::Rng;

Expand Down Expand Up @@ -1140,16 +1137,94 @@ mod tests {
}

#[test]
fn check_fo_output() {
fn check_fo_output_up() {
let mut resampler =
FastFixedOut::<f64>::new(8.0, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_output!(check_fo_output, resampler);
check_output!(resampler);
}

#[test]
fn check_fi_output() {
fn check_fo_output_down() {
let mut resampler =
FastFixedOut::<f64>::new(0.8, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_output!(resampler);
}

#[test]
fn check_fi_output_up() {
let mut resampler =
FastFixedIn::<f64>::new(8.0, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_output!(check_fo_output, resampler);
check_output!(resampler);
}

#[test]
fn check_fi_output_down() {
let mut resampler =
FastFixedIn::<f64>::new(0.8, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_output!(resampler);
}

#[test]
fn resample_small_fo_up() {
let ratio = 96000.0 / 44100.0;
let mut resampler =
FastFixedOut::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap();
check_ratio!(resampler, ratio, 1000000);
}

#[test]
fn resample_big_fo_up() {
let ratio = 96000.0 / 44100.0;
let mut resampler =
FastFixedOut::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_ratio!(resampler, ratio, 1000);
}

#[test]
fn resample_small_fo_down() {
let ratio = 44100.0 / 96000.0;
let mut resampler =
FastFixedOut::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap();
check_ratio!(resampler, ratio, 1000000);
}

#[test]
fn resample_big_fo_down() {
let ratio = 44100.0 / 96000.0;
let mut resampler =
FastFixedOut::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_ratio!(resampler, ratio, 1000);
}

#[test]
fn resample_small_fi_up() {
let ratio = 96000.0 / 44100.0;
let mut resampler =
FastFixedIn::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap();
check_ratio!(resampler, ratio, 1000000);
}

#[test]
fn resample_big_fi_up() {
let ratio = 96000.0 / 44100.0;
let mut resampler =
FastFixedIn::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_ratio!(resampler, ratio, 1000);
}

#[test]
fn resample_small_fi_down() {
let ratio = 44100.0 / 96000.0;
let mut resampler =
FastFixedIn::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap();
check_ratio!(resampler, ratio, 1000000);
}

#[test]
fn resample_big_fi_down() {
let ratio = 44100.0 / 96000.0;
let mut resampler =
FastFixedIn::<f32>::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap();
check_ratio!(resampler, ratio, 1000);
}
}
Loading

0 comments on commit acca880

Please sign in to comment.