mqa-identify 0.3.0

A minimal library, to check if a flac file has been encoded by/with MQA
Documentation
#![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;

/// An error that prevented a stream from being inspected.
///
/// The underlying [`claxon::Error`] is available through [`std::error::Error::source`].
#[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)
    }
}

/// Checks if the FLAC file at `path` is an MQA file by looking for the MQA magic word in the least
/// significant bits of the samples.
///
/// See [`identify_mqa_reader`] for details on how the check is performed.
///
/// # Errors
/// Returns an error if the file cannot be opened, is not a valid FLAC file, or cannot be decoded.
pub fn identify_mqa(path: impl AsRef<Path>) -> Result<bool, Error> {
    identify(FlacReader::open(path)?)
}

/// Checks if the FLAC stream read from `reader` is an MQA file by looking for the MQA magic word in
/// the least significant bits of the samples.
///
/// The magic word is searched for in the three least significant bit planes of the XOR of the two
/// channels, over the first three seconds of audio data, which is enough to find it if it is
/// present. Note that the magic word may also happen to appear in a non-MQA file, but this is very
/// unlikely.
///
/// Streams that cannot carry MQA at all — anything that is not stereo, or that has fewer than 16
/// bits per sample — return `Ok(false)` without being decoded.
///
/// # Errors
/// Returns an error if the stream is not a valid FLAC stream or cannot be decoded.
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
    }
}