WIP resampling
This commit is contained in:
parent
229ef2a25b
commit
d29fb6d61a
|
@ -121,12 +121,38 @@ fn build_vorbis(
|
|||
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() {
|
||||
let out_dir = Path::new(&var_os("OUT_DIR").unwrap()).to_path_buf();
|
||||
println!("cargo:rustc-link-search=native={}", out_dir.display());
|
||||
|
||||
let OggBuildResult { ogg_include_dir } = build_ogg(&out_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());
|
||||
}
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
use crate::{AudioClip, Segment};
|
||||
use crate::{resampled_audio_clip::ResampledAudioClip, AudioClip, Segment};
|
||||
|
||||
/// Blanket implementations of audio filters for audio clips.
|
||||
pub trait AudioFilters {
|
||||
/// Returns a new audio clip containing a segment of the specified clip.
|
||||
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> {
|
||||
fn segment(self, start: u64, end: u64) -> Box<dyn AudioClip> {
|
||||
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
|
||||
|
@ -20,4 +27,9 @@ where
|
|||
let boxed_audio_clip: Box<dyn AudioClip> = Box::new(self);
|
||||
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 open_audio_file;
|
||||
mod read_and_seek;
|
||||
mod resampled_audio_clip;
|
||||
mod sample_reader_assertions;
|
||||
mod segment;
|
||||
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