fix: avoid inferring Hi-Res upsampling from bandwidth alone

This commit is contained in:
zarzet
2026-09-25 19:57:44 +07:00
parent d0d12efd85
commit 1bae8156bb
3 changed files with 254 additions and 122 deletions
@@ -5,19 +5,17 @@
//! separate LOSSLESS copy preserves the whole file.
//!
//! - Integer-ratio upsampling artifacts (sample-and-hold, linear
//! interpolation) and spectral imaging are exact fingerprints: "certain".
//! interpolation) give strong evidence. Spectral imaging is a heuristic.
//! - A brickwall right at a CD/DAT Nyquist (22.05 / 24 kHz) means a
//! 44.1/48 kHz chain. With an in-band noise floor no lower than 16-bit
//! quantization noise, a CD-derived source is "likely". This remains a
//! heuristic and does not authorize automatic replacement.
//! - Anything else that fails the cutoff test may be a genuine master:
//! "suspect", never replaced automatically.
//! - A limited bandwidth alone does not establish a file's provenance.
use super::fft::Radix2Fft;
pub const CONFIDENCE_CERTAIN: &str = "certain";
pub const CONFIDENCE_LIKELY: &str = "likely";
pub const CONFIDENCE_SUSPECT: &str = "suspect";
pub const ARTIFACT_SAMPLE_HOLD: &str = "sample_hold";
pub const ARTIFACT_INTERPOLATION: &str = "linear_interpolation";
@@ -82,9 +80,9 @@ pub struct StftStats {
pub music_band_spreads: Vec<f64>,
}
/// Matches `np.abs(librosa.stft(y, n_fft)).mean(axis=1)` for the averaged
/// spectrum: periodic Hann window, hop n_fft/4, frames centred by
/// zero-padding n_fft/2 at both ends.
/// 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.
pub fn analyze_stft(
y: &[f32],
n_fft: usize,
@@ -99,11 +97,10 @@ pub fn analyze_stft(
quiet_floor_var: f64::NAN,
music_band_spreads: Vec::new(),
};
let padded_len = y.len() + 2 * half;
if padded_len < n_fft {
if y.len() < n_fft {
return Ok(stats);
}
let frame_count = 1 + (padded_len - n_fft) / hop;
let frame_count = 1 + (y.len() - n_fft) / hop;
let window: Vec<f64> = (0..n_fft)
.map(|i| 0.5 - 0.5 * (2.0 * std::f64::consts::PI * i as f64 / n_fft as f64).cos())
@@ -142,14 +139,9 @@ pub fn analyze_stft(
check()?;
}
// Index into y of the frame's first sample.
let start = (f * hop) as isize - half as isize;
let start = f * hop;
for (i, (r, w)) in re.iter_mut().zip(&window).enumerate() {
let j = start + i as isize;
*r = if j >= 0 && (j as usize) < y.len() {
f64::from(y[j as usize]) * w
} else {
0.0
};
*r = f64::from(y[start + i]) * w;
}
im.fill(0.0);
fft.transform(&mut re, &mut im);
@@ -157,9 +149,7 @@ pub fn analyze_stft(
*avg += re[k].hypot(im[k]);
}
// Frames that overlap the zero padding would read as quiet for the
// wrong reason; only frames fully inside the signal are candidates.
if start < 0 || start as usize + n_fft > y.len() || band_high <= band_low {
if band_high <= band_low {
continue;
}
band_power.clear();
+129 -86
View File
@@ -1,20 +1,19 @@
//! Hi-Res authenticity check, in lockstep with SpotiFLAC-Module-Version's
//! `core/hires_check.py`: same thresholds, same verdicts.
//! Sampled Hi-Res analysis. A spectral roll-off is an observation, not proof
//! that a master was upsampled or that a replacement preserves its audio.
//!
//! A file can claim Hi-Res along two independent axes, and each is checked on
//! its own terms:
//!
//! - Sample rate: it declares e.g. 96 kHz but its spectral content stops at,
//! or just above, the ~22.05 kHz Nyquist of a 44.1 kHz source, the
//! fingerprint of upsampling. Measuring it is a heuristic.
//! - Sample rate: spectral roll-off, noise floor and interpolation patterns
//! can suggest a lower-rate source. A cutoff alone is not a fingerprint.
//! - Bit depth: it declares 24-bit but only 16 of those bits ever carry data,
//! the low 8 being zero in every sample: a CD master padded out. Unlike the
//! spectral test this one is exact, and it is the only test that can judge
//! a 24-bit/44.1 kHz file.
//!
//! The cutoff alone cannot tell an upsampled CD from a genuine master that
//! was low-pass filtered in mastering, so every `fake_hires` verdict is graded
//! ("certain", "likely", "suspect") by the evidence in `evidence`. These
//! was low-pass filtered in mastering; that finding is `band_limited`.
//! `fake_hires` requires additional evidence ("certain" or "likely"). These
//! confidence levels describe the sampled evidence, not permission to discard
//! audio. Replacement additionally requires a full, exact PCM comparison.
//!
@@ -36,6 +35,7 @@ use std::io::{BufReader, Read, Seek, SeekFrom};
pub const VERDICT_FAKE: &str = "fake_hires";
pub const VERDICT_STANDARD: &str = "standard_definition";
pub const VERDICT_GENUINE: &str = "genuine_hires";
pub const VERDICT_BAND_LIMITED: &str = "band_limited";
pub const VERDICT_INCONCLUSIVE: &str = "inconclusive";
#[derive(Debug, PartialEq, Eq)]
@@ -71,10 +71,8 @@ pub struct HiResCheckOptions {
pub noise_floor_db: f64,
/// Sample rate above which a file claims Hi-Res.
pub hires_sample_rate_threshold: i64,
/// Minimum active-content cutoff a genuine Hi-Res file must reach. Sits
/// well above 22.05 kHz on purpose: a resampler upsampling from CD leaves
/// a transition-band tail a couple of kHz wide (a measured 44.1 -> 176.4
/// kHz upsample reached ~24.7 kHz).
/// Threshold for reporting limited bandwidth, including transition-band
/// tails above CD bandwidth. This is never a requirement for authenticity.
pub hires_cutoff_threshold_hz: f64,
/// STFT window size; must be a power of two. Shrunk for short segments.
pub n_fft: i64,
@@ -108,9 +106,8 @@ pub struct HiResCheckResult {
pub effective_bit_depth: u32,
/// Why the file was flagged, in one clause; empty when it was not.
pub reason: String,
/// How sure a fake_hires verdict is: "certain" (exact fingerprint),
/// "likely" (a resampler's cliff over a 16-bit floor) or "suspect" (may be
/// a genuine low-pass filtered master). Empty for other verdicts.
/// Strength of the sampled evidence: "certain" (padding or integer
/// pattern), "likely" (spectral evidence). Empty for other verdicts.
pub confidence: String,
/// Exact upsampling fingerprint found, if any: "sample_hold",
/// "linear_interpolation" or "imaging".
@@ -164,16 +161,16 @@ impl HiResCheckResult {
}
}
/// One decoded segment: the mono signal for the spectrum and the OR of every
/// raw sample for the bit-depth test, gathered in one pass.
/// One channel's decoded segment. Channels are analyzed separately to avoid
/// phase cancellation, keeping memory bounded to one channel at a time.
#[derive(Default)]
struct Window {
mono: Vec<f32>,
signal: Vec<f32>,
/// OR of all raw integer samples, right-justified at the declared depth.
or_bits: u32,
/// First channel's raw integer samples, for the upsampling-artifact
/// This channel's raw integer samples, for the upsampling-artifact
/// tests; empty for float PCM.
first_channel: Vec<i32>,
samples: Vec<i32>,
}
/// Analyses one file and returns a populated result. Only a short segment from
@@ -187,8 +184,10 @@ pub fn check_file(
if options.sample_seconds <= 0 {
return Err("sample_seconds must be positive".to_string().into());
}
if options.n_fft <= 0 || (options.n_fft & (options.n_fft - 1)) != 0 {
return Err("n_fft must be a positive power of two".to_string().into());
if options.n_fft < 4 || (options.n_fft & (options.n_fft - 1)) != 0 {
return Err("n_fft must be a power of two of at least 4"
.to_string()
.into());
}
let size = file.metadata().map_err(|e| e.to_string())?.len();
if size == 0 {
@@ -196,7 +195,7 @@ pub fn check_file(
}
check()?;
let mut source = Source::open(&mut file)?;
let source = Source::open(&mut file)?;
let sr = source.sample_rate();
if sr == 0 {
return Err("invalid declared sample rate 0".to_string().into());
@@ -213,13 +212,9 @@ pub fn check_file(
let start_frame = (offset * f64::from(sr)) as u64;
let window_frames = (analyzed_duration * f64::from(sr)) as u64;
let window = source
.read_window(start_frame, window_frames, check)
.map_err(|e| HiResCheckError::Failed(format!("could not decode audio: {e}")))?;
let y = &window.mono;
if y.is_empty() {
return Err("decoded audio segment is empty".to_string().into());
}
let declared_bits = source.declared_bits();
let channels = source.channel_count();
drop(source);
let mut result = HiResCheckResult {
file_path: file_path.to_string(),
@@ -227,23 +222,71 @@ pub fn check_file(
total_duration_s: total_duration,
analyzed_duration_s: analyzed_duration,
noise_floor_db: options.noise_floor_db,
declared_bit_depth: declared_bits,
useful_sample_rate: sr,
verdict: VERDICT_INCONCLUSIVE.into(),
..HiResCheckResult::default()
};
// A silent segment makes spectral analysis meaningless rather than wrong.
if !y.iter().any(|v| f64::from(v.abs()) > 1e-9) {
return Ok(result);
}
// Shrink n_fft for very short segments so the window does not end up
// measuring its own zero padding.
// Use complete windows only. Zero-padding a segment boundary introduces
// an artificial broadband transient and can hide a real spectral cutoff.
let mut n_fft = options.n_fft as usize;
while n_fft > 256 && n_fft > y.len() * 2 {
while n_fft > 256 && n_fft as u64 > window_frames {
n_fft /= 2;
}
let stats = analyze_stft(y, n_fft, sr, check)?;
let claims_by_rate = i64::from(sr) > options.hires_sample_rate_threshold;
let mut combined: Option<StftStats> = None;
let mut or_bits = 0;
let mut common_artifact: Option<&str> = None;
let mut all_floors_at_16bit = true;
for channel in 0..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() {
return Err("decoded audio segment is empty".to_string().into());
}
or_bits |= window.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) {
continue;
}
let stats = analyze_stft(&window.signal, n_fft, sr, check)?;
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);
// A pattern in one channel must not implicate independent,
// full-resolution content in another channel.
common_artifact = Some(match common_artifact {
None => artifact,
Some(previous) if previous == artifact => artifact,
Some(_) => "",
});
}
if let Some(combined) = &mut combined {
for (all, channel) in combined.avg_magnitude.iter_mut().zip(&stats.avg_magnitude) {
*all = all.max(*channel);
}
for (all, channel) in combined
.music_band_spreads
.iter_mut()
.zip(&stats.music_band_spreads)
{
*all = all.max(*channel);
}
combined.quiet_floor_var = combined.quiet_floor_var.max(stats.quiet_floor_var);
} else {
combined = Some(stats);
}
}
let Some(stats) = combined else {
return Ok(result);
};
if !stats.avg_magnitude.iter().any(|&v| v > 0.0) {
return Ok(result);
}
@@ -257,19 +300,17 @@ pub fn check_file(
.map_or(0.0, |k| k as f64 * f64::from(sr) / n_fft as f64);
result.cutoff_frequency_hz = cutoff;
let declared_bits = source.declared_bits();
let mut effective_bits = 0;
if declared_bits > 0 && window.or_bits != 0 {
if declared_bits > 0 && or_bits != 0 {
// Digital silence carries no bits at all; leaving it at 0 keeps it
// out of the padded-depth test.
effective_bits = declared_bits.saturating_sub(window.or_bits.trailing_zeros());
effective_bits = declared_bits.saturating_sub(or_bits.trailing_zeros());
}
result.declared_bit_depth = declared_bits;
result.effective_bit_depth = effective_bits;
// A file can claim Hi-Res by rate, by depth, or both, and each claim is
// answered by the test that can actually judge it.
let claims_by_rate = i64::from(sr) > options.hires_sample_rate_threshold;
let claims_by_depth = declared_bits > 16;
result.useful_sample_rate = sr;
if claims_by_rate {
@@ -292,42 +333,46 @@ pub fn check_file(
result.brickwall_hz = detect_brickwall(&spec_db, sr, n_fft, options.noise_floor_db);
}
let cutoff_is_low = claims_by_rate && cutoff < options.hires_cutoff_threshold_hz;
let rate_is_fake = cutoff_is_low || result.brickwall_hz > 0.0;
let limited_bandwidth = cutoff_is_low || result.brickwall_hz > 0.0;
let depth_is_fake = claims_by_depth && effective_bits > 0 && effective_bits <= 16;
// Exact fingerprints of a conversion. Imaging puts content back above
// Integer fingerprints of a conversion. Imaging puts content back above
// 22 kHz, so such a file can pass the cutoff test and still be a fake.
let mut artifact = "";
if claims_by_rate {
artifact = detect_integer_upsampling(&window.first_channel, window.or_bits, sr);
if artifact.is_empty() && detect_imaging(&spec_db, sr, n_fft, options.noise_floor_db) {
artifact = ARTIFACT_IMAGING;
}
let mut artifact = common_artifact.unwrap_or("");
if claims_by_rate
&& artifact.is_empty()
&& detect_imaging(&spec_db, sr, n_fft, options.noise_floor_db)
{
artifact = ARTIFACT_IMAGING;
}
result.upsampling_artifact = artifact.into();
if rate_is_fake {
let (class, vs_16bit_db) =
classify_noise_floor(stats.quiet_floor_var, source.channel_count());
if limited_bandwidth {
let (class, vs_16bit_db) = classify_noise_floor(stats.quiet_floor_var, 1);
result.noise_floor_class = class.into();
result.noise_floor_vs_16bit_db = vs_16bit_db;
}
// A noisier channel must not obscure a quieter channel's extra precision.
let likely_upsampled = result.brickwall_hz > 0.0 && all_floors_at_16bit;
result.verdict = if !claims_by_rate && !claims_by_depth {
VERDICT_STANDARD
} else if rate_is_fake || depth_is_fake || !artifact.is_empty() {
} else if likely_upsampled || depth_is_fake || !artifact.is_empty() {
VERDICT_FAKE
} else if limited_bandwidth {
VERDICT_BAND_LIMITED
} else {
VERDICT_GENUINE
}
.into();
if result.verdict == VERDICT_FAKE {
result.confidence = if depth_is_fake || !artifact.is_empty() {
result.confidence = if depth_is_fake
|| artifact == ARTIFACT_SAMPLE_HOLD
|| artifact == ARTIFACT_INTERPOLATION
{
CONFIDENCE_CERTAIN
} else if result.brickwall_hz > 0.0 && result.noise_floor_class == FLOOR_AT_16BIT {
CONFIDENCE_LIKELY
} else {
CONFIDENCE_SUSPECT
CONFIDENCE_LIKELY
}
.into();
}
@@ -349,7 +394,7 @@ pub fn check_file(
findings.push(format!(
"declares {sr} Hz but content stops at ~{cutoff:.0} Hz"
));
} else if rate_is_fake {
} else if limited_bandwidth {
findings.push(format!(
"declares {sr} Hz but the spectrum falls off a cliff at {:.0} Hz",
result.brickwall_hz
@@ -360,8 +405,8 @@ pub fn check_file(
"declares {declared_bits}-bit but only {effective_bits} bits carry data"
));
}
if result.confidence == CONFIDENCE_SUSPECT {
findings.push("may be a genuine master low-pass filtered in mastering".into());
if result.verdict == VERDICT_BAND_LIMITED {
findings.push("bandwidth alone does not establish upsampling or master provenance".into());
}
result.reason = findings.join("; ");
Ok(result)
@@ -431,11 +476,12 @@ impl<'a> Source<'a> {
&mut self,
start_frame: u64,
frames: u64,
channel: u32,
check: &dyn Fn() -> Result<(), String>,
) -> Result<Window, String> {
match self {
Source::Flac(reader) => read_flac_window(reader, start_frame, frames, check),
Source::Wav(wav) => wav.read_window(start_frame, frames, check),
Source::Flac(reader) => read_flac_window(reader, start_frame, frames, channel, check),
Source::Wav(wav) => wav.read_window(start_frame, frames, channel, check),
}
}
}
@@ -448,21 +494,22 @@ fn read_flac_window(
reader: &mut claxon::FlacReader<BufReader<&mut File>>,
start_frame: u64,
frames: u64,
channel: u32,
check: &dyn Fn() -> Result<(), String>,
) -> Result<Window, String> {
let bits = reader.streaminfo().bits_per_sample;
let scale = 1.0 / 2f64.powi(bits as i32 - 1);
let capacity = usize::try_from(frames).unwrap_or(0);
let mut out = Window {
mono: Vec::with_capacity(capacity),
first_channel: Vec::with_capacity(capacity),
signal: Vec::with_capacity(capacity),
samples: Vec::with_capacity(capacity),
..Window::default()
};
let end = start_frame + frames;
let mut blocks = reader.blocks();
let mut buffer = Vec::new();
let mut decoded = 0u64;
while (out.mono.len() as u64) < frames {
while (out.signal.len() as u64) < frames {
if decoded.is_multiple_of(64) {
check()?;
}
@@ -471,25 +518,20 @@ fn read_flac_window(
Ok(Some(block)) => block,
Ok(None) => break,
// A truncated tail still leaves a usable window.
Err(_) if !out.mono.is_empty() => break,
Err(_) if !out.signal.is_empty() => break,
Err(e) => return Err(e.to_string()),
};
let first = block.time();
let n = u64::from(block.duration());
if first + n > start_frame {
let channels = block.channels();
let from = start_frame.saturating_sub(first);
let to = n.min(end - first);
for i in from..to {
let i = i as usize;
let mut sum = 0.0;
for ch in 0..channels {
let v = block.channel(ch)[i];
out.or_bits |= v as u32;
sum += f64::from(v);
}
out.first_channel.push(block.channel(0)[i]);
out.mono.push((sum / f64::from(channels) * scale) as f32);
let v = block.channel(channel)[i];
out.or_bits |= v as u32;
out.samples.push(v);
out.signal.push((f64::from(v) * scale) as f32);
}
}
buffer = block.into_buffer();
@@ -599,6 +641,7 @@ impl<'a> WavSource<'a> {
&mut self,
start_frame: u64,
frames: u64,
channel: u32,
check: &dyn Fn() -> Result<(), String>,
) -> Result<Window, String> {
let total = self.data_size / self.frame_bytes();
@@ -614,9 +657,9 @@ impl<'a> WavSource<'a> {
))
.map_err(|e| e.to_string())?;
let capacity = usize::try_from(frames).unwrap_or(0);
out.mono.reserve(capacity);
out.signal.reserve(capacity);
if !self.is_float {
out.first_channel.reserve(capacity);
out.samples.reserve(capacity);
}
let bytes_per_sample = (self.container_bits / 8) as usize;
let scale = 1.0 / 2f64.powi(self.container_bits as i32 - 1);
@@ -630,14 +673,17 @@ impl<'a> WavSource<'a> {
if reader.read_exact(&mut frame).is_err() {
break;
}
let mut sum = 0.0;
for (c, b) in frame.chunks_exact(bytes_per_sample).enumerate() {
if c != channel as usize {
continue;
}
if self.is_float {
sum += if self.container_bits == 32 {
let v = if self.container_bits == 32 {
f64::from(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
} else {
f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
};
out.signal.push(v as f32);
continue;
}
let v: i32 = match self.container_bits {
@@ -647,12 +693,9 @@ impl<'a> WavSource<'a> {
_ => i32::from_le_bytes([b[0], b[1], b[2], b[3]]),
};
out.or_bits |= v as u32;
if c == 0 {
out.first_channel.push(v);
}
sum += f64::from(v) * scale;
out.samples.push(v);
out.signal.push((f64::from(v) * scale) as f32);
}
out.mono.push((sum / f64::from(self.channels)) as f32);
}
Ok(out)
}
+115 -16
View File
@@ -397,12 +397,106 @@ fn genuine_hires_is_not_flagged() {
}
#[test]
fn an_upsampled_cd_signal_that_is_never_quiet_is_only_suspect() {
fn full_bandwidth_is_measured_relative_to_each_sample_rate() {
let dir = TempDir::new("rates");
for sr in [44_100, 48_000, 88_200, 96_000, 176_400, 192_000] {
let samples = quantize(&full_band_noise(65_536, 21), 24);
let r = check(&flac(&dir, &format!("{sr}.flac"), &samples, sr, 24));
assert_eq!(r.verdict, VERDICT_GENUINE, "{sr}: {}", r.reason);
assert!((r.cutoff_frequency_hz - f64::from(sr) / 2.0).abs() < 50.0);
assert_eq!(r.effective_bit_depth, 24);
}
}
#[test]
fn a_96k_master_filtered_at_27k_is_not_evidence_of_upsampling() {
let dir = TempDir::new("filtered96");
let signal = shape_spectrum(&full_band_noise(131_072, 22), 96_000, |hz| {
if hz < 27_000.0 { 1.0 } else { 0.0 }
});
let r = check(&flac(
&dir,
"filtered.flac",
&quantize(&signal, 24),
96_000,
24,
));
assert_eq!(r.verdict, VERDICT_BAND_LIMITED);
assert!((r.cutoff_frequency_hz - 27_000.0).abs() < 1000.0);
assert_eq!(r.effective_bit_depth, 24);
assert!(!r.is_suspicious());
assert!(!r.redownload_safe());
}
fn write_stereo_wav(path: &Path, left: &[i32], right: &[i32], sr: u32) {
assert_eq!(left.len(), right.len());
let interleaved: Vec<i32> = left.iter().zip(right).flat_map(|(&l, &r)| [l, r]).collect();
write_wav(path, &interleaved, sr, 24);
let mut bytes = std::fs::read(path).expect("wav");
bytes[22..24].copy_from_slice(&2u16.to_le_bytes());
bytes[28..32].copy_from_slice(&(sr * 6).to_le_bytes());
bytes[32..34].copy_from_slice(&6u16.to_le_bytes());
std::fs::write(path, bytes).expect("stereo header");
}
#[test]
fn antiphase_channels_do_not_cancel_spectral_evidence() {
let dir = TempDir::new("antiphase");
let left = quantize(&full_band_noise(65_536, 23), 24);
let right: Vec<i32> = left.iter().map(|v| -*v).collect();
let path = dir.file("antiphase.wav");
write_stereo_wav(&path, &left, &right, 96_000);
let r = check(&path);
assert_eq!(r.verdict, VERDICT_GENUINE);
assert!(r.cutoff_frequency_hz > 47_000.0);
assert_eq!(r.effective_bit_depth, 24);
}
#[test]
fn a_full_resolution_channel_prevents_a_false_padding_or_pattern_verdict() {
let dir = TempDir::new("independent-channels");
let source = pcm_using_bits(16, 24, 16_384, 24);
let left: Vec<i32> = source.iter().flat_map(|&v| [v; 4]).collect();
let right = pcm_using_bits(24, 24, left.len(), 25);
let path = dir.file("independent.wav");
write_stereo_wav(&path, &left, &right, HIRES_SR);
let r = check(&path);
assert_eq!(r.verdict, VERDICT_GENUINE);
assert_eq!(r.upsampling_artifact, "");
assert_eq!(r.effective_bit_depth, 24);
assert!(r.cutoff_frequency_hz > 80_000.0);
}
#[test]
fn cropped_segment_edges_do_not_create_ultrasonic_energy() {
let signal: Vec<f32> = (0..10_000)
.map(|i| (2.0 * std::f64::consts::PI * 3000.0 * i as f64 / 96_000.0).cos() as f32)
.collect();
let stats = analyze_stft(&signal, 4096, 96_000, &|| Ok(())).expect("stft");
let db = spectrum_db(&stats.avg_magnitude);
assert!(db[1024..].iter().all(|v| *v < -100.0));
}
#[test]
fn a_16bit_noise_floor_in_one_channel_does_not_hide_quieter_detail_in_another() {
let dir = TempDir::new("mixed-floors");
let left = ideal_upsample(&dither_to_16_bit(&cd_source_with_quiet_half(0.0)), 4);
let right = ideal_upsample(&cd_source_with_quiet_half(1e-6), 4);
let path = dir.file("mixed.wav");
write_stereo_wav(&path, &quantize(&left, 24), &quantize(&right, 24), HIRES_SR);
let r = check(&path);
assert_eq!(r.brickwall_hz, 22_050.0);
assert_eq!(r.verdict, VERDICT_BAND_LIMITED);
assert!(!r.redownload_safe());
}
#[test]
fn bandwidth_alone_cannot_identify_an_upsampled_cd() {
let dir = TempDir::new("upsampled");
let samples = quantize(&upsampled_from_cd(), 24);
let r = check(&flac(&dir, "fake.flac", &samples, HIRES_SR, 24));
assert_eq!(r.verdict, VERDICT_FAKE);
assert!(r.is_suspicious());
assert_eq!(r.verdict, VERDICT_BAND_LIMITED);
assert!(!r.is_suspicious());
assert!(
r.cutoff_frequency_hz > 22_000.0 && r.cutoff_frequency_hz < 28_000.0,
"{}",
@@ -410,10 +504,10 @@ fn an_upsampled_cd_signal_that_is_never_quiet_is_only_suspect() {
);
assert!(r.reason.contains("content stops"), "{}", r.reason);
// Loud from start to end: the floor cannot be read, so a genuine master
// filtered at 22 kHz would look the same. Flagged, never replaced.
// filtered at 22 kHz would look the same. Report bandwidth, not provenance.
assert_eq!(r.brickwall_hz, 22_050.0);
assert_eq!(r.noise_floor_class, FLOOR_MASKED);
assert_eq!(r.confidence, CONFIDENCE_SUSPECT);
assert_eq!(r.confidence, "");
assert!(!r.redownload_safe());
}
@@ -536,7 +630,7 @@ fn wav_is_checked_too() {
let r = check(&fake);
assert_eq!(
(r.verdict.as_str(), r.effective_bit_depth),
(VERDICT_FAKE, 24)
(VERDICT_BAND_LIMITED, 24)
);
}
@@ -613,7 +707,7 @@ fn an_upsampled_16_bit_master_is_likely() {
/// The same cliff, but the quiet half carries detail far below 16-bit noise:
/// a 24-bit master made at 44.1 kHz. LOSSLESS would lose that depth.
#[test]
fn a_24_bit_master_made_at_44k_is_only_suspect() {
fn a_24_bit_master_made_at_44k_has_limited_bandwidth() {
let dir = TempDir::new("master24");
let signal = ideal_upsample(&cd_source_with_quiet_half(1e-6), 4);
let r = check(&flac(
@@ -623,22 +717,22 @@ fn a_24_bit_master_made_at_44k_is_only_suspect() {
HIRES_SR,
24,
));
assert_eq!(r.verdict, VERDICT_FAKE);
assert_eq!(r.verdict, VERDICT_BAND_LIMITED);
assert_eq!(
r.noise_floor_class, FLOOR_BELOW_16BIT,
"{} dB",
r.noise_floor_vs_16bit_db
);
assert_eq!(r.confidence, CONFIDENCE_SUSPECT);
assert_eq!(r.confidence, "");
assert!(!r.redownload_safe());
assert!(r.reason.contains("genuine master"), "{}", r.reason);
assert!(r.reason.contains("provenance"), "{}", r.reason);
}
/// A gradual mastering roll-off that still ends below 28 kHz: no resampler
/// cliff, so at most a suspect. Faded in and out: an abrupt start inside the
/// cliff, so no evidence of upsampling. An abrupt start inside the
/// analyzed window is a step, whose broadband splatter reads as content.
#[test]
fn a_gradual_mastering_roll_off_is_only_suspect() {
fn a_gradual_mastering_roll_off_is_not_flagged_as_fake() {
let dir = TempDir::new("lpf");
let mut signal = shape_spectrum(&full_band_noise(HIRES_FRAMES, 0), HIRES_SR, |freq| {
let gain_db = if freq > 16_000.0 {
@@ -656,15 +750,15 @@ fn a_gradual_mastering_roll_off_is_only_suspect() {
HIRES_SR,
24,
));
assert_eq!(r.verdict, VERDICT_FAKE);
assert_eq!(r.verdict, VERDICT_BAND_LIMITED);
assert_eq!(r.brickwall_hz, 0.0);
assert_eq!(r.confidence, CONFIDENCE_SUSPECT);
assert_eq!(r.confidence, "");
}
/// 24-bit source samples (so the depth test passes) taken to 176.4 kHz by the
/// cheap upsamplers whose output is an exact, checkable pattern.
#[test]
fn integer_upsampling_artifacts_are_certain() {
fn integer_patterns_are_certain_but_spectral_imaging_is_likely() {
let source = pcm_using_bits(24, 24, CD_SOURCE_FRAMES + 1, 1);
let (mut hold, mut line, mut zero_stuffed) = (Vec::new(), Vec::new(), Vec::new());
for pair in source.windows(2) {
@@ -684,7 +778,12 @@ fn integer_upsampling_artifacts_are_certain() {
let r = check(&flac(&dir, &format!("{name}.flac"), &samples, HIRES_SR, 24));
assert_eq!(r.upsampling_artifact, artifact, "{name}");
assert_eq!(r.verdict, VERDICT_FAKE, "{name}");
assert_eq!(r.confidence, CONFIDENCE_CERTAIN, "{name}");
let confidence = if artifact == ARTIFACT_IMAGING {
CONFIDENCE_LIKELY
} else {
CONFIDENCE_CERTAIN
};
assert_eq!(r.confidence, confidence, "{name}");
}
}