moosicbox_lofty/iff/aiff/
properties.rs

1use super::read::CompressionPresent;
2use crate::error::Result;
3use crate::macros::{decode_err, try_vec};
4use crate::properties::FileProperties;
5use crate::util::text::utf8_decode;
6
7use std::borrow::Cow;
8use std::io::Read;
9use std::time::Duration;
10
11use byteorder::{BigEndian, ReadBytesExt};
12
13/// The AIFC compression type
14///
15/// This contains a non-exhaustive list of compression types
16#[allow(non_camel_case_types)]
17#[derive(Clone, Eq, PartialEq, Default, Debug)]
18pub enum AiffCompressionType {
19	#[default]
20	/// PCM
21	None,
22	/// 2-to-1 IIGS ACE (Audio Compression / Expansion)
23	ACE2,
24	/// 8-to-3 IIGS ACE (Audio Compression / Expansion)
25	ACE8,
26	/// 3-to-1 Macintosh Audio Compression / Expansion
27	MAC3,
28	/// 6-to-1 Macintosh Audio Compression / Expansion
29	MAC6,
30	/// PCM (byte swapped)
31	sowt,
32	/// IEEE 32-bit float
33	fl32,
34	/// IEEE 64-bit float
35	fl64,
36	/// 8-bit ITU-T G.711 A-law
37	alaw,
38	/// 8-bit ITU-T G.711 µ-law
39	ulaw,
40	/// 8-bit ITU-T G.711 µ-law (64 kb/s)
41	ULAW,
42	/// 8-bit ITU-T G.711 A-law (64 kb/s)
43	ALAW,
44	/// IEEE 32-bit float (From SoundHack & Csound)
45	FL32,
46	/// Catch-all for unknown compression algorithms
47	Other {
48		/// Identifier from the compression algorithm
49		compression_type: [u8; 4],
50		/// Human-readable description of the compression algorithm
51		compression_name: String,
52	},
53}
54
55impl AiffCompressionType {
56	/// Get the compression name for a compression type
57	///
58	/// For variants other than [`AiffCompressionType::Other`], this will use statically known names.
59	///
60	/// # Examples
61	///
62	/// ```rust
63	/// use moosicbox_lofty::iff::aiff::AiffCompressionType;
64	///
65	/// let compression_type = AiffCompressionType::alaw;
66	/// assert_eq!(compression_type.compression_name(), "ALaw 2:1");
67	/// ```
68	pub fn compression_name(&self) -> Cow<'_, str> {
69		match self {
70			AiffCompressionType::None => Cow::Borrowed("not compressed"),
71			AiffCompressionType::ACE2 => Cow::Borrowed("ACE 2-to-1"),
72			AiffCompressionType::ACE8 => Cow::Borrowed("ACE 8-to-3"),
73			AiffCompressionType::MAC3 => Cow::Borrowed("MACE 3-to-1"),
74			AiffCompressionType::MAC6 => Cow::Borrowed("MACE 6-to-1"),
75			AiffCompressionType::sowt => Cow::Borrowed(""), // Has no compression name
76			AiffCompressionType::fl32 => Cow::Borrowed("32-bit floating point"),
77			AiffCompressionType::fl64 => Cow::Borrowed("64-bit floating point"),
78			AiffCompressionType::alaw => Cow::Borrowed("ALaw 2:1"),
79			AiffCompressionType::ulaw => Cow::Borrowed("µLaw 2:1"),
80			AiffCompressionType::ULAW => Cow::Borrowed("CCITT G.711 u-law"),
81			AiffCompressionType::ALAW => Cow::Borrowed("CCITT G.711 A-law"),
82			AiffCompressionType::FL32 => Cow::Borrowed("Float 32"),
83			AiffCompressionType::Other {
84				compression_name, ..
85			} => Cow::from(compression_name),
86		}
87	}
88}
89
90/// A AIFF file's audio properties
91#[derive(Debug, PartialEq, Eq, Clone, Default)]
92#[non_exhaustive]
93pub struct AiffProperties {
94	pub(crate) duration: Duration,
95	pub(crate) overall_bitrate: u32,
96	pub(crate) audio_bitrate: u32,
97	pub(crate) sample_rate: u32,
98	pub(crate) sample_size: u16,
99	pub(crate) channels: u16,
100	pub(crate) compression_type: Option<AiffCompressionType>,
101}
102
103impl From<AiffProperties> for FileProperties {
104	fn from(value: AiffProperties) -> Self {
105		Self {
106			duration: value.duration,
107			overall_bitrate: Some(value.overall_bitrate),
108			audio_bitrate: Some(value.audio_bitrate),
109			sample_rate: Some(value.sample_rate),
110			bit_depth: Some(value.sample_size as u8),
111			channels: Some(value.channels as u8),
112			channel_mask: None,
113		}
114	}
115}
116
117impl AiffProperties {
118	/// Duration of the audio
119	pub fn duration(&self) -> Duration {
120		self.duration
121	}
122
123	/// Overall bitrate (kbps)
124	pub fn overall_bitrate(&self) -> u32 {
125		self.overall_bitrate
126	}
127
128	/// Audio bitrate (kbps)
129	pub fn audio_bitrate(&self) -> u32 {
130		self.audio_bitrate
131	}
132
133	/// Sample rate (Hz)
134	pub fn sample_rate(&self) -> u32 {
135		self.sample_rate
136	}
137
138	/// Bits per sample
139	pub fn sample_size(&self) -> u16 {
140		self.sample_size
141	}
142
143	/// Channel count
144	pub fn channels(&self) -> u16 {
145		self.channels
146	}
147
148	/// AIFC compression type, if an AIFC file was read
149	pub fn compression_type(&self) -> Option<&AiffCompressionType> {
150		self.compression_type.as_ref()
151	}
152}
153
154pub(super) fn read_properties(
155	comm: &mut &[u8],
156	compression_present: CompressionPresent,
157	stream_len: u32,
158	file_length: u64,
159) -> Result<AiffProperties> {
160	let channels = comm.read_u16::<BigEndian>()?;
161
162	if channels == 0 {
163		decode_err!(@BAIL Aiff, "File contains 0 channels");
164	}
165
166	let sample_frames = comm.read_u32::<BigEndian>()?;
167	let sample_size = comm.read_u16::<BigEndian>()?;
168
169	let mut sample_rate_bytes = [0; 10];
170	comm.read_exact(&mut sample_rate_bytes)?;
171
172	let sign = u64::from(sample_rate_bytes[0] & 0x80);
173
174	sample_rate_bytes[0] &= 0x7F;
175
176	let mut exponent = u16::from(sample_rate_bytes[0]) << 8 | u16::from(sample_rate_bytes[1]);
177	exponent = exponent - 16383 + 1023;
178
179	let fraction = &mut sample_rate_bytes[2..];
180	fraction[0] &= 0x7F;
181
182	let fraction: Vec<u64> = fraction.iter_mut().map(|v| u64::from(*v)).collect();
183
184	let fraction = fraction[0] << 56
185		| fraction[1] << 48
186		| fraction[2] << 40
187		| fraction[3] << 32
188		| fraction[4] << 24
189		| fraction[5] << 16
190		| fraction[6] << 8
191		| fraction[7];
192
193	let f64_bytes = sign << 56 | u64::from(exponent) << 52 | fraction >> 11;
194	let float = f64::from_be_bytes(f64_bytes.to_be_bytes());
195
196	let sample_rate = float.round() as u32;
197
198	let (duration, overall_bitrate, audio_bitrate) = if sample_rate > 0 && sample_frames > 0 {
199		let length = (f64::from(sample_frames) * 1000.0) / f64::from(sample_rate);
200
201		(
202			Duration::from_millis(length as u64),
203			((file_length as f64) * 8.0 / length + 0.5) as u32,
204			(f64::from(stream_len) * 8.0 / length + 0.5) as u32,
205		)
206	} else {
207		(Duration::ZERO, 0, 0)
208	};
209
210	let mut compression = None;
211	if comm.len() >= 5 && compression_present == CompressionPresent::Yes {
212		let mut compression_type = [0u8; 4];
213		comm.read_exact(&mut compression_type)?;
214
215		compression = Some(match &compression_type {
216			b"NONE" => AiffCompressionType::None,
217			b"ACE2" => AiffCompressionType::ACE2,
218			b"ACE8" => AiffCompressionType::ACE8,
219			b"MAC3" => AiffCompressionType::MAC3,
220			b"MAC6" => AiffCompressionType::MAC6,
221			b"sowt" => AiffCompressionType::sowt,
222			b"fl32" => AiffCompressionType::fl32,
223			b"fl64" => AiffCompressionType::fl64,
224			b"alaw" => AiffCompressionType::alaw,
225			b"ulaw" => AiffCompressionType::ulaw,
226			b"ULAW" => AiffCompressionType::ULAW,
227			b"ALAW" => AiffCompressionType::ALAW,
228			b"FL32" => AiffCompressionType::FL32,
229			_ => {
230				// We have to read the compression name string
231				let mut compression_name = String::new();
232
233				let compression_name_size = comm.read_u8()?;
234				if compression_name_size > 0 {
235					let mut compression_name_bytes = try_vec![0u8; compression_name_size as usize];
236					comm.read_exact(&mut compression_name_bytes)?;
237
238					compression_name = utf8_decode(compression_name_bytes)?;
239				}
240
241				AiffCompressionType::Other {
242					compression_type,
243					compression_name,
244				}
245			},
246		});
247	}
248
249	Ok(AiffProperties {
250		duration,
251		overall_bitrate,
252		audio_bitrate,
253		sample_rate,
254		sample_size,
255		channels,
256		compression_type: compression,
257	})
258}