1use crate::formats::predictor;
47use std::fmt;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Layout {
53 V2,
55 V3,
57 V4,
59}
60
61pub const V5_FROM_VERSION: u32 = 500;
65
66impl Layout {
67 pub fn from_version(version: u32) -> Option<Layout> {
70 match version {
71 v if v >= V5_FROM_VERSION => None,
72 v if v >= super::V4_FROM_VERSION => Some(Layout::V4),
73 v if v >= super::V3_FROM_VERSION => Some(Layout::V3),
74 _ => Some(Layout::V2),
75 }
76 }
77
78 pub const fn extension(self) -> &'static str {
80 match self {
81 Layout::V2 => "nsmp",
82 Layout::V3 => "nsmp3",
83 Layout::V4 => "nsmp4",
84 }
85 }
86
87 pub const fn splits_wide_openings(self) -> bool {
90 matches!(self, Layout::V4)
91 }
92
93 pub const fn word(self) -> usize {
95 match self {
96 Layout::V2 => 3,
97 Layout::V3 | Layout::V4 => 4,
98 }
99 }
100
101 pub const fn header_len(self) -> usize {
103 match self {
104 Layout::V2 => 51,
105 Layout::V3 | Layout::V4 => 68,
106 }
107 }
108
109 pub const fn cell(self) -> usize {
111 match self {
112 Layout::V2 => 24,
113 Layout::V3 | Layout::V4 => 32,
114 }
115 }
116
117 pub const fn rmax(self) -> usize {
121 match self {
122 Layout::V2 => 32,
123 Layout::V3 | Layout::V4 => 48,
124 }
125 }
126
127 pub const fn signed_peak(self) -> bool {
130 !matches!(self, Layout::V2)
131 }
132
133 const fn word_bits(self) -> usize {
134 self.word() * 8
135 }
136}
137
138pub(super) const MANTISSA_AT: usize = 9;
140
141pub(super) const STAT_A_EXP_AT: usize = 12;
143
144pub(super) const PEAK_AT: usize = 13;
146
147pub(super) const TAIL_FLOATS_AT: [usize; 2] = [57, 62];
150
151const EXPONENT_BIAS: i32 = 22;
155
156pub(crate) const SHIFT_LIMIT: i32 = 32;
158
159pub(super) const SEEK_AT: usize = 20;
161pub(super) const SEEK_STRIDE: usize = 9;
162
163pub const WRAP: usize = 1 << 16;
166
167pub const PITCH_NUM: u32 = 22_050;
171pub const PITCH_DEN: u32 = 17_501;
173
174pub const SOURCE_RATE: u32 = 44_100;
177
178pub const FIELD_RATE: u32 = SOURCE_RATE * PITCH_DEN / PITCH_NUM;
180const _: () = assert!((SOURCE_RATE * PITCH_DEN).is_multiple_of(PITCH_NUM));
181
182const COUNT_MASK: u32 = 0x3fff;
184
185const MAX_ORDER: usize = predictor::MAX_ORDER;
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum Unsupported {
192 Short,
194 Directory {
198 pointer: u16,
200 },
201 Malformed {
203 word: usize,
206 },
207 Desync {
210 word: usize,
213 },
214 PartialWord {
216 bytes: usize,
218 },
219 Shift {
221 bits: i32,
223 },
224 NoTerminator,
226}
227
228impl Unsupported {
229 pub fn reason(self) -> &'static str {
231 match self {
232 Unsupported::Short => "short-stroke",
233 Unsupported::Directory { .. } => "bad-directory",
234 Unsupported::Malformed { .. } => "malformed-record",
235 Unsupported::Desync { .. } => "desync",
236 Unsupported::PartialWord { .. } => "partial-word",
237 Unsupported::Shift { .. } => "invalid-shift",
238 Unsupported::NoTerminator => "no-terminator",
239 }
240 }
241}
242
243impl fmt::Display for Unsupported {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 match self {
246 Unsupported::Short => write!(f, "the stroke is shorter than its own header"),
247 Unsupported::Directory { pointer } => write!(
248 f,
249 "the directory's opening pointer {pointer} names no word in the stroke"
250 ),
251 Unsupported::Malformed { word } => {
252 write!(f, "word {word} is not a record header")
253 }
254 Unsupported::Desync { word } => write!(
255 f,
256 "the record at word {word} runs past the end of the stroke"
257 ),
258 Unsupported::PartialWord { bytes } => {
259 write!(f, "the stream ends with {bytes} byte(s) of a partial word")
260 }
261 Unsupported::Shift { bits } => {
262 write!(f, "the header's {bits}-bit quantiser shift is invalid")
263 }
264 Unsupported::NoTerminator => {
265 write!(f, "the chain ran off the end with no terminator")
266 }
267 }
268 }
269}
270
271impl std::error::Error for Unsupported {}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
275pub struct Record {
276 pub at: usize,
279 pub first_field: usize,
281 pub one_to_one: bool,
284 pub width: u8,
286 pub order: u8,
288 pub mark: bool,
290 pub values: Vec<i32>,
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
296pub struct Stream {
297 pub records: Vec<Record>,
298 pub fields: usize,
300 pub first_record: usize,
302 pub terminator: usize,
304 pub cell: Option<usize>,
306 pub channels: usize,
308}
309
310#[derive(Debug, Clone, PartialEq, Eq)]
312pub struct Audio {
313 pub samples: Vec<i16>,
316 pub channels: u16,
318 pub clipped: usize,
320 pub differenced: usize,
322}
323
324impl Audio {
325 pub fn frames(&self) -> usize {
327 self.samples.len() / usize::from(self.channels).max(1)
328 }
329
330 pub fn seconds(&self) -> f64 {
332 self.frames() as f64 / f64::from(FIELD_RATE)
333 }
334}
335
336pub fn peak(stroke: &[u8], layout: Layout) -> Option<i32> {
338 let b = stroke.get(PEAK_AT..PEAK_AT + 3)?;
339 let v = u32::from_be_bytes([0, b[0], b[1], b[2]]);
340 Some(match layout.signed_peak() && v >= 1 << 23 {
341 true => v as i32 - (1 << 24),
342 false => v as i32,
343 })
344}
345
346pub fn shift(stroke: &[u8], layout: Layout) -> Option<i32> {
350 let peak = peak(stroke, layout)?.unsigned_abs().max(1);
351 let exponent = i32::from(*stroke.get(STAT_A_EXP_AT)?);
352 let bits = peak.ilog2() as i32 + 1;
353 let exact_power = i32::from(peak.is_power_of_two());
354 Some(exponent + bits - EXPONENT_BIAS - exact_power)
355}
356
357fn tail_float(stroke: &[u8], layout: Layout, at: usize) -> Option<f32> {
358 if layout == Layout::V2 {
359 return None;
360 }
361 let b = stroke.get(at..at + 4)?;
362 Some(f32::from_be_bytes([b[0], b[1], b[2], b[3]]))
363}
364
365pub fn zone_gain_db(stroke: &[u8], layout: Layout) -> Option<f32> {
374 tail_float(stroke, layout, TAIL_FLOATS_AT[0])
375}
376
377pub fn loop_decay(stroke: &[u8], layout: Layout) -> Option<f32> {
385 tail_float(stroke, layout, TAIL_FLOATS_AT[1])
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub struct Directory {
391 pub first_record: u16,
393 pub resync: u16,
395 pub mark: u16,
397 pub terminator: u16,
399}
400
401impl Directory {
402 pub fn read(stroke: &[u8]) -> Option<Directory> {
404 let at = |i: usize| -> Option<u16> {
405 let o = SEEK_AT + SEEK_STRIDE * i;
406 let b = stroke.get(o..o + 2)?;
407 Some(u16::from_be_bytes([b[0], b[1]]))
408 };
409 Some(Directory {
410 first_record: at(0)?,
411 resync: at(1)?,
412 mark: at(2)?,
413 terminator: at(3)?,
414 })
415 }
416
417 pub fn resolve(pointer: u16, stroke_at: usize, layout: Layout) -> usize {
419 let base = (stroke_at + layout.header_len()) / layout.word() % WRAP;
420 (usize::from(pointer) + WRAP - base) % WRAP
421 }
422
423 pub fn resolve_end(pointer: u16, stroke_at: usize, layout: Layout, words: usize) -> usize {
425 let mut at = Directory::resolve(pointer, stroke_at, layout);
426 while at + WRAP < words {
427 at += WRAP;
428 }
429 at
430 }
431}
432
433fn terminator_cell(raw: u32, cell: usize) -> Option<usize> {
435 let v = raw & 0x00ff_ffff;
436 let one_to_one = v >> 23 != 0;
437 let width = ((v >> 19) & 0xf) + 1;
438 let mark = (v >> 18) & 1 != 0;
439 let reserved = (v >> 17) & 1 != 0;
440 let order = (v >> 14) & 0x7;
441 let count = (v & COUNT_MASK) as usize;
442 let ok = one_to_one
443 && width == 1
444 && raw >> 24 == 0
445 && !mark
446 && !reserved
447 && order == 0
448 && (count == cell || count == 2 * cell);
449 ok.then_some(count)
450}
451
452pub fn walk(stroke: &[u8], stroke_at: usize, layout: Layout) -> Result<Stream, Unsupported> {
455 let word_len = layout.word();
456 let word_bits = layout.word_bits();
457 let cell = layout.cell();
458 let stream = stroke
459 .get(layout.header_len()..)
460 .ok_or(Unsupported::Short)?;
461 let trailing = stream.len() % word_len;
462 if trailing != 0 {
463 return Err(Unsupported::PartialWord { bytes: trailing });
464 }
465 let words = stream.len() / word_len;
466 let word = |i: usize| -> u32 {
467 stream[i * word_len..][..word_len]
468 .iter()
469 .fold(0u32, |v, &b| (v << 8) | u32::from(b))
470 };
471 let directory = Directory::read(stroke).ok_or(Unsupported::Short)?;
472
473 let first_record = Directory::resolve(directory.first_record, stroke_at, layout);
474 if first_record >= words {
475 return Err(Unsupported::Directory {
476 pointer: directory.first_record,
477 });
478 }
479 let last = Some(Directory::resolve_end(
480 directory.terminator,
481 stroke_at,
482 layout,
483 words,
484 ))
485 .filter(|&at| at < words);
486
487 let stereo = last.is_some_and(|at| terminator_cell(word(at), cell) == Some(2 * cell));
490 let wide_openings = stereo && layout.splits_wide_openings();
491
492 let mut records = Vec::new();
493 let mut gathered: Vec<u8> = Vec::new();
495 let mut fields = 0usize;
496 let mut i = first_record;
497 while i < words {
498 let raw = word(i);
499 let over = raw >> 24;
502 let v = raw & 0x00ff_ffff;
503 let one_to_one = v >> 23 != 0;
504 let width = (((v >> 19) & 0xf) + 1) as u8;
505 let mark = (v >> 18) & 1 != 0;
506 let order = ((v >> 14) & 0x7) as u8;
507 let count = (v & COUNT_MASK) as usize;
508
509 let terminal_cell = terminator_cell(raw, cell);
510 let ends_here = terminal_cell.is_some();
511 if Some(i) == last || ends_here {
512 if terminal_cell == Some(2 * cell) && !stereo {
513 return Err(Unsupported::Malformed { word: i });
514 }
515 return Ok(Stream {
516 records,
517 fields,
518 first_record,
519 terminator: i,
520 cell: ends_here.then_some(count),
521 channels: if stereo { 2 } else { 1 },
522 });
523 }
524 if over != 0
525 || (v >> 17) & 1 != 0
526 || usize::from(order) > MAX_ORDER
527 || count == 0
528 || (stereo && !count.is_multiple_of(2))
529 || (!one_to_one && !count.is_multiple_of(cell))
530 {
531 return Err(Unsupported::Malformed { word: i });
532 }
533
534 let span = if wide_openings && one_to_one {
535 1 + 2 * (count / 2 * usize::from(width)).div_ceil(word_bits)
537 } else {
538 (word_bits + count * usize::from(width)).div_ceil(word_bits)
539 };
540 if i + span > last.unwrap_or(words) {
541 return Err(Unsupported::Desync { word: i });
542 }
543 let base = (i + 1) * word_bits;
544 let values = if !stereo {
545 (0..count)
546 .map(|k| read_field(stream, base + k * usize::from(width), width))
547 .collect()
548 } else if wide_openings {
549 let per = count / 2;
551 let channel_words = (per * usize::from(width)).div_ceil(word_bits);
552 let mut values = Vec::with_capacity(count);
553 for channel in 0..2 {
554 gathered.clear();
555 for k in 0..channel_words {
556 let at = (i + 1 + 2 * k + channel) * word_len;
557 gathered.extend_from_slice(&stream[at..at + word_len]);
558 }
559 values
560 .extend((0..per).map(|k| read_field(&gathered, k * usize::from(width), width)));
561 }
562 values
563 } else {
564 let field = |k: usize| read_field(stream, base + k * usize::from(width), width);
566 (0..count)
567 .map(|k| field(2 * (k % (count / 2)) + k / (count / 2)))
568 .collect()
569 };
570
571 records.push(Record {
572 at: i,
573 first_field: fields,
574 one_to_one,
575 width,
576 order,
577 mark,
578 values,
579 });
580 fields += count;
581 i += span;
582 while i < words && word(i) == 0 {
583 i += 1;
584 }
585 }
586 Err(Unsupported::NoTerminator)
587}
588
589pub fn decode(stroke: &[u8], stroke_at: usize, layout: Layout) -> Result<Audio, Unsupported> {
591 let stream = walk(stroke, stroke_at, layout)?;
592 let channels = stream.channels;
593 let shift = shift(stroke, layout).ok_or(Unsupported::Short)?;
594 if !(-SHIFT_LIMIT..=SHIFT_LIMIT).contains(&shift) {
595 return Err(Unsupported::Shift { bits: shift });
596 }
597 let mut samples = vec![0i16; stream.fields];
598 let mut clipped = 0;
599 let mut differenced = 0;
600 let mut history = [[0i64; MAX_ORDER]; 2];
602 for record in &stream.records {
603 let order = if record.one_to_one {
605 0
606 } else {
607 usize::from(record.order)
608 };
609 if order > 0 {
610 differenced += record.values.len();
611 }
612 let per = record.values.len() / channels;
615 for (k, &residual) in record.values.iter().enumerate() {
616 let (channel, k) = if channels == 2 {
617 (k / per, k % per)
618 } else {
619 (0, k)
620 };
621 let value = predictor::predict(&mut history[channel], order, i64::from(residual));
622
623 let at = record.first_field + k * channels + channel;
624 let Some(slot) = samples.get_mut(at) else {
625 continue;
626 };
627 let wide = if shift >= 0 {
628 value.saturating_mul(1i64 << shift)
629 } else {
630 value >> -shift
631 };
632 *slot = wide.clamp(i64::from(i16::MIN), i64::from(i16::MAX)) as i16;
633 if i64::from(*slot) != wide {
634 clipped += 1;
635 }
636 }
637 }
638 Ok(Audio {
639 samples,
640 channels: channels as u16,
641 clipped,
642 differenced,
643 })
644}
645
646fn read_field(stream: &[u8], bit: usize, width: u8) -> i32 {
648 let mut v: u32 = 0;
649 for i in bit..bit + usize::from(width) {
650 v = (v << 1) | u32::from((stream[i / 8] >> (7 - i % 8)) & 1);
651 }
652 if v & (1 << (width - 1)) != 0 {
653 v as i32 - (1i32 << width)
654 } else {
655 v as i32
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662
663 const BOTH: [Layout; 2] = [Layout::V2, Layout::V3];
664
665 fn terminator(layout: Layout) -> Vec<u8> {
667 let head = (1u32 << 23) | layout.cell() as u32;
668 head.to_be_bytes()[4 - layout.word()..].to_vec()
669 }
670
671 fn block(layout: Layout, one_to_one: bool, width: u8, order: u8, values: &[i32]) -> Vec<u8> {
673 packed(layout, one_to_one, width, order, false, values)
674 }
675
676 fn packed(
677 layout: Layout,
678 one_to_one: bool,
679 width: u8,
680 order: u8,
681 mark: bool,
682 values: &[i32],
683 ) -> Vec<u8> {
684 let bits = layout.word_bits();
685 let count = values.len();
686 let head = (u32::from(one_to_one) << 23)
687 | (u32::from(width - 1) << 19)
688 | (u32::from(mark) << 18)
689 | (u32::from(order) << 14)
690 | count as u32;
691 let span = (bits + count * usize::from(width)).div_ceil(bits);
692 let mut out = vec![0u8; span * layout.word()];
693 out[..layout.word()].copy_from_slice(&head.to_be_bytes()[4 - layout.word()..]);
694 write_values(&mut out, bits, width, values);
695 out
696 }
697
698 fn split_block(layout: Layout, width: u8, values: &[i32]) -> Vec<u8> {
699 let bits = layout.word_bits();
700 let half = values.len() / 2;
701 let half_words = (half * usize::from(width)).div_ceil(bits);
702 let mut out = vec![0u8; (1 + 2 * half_words) * layout.word()];
703 let head = (1u32 << 23) | (u32::from(width - 1) << 19) | values.len() as u32;
704 out[..layout.word()].copy_from_slice(&head.to_be_bytes());
705 for channel in 0..2 {
706 let mut packed = vec![0u8; half_words * layout.word()];
707 write_values(
708 &mut packed,
709 0,
710 width,
711 &values[channel * half..(channel + 1) * half],
712 );
713 for word in 0..half_words {
714 let from = word * layout.word();
715 let to = (1 + 2 * word + channel) * layout.word();
716 out[to..to + layout.word()].copy_from_slice(&packed[from..from + layout.word()]);
717 }
718 }
719 out
720 }
721
722 fn write_values(out: &mut [u8], mut at: usize, width: u8, values: &[i32]) {
723 for &v in values {
724 let raw = (v as u32) & ((1u32 << width) - 1);
725 for b in (0..width).rev() {
726 if raw >> b & 1 != 0 {
727 out[at / 8] |= 1 << (7 - at % 8);
728 }
729 at += 1;
730 }
731 }
732 }
733
734 fn stroke(layout: Layout, peak: i32, exponent: u8, lead: usize, chain: &[Vec<u8>]) -> Vec<u8> {
736 let word = layout.word();
737 let mut s = vec![0u8; layout.header_len()];
738 s[STAT_A_EXP_AT] = exponent;
739 s[PEAK_AT..PEAK_AT + 3].copy_from_slice(&(peak as u32 & 0xff_ffff).to_be_bytes()[1..]);
740 let body: Vec<u8> = chain.concat();
741 let base = layout.header_len() / word;
742 let at = (base + lead) as u16;
743 let end = (base + lead + body.len() / word) as u16;
744 for (i, p) in [at, at, end, end].iter().enumerate() {
745 let o = SEEK_AT + SEEK_STRIDE * i;
746 s[o..o + 2].copy_from_slice(&p.to_be_bytes());
747 }
748 s.extend(std::iter::repeat_n(0u8, lead * word));
749 s.extend_from_slice(&body);
750 s.extend_from_slice(&terminator(layout));
751 s
752 }
753
754 fn stereo_stroke(layout: Layout, width: u8, l: &[i32], r: &[i32]) -> Vec<u8> {
758 assert_eq!(l.len(), r.len());
759 let word = layout.word();
760 let bits = layout.word_bits();
761 let count = l.len() * 2;
762 let head = (u32::from(width - 1) << 19) | count as u32;
763
764 let mut body = head.to_be_bytes()[4 - word..].to_vec();
765 if layout.splits_wide_openings() {
766 let per = |v: &[i32]| {
768 let mut out = vec![0u8; (v.len() * usize::from(width)).div_ceil(bits) * word];
769 let mut at = 0;
770 for &x in v {
771 for b in (0..width).rev() {
772 if (x as u32) >> b & 1 != 0 {
773 out[at / 8] |= 1 << (7 - at % 8);
774 }
775 at += 1;
776 }
777 }
778 out
779 };
780 let (a, b) = (per(l), per(r));
781 for k in 0..a.len() / word {
782 body.extend_from_slice(&a[k * word..][..word]);
783 body.extend_from_slice(&b[k * word..][..word]);
784 }
785 } else {
786 let woven: Vec<i32> = l.iter().zip(r).flat_map(|(&a, &b)| [a, b]).collect();
787 body = block(layout, false, width, 0, &woven);
788 }
789
790 let mut s = vec![0u8; layout.header_len()];
791 s[STAT_A_EXP_AT] = exponent_for(1, 0);
792 s[PEAK_AT..PEAK_AT + 3].copy_from_slice(&1u32.to_be_bytes()[1..]);
793 let base = (layout.header_len() / word) as u16;
794 let end = base + (body.len() / word) as u16;
795 for (i, p) in [base, base, end, end].iter().enumerate() {
796 let o = SEEK_AT + SEEK_STRIDE * i;
797 s[o..o + 2].copy_from_slice(&p.to_be_bytes());
798 }
799 s.extend_from_slice(&body);
800 let term = (1u32 << 23) | (2 * layout.cell()) as u32;
801 s.extend_from_slice(&term.to_be_bytes()[4 - word..]);
802 s
803 }
804
805 #[test]
806 fn a_stereo_stroke_decodes_to_two_channels() {
807 for layout in [Layout::V2, Layout::V3, Layout::V4] {
808 let per = layout.cell(); let l: Vec<i32> = (0..per as i32).map(|k| 100 + k).collect();
810 let r: Vec<i32> = (0..per as i32).map(|k| -100 - k).collect();
811 let s = stereo_stroke(layout, 11, &l, &r);
812
813 let stream = walk(&s, 0, layout).expect("the stereo stroke walks");
814 assert_eq!(stream.channels, 2, "{layout:?}");
815 assert_eq!(stream.cell, Some(2 * layout.cell()), "{layout:?}");
816
817 let audio = decode(&s, 0, layout).expect("the stereo stroke decodes");
818 assert_eq!(audio.channels, 2, "{layout:?}");
819 assert_eq!(audio.frames(), per, "{layout:?}");
820 let got_l: Vec<i32> = audio
821 .samples
822 .iter()
823 .step_by(2)
824 .map(|&v| i32::from(v))
825 .collect();
826 let got_r: Vec<i32> = audio.samples[1..]
827 .iter()
828 .step_by(2)
829 .map(|&v| i32::from(v))
830 .collect();
831 assert_eq!(got_l, l, "{layout:?}: left channel");
832 assert_eq!(got_r, r, "{layout:?}: right channel");
833 }
834 }
835
836 #[test]
837 fn a_stereo_terminator_needs_a_valid_directory_pointer() {
838 let layout = Layout::V2;
839 let fields = vec![0; layout.cell()];
840 let mut s = stereo_stroke(layout, 1, &fields, &fields);
841 let at = SEEK_AT + 3 * SEEK_STRIDE;
842 s[at..at + 2].copy_from_slice(&u16::MAX.to_be_bytes());
843 let terminator = (s.len() - layout.header_len()) / layout.word() - 1;
844
845 assert_eq!(
846 walk(&s, 0, layout),
847 Err(Unsupported::Malformed { word: terminator })
848 );
849 }
850
851 #[test]
852 fn an_opening_pointer_outside_the_stream_is_refused_rather_than_searched_for() {
853 for layout in BOTH {
854 let values = run(layout, 6);
855 let mut s = stroke(layout, 1, 22, 2, &[block(layout, false, 4, 0, &values)]);
856 s[SEEK_AT..SEEK_AT + 2].copy_from_slice(&u16::MAX.to_be_bytes());
857 assert_eq!(
858 walk(&s, 0, layout),
859 Err(Unsupported::Directory { pointer: u16::MAX }),
860 "{layout:?}"
861 );
862 }
863 }
864
865 #[test]
866 fn a_stereo_stroke_predicts_each_channel_against_its_own_history() {
867 for layout in [Layout::V2, Layout::V3, Layout::V4] {
869 let per = layout.cell();
870 let l: Vec<i32> = (0..per as i32).map(|k| 10 * k).collect();
871 let r: Vec<i32> = (0..per as i32).map(|k| -7 * k).collect();
872 let diff = |v: &[i32]| -> Vec<i32> {
873 v.iter()
874 .enumerate()
875 .map(|(i, &x)| if i == 0 { x } else { x - v[i - 1] })
876 .collect()
877 };
878 let s = stereo_stroke_ordered(layout, 11, &diff(&l), &diff(&r));
879 let audio = decode(&s, 0, layout).expect("decodes");
880 let got_l: Vec<i32> = audio
881 .samples
882 .iter()
883 .step_by(2)
884 .map(|&v| i32::from(v))
885 .collect();
886 let got_r: Vec<i32> = audio.samples[1..]
887 .iter()
888 .step_by(2)
889 .map(|&v| i32::from(v))
890 .collect();
891 assert_eq!(got_l, l, "{layout:?}: left ramp");
892 assert_eq!(got_r, r, "{layout:?}: right ramp");
893 }
894 }
895
896 fn stereo_stroke_ordered(layout: Layout, width: u8, l: &[i32], r: &[i32]) -> Vec<u8> {
898 let mut s = stereo_stroke(layout, width, l, r);
899 let at = layout.header_len();
900 let word = layout.word();
901 let mut head = 0u32;
902 for &b in &s[at..at + word] {
903 head = (head << 8) | u32::from(b);
904 }
905 head |= 1 << 14;
906 s[at..at + word].copy_from_slice(&head.to_be_bytes()[4 - word..]);
907 s
908 }
909
910 fn run(layout: Layout, value: i32) -> Vec<i32> {
912 let mut v = vec![0; layout.cell()];
913 v[0] = value;
914 v
915 }
916
917 fn exponent_for(peak: i32, shift: i32) -> u8 {
920 let peak = peak.unsigned_abs().max(1);
921 let bits = peak.ilog2() as i32 + 1;
922 (EXPONENT_BIAS + shift - bits + i32::from(peak.is_power_of_two())) as u8
923 }
924
925 #[test]
926 fn the_shift_comes_off_the_exponent_byte_and_is_signed() {
927 for layout in BOTH {
928 for (peak, want) in [(8191i32, 2i32), (1, 0), (4096, 7), (255, -8), (12345, 3)] {
931 let s = stroke(layout, peak, exponent_for(peak, want), 0, &[]);
932 assert_eq!(shift(&s, layout), Some(want), "{layout:?} peak {peak}");
933 }
934 let s = stroke(layout, 0, exponent_for(1, 5), 0, &[]);
936 assert_eq!(shift(&s, layout), Some(5), "{layout:?}");
937 }
938 }
939
940 #[test]
941 fn statistic_b_is_signed_in_the_wide_layout() {
942 let s = stroke(Layout::V3, -8191, exponent_for(8191, 2), 0, &[]);
943 assert_eq!(peak(&s, Layout::V3), Some(-8191));
944 assert_eq!(shift(&s, Layout::V3), Some(2));
945 let silent = stroke(Layout::V3, -1, exponent_for(1, 0), 0, &[]);
946 assert_eq!(peak(&silent, Layout::V3), Some(-1));
947 assert_eq!(shift(&silent, Layout::V3), Some(0));
948 assert_eq!(peak(&silent, Layout::V2), Some(0xff_ffff));
951 }
952
953 #[test]
956 fn the_wide_header_carries_a_zone_gain_and_a_loop_decay() {
957 let mut s = stroke(Layout::V3, 1, 22, 0, &[]);
958 s[TAIL_FLOATS_AT[0]..][..4].copy_from_slice(&(-6.0206f32).to_be_bytes());
959 s[TAIL_FLOATS_AT[1]..][..4].copy_from_slice(&20.0f32.to_be_bytes());
960 assert_eq!(zone_gain_db(&s, Layout::V3), Some(-6.0206));
961 assert_eq!(loop_decay(&s, Layout::V3), Some(20.0));
962 assert_eq!(zone_gain_db(&s, Layout::V2), None);
963 assert_eq!(loop_decay(&s, Layout::V2), None);
964 }
965
966 #[test]
967 fn fields_are_left_anchored_and_sign_extended() {
968 for layout in BOTH {
969 let mut values = run(layout, 0);
970 values[..4].copy_from_slice(&[1, -1, 4095, -4096]);
971 let s = stroke(layout, 1, 22, 0, &[block(layout, false, 13, 0, &values)]);
972 let walked = walk(&s, 0, layout).unwrap();
973 assert_eq!(walked.records.len(), 1, "{layout:?}");
974 assert_eq!(walked.records[0].width, 13);
975 assert_eq!(walked.records[0].values[..4], [1, -1, 4095, -4096]);
976 assert_eq!(walked.fields, layout.cell());
977 assert_eq!(walked.cell, Some(layout.cell()));
978 }
979 }
980
981 #[test]
984 fn an_order_moves_neither_the_length_nor_the_field_base() {
985 for layout in BOTH {
986 let values = run(layout, 3);
987 let plain = stroke(layout, 1, 22, 0, &[block(layout, false, 4, 0, &values)]);
988 let ordered = stroke(layout, 1, 22, 0, &[block(layout, false, 4, 2, &values)]);
989 let a = walk(&plain, 0, layout).unwrap();
990 let b = walk(&ordered, 0, layout).unwrap();
991 assert_eq!(a.fields, b.fields);
992 assert_eq!(a.records[0].first_field, b.records[0].first_field);
993 assert_eq!(b.records[0].order, 2);
994 assert_eq!(a.terminator, b.terminator);
995 }
996 }
997
998 #[test]
999 fn a_marked_record_walks_and_says_it_is_marked() {
1000 for layout in BOTH {
1001 let values = run(layout, 1);
1002 let s = stroke(
1003 layout,
1004 1,
1005 22,
1006 0,
1007 &[packed(layout, true, 4, 0, true, &values)],
1008 );
1009 let walked = walk(&s, 0, layout).unwrap();
1010 assert_eq!(walked.records.len(), 1, "{layout:?}");
1011 assert!(walked.records[0].mark, "{layout:?}");
1012 assert_eq!(walked.records[0].values, values);
1013 let mut s = stroke(layout, 1, 22, 0, &[block(layout, true, 4, 0, &values)]);
1015 let head = layout.header_len();
1016 s[head + layout.word() - 3] |= 0x02;
1017 assert_eq!(
1018 walk(&s, 0, layout),
1019 Err(Unsupported::Malformed { word: 0 }),
1020 "{layout:?}"
1021 );
1022 }
1023 }
1024
1025 #[test]
1026 fn a_differenced_run_integrates_from_the_running_history() {
1027 for layout in BOTH {
1028 let settle = vec![100i32; layout.cell()];
1031 let hold = vec![0i32; 2 * layout.cell()];
1032 let s = stroke(
1033 layout,
1034 1,
1035 22,
1036 0,
1037 &[
1038 block(layout, true, 13, 0, &settle),
1039 block(layout, false, 13, 1, &hold),
1040 ],
1041 );
1042 let audio = decode(&s, 0, layout).unwrap();
1043 assert_eq!(audio.differenced, 2 * layout.cell(), "{layout:?}");
1044 assert!(
1047 audio.samples[layout.cell()..].iter().all(|&v| v == 100),
1048 "{layout:?}"
1049 );
1050 }
1051 }
1052
1053 #[test]
1056 fn a_second_order_run_carries_slope_as_well_as_level() {
1057 for layout in BOTH {
1058 let ramp: Vec<i32> = (0..layout.cell()).map(|k| 10 * k as i32).collect();
1059 let coast = vec![0i32; layout.cell()];
1060 let s = stroke(
1061 layout,
1062 1,
1063 22,
1064 0,
1065 &[
1066 block(layout, true, 13, 0, &ramp),
1067 block(layout, false, 13, 2, &coast),
1068 ],
1069 );
1070 let audio = decode(&s, 0, layout).unwrap();
1071 let last = layout.cell() - 1;
1072 assert_eq!(audio.samples[last], 10 * last as i16, "{layout:?}");
1073 assert_eq!(
1074 audio.samples[last + 1],
1075 10 * (last + 1) as i16,
1076 "{layout:?}"
1077 );
1078 assert_eq!(
1079 audio.samples[last + 2],
1080 10 * (last + 2) as i16,
1081 "{layout:?}"
1082 );
1083 }
1084 }
1085
1086 #[test]
1089 fn a_one_to_one_record_with_an_alignment_tail_reads_from_the_front() {
1090 for layout in BOTH {
1091 let values: Vec<i32> = (0..layout.cell() as i32 + 6).map(|k| k * 7 - 40).collect();
1094 let spent = values.len() * 13;
1095 assert_ne!(spent % layout.word_bits(), 0, "{layout:?}: no tail to test");
1096 let s = stroke(layout, 1, 22, 0, &[block(layout, true, 13, 0, &values)]);
1097 let walked = walk(&s, 0, layout).unwrap();
1098 assert_eq!(walked.records[0].values, values, "{layout:?}");
1099 }
1100 }
1101
1102 #[test]
1103 fn v4_stereo_openings_skip_each_channels_padding() {
1104 let values: Vec<i32> = (0..66).map(|k| k % 31 - 15).collect();
1105 let mut s = stroke(Layout::V4, 1, 22, 0, &[split_block(Layout::V4, 5, &values)]);
1106 let term = s.len() - Layout::V4.word();
1107 s[term..].copy_from_slice(&((1u32 << 23) | 64).to_be_bytes());
1108
1109 let walked = walk(&s, 0, Layout::V4).unwrap();
1110 assert_eq!(walked.records[0].values, values);
1111 assert_eq!(walked.cell, Some(64));
1112 let audio = decode(&s, 0, Layout::V4).unwrap();
1113 let interleaved = values[..33]
1114 .iter()
1115 .zip(&values[33..])
1116 .flat_map(|(&left, &right)| [left as i16, right as i16])
1117 .collect::<Vec<_>>();
1118 assert_eq!(audio.samples, interleaved);
1119 }
1120
1121 fn v4_split_stroke(width: u8, count: usize) -> Vec<u8> {
1124 let layout = Layout::V4;
1125 let values: Vec<i32> = (0..66).map(|k| k % 31 - 15).collect();
1126 let mut s = stroke(layout, 1, 22, 0, &[split_block(layout, width, &values)]);
1127 let term = s.len() - layout.word();
1128 s[term..].copy_from_slice(&((1u32 << 23) | (2 * layout.cell()) as u32).to_be_bytes());
1129 let head = layout.header_len();
1130 let raw = (1u32 << 23) | (u32::from(width - 1) << 19) | count as u32;
1131 s[head..head + layout.word()].copy_from_slice(&raw.to_be_bytes());
1132 s
1133 }
1134
1135 #[test]
1136 fn a_v4_stereo_opening_whose_channels_outrun_the_terminator_is_a_desync() {
1137 assert!(walk(&v4_split_stroke(5, 66), 0, Layout::V4).is_ok());
1138 assert_eq!(
1139 walk(&v4_split_stroke(5, 80), 0, Layout::V4),
1140 Err(Unsupported::Desync { word: 0 })
1141 );
1142 }
1143
1144 #[test]
1145 fn a_v4_stereo_record_needs_whole_channel_pairs() {
1146 assert_eq!(
1147 walk(&v4_split_stroke(5, 33), 0, Layout::V4),
1148 Err(Unsupported::Malformed { word: 0 })
1149 );
1150 }
1151
1152 #[test]
1153 fn a_stereo_record_needs_whole_channel_pairs() {
1154 let layout = Layout::V3;
1155 let mut s = stroke(layout, 1, 22, 0, &[block(layout, true, 5, 0, &[1])]);
1156 let term = s.len() - layout.word();
1157 s[term..].copy_from_slice(&((1u32 << 23) | 64).to_be_bytes());
1158 assert_eq!(walk(&s, 0, layout), Err(Unsupported::Malformed { word: 0 }));
1159 }
1160
1161 #[test]
1162 fn dequantising_shifts_by_the_headers_own_scale() {
1163 for layout in BOTH {
1164 let mut values = run(layout, 0);
1165 values[..2].copy_from_slice(&[100, -100]);
1166 let s = stroke(
1167 layout,
1168 8191,
1169 exponent_for(8191, 1),
1170 0,
1171 &[block(layout, false, 13, 0, &values)],
1172 );
1173 assert_eq!(decode(&s, 0, layout).unwrap().samples[..2], [200, -200]);
1174 }
1175 }
1176
1177 #[test]
1180 fn a_negative_shift_scales_back_down() {
1181 for layout in BOTH {
1182 let mut values = run(layout, 0);
1183 values[..2].copy_from_slice(&[2048, -2048]);
1184 let s = stroke(
1185 layout,
1186 8191,
1187 exponent_for(8191, -4),
1188 0,
1189 &[block(layout, false, 13, 0, &values)],
1190 );
1191 assert_eq!(decode(&s, 0, layout).unwrap().samples[..2], [128, -128]);
1192 }
1193 }
1194
1195 #[test]
1196 fn a_transient_past_full_scale_clamps_and_says_so() {
1197 for layout in BOTH {
1198 let mut values = run(layout, 0);
1199 values[0] = 4095;
1200 let s = stroke(
1201 layout,
1202 8191,
1203 exponent_for(8191, 4),
1204 0,
1205 &[block(layout, false, 13, 0, &values)],
1206 );
1207 let audio = decode(&s, 0, layout).unwrap();
1208 assert_eq!(audio.samples[0], i16::MAX);
1209 assert_eq!(audio.clipped, 1);
1210 }
1211 }
1212
1213 #[test]
1216 fn a_merged_run_carries_a_count_past_a_byte() {
1217 for layout in BOTH {
1218 let n = 43 * layout.cell();
1219 let values: Vec<i32> = (0..n).map(|k| k as i32 % 7 - 3).collect();
1220 let s = stroke(layout, 1, 22, 0, &[block(layout, false, 4, 0, &values)]);
1221 let walked = walk(&s, 0, layout).unwrap();
1222 assert_eq!(walked.records.len(), 1, "{layout:?}");
1223 assert_eq!(walked.records[0].values, values);
1224 }
1225 }
1226
1227 #[test]
1230 fn the_walk_starts_where_the_directory_says_not_at_the_first_data() {
1231 for layout in BOTH {
1232 let values = run(layout, 6);
1233 let mut s = stroke(layout, 1, 22, 2, &[block(layout, false, 4, 0, &values)]);
1234 let head = layout.header_len();
1235 s[head..head + 2 * layout.word()].fill(0x5a);
1236 let walked = walk(&s, 0, layout).unwrap();
1237 assert_eq!(walked.first_record, 2, "{layout:?}");
1238 assert_eq!(walked.records.len(), 1);
1239 assert_eq!(walked.records[0].values[0], 6);
1240 }
1241 }
1242
1243 #[test]
1244 fn every_refusal_names_itself() {
1245 for layout in BOTH {
1246 assert_eq!(walk(&[0u8; 8], 0, layout), Err(Unsupported::Short));
1247
1248 let s = stroke(layout, 1, 22, 0, &[block(layout, false, 4, 0, &[1, 2, 3])]);
1250 assert_eq!(
1251 walk(&s, 0, layout),
1252 Err(Unsupported::Malformed { word: 0 }),
1253 "{layout:?}"
1254 );
1255
1256 let mut s = stroke(
1258 layout,
1259 1,
1260 22,
1261 0,
1262 &[block(layout, false, 13, 0, &run(layout, 1))],
1263 );
1264 s[layout.header_len() + layout.word() - 2] = 0xff;
1265 assert!(
1266 matches!(walk(&s, 0, layout), Err(Unsupported::Desync { .. })),
1267 "{layout:?}"
1268 );
1269
1270 let mut s = stroke(
1272 layout,
1273 1,
1274 22,
1275 0,
1276 &[block(layout, false, 4, 0, &run(layout, 1))],
1277 );
1278 s.truncate(s.len() - layout.word());
1279 assert_eq!(walk(&s, 0, layout), Err(Unsupported::NoTerminator));
1280 }
1281 }
1282
1283 #[test]
1284 fn malformed_codec_boundaries_are_refused() {
1285 for layout in BOTH {
1286 let values = run(layout, 1);
1287 let order_five = stroke(layout, 1, 22, 0, &[block(layout, false, 4, 5, &values)]);
1288 assert_eq!(
1289 walk(&order_five, 0, layout),
1290 Err(Unsupported::Malformed { word: 0 })
1291 );
1292
1293 let mut partial = stroke(layout, 1, 22, 0, &[]);
1294 partial.push(0);
1295 assert_eq!(
1296 walk(&partial, 0, layout),
1297 Err(Unsupported::PartialWord { bytes: 1 })
1298 );
1299
1300 let shifted = stroke(layout, 1, exponent_for(1, SHIFT_LIMIT + 1), 0, &[]);
1301 assert_eq!(
1302 decode(&shifted, 0, layout),
1303 Err(Unsupported::Shift {
1304 bits: SHIFT_LIMIT + 1
1305 })
1306 );
1307 }
1308 }
1309
1310 #[test]
1313 fn a_wide_word_with_a_top_byte_is_not_a_record() {
1314 let mut s = stroke(
1315 Layout::V3,
1316 1,
1317 22,
1318 0,
1319 &[block(Layout::V3, false, 4, 0, &run(Layout::V3, 1))],
1320 );
1321 s[Layout::V3.header_len()] = 0x01;
1322 assert_eq!(
1323 walk(&s, 0, Layout::V3),
1324 Err(Unsupported::Malformed { word: 0 })
1325 );
1326 }
1327
1328 #[test]
1329 fn the_directory_resolves_against_the_strokes_own_offset() {
1330 let mut s = vec![0u8; Layout::V2.header_len()];
1331 for (i, p) in [444u16, 483, 762, 762].iter().enumerate() {
1332 let at = SEEK_AT + SEEK_STRIDE * i;
1333 s[at..at + 2].copy_from_slice(&p.to_be_bytes());
1334 }
1335 let dir = Directory::read(&s).unwrap();
1336 assert_eq!(dir.first_record, 444);
1337 assert_eq!(dir.resync, 483);
1338 assert_eq!(dir.mark, 762);
1339 assert_eq!(dir.terminator, 762);
1340 assert_eq!(Directory::resolve(dir.first_record, 981, Layout::V2), 100);
1343 assert_eq!(Directory::resolve(dir.terminator, 981, Layout::V2), 418);
1344 assert_eq!(Directory::resolve(1, 981, Layout::V2), WRAP - 343);
1347 let far = 3 * (344 + 3 * WRAP) - Layout::V2.header_len();
1350 assert_eq!(Directory::resolve(dir.first_record, far, Layout::V2), 100);
1351 assert_eq!(Directory::resolve(444, 4 * 100 - 68, Layout::V3), 344);
1354 }
1355
1356 #[test]
1357 fn the_field_rate_is_the_lattice_rate() {
1358 assert_eq!(FIELD_RATE, 35_002);
1359 }
1360
1361 #[test]
1362 fn the_layout_follows_the_content_version() {
1363 assert_eq!(Layout::from_version(8), Some(Layout::V2));
1364 assert_eq!(Layout::from_version(200), Some(Layout::V2));
1365 assert_eq!(Layout::from_version(300), Some(Layout::V3));
1366 assert_eq!(Layout::from_version(310), Some(Layout::V3));
1367 assert_eq!(Layout::from_version(400), Some(Layout::V4));
1368 assert_eq!(Layout::from_version(420), Some(Layout::V4));
1369 }
1370
1371 #[test]
1374 fn a_content_version_past_the_last_modelled_generation_is_refused() {
1375 assert_eq!(
1376 Layout::from_version(V5_FROM_VERSION - 1),
1377 Some(Layout::V4),
1378 "the ceiling is exclusive"
1379 );
1380 assert_eq!(Layout::from_version(V5_FROM_VERSION), None);
1381 assert_eq!(Layout::from_version(u32::MAX), None);
1382 }
1383
1384 #[test]
1385 fn only_v4_splits_a_stereo_stroke_s_openings() {
1386 assert!(Layout::V4.splits_wide_openings());
1387 assert!(!Layout::V3.splits_wide_openings());
1388 assert!(!Layout::V2.splits_wide_openings());
1389 }
1390
1391 #[test]
1392 fn a_terminator_is_a_width_one_word_stating_the_cell_size() {
1393 assert_eq!(terminator_cell(0x0080_0018, 24), Some(24));
1395 assert_eq!(terminator_cell(0x0080_0030, 24), Some(48));
1396 assert_eq!(terminator_cell(0x0080_0020, 32), Some(32));
1397 assert_eq!(terminator_cell(0x0080_0040, 32), Some(64));
1398 assert_eq!(terminator_cell(0x0080_0000, 32), None);
1400 assert_eq!(terminator_cell(0x0080_0018, 32), None);
1401 assert_eq!(terminator_cell(0x00c4_0020, 32), None);
1403 assert_eq!(terminator_cell(0x0082_0020, 32), None);
1404 assert_eq!(terminator_cell(0x0080_4020, 32), None);
1405 assert_eq!(terminator_cell(0x6580_0020, 32), None);
1406 }
1407
1408 #[test]
1409 fn the_terminator_pointer_rises_past_the_period_and_the_opening_does_not() {
1410 let first = Directory::resolve(5_999, 0, Layout::V2);
1411 assert_eq!(
1413 Directory::resolve_end(5_999, 0, Layout::V2, first + 1),
1414 first
1415 );
1416 for periods in 1..4 {
1419 let words = first + periods * WRAP + 1;
1420 assert_eq!(
1421 Directory::resolve_end(5_999, 0, Layout::V2, words),
1422 first + periods * WRAP
1423 );
1424 }
1425 }
1426}