use crate::error::{Error, Result};
use crate::latent::{Latent, LatentKind};
use crate::seams::{AudioCodec, AudioEncoder};
use crate::signal::{Pcm, PcmBlock};
use crate::stream::{StreamSession, StreamingDecoder};
pub fn streaming_matches_offline<K, D>(decoder: &D, latent: &Latent<K>) -> Result<()>
where
K: LatentKind,
D: StreamingDecoder<K>,
{
let spec = decoder.spec();
if latent.time_base() != spec.time_base() {
return Err(Error::ClockMismatch {
left: latent.time_base(),
right: spec.time_base(),
});
}
let offline = decoder.decode(latent)?;
let mut session = decoder.open();
let mut emitted = Vec::new();
for block in latent.blocks(spec.block())? {
emitted.push(session.push(&block)?);
}
emitted.push(session.finish()?);
let streamed = PcmBlock::concat(emitted)?;
if streamed.rate() != offline.rate() || streamed.layout() != offline.layout() {
return Err(Error::validation(format!(
"streaming law: clock/layout diverged ({} {} vs {} {})",
streamed.rate(),
streamed.layout(),
offline.rate(),
offline.layout()
)));
}
if streamed.samples().len() != offline.samples().len() {
return Err(Error::validation(format!(
"streaming law: length diverged ({} streamed vs {} offline samples)",
streamed.samples().len(),
offline.samples().len()
)));
}
if let Some(i) = (0..streamed.samples().len())
.find(|&i| streamed.samples()[i].to_bits() != offline.samples()[i].to_bits())
{
return Err(Error::validation(format!(
"streaming law: first divergence at sample {i}: {} streamed vs {} offline",
streamed.samples()[i],
offline.samples()[i]
)));
}
Ok(())
}
pub fn codec_roundtrip_stable<K, C>(codec: &C, pcm: &Pcm, tolerance: f32) -> Result<()>
where
K: LatentKind,
C: AudioCodec<K>,
{
let latent = AudioEncoder::<K>::encode(codec, pcm)?;
if latent.time_base() != codec.time_base() {
return Err(Error::ClockMismatch {
left: latent.time_base(),
right: codec.time_base(),
});
}
let decoded = codec.decode(&latent)?;
if decoded.rate() != pcm.rate() || decoded.layout() != pcm.layout() {
return Err(Error::validation(format!(
"roundtrip law: clock/layout diverged ({} {} vs {} {})",
decoded.rate(),
decoded.layout(),
pcm.rate(),
pcm.layout()
)));
}
if decoded.frames() != pcm.frames() {
return Err(Error::validation(format!(
"roundtrip law: length diverged ({} vs {} frames)",
decoded.frames(),
pcm.frames()
)));
}
let worst = decoded
.samples()
.iter()
.zip(pcm.samples())
.map(|(a, b)| (a - b).abs())
.fold(0.0f32, f32::max);
if worst > tolerance {
return Err(Error::validation(format!(
"roundtrip law: max reconstruction error {worst} exceeds tolerance {tolerance}"
)));
}
Ok(())
}
pub fn fingerprint_stable<T: serde::Serialize>(value: &T) -> Result<()> {
let a = crate::provenance::Fingerprint::of(value)?;
let b = crate::provenance::Fingerprint::of(value)?;
if a != b {
return Err(Error::validation(format!(
"fingerprint law: same value digested to {a} and {b}"
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU32;
use super::*;
use crate::latent::{Continuous, FrameSeq, LatentBlock};
use crate::seams::{AudioDecoder, Clocked};
use crate::signal::ChannelLayout;
use crate::stream::StreamSpec;
use crate::time::{Frames, SampleRate, TimeBase};
struct MeanHold {
time_base: TimeBase,
}
fn render(time_base: TimeBase, seq: &FrameSeq) -> Pcm {
let stride = time_base.stride().get() as usize;
let mut samples = Vec::with_capacity(seq.frames() * stride);
for frame in 0..seq.frames() {
let row = seq.frame(frame);
let mean = row.iter().sum::<f32>() / row.len() as f32;
samples.extend(std::iter::repeat_n(mean.tanh(), stride));
}
Pcm::new(time_base.sample_rate(), ChannelLayout::Mono, samples).unwrap()
}
impl Clocked for MeanHold {
fn time_base(&self) -> TimeBase {
self.time_base
}
}
impl AudioDecoder<Continuous> for MeanHold {
fn decode(&self, latent: &Latent<Continuous>) -> Result<Pcm> {
Ok(render(self.time_base, latent.repr()))
}
}
struct MeanHoldSession {
time_base: TimeBase,
}
impl StreamSession<Continuous> for MeanHoldSession {
fn push(&mut self, block: &LatentBlock<Continuous>) -> Result<PcmBlock> {
Ok(PcmBlock::new(render(self.time_base, block.repr())))
}
fn finish(self) -> Result<PcmBlock> {
let rate = self.time_base.sample_rate();
Ok(PcmBlock::new(
Pcm::new(rate, ChannelLayout::Mono, vec![]).unwrap(),
))
}
}
impl StreamingDecoder<Continuous> for MeanHold {
type Session = MeanHoldSession;
fn spec(&self) -> StreamSpec {
StreamSpec::new(Frames(2), Frames(0), self.time_base).unwrap()
}
fn open(&self) -> Self::Session {
MeanHoldSession {
time_base: self.time_base,
}
}
}
fn decoder() -> MeanHold {
MeanHold {
time_base: TimeBase::new(
SampleRate::try_from(8_000).unwrap(),
NonZeroU32::new(4).unwrap(),
),
}
}
#[test]
fn streaming_law_accepts_a_faithful_decoder() {
let d = decoder();
let seq = FrameSeq::new(6, 3, (0..18).map(|i| i as f32 * 0.1).collect::<Vec<_>>()).unwrap();
let latent = Latent::new(seq, d.time_base);
streaming_matches_offline(&d, &latent).unwrap();
}
#[test]
fn streaming_law_rejects_a_clock_mismatch() {
let d = decoder();
let other = TimeBase::new(
SampleRate::try_from(16_000).unwrap(),
NonZeroU32::new(4).unwrap(),
);
let latent = Latent::new(FrameSeq::new(2, 1, vec![0.0, 0.0]).unwrap(), other);
assert!(matches!(
streaming_matches_offline(&d, &latent),
Err(Error::ClockMismatch { .. })
));
}
#[test]
fn fingerprint_law_holds_for_vocabulary_values() {
fingerprint_stable(&"anything serializable").unwrap();
}
}