WIP resampling
This commit is contained in:
parent
229ef2a25b
commit
d29fb6d61a
|
@ -121,12 +121,38 @@ fn build_vorbis(
|
||||||
VorbisBuildResult { vorbis_utils_path }
|
VorbisBuildResult { vorbis_utils_path }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_libsamplerate(parent_dir: impl AsRef<Path>) {
|
||||||
|
let version = "0.2.2";
|
||||||
|
let repo_dir = checkout(
|
||||||
|
&parent_dir,
|
||||||
|
"https://github.com/libsndfile/libsamplerate.git",
|
||||||
|
version,
|
||||||
|
"libsamplerate",
|
||||||
|
);
|
||||||
|
let include_dir = repo_dir.join("include");
|
||||||
|
let src_dir = repo_dir.join("src");
|
||||||
|
cc::Build::new()
|
||||||
|
.define("PACKAGE", "\"libsamplerate\"")
|
||||||
|
.define("VERSION", format!("\"{version}\"").as_str())
|
||||||
|
.define("HAVE_STDBOOL_H", "1")
|
||||||
|
.define("ENABLE_SINC_MEDIUM_CONVERTER", "1")
|
||||||
|
.include(&include_dir)
|
||||||
|
.files(
|
||||||
|
["samplerate.c", "src_linear.c", "src_sinc.c", "src_zoh.c"]
|
||||||
|
.map(|name| src_dir.join(name)),
|
||||||
|
)
|
||||||
|
.compile("libsamplerate");
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-lib=static=libsamplerate");
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let out_dir = Path::new(&var_os("OUT_DIR").unwrap()).to_path_buf();
|
let out_dir = Path::new(&var_os("OUT_DIR").unwrap()).to_path_buf();
|
||||||
println!("cargo:rustc-link-search=native={}", out_dir.display());
|
println!("cargo:rustc-link-search=native={}", out_dir.display());
|
||||||
|
|
||||||
let OggBuildResult { ogg_include_dir } = build_ogg(&out_dir);
|
let OggBuildResult { ogg_include_dir } = build_ogg(&out_dir);
|
||||||
let VorbisBuildResult { vorbis_utils_path } = build_vorbis(&out_dir, ogg_include_dir);
|
let VorbisBuildResult { vorbis_utils_path } = build_vorbis(&out_dir, ogg_include_dir);
|
||||||
|
build_libsamplerate(out_dir);
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed={}", vorbis_utils_path.display());
|
println!("cargo:rerun-if-changed={}", vorbis_utils_path.display());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
use crate::{AudioClip, Segment};
|
use crate::{resampled_audio_clip::ResampledAudioClip, AudioClip, Segment};
|
||||||
|
|
||||||
/// Blanket implementations of audio filters for audio clips.
|
/// Blanket implementations of audio filters for audio clips.
|
||||||
pub trait AudioFilters {
|
pub trait AudioFilters {
|
||||||
/// Returns a new audio clip containing a segment of the specified clip.
|
/// Returns a new audio clip containing a segment of the specified clip.
|
||||||
fn segment(self, start: u64, end: u64) -> Box<dyn AudioClip>;
|
fn segment(self, start: u64, end: u64) -> Box<dyn AudioClip>;
|
||||||
|
|
||||||
|
/// Returns a new audio clip resampled to the specified sampling rate.
|
||||||
|
fn resampled(self, sampling_rate: u32) -> Box<dyn AudioClip>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFilters for Box<dyn AudioClip> {
|
impl AudioFilters for Box<dyn AudioClip> {
|
||||||
fn segment(self, start: u64, end: u64) -> Box<dyn AudioClip> {
|
fn segment(self, start: u64, end: u64) -> Box<dyn AudioClip> {
|
||||||
Box::new(Segment::new(self, start, end))
|
Box::new(Segment::new(self, start, end))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resampled(self, sampling_rate: u32) -> Box<dyn AudioClip> {
|
||||||
|
Box::new(ResampledAudioClip::new(self, sampling_rate))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TAudioClip> AudioFilters for TAudioClip
|
impl<TAudioClip> AudioFilters for TAudioClip
|
||||||
|
@ -20,4 +27,9 @@ where
|
||||||
let boxed_audio_clip: Box<dyn AudioClip> = Box::new(self);
|
let boxed_audio_clip: Box<dyn AudioClip> = Box::new(self);
|
||||||
boxed_audio_clip.segment(start, end)
|
boxed_audio_clip.segment(start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resampled(self, sampling_rate: u32) -> Box<dyn AudioClip> {
|
||||||
|
let boxed_audio_clip: Box<dyn AudioClip> = Box::new(self);
|
||||||
|
boxed_audio_clip.resampled(sampling_rate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ mod memory_audio_clip;
|
||||||
mod ogg_audio_clip;
|
mod ogg_audio_clip;
|
||||||
mod open_audio_file;
|
mod open_audio_file;
|
||||||
mod read_and_seek;
|
mod read_and_seek;
|
||||||
|
mod resampled_audio_clip;
|
||||||
mod sample_reader_assertions;
|
mod sample_reader_assertions;
|
||||||
mod segment;
|
mod segment;
|
||||||
mod wave_audio_clip;
|
mod wave_audio_clip;
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Raw FFI for libsamplerate
|
||||||
|
|
||||||
|
use std::os::raw::{c_int, c_long, c_void};
|
||||||
|
|
||||||
|
use crate::Sample;
|
||||||
|
|
||||||
|
#[link(name = "libsamplerate")]
|
||||||
|
extern "C" {
|
||||||
|
pub fn src_callback_new(
|
||||||
|
read_func: unsafe extern "C" fn(
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
buffer: *mut *const Sample,
|
||||||
|
) -> c_long,
|
||||||
|
converter_type: ConverterType,
|
||||||
|
channel_count: c_int,
|
||||||
|
error: *mut c_int,
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
) -> *mut State;
|
||||||
|
|
||||||
|
pub fn src_callback_read(
|
||||||
|
state: *mut State,
|
||||||
|
ratio: f64,
|
||||||
|
frame_count: c_long,
|
||||||
|
buffer: *mut Sample,
|
||||||
|
) -> c_long;
|
||||||
|
|
||||||
|
pub fn src_error(state: *mut State) -> c_int;
|
||||||
|
|
||||||
|
pub fn src_reset(state: *mut State) -> c_int;
|
||||||
|
|
||||||
|
// Always returns null
|
||||||
|
pub fn src_delete(state: *mut State) -> *const c_void;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum ConverterType {
|
||||||
|
/// Band-limited sinc interpolation, best quality, 144 dB SNR, 96% BW.
|
||||||
|
SincBestQuality = 0,
|
||||||
|
/// Band-limited sinc interpolation, medium quality, 121 dB SNR, 90% BW.
|
||||||
|
SincMediumQuality = 1,
|
||||||
|
/// Band-limited sinc interpolation, low quality, 97 dB SNR, 80% BW.
|
||||||
|
SincFastest = 2,
|
||||||
|
/// Zero order hold interpolator, very fast, poor quality.
|
||||||
|
ZeroOrderHold = 3,
|
||||||
|
/// Linear interpolator, blindingly fast, poor quality.
|
||||||
|
Linear = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct State {
|
||||||
|
private: [u8; 0],
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
mod libsamplerate_raw;
|
||||||
|
mod resampled_audio_clip;
|
||||||
|
|
||||||
|
pub use resampled_audio_clip::ResampledAudioClip;
|
|
@ -0,0 +1,175 @@
|
||||||
|
use crate::{
|
||||||
|
resampled_audio_clip::libsamplerate_raw::src_error,
|
||||||
|
sample_reader_assertions::SampleReaderAssertions, AudioClip, AudioError, Sample, SampleReader,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
cmp::min,
|
||||||
|
mem::MaybeUninit,
|
||||||
|
os::raw::{c_long, c_void},
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::libsamplerate_raw::{
|
||||||
|
src_callback_new, src_callback_read, src_delete, src_reset, ConverterType, State,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A resampled representation of another audio clip.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResampledAudioClip {
|
||||||
|
inner_clip: Arc<dyn AudioClip>,
|
||||||
|
sampling_rate: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResampledAudioClip {
|
||||||
|
pub fn new(inner_clip: Box<dyn AudioClip>, sampling_rate: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
inner_clip: Arc::from(inner_clip),
|
||||||
|
sampling_rate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn factor(&self) -> f64 {
|
||||||
|
f64::from(self.sampling_rate) / f64::from(self.inner_clip.sampling_rate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioClip for ResampledAudioClip {
|
||||||
|
fn len(&self) -> u64 {
|
||||||
|
(self.inner_clip.len() as f64 * self.factor()).round() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sampling_rate(&self) -> u32 {
|
||||||
|
self.sampling_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_sample_reader(&self) -> Result<Box<dyn SampleReader>, AudioError> {
|
||||||
|
Ok(Box::new(ResampledSampleReader::new(self)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ResampledSampleReader {
|
||||||
|
callback_data: Pin<Box<CallbackData>>,
|
||||||
|
factor: f64,
|
||||||
|
len: u64,
|
||||||
|
position: u64,
|
||||||
|
position_changed: bool,
|
||||||
|
libsamplerate_state: *mut State,
|
||||||
|
}
|
||||||
|
|
||||||
|
const INPUT_BUFFER_SIZE: usize = 500;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CallbackData {
|
||||||
|
inner_sample_reader: Box<dyn SampleReader>,
|
||||||
|
input_buffer: [Sample; INPUT_BUFFER_SIZE],
|
||||||
|
last_error: Option<AudioError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResampledSampleReader {
|
||||||
|
fn new(clip: &ResampledAudioClip) -> Result<Self, AudioError> {
|
||||||
|
let callback_data = Box::pin(CallbackData {
|
||||||
|
inner_sample_reader: clip.inner_clip.create_sample_reader()?,
|
||||||
|
input_buffer: [0.0; INPUT_BUFFER_SIZE],
|
||||||
|
last_error: None,
|
||||||
|
});
|
||||||
|
let channel_count = 1;
|
||||||
|
let mut error = MaybeUninit::uninit();
|
||||||
|
let libsamplerate_state = unsafe {
|
||||||
|
src_callback_new(
|
||||||
|
read,
|
||||||
|
ConverterType::SincMediumQuality,
|
||||||
|
channel_count,
|
||||||
|
error.as_mut_ptr(),
|
||||||
|
&*callback_data as *const CallbackData as *mut c_void,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert_eq!(unsafe { error.assume_init() }, 0);
|
||||||
|
Ok(Self {
|
||||||
|
callback_data,
|
||||||
|
factor: clip.factor(),
|
||||||
|
len: clip.len(),
|
||||||
|
position: 0,
|
||||||
|
position_changed: false,
|
||||||
|
libsamplerate_state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ResampledSampleReader {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
src_delete(self.libsamplerate_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SampleReader for ResampledSampleReader {
|
||||||
|
fn len(&self) -> u64 {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position(&self) -> u64 {
|
||||||
|
self.position
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_position(&mut self, position: u64) {
|
||||||
|
if position == self.position {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assert_valid_seek_position(position);
|
||||||
|
self.position = position;
|
||||||
|
self.position_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&mut self, buffer: &mut [Sample]) -> Result<(), AudioError> {
|
||||||
|
self.assert_valid_read_size(buffer);
|
||||||
|
if self.position_changed {
|
||||||
|
self.callback_data
|
||||||
|
.inner_sample_reader
|
||||||
|
.set_position((self.position as f64 / self.factor) as u64);
|
||||||
|
let error = unsafe { src_reset(self.libsamplerate_state) };
|
||||||
|
assert_eq!(error, 0);
|
||||||
|
self.position_changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let samples_read = unsafe {
|
||||||
|
src_callback_read(
|
||||||
|
self.libsamplerate_state,
|
||||||
|
self.factor,
|
||||||
|
buffer.len() as i32,
|
||||||
|
buffer.as_mut_ptr(),
|
||||||
|
) as usize
|
||||||
|
};
|
||||||
|
if let Some(error) = self.callback_data.last_error.take() {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
if samples_read < buffer.len() {
|
||||||
|
assert_eq!(unsafe { src_error(self.libsamplerate_state) }, 0);
|
||||||
|
panic!("Expected {} samples, got {}.", buffer.len(), samples_read);
|
||||||
|
}
|
||||||
|
self.position += buffer.len() as u64;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn read(callback_data: *mut c_void, buffer: *mut *const Sample) -> c_long {
|
||||||
|
let callback_data = callback_data.cast::<CallbackData>();
|
||||||
|
let inner_sample_reader = &mut (*callback_data).inner_sample_reader;
|
||||||
|
let sample_count = min(inner_sample_reader.remainder(), INPUT_BUFFER_SIZE as u64) as usize;
|
||||||
|
|
||||||
|
// Allow reading beyond the end of the audio clip by filling with zeros
|
||||||
|
let (sample_buffer, zero_buffer) = (*callback_data).input_buffer.split_at_mut(sample_count);
|
||||||
|
let read_result = inner_sample_reader.read(sample_buffer);
|
||||||
|
if let Err(error) = read_result {
|
||||||
|
(*callback_data).last_error = Some(error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
zero_buffer.fill(0.0);
|
||||||
|
|
||||||
|
*buffer = sample_buffer.as_ptr();
|
||||||
|
sample_buffer.len() as c_long
|
||||||
|
}
|
Loading…
Reference in New Issue