Skip to main content

sva_engine/cache/
codec.rs

1// Concern: one stored node value, tagged with the store's format, and one staged run, as bytes and back | Non-concern: where the bytes live or when | IO: (Stored or Buffer) <-> bytes, a bad one None
2
3use sva_formula::Codomain;
4use sva_samples::{
5    BLOCK, Buffer, Detail, Dropped, Extent, Grid, Label, PSYCHOACOUSTIC_V1, Rule, Source, block_end,
6};
7
8use sva_formula::{FOURIER_DUAL_RULES_VERSION, Hash};
9
10use super::Stored;
11use super::stored::{Cut, Header, Laid, Samples};
12
13/// Bumped by, and only by, a change to a stored value's bytes or to the key it is stored under.
14pub const STORE_FORMAT: u32 = 46;
15
16/// Every entry opens with its format and Fourier dual rules version, so one another wrote is
17/// never read as a value.
18fn entry_tag() -> Vec<u8> {
19    [
20        &b"SVAh"[..],
21        &STORE_FORMAT.to_le_bytes(),
22        &FOURIER_DUAL_RULES_VERSION.to_le_bytes(),
23    ]
24    .concat()
25}
26
27const STAGED_RUN: &[u8; 4] = b"SVAc";
28
29/// A run's chunks are its samples within each block of the grid: what one checksum covers,
30/// and the least a read loads. Samples stay f64, as the render computes them: a stored value
31/// is read by others, and f32 would change the bits of every value that reads it.
32fn chunks(start: i64, len: usize) -> Option<usize> {
33    if len == 0 {
34        return Some(0);
35    }
36    let last = start.checked_add(i64::try_from(len).ok()? - 1)?;
37    usize::try_from(last.div_euclid(BLOCK) - start.div_euclid(BLOCK) + 1).ok()
38}
39
40/// How many of a run's samples come before its chunk `k`.
41fn before(run: &Laid, k: usize) -> usize {
42    let edge = match k {
43        0 => run.start,
44        k => (run.start.div_euclid(BLOCK) + k as i64).saturating_mul(BLOCK),
45    };
46    (edge.min(run.start + run.len as i64) - run.start) as usize
47}
48
49/// The chunks of `run` that hold `over`, met with the run.
50pub(crate) fn chunks_over(run: &Laid, over: Extent) -> (usize, usize) {
51    let first = run.start.div_euclid(BLOCK);
52    let at = |n: i64| (n.div_euclid(BLOCK) - first) as usize;
53    (at(over.start), at(over.end - 1) + 1)
54}
55
56/// A header's length, then the header, then each run's samples chunk by chunk, every plane of
57/// a chunk together, so a stretch of a run is one read.
58pub(crate) fn entry(head: &Header, runs: &[Buffer]) -> Vec<u8> {
59    let mut laid = Vec::new();
60    let mut body = Vec::new();
61    for run in runs {
62        let mut sums = Vec::new();
63        let at = body.len() as u64;
64        let mut from = 0;
65        while from < run.len() {
66            let to = (block_end(run.start + from as i64) - run.start).min(run.len() as i64);
67            let to = to as usize;
68            let chunk = body.len();
69            for plane in &run.planes {
70                for sample in &plane[from..to] {
71                    word(&mut body, sample.to_bits());
72                }
73            }
74            sums.push(checksum(&body[chunk..]));
75            from = to;
76        }
77        laid.push(Laid {
78            rate: run.rate,
79            start: run.start,
80            width: run.width(),
81            len: run.len(),
82            at,
83            sums,
84        });
85    }
86    let head = sealed(header(head, &laid));
87    let mut out = Vec::with_capacity(8 + head.len() + body.len());
88    word(&mut out, head.len() as u64);
89    out.extend_from_slice(&head);
90    out.extend_from_slice(&body);
91    out
92}
93
94fn header(head: &Header, laid: &[Laid]) -> Vec<u8> {
95    let stored = head.stored();
96    let mut out = entry_tag();
97    word(&mut out, stored.key.0);
98    word(&mut out, stored.key.1);
99    word(&mut out, stored.identity.0);
100    word(&mut out, stored.identity.1);
101    labelled(&mut out, &stored.label);
102    out.push(stored.width);
103    out.push(match stored.codomain {
104        Codomain::Real => 0,
105        Codomain::Complex => 1,
106    });
107    maybe(&mut out, stored.rate.map(u64::from));
108    word(&mut out, u64::from(stored.grid.rate));
109    out.extend_from_slice(&stored.grid.a.to_le_bytes());
110    out.extend_from_slice(&stored.grid.d.to_le_bytes());
111    word(&mut out, stored.support.start as u64);
112    word(&mut out, stored.support.end as u64);
113    word(&mut out, stored.moved.to_bits());
114    out.push(u8::from(stored.readable));
115    out.push(u8::from(stored.sampled));
116    match stored.cut {
117        None => out.push(0),
118        Some(cut) => {
119            out.push(1);
120            word(&mut out, cut.silence_threshold_dbfs.to_bits());
121            maybe(
122                &mut out,
123                cut.treated_as_silent_from_sample.map(|at| at as u64),
124            );
125        }
126    }
127    match head.samples() {
128        &Samples::Of { key, by } => {
129            out.push(1);
130            word(&mut out, key.0);
131            word(&mut out, key.1);
132            word(&mut out, by as u64);
133        }
134        _ => out.push(0),
135    }
136    word(&mut out, laid.len() as u64);
137    for run in laid {
138        word(&mut out, u64::from(run.rate));
139        word(&mut out, run.start as u64);
140        word(&mut out, run.width as u64);
141        word(&mut out, run.len as u64);
142        for sum in &run.sums {
143            word(&mut out, *sum);
144        }
145    }
146    out
147}
148
149/// How many of an entry's first bytes its header takes, off its first eight.
150pub(crate) fn head_len(first: &[u8]) -> Option<usize> {
151    let len = u64::from_le_bytes(first.get(..8)?.try_into().ok()?);
152    8usize.checked_add(usize::try_from(len).ok()?)
153}
154
155/// The meta an entry's header states, its samples laid in `file`, and the entry's length; a
156/// truncated, corrupt or foreign header is `None`, never a partial value.
157pub(crate) fn read_head(bytes: &[u8], file: Hash) -> Option<(Header, u64)> {
158    let span = head_len(bytes)?;
159    let mut r = Reader(opened(bytes.get(8..span)?, &entry_tag())?);
160    let key = Hash(r.word()?, r.word()?);
161    let identity = Hash(r.word()?, r.word()?);
162    let label = r.label()?;
163    let width = r.byte()?;
164    let codomain = match r.byte()? {
165        0 => Codomain::Real,
166        1 => Codomain::Complex,
167        _ => return None,
168    };
169    let rate = match r.maybe()? {
170        Some(rate) => Some(u32::try_from(rate).ok()?),
171        None => None,
172    };
173    let grid = Grid {
174        rate: u32::try_from(r.word()?).ok()?,
175        a: r.wide()? as i128,
176        d: r.wide()? as i128,
177    };
178    let (start, end) = (r.word()? as i64, r.word()? as i64);
179    let support = (start <= end).then(|| Extent::new(start, end))?;
180    let moved = f64::from_bits(r.word()?);
181    let readable = r.flag()?;
182    let sampled = r.flag()?;
183    let cut = match r.byte()? {
184        0 => None,
185        1 => Some(Cut {
186            silence_threshold_dbfs: f64::from_bits(r.word()?),
187            treated_as_silent_from_sample: r.maybe()?.map(|at| at as i64),
188        }),
189        _ => return None,
190    };
191    let of = match r.byte()? {
192        0 => None,
193        1 => Some(Samples::Of {
194            key: Hash(r.word()?, r.word()?),
195            by: r.word()? as i64,
196        }),
197        _ => return None,
198    };
199    let count = r.word()? as usize;
200    let mut runs = Vec::new();
201    let mut at = span as u64;
202    for _ in 0..count.min(r.0.len()) {
203        let rate = u32::try_from(r.word()?).ok()?;
204        let start = r.word()? as i64;
205        let (width, len) = (r.word()? as usize, r.word()? as usize);
206        let count = chunks(start, len)?;
207        let sums = (0..count.min(r.0.len()))
208            .map(|_| r.word())
209            .collect::<Option<Vec<u64>>>()?;
210        if sums.len() != count {
211            return None;
212        }
213        runs.push(Laid {
214            rate,
215            start,
216            width,
217            len,
218            at,
219            sums,
220        });
221        at = at.checked_add((width.checked_mul(len)?.checked_mul(8)?) as u64)?;
222    }
223    let samples = match (of, runs.is_empty()) {
224        (Some(of), true) => of,
225        (Some(_), false) => return None,
226        (None, true) => Samples::None,
227        (None, false) => Samples::Entry {
228            file,
229            runs,
230            shift: 0,
231        },
232    };
233    let stored = Stored {
234        key,
235        identity,
236        label,
237        width,
238        codomain,
239        rate,
240        grid,
241        support,
242        moved,
243        readable,
244        sampled,
245        cut,
246        held: Vec::new(),
247    };
248    let whole = runs_whole(count, &samples) && r.0.is_empty();
249    whole.then(|| (Header::new(stored, samples), at))
250}
251
252fn runs_whole(count: usize, samples: &Samples) -> bool {
253    match samples {
254        Samples::Entry { runs, .. } => runs.len() == count,
255        _ => count == 0,
256    }
257}
258
259/// Where in its file a run's chunks `from..to` lie, and how many bytes.
260pub(crate) fn span_of(run: &Laid, from: usize, to: usize) -> (u64, u64) {
261    let bytes = |k: usize| (before(run, k) * run.width * 8) as u64;
262    (run.at + bytes(from), bytes(to) - bytes(from))
263}
264
265/// A run's chunks `from..to` off the bytes `span_of` names; `None` where a checksum fails.
266pub(crate) fn read_chunks(bytes: &[u8], run: &Laid, from: usize, to: usize) -> Option<Buffer> {
267    let first = before(run, from);
268    let len = before(run, to).checked_sub(first)?;
269    if bytes.len() != len * run.width * 8 {
270        return None;
271    }
272    let mut planes = vec![Vec::with_capacity(len); run.width];
273    let mut at = 0;
274    for (k, sum) in run.sums[from..to].iter().enumerate() {
275        let n = before(run, from + k + 1) - before(run, from + k);
276        let chunk = &bytes[at..at + n * run.width * 8];
277        if checksum(chunk) != *sum {
278            return None;
279        }
280        for (c, plane) in planes.iter_mut().enumerate() {
281            let own = &chunk[c * n * 8..(c + 1) * n * 8];
282            plane.extend(
283                own.chunks_exact(8).map(|b| {
284                    f64::from_bits(u64::from_le_bytes(b.try_into().expect("eight bytes")))
285                }),
286            );
287        }
288        at += chunk.len();
289    }
290    let mut out = Buffer::of_planes(run.rate, planes);
291    out.start = run.start + first as i64;
292    Some(out)
293}
294
295/// Every run an entry under `key` lays out; `None` where any is corrupt.
296pub(crate) fn read_runs(bytes: &[u8], key: Hash) -> Option<Vec<Buffer>> {
297    let (head, _) = read_head(bytes, key)?;
298    let Samples::Entry { runs, .. } = head.samples() else {
299        return Some(Vec::new());
300    };
301    let read = |run: &Laid| {
302        let n = chunks(run.start, run.len)?;
303        let (at, len) = span_of(run, 0, n);
304        let span = bytes.get(at as usize..(at + len) as usize)?;
305        read_chunks(span, run, 0, n)
306    };
307    runs.iter().map(read).collect()
308}
309
310pub(crate) fn chunk(samples: &Buffer) -> Vec<u8> {
311    let mut out = STAGED_RUN.to_vec();
312    segments(&mut out, std::slice::from_ref(samples));
313    sealed(out)
314}
315
316pub(crate) fn read_chunk(bytes: &[u8]) -> Option<Buffer> {
317    let mut r = Reader(opened(bytes, STAGED_RUN)?);
318    let mut parts = r.segments()?;
319    (r.0.is_empty() && parts.len() == 1).then(|| parts.remove(0))
320}
321
322fn sealed(mut out: Vec<u8>) -> Vec<u8> {
323    let sum = checksum(&out);
324    word(&mut out, sum);
325    out
326}
327
328/// The body past `magic`, where the checksum holds.
329fn opened<'b>(bytes: &'b [u8], magic: &[u8]) -> Option<&'b [u8]> {
330    let body = bytes.len().checked_sub(8)?;
331    let (body, sum) = bytes.split_at(body);
332    (checksum(body).to_le_bytes() == sum && body.starts_with(magic)).then(|| &body[magic.len()..])
333}
334
335fn segments(out: &mut Vec<u8>, parts: &[Buffer]) {
336    word(out, parts.len() as u64);
337    for part in parts {
338        word(out, u64::from(part.rate));
339        word(out, part.start as u64);
340        word(out, part.width() as u64);
341        word(out, part.len() as u64);
342        for plane in &part.planes {
343            for sample in plane {
344                word(out, sample.to_bits());
345            }
346        }
347    }
348}
349
350fn checksum(bytes: &[u8]) -> u64 {
351    bytes.iter().fold(0xcbf2_9ce4_8422_2325, |h, b| {
352        (h ^ u64::from(*b)).wrapping_mul(0x0100_0000_01b3)
353    })
354}
355
356fn word(out: &mut Vec<u8>, w: u64) {
357    out.extend_from_slice(&w.to_le_bytes());
358}
359
360fn float(out: &mut Vec<u8>, v: Option<f64>) {
361    maybe(out, v.map(f64::to_bits));
362}
363
364fn maybe(out: &mut Vec<u8>, w: Option<u64>) {
365    match w {
366        None => out.push(0),
367        Some(w) => {
368            out.push(1);
369            word(out, w);
370        }
371    }
372}
373
374fn text(out: &mut Vec<u8>, s: &str) {
375    word(out, s.len() as u64);
376    out.extend_from_slice(s.as_bytes());
377}
378
379fn labelled(out: &mut Vec<u8>, label: &Label) {
380    out.push(match label.source {
381        Source::Exact => 0,
382        Source::Measured => 1,
383    });
384    text(out, label.profile);
385    word(out, u64::from(label.rate));
386    detailed(out, &label.detail);
387    float(out, label.moved);
388    match &label.cutting_below_silence_threshold {
389        None => out.push(0),
390        Some(cutting) => {
391            out.push(1);
392            word(out, cutting.silence_threshold_dbfs.to_bits());
393            word(out, cutting.treated_as_silent_from_sample.len() as u64);
394            for (node, at) in &cutting.treated_as_silent_from_sample {
395                text(out, node);
396                word(out, *at as u64);
397            }
398        }
399    }
400}
401
402fn detailed(out: &mut Vec<u8>, detail: &Detail) {
403    let rule = |out: &mut Vec<u8>, rule: &Rule| text(out, rule.as_str());
404    match detail {
405        Detail::Lines {
406            rule: r,
407            summed,
408            dropped,
409            dropped_more,
410            tail_db,
411        } => {
412            out.push(0);
413            rule(out, r);
414            word(out, *summed as u64);
415            word(out, dropped.len() as u64);
416            for d in dropped {
417                word(out, d.hz.to_bits());
418                word(out, d.db.to_bits());
419            }
420            word(out, *dropped_more as u64);
421            float(out, *tail_db);
422        }
423        Detail::Continuous { rule: r } => {
424            out.push(1);
425            rule(out, r);
426        }
427        Detail::Cropped { rule: r, tail_db } => {
428            out.push(2);
429            rule(out, r);
430            float(out, *tail_db);
431        }
432        Detail::Point {
433            rule: r,
434            alias_db,
435            tail_db,
436        } => {
437            out.push(3);
438            rule(out, r);
439            float(out, *alias_db);
440            float(out, *tail_db);
441        }
442        Detail::Roundtrip { rule: r, edited } => {
443            out.push(5);
444            rule(out, r);
445            out.push(u8::from(*edited));
446        }
447        Detail::Reading { rule: r } => {
448            out.push(6);
449            rule(out, r);
450        }
451        Detail::Added { parts } => {
452            out.push(7);
453            word(out, parts.len() as u64);
454            for part in parts {
455                detailed(out, part);
456            }
457        }
458    }
459}
460
461struct Reader<'b>(&'b [u8]);
462
463impl Reader<'_> {
464    fn take(&mut self, n: usize) -> Option<&[u8]> {
465        if self.0.len() < n {
466            return None;
467        }
468        let (head, rest) = self.0.split_at(n);
469        self.0 = rest;
470        Some(head)
471    }
472
473    fn byte(&mut self) -> Option<u8> {
474        self.take(1).map(|b| b[0])
475    }
476
477    fn flag(&mut self) -> Option<bool> {
478        match self.byte()? {
479            0 => Some(false),
480            1 => Some(true),
481            _ => None,
482        }
483    }
484
485    fn word(&mut self) -> Option<u64> {
486        Some(u64::from_le_bytes(self.take(8)?.try_into().ok()?))
487    }
488
489    fn segments(&mut self) -> Option<Vec<Buffer>> {
490        let count = self.word()? as usize;
491        let mut parts = Vec::new();
492        for _ in 0..count.min(self.0.len()) {
493            let rate = u32::try_from(self.word()?).ok()?;
494            let start = self.word()? as i64;
495            let (width, len) = (self.word()? as usize, self.word()? as usize);
496            if width.saturating_mul(len).saturating_mul(8) > self.0.len() {
497                return None;
498            }
499            let planes = (0..width)
500                .map(|_| (0..len).map(|_| self.word().map(f64::from_bits)).collect())
501                .collect::<Option<Vec<Vec<f64>>>>()?;
502            let mut part = Buffer::of_planes(rate, planes);
503            part.start = start;
504            parts.push(part);
505        }
506        (parts.len() == count).then_some(parts)
507    }
508
509    fn wide(&mut self) -> Option<u128> {
510        Some(u128::from_le_bytes(self.take(16)?.try_into().ok()?))
511    }
512
513    fn maybe(&mut self) -> Option<Option<u64>> {
514        match self.byte()? {
515            0 => Some(None),
516            1 => Some(Some(self.word()?)),
517            _ => None,
518        }
519    }
520
521    fn float(&mut self) -> Option<Option<f64>> {
522        Some(self.maybe()?.map(f64::from_bits))
523    }
524
525    fn text(&mut self) -> Option<&str> {
526        let len = self.word()? as usize;
527        std::str::from_utf8(self.take(len)?).ok()
528    }
529
530    fn rule(&mut self) -> Option<Rule> {
531        Rule::named(self.text()?)
532    }
533
534    fn label(&mut self) -> Option<Label> {
535        let source = match self.byte()? {
536            0 => Source::Exact,
537            1 => Source::Measured,
538            _ => return None,
539        };
540        let profile = match self.text()? {
541            name if name == PSYCHOACOUSTIC_V1.name => PSYCHOACOUSTIC_V1.name,
542            _ => return None,
543        };
544        let rate = u32::try_from(self.word()?).ok()?;
545        let detail = self.detail()?;
546        let moved = self.float()?;
547        let cutting_below_silence_threshold = match self.byte()? {
548            0 => None,
549            1 => {
550                let silence_threshold_dbfs = f64::from_bits(self.word()?);
551                let count = usize::try_from(self.word()?).ok()?;
552                let treated_as_silent_from_sample = (0..count)
553                    .map(|_| Some((self.text()?.to_string(), self.word()? as i64)))
554                    .collect::<Option<Vec<_>>>()?;
555                Some(sva_samples::CuttingBelowSilenceThreshold {
556                    silence_threshold_dbfs,
557                    treated_as_silent_from_sample,
558                })
559            }
560            _ => return None,
561        };
562        Some(Label {
563            source,
564            profile,
565            rate,
566            detail,
567            moved,
568            cutting_below_silence_threshold,
569        })
570    }
571
572    fn detail(&mut self) -> Option<Detail> {
573        Some(match self.byte()? {
574            0 => {
575                let rule = self.rule()?;
576                let summed = self.word()? as usize;
577                let count = self.word()? as usize;
578                let dropped = (0..count.min(self.0.len()))
579                    .map(|_| {
580                        Some(Dropped {
581                            hz: f64::from_bits(self.word()?),
582                            db: f64::from_bits(self.word()?),
583                        })
584                    })
585                    .collect::<Option<Vec<_>>>()?;
586                if dropped.len() != count {
587                    return None;
588                }
589                Detail::Lines {
590                    rule,
591                    summed,
592                    dropped,
593                    dropped_more: self.word()? as usize,
594                    tail_db: self.float()?,
595                }
596            }
597            1 => Detail::Continuous { rule: self.rule()? },
598            2 => Detail::Cropped {
599                rule: self.rule()?,
600                tail_db: self.float()?,
601            },
602            3 => Detail::Point {
603                rule: self.rule()?,
604                alias_db: self.float()?,
605                tail_db: self.float()?,
606            },
607            5 => Detail::Roundtrip {
608                rule: self.rule()?,
609                edited: match self.byte()? {
610                    0 => false,
611                    1 => true,
612                    _ => return None,
613                },
614            },
615            6 => Detail::Reading { rule: self.rule()? },
616            7 => {
617                let count = self.word()? as usize;
618                let parts = (0..count.min(self.0.len()))
619                    .map(|_| self.detail())
620                    .collect::<Option<Vec<_>>>()?;
621                if parts.len() != count {
622                    return None;
623                }
624                Detail::Added { parts }
625            }
626            _ => return None,
627        })
628    }
629}