1#![forbid(
53 unsafe_code,
54 clippy::panic,
55 clippy::exit,
56 clippy::unwrap_used,
57 clippy::expect_used,
58 clippy::unimplemented,
59 clippy::todo,
60 clippy::unreachable,
61)]
62#![deny(
63 clippy::cast_ptr_alignment,
64 clippy::char_lit_as_u8,
65 clippy::unnecessary_cast,
66 clippy::cast_lossless,
67 clippy::cast_possible_truncation,
68 clippy::cast_possible_wrap,
69 clippy::cast_sign_loss,
70 clippy::checked_conversions,
71)]
72
73#[doc = include_str!("../README.md")]
76#[cfg(doctest)]
77pub struct ReadmeDoctests;
78
79use std::io::{Read, Write, Seek, SeekFrom};
80
81mod aifcreader;
82mod aifcwriter;
83mod chunks;
84mod aifcresult;
85mod cast;
86mod f80;
87
88pub use aifcreader::{AifcReader, AifcReadInfo, Samples, Sample, Chunks};
89pub use aifcwriter::{AifcWriter, AifcWriteInfo};
90pub use chunks::{Markers, Marker, Comments, Comment, Instrument, Loop};
91pub use aifcresult::{AifcResult, AifcError};
92
93pub type ChunkId = [u8; 4];
99
100pub type MarkerId = i16;
102
103fn buffer_size_error() -> std::io::Error { std::io::Error::from(std::io::ErrorKind::InvalidInput) }
104fn read_error() -> std::io::Error { std::io::Error::from(std::io::ErrorKind::Other) }
105fn unexpectedeof() -> std::io::Error { std::io::Error::from(std::io::ErrorKind::UnexpectedEof) }
106
107const UNIX_TIMESTAMP_OFFSET: i64 = 2082844800;
109
110const CHUNKID_FORM: [u8; 4] = *b"FORM";
111const CHUNKID_AIFF: [u8; 4] = *b"AIFF";
112const CHUNKID_AIFC: [u8; 4] = *b"AIFC";
113
114pub const CHUNKID_COMM: [u8; 4] = *b"COMM";
116pub const CHUNKID_FVER: [u8; 4] = *b"FVER";
118pub const CHUNKID_SSND: [u8; 4] = *b"SSND";
120pub const CHUNKID_MARK: [u8; 4] = *b"MARK";
122pub const CHUNKID_COMT: [u8; 4] = *b"COMT";
124pub const CHUNKID_INST: [u8; 4] = *b"INST";
126pub const CHUNKID_MIDI: [u8; 4] = *b"MIDI";
128pub const CHUNKID_AESD: [u8; 4] = *b"AESD";
130pub const CHUNKID_APPL: [u8; 4] = *b"APPL";
132pub const CHUNKID_NAME: [u8; 4] = *b"NAME";
134pub const CHUNKID_AUTH: [u8; 4] = *b"AUTH";
136pub const CHUNKID_COPY: [u8; 4] = *b"(c) ";
138pub const CHUNKID_ANNO: [u8; 4] = *b"ANNO";
142pub const CHUNKID_ID3: [u8; 4] = *b"ID3 ";
144
145const COMPRESSIONTYPE_NONE: [u8; 4] = *b"NONE";
146const COMPRESSIONTYPE_TWOS: [u8; 4] = *b"twos";
147const COMPRESSIONTYPE_IN24: [u8; 4] = *b"in24";
148const COMPRESSIONTYPE_IN32: [u8; 4] = *b"in32";
149const COMPRESSIONTYPE_RAW: [u8; 4] = *b"raw ";
150const COMPRESSIONTYPE_SOWT: [u8; 4] = *b"sowt";
151const COMPRESSIONTYPE_23NI: [u8; 4] = *b"23ni";
152const COMPRESSIONTYPE_FL32_UPPER: [u8; 4] = *b"FL32";
153const COMPRESSIONTYPE_FL32: [u8; 4] = *b"fl32";
154const COMPRESSIONTYPE_FL64_UPPER: [u8; 4] = *b"FL64";
155const COMPRESSIONTYPE_FL64: [u8; 4] = *b"fl64";
156const COMPRESSIONTYPE_ULAW: [u8; 4] = *b"ulaw";
157const COMPRESSIONTYPE_ULAW_UPPER: [u8; 4] = *b"ULAW";
158const COMPRESSIONTYPE_ALAW: [u8; 4] = *b"alaw";
159const COMPRESSIONTYPE_ALAW_UPPER: [u8; 4] = *b"ALAW";
160const COMPRESSIONTYPE_IMA4: [u8; 4] = *b"ima4";
161
162#[derive(Debug, Clone, PartialEq)]
164pub struct ChunkRef {
165 pub id: ChunkId,
167 pub pos: u64,
170 pub size: u32
172}
173
174#[derive(Debug, Clone, Copy, PartialEq)]
176pub enum FileFormat {
177 Aiff,
179 Aifc
181}
182
183#[derive(Debug, Clone, Copy, PartialEq)]
187pub enum SampleFormat {
188 U8,
190 I8,
192 I16,
194 I16LE,
196 I24,
198 I32,
200 I32LE,
202 F32,
204 F64,
206 CompressedUlaw,
209 CompressedAlaw,
212 CompressedIma4,
215 Custom([u8; 4])
218}
219
220impl SampleFormat {
221 pub fn decoded_size(&self) -> usize {
224 match &self {
225 SampleFormat::U8 => 1,
226 SampleFormat::I8 => 1,
227 SampleFormat::I16 | SampleFormat::I16LE => 2,
228 SampleFormat::I24 => 3,
229 SampleFormat::I32 | SampleFormat::I32LE => 4,
230 SampleFormat::F32 => 4,
231 SampleFormat::F64 => 8,
232 SampleFormat::CompressedUlaw => 2,
233 SampleFormat::CompressedAlaw => 2,
234 SampleFormat::CompressedIma4 => 2,
235 SampleFormat::Custom(_) => 0,
236 }
237 }
238
239 #[inline(always)]
242 fn encoded_size(&self) -> u64 {
243 match &self {
244 SampleFormat::U8 => 1,
245 SampleFormat::I8 => 1,
246 SampleFormat::I16 | SampleFormat::I16LE => 2,
247 SampleFormat::I24 => 3,
248 SampleFormat::I32 | SampleFormat::I32LE => 4,
249 SampleFormat::F32 => 4,
250 SampleFormat::F64 => 8,
251 SampleFormat::CompressedUlaw => 1,
252 SampleFormat::CompressedAlaw => 1,
253 SampleFormat::CompressedIma4 => 0,
254 SampleFormat::Custom(_) => 1,
255 }
256 }
257
258 fn calculate_sample_len(&self, sample_byte_len: u32) -> Option<u64> {
260 match self {
261 SampleFormat::CompressedIma4 => {
262 Some(u64::from(sample_byte_len / 34) * 64)
264 },
265 SampleFormat::Custom(_) => None,
266 _ => {
267 Some(u64::from(sample_byte_len) / self.encoded_size())
269 }
270 }
271 }
272
273 fn maximum_channel_count(&self) -> i16 {
275 match self {
276 SampleFormat::CompressedIma4 => 2,
277 _ => i16::MAX
278 }
279 }
280
281 fn bits_per_sample(&self) -> u8 {
284 match &self {
285 SampleFormat::U8 => 8,
286 SampleFormat::I8 => 8,
287 SampleFormat::I16 => 16,
288 SampleFormat::I16LE => 16,
289 SampleFormat::I24 => 24,
290 SampleFormat::I32 => 32,
291 SampleFormat::I32LE => 32,
292 SampleFormat::F32 => 32,
293 SampleFormat::F64 => 64,
294 SampleFormat::CompressedUlaw => 0,
295 SampleFormat::CompressedAlaw => 0,
296 SampleFormat::CompressedIma4 => 0,
297 SampleFormat::Custom(_) => 0,
298 }
299 }
300}
301
302pub fn recognize(data: &[u8]) -> Option<FileFormat> {
317 if data.len() < 12 ||
318 data[0] != b'F' || data[1] != b'O' || data[2] != b'R' || data[3] != b'M' ||
319 data[8] != b'A' || data[9] != b'I' || data[10] != b'F' {
320 return None;
321 }
322 match data[11] {
323 b'F' => Some(FileFormat::Aiff),
324 b'C' => Some(FileFormat::Aifc),
325 _ => None
326 }
327}
328
329struct CountingWrite<W> where W: Write {
331 pub handle: W,
332 pub bytes_written: u64
333}
334
335impl<W: Write> CountingWrite<W> {
336 pub fn new(handle: W) -> CountingWrite<W> {
337 CountingWrite {
338 handle,
339 bytes_written: 0
340 }
341 }
342
343 fn write_not_counted(&mut self, buf: &[u8]) -> Result<usize, crate::aifcresult::AifcError> {
345 self.handle.write_all(buf)?;
346 Ok(buf.len())
347 }
348}
349
350impl<W: Write> Write for CountingWrite<W> {
351 fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
352 self.handle.write_all(buf)?;
353 self.bytes_written += u64::try_from(buf.len()).map_err(|_| crate::buffer_size_error())?;
354 Ok(buf.len())
355 }
356 fn flush(&mut self) -> Result<(), std::io::Error> {
357 self.handle.flush()
358 }
359}
360
361fn is_even_u32(value: u32) -> bool {
362 value & 1 == 0
363}
364
365fn is_even_u64(value: u64) -> bool {
366 value & 1 == 0
367}
368
369fn is_even_usize(value: usize) -> bool {
370 value & 1 == 0
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_recognize() {
379 assert_eq!(recognize(&[]), None);
380 assert_eq!(recognize(b"FORM"), None);
381 assert_eq!(recognize(b"FORM....AIFX"), None);
382 assert_eq!(recognize(b"form....AIFF"), None);
383 assert_eq!(recognize(b"FORM....aiff"), None);
384 assert_eq!(recognize(b"FORM....AIFF"), Some(FileFormat::Aiff));
385 assert_eq!(recognize(b"FORM....AIFC"), Some(FileFormat::Aifc));
386 assert_eq!(recognize(b"FORM....AIFFCOMM....blahblah.."), Some(FileFormat::Aiff));
387 }
388}