helena 0.1.0

Core types and component interfaces for helena, a latent data-to-waveform generation platform.
Documentation
//! Conformance laws (A3): the contracts the type system cannot carry,
//! stated once, generically, and instantiated as tests by every
//! implementation.
//!
//! A law returns `Ok(())` or an [`Error::Validation`] describing the first
//! divergence precisely — laws run in test suites, where a rich message *is*
//! the interface. Drivers supply their own cases (fixed vectors or proptest
//! strategies); the law owns only the statement.

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};

/// **The streaming law** (PRD NFR-004; the RAVE-lineage cached-convolution
/// invariant, promoted from one crate's tests to a workspace contract):
///
/// > Cutting a latent into `spec().block`-sized blocks and concatenating the
/// > session's `push` outputs, followed by `finish`, is **bit-exactly** the
/// > offline `decode` of the whole latent.
///
/// Bit-exact means `f32` equality, not tolerance: streaming is a *view* of
/// the same computation, not an approximation of it. The latent's frame
/// count must be a whole number of blocks (the driver's obligation — pick
/// case sizes accordingly).
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(())
}

/// **The roundtrip law**: `decode ∘ encode` preserves the clip's clock,
/// layout, and length exactly, and its samples within `tolerance`
/// (max-absolute difference). The tolerance is the codec's stated
/// reconstruction quality — `0.0` for an analytic perfect-reconstruction
/// codec, looser for a learned one.
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(())
}

/// **The fingerprint law**: fingerprinting is deterministic — two
/// serializations of the same value digest identically.
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};

    /// A reference decoder: each latent frame's mean becomes `stride` output
    /// samples, scaled by tanh (bounded, deterministic, causal — its stream
    /// state is trivially empty, so the law must hold exactly).
    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()))
        }
    }

    /// The session owns the facts it needs — real implementations own their
    /// per-stream state the same way (never borrowed from a twin).
    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();
    }
}