Skip to main content

qualia_core_db/audio/
audio_sidecar_link.rs

1//! Cold-path audio sidecar linker — bake, hash, mmap-ready bytes, optional native file write.
2//!
3//! Links baked tensors to `q42:hasSpectralSheet` NQuin objects at ingest.
4
5use crate::audio::audio_spectral_sheet::preview_bins_from_tensor;
6use crate::audio::audio_spectral_sheet::{
7    copy_sidecar_frame_to_preview_bins, SPECTRAL_PREVIEW_BINS,
8};
9use crate::audio::cqt_bake::bake_cqt_sidecar_from_preview;
10use crate::audio::stft_bake::{bake_stft_sidecar_from_preview, StftBakeError};
11use crate::tensor::bake_pipeline::{audio_sidecar_relpath, PRED_HAS_SPECTRAL_SHEET};
12use crate::tensor::Tensor10D;
13use crate::NQuin;
14
15/// FNV-1a over preview bins — stable sidecar filename hash.
16#[inline]
17pub fn sidecar_content_hash(preview: &[f32; SPECTRAL_PREVIEW_BINS]) -> u64 {
18    let mut h: u64 = 0xcbf2_9ce4_8422_2325;
19    for &b in preview {
20        let bits = b.to_bits() as u64;
21        h ^= bits;
22        h = h.wrapping_mul(0x0100_0000_01b3);
23    }
24    h
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum SidecarBakeKind {
29    Stft,
30    Cqt,
31}
32
33/// Bake sidecar bytes into `out`; returns (content_hash, bytes_written).
34pub fn bake_audio_sidecar_into(
35    preview: &[f32; SPECTRAL_PREVIEW_BINS],
36    frame_count: u32,
37    kind: SidecarBakeKind,
38    out: &mut [u8],
39) -> Result<(u64, usize), StftBakeError> {
40    let hash = sidecar_content_hash(preview);
41    let n = match kind {
42        SidecarBakeKind::Stft => bake_stft_sidecar_from_preview(preview, frame_count, 48_000, out)?,
43        SidecarBakeKind::Cqt => bake_cqt_sidecar_from_preview(preview, frame_count, 48_000, out)?,
44    };
45    Ok((hash, n))
46}
47
48/// Emit linker NQuin: subject → `q42:hasSpectralSheet` → sheet index (lower 60 bits of hash).
49#[inline]
50pub fn compile_spectral_sheet_quin(subject_hash: u64, sheet_index: u32) -> NQuin {
51    let mut q = NQuin::default();
52    q.subject = subject_hash;
53    q.predicate = PRED_HAS_SPECTRAL_SHEET;
54    q.object = (sheet_index as u64) & 0x0FFF_FFFF_FFFF_FFFF;
55    q
56}
57
58/// Write relative path `spectral/audio/{hash:016x}.bin` into caller buffer; returns length.
59#[inline]
60pub fn format_sidecar_relpath(content_hash: u64, out: &mut [u8]) -> usize {
61    audio_sidecar_relpath(content_hash, out)
62}
63
64/// Bake from tensor preview + link quin for cold ingest pipelines.
65pub fn link_tensor_audio_sidecar(
66    t: &Tensor10D,
67    subject_hash: u64,
68    frame_count: u32,
69    kind: SidecarBakeKind,
70    out_bytes: &mut [u8],
71) -> Result<(NQuin, u64, usize), StftBakeError> {
72    let preview = preview_bins_from_tensor(t);
73    let (hash, n) = bake_audio_sidecar_into(&preview, frame_count, kind, out_bytes)?;
74    let index = (hash & 0xffff_ffff) as u32;
75    let quin = compile_spectral_sheet_quin(subject_hash, index);
76    Ok((quin, hash, n))
77}
78
79/// Hot path: overlay mmap/CQT/STFT column into uniform preview bins.
80#[inline]
81pub fn enrich_preview_from_sidecar(
82    sidecar_bytes: &[u8],
83    frame_index: u32,
84    preview: &mut [f32; SPECTRAL_PREVIEW_BINS],
85) -> bool {
86    copy_sidecar_frame_to_preview_bins(sidecar_bytes, frame_index, preview)
87}
88
89#[cfg(not(target_arch = "wasm32"))]
90pub fn write_sidecar_file(
91    storage_root: &std::path::Path,
92    content_hash: u64,
93    bytes: &[u8],
94) -> std::io::Result<std::path::PathBuf> {
95    use std::io::Write;
96    let mut rel = [0u8; 64];
97    let n = format_sidecar_relpath(content_hash, &mut rel);
98    let rel_str = std::str::from_utf8(&rel[..n])
99        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
100    let path = storage_root.join(rel_str);
101    if let Some(parent) = path.parent() {
102        std::fs::create_dir_all(parent)?;
103    }
104    let mut f = std::fs::File::create(&path)?;
105    f.write_all(bytes)?;
106    Ok(path)
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn hash_stable_for_same_preview() {
115        let p = [0.5_f32; SPECTRAL_PREVIEW_BINS];
116        assert_eq!(sidecar_content_hash(&p), sidecar_content_hash(&p));
117    }
118
119    #[test]
120    fn link_emits_sheet_quin() {
121        let t = Tensor10D::new(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.55);
122        let mut buf = [0u8; 20 + 64 * 16 * 4];
123        let (q, _, n) =
124            link_tensor_audio_sidecar(&t, 0xabc, 16, SidecarBakeKind::Cqt, &mut buf).unwrap();
125        assert!(n > 20);
126        assert_eq!(q.subject, 0xabc);
127        assert_eq!(q.predicate, PRED_HAS_SPECTRAL_SHEET);
128    }
129
130    #[test]
131    fn enrich_preview_from_baked_sidecar() {
132        let preview = [0.7_f32; SPECTRAL_PREVIEW_BINS];
133        let mut buf = [0u8; 20 + 64 * 4 * 4];
134        bake_stft_sidecar_from_preview(&preview, 4, 48_000, &mut buf).unwrap();
135        let mut out = [0.0_f32; SPECTRAL_PREVIEW_BINS];
136        assert!(enrich_preview_from_sidecar(&buf, 2, &mut out));
137        assert!(out[0] > 0.0);
138    }
139}