Skip to main content

lofty/musepack/sv7/
properties.rs

1use crate::error::Result;
2use crate::macros::decode_err;
3use crate::musepack::constants::{
4	FREQUENCY_TABLE, MPC_DECODER_SYNTH_DELAY, MPC_FRAME_LENGTH, MPC_OLD_GAIN_REF,
5};
6use crate::properties::FileProperties;
7
8use std::io::Read;
9use std::time::Duration;
10
11use byteorder::{LittleEndian, ReadBytesExt};
12
13/// Used profile
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum Profile {
16	/// No profile
17	#[default]
18	None,
19	/// Unstable/Experimental
20	Unstable,
21	/// Profiles 2-4
22	Unused,
23	/// Below Telephone (q= 0.0)
24	BelowTelephone0,
25	/// Below Telephone (q= 1.0)
26	BelowTelephone1,
27	/// Telephone (q= 2.0)
28	Telephone,
29	/// Thumb (q= 3.0)
30	Thumb,
31	/// Radio (q= 4.0)
32	Radio,
33	/// Standard (q= 5.0)
34	Standard,
35	/// Xtreme (q= 6.0)
36	Xtreme,
37	/// Insane (q= 7.0)
38	Insane,
39	/// BrainDead (q= 8.0)
40	BrainDead,
41	/// Above BrainDead (q= 9.0)
42	AboveBrainDead9,
43	/// Above BrainDead (q= 10.0)
44	AboveBrainDead10,
45}
46
47impl Profile {
48	/// Get a `Profile` from a u8
49	///
50	/// The mapping is available here: <http://trac.musepack.net/musepack/wiki/SV7Specification>
51	#[rustfmt::skip]
52	pub fn from_u8(value: u8) -> Option<Self> {
53		match value {
54			0         => Some(Self::None),
55			1         => Some(Self::Unstable),
56			2 | 3 | 4 => Some(Self::Unused),
57			5         => Some(Self::BelowTelephone0),
58			6         => Some(Self::BelowTelephone1),
59			7         => Some(Self::Telephone),
60			8         => Some(Self::Thumb),
61			9         => Some(Self::Radio),
62			10        => Some(Self::Standard),
63			11        => Some(Self::Xtreme),
64			12        => Some(Self::Insane),
65			13        => Some(Self::BrainDead),
66			14        => Some(Self::AboveBrainDead9),
67			15        => Some(Self::AboveBrainDead10),
68			_         => None,
69		}
70	}
71}
72
73/// Volume description for the start and end of the title
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
75pub enum Link {
76	/// Title starts or ends with a very low level (no live or classical genre titles)
77	#[default]
78	VeryLowStartOrEnd,
79	/// Title ends loudly
80	LoudEnd,
81	/// Title starts loudly
82	LoudStart,
83	/// Title starts loudly and ends loudly
84	LoudStartAndEnd,
85}
86
87impl Link {
88	/// Get a `Link` from a u8
89	///
90	/// The mapping is available here: <http://trac.musepack.net/musepack/wiki/SV7Specification>
91	pub fn from_u8(value: u8) -> Option<Self> {
92		match value {
93			0 => Some(Self::VeryLowStartOrEnd),
94			1 => Some(Self::LoudEnd),
95			2 => Some(Self::LoudStart),
96			3 => Some(Self::LoudStartAndEnd),
97			_ => None,
98		}
99	}
100}
101
102// http://trac.musepack.net/musepack/wiki/SV7Specification
103
104/// MPC stream version 7 audio properties
105#[derive(Debug, Clone, PartialEq, Default)]
106#[allow(clippy::struct_excessive_bools)]
107pub struct MpcSv7Properties {
108	pub(crate) duration: Duration,
109	pub(crate) average_bitrate: u32,
110	pub(crate) channels: u8, // NOTE: always 2
111	// -- Section 1 --
112	pub(crate) frame_count: u32,
113	// -- Section 2 --
114	pub(crate) intensity_stereo: bool,
115	pub(crate) mid_side_stereo: bool,
116	pub(crate) max_band: u8,
117	pub(crate) profile: Profile,
118	pub(crate) link: Link,
119	pub(crate) sample_freq: u32,
120	pub(crate) max_level: u16,
121	// -- Section 3 --
122	pub(crate) title_gain: i16,
123	pub(crate) title_peak: u16,
124	// -- Section 4 --
125	pub(crate) album_gain: i16,
126	pub(crate) album_peak: u16,
127	// -- Section 5 --
128	pub(crate) true_gapless: bool,
129	pub(crate) last_frame_length: u16,
130	pub(crate) fast_seeking_safe: bool,
131	// -- Section 6 --
132	pub(crate) encoder_version: u8,
133}
134
135impl From<MpcSv7Properties> for FileProperties {
136	fn from(input: MpcSv7Properties) -> Self {
137		Self {
138			duration: input.duration,
139			overall_bitrate: Some(input.average_bitrate),
140			audio_bitrate: Some(input.average_bitrate),
141			sample_rate: Some(input.sample_freq),
142			bit_depth: None,
143			channels: Some(input.channels),
144			channel_mask: None,
145		}
146	}
147}
148
149impl MpcSv7Properties {
150	/// Duration of the audio
151	pub fn duration(&self) -> Duration {
152		self.duration
153	}
154
155	/// Average bitrate (kbps)
156	pub fn average_bitrate(&self) -> u32 {
157		self.average_bitrate
158	}
159
160	/// Sample rate (Hz)
161	pub fn sample_rate(&self) -> u32 {
162		self.sample_freq
163	}
164
165	/// Channel count
166	pub fn channels(&self) -> u8 {
167		self.channels
168	}
169
170	/// Total number of audio frames
171	pub fn frame_count(&self) -> u32 {
172		self.frame_count
173	}
174
175	/// Whether intensity stereo coding (IS) is used
176	pub fn intensity_stereo(&self) -> bool {
177		self.intensity_stereo
178	}
179
180	/// Whether MidSideStereo is used
181	pub fn mid_side_stereo(&self) -> bool {
182		self.mid_side_stereo
183	}
184
185	/// Last subband used in the whole file
186	pub fn max_band(&self) -> u8 {
187		self.max_band
188	}
189
190	/// Profile used
191	pub fn profile(&self) -> Profile {
192		self.profile
193	}
194
195	/// Volume description of the start and end
196	pub fn link(&self) -> Link {
197		self.link
198	}
199
200	/// Maximum level of the coded PCM input signal
201	pub fn max_level(&self) -> u16 {
202		self.max_level
203	}
204
205	/// Change in the replay level
206	///
207	/// The value is a signed 16-bit integer, with the level being attenuated by that many mB
208	pub fn title_gain(&self) -> i16 {
209		self.title_gain
210	}
211
212	/// Maximum level of the decoded title
213	///
214	/// * 16422: -6 dB
215	/// * 32767:  0 dB
216	/// * 65379: +6 dB
217	pub fn title_peak(&self) -> u16 {
218		self.title_peak
219	}
220
221	/// Change in the replay level if the whole CD is supposed to be played with the same level change
222	///
223	/// The value is a signed 16-bit integer, with the level being attenuated by that many mB
224	pub fn album_gain(&self) -> i16 {
225		self.album_gain
226	}
227
228	/// Maximum level of the whole decoded CD
229	///
230	/// * 16422: -6 dB
231	/// * 32767:  0 dB
232	/// * 65379: +6 dB
233	pub fn album_peak(&self) -> u16 {
234		self.album_peak
235	}
236
237	/// Whether true gapless is used
238	pub fn true_gapless(&self) -> bool {
239		self.true_gapless
240	}
241
242	/// Used samples of the last frame
243	///
244	/// * TrueGapless = 0: always 0
245	/// * TrueGapless = 1: 1...1152
246	pub fn last_frame_length(&self) -> u16 {
247		self.last_frame_length
248	}
249
250	/// Whether fast seeking can be used safely
251	pub fn fast_seeking_safe(&self) -> bool {
252		self.fast_seeking_safe
253	}
254
255	/// Encoder version
256	///
257	/// * Encoder version * 100  (106 = 1.06)
258	/// * EncoderVersion % 10 == 0        Release (1.0)
259	/// * EncoderVersion %  2 == 0        Beta (1.06)
260	/// * EncoderVersion %  2 == 1        Alpha (1.05a...z)
261	pub fn encoder_version(&self) -> u8 {
262		self.encoder_version
263	}
264
265	pub(crate) fn read<R>(reader: &mut R, stream_length: u64) -> Result<Self>
266	where
267		R: Read,
268	{
269		let version = reader.read_u8()?;
270		if version & 0x0F != 7 {
271			decode_err!(@BAIL Mpc, "Expected stream version 7");
272		}
273
274		let mut properties = MpcSv7Properties {
275			channels: 2, // Always 2 channels
276			..Self::default()
277		};
278
279		// TODO: Make a Bitreader, would be nice crate-wide but especially here
280		// The SV7 header is split into 6 32-bit sections
281
282		// -- Section 1 --
283		properties.frame_count = reader.read_u32::<LittleEndian>()?;
284
285		// -- Section 2 --
286		let chunk = reader.read_u32::<LittleEndian>()?;
287
288		let byte1 = ((chunk & 0xFF00_0000) >> 24) as u8;
289
290		properties.intensity_stereo = ((byte1 & 0x80) >> 7) == 1;
291		properties.mid_side_stereo = ((byte1 & 0x40) >> 6) == 1;
292		properties.max_band = byte1 & 0x3F;
293
294		let byte2 = ((chunk & 0xFF_0000) >> 16) as u8;
295
296		properties.profile = Profile::from_u8((byte2 & 0xF0) >> 4).unwrap(); // Infallible
297		properties.link = Link::from_u8((byte2 & 0x0C) >> 2).unwrap(); // Infallible
298
299		let sample_freq_index = byte2 & 0x03;
300		properties.sample_freq = FREQUENCY_TABLE[sample_freq_index as usize];
301
302		let remaining_bytes = (chunk & 0xFFFF) as u16;
303		properties.max_level = remaining_bytes;
304
305		// -- Section 3 --
306		let title_peak = reader.read_u16::<LittleEndian>()?;
307		let title_gain = reader.read_u16::<LittleEndian>()?;
308
309		// -- Section 4 --
310		let album_peak = reader.read_u16::<LittleEndian>()?;
311		let album_gain = reader.read_u16::<LittleEndian>()?;
312
313		// -- Section 5 --
314		let chunk = reader.read_u32::<LittleEndian>()?;
315
316		properties.true_gapless = (chunk >> 31) == 1;
317
318		if properties.true_gapless {
319			properties.last_frame_length = ((chunk >> 20) & 0x7FF) as u16;
320		}
321
322		properties.fast_seeking_safe = (chunk >> 19) & 1 == 1;
323
324		// NOTE: Rest of the chunk is zeroed and unused
325
326		// -- Section 6 --
327		properties.encoder_version = reader.read_u8()?;
328
329		// -- End of parsing --
330
331		// Convert ReplayGain values
332		let set_replay_gain = |gain: u16| -> i16 {
333			if gain == 0 {
334				return 0;
335			}
336
337			let gain = ((MPC_OLD_GAIN_REF - f32::from(gain) / 100.0) * 256.0 + 0.5) as i16;
338			if !(0..i16::MAX).contains(&gain) {
339				return 0;
340			}
341			gain
342		};
343		let set_replay_peak = |peak: u16| -> u16 {
344			if peak == 0 {
345				return 0;
346			}
347
348			((f64::from(peak).log10() * 20.0 * 256.0) + 0.5) as u16
349		};
350
351		properties.title_gain = set_replay_gain(title_gain);
352		properties.title_peak = set_replay_peak(title_peak);
353		properties.album_gain = set_replay_gain(album_gain);
354		properties.album_peak = set_replay_peak(album_peak);
355
356		if properties.last_frame_length > MPC_FRAME_LENGTH as u16 {
357			decode_err!(@BAIL Mpc, "Invalid last frame length");
358		}
359
360		if properties.sample_freq == 0 {
361			log::warn!("Sample rate is 0, unable to calculate duration and bitrate");
362			return Ok(properties);
363		}
364
365		if properties.frame_count == 0 {
366			log::warn!("Frame count is 0, unable to calculate duration and bitrate");
367			return Ok(properties);
368		}
369
370		let time_per_frame = (MPC_FRAME_LENGTH as f64) / f64::from(properties.sample_freq);
371		let length = (f64::from(properties.frame_count) * time_per_frame) * 1000.0;
372		properties.duration = Duration::from_millis(length as u64);
373
374		let total_samples;
375		if properties.true_gapless {
376			total_samples = (u64::from(properties.frame_count) * MPC_FRAME_LENGTH)
377				- (MPC_FRAME_LENGTH - u64::from(properties.last_frame_length));
378		} else {
379			total_samples =
380				(u64::from(properties.frame_count) * MPC_FRAME_LENGTH) - MPC_DECODER_SYNTH_DELAY;
381		}
382
383		properties.average_bitrate = ((stream_length * 8 * u64::from(properties.sample_freq))
384			/ (total_samples * 1000)) as u32;
385
386		Ok(properties)
387	}
388}