#![deny(clippy::pedantic)]
use std::fmt;
use std::io::Read;
use std::path::Path;
use claxon::FlacReader;
const MQA_MAGIC_WORD: u64 = 0xb_e049_8c88;
const MQA_MAGIC_MASK: u64 = 0xF_FFFF_FFFF;
const MQA_CHANNELS: u32 = 2;
const MQA_MIN_BITS_PER_SAMPLE: u32 = 16;
const SECONDS_TO_CHECK: u32 = 3;
const BIT_PLANES_TO_CHECK: usize = 3;
#[derive(Debug)]
pub struct Error(claxon::Error);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
impl From<claxon::Error> for Error {
fn from(error: claxon::Error) -> Self {
Self(error)
}
}
pub fn identify_mqa(path: impl AsRef<Path>) -> Result<bool, Error> {
identify(FlacReader::open(path)?)
}
pub fn identify_mqa_reader(reader: impl Read) -> Result<bool, Error> {
identify(FlacReader::new(reader)?)
}
fn identify<R: Read>(mut reader: FlacReader<R>) -> Result<bool, Error> {
let streaminfo = reader.streaminfo();
if streaminfo.channels != MQA_CHANNELS || streaminfo.bits_per_sample < MQA_MIN_BITS_PER_SAMPLE {
return Ok(false);
}
let mut detector = MagicWordDetector::new(streaminfo.bits_per_sample);
let samples_to_check = streaminfo.sample_rate * SECONDS_TO_CHECK;
let mut checked_samples = 0;
let mut block_reader = reader.blocks();
let mut buffer = Vec::new();
while checked_samples < samples_to_check {
let Some(block) = block_reader.read_next_or_eof(buffer)? else {
break;
};
for (left, right) in block.stereo_samples() {
if detector.push(left ^ right) {
return Ok(true);
}
}
checked_samples += block.duration();
buffer = block.into_buffer();
}
Ok(false)
}
struct MagicWordDetector {
buffers: [u64; BIT_PLANES_TO_CHECK],
position: u32,
}
impl MagicWordDetector {
fn new(bits_per_sample: u32) -> Self {
Self {
buffers: [0; BIT_PLANES_TO_CHECK],
position: bits_per_sample - MQA_MIN_BITS_PER_SAMPLE,
}
}
fn push(&mut self, sample: i32) -> bool {
#[allow(clippy::cast_sign_loss)]
let bits = u64::from(sample as u32 >> self.position);
let mut found = false;
for (plane, buffer) in self.buffers.iter_mut().enumerate() {
*buffer |= (bits >> plane) & 1;
found |= *buffer == MQA_MAGIC_WORD;
*buffer = (*buffer << 1) & MQA_MAGIC_MASK;
}
found
}
}