#![forbid(
unsafe_code,
clippy::panic,
clippy::exit,
clippy::unwrap_used,
clippy::expect_used,
clippy::unimplemented,
clippy::todo,
clippy::unreachable,
)]
#![deny(
clippy::cast_ptr_alignment,
clippy::char_lit_as_u8,
clippy::unnecessary_cast,
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::checked_conversions,
)]
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;
use std::io::{Read, Write, Seek, SeekFrom};
mod aifcreader;
mod aifcwriter;
mod chunks;
mod aifcresult;
mod cast;
mod f80;
pub use aifcreader::{AifcReader, AifcReadInfo, Samples, Sample, Chunks};
pub use aifcwriter::{AifcWriter, AifcWriteInfo};
pub use chunks::{Markers, Marker, Comments, Comment, Instrument, Loop};
pub use aifcresult::{AifcResult, AifcError};
pub type ChunkId = [u8; 4];
pub type MarkerId = i16;
fn buffer_size_error() -> std::io::Error { std::io::Error::from(std::io::ErrorKind::InvalidInput) }
fn read_error() -> std::io::Error { std::io::Error::from(std::io::ErrorKind::Other) }
fn unexpectedeof() -> std::io::Error { std::io::Error::from(std::io::ErrorKind::UnexpectedEof) }
const UNIX_TIMESTAMP_OFFSET: i64 = 2082844800;
const CHUNKID_FORM: [u8; 4] = *b"FORM";
const CHUNKID_AIFF: [u8; 4] = *b"AIFF";
const CHUNKID_AIFC: [u8; 4] = *b"AIFC";
pub const CHUNKID_COMM: [u8; 4] = *b"COMM";
pub const CHUNKID_FVER: [u8; 4] = *b"FVER";
pub const CHUNKID_SSND: [u8; 4] = *b"SSND";
pub const CHUNKID_MARK: [u8; 4] = *b"MARK";
pub const CHUNKID_COMT: [u8; 4] = *b"COMT";
pub const CHUNKID_INST: [u8; 4] = *b"INST";
pub const CHUNKID_MIDI: [u8; 4] = *b"MIDI";
pub const CHUNKID_AESD: [u8; 4] = *b"AESD";
pub const CHUNKID_APPL: [u8; 4] = *b"APPL";
pub const CHUNKID_NAME: [u8; 4] = *b"NAME";
pub const CHUNKID_AUTH: [u8; 4] = *b"AUTH";
pub const CHUNKID_COPY: [u8; 4] = *b"(c) ";
pub const CHUNKID_ANNO: [u8; 4] = *b"ANNO";
pub const CHUNKID_ID3: [u8; 4] = *b"ID3 ";
const COMPRESSIONTYPE_NONE: [u8; 4] = *b"NONE";
const COMPRESSIONTYPE_TWOS: [u8; 4] = *b"twos";
const COMPRESSIONTYPE_IN24: [u8; 4] = *b"in24";
const COMPRESSIONTYPE_IN32: [u8; 4] = *b"in32";
const COMPRESSIONTYPE_RAW: [u8; 4] = *b"raw ";
const COMPRESSIONTYPE_SOWT: [u8; 4] = *b"sowt";
const COMPRESSIONTYPE_23NI: [u8; 4] = *b"23ni";
const COMPRESSIONTYPE_FL32_UPPER: [u8; 4] = *b"FL32";
const COMPRESSIONTYPE_FL32: [u8; 4] = *b"fl32";
const COMPRESSIONTYPE_FL64_UPPER: [u8; 4] = *b"FL64";
const COMPRESSIONTYPE_FL64: [u8; 4] = *b"fl64";
const COMPRESSIONTYPE_ULAW: [u8; 4] = *b"ulaw";
const COMPRESSIONTYPE_ULAW_UPPER: [u8; 4] = *b"ULAW";
const COMPRESSIONTYPE_ALAW: [u8; 4] = *b"alaw";
const COMPRESSIONTYPE_ALAW_UPPER: [u8; 4] = *b"ALAW";
const COMPRESSIONTYPE_IMA4: [u8; 4] = *b"ima4";
#[derive(Debug, Clone, PartialEq)]
pub struct ChunkRef {
pub id: ChunkId,
pub pos: u64,
pub size: u32
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FileFormat {
Aiff,
Aifc
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SampleFormat {
U8,
I8,
I16,
I16LE,
I24,
I32,
I32LE,
F32,
F64,
CompressedUlaw,
CompressedAlaw,
CompressedIma4,
Custom([u8; 4])
}
impl SampleFormat {
pub fn decoded_size(&self) -> usize {
match &self {
SampleFormat::U8 => 1,
SampleFormat::I8 => 1,
SampleFormat::I16 | SampleFormat::I16LE => 2,
SampleFormat::I24 => 3,
SampleFormat::I32 | SampleFormat::I32LE => 4,
SampleFormat::F32 => 4,
SampleFormat::F64 => 8,
SampleFormat::CompressedUlaw => 2,
SampleFormat::CompressedAlaw => 2,
SampleFormat::CompressedIma4 => 2,
SampleFormat::Custom(_) => 0,
}
}
#[inline(always)]
fn encoded_size(&self) -> u64 {
match &self {
SampleFormat::U8 => 1,
SampleFormat::I8 => 1,
SampleFormat::I16 | SampleFormat::I16LE => 2,
SampleFormat::I24 => 3,
SampleFormat::I32 | SampleFormat::I32LE => 4,
SampleFormat::F32 => 4,
SampleFormat::F64 => 8,
SampleFormat::CompressedUlaw => 1,
SampleFormat::CompressedAlaw => 1,
SampleFormat::CompressedIma4 => 0,
SampleFormat::Custom(_) => 1,
}
}
fn calculate_sample_len(&self, sample_byte_len: u32) -> Option<u64> {
match self {
SampleFormat::CompressedIma4 => {
Some(u64::from(sample_byte_len / 34) * 64)
},
SampleFormat::Custom(_) => None,
_ => {
Some(u64::from(sample_byte_len) / self.encoded_size())
}
}
}
fn maximum_channel_count(&self) -> i16 {
match self {
SampleFormat::CompressedIma4 => 2,
_ => i16::MAX
}
}
fn bits_per_sample(&self) -> u8 {
match &self {
SampleFormat::U8 => 8,
SampleFormat::I8 => 8,
SampleFormat::I16 => 16,
SampleFormat::I16LE => 16,
SampleFormat::I24 => 24,
SampleFormat::I32 => 32,
SampleFormat::I32LE => 32,
SampleFormat::F32 => 32,
SampleFormat::F64 => 64,
SampleFormat::CompressedUlaw => 0,
SampleFormat::CompressedAlaw => 0,
SampleFormat::CompressedIma4 => 0,
SampleFormat::Custom(_) => 0,
}
}
}
pub fn recognize(data: &[u8]) -> Option<FileFormat> {
if data.len() < 12 ||
data[0] != b'F' || data[1] != b'O' || data[2] != b'R' || data[3] != b'M' ||
data[8] != b'A' || data[9] != b'I' || data[10] != b'F' {
return None;
}
match data[11] {
b'F' => Some(FileFormat::Aiff),
b'C' => Some(FileFormat::Aifc),
_ => None
}
}
struct CountingWrite<W> where W: Write {
pub handle: W,
pub bytes_written: u64
}
impl<W: Write> CountingWrite<W> {
pub fn new(handle: W) -> CountingWrite<W> {
CountingWrite {
handle,
bytes_written: 0
}
}
fn write_not_counted(&mut self, buf: &[u8]) -> Result<usize, crate::aifcresult::AifcError> {
self.handle.write_all(buf)?;
Ok(buf.len())
}
}
impl<W: Write> Write for CountingWrite<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.handle.write_all(buf)?;
self.bytes_written += u64::try_from(buf.len()).map_err(|_| crate::buffer_size_error())?;
Ok(buf.len())
}
fn flush(&mut self) -> Result<(), std::io::Error> {
self.handle.flush()
}
}
fn is_even_u32(value: u32) -> bool {
value & 1 == 0
}
fn is_even_u64(value: u64) -> bool {
value & 1 == 0
}
fn is_even_usize(value: usize) -> bool {
value & 1 == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recognize() {
assert_eq!(recognize(&[]), None);
assert_eq!(recognize(b"FORM"), None);
assert_eq!(recognize(b"FORM....AIFX"), None);
assert_eq!(recognize(b"form....AIFF"), None);
assert_eq!(recognize(b"FORM....aiff"), None);
assert_eq!(recognize(b"FORM....AIFF"), Some(FileFormat::Aiff));
assert_eq!(recognize(b"FORM....AIFC"), Some(FileFormat::Aifc));
assert_eq!(recognize(b"FORM....AIFFCOMM....blahblah.."), Some(FileFormat::Aiff));
}
}