use crate::{
header::StreamInfo,
read::{ReadError, Reader},
};
use std::{
error::Error,
fmt::{Display, Formatter, Result as FmtResult},
io::{copy, Error as IoError, Read, Write},
};
pub(super) fn encode<R: Read, W: Write, const BYTE_DEPTH: usize>(
format: Format,
order: Endianness,
info: &StreamInfo,
source: &mut Reader<R>,
mut sink: W,
) -> Result<W, PcmError> {
write_header(
info.size.into(),
u16::from(u8::from(info.channels)),
info.sample_rate.into(),
format,
BYTE_DEPTH.try_into().expect("byte depth is less than u16::MAX"),
&mut sink,
)
.map_err(PcmError::from_io(PcmErrorKind::CreateHeader))?;
let start_pos = source.position();
let stream_size = u32::from(info.size) as usize;
if format == Format::Float || order == Endianness::Little {
return copy(&mut source.limit(stream_size), &mut sink)
.map(|_| sink)
.map_err(PcmError::from_io(PcmErrorKind::EncodeStream));
}
while source.position() - start_pos < stream_size {
let mut sample = source
.take_const::<BYTE_DEPTH>()
.map_err(PcmError::from_read(PcmErrorKind::DecodeSample))?;
sample.reverse();
sink.write_all(sample.as_slice())
.map_err(PcmError::from_io(PcmErrorKind::EncodeSample))?;
}
sink.flush()
.map(|_| sink)
.map_err(PcmError::from_io(PcmErrorKind::FinishStream))
}
fn write_header<W: Write>(
file_size: u32,
channels: u16,
sample_rate: u32,
format: Format,
byte_depth: u16,
sink: &mut W,
) -> Result<(), IoError> {
let format_id = match format {
Format::Integer => 1u16,
Format::Float => 3u16,
};
let bytes_per_second = sample_rate * u32::from(channels) * u32::from(byte_depth);
sink.write_all(b"RIFF")?;
sink.write_all((file_size - 8).to_le_bytes().as_slice())?;
sink.write_all(b"WAVE")?;
sink.write_all(b"fmt ")?;
sink.write_all(16u32.to_le_bytes().as_slice())?;
sink.write_all(format_id.to_le_bytes().as_slice())?;
sink.write_all(channels.to_le_bytes().as_slice())?;
sink.write_all(sample_rate.to_le_bytes().as_slice())?;
sink.write_all(bytes_per_second.to_le_bytes().as_slice())?;
sink.write_all((channels * byte_depth).to_le_bytes().as_slice())?;
sink.write_all((byte_depth * 8).to_le_bytes().as_slice())?;
sink.write_all(b"data")?;
sink.write_all((file_size - 40).to_le_bytes().as_slice())?;
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum Format {
Integer,
Float,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum Endianness {
Little,
Big,
}
#[derive(Debug)]
pub struct PcmError {
kind: PcmErrorKind,
source: PcmErrorSource,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PcmErrorKind {
CreateHeader,
EncodeStream,
DecodeSample,
EncodeSample,
FinishStream,
}
#[derive(Debug)]
enum PcmErrorSource {
Io(IoError),
Read(ReadError),
}
impl PcmError {
fn from_io(kind: PcmErrorKind) -> impl FnOnce(IoError) -> Self {
move |source| Self {
kind,
source: PcmErrorSource::Io(source),
}
}
fn from_read(kind: PcmErrorKind) -> impl FnOnce(ReadError) -> Self {
move |source| Self {
kind,
source: PcmErrorSource::Read(source),
}
}
#[must_use]
pub fn kind(&self) -> PcmErrorKind {
self.kind
}
}
impl Display for PcmError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.kind.fmt(f)
}
}
impl Error for PcmError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.source {
PcmErrorSource::Io(e) => Some(e),
PcmErrorSource::Read(e) => Some(e),
}
}
}
impl Display for PcmErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(match self {
Self::CreateHeader => "failed to encode file header",
Self::EncodeStream => "failed to encode full PCM stream",
Self::DecodeSample => "failed to decode sample from PCM stream",
Self::EncodeSample => "failed to encode sample",
Self::FinishStream => "failed to finalize writing PCM stream data",
})
}
}