use crate::EvaluationError;
use std::path::Path;
use voirs_sdk::AudioBuffer;
pub mod auto_conversion;
pub mod conversion;
pub mod formats;
pub mod loader;
pub mod streaming;
pub mod validation;
pub use auto_conversion::*;
pub use conversion::*;
pub use formats::*;
pub use loader::*;
pub use streaming::*;
pub use validation::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AudioFormat {
Wav,
Flac,
Mp3,
Ogg,
M4a,
Aiff,
Unknown,
}
impl AudioFormat {
pub fn from_extension(path: &Path) -> Self {
if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
match ext.to_lowercase().as_str() {
"wav" | "wave" => Self::Wav,
"flac" => Self::Flac,
"mp3" => Self::Mp3,
"ogg" => Self::Ogg,
"m4a" | "aac" => Self::M4a,
"aiff" | "aif" => Self::Aiff,
_ => Self::Unknown,
}
} else {
Self::Unknown
}
}
pub fn extensions(&self) -> &'static [&'static str] {
match self {
Self::Wav => &["wav", "wave"],
Self::Flac => &["flac"],
Self::Mp3 => &["mp3"],
Self::Ogg => &["ogg"],
Self::M4a => &["m4a", "aac"],
Self::Aiff => &["aiff", "aif"],
Self::Unknown => &[],
}
}
pub fn supports_metadata(&self) -> bool {
matches!(self, Self::Flac | Self::Mp3 | Self::Ogg | Self::M4a)
}
pub fn is_lossless(&self) -> bool {
matches!(self, Self::Wav | Self::Flac | Self::Aiff)
}
}
#[derive(Debug, Clone)]
pub struct LoadOptions {
pub target_sample_rate: Option<u32>,
pub target_channels: Option<u32>,
pub normalize: bool,
pub remove_dc_offset: bool,
pub start_offset: Option<f64>,
pub duration: Option<f64>,
pub resample_quality: u8,
}
impl Default for LoadOptions {
fn default() -> Self {
Self {
target_sample_rate: None,
target_channels: None,
normalize: false,
remove_dc_offset: true,
start_offset: None,
duration: None,
resample_quality: 7, }
}
}
impl LoadOptions {
pub fn new() -> Self {
Self::default()
}
pub fn target_sample_rate(mut self, sample_rate: u32) -> Self {
self.target_sample_rate = Some(sample_rate);
self
}
pub fn target_channels(mut self, channels: u32) -> Self {
self.target_channels = Some(channels);
self
}
pub fn normalize(mut self, normalize: bool) -> Self {
self.normalize = normalize;
self
}
pub fn remove_dc_offset(mut self, remove_dc: bool) -> Self {
self.remove_dc_offset = remove_dc;
self
}
pub fn start_offset(mut self, offset: f64) -> Self {
self.start_offset = Some(offset);
self
}
pub fn duration(mut self, duration: f64) -> Self {
self.duration = Some(duration);
self
}
pub fn resample_quality(mut self, quality: u8) -> Self {
self.resample_quality = quality.min(10);
self
}
}
#[derive(Debug, Clone, Default)]
pub struct AudioMetadata {
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub track: Option<u32>,
pub year: Option<u32>,
pub genre: Option<String>,
pub duration: Option<f64>,
pub bitrate: Option<u32>,
}
#[derive(Debug, thiserror::Error)]
pub enum AudioIoError {
#[error("Unsupported audio format: {format:?}")]
UnsupportedFormat {
format: AudioFormat,
},
#[error("File I/O error: {message}")]
IoError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Audio decoding error: {message}")]
DecodingError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Invalid audio parameters: {message}")]
InvalidParameters {
message: String,
},
#[error("Audio conversion error: {message}")]
ConversionError {
message: String,
},
}
impl From<AudioIoError> for EvaluationError {
fn from(err: AudioIoError) -> Self {
EvaluationError::AudioProcessingError {
message: err.to_string(),
source: Some(Box::new(err)),
}
}
}
pub type AudioIoResult<T> = Result<T, AudioIoError>;
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_format_detection() {
assert_eq!(
AudioFormat::from_extension(Path::new("test.wav")),
AudioFormat::Wav
);
assert_eq!(
AudioFormat::from_extension(Path::new("test.flac")),
AudioFormat::Flac
);
assert_eq!(
AudioFormat::from_extension(Path::new("test.mp3")),
AudioFormat::Mp3
);
assert_eq!(
AudioFormat::from_extension(Path::new("test.ogg")),
AudioFormat::Ogg
);
assert_eq!(
AudioFormat::from_extension(Path::new("test.m4a")),
AudioFormat::M4a
);
assert_eq!(
AudioFormat::from_extension(Path::new("test.aiff")),
AudioFormat::Aiff
);
assert_eq!(
AudioFormat::from_extension(Path::new("test.xyz")),
AudioFormat::Unknown
);
}
#[test]
fn test_format_properties() {
assert!(AudioFormat::Wav.is_lossless());
assert!(AudioFormat::Flac.is_lossless());
assert!(!AudioFormat::Mp3.is_lossless());
assert!(!AudioFormat::Ogg.is_lossless());
assert!(!AudioFormat::Wav.supports_metadata());
assert!(AudioFormat::Flac.supports_metadata());
assert!(AudioFormat::Mp3.supports_metadata());
}
#[test]
fn test_load_options() {
let options = LoadOptions::new()
.target_sample_rate(16000)
.target_channels(1)
.normalize(true);
assert_eq!(options.target_sample_rate, Some(16000));
assert_eq!(options.target_channels, Some(1));
assert!(options.normalize);
}
#[test]
fn test_load_options_defaults() {
let options = LoadOptions::default();
assert_eq!(options.target_sample_rate, None);
assert_eq!(options.target_channels, None);
assert!(!options.normalize);
assert!(options.remove_dc_offset);
assert_eq!(options.resample_quality, 7);
}
}