Skip to main content

sva_engine/cache/
entry_bytes.rs

1// Concern: the bytes one persisted entry is, its samples spelled by a codec | Non-concern: where the bytes live (disk.rs, pack.rs), the label encoding (label.rs) | IO: (Buffer, traces, label) <-> bytes
2
3use sva_formula::filter::Shape;
4use sva_samples::{AutomationFrame, Buffer, FilterTrace, Label};
5
6use super::{Entry, Payload, Tier};
7
8/// A codec id after the magic makes an older file a miss rather than a misread.
9const MAGIC: &[u8; 4] = b"RBC6";
10
11/// A persisted entry's samples. The id is written into every entry, and a store reading an
12/// entry another codec wrote answers a miss: the bytes are not wrong, only not its own.
13///
14/// Only an exact codec, one whose round trip is bit-identical, may implement this until keyed,
15/// opt-in approximate storage exists: a lossy one would change the value a key names.
16pub trait SampleCodec: Sync {
17    fn id(&self) -> u8;
18
19    fn encode(&self, buffer: &Buffer, out: &mut Vec<u8>);
20
21    /// `None` unless `bytes` is exactly `width` planes of `samples` each.
22    fn decode(&self, bytes: &[u8], rate: u32, width: usize, samples: usize) -> Option<Buffer>;
23}
24
25pub struct RawF64;
26
27impl SampleCodec for RawF64 {
28    fn id(&self) -> u8 {
29        0
30    }
31
32    fn encode(&self, buffer: &Buffer, out: &mut Vec<u8>) {
33        out.reserve(buffer.len() * buffer.width * size_of::<f64>());
34        for c in 0..buffer.width {
35            for &s in buffer.plane(c) {
36                out.extend_from_slice(&s.to_le_bytes());
37            }
38        }
39    }
40
41    fn decode(&self, bytes: &[u8], rate: u32, width: usize, samples: usize) -> Option<Buffer> {
42        let plane = samples.checked_mul(size_of::<f64>())?;
43        if bytes.len() != plane.checked_mul(width)? {
44            return None;
45        }
46        let planes = (0..width)
47            .map(|c| {
48                bytes[c * plane..(c + 1) * plane]
49                    .chunks_exact(8)
50                    .map(|b| f64::from_le_bytes(b.try_into().expect("chunks_exact(8)")))
51                    .collect()
52            })
53            .collect();
54        Some(Buffer::of_planes(rate, planes))
55    }
56}
57
58#[derive(Debug, PartialEq, Eq)]
59pub(super) enum Unread {
60    OtherCodec,
61    Damaged,
62}
63
64pub(super) struct Writer(pub Vec<u8>);
65
66impl Writer {
67    pub(super) fn u32(&mut self, v: u32) {
68        self.0.extend_from_slice(&v.to_le_bytes());
69    }
70    fn u64(&mut self, v: u64) {
71        self.0.extend_from_slice(&v.to_le_bytes());
72    }
73    pub(super) fn f64(&mut self, v: f64) {
74        self.0.extend_from_slice(&v.to_le_bytes());
75    }
76}
77
78pub(super) struct Reader<'a>(pub &'a [u8]);
79
80impl<'a> Reader<'a> {
81    pub(super) fn take(&mut self, n: usize) -> Option<&'a [u8]> {
82        let (head, rest) = self.0.split_at_checked(n)?;
83        self.0 = rest;
84        Some(head)
85    }
86    pub(super) fn u32(&mut self) -> Option<u32> {
87        Some(u32::from_le_bytes(self.take(4)?.try_into().ok()?))
88    }
89    fn u64(&mut self) -> Option<u64> {
90        Some(u64::from_le_bytes(self.take(8)?.try_into().ok()?))
91    }
92    pub(super) fn f64(&mut self) -> Option<f64> {
93        Some(f64::from_le_bytes(self.take(8)?.try_into().ok()?))
94    }
95}
96
97pub(super) fn encode(
98    buffer: &Buffer,
99    traces: &[FilterTrace],
100    label: Option<&Label>,
101    codec: &dyn SampleCodec,
102) -> Vec<u8> {
103    let mut w = Writer(Vec::with_capacity(buffer.len() * buffer.width * 8 + 64));
104    w.0.extend_from_slice(MAGIC);
105    w.0.push(codec.id());
106    w.u32(buffer.rate);
107    w.u64(buffer.len() as u64);
108    w.u32(buffer.width as u32);
109    write_traces(&mut w, traces);
110    super::label::write(&mut w, label);
111    codec.encode(buffer, &mut w.0);
112    w.0
113}
114
115fn write_traces(w: &mut Writer, traces: &[FilterTrace]) {
116    w.u32(traces.len() as u32);
117    for trace in traces {
118        w.u32(trace.site as u32);
119        w.u32(trace.channel.map_or(u32::MAX, |c| c as u32));
120        w.0.push(u8::from(trace.clamped));
121        w.u32(trace.shape.len() as u32);
122        w.0.extend_from_slice(trace.shape.as_bytes());
123        w.f64(trace.trace_secs);
124        w.u32(trace.frames.len() as u32);
125        for f in &trace.frames {
126            w.f64(f.t_secs);
127            w.f64(f.cutoff);
128            w.f64(f.q);
129            w.f64(f.gain_db);
130        }
131    }
132}
133
134/// Every field is checked against what the caller asked for, not merely parsed: the hash
135/// already rules out a mismatch, so one here means the hash's domain is wrong and the only
136/// safe answer is to re-render.
137pub(super) fn decode(
138    bytes: &[u8],
139    node: &str,
140    (rate, width, samples): (u32, usize, usize),
141    codec: &dyn SampleCodec,
142) -> Result<Entry, Unread> {
143    let mut r = Reader(bytes);
144    if r.take(4) != Some(MAGIC) {
145        return Err(Unread::Damaged);
146    }
147    match r.take(1) {
148        Some([id]) if *id == codec.id() => {}
149        Some(_) => return Err(Unread::OtherCodec),
150        None => return Err(Unread::Damaged),
151    }
152    decode_samples(&mut r, node, rate, width, samples, codec).ok_or(Unread::Damaged)
153}
154
155fn decode_samples(
156    r: &mut Reader,
157    node: &str,
158    rate: u32,
159    width: usize,
160    samples: usize,
161    codec: &dyn SampleCodec,
162) -> Option<Entry> {
163    if r.u32()? != rate || r.u64()? != samples as u64 || r.u32()? != width as u32 {
164        return None;
165    }
166    let traces = read_traces(r, node)?;
167    let label = super::label::read(r)?;
168    let buffer = codec.decode(r.0, rate, width, samples)?;
169    Some(Entry {
170        payload: Payload::Samples(Box::new(buffer)),
171        traces,
172        label,
173        tier: Tier::Persistent,
174    })
175}
176
177fn read_traces(r: &mut Reader, node: &str) -> Option<Vec<FilterTrace>> {
178    let mut traces = Vec::new();
179    for _ in 0..r.u32()? {
180        let site = r.u32()? as usize;
181        let channel = match r.u32()? {
182            u32::MAX => None,
183            c => Some(c as usize),
184        };
185        let clamped = r.take(1)?[0] == 1;
186        let name_len = r.u32()? as usize;
187        let shape = Shape::from_name(std::str::from_utf8(r.take(name_len)?).ok()?)?.name();
188        let trace_secs = r.f64()?;
189        let frame_count = r.u32()? as usize;
190        let mut frames = Vec::with_capacity(frame_count.min(1 << 20));
191        for _ in 0..frame_count {
192            frames.push(AutomationFrame {
193                t_secs: r.f64()?,
194                cutoff: r.f64()?,
195                q: r.f64()?,
196                gain_db: r.f64()?,
197            });
198        }
199        traces.push(FilterTrace {
200            node: node.to_string(),
201            site,
202            channel,
203            shape,
204            clamped,
205            trace_secs,
206            frames,
207        });
208    }
209    Some(traces)
210}