perf(hires): analyze every channel in one audio decode pass

This commit is contained in:
zarzet committed 2026-10-04 01:41:00 +07:00
1 parent 3b553b38a3
commit 5019851903
4 files changed
+672 -17

No files matched your search

@@ -14,6 +14,9 @@
use super::fft::Radix2Fft;
mod streaming;
pub(super) use streaming::{IntegerEvidence, StftAccumulator, StftPlan};
pub const CONFIDENCE_CERTAIN: &str = "certain";
pub const CONFIDENCE_LIKELY: &str = "likely";
@@ -83,6 +86,7 @@ pub struct StftStats {
/// Averaged channel spectrum with a periodic Hann window and hop n_fft/4.
/// Only complete frames are used: padding a cropped segment with zeros
/// introduces an artificial discontinuity and broadband spectral leakage.
#[cfg(test)]
pub fn analyze_stft(
y: &[f32],
n_fft: usize,
@@ -337,6 +341,7 @@ pub fn detect_imaging(
/// every sample repeated (sample-and-hold) or the in-between samples on a
/// straight line (linear interpolation). `samples` is one channel,
/// right-justified; `or_bits` is the OR over the whole window.
#[cfg(test)]
pub fn detect_integer_upsampling(samples: &[i32], or_bits: u32, sample_rate: u32) -> &'static str {
if or_bits == 0 || samples.len() < 4 {
return "";
@@ -362,6 +367,7 @@ pub fn detect_integer_upsampling(samples: &[i32], or_bits: u32, sample_rate: u32
""
}
#[cfg(test)]
fn integer_upsampling_at_phase(
samples: &[i32],
unused: u32,
@@ -0,0 +1,385 @@
//! Exact evidence with one circular STFT frame per channel. Only scalar
//! frame levels survive the FFT, for the existing floor/band percentiles.
use super::*;
pub(in crate::media::hires) struct StftPlan {
n: usize,
window: Vec<f64>,
window_power: f64,
band_low: usize,
band_high: usize,
music_bands: Vec<(usize, usize)>,
fft: Radix2Fft,
re: Vec<f64>,
im: Vec<f64>,
band_power: Vec<f64>,
}
impl StftPlan {
pub(in crate::media::hires) fn new(n: usize, sample_rate: u32) -> Self {
let window: Vec<f64> = (0..n)
.map(|i| 0.5 - 0.5 * (2.0 * std::f64::consts::PI * i as f64 / n as f64).cos())
.collect();
let window_power = window.iter().map(|w| w * w).sum();
let bin_hz = f64::from(sample_rate) / n as f64;
let band_low = (FLOOR_BAND_LOW_HZ / bin_hz).ceil() as usize;
let band_high = ((FLOOR_BAND_HIGH_HZ / bin_hz) as usize).min(n / 2);
let nyquist = f64::from(sample_rate) / 2.0;
let mut music_bands = Vec::new();
if nyquist > f64::from(SOURCE_RATES[1]) / 2.0 {
let mut lo = MUSIC_BAND_START_HZ;
while lo + MUSIC_BAND_WIDTH_HZ <= nyquist {
let first = (lo / bin_hz).ceil() as usize;
let last = (((lo + MUSIC_BAND_WIDTH_HZ) / bin_hz).ceil() as usize).min(n / 2 + 1);
if last > first {
music_bands.push((first, last));
}
lo += MUSIC_BAND_WIDTH_HZ;
}
}
Self {
n,
window,
window_power,
band_low,
band_high,
music_bands,
fft: Radix2Fft::new(n),
re: vec![0.0; n],
im: vec![0.0; n],
band_power: Vec::with_capacity(band_high.saturating_sub(band_low) + 1),
}
}
}
pub(in crate::media::hires) struct StftAccumulator {
signal: Vec<f32>,
cursor: usize,
until_frame: usize,
frame_count: usize,
stats: StftStats,
frames: Vec<(f64, f64)>,
music_levels: Vec<Vec<f64>>,
}
impl StftAccumulator {
pub(in crate::media::hires) fn new(plan: &StftPlan) -> Self {
Self {
signal: vec![0.0; plan.n],
cursor: 0,
until_frame: plan.n,
frame_count: 0,
stats: StftStats {
avg_magnitude: vec![0.0; plan.n / 2 + 1],
quiet_floor_var: f64::NAN,
music_band_spreads: Vec::new(),
},
frames: Vec::new(),
music_levels: vec![Vec::new(); plan.music_bands.len()],
}
}
pub(in crate::media::hires) fn push(
&mut self,
value: f32,
plan: &mut StftPlan,
check: &dyn Fn() -> Result<(), String>,
) -> Result<(), String> {
self.signal[self.cursor] = value;
self.cursor += 1;
if self.cursor == plan.n {
self.cursor = 0;
}
self.until_frame -= 1;
if self.until_frame != 0 {
return Ok(());
}
if self.frame_count.is_multiple_of(256) {
check()?;
}
self.frame_count += 1;
self.until_frame = plan.n / 4;
if self.signal.iter().all(|sample| *sample == 0.0) {
return Ok(());
}
let ordered = self.signal[self.cursor..]
.iter()
.chain(&self.signal[..self.cursor]);
for ((r, w), sample) in plan.re.iter_mut().zip(&plan.window).zip(ordered) {
*r = f64::from(*sample) * w;
}
plan.im.fill(0.0);
plan.fft.transform(&mut plan.re, &mut plan.im);
for (k, avg) in self.stats.avg_magnitude.iter_mut().enumerate() {
*avg += plan.re[k].hypot(plan.im[k]);
}
if plan.band_high <= plan.band_low {
return Ok(());
}
plan.band_power.clear();
let mut sum = 0.0;
for k in plan.band_low..=plan.band_high {
let power = plan.re[k] * plan.re[k] + plan.im[k] * plan.im[k];
plan.band_power.push(power);
sum += power;
}
if sum == 0.0 {
return Ok(());
}
self.frames.push((
sum / plan.band_power.len() as f64,
median_in_place(&mut plan.band_power),
));
for (levels, &(first, last)) in self.music_levels.iter_mut().zip(&plan.music_bands) {
let power = (first..last)
.map(|k| plan.re[k] * plan.re[k] + plan.im[k] * plan.im[k])
.sum::<f64>()
/ (last - first) as f64;
levels.push(10.0 * power.max(1e-30).log10());
}
Ok(())
}
pub(in crate::media::hires) fn finish(mut self, plan: &StftPlan) -> StftStats {
if self.frame_count == 0 {
return self.stats;
}
for avg in &mut self.stats.avg_magnitude {
*avg /= self.frame_count as f64;
}
if !self.frames.is_empty() {
self.frames.sort_by(|a, b| a.0.total_cmp(&b.0));
let count = ((self.frames.len() as f64 * QUIET_FRAME_FRACTION) as usize).max(1);
let mut estimates: Vec<f64> = self.frames[..count]
.iter()
.map(|&(_, median)| median / std::f64::consts::LN_2 / plan.window_power)
.collect();
self.stats.quiet_floor_var = median_in_place(&mut estimates);
}
for mut levels in self.music_levels {
if levels.len() < 2 {
break;
}
levels.sort_by(f64::total_cmp);
self.stats
.music_band_spreads
.push(percentile_sorted(&levels, 0.95) - percentile_sorted(&levels, 0.05));
}
self.stats
}
}
struct Position {
count: usize,
moving: usize,
// Bucket floor(log2(abs(d2) - 1)). Then abs(d2) > 2 << unused
// exactly when its bucket is at least unused + 1. Samples are all
// divisible by 1 << unused, once the final OR establishes that shift.
second_derivatives: [usize; 33],
}
impl Default for Position {
fn default() -> Self {
Self {
count: 0,
moving: 0,
second_derivatives: [0; 33],
}
}
}
pub(in crate::media::hires) struct IntegerEvidence {
pub(in crate::media::hires) or_bits: u32,
count: usize,
previous: [i32; 2],
ratios: Vec<Vec<Position>>,
}
impl IntegerEvidence {
pub(in crate::media::hires) fn new(sample_rate: u32) -> Self {
let ratios = SOURCE_RATES
.into_iter()
.filter_map(|rate| {
let ratio = (sample_rate / rate) as usize;
(sample_rate.is_multiple_of(rate) && (2..=8).contains(&ratio))
.then(|| (0..ratio).map(|_| Position::default()).collect())
})
.collect();
Self {
or_bits: 0,
count: 0,
previous: [0; 2],
ratios,
}
}
pub(in crate::media::hires) fn push(&mut self, value: i32) {
self.or_bits |= value as u32;
if self.count >= 2 {
let d1 = i64::from(self.previous[1]) - i64::from(self.previous[0]);
let d2 = (i64::from(self.previous[0]) - 2 * i64::from(self.previous[1])
+ i64::from(value))
.unsigned_abs();
let bucket = d2.saturating_sub(1).checked_ilog2();
for positions in &mut self.ratios {
let position = (self.count - 1) % positions.len();
let state = &mut positions[position];
state.count += 1;
state.moving += usize::from(d1 != 0);
if let Some(bucket) = bucket {
state.second_derivatives[bucket as usize] += 1;
}
}
}
self.previous = [self.previous[1], value];
self.count += 1;
}
pub(in crate::media::hires) fn artifact(&self) -> &'static str {
if self.or_bits == 0 || self.count < 4 {
return "";
}
let first_bucket = self.or_bits.trailing_zeros() as usize + 1;
for positions in &self.ratios {
let inner_total: usize = positions.iter().map(|state| state.count).sum();
let hold_total: usize = positions.iter().map(|state| state.moving).sum();
let lines: Vec<usize> = positions
.iter()
.map(|state| state.second_derivatives[first_bucket..].iter().sum())
.collect();
let line_total: usize = lines.iter().sum();
for (phase, anchor) in positions.iter().enumerate() {
let maximum = (inner_total - anchor.count) as f64 * ARTIFACT_MAX_VIOLATION_RATE;
if anchor.moving >= ARTIFACT_MIN_MOVING_ANCHORS
&& (hold_total - anchor.moving) as f64 <= maximum
{
return ARTIFACT_SAMPLE_HOLD;
}
if lines[phase] >= ARTIFACT_MIN_MOVING_ANCHORS
&& (line_total - lines[phase]) as f64 <= maximum
{
return ARTIFACT_INTERPOLATION;
}
}
}
""
}
}
#[cfg(test)]
mod tests {
use super::*;
pub(in crate::media::hires) fn assert_stats_equal(actual: &StftStats, expected: &StftStats) {
assert_eq!(actual.avg_magnitude, expected.avg_magnitude);
assert_eq!(
actual.quiet_floor_var.to_bits(),
expected.quiet_floor_var.to_bits()
);
assert_eq!(actual.music_band_spreads, expected.music_band_spreads);
}
#[test]
fn circular_stft_matches_buffered_reference_exactly() {
for rate in [44_100, 96_000, 192_000] {
for n in [256, 1024] {
let mut plan = StftPlan::new(n, rate);
for length in [n - 1, n, n + n / 4 - 1, 7 * n + 13] {
let signals: Vec<Vec<f32>> = (0..3)
.map(|channel| {
(0..length)
.map(|i| match channel {
0 => 0.0,
1 if i < 2 * n => 0.0,
1 => ((i * 7919 % 65537) as f32 - 32768.0) / 65536.0,
_ => -((i * 7919 % 65537) as f32 - 32768.0) / 65536.0,
})
.collect()
})
.collect();
let mut channels: Vec<_> = signals
.iter()
.map(|_| StftAccumulator::new(&plan))
.collect();
for i in 0..length {
for (channel, signal) in channels.iter_mut().zip(&signals) {
channel.push(signal[i], &mut plan, &|| Ok(())).unwrap();
}
}
for (channel, signal) in channels.into_iter().zip(signals) {
assert_stats_equal(
&channel.finish(&plan),
&analyze_stft(&signal, n, rate, &|| Ok(())).unwrap(),
);
}
}
}
}
}
fn assert_integer_equal(samples: &[i32], rate: u32) {
let mut evidence = IntegerEvidence::new(rate);
for &sample in samples {
evidence.push(sample);
}
let orbit = samples.iter().fold(0, |bits, sample| bits | *sample as u32);
assert_eq!(evidence.or_bits, orbit);
assert_eq!(
evidence.artifact(),
detect_integer_upsampling(samples, orbit, rate)
);
}
#[test]
fn integer_histograms_match_every_ratio_phase_padding_and_violation_boundary() {
for base_rate in SOURCE_RATES {
for ratio in 2..=8 {
for phase in 0..ratio {
for shift in [0, 8, 16] {
let anchors: Vec<i32> =
(0..1300).map(|i| (i * 137 % 1021 - 510) * 16).collect();
let hold: Vec<i32> = anchors
.iter()
.flat_map(|value| std::iter::repeat_n(*value << shift, ratio))
.skip(phase)
.collect();
let line: Vec<i32> = anchors
.windows(2)
.flat_map(|pair| {
(0..ratio).map(move |i| {
(pair[0] + (pair[1] - pair[0]) * i as i32 / ratio as i32)
<< shift
})
})
.skip(phase)
.collect();
let rate = base_rate * ratio as u32;
for signal in [hold, line] {
assert_integer_equal(&signal, rate);
for violations in [1, ratio, ratio * 2] {
let mut altered = signal.clone();
for i in 0..violations {
altered[10 + i * 17] += 37 << shift;
}
assert_integer_equal(&altered, rate);
}
// The final value changes the unused-bit shift for all earlier derivatives.
let mut altered = signal;
altered.push(1);
assert_integer_equal(&altered, rate);
}
}
}
}
}
for samples in [
vec![],
vec![0; 10],
vec![i32::MIN, i32::MAX, i32::MIN, 1],
vec![i32::MIN; 5000],
] {
assert_integer_equal(&samples, 96_000);
}
}
}
+172 -17
View File
@@ -161,8 +161,8 @@ impl HiResCheckResult {
}
}
/// One channel's decoded segment. Channels are analyzed separately to avoid
/// phase cancellation, keeping memory bounded to one channel at a time.
/// Reference decoder retained for streaming parity tests.
#[cfg(test)]
#[derive(Default)]
struct Window {
signal: Vec<f32>,
@@ -173,6 +173,36 @@ struct Window {
samples: Vec<i32>,
}
struct ChannelAnalysis {
stft: StftAccumulator,
integers: IntegerEvidence,
count: usize,
non_silent: bool,
}
impl ChannelAnalysis {
fn push(
&mut self,
sample: f32,
integer: Option<i32>,
plan: &mut StftPlan,
check: &dyn Fn() -> Result<(), String>,
) -> Result<(), String> {
self.count += 1;
self.non_silent |= f64::from(sample.abs()) > 1e-9;
if let Some(integer) = integer {
self.integers.push(integer);
}
self.stft.push(sample, plan, check)
}
}
struct WindowAnalysis {
channels: Vec<ChannelAnalysis>,
#[cfg(test)]
decoded_blocks: usize,
}
/// Analyses one file and returns a populated result. Only a short segment from
/// the middle of the track is decoded, never the whole file.
pub fn check_file(
@@ -195,7 +225,7 @@ pub fn check_file(
}
check()?;
let source = Source::open(&mut file)?;
let mut source = Source::open(&mut file)?;
let sr = source.sample_rate();
if sr == 0 {
return Err("invalid declared sample rate 0".to_string().into());
@@ -213,8 +243,6 @@ pub fn check_file(
let window_frames = (analyzed_duration * f64::from(sr)) as u64;
let declared_bits = source.declared_bits();
let channels = source.channel_count();
drop(source);
let mut result = HiResCheckResult {
file_path: file_path.to_string(),
@@ -239,27 +267,26 @@ pub fn check_file(
let mut or_bits = 0;
let mut common_artifact: Option<&str> = None;
let mut all_floors_at_16bit = true;
for channel in 0..channels {
let mut plan = StftPlan::new(n_fft, sr);
let analysis = source
.analyze_window(start_frame, window_frames, &mut plan, check)
.map_err(|e| HiResCheckError::Failed(format!("could not decode audio: {e}")))?;
for channel in analysis.channels {
check()?;
file.seek(SeekFrom::Start(0)).map_err(|e| e.to_string())?;
let mut source = Source::open(&mut file)?;
let window = source
.read_window(start_frame, window_frames, channel, check)
.map_err(|e| HiResCheckError::Failed(format!("could not decode audio: {e}")))?;
if window.signal.is_empty() {
if channel.count == 0 {
return Err("decoded audio segment is empty".to_string().into());
}
or_bits |= window.or_bits;
or_bits |= channel.integers.or_bits;
result.analyzed_duration_s = result
.analyzed_duration_s
.min(window.signal.len() as f64 / f64::from(sr));
if !window.signal.iter().any(|v| f64::from(v.abs()) > 1e-9) {
.min(channel.count as f64 / f64::from(sr));
if !channel.non_silent {
continue;
}
let stats = analyze_stft(&window.signal, n_fft, sr, check)?;
let stats = channel.stft.finish(&plan);
all_floors_at_16bit &= classify_noise_floor(stats.quiet_floor_var, 1).0 == FLOOR_AT_16BIT;
if claims_by_rate {
let artifact = detect_integer_upsampling(&window.samples, window.or_bits, sr);
let artifact = channel.integers.artifact();
// A pattern in one channel must not implicate independent,
// full-resolution content in another channel.
common_artifact = Some(match common_artifact {
@@ -472,6 +499,7 @@ impl<'a> Source<'a> {
}
}
#[cfg(test)]
fn read_window(
&mut self,
start_frame: u64,
@@ -484,12 +512,85 @@ impl<'a> Source<'a> {
Source::Wav(wav) => wav.read_window(start_frame, frames, channel, check),
}
}
fn analyze_window(
&mut self,
start_frame: u64,
frames: u64,
plan: &mut StftPlan,
check: &dyn Fn() -> Result<(), String>,
) -> Result<WindowAnalysis, String> {
let sample_rate = self.sample_rate();
let mut analysis = WindowAnalysis {
channels: (0..self.channel_count())
.map(|_| ChannelAnalysis {
stft: StftAccumulator::new(plan),
integers: IntegerEvidence::new(sample_rate),
count: 0,
non_silent: false,
})
.collect(),
#[cfg(test)]
decoded_blocks: 0,
};
match self {
Source::Flac(reader) => {
let scale = 1.0 / 2f64.powi(reader.streaminfo().bits_per_sample as i32 - 1);
let end = start_frame + frames;
let mut blocks = reader.blocks();
let mut buffer = Vec::new();
let mut decoded = 0_u64;
while analysis
.channels
.first()
.is_some_and(|channel| (channel.count as u64) < frames)
{
if decoded.is_multiple_of(64) {
check()?;
}
decoded += 1;
let block = match blocks.read_next_or_eof(buffer) {
Ok(Some(block)) => block,
Ok(None) => break,
Err(_) if analysis.channels[0].count > 0 => break,
Err(error) => return Err(error.to_string()),
};
#[cfg(test)]
{
analysis.decoded_blocks += 1;
}
let first = block.time();
let n = u64::from(block.duration());
if first + n > start_frame {
let from = start_frame.saturating_sub(first) as usize;
let to = n.min(end.saturating_sub(first)) as usize;
for (index, channel) in analysis.channels.iter_mut().enumerate() {
for &value in &block.channel(index as u32)[from..to] {
channel.push(
(f64::from(value) * scale) as f32,
Some(value),
plan,
check,
)?;
}
}
}
buffer = block.into_buffer();
}
}
Source::Wav(wav) => {
wav.analyze_window(start_frame, frames, &mut analysis, plan, check)?
}
}
Ok(analysis)
}
}
/// Decodes [start_frame, start_frame + frames). claxon hands back samples
/// right-justified with wasted bits already shifted back in and inter-channel
/// decorrelation undone, so each value is the stored sample. It cannot seek,
/// so the frames before the window are decoded and skipped.
#[cfg(test)]
fn read_flac_window(
reader: &mut claxon::FlacReader<BufReader<&mut File>>,
start_frame: u64,
@@ -637,6 +738,7 @@ impl<'a> WavSource<'a> {
u64::from(self.channels * self.container_bits / 8)
}
#[cfg(test)]
fn read_window(
&mut self,
start_frame: u64,
@@ -699,4 +801,57 @@ impl<'a> WavSource<'a> {
}
Ok(out)
}
fn analyze_window(
&mut self,
start_frame: u64,
frames: u64,
analysis: &mut WindowAnalysis,
plan: &mut StftPlan,
check: &dyn Fn() -> Result<(), String>,
) -> Result<(), String> {
let total = self.data_size / self.frame_bytes();
let frames = frames.min(total.saturating_sub(start_frame));
let frame_bytes = self.frame_bytes() as usize;
self.file
.seek(SeekFrom::Start(
self.data_offset + start_frame * frame_bytes as u64,
))
.map_err(|e| e.to_string())?;
let bytes_per_sample = (self.container_bits / 8) as usize;
let scale = 1.0 / 2f64.powi(self.container_bits as i32 - 1);
let mut reader = BufReader::with_capacity(1 << 16, &mut *self.file);
let mut frame = vec![0; frame_bytes];
for index in 0..frames {
if index.is_multiple_of(65_536) {
check()?;
}
if reader.read_exact(&mut frame).is_err() {
break;
}
for (channel, bytes) in analysis
.channels
.iter_mut()
.zip(frame.chunks_exact(bytes_per_sample))
{
if self.is_float {
let value = if self.container_bits == 32 {
f64::from(f32::from_le_bytes(bytes.try_into().expect("32-bit float")))
} else {
f64::from_le_bytes(bytes.try_into().expect("64-bit float"))
};
channel.push(value as f32, None, plan, check)?;
} else {
let value = match self.container_bits {
8 => i32::from(bytes[0]) - 128,
16 => i32::from(i16::from_le_bytes(bytes.try_into().expect("16-bit PCM"))),
24 => i32::from_le_bytes([0, bytes[0], bytes[1], bytes[2]]) >> 8,
_ => i32::from_le_bytes(bytes.try_into().expect("32-bit PCM")),
};
channel.push((f64::from(value) * scale) as f32, Some(value), plan, check)?;
}
}
}
Ok(())
}
}
@@ -439,6 +439,115 @@ fn write_stereo_wav(path: &Path, left: &[i32], right: &[i32], sr: u32) {
std::fs::write(path, bytes).expect("stereo header");
}
fn assert_streamed_window_matches_reference(path: &Path, start: u64, frames: u64) -> usize {
let mut file = File::open(path).unwrap();
let mut source = Source::open(&mut file).unwrap();
let rate = source.sample_rate();
let mut plan = StftPlan::new(1024, rate);
let streamed = source
.analyze_window(start, frames, &mut plan, &|| Ok(()))
.unwrap();
for (index, channel) in streamed.channels.into_iter().enumerate() {
let mut file = File::open(path).unwrap();
let mut reference = Source::open(&mut file).unwrap();
let window = reference
.read_window(start, frames, index as u32, &|| Ok(()))
.unwrap();
assert_eq!(channel.count, window.signal.len());
assert_eq!(
channel.non_silent,
window
.signal
.iter()
.any(|sample| f64::from(sample.abs()) > 1e-9)
);
assert_eq!(channel.integers.or_bits, window.or_bits);
assert_eq!(
channel.integers.artifact(),
evidence::detect_integer_upsampling(&window.samples, window.or_bits, rate)
);
let expected = evidence::analyze_stft(&window.signal, 1024, rate, &|| Ok(())).unwrap();
let actual = channel.stft.finish(&plan);
assert_eq!(actual.avg_magnitude, expected.avg_magnitude);
assert_eq!(
actual.quiet_floor_var.to_bits(),
expected.quiet_floor_var.to_bits()
);
assert_eq!(actual.music_band_spreads, expected.music_band_spreads);
}
streamed.decoded_blocks
}
#[test]
fn one_flac_decode_pass_serves_all_eight_channels_with_exact_window_evidence() {
let dir = TempDir::new("streamed-flac");
let path = dir.file("eight-channels.flac");
let samples = pcm_using_bits(16, 24, 4096 * 6, 29);
write_flac(&path, &samples, 96_000, 24, 8);
// Three prefix blocks plus the end of the sampled window: four decoded
// blocks total, rather than four separately for every channel.
assert_eq!(
assert_streamed_window_matches_reference(&path, 8500, 5000),
4
);
assert_eq!(assert_streamed_window_matches_reference(&path, 0, 700), 1);
assert_eq!(
assert_streamed_window_matches_reference(&path, 20_000, 8000),
6
);
}
#[test]
fn streaming_wav_preserves_independent_antiphase_channels_and_partial_windows() {
let dir = TempDir::new("streamed-wav");
let left = quantize(&full_band_noise(20_003, 23), 24);
let right: Vec<i32> = left.iter().map(|sample| -*sample).collect();
let path = dir.file("antiphase.wav");
write_stereo_wav(&path, &left, &right, 96_000);
assert_eq!(
assert_streamed_window_matches_reference(&path, 17, 13_013),
0
);
assert_eq!(
assert_streamed_window_matches_reference(&path, 18_000, 6000),
0
);
}
#[test]
fn streaming_wav_matches_all_integer_and_float_sample_encodings() {
let dir = TempDir::new("streamed-encodings");
for bits in [8, 16, 24, 32] {
let path = dir.file(&format!("integer-{bits}.wav"));
let samples: Vec<i32> = (0..4099).map(|i| (i * 37 % 127) - 63).collect();
write_wav(&path, &samples, 96_000, bits);
assert_streamed_window_matches_reference(&path, 31, 3500);
}
for bits in [32, 64] {
let path = dir.file(&format!("float-{bits}.wav"));
write_wav(&path, &[0], 96_000, 32);
let mut bytes = std::fs::read(&path).unwrap();
bytes.truncate(44);
bytes[20..22].copy_from_slice(&3u16.to_le_bytes());
bytes[28..32].copy_from_slice(&(96_000_u32 * bits / 8).to_le_bytes());
bytes[32..34].copy_from_slice(&((bits / 8) as u16).to_le_bytes());
bytes[34..36].copy_from_slice(&(bits as u16).to_le_bytes());
for i in 0..4099 {
let value = ((i * 37 % 127) as f64 - 63.0) / 128.0;
if bits == 32 {
bytes.extend_from_slice(&(value as f32).to_le_bytes());
} else {
bytes.extend_from_slice(&value.to_le_bytes());
}
}
let data_size = (bytes.len() - 44) as u32;
bytes[4..8].copy_from_slice(&(36 + data_size).to_le_bytes());
bytes[40..44].copy_from_slice(&data_size.to_le_bytes());
std::fs::write(&path, bytes).unwrap();
assert_streamed_window_matches_reference(&path, 31, 3500);
}
}
#[test]
fn antiphase_channels_do_not_cancel_spectral_evidence() {
let dir = TempDir::new("antiphase");