use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
use core::str::FromStr;
use std::borrow::{Borrow, Cow};
use std::fs::File;
use std::io;
use std::num::{NonZeroU32, NonZeroUsize};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::time::Duration;
use audio_samples::SampleType;
use memmap2::Mmap;
use crate::error::AudioIOError;
use crate::traits::AudioInfoMarker;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidatedSampleType {
U8,
I16,
I24,
I32,
F32,
F64,
}
impl ValidatedSampleType {
pub const fn bits_per_sample(&self) -> NonZeroUsize {
match self {
ValidatedSampleType::U8 => audio_samples::nzu!(8),
ValidatedSampleType::I16 => audio_samples::nzu!(16),
ValidatedSampleType::I24 => audio_samples::nzu!(24),
ValidatedSampleType::I32 | ValidatedSampleType::F32 => audio_samples::nzu!(32),
ValidatedSampleType::F64 => audio_samples::nzu!(64),
}
}
pub const fn bytes_per_sample(&self) -> NonZeroUsize {
self.bits_per_sample().div_ceil(audio_samples::nzu!(8))
}
}
impl TryFrom<SampleType> for ValidatedSampleType {
type Error = AudioIOError;
fn try_from(value: SampleType) -> Result<Self, Self::Error> {
match value {
SampleType::U8 => Ok(ValidatedSampleType::U8),
SampleType::I16 => Ok(ValidatedSampleType::I16),
SampleType::I24 => Ok(ValidatedSampleType::I24),
SampleType::I32 => Ok(ValidatedSampleType::I32),
SampleType::F32 => Ok(ValidatedSampleType::F32),
SampleType::F64 => Ok(ValidatedSampleType::F64),
_ => Err(AudioIOError::unsupported_format(format!(
"Unsupported sample type: {value:?}"
))),
}
}
}
impl From<ValidatedSampleType> for SampleType {
fn from(val: ValidatedSampleType) -> Self {
match val {
ValidatedSampleType::U8 => SampleType::U8,
ValidatedSampleType::I16 => SampleType::I16,
ValidatedSampleType::I24 => SampleType::I24,
ValidatedSampleType::I32 => SampleType::I32,
ValidatedSampleType::F32 => SampleType::F32,
ValidatedSampleType::F64 => SampleType::F64,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AudioInfo<I: AudioInfoMarker> {
pub fp: PathBuf,
pub base_info: BaseAudioInfo,
pub specific_info: I,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FileType {
#[default]
WAV,
MP3,
OGG,
FLAC,
AIFF,
Unknown,
}
impl FileType {
pub const fn as_str(self) -> &'static str {
match self {
FileType::WAV => "wav",
FileType::MP3 => "mp3",
FileType::OGG => "ogg",
FileType::FLAC => "flac",
FileType::AIFF => "aiff",
FileType::Unknown => "unknown",
}
}
pub const fn description(self) -> &'static str {
match self {
FileType::WAV => "Waveform Audio File Format",
FileType::MP3 => "MPEG-1 Audio Layer III",
FileType::OGG => "Ogg Vorbis Container",
FileType::FLAC => "Free Lossless Audio Codec",
FileType::AIFF => "Audio Interchange File Format",
FileType::Unknown => "Unknown or unsupported audio container",
}
}
pub const fn is_compressed(self) -> bool {
matches!(self, FileType::MP3 | FileType::OGG | FileType::FLAC)
}
pub const fn is_lossless(self) -> bool {
matches!(self, FileType::WAV | FileType::FLAC | FileType::AIFF)
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
let Some(ext) = path.as_ref().extension().and_then(|e| e.to_str()) else {
return FileType::Unknown;
};
ext.parse().unwrap_or(FileType::Unknown)
}
}
impl Display for FileType {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if f.alternate() {
write!(f, "{}", self.description())
} else {
write!(f, "{}", self.as_str())
}
}
}
impl FromStr for FileType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"wav" | "WAV" => Ok(FileType::WAV),
"mp3" | "MP3" => Ok(FileType::MP3),
"ogg" | "OGG" => Ok(FileType::OGG),
"flac" | "FLAC" => Ok(FileType::FLAC),
"aiff" | "aif" | "AIFF" | "AIF" => Ok(FileType::AIFF),
_ => Err(()),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct BaseAudioInfo {
pub sample_rate: NonZeroU32,
pub channels: u16,
pub bits_per_sample: u16,
pub bytes_per_sample: u16,
pub byte_rate: u32,
pub block_align: u16,
pub total_samples: usize,
pub duration: Duration,
pub file_type: FileType,
pub sample_type: SampleType,
}
impl BaseAudioInfo {
pub const fn new(
sample_rate: NonZeroU32,
channels: u16,
bits_per_sample: u16,
bytes_per_sample: u16,
byte_rate: u32,
block_align: u16,
total_samples: usize,
duration: Duration,
file_type: FileType,
sample_type: SampleType,
) -> Self {
BaseAudioInfo {
sample_rate,
channels,
bits_per_sample,
bytes_per_sample,
byte_rate,
block_align,
total_samples,
duration,
file_type,
sample_type,
}
}
}
impl Display for BaseAudioInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if !f.alternate() {
return write!(
f,
"{} {} | {} Hz, {} ch, {}-bit, {:.2} s",
self.file_type,
self.sample_type,
self.sample_rate,
self.channels,
self.bits_per_sample,
self.duration.as_secs_f32()
);
}
#[cfg(feature = "colored")]
{
fn label(s: &str) -> ColoredString {
s.bold().bright_blue()
}
fn value<T: ToString>(v: T) -> ColoredString {
v.to_string().bright_green()
}
writeln!(f, "{}", "Audio Info".bold().underline())?;
writeln!(f, "├─ {}: {}", label("File Type"), value(self.format))?;
writeln!(
f,
"├─ {}: {}",
label("Sample Type"),
value(format!("{}", self.sample_type))
)?;
writeln!(
f,
"├─ {}: {}",
label("Sample Rate"),
value(format!("{} Hz", self.sample_rate))
)?;
writeln!(f, "├─ {}: {}", label("Channels"), value(self.channels))?;
writeln!(
f,
"├─ {}: {}",
label("Bits per Sample"),
value(format!("{}-bit", self.bits_per_sample))
)?;
writeln!(
f,
"├─ {}: {}",
label("Bytes per Sample"),
value(format!("{} bytes", self.bytes_per_sample))
)?;
writeln!(
f,
"├─ {}: {}",
label("Total Samples"),
value(self.n_samples)
)?;
writeln!(
f,
"└─ {}: {}",
label("Duration"),
value(format!("{:.2} s", self.duration.as_secs_f32()))
)
}
#[cfg(not(feature = "colored"))]
{
writeln!(f, "Audio Info:")?;
writeln!(f, "├─ File Type: {}", self.file_type)?;
writeln!(f, "├─ Sample Type: {}", self.sample_type)?;
writeln!(f, "├─ Sample Rate: {} Hz", self.sample_rate)?;
writeln!(f, "├─ Channels: {}", self.channels)?;
writeln!(f, "├─ Bits per Sample: {}-bit", self.bits_per_sample)?;
writeln!(f, "├─ Bytes per Sample: {} bytes", self.bytes_per_sample)?;
writeln!(f, "├─ Total Samples: {}", self.total_samples)?;
writeln!(f, "└─ Duration: {:.2} s", self.duration.as_secs_f32())
}
}
}
#[non_exhaustive]
pub enum AudioDataSource<'a> {
Owned(Vec<u8>),
MemoryMapped(Mmap),
Borrowed(&'a [u8]),
}
impl<'a> AudioDataSource<'a> {
#[inline]
pub fn as_bytes(&'a self) -> &'a [u8] {
match self {
AudioDataSource::Owned(data) => data.as_slice(),
AudioDataSource::MemoryMapped(mmap) => mmap.as_ref(),
AudioDataSource::Borrowed(slice) => slice,
}
}
#[inline]
pub fn len(&self) -> usize {
self.as_bytes().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_cow(&'a self) -> Cow<'a, [u8]> {
match self {
AudioDataSource::Borrowed(slice) => Cow::Borrowed(slice),
AudioDataSource::Owned(vec) => Cow::Borrowed(vec.as_slice()),
AudioDataSource::MemoryMapped(mmap) => Cow::Borrowed(mmap.as_ref()),
}
}
pub fn into_owned(self) -> Vec<u8> {
match self {
AudioDataSource::Owned(data) => data,
AudioDataSource::Borrowed(slice) => slice.to_vec(),
AudioDataSource::MemoryMapped(mmap) => mmap.as_ref().to_vec(),
}
}
pub fn from_file(file: &File) -> io::Result<Self> {
let mmap = unsafe { Mmap::map(file)? };
Ok(AudioDataSource::MemoryMapped(mmap))
}
}
impl<'a> Deref for AudioDataSource<'a> {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
self.as_bytes()
}
}
impl<'a> From<Vec<u8>> for AudioDataSource<'a> {
fn from(value: Vec<u8>) -> Self {
AudioDataSource::Owned(value)
}
}
impl<'a> From<&'a [u8]> for AudioDataSource<'a> {
fn from(value: &'a [u8]) -> Self {
AudioDataSource::Borrowed(value)
}
}
impl<'a> From<Mmap> for AudioDataSource<'a> {
fn from(value: Mmap) -> Self {
AudioDataSource::MemoryMapped(value)
}
}
impl<'a> From<AudioDataSource<'a>> for Vec<u8> {
fn from(value: AudioDataSource<'a>) -> Self {
value.into_owned()
}
}
impl<'a> AsRef<[u8]> for AudioDataSource<'a> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<'a> Borrow<[u8]> for AudioDataSource<'a> {
#[inline]
fn borrow(&self) -> &[u8] {
self.as_bytes()
}
}
impl<'a> IntoIterator for &'a AudioDataSource<'a> {
type Item = u8;
type IntoIter = std::iter::Copied<std::slice::Iter<'a, u8>>;
fn into_iter(self) -> Self::IntoIter {
self.as_bytes().iter().copied()
}
}
impl<'a> PartialEq<[u8]> for AudioDataSource<'a> {
fn eq(&self, other: &[u8]) -> bool {
self.as_bytes() == other
}
}
impl<'a> PartialEq<AudioDataSource<'a>> for [u8] {
fn eq(&self, other: &AudioDataSource<'a>) -> bool {
self == other.as_bytes()
}
}
impl<'a> From<Cow<'a, [u8]>> for AudioDataSource<'a> {
fn from(value: Cow<'a, [u8]>) -> Self {
match value {
Cow::Borrowed(slice) => AudioDataSource::Borrowed(slice),
Cow::Owned(vec) => AudioDataSource::Owned(vec),
}
}
}
impl<'a> Debug for AudioDataSource<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
AudioDataSource::Owned(data) => f
.debug_struct("AudioDataSource::Owned")
.field("len", &data.len())
.finish(),
AudioDataSource::MemoryMapped(mmap) => f
.debug_struct("AudioDataSource::MemoryMapped")
.field("len", &mmap.len())
.finish(),
AudioDataSource::Borrowed(slice) => f
.debug_struct("AudioDataSource::Borrowed")
.field("len", &slice.len())
.finish(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct OpenOptions {
pub use_memory_map: bool,
}
impl Default for OpenOptions {
fn default() -> Self {
OpenOptions {
use_memory_map: true,
}
}
}
#[allow(dead_code)]
const fn _assert_send_sync()
where
AudioDataSource<'static>: Send + Sync,
{
}