1use super::codec::{self, Layout, PITCH_DEN, PITCH_NUM, WRAP};
68use super::kernel;
69use super::section::{self, Section, Section4};
70use super::stroke::packet_len;
71use super::{Sample, SampleV3};
72use crate::cbin::{Cbin, Generation, Header};
73use crate::error::{Error, ParseError};
74use crate::formats::nsmpproj;
75
76const fn version(layout: Layout) -> u32 {
79 match layout {
80 Layout::V2 => 200,
81 Layout::V3 => 300,
82 Layout::V4 => 400,
83 }
84}
85
86const AUX: u32 = 0x000f_0000;
89
90const MAX_COUNT: usize = (1 << 14) - 1;
94
95const PEAK_WIDTH: u8 = 14;
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106struct Units {
107 layout: Layout,
108 channels: usize,
110}
111
112impl Units {
113 const fn word(self) -> usize {
114 self.layout.word()
115 }
116
117 const fn word_bits(self) -> usize {
118 self.layout.word() * 8
119 }
120
121 const fn cell(self) -> usize {
123 self.layout.cell() * self.channels
124 }
125
126 const fn chunk(self) -> usize {
128 self.layout.rmax() * self.channels
129 }
130
131 const fn splits(self) -> bool {
134 self.channels == 2 && self.layout.splits_wide_openings()
135 }
136
137 const fn half(self, count: usize, width: u8) -> usize {
139 (count / 2 * width as usize).div_ceil(self.word_bits())
140 }
141
142 const fn span(self, count: usize, width: u8) -> usize {
147 if self.splits() {
148 1 + 2 * self.half(count, width)
149 } else {
150 (self.word_bits() + count * width as usize).div_ceil(self.word_bits())
151 }
152 }
153
154 const fn packet_words(self) -> usize {
156 packet_len(self.layout) / self.word()
157 }
158
159 const fn min_lead(self) -> usize {
167 match self.layout {
168 Layout::V2 => 0,
169 Layout::V3 | Layout::V4 => 7,
170 }
171 }
172
173 const fn max_fields(self) -> usize {
175 MAX_STREAM_WORDS * self.word_bits() / MIN_WIDTH as usize
176 }
177}
178
179const fn dead_last_record(layout: Layout) -> Option<&'static [usize]> {
189 match layout {
190 Layout::V2 => Some(&[24, 29, 32]),
191 Layout::V3 => Some(&[32, 41, 43, 45, 47, 48]),
192 Layout::V4 => None,
193 }
194}
195
196fn spends_extra_bit(values: &[i64], plan: &Plan) -> bool {
215 if plan.channels != 1 {
216 return false;
217 }
218 let Some(dead) = dead_last_record(plan.layout) else {
219 return false;
220 };
221 let over = 1i64 << (PEAK_WIDTH - 2);
222 let shift = peak_shift(values, PEAK_WIDTH);
223 [
224 Some((0, plan.warmup)),
225 Some((plan.resync_at, plan.resync)),
226 plan.looped.map(|points| (points.at, points.warmup)),
227 ]
228 .into_iter()
229 .flatten()
230 .any(|(base, run)| {
231 let Some(&last) = chunks(run, plan.chunk()).last() else {
232 return false;
233 };
234 !dead.contains(&(last / plan.channels))
235 && values[base + run - last..base + run].iter().any(|&v| {
236 let v = v >> shift;
237 v < -over || v >= over
238 })
239 })
240}
241
242fn peak_shift(values: &[i64], width: u8) -> i32 {
244 let low = values.iter().copied().min().unwrap_or(0);
245 let high = values.iter().copied().max().unwrap_or(0);
246 let mut shift = 0i32;
247 while width_of(low >> shift, high >> shift) > width {
248 shift += 1;
249 }
250 shift
251}
252
253const MAX_STORED_WIDTH: u8 = 16;
256
257const MIN_WIDTH: u8 = 2;
260
261const MAX_CHANNELS: usize = 2;
264
265const MAX_ZONES: usize = u8::MAX as usize;
267
268const MAX_STROKE_ID: u32 = u8::MAX as u32;
271
272const RING_OUT: usize = 127;
275
276const RAMP_IN: usize = 35;
280
281pub const MIN_FRAMES: usize = 92;
285
286const LOOP_LEAD: usize = 5;
290
291const fn min_resync_gap(layout: Layout) -> usize {
301 match layout {
302 Layout::V2 => 72,
303 Layout::V3 | Layout::V4 => 64,
304 }
305}
306
307const MAX_STREAM_WORDS: usize = WRAP;
309
310const DIFFERENCE: [&[i32]; 5] = [
312 &[1],
313 &[1, -1],
314 &[1, -2, 1],
315 &[1, -3, 3, -1],
316 &[1, -4, 6, -4, 1],
317];
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
321pub enum Predictor {
322 Plain,
324 #[default]
328 Minimising,
329}
330
331#[derive(Debug, Clone, Copy, PartialEq)]
338pub struct Loop {
339 pub start: usize,
341 pub end: usize,
343 pub crossfade: f64,
349}
350
351impl Loop {
352 pub fn new(start: usize, end: usize) -> Loop {
354 Loop {
355 start,
356 end,
357 crossfade: 0.0,
358 }
359 }
360
361 pub fn crossfade(mut self, frames: f64) -> Loop {
362 self.crossfade = frames;
363 self
364 }
365}
366
367#[derive(Debug, Clone)]
369pub struct Options {
370 name: String,
371 root_key: u8,
372 top_note: Option<u8>,
373 predictor: Predictor,
374 loops: Option<Loop>,
375 channels: u16,
376 secondary_start: Option<f64>,
377 shift: Option<u8>,
378 layout: Layout,
379}
380
381impl Options {
382 pub fn new(name: impl Into<String>) -> Options {
385 Options {
386 name: name.into(),
387 root_key: 60,
388 top_note: None,
389 predictor: Predictor::default(),
390 loops: None,
391 channels: 1,
392 secondary_start: None,
393 shift: None,
394 layout: Layout::V2,
395 }
396 }
397
398 pub fn layout(mut self, layout: Layout) -> Options {
401 self.layout = layout;
402 self
403 }
404
405 pub fn secondary_start(mut self, frames: f64) -> Options {
409 self.secondary_start = Some(frames);
410 self
411 }
412
413 pub fn channels(mut self, channels: u16) -> Options {
416 self.channels = channels;
417 self
418 }
419
420 pub fn shift(mut self, bits: u8) -> Options {
424 self.shift = Some(bits);
425 self
426 }
427
428 pub fn loops(mut self, points: Loop) -> Options {
430 self.loops = Some(points);
431 self
432 }
433
434 pub fn root_key(mut self, note: u8) -> Options {
436 self.root_key = note;
437 self
438 }
439
440 pub fn top_note(mut self, note: u8) -> Options {
443 self.top_note = Some(note);
444 self
445 }
446
447 pub fn predictor(mut self, predictor: Predictor) -> Options {
448 self.predictor = predictor;
449 self
450 }
451
452 fn resolved_top_note(&self) -> u8 {
453 self.top_note
454 .unwrap_or_else(|| self.root_key.saturating_add(24).min(127))
455 }
456}
457
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461pub struct Looped {
462 pub at: usize,
464 pub lead: usize,
468 pub crossfade: usize,
470 pub warmup: usize,
472 pub cells: usize,
474}
475
476#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482pub struct Plan {
483 pub layout: Layout,
485 pub channels: usize,
487 pub fields: usize,
490 pub resync_at: usize,
492 pub warmup: usize,
494 pub resync: usize,
496 pub cells_before: usize,
498 pub cells_after: usize,
500 pub looped: Option<Looped>,
502}
503
504impl Plan {
505 const fn units(&self) -> Units {
506 Units {
507 layout: self.layout,
508 channels: self.channels,
509 }
510 }
511
512 pub const fn cell(&self) -> usize {
514 self.units().cell()
515 }
516
517 const fn chunk(&self) -> usize {
519 self.units().chunk()
520 }
521}
522
523fn fields_of(frames: usize) -> Option<usize> {
525 let frames = u64::try_from(frames).ok()?;
526 frames
527 .checked_mul(u64::from(PITCH_DEN))
528 .and_then(|n| round_ratio(n, u64::from(PITCH_NUM)))
529}
530
531fn fields_at(frames: f64) -> Option<usize> {
535 let fields = frames * f64::from(PITCH_DEN) / f64::from(PITCH_NUM);
536 (fields.is_finite() && (0.0..=f64::from(u32::MAX)).contains(&fields))
537 .then_some(fields.round() as usize)
538}
539
540impl Plan {
541 pub fn new(
549 layout: Layout,
550 frames: usize,
551 channels: usize,
552 secondary_start: f64,
553 ) -> Result<Plan, Error> {
554 Plan::modelled(frames, channels)?;
555 let fields = fields_of(frames)
556 .and_then(|f| f.checked_add(RING_OUT))
557 .and_then(|f| f.checked_mul(channels))
558 .ok_or_else(|| size_error(frames))?;
559 let resync_at = Plan::resync_at(secondary_start, channels)?;
560 Plan::lay_out(layout, frames, channels, fields, None, resync_at)
561 }
562
563 pub fn looped(
576 layout: Layout,
577 frames: usize,
578 channels: usize,
579 points: Loop,
580 secondary_start: f64,
581 ) -> Result<Plan, Error> {
582 Plan::modelled(points.end, channels)?;
583 if points.start >= points.end || points.end > frames {
584 return Err(ParseError::OutOfBounds {
585 value: format!("a loop over frames {}..{}", points.start, points.end),
586 bound: format!("a non-empty region of the {frames} frames given"),
587 }
588 .into());
589 }
590 let lattice = |n: usize| fields_of(n).and_then(|f| f.checked_mul(channels));
593 let lattice_at = |n: f64| fields_at(n).and_then(|f| f.checked_mul(channels));
594 let start = lattice(points.start).ok_or_else(|| size_error(points.start))?;
595 let span = points.end - points.start;
598 let length = lattice(span).ok_or_else(|| size_error(points.end))?;
599 let end = start
600 .checked_add(length)
601 .ok_or_else(|| size_error(points.end))?;
602 let units = Units { layout, channels };
603 let (cell, chunk) = (units.cell(), units.chunk());
604 let resync_at = Plan::resync_at(secondary_start, channels)?;
605 if resync_at > start {
606 return Err(ParseError::OutOfBounds {
607 value: format!("a secondary start at field {resync_at}"),
608 bound: format!(
609 "field {start}, where the loop starts, or earlier — the marked \
610 record clears the resync point, so the loop cannot open ahead of it"
611 ),
612 }
613 .into());
614 }
615 let at = start
618 .checked_add(LOOP_LEAD * channels)
619 .zip(resync_at.checked_add(min_resync_gap(layout) * channels))
620 .map(|(ideal, floor)| ideal.max(floor))
621 .ok_or_else(|| size_error(points.start))?;
622 let lead = at - start;
623 let fields = end
624 .checked_add(lead)
625 .ok_or_else(|| size_error(points.end))?;
626 let warmup = band(length, cell, chunk);
627 if length < warmup.saturating_add(cell) {
628 return Err(ParseError::OutOfBounds {
629 value: format!("a {length}-field loop"),
630 bound: format!(
631 "a loop long enough for the {warmup}-field 1:1 run it opens with and \
632 one {cell}-field cell after it"
633 ),
634 }
635 .into());
636 }
637 if !(0.0..=points.start as f64).contains(&points.crossfade) {
638 return Err(ParseError::OutOfBounds {
639 value: format!("a {} frame crossfade", points.crossfade),
640 bound: format!(
641 "the {} frames before the loop starts — the fade compares \
642 each frame with the material one loop length behind it",
643 points.start,
644 ),
645 }
646 .into());
647 }
648 let crossfade = if points.crossfade <= span as f64 {
651 let opens =
652 lattice_at(span as f64 - points.crossfade).ok_or_else(|| size_error(span))?;
653 length.checked_sub(opens).ok_or_else(|| size_error(span))?
654 } else {
655 let before = lattice_at(points.crossfade - span as f64)
656 .ok_or_else(|| size_error(points.start))?;
657 length
658 .checked_add(before)
659 .ok_or_else(|| size_error(points.end))?
660 };
661 if crossfade > start {
662 return Err(ParseError::OutOfBounds {
663 value: format!("a {} frame crossfade", points.crossfade),
664 bound: format!(
665 "the {} frames before the loop starts — the field lattice \
666 leaves no earlier material to compare",
667 points.start,
668 ),
669 }
670 .into());
671 }
672 Plan::lay_out(
673 layout,
674 frames,
675 channels,
676 fields,
677 Some(Looped {
678 at,
679 lead,
680 crossfade,
681 warmup,
682 cells: (length - warmup) / cell,
683 }),
684 resync_at,
685 )
686 }
687
688 fn resync_at(secondary_start: f64, channels: usize) -> Result<usize, Error> {
691 fields_at(secondary_start)
692 .and_then(|f| f.checked_mul(channels))
693 .ok_or_else(|| {
694 ParseError::OutOfBounds {
695 value: format!("a secondary start at frame {secondary_start}"),
696 bound: "a position on the field lattice".into(),
697 }
698 .into()
699 })
700 }
701
702 fn modelled(frames: usize, channels: usize) -> Result<(), Error> {
703 if !(1..=MAX_CHANNELS).contains(&channels) {
704 return Err(ParseError::OutOfBounds {
705 value: format!("{channels} channels"),
706 bound: format!(
707 "1 or {MAX_CHANNELS} — the terminator states one cell size, and all \
708 it can say is whether the cell is doubled"
709 ),
710 }
711 .into());
712 }
713 if frames >= MIN_FRAMES {
714 return Ok(());
715 }
716 Err(ParseError::OutOfBounds {
717 value: format!("{frames} frames"),
718 bound: format!(
719 "the modelled range: at least {MIN_FRAMES} frames, below which the \
720 stream opens a way this crate has not modelled"
721 ),
722 }
723 .into())
724 }
725
726 fn lay_out(
729 layout: Layout,
730 frames: usize,
731 channels: usize,
732 fields: usize,
733 looped: Option<Looped>,
734 resync_at: usize,
735 ) -> Result<Plan, Error> {
736 let units = Units { layout, channels };
737 if fields > units.max_fields() {
738 return Err(size_error(frames).into());
739 }
740 let (cell, chunk) = (units.cell(), units.chunk());
741 let band = |r: usize| band(r, cell, chunk);
742 let head = looped.map_or(fields, |l| l.at);
743 let warmup = band(resync_at);
744 let fits = resync_at >= warmup
745 && head
746 .checked_sub(warmup)
747 .and_then(|rest| resync_at.checked_add(band(rest)))
748 .is_some_and(|end| head >= end);
749 if !fits {
750 return Err(ParseError::OutOfBounds {
751 value: format!("a secondary start at field {resync_at}"),
752 bound: format!(
753 "the {head} fields ahead of the {}, less the 1:1 run at each end",
754 if looped.is_some() {
755 "loop"
756 } else {
757 "terminator"
758 }
759 ),
760 }
761 .into());
762 }
763 let resync = band(head - warmup);
764 Ok(Plan {
765 layout,
766 channels,
767 fields,
768 resync_at,
769 warmup,
770 resync,
771 cells_before: (resync_at - warmup) / cell,
772 cells_after: (head - resync_at - resync) / cell,
773 looped,
774 })
775 }
776}
777
778pub fn default_secondary_start(frames: usize, loops: Option<Loop>) -> f64 {
782 let stop = frames as f64;
783 nsmpproj::repaired_secondary_start(
784 nsmpproj::default_secondary_start(stop),
785 stop,
786 loops.map(|l| nsmpproj::repaired_loop_start(l.start as f64)),
787 )
788}
789
790fn round_ratio(num: u64, den: u64) -> Option<usize> {
792 num.checked_add(den / 2)
793 .and_then(|n| usize::try_from(n / den).ok())
794}
795
796fn frames_of(source: &[i16], channels: usize) -> Result<usize, Error> {
798 if channels == 0 || !source.len().is_multiple_of(channels) {
799 return Err(ParseError::AssertFail(format!(
800 "{} sample(s) is not a whole number of {channels}-channel frames",
801 source.len()
802 ))
803 .into());
804 }
805 Ok(source.len() / channels)
806}
807
808fn size_error(frames: usize) -> ParseError {
809 ParseError::OutOfBounds {
810 value: format!("{frames} frames"),
811 bound: format!("audio whose encoded stream fits {MAX_STREAM_WORDS} words"),
812 }
813}
814
815fn band(r: usize, cell: usize, rmax: usize) -> usize {
823 let residue = if r.is_multiple_of(cell) {
824 cell
825 } else {
826 r % cell
827 };
828 let mut length = if residue == cell {
829 cell
830 } else {
831 residue + cell
832 };
833 while length <= 64 * cell {
836 if (1..=8).any(|j| j * cell <= length && length <= j * rmax) {
837 return length;
838 }
839 length += cell;
840 }
841 length
842}
843
844fn chunks(mut n: usize, chunk: usize) -> Vec<usize> {
847 let mut out = Vec::new();
848 while n > chunk {
849 out.push(chunk);
850 n -= chunk;
851 }
852 out.push(n);
853 out
854}
855
856#[derive(Debug, Clone)]
859struct Quantised {
860 values: Vec<i32>,
862 shift: i32,
864 peak: i32,
867}
868
869const MAX_PEAK: i64 = (1 << 23) - 1;
872
873fn ramp_in(fields: &mut [i64]) {
876 let cube = |n: usize| (n * n * n) as i64;
877 for (f, value) in fields.iter_mut().enumerate().take(RAMP_IN) {
878 *value = *value * cube(f) / cube(RAMP_IN);
879 }
880}
881
882fn bake_loop(raw: &mut [i64], at: usize, lead: usize, crossfade: usize) {
892 let fields = raw.len();
893 let end = fields - lead;
894 let length = fields - at;
895 let span = crossfade as i64;
896 for k in 0..crossfade {
897 let f = end - crossfade + k;
898 let (near, far) = (raw[f], raw[f - length]);
899 let step = (far - near) * k as i64;
900 raw[f] = near + (2 * step + span * step.signum()) / (2 * span);
901 }
902 for k in 0..lead {
905 raw[end + k] = raw[at - lead + k];
906 }
907}
908
909fn quantise(source: &[i16], plan: &Plan, forced: Option<u8>) -> Quantised {
917 let channels = plan.channels;
918 let per = plan.fields / channels;
919 let mut raw = vec![0i64; plan.fields];
920 let mut sums = vec![0f64; plan.fields];
923 let mut lane: Vec<i16> = Vec::with_capacity(source.len().div_ceil(channels));
924 for channel in 0..channels {
925 lane.clear();
926 lane.extend(source.iter().skip(channel).step_by(channels).copied());
927 let accumulated: Vec<f64> = (0..per).map(|f| kernel::accumulate(&lane, f)).collect();
928 let mut fields: Vec<i64> = accumulated.iter().map(|sum| sum.trunc() as i64).collect();
929 ramp_in(&mut fields);
930 match &plan.looped {
931 Some(points) => bake_loop(
932 &mut fields,
933 points.at / channels,
934 points.lead / channels,
935 points.crossfade / channels,
936 ),
937 None => fields[per - RING_OUT..].fill(0),
938 }
939 for (f, (value, sum)) in fields.into_iter().zip(accumulated).enumerate() {
940 let at = f * channels + channel;
941 raw[at] = value;
942 sums[at] = if value == sum.trunc() as i64 {
944 sum
945 } else {
946 value as f64
947 };
948 }
949 }
950 let mut shift = peak_shift(&raw, PEAK_WIDTH);
951 if spends_extra_bit(&raw, plan) {
952 shift += 1;
953 }
954 if let Some(bits) = forced {
955 shift = i32::from(bits);
956 }
957
958 let opening = plan.looped.map(|l| l.at..l.at + l.warmup);
963 let content = |f: usize| {
964 ((f >= plan.warmup && f < plan.resync_at) || f >= plan.resync_at + plan.resync)
965 && !opening.as_ref().is_some_and(|run| run.contains(&f))
966 };
967 let extreme = (0..plan.fields)
968 .filter(|&f| content(f))
969 .fold(None, |best: Option<usize>, f| match best {
970 Some(b) if sums[f].abs() <= sums[b].abs() => Some(b),
971 _ => Some(f),
972 });
973 let signed = extreme
974 .map_or(0, |f| raw[f] >> 2)
975 .clamp(-MAX_PEAK - 1, MAX_PEAK) as i32;
976 let peak = match plan.layout.signed_peak() {
977 true => signed,
978 false => signed.abs(),
979 };
980
981 Quantised {
982 values: raw.iter().map(|&v| (v >> shift) as i32).collect(),
983 shift,
984 peak,
985 }
986}
987
988fn width_of(low: i64, high: i64) -> u8 {
991 let mut w = MIN_WIDTH;
992 while i128::from(low) < -(1i128 << (w - 1)) || i128::from(high) > (1i128 << (w - 1)) - 1 {
993 w += 1;
994 }
995 w
996}
997
998#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1000struct Spec {
1001 one_to_one: bool,
1002 width: u8,
1003 order: u8,
1004 mark: bool,
1006 first: usize,
1007 count: usize,
1008}
1009
1010impl Spec {
1011 fn span(&self, units: Units) -> usize {
1013 units.span(self.count, self.width)
1014 }
1015}
1016
1017fn residual(values: &[i32], at: usize, order: u8, stride: usize) -> i64 {
1022 DIFFERENCE[usize::from(order)]
1023 .iter()
1024 .enumerate()
1025 .map(|(j, &c)| match at.checked_sub(j * stride) {
1026 Some(k) => i64::from(c) * i64::from(values[k]),
1027 None => 0,
1028 })
1029 .sum()
1030}
1031
1032fn width_at(values: &[i32], first: usize, order: u8, cell: usize, stride: usize) -> u8 {
1035 let mut low = 0i64;
1036 let mut high = 0i64;
1037 for at in first..first + cell {
1038 let e = residual(values, at, order, stride);
1039 low = low.min(e);
1040 high = high.max(e);
1041 }
1042 width_of(low, high)
1043}
1044
1045fn widths_at(
1048 values: &[i32],
1049 first: usize,
1050 predictor: Predictor,
1051 cell: usize,
1052 stride: usize,
1053) -> Vec<u8> {
1054 let orders = match predictor {
1055 Predictor::Plain => 1,
1056 Predictor::Minimising => DIFFERENCE.len(),
1057 };
1058 (0..orders as u8)
1059 .map(|order| width_at(values, first, order, cell, stride))
1060 .collect()
1061}
1062
1063fn choose_order(widths: &[u8], extending: Option<(u8, u8)>) -> (u8, u8) {
1068 let narrowest = *widths.iter().min().unwrap_or(&MIN_WIDTH);
1069 let reaches = |order: u8| widths.get(usize::from(order)) == Some(&narrowest);
1070 let order = extending
1071 .filter(|&(order, width)| width == narrowest && reaches(order))
1072 .map_or_else(
1073 || (0..widths.len() as u8).find(|&o| reaches(o)).unwrap_or(0),
1074 |(order, _)| order,
1075 );
1076 (order, narrowest)
1077}
1078
1079fn records(values: &[i32], plan: &Plan, predictor: Predictor) -> Result<(Vec<Spec>, usize), Error> {
1085 let mut out = Vec::new();
1086 let mut at = 0usize;
1087 let (cell, chunk, stride) = (plan.cell(), plan.chunk(), plan.channels);
1088
1089 let one_to_one = |out: &mut Vec<Spec>, at: &mut usize, fields: usize| {
1090 for count in chunks(fields, chunk) {
1091 let mut low = 0i64;
1092 let mut high = 0i64;
1093 for &v in &values[*at..*at + count] {
1094 low = low.min(i64::from(v));
1095 high = high.max(i64::from(v));
1096 }
1097 out.push(Spec {
1098 one_to_one: true,
1099 width: width_of(low, high),
1100 order: 0,
1101 mark: false,
1102 first: *at,
1103 count,
1104 });
1105 *at += count;
1106 }
1107 };
1108
1109 let content = |out: &mut Vec<Spec>, at: &mut usize, cells: usize| {
1113 let mut run: Option<Spec> = None;
1114 for index in 0..cells {
1115 let first = *at + index * cell;
1116 let widths = widths_at(values, first, predictor, cell, stride);
1117 let (order, width) = choose_order(&widths, run.map(|r| (r.order, r.width)));
1118 match run {
1119 Some(ref mut record)
1120 if (record.order, record.width) == (order, width)
1121 && record.count + cell <= MAX_COUNT =>
1122 {
1123 record.count += cell;
1124 }
1125 _ => {
1126 out.extend(run.take());
1127 run = Some(Spec {
1128 one_to_one: false,
1129 width,
1130 order,
1131 mark: false,
1132 first,
1133 count: cell,
1134 });
1135 }
1136 }
1137 }
1138 out.extend(run);
1139 *at += cells * cell;
1140 };
1141
1142 one_to_one(&mut out, &mut at, plan.warmup);
1143 content(&mut out, &mut at, plan.cells_before);
1144 let resync_record = out.len();
1145 one_to_one(&mut out, &mut at, plan.resync);
1146 content(&mut out, &mut at, plan.cells_after);
1147 if let Some(points) = &plan.looped {
1148 let opening = out.len();
1149 one_to_one(&mut out, &mut at, points.warmup);
1150 out[opening].mark = true;
1151 content(&mut out, &mut at, points.cells);
1152 pad_to_packet(&mut out, opening, plan.units())?;
1153 }
1154 if at != plan.fields {
1155 return Err(ParseError::AssertFail(format!(
1156 "the record plan covered {at} of {} fields",
1157 plan.fields
1158 ))
1159 .into());
1160 }
1161 Ok((out, resync_record))
1162}
1163
1164fn pad_to_packet(specs: &mut Vec<Spec>, opening: usize, units: Units) -> Result<(), Error> {
1175 let cell = units.cell();
1176 let packet = units.packet_words();
1177 let words = |specs: &[Spec]| specs.iter().map(|s| s.span(units)).sum::<usize>();
1178 let mut pad = (packet - words(&specs[opening..]) % packet) % packet;
1179
1180 let splittable = |spec: &Spec| !spec.one_to_one && spec.count > cell;
1181 while pad > 0 && specs[opening..].iter().any(splittable) {
1182 let mut at = opening;
1183 while pad > 0 && at < specs.len() {
1184 let spec = specs[at];
1185 if splittable(&spec) {
1186 let head = spec.count / cell / 2 * cell;
1187 specs[at].count = head;
1188 specs.insert(
1189 at + 1,
1190 Spec {
1191 first: spec.first + head,
1192 count: spec.count - head,
1193 ..spec
1194 },
1195 );
1196 pad -= 1;
1197 }
1198 at += 1;
1199 }
1200 }
1201
1202 let cap = widen_cap(units.layout);
1203 for spec in specs[opening..].iter_mut() {
1204 if pad == 0 {
1205 break;
1206 }
1207 if spec.one_to_one {
1208 continue;
1209 }
1210 let count = spec.count;
1211 let step = |width: u8| units.span(count, width + 1) - units.span(count, width);
1212 while spec.width < cap && step(spec.width) <= pad {
1213 pad -= step(spec.width);
1214 spec.width += 1;
1215 }
1216 }
1217 if pad > 0 {
1218 return Err(ParseError::OutOfBounds {
1219 value: format!("a loop of {} record(s)", specs.len() - opening),
1220 bound: format!(
1221 "a loop with {pad} more word(s) of room in it — the encoded loop has to \
1222 be whole packets long, and no record of this one may be widened past \
1223 {cap}"
1224 ),
1225 }
1226 .into());
1227 }
1228 Ok(())
1229}
1230
1231const fn widen_cap(layout: Layout) -> u8 {
1241 match layout {
1242 Layout::V2 | Layout::V3 => 13,
1243 Layout::V4 => 14,
1244 }
1245}
1246
1247struct Stream {
1249 words: Vec<u8>,
1250 first_record: usize,
1251 resync: usize,
1252 mark: Option<usize>,
1254 terminator: usize,
1255}
1256
1257fn pack(
1264 specs: &[Spec],
1265 values: &[i32],
1266 resync_record: usize,
1267 preamble: usize,
1268 plan: &Plan,
1269) -> Result<Stream, Error> {
1270 let units = plan.units();
1271 let (word, header) = (units.word(), plan.layout.header_len());
1272 let chain: usize = specs.iter().map(|s| s.span(units)).sum::<usize>() + 1;
1273 let need = (chain + units.min_lead())
1274 .checked_mul(word)
1275 .and_then(|bytes| bytes.checked_add(header))
1276 .ok_or_else(|| ParseError::OutOfBounds {
1277 value: format!("a chain of {chain} words"),
1278 bound: "a stroke payload of addressable length".into(),
1279 })?;
1280 let mut payload = preamble;
1281 while payload < need {
1282 payload += packet_len(plan.layout);
1283 }
1284 if !(payload - header).is_multiple_of(word) {
1285 return Err(ParseError::AssertFail(format!(
1286 "a {preamble}-byte preamble puts the word stream off a word boundary; the \
1287 sections in front of the stroke are not whole words"
1288 ))
1289 .into());
1290 }
1291 let total = (payload - header) / word;
1292 if total > MAX_STREAM_WORDS {
1293 return Err(ParseError::OutOfBounds {
1294 value: format!("a stream of {total} words"),
1295 bound: format!(
1296 "{MAX_STREAM_WORDS} words, the reach of the stroke header's 16-bit word \
1297 directory"
1298 ),
1299 }
1300 .into());
1301 }
1302
1303 let mut words = vec![0u8; total * word];
1304 let lead = total - chain;
1305 let mut at = lead;
1306 let mut resync = lead;
1307 let mut mark = None;
1308 for (index, spec) in specs.iter().enumerate() {
1309 if index == resync_record {
1310 resync = at;
1311 }
1312 if spec.mark {
1313 mark = Some(at);
1314 }
1315 write_record(&mut words, at, spec, values, units);
1316 at += spec.span(units);
1317 }
1318 if at.checked_add(1) != Some(total) {
1321 return Err(ParseError::AssertFail(format!(
1322 "the record chain ended at word {at} of {total}"
1323 ))
1324 .into());
1325 }
1326 let terminator = (1u32 << 23) | plan.cell() as u32;
1327 words[at * word..(at + 1) * word].copy_from_slice(&terminator.to_be_bytes()[4 - word..]);
1328
1329 Ok(Stream {
1330 words,
1331 first_record: lead,
1332 resync,
1333 mark,
1334 terminator: at,
1335 })
1336}
1337
1338fn write_record(words: &mut [u8], at: usize, spec: &Spec, values: &[i32], units: Units) {
1346 let (word, bits) = (units.word(), units.word_bits());
1347 let head = (u32::from(spec.one_to_one) << 23)
1348 | (u32::from(spec.width - 1) << 19)
1349 | (u32::from(spec.mark) << 18)
1350 | (u32::from(spec.order) << 14)
1351 | spec.count as u32;
1352 words[at * word..(at + 1) * word].copy_from_slice(&head.to_be_bytes()[4 - word..]);
1353
1354 let stored = |k: usize| -> u64 {
1355 let value = residual(values, spec.first + k, spec.order, units.channels);
1356 (value as u64) & ((1u64 << spec.width) - 1)
1357 };
1358 let put = |words: &mut [u8], mut bit: usize, raw: u64| {
1359 for b in (0..spec.width).rev() {
1360 if raw >> b & 1 != 0 {
1361 words[bit / 8] |= 1 << (7 - bit % 8);
1362 }
1363 bit += 1;
1364 }
1365 };
1366
1367 if !units.splits() {
1368 for k in 0..spec.count {
1369 put(
1370 words,
1371 (at + 1) * bits + k * usize::from(spec.width),
1372 stored(k),
1373 );
1374 }
1375 return;
1376 }
1377 let per = spec.count / 2;
1380 let half = units.half(spec.count, spec.width);
1381 let mut packed = vec![0u8; half * word];
1382 for channel in 0..2 {
1383 packed.fill(0);
1384 for k in 0..per {
1385 put(
1386 &mut packed,
1387 k * usize::from(spec.width),
1388 stored(2 * k + channel),
1389 );
1390 }
1391 for w in 0..half {
1392 let to = (at + 1 + 2 * w + channel) * word;
1393 words[to..to + word].copy_from_slice(&packed[w * word..(w + 1) * word]);
1394 }
1395 }
1396}
1397
1398fn statistic_a(peak: u32, shift: i32, gain: u64) -> (u32, u8) {
1415 let peak = u64::from(peak.max(1));
1416 let bits = 64 - peak.leading_zeros() as i32;
1417 let exact_power = i32::from(peak.is_power_of_two());
1418 let reciprocal = (1u64 << (21 + bits + (1 - exact_power))) / peak;
1419 let mantissa = (reciprocal * gain) >> (super::zone::GAIN_BITS + 3);
1420 (
1421 (mantissa % (1 << 24)) as u32,
1422 (22 + shift - bits + exact_power) as u8,
1423 )
1424}
1425
1426fn stroke_header(
1428 layout: Layout,
1429 zone: &NewZone<'_>,
1430 encoded: &Encoded,
1431 body_at: usize,
1432 file_peak: u32,
1433) -> Vec<u8> {
1434 let (q, stream) = (&encoded.q, &encoded.stream);
1435 let mut head = vec![0u8; layout.header_len()];
1436 head[0..4].copy_from_slice(&zone.global_id.to_be_bytes());
1437 head[super::stroke::ROOT_KEY] = zone.root_key;
1438 head[6..8].copy_from_slice(&[0x88, 0xba]);
1440 head[8] = zone.channels as u8;
1443
1444 let (mantissa, exponent) =
1445 statistic_a(file_peak, q.shift, gain_units(gain_decibels(zone.gain)));
1446 head[codec::MANTISSA_AT..codec::MANTISSA_AT + 3].copy_from_slice(&mantissa.to_be_bytes()[1..]);
1447 head[codec::STAT_A_EXP_AT] = exponent;
1448 head[codec::PEAK_AT..codec::PEAK_AT + 3].copy_from_slice(&(q.peak as u32).to_be_bytes()[1..]);
1449
1450 let base = (body_at + layout.header_len()) / layout.word() % WRAP;
1451 let pointer = |word: usize| ((base + word) % WRAP) as u16;
1452 let directory = [
1455 pointer(stream.first_record),
1456 pointer(stream.resync),
1457 pointer(stream.mark.unwrap_or(stream.terminator)),
1458 pointer(stream.terminator),
1459 ];
1460 for (i, p) in directory.iter().enumerate() {
1461 let at = codec::SEEK_AT + codec::SEEK_STRIDE * i;
1462 head[at..at + 2].copy_from_slice(&p.to_be_bytes());
1463 if i < 3 {
1465 head[at + 2] = 0x80;
1466 }
1467 }
1468 let tails = [gain_decibels(zone.gain), zone.loop_decay];
1470 for (at, value) in codec::TAIL_FLOATS_AT.iter().zip(tails) {
1471 if let Some(slot) = head.get_mut(*at..at + 4) {
1472 slot.copy_from_slice(&value.to_be_bytes());
1473 }
1474 }
1475 head
1476}
1477
1478pub const DEFAULT_LOOP_DECAY: f32 = 20.0;
1480
1481struct Encoded {
1487 q: Quantised,
1488 stream: Stream,
1489}
1490
1491fn encode_stroke(
1493 layout: Layout,
1494 zone: &NewZone<'_>,
1495 preamble: usize,
1496 predictor: Predictor,
1497) -> Result<Encoded, Error> {
1498 let channels = usize::from(zone.channels);
1499 let frames = frames_of(zone.source, channels)?;
1500 let plan = match zone.loops {
1501 Some(points) => Plan::looped(layout, frames, channels, points, zone.secondary_start)?,
1502 None => Plan::new(layout, frames, channels, zone.secondary_start)?,
1503 };
1504 if let Some(bits) = zone.shift {
1505 if i32::from(bits) > codec::SHIFT_LIMIT {
1506 return Err(ParseError::OutOfBounds {
1507 value: format!("a quantiser shift of {bits} bits"),
1508 bound: format!("0 through {} bits", codec::SHIFT_LIMIT),
1509 }
1510 .into());
1511 }
1512 }
1513 let q = quantise(zone.source, &plan, zone.shift);
1514 let low = q.values.iter().copied().min().unwrap_or(0);
1515 let high = q.values.iter().copied().max().unwrap_or(0);
1516 if width_of(i64::from(low), i64::from(high)) > MAX_STORED_WIDTH {
1517 return Err(ParseError::OutOfBounds {
1518 value: format!(
1519 "a quantiser shift of {} bits for fields spanning {low}..={high}",
1520 q.shift
1521 ),
1522 bound: format!("values that fit the stream's {MAX_STORED_WIDTH}-bit fields"),
1523 }
1524 .into());
1525 }
1526 let (specs, resync_record) = records(&q.values, &plan, predictor)?;
1527 let stream = pack(&specs, &q.values, resync_record, preamble, &plan)?;
1528 Ok(Encoded { q, stream })
1529}
1530
1531fn encode_strokes(
1533 layout: Layout,
1534 zones: &[NewZone<'_>],
1535 predictor: Predictor,
1536 cat_len: usize,
1537 map_len: usize,
1538) -> Result<(Vec<Encoded>, u32), Error> {
1539 let encoded = zones
1540 .iter()
1541 .enumerate()
1542 .map(|(index, zone)| {
1543 let chain = super::Chain::written_for(layout);
1544 let preamble = super::stroke::header_len(layout, chain, index, cat_len, map_len);
1545 encode_stroke(layout, zone, preamble, predictor)
1546 })
1547 .collect::<Result<Vec<_>, Error>>()?;
1548 let peak = encoded
1549 .iter()
1550 .map(|e| e.q.peak.unsigned_abs())
1551 .max()
1552 .unwrap_or(1);
1553 Ok((encoded, peak))
1554}
1555
1556fn stroke_payload(
1563 layout: Layout,
1564 zone: &NewZone<'_>,
1565 encoded: &Encoded,
1566 body_at: usize,
1567 file_peak: u32,
1568) -> Result<Vec<u8>, Error> {
1569 midi_note("root key", zone.root_key)?;
1570 body_at
1571 .checked_add(layout.header_len())
1572 .ok_or_else(|| ParseError::OutOfBounds {
1573 value: format!("body offset {body_at}"),
1574 bound: "an addressable stroke header".into(),
1575 })?;
1576 let mut payload = stroke_header(layout, zone, encoded, body_at, file_peak);
1577 payload.extend_from_slice(&encoded.stream.words);
1578 Ok(payload)
1579}
1580
1581const HDR_VERSION: u8 = 9;
1584const CAT_VERSION: u8 = 5;
1585const STK_VERSION: u8 = 9;
1586const STY_VERSION: u8 = 5;
1587const CONTAINER_VERSION: u8 = 11;
1588
1589const CATEGORY: u8 = 0x0f;
1592
1593fn hdr(name: &str) -> Result<Section, Error> {
1595 let mut payload = vec![0u8; 111];
1596 payload[0..6].copy_from_slice(&[0x00, 0x01, 0xb4, 0x00, 0x06, 0x50]);
1598 super::StringField::NAME.write(&mut payload, name)?;
1599 Ok(Section {
1600 tag: *section::HDR,
1601 version: HDR_VERSION,
1602 payload,
1603 })
1604}
1605
1606fn cat() -> Section {
1608 let mut payload = vec![CATEGORY, 0x00, 0x00, 0x00, 0x01];
1609 for label in [&b"Production"[..], &b"Origin"[..]] {
1610 payload.push(label.len() as u8);
1611 payload.extend_from_slice(label);
1612 }
1613 while !payload.len().is_multiple_of(3) {
1616 payload.push(0);
1617 }
1618 Section {
1619 tag: *section::CAT,
1620 version: CAT_VERSION,
1621 payload,
1622 }
1623}
1624
1625fn map(map_gain: u32, zones: &[ZoneRecord]) -> Result<Section, Error> {
1630 let mut payload = vec![0u8; super::zone::RECORDS_AT + super::zone::RECORD_LEN * zones.len()];
1631 let mut keys = super::keymap::KeyTable::NEUTRAL;
1632 keys.instrument = super::keymap::Level::new(map_gain, 0)?;
1633 payload[..super::zone::COUNT_AT].copy_from_slice(&keys.prefix());
1634 payload[super::zone::COUNT_AT] = zones.len() as u8;
1635 for (index, record) in zones.iter().enumerate() {
1637 let at = super::zone::RECORDS_AT + super::zone::RECORD_LEN * index;
1638 payload[at + 2] = record.id;
1639 payload[at + 3..at + 6].copy_from_slice(&record.gain.to_be_bytes()[1..]);
1642 payload[at + 9] = record.top_note;
1643 payload[at + 10..at + 12].copy_from_slice(&super::zone::REL_STRENGTH_DEFAULT.to_be_bytes());
1647 }
1648 Ok(Section {
1649 tag: *section::MAP,
1650 version: super::keymap::VERSION,
1651 payload,
1652 })
1653}
1654
1655fn sty(preset: Preset) -> Result<Section, Error> {
1657 if preset.velocity_to_amplitude >= super::sty::VELOCITY_LEVELS
1658 || preset.velocity_to_timbre >= super::sty::VELOCITY_LEVELS
1659 {
1660 return Err(ParseError::OutOfBounds {
1661 value: format!(
1662 "velocity levels {} and {}",
1663 preset.velocity_to_amplitude, preset.velocity_to_timbre
1664 ),
1665 bound: format!("levels below {}", super::sty::VELOCITY_LEVELS),
1666 }
1667 .into());
1668 }
1669 let mut payload = vec![0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00];
1670 payload[3] = u8::from(preset.dynamics_enabled);
1671 payload[4] = preset.velocity_to_amplitude;
1672 payload[5] = preset.velocity_to_timbre;
1673 Ok(Section {
1674 tag: *section::STY,
1675 version: STY_VERSION,
1676 payload,
1677 })
1678}
1679
1680struct WideSchema {
1688 container: u32,
1689 container_payload: [u8; 4],
1692 hdr: u32,
1693 map: u32,
1694 key_stride: usize,
1697 map_gap: &'static [u8],
1699 map_tail: &'static [u8],
1701 sty: u32,
1702 sty_payload: &'static [u8],
1705 sty_dynamics: &'static [(usize, u8)],
1707}
1708
1709fn level(gain: u32) -> [u8; super::keymap::RECORD_LEN] {
1712 let mut out = [0u8; super::keymap::RECORD_LEN];
1713 out[..3].copy_from_slice(&gain.to_be_bytes()[1..]);
1714 out
1715}
1716
1717const STY_V3_PAYLOAD: [u8; super::sty::V3_LEN] = [
1718 0x00, 0x00, 0x7f, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x02, 0x00,
1719 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
1720];
1721
1722const STY_V4_PAYLOAD: [u8; super::sty::V4_LEN_LONG] = [
1723 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1724 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1725 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1726 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1727 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e,
1728 0x1e, 0x1e, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
1729 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1730];
1731
1732const STY_V3_DYNAMICS: [(usize, u8); 4] = [(4, 43), (12, 74), (14, 1), (16, 74)];
1733const STY_V4_DYNAMICS: [(usize, u8); 5] = [(3, 1), (4, 1), (85, 74), (86, 82), (87, 90)];
1734
1735fn wide_schema(layout: Layout) -> Option<WideSchema> {
1737 match layout {
1738 Layout::V2 => None,
1739 Layout::V3 => Some(WideSchema {
1740 container: 30,
1741 container_payload: [0x00, 0x02, 0x00, 0x0c],
1742 hdr: 10,
1743 map: 14,
1744 key_stride: super::keymap::RECORD_LEN,
1745 map_gap: &[],
1746 map_tail: &[0x00],
1747 sty: super::sty::VERSION_V3,
1748 sty_payload: &STY_V3_PAYLOAD,
1749 sty_dynamics: &STY_V3_DYNAMICS,
1750 }),
1751 Layout::V4 => Some(WideSchema {
1752 container: 40,
1753 container_payload: [0x00, 0x02, 0x00, 0x05],
1754 hdr: 11,
1755 map: 21,
1756 key_stride: super::keymap::RECORD_LEN + 4,
1757 map_gap: &[
1758 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
1759 0x02, 0x02, 0x02, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00,
1760 0x00, 0x00, 0x00,
1761 ],
1762 map_tail: &[0x00, 0x00, 0x00, 0x01, 0x00, 0x00],
1763 sty: super::sty::VERSION_V4,
1764 sty_payload: &STY_V4_PAYLOAD,
1765 sty_dynamics: &STY_V4_DYNAMICS,
1766 }),
1767 }
1768}
1769
1770fn hdr4(schema: &WideSchema, name: &str) -> Result<Section4, Error> {
1773 let mut payload = vec![0u8; 112];
1774 payload[4..6].copy_from_slice(&[0x06, 0x50]);
1776 super::StringField::NAME_V3.write(&mut payload, name)?;
1777 Ok(Section4 {
1778 tag: *section::HDR4,
1779 version: schema.hdr,
1780 payload,
1781 })
1782}
1783
1784fn cat4() -> Section4 {
1787 let mut payload = vec![0u8; 8];
1788 payload[0] = CATEGORY;
1789 Section4 {
1790 tag: *section::CAT4,
1791 version: 7,
1792 payload,
1793 }
1794}
1795
1796fn map4(schema: &WideSchema, map_gain: u32, zones: &[WideZoneRecord]) -> Section4 {
1803 let mut payload = Vec::with_capacity(
1804 super::keymap::RECORD_LEN
1805 + super::keymap::KEYS * schema.key_stride
1806 + schema.map_gap.len()
1807 + 1
1808 + super::zone::WIDE_RECORD_LEN * zones.len()
1809 + schema.map_tail.len(),
1810 );
1811 payload.extend_from_slice(&level(map_gain));
1812 for key in 0..super::keymap::KEYS as u8 {
1813 payload.extend_from_slice(&level(super::zone::GAIN_UNITY));
1814 payload.extend(std::iter::repeat_n(
1815 key,
1816 schema.key_stride - super::keymap::RECORD_LEN,
1817 ));
1818 }
1819 payload.extend_from_slice(schema.map_gap);
1820 payload.push(zones.len() as u8);
1821 for record in zones {
1822 payload.extend_from_slice(&record.bytes());
1823 }
1824 payload.extend_from_slice(schema.map_tail);
1825 Section4 {
1826 tag: *section::MAP4,
1827 version: schema.map,
1828 payload,
1829 }
1830}
1831
1832fn sty4(schema: &WideSchema, preset: Preset) -> Section4 {
1834 let mut payload = schema.sty_payload.to_vec();
1835 if preset.dynamics_enabled {
1836 for &(at, value) in schema.sty_dynamics {
1837 payload[at] = value;
1838 }
1839 }
1840 Section4 {
1841 tag: *section::STY4,
1842 version: schema.sty,
1843 payload,
1844 }
1845}
1846
1847fn meta4(chain_len: usize) -> Section4 {
1850 let mut payload = vec![0u8; super::meta::LEN];
1851 payload[0..2].copy_from_slice(&2u16.to_be_bytes());
1852 payload[2..6].copy_from_slice(&(chain_len as u32).to_be_bytes());
1853 Section4 {
1854 tag: *section::META4,
1855 version: super::meta::VERSION,
1856 payload,
1857 }
1858}
1859
1860#[derive(Debug, Clone, Copy, PartialEq)]
1863pub struct NewZone<'a> {
1864 pub source: &'a [i16],
1867 pub channels: u16,
1869 pub root_key: u8,
1871 pub top_note: u8,
1874 pub global_id: u32,
1877 pub loops: Option<Loop>,
1879 pub secondary_start: f64,
1883 pub shift: Option<u8>,
1886 pub loop_decay: f32,
1893 pub gain: f64,
1901}
1902
1903pub fn instrument(source: &[i16], options: &Options) -> Result<crate::Sample, Error> {
1907 midi_note("root key", options.root_key)?;
1908 let frames = frames_of(source, usize::from(options.channels))?;
1909 let secondary_start = options
1910 .secondary_start
1911 .unwrap_or_else(|| default_secondary_start(frames, options.loops));
1912 multi_zone(
1913 Instrument {
1914 name: &options.name,
1915 map_gain: 1.0,
1916 predictor: options.predictor,
1917 layout: options.layout,
1918 preset: Preset::default(),
1919 },
1920 &[NewZone {
1921 source,
1922 channels: options.channels,
1923 root_key: options.root_key,
1924 top_note: options.resolved_top_note(),
1925 global_id: 1,
1926 loops: options.loops,
1927 secondary_start,
1928 shift: options.shift,
1929 gain: 1.0,
1930 loop_decay: DEFAULT_LOOP_DECAY,
1931 }],
1932 )
1933}
1934
1935#[derive(Debug, Clone, Copy)]
1937pub struct Instrument<'a> {
1938 pub name: &'a str,
1940 pub map_gain: f64,
1944 pub predictor: Predictor,
1946 pub layout: Layout,
1948 pub preset: Preset,
1950}
1951
1952#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1954pub struct Preset {
1955 pub dynamics_enabled: bool,
1957 pub velocity_to_amplitude: u8,
1959 pub velocity_to_timbre: u8,
1961}
1962
1963impl Default for Preset {
1964 fn default() -> Preset {
1965 Preset {
1966 dynamics_enabled: false,
1967 velocity_to_amplitude: 1,
1968 velocity_to_timbre: 1,
1969 }
1970 }
1971}
1972
1973pub fn multi_zone(
1979 instrument: Instrument<'_>,
1980 zones: &[NewZone<'_>],
1981) -> Result<crate::Sample, Error> {
1982 match wide_schema(instrument.layout) {
1983 Some(schema) => wide_chain(instrument, zones, &schema).map(crate::Sample::V3),
1984 None => narrow_chain(instrument, zones).map(crate::Sample::V2),
1985 }
1986}
1987
1988fn container(layout: Layout) -> Header {
1990 Header {
1991 generation: Generation::V1,
1992 tag: *b"nsmp",
1993 location: 0xFFFF_FFFF,
1994 aux: AUX,
1995 version: version(layout),
1996 }
1997}
1998
1999fn narrow_chain(instrument: Instrument<'_>, zones: &[NewZone<'_>]) -> Result<Cbin<Sample>, Error> {
2000 let table = zone_table(zones)?;
2001 let hdr = hdr(instrument.name)?;
2002 let cat = cat();
2003 let map = map(map_gain_units(instrument.map_gain), &table)?;
2004 let cat_len = cat.payload.len();
2008 let map_len = map.payload.len();
2009
2010 let mut sections = vec![
2011 Section {
2012 tag: *section::CONTAINER,
2013 version: CONTAINER_VERSION,
2014 payload: Vec::new(),
2015 },
2016 hdr,
2017 cat,
2018 map,
2019 ];
2020 let (encoded, file_peak) =
2021 encode_strokes(Layout::V2, zones, instrument.predictor, cat_len, map_len)?;
2022 let mut body_at: usize = sections.iter().map(Section::encoded_len).sum();
2023 for (zone, stroke) in zones.iter().zip(&encoded) {
2024 let payload = stroke_payload(
2025 Layout::V2,
2026 zone,
2027 stroke,
2028 body_at + section::HEADER_LEN,
2029 file_peak,
2030 )?;
2031 body_at += section::HEADER_LEN + payload.len();
2032 sections.push(Section {
2033 tag: *section::STK,
2034 version: STK_VERSION,
2035 payload,
2036 });
2037 }
2038 sections.push(sty(instrument.preset)?);
2039
2040 Ok(Cbin {
2041 header: container(Layout::V2),
2042 body: Sample { sections },
2043 })
2044}
2045
2046const STK4_VERSION: u32 = 11;
2048
2049fn wide_chain(
2050 instrument: Instrument<'_>,
2051 zones: &[NewZone<'_>],
2052 schema: &WideSchema,
2053) -> Result<Cbin<SampleV3>, Error> {
2054 let layout = instrument.layout;
2055 let table = wide_zone_table(zones)?;
2056 let hdr = hdr4(schema, instrument.name)?;
2057 let cat = cat4();
2058 let map = map4(schema, map_gain_units(instrument.map_gain), &table);
2059 let cat_len = cat.payload.len();
2060 let map_len = map.payload.len();
2061
2062 let mut sections = vec![
2063 Section4 {
2064 tag: *section::CONTAINER4,
2065 version: schema.container,
2066 payload: schema.container_payload.to_vec(),
2067 },
2068 hdr,
2069 cat,
2070 map,
2071 ];
2072 let (encoded, file_peak) =
2073 encode_strokes(layout, zones, instrument.predictor, cat_len, map_len)?;
2074 let mut body_at: usize = sections.iter().map(Section4::encoded_len).sum();
2075 for (zone, stroke) in zones.iter().zip(&encoded) {
2076 let payload = stroke_payload(
2077 layout,
2078 zone,
2079 stroke,
2080 body_at + section::HEADER4_LEN,
2081 file_peak,
2082 )?;
2083 body_at += section::HEADER4_LEN + payload.len();
2084 sections.push(Section4 {
2085 tag: *section::STK4,
2086 version: STK4_VERSION,
2087 payload,
2088 });
2089 }
2090 sections.push(sty4(schema, instrument.preset));
2091 let chain_len: usize = sections.iter().map(Section4::encoded_len).sum();
2092 sections.push(meta4(chain_len));
2093
2094 Ok(Cbin {
2095 header: container(layout),
2096 body: SampleV3 { sections },
2097 })
2098}
2099
2100struct WideZoneRecord {
2102 root_key: u8,
2103 top_note: u8,
2104 low_note: u8,
2105 global_id: u32,
2106}
2107
2108impl WideZoneRecord {
2109 fn bytes(&self) -> [u8; super::zone::WIDE_RECORD_LEN] {
2110 let mut r = [0u8; super::zone::WIDE_RECORD_LEN];
2111 r[0] = self.root_key;
2112 r[1] = self.top_note;
2113 r[2] = self.low_note;
2114 r[7] = 1;
2116 r[8..12].copy_from_slice(&self.global_id.to_be_bytes());
2117 r[12..14].copy_from_slice(&super::zone::REL_STRENGTH_DEFAULT.to_be_bytes());
2120 let full = super::zone::VelocityWindow::FULL;
2121 r[14] = full.low;
2122 r[15] = full.high;
2123 r
2124 }
2125}
2126
2127fn wide_zone_table(zones: &[NewZone<'_>]) -> Result<Vec<WideZoneRecord>, Error> {
2132 let table = zone_table(zones)?;
2133 Ok(table
2134 .iter()
2135 .enumerate()
2136 .map(|(index, record)| WideZoneRecord {
2137 root_key: zones[index].root_key,
2138 top_note: record.top_note,
2139 low_note: match table.get(index + 1) {
2140 Some(below) => below.top_note.saturating_add(1),
2141 None => super::zone::KEY_FLOOR,
2142 },
2143 global_id: zones[index].global_id,
2144 })
2145 .collect())
2146}
2147
2148struct ZoneRecord {
2150 id: u8,
2151 top_note: u8,
2152 gain: u32,
2153}
2154
2155fn zone_table(zones: &[NewZone<'_>]) -> Result<Vec<ZoneRecord>, Error> {
2157 if zones.is_empty() || zones.len() > MAX_ZONES {
2158 return Err(ParseError::OutOfBounds {
2159 value: format!("{} zones", zones.len()),
2160 bound: format!("1 through {MAX_ZONES}, the map section's own count byte"),
2161 }
2162 .into());
2163 }
2164 let mut table = Vec::with_capacity(zones.len());
2165 for (index, zone) in zones.iter().enumerate() {
2166 midi_note("root key", zone.root_key)?;
2167 midi_note("top note", zone.top_note)?;
2168 if !(1..=MAX_STROKE_ID).contains(&zone.global_id) {
2169 return Err(ParseError::OutOfBounds {
2170 value: format!("stroke id {}", zone.global_id),
2171 bound: format!("1 through {MAX_STROKE_ID}, what a zone record can name"),
2172 }
2173 .into());
2174 }
2175 if !zone.gain.is_finite() || zone.gain > MAX_ZONE_GAIN {
2176 return Err(ParseError::OutOfBounds {
2177 value: format!("zone {index} gain {}", zone.gain),
2178 bound: format!("a finite gain up to {MAX_ZONE_GAIN}"),
2179 }
2180 .into());
2181 }
2182 let id = zone.global_id as u8;
2183 if table.iter().any(|seen: &ZoneRecord| seen.id == id) {
2184 return Err(ParseError::AssertFail(format!(
2185 "two zones claim stroke id {id}, and a zone record names its stroke by id"
2186 ))
2187 .into());
2188 }
2189 if index > 0 && zone.top_note >= zones[index - 1].top_note {
2190 return Err(ParseError::AssertFail(format!(
2191 "zone {index} reaches up to note {} but the zone before it stops at {}; \
2192 zones are stored highest first and may not overlap",
2193 zone.top_note,
2194 zones[index - 1].top_note
2195 ))
2196 .into());
2197 }
2198 table.push(ZoneRecord {
2199 id,
2200 top_note: zone.top_note,
2201 gain: zone_record_gain(zone.gain),
2202 });
2203 }
2204 Ok(table)
2205}
2206
2207pub const MAX_MAP_GAIN_DB: f64 = 9.0;
2210
2211pub const MAX_ZONE_GAIN: f64 = 1000.0;
2214
2215fn gain_decibels(gain: f64) -> f32 {
2223 let decibels = 20.0 * gain.log10();
2224 match decibels.is_nan() {
2225 true => f32::from_bits(0x7fc0_0000),
2226 false => decibels as f32,
2227 }
2228}
2229
2230fn gain_units(decibels: f32) -> u64 {
2239 let units = 10f64.powf(f64::from(decibels) / 20.0) * f64::from(super::zone::GAIN_UNITY);
2240 units.round() as u64
2241}
2242
2243fn map_gain_units(gain: f64) -> u32 {
2245 let ceiling = MAX_MAP_GAIN_DB as f32;
2246 let decibels = gain_decibels(gain);
2247 let clamped = if decibels < ceiling {
2250 decibels
2251 } else {
2252 ceiling
2253 };
2254 gain_units(clamped) as u32
2255}
2256
2257fn zone_record_gain(gain: f64) -> u32 {
2264 let units = (gain * f64::from(super::zone::GAIN_UNITY)).round() as u64;
2265 (units % (1 << 24)) as u32
2266}
2267
2268fn midi_note(name: &str, note: u8) -> Result<(), Error> {
2269 if note <= 127 {
2270 return Ok(());
2271 }
2272 Err(ParseError::OutOfBounds {
2273 value: format!("{name} {note}"),
2274 bound: "a MIDI note from 0 through 127".into(),
2275 }
2276 .into())
2277}
2278
2279#[cfg(test)]
2280mod tests {
2281 use super::super::codec;
2282 use super::super::zone::GAIN_UNITY;
2283 use super::*;
2284
2285 const CELL: usize = Layout::V2.cell();
2287 const CHUNK: usize = Layout::V2.rmax();
2288 const HEADER_LEN: usize = Layout::V2.header_len();
2289 const PACKET_LEN: usize = packet_len(Layout::V2);
2290 const VERSION: u32 = version(Layout::V2);
2291 const MONO: Units = Units {
2292 layout: Layout::V2,
2293 channels: 1,
2294 };
2295 const PACKET_WORDS: usize = MONO.packet_words();
2296
2297 fn narrow(sample: crate::Sample) -> Cbin<Sample> {
2299 match sample {
2300 crate::Sample::V2(file) => file,
2301 crate::Sample::V3(_) => panic!("the narrow chain was asked for"),
2302 }
2303 }
2304
2305 fn built(
2306 zones: &[NewZone<'_>],
2307 name: &str,
2308 predictor: Predictor,
2309 ) -> Result<Cbin<Sample>, Error> {
2310 multi_zone(made(name, predictor, Layout::V2), zones).map(narrow)
2311 }
2312
2313 fn made(name: &str, predictor: Predictor, layout: Layout) -> Instrument<'_> {
2315 Instrument {
2316 name,
2317 map_gain: 1.0,
2318 predictor,
2319 layout,
2320 preset: Preset::default(),
2321 }
2322 }
2323
2324 fn plan(frames: usize, channels: usize) -> Result<Plan, Error> {
2325 Plan::new(
2326 Layout::V2,
2327 frames,
2328 channels,
2329 default_secondary_start(frames, None),
2330 )
2331 }
2332
2333 fn looped(frames: usize, channels: usize, points: Loop) -> Result<Plan, Error> {
2334 Plan::looped(
2335 Layout::V2,
2336 frames,
2337 channels,
2338 points,
2339 default_secondary_start(frames, Some(points)),
2340 )
2341 }
2342
2343 fn sine(hz: f64, amplitude: f64, frames: usize) -> Vec<i16> {
2344 (0..frames)
2345 .map(|k| {
2346 let t = k as f64 / f64::from(codec::SOURCE_RATE);
2347 (amplitude * (2.0 * std::f64::consts::PI * hz * t).sin()).round() as i16
2348 })
2349 .collect()
2350 }
2351
2352 fn encoded(source: &[i16], predictor: Predictor) -> Cbin<Sample> {
2353 narrow(instrument(source, &Options::new("Test").predictor(predictor)).unwrap())
2354 }
2355
2356 #[test]
2357 fn the_band_is_the_shortest_run_a_whole_number_of_records_can_cover() {
2358 for channels in [1usize, 2] {
2359 let (cell, rmax) = (CELL * channels, CHUNK * channels);
2360 for r in 0..2000usize {
2361 let b = band(r, cell, rmax);
2362 assert_eq!(b % cell, r % cell, "{channels}ch r {r}");
2363 assert!(b >= cell, "band({r}) = {b}");
2364 let records = (1..=8).find(|j| j * cell <= b && b <= j * rmax);
2365 assert!(records.is_some(), "{channels}ch band({r}) = {b}");
2366 for shorter in (cell..b).filter(|s| s % cell == b % cell) {
2367 assert!(
2368 !(1..=8).any(|j| j * cell <= shorter && shorter <= j * rmax),
2369 "{channels}ch band({r}) = {b}, but {shorter} is reachable"
2370 );
2371 }
2372 }
2373 assert_eq!(band(0, cell, rmax), cell);
2374 assert_eq!(band(cell, cell, rmax), cell);
2375 }
2376 }
2377
2378 #[test]
2379 fn every_one_to_one_chunk_is_a_legal_count() {
2380 for channels in [1usize, 2] {
2381 let (cell, rmax) = (CELL * channels, CHUNK * channels);
2382 for r in 0..2000usize {
2383 let run = band(r, cell, rmax);
2384 let split = chunks(run, rmax);
2385 assert_eq!(split.iter().sum::<usize>(), run, "band({r})");
2386 for c in split {
2387 assert!((cell..=rmax).contains(&c), "band({r}) chunk {c}");
2388 }
2389 }
2390 }
2391 }
2392
2393 #[test]
2394 fn the_plan_covers_every_field_exactly_once() {
2395 for frames in [4096, 8192, 10_000, 44_100, 100_000, 441_000] {
2396 let p = plan(frames, 1).unwrap();
2397 assert_eq!(
2398 p.warmup + CELL * p.cells_before + p.resync + CELL * p.cells_after,
2399 p.fields,
2400 "{frames} frames"
2401 );
2402 assert_eq!(p.warmup + CELL * p.cells_before, p.resync_at);
2403 }
2404 }
2405
2406 #[test]
2410 fn the_resync_lands_where_the_projects_secondary_start_says() {
2411 let mono = Plan::new(Layout::V2, 44_099, 1, 5_512.5 - 1.0).unwrap();
2412 assert_eq!(
2413 (mono.fields, mono.warmup, mono.resync_at, mono.resync),
2414 (35_128, 30, 4_374, 58)
2415 );
2416 let both = Plan::new(Layout::V2, 30_869, 2, 3_858.75 - 1.0).unwrap();
2417 assert_eq!(
2418 (both.fields, both.warmup, both.resync_at, both.resync),
2419 (49_256, 124, 6_124, 124)
2420 );
2421 assert_eq!(
2423 Plan::new(Layout::V2, 88_200, 1, 11_025.0)
2424 .unwrap()
2425 .resync_at,
2426 8_751
2427 );
2428 }
2429
2430 #[test]
2431 fn a_secondary_start_the_stream_cannot_resync_at_is_refused() {
2432 for at in [0.0, 20.0, 50_000.0, -1.0, f64::NAN, f64::INFINITY] {
2433 assert!(
2434 Plan::new(Layout::V2, 44_100, 1, at).is_err(),
2435 "secondary start {at}"
2436 );
2437 }
2438 let looped = |at| Plan::looped(Layout::V2, 44_100, 1, Loop::new(8_192, 40_000), at);
2439 assert!(looped(8_193.0).is_err(), "past the loop start");
2440 assert!(looped(8_192.0).is_ok(), "at the loop start, mark pushed");
2441 assert!(looped(4_096.0).is_ok());
2442 }
2443
2444 #[test]
2447 fn a_loop_mark_clears_the_resync_point_by_the_generations_floor() {
2448 for (start, secondary, channels, marks) in [
2451 (92, 92.0, 1, [145, 137, 137]),
2452 (200, 150.0, 1, [191, 183, 183]),
2453 (600, 500.0, 1, [481, 481, 481]),
2454 (92, 92.0, 2, [290, 274, 274]),
2455 ] {
2456 let points = Loop::new(start, start + 16_384);
2457 for (layout, mark) in [Layout::V2, Layout::V3, Layout::V4].into_iter().zip(marks) {
2458 let plan = Plan::looped(layout, 88_200, channels, points, secondary).unwrap();
2459 let looped = plan.looped.unwrap();
2460 assert_eq!(
2461 looped.at, mark,
2462 "{layout:?} {channels}ch: a loop at frame {start} resyncing at {secondary}"
2463 );
2464 assert_eq!(looped.lead, mark - fields_of(start).unwrap() * channels);
2465 assert_eq!(plan.fields, mark + fields_of(16_384).unwrap() * channels);
2466 }
2467 }
2468 }
2469
2470 #[test]
2471 fn audio_without_a_project_resyncs_where_a_fresh_project_would() {
2472 assert_eq!(default_secondary_start(44_100, None), 5_512.5);
2473 assert_eq!(
2474 default_secondary_start(44_100, Some(Loop::new(1_000, 40_000))),
2475 500.0
2476 );
2477 assert_eq!(
2478 default_secondary_start(44_100, Some(Loop::new(0, 40_000))),
2479 nsmpproj::MIN_SECONDARY_START
2480 );
2481 let stated = instrument(
2482 &vec![0i16; 44_100],
2483 &Options::new("Stated").secondary_start(5_521.281862),
2484 )
2485 .unwrap();
2486 let fresh = instrument(&vec![0i16; 44_100], &Options::new("Stated")).unwrap();
2487 assert_ne!(stated.stroke_streams()[0].1, fresh.stroke_streams()[0].1);
2488 }
2489
2490 #[test]
2491 fn the_stream_opens_on_a_cubic_ramp() {
2492 let mut fields = vec![-4_000i64; 40];
2493 ramp_in(&mut fields);
2494 assert_eq!(fields[0], 0);
2495 assert_eq!(fields[7], -4_000 * 343 / 42_875);
2496 assert_eq!(fields[34], -4_000 * 39_304 / 42_875);
2497 assert!(fields[..RAMP_IN].windows(2).all(|w| w[0] >= w[1]));
2498 assert!(fields[RAMP_IN..].iter().all(|&v| v == -4_000));
2499 }
2500
2501 #[test]
2502 fn a_width_tie_goes_to_the_lowest_order_unless_a_record_already_holds_it() {
2503 let widths = [13, 10, 7, 4, 4];
2504 assert_eq!(choose_order(&widths, None), (3, 4));
2505 assert_eq!(choose_order(&widths, Some((4, 4))), (4, 4));
2506 assert_eq!(choose_order(&widths, Some((4, 3))), (3, 4), "width changed");
2507 assert_eq!(choose_order(&widths, Some((2, 4))), (3, 4));
2508 assert_eq!(choose_order(&[9], Some((3, 9))), (0, 9));
2509 let values: Vec<i32> = (0..48).map(|k| k * (k - 1) * (k - 2) / 6).collect();
2512 assert_eq!(
2513 widths_at(&values, 8, Predictor::Minimising, CELL, 1)[3..],
2514 [MIN_WIDTH, MIN_WIDTH]
2515 );
2516 assert_eq!(widths_at(&values, 8, Predictor::Plain, CELL, 1).len(), 1);
2517 }
2518
2519 #[test]
2520 fn short_input_is_refused_rather_than_guessed_at() {
2521 assert!(plan(MIN_FRAMES - 1, 1).is_err());
2522 assert!(plan(MIN_FRAMES, 1).is_ok());
2523 assert!(plan(usize::MAX, 1).is_err());
2524 assert!(instrument(&[0i16; MIN_FRAMES - 1], &Options::new("Test")).is_err());
2525 assert!(instrument(&[0i16; MIN_FRAMES], &Options::new("Test")).is_ok());
2526 }
2527
2528 #[test]
2529 fn forced_shifts_that_cannot_be_encoded_are_refused() {
2530 let mut step = vec![i16::MIN; MIN_FRAMES];
2531 step[MIN_FRAMES / 2..].fill(i16::MAX);
2532 assert!(instrument(&step, &Options::new("Test").shift(0)).is_err());
2533 assert!(instrument(
2534 &[0i16; MIN_FRAMES],
2535 &Options::new("Test").shift(codec::SHIFT_LIMIT as u8 + 1)
2536 )
2537 .is_err());
2538 }
2539
2540 #[test]
2541 fn midi_notes_outside_the_wire_range_are_refused() {
2542 let source = vec![0i16; MIN_FRAMES];
2543 assert!(instrument(&source, &Options::new("Test").root_key(128)).is_err());
2544 assert!(instrument(&source, &Options::new("Test").top_note(255)).is_err());
2545 let bad_root = NewZone {
2546 root_key: 128,
2547 ..zone(&source, 60, 127, 1)
2548 };
2549 let encoded = encode_stroke(Layout::V2, &bad_root, 165, Predictor::Plain).unwrap();
2550 assert!(stroke_payload(Layout::V2, &bad_root, &encoded, 0, 1).is_err());
2551 }
2552
2553 #[test]
2554 fn the_allocation_is_whole_packets_with_the_chain_at_the_end() {
2555 let file = encoded(&sine(440.0, 8000.0, 44_100), Predictor::Plain);
2556 let map_len = section::find(&file.body.sections, section::MAP)
2557 .unwrap()
2558 .payload
2559 .len();
2560 let cat_len = section::find(&file.body.sections, section::CAT)
2561 .unwrap()
2562 .payload
2563 .len();
2564 let stroke = section::find(&file.body.sections, section::STK).unwrap();
2565 let head = super::super::stroke::header_len(
2566 Layout::V2,
2567 super::super::Chain::Library2,
2568 0,
2569 cat_len,
2570 map_len,
2571 );
2572 assert_eq!((stroke.payload.len() - head) % PACKET_LEN, 0);
2573 assert_eq!(&stroke.payload[stroke.payload.len() - 3..], &[0x80, 0, 24]);
2574 }
2575
2576 #[test]
2577 fn every_predictor_round_trips_through_the_decoder_exactly() {
2578 let mut differenced = 0usize;
2579 for predictor in [Predictor::Plain, Predictor::Minimising] {
2580 for source in [
2581 sine(440.0, 12_000.0, 44_100),
2582 sine(30.0, 32_000.0, 20_000),
2583 vec![0i16; 8192],
2584 vec![9000i16; 8192],
2585 ] {
2586 let file = encoded(&source, predictor);
2587 let (at, stroke) = file.stroke_streams()[0];
2588 let plan = plan(source.len(), 1).unwrap();
2589 let q = quantise(&source, &plan, None);
2590
2591 let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
2592 assert_eq!(audio.samples.len(), plan.fields);
2593 if predictor == Predictor::Plain {
2594 assert_eq!(audio.differenced, 0);
2595 } else {
2596 differenced += audio.differenced;
2597 }
2598 let gain = 1i32 << q.shift;
2599 for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
2600 assert_eq!(i32::from(got), want * gain, "{predictor:?} field {f}");
2601 }
2602 }
2603 }
2604 assert!(differenced > 0, "minimising never chose a predictor");
2605 }
2606
2607 #[test]
2608 fn a_sine_comes_back_a_sine() {
2609 let source = sine(440.0, 20_000.0, 44_100);
2610 let file = encoded(&source, Predictor::Plain);
2611 let (at, stroke) = file.stroke_streams()[0];
2612 let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
2613 let window = &audio.samples[10_000..20_000];
2615 let peak = window.iter().map(|&v| i32::from(v).abs()).max().unwrap();
2616 assert!((19_000..=21_000).contains(&peak), "peak {peak}");
2617 let zero_crossings = window.windows(2).filter(|w| w[0] < 0 && w[1] >= 0).count();
2618 assert!((124..=127).contains(&zero_crossings), "{zero_crossings}");
2620 }
2621
2622 #[test]
2623 fn a_records_fields_start_right_after_its_header() {
2624 let spec = Spec {
2626 one_to_one: true,
2627 width: 13,
2628 order: 0,
2629 mark: false,
2630 first: 0,
2631 count: 30,
2632 };
2633 let tail = spec.span(MONO) * 24 - 24 - spec.count * usize::from(spec.width);
2634 assert_eq!(tail, 18, "this spec is chosen to leave a tail");
2635
2636 let values: Vec<i32> = (0..30).map(|k| k * 7 - 40).collect();
2637 let mut words = vec![0u8; spec.span(MONO) * 3];
2638 write_record(&mut words, 0, &spec, &values, MONO);
2639
2640 let total = spec.span(MONO) * 24;
2642 for bit in total - tail..total {
2643 assert_eq!(
2644 words[bit / 8] >> (7 - bit % 8) & 1,
2645 0,
2646 "bit {bit} is in the alignment tail and should be clear"
2647 );
2648 }
2649 let mut stroke = vec![0u8; HEADER_LEN];
2651 stroke.extend_from_slice(&words);
2652 stroke.extend_from_slice(&[0x80, 0x00, 0x18]);
2653 let end = (HEADER_LEN / 3 + spec.span(MONO)) as u16;
2654 for (i, p) in [HEADER_LEN as u16 / 3, 0, end, end].iter().enumerate() {
2655 let at = codec::SEEK_AT + codec::SEEK_STRIDE * i;
2656 stroke[at..at + 2].copy_from_slice(&p.to_be_bytes());
2657 }
2658 let walked = codec::walk(&stroke, 0, codec::Layout::V2).unwrap();
2659 assert_eq!(walked.records[0].values, values);
2660 }
2661
2662 #[test]
2663 fn the_instrument_reads_back_as_one() {
2664 let file = instrument(
2665 &sine(220.0, 15_000.0, 30_000),
2666 &Options::new("Encoded").root_key(48).top_note(72),
2667 )
2668 .unwrap();
2669 let bytes = file.to_bytes().unwrap();
2670 let read = super::super::from_bytes(&bytes).unwrap();
2671 assert_eq!(read.name().unwrap(), "Encoded");
2672 assert_eq!(read.header.version, VERSION);
2673 let zones = read.zones().unwrap();
2674 assert_eq!(zones.len(), 1);
2675 assert_eq!(zones[0].top_note, 72);
2676 assert_eq!(read.strokes().unwrap()[0].root_key, 48);
2677 assert_eq!(read.to_bytes().unwrap(), bytes);
2678 }
2679
2680 #[test]
2681 fn the_directory_names_the_records_the_walk_finds() {
2682 let file = encoded(&sine(300.0, 9000.0, 50_000), Predictor::Plain);
2683 let (at, stroke) = file.stroke_streams()[0];
2684 let stream = codec::walk(stroke, at, codec::Layout::V2).unwrap();
2685 let directory = codec::Directory::read(stroke).unwrap();
2686 assert_eq!(
2687 codec::Directory::resolve(directory.first_record, at, codec::Layout::V2),
2688 stream.first_record
2689 );
2690 assert_eq!(
2691 codec::Directory::resolve(directory.terminator, at, codec::Layout::V2),
2692 stream.terminator
2693 );
2694 let resync = codec::Directory::resolve(directory.resync, at, codec::Layout::V2);
2695 let record = stream.records.iter().find(|r| r.at == resync).unwrap();
2696 assert!(record.one_to_one);
2697 assert_eq!(record.first_field, plan(50_000, 1).unwrap().resync_at);
2698 }
2699
2700 #[test]
2701 fn the_header_states_the_shift_it_quantised_at() {
2702 for amplitude in [40.0, 900.0, 8000.0, 32_000.0] {
2703 let source = sine(440.0, amplitude, 20_000);
2704 let plan = plan(source.len(), 1).unwrap();
2705 let file = encoded(&source, Predictor::Plain);
2706 let (_, stroke) = file.stroke_streams()[0];
2707 let q = quantise(&source, &plan, None);
2708 assert_eq!(
2709 codec::shift(stroke, codec::Layout::V2),
2710 Some(q.shift),
2711 "amplitude {amplitude}"
2712 );
2713 assert_eq!(codec::peak(stroke, codec::Layout::V2), Some(q.peak));
2714 assert!(q.shift >= 0);
2715 }
2716 }
2717
2718 #[test]
2719 fn the_shift_tracks_how_loud_the_content_is() {
2720 let quiet = plan(20_000, 1)
2721 .map(|p| quantise(&sine(440.0, 500.0, 20_000), &p, None).shift)
2722 .unwrap();
2723 let loud = plan(20_000, 1)
2724 .map(|p| quantise(&sine(440.0, 32_000.0, 20_000), &p, None).shift)
2725 .unwrap();
2726 assert_eq!(quiet, 0);
2727 assert!(loud > quiet, "loud {loud} vs quiet {quiet}");
2728 }
2729
2730 #[test]
2731 fn a_stereo_stroke_stops_shifting_where_its_peak_fits() {
2732 let frames = 30_000;
2733 let left = sine(220.0, 12_000.0, frames);
2734 let right = sine(330.0, 12_000.0, frames);
2735 let both: Vec<i16> = left
2736 .iter()
2737 .zip(&right)
2738 .flat_map(|(&l, &r)| [l, r])
2739 .collect();
2740 let mono = quantise(&left, &plan(frames, 1).unwrap(), None);
2741 let stereo = quantise(&both, &plan(frames, 2).unwrap(), None);
2742 assert_eq!(stereo.shift, 1);
2743 let widest = stereo
2744 .values
2745 .iter()
2746 .map(|v| width_of(i64::from(*v), i64::from(*v)))
2747 .max()
2748 .unwrap();
2749 assert_eq!(widest, PEAK_WIDTH);
2750 assert!((stereo.shift..=stereo.shift + 1).contains(&mono.shift));
2752 }
2753
2754 fn probe(layout: Layout, resync: usize, field: usize) -> (Plan, Vec<i64>) {
2756 let frames = 100_000;
2757 let secondary = resync as f64 * f64::from(PITCH_NUM) / f64::from(PITCH_DEN);
2758 let plan = Plan::new(layout, frames, 1, secondary).unwrap();
2759 assert_eq!(plan.resync_at, resync);
2760 let mut values = vec![0i64; plan.fields];
2761 values[field] = 1 << (PEAK_WIDTH - 2);
2762 (plan, values)
2763 }
2764
2765 #[test]
2766 fn only_a_run_s_last_record_buys_the_extra_bit() {
2767 let (plan, values) = probe(Layout::V2, 5464, 5464 + 76);
2769 assert!(spends_extra_bit(&values, &plan));
2770 for offset in [12, 61, 89, 95] {
2771 let (plan, values) = probe(Layout::V2, 5464, 5464 + offset);
2772 assert!(!spends_extra_bit(&values, &plan), "run offset {offset}");
2773 }
2774 }
2775
2776 fn opening_run(layout: Layout, last: usize, value: i64) -> (Plan, Vec<i64>) {
2779 let chunk = layout.rmax();
2780 let warmup = if last == chunk { chunk } else { chunk + last };
2781 let plan = Plan {
2782 layout,
2783 channels: 1,
2784 fields: warmup,
2785 resync_at: warmup,
2786 warmup,
2787 resync: 0,
2788 cells_before: 0,
2789 cells_after: 0,
2790 looped: None,
2791 };
2792 let mut values = vec![0; warmup];
2793 values[warmup - 1] = value;
2794 (plan, values)
2795 }
2796
2797 #[test]
2798 fn each_last_record_width_obeys_the_measured_rule() {
2799 for (last, buys) in [
2800 (24, false),
2801 (25, true),
2802 (26, true),
2803 (27, true),
2804 (28, true),
2805 (29, false),
2806 (30, true),
2807 (31, true),
2808 (32, false),
2809 ] {
2810 let (plan, values) = opening_run(Layout::V2, last, 1 << (PEAK_WIDTH - 2));
2811 assert_eq!(spends_extra_bit(&values, &plan), buys, "width {last}");
2812 }
2813 }
2814
2815 #[test]
2816 fn the_extra_bit_uses_signed_thirteen_bit_bounds() {
2817 for (value, buys) in [(-4097, true), (-4096, false), (4095, false), (4096, true)] {
2818 let (plan, values) = opening_run(Layout::V2, 25, value);
2819 assert_eq!(spends_extra_bit(&values, &plan), buys, "value {value}");
2820 }
2821 }
2822
2823 const V3_LIVE: [usize; 11] = [33, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46];
2826
2827 #[test]
2828 fn six_of_the_seventeen_v3_last_record_widths_never_buy_it() {
2829 for last in 32..=48 {
2830 let (plan, values) = opening_run(Layout::V3, last, 1 << (PEAK_WIDTH - 2));
2831 assert_eq!(
2832 spends_extra_bit(&values, &plan),
2833 V3_LIVE.contains(&last),
2834 "width {last}"
2835 );
2836 }
2837 }
2838
2839 #[test]
2840 fn a_v4_mono_stroke_never_buys_the_extra_bit() {
2841 for last in 32..=48 {
2842 let (plan, values) = opening_run(Layout::V4, last, 1 << (PEAK_WIDTH - 2));
2843 assert!(!spends_extra_bit(&values, &plan), "width {last}");
2844 }
2845 }
2846
2847 fn loop_run(layout: Layout, last: usize) -> (Plan, Vec<i64>) {
2851 let at = layout.rmax();
2852 let fields = at + last;
2853 let plan = Plan {
2854 layout,
2855 channels: 1,
2856 fields,
2857 resync_at: at,
2858 warmup: at,
2859 resync: 0,
2860 cells_before: 0,
2861 cells_after: 0,
2862 looped: Some(Looped {
2863 at,
2864 lead: 0,
2865 crossfade: 0,
2866 warmup: last,
2867 cells: 0,
2868 }),
2869 };
2870 let mut values = vec![0; fields];
2871 values[fields - 1] = 1 << (PEAK_WIDTH - 2);
2872 (plan, values)
2873 }
2874
2875 #[test]
2876 fn the_run_a_loop_mark_opens_buys_the_extra_bit() {
2877 for (layout, live, dead) in [(Layout::V2, 25, 29), (Layout::V3, 33, 41)] {
2878 let (plan, values) = loop_run(layout, live);
2879 assert!(spends_extra_bit(&values, &plan), "{layout:?} live");
2880
2881 let unmarked = Plan {
2882 looped: None,
2883 ..plan
2884 };
2885 assert!(!spends_extra_bit(&values, &unmarked), "{layout:?} unlooped");
2886
2887 let (plan, values) = loop_run(layout, dead);
2888 assert!(!spends_extra_bit(&values, &plan), "{layout:?} dead");
2889 }
2890 }
2891
2892 #[test]
2893 fn the_extra_bit_narrows_the_stroke_the_header_declares() {
2894 let loud = header_shift(&sine(440.0, 12_000.0, 44_100), 1);
2895 let quiet = header_shift(&sine(440.0, 3_000.0, 44_100), 1);
2896 assert_eq!(loud - quiet, 2);
2897 }
2898
2899 fn header_shift(source: &[i16], channels: u16) -> i32 {
2900 let options = Options::new("Shift")
2901 .channels(channels)
2902 .predictor(Predictor::Minimising);
2903 let file = instrument(source, &options).unwrap();
2904 let (_, stroke) = file.stroke_streams()[0];
2905 codec::shift(stroke, codec::Layout::V2).unwrap()
2906 }
2907
2908 #[test]
2909 fn statistic_b_takes_the_sign_of_the_extreme_field() {
2910 let frames = 20_000;
2911 let mut up = vec![0i16; frames];
2912 up[10_000] = 13;
2913 let down: Vec<i16> = up.iter().map(|v| -v).collect();
2914 let positive = quantise(&up, &plan(frames, 1).unwrap(), None).peak;
2915 let negative = quantise(&down, &plan(frames, 1).unwrap(), None).peak;
2916 assert_eq!(positive, 2);
2917 assert_eq!(negative, 3);
2918 let opposed: Vec<i16> = up.iter().zip(&down).flat_map(|(&l, &r)| [l, r]).collect();
2919 let stereo = quantise(&opposed, &plan(frames, 2).unwrap(), None).peak;
2920 assert_eq!(stereo, positive);
2921 }
2922
2923 #[test]
2924 fn no_field_overflows_the_width_its_record_declares() {
2925 for predictor in [Predictor::Plain, Predictor::Minimising] {
2926 let source = sine(440.0, 32_000.0, 30_000);
2927 let plan = plan(source.len(), 1).unwrap();
2928 let q = quantise(&source, &plan, None);
2929 let (specs, _) = records(&q.values, &plan, predictor).unwrap();
2930 for spec in specs {
2931 let limit = 1i64 << (spec.width - 1);
2932 for k in 0..spec.count {
2933 let v = residual(&q.values, spec.first + k, spec.order, 1);
2934 assert!((-limit..limit).contains(&v), "{spec:?} field {k} = {v}");
2935 }
2936 assert!(spec.width <= PEAK_WIDTH || spec.order > 0);
2937 }
2938 }
2939 }
2940
2941 #[test]
2942 fn records_tile_the_lattice_the_way_the_laws_say() {
2943 let source = sine(440.0, 20_000.0, 60_000);
2944 let plan = plan(source.len(), 1).unwrap();
2945 let q = quantise(&source, &plan, None);
2946 let (specs, _) = records(&q.values, &plan, Predictor::Plain).unwrap();
2947
2948 let mut at = 0;
2949 for spec in &specs {
2950 assert_eq!(spec.first, at);
2951 if !spec.one_to_one {
2952 assert_eq!(spec.count % CELL, 0);
2953 assert!(spec.count <= MAX_COUNT);
2954 }
2955 at += spec.count;
2956 }
2957 assert_eq!(at, plan.fields);
2958 let one_to_one: usize = specs.iter().filter(|s| s.one_to_one).map(|s| s.count).sum();
2959 assert_eq!(one_to_one, plan.warmup + plan.resync);
2960 }
2961
2962 #[test]
2963 fn the_minimising_predictor_narrows_smooth_material() {
2964 let source = sine(60.0, 30_000.0, 60_000);
2965 let plan = plan(source.len(), 1).unwrap();
2966 let q = quantise(&source, &plan, None);
2967 let (plain, _) = records(&q.values, &plan, Predictor::Plain).unwrap();
2968 let (minimised, _) = records(&q.values, &plan, Predictor::Minimising).unwrap();
2969
2970 let bits = |specs: &[Spec]| -> usize { specs.iter().map(|s| s.span(MONO)).sum() };
2971 assert!(
2972 bits(&minimised) < bits(&plain),
2973 "{} words vs {}",
2974 bits(&minimised),
2975 bits(&plain)
2976 );
2977 assert!(minimised.iter().any(|s| s.order > 0));
2978 assert!(minimised.iter().all(|s| !s.one_to_one || s.order == 0));
2980 }
2981
2982 #[test]
2983 fn a_residual_integrates_back_to_the_field_it_came_from() {
2984 let values: Vec<i32> = (0..200).map(|k| (k * k / 7) % 501 - 250).collect();
2985 for order in 1..DIFFERENCE.len() as u8 {
2986 for at in usize::from(order)..values.len() {
2987 let mut v = residual(&values, at, order, 1);
2988 for (j, &c) in DIFFERENCE[usize::from(order)].iter().enumerate().skip(1) {
2989 v -= i64::from(c) * i64::from(values[at - j]);
2990 }
2991 assert_eq!(v, i64::from(values[at]), "order {order} at {at}");
2992 }
2993 }
2994 }
2995
2996 #[test]
2997 fn statistic_a_round_trips_the_shift() {
2998 for peak in [0u32, 1, 2, 255, 4095, 4096, 8191, 8192] {
2999 for shift in 0..6 {
3000 let (mantissa, exponent) = statistic_a(peak, shift, u64::from(GAIN_UNITY));
3001 let mut stroke = vec![0u8; HEADER_LEN];
3002 stroke[codec::STAT_A_EXP_AT] = exponent;
3003 stroke[codec::PEAK_AT..codec::PEAK_AT + 3]
3004 .copy_from_slice(&peak.to_be_bytes()[1..]);
3005 assert_eq!(
3006 codec::shift(&stroke, codec::Layout::V2),
3007 Some(shift),
3008 "peak {peak}"
3009 );
3010 assert!((1 << 19..1 << 20).contains(&mantissa) || peak == 0);
3011 }
3012 }
3013 }
3014
3015 #[test]
3016 fn the_stroke_header_holds_the_fixed_bytes_where_the_format_puts_them() {
3017 let file = instrument(
3018 &sine(440.0, 9000.0, 20_000),
3019 &Options::new("Test").root_key(64),
3020 )
3021 .unwrap();
3022 let (_, head) = file.stroke_streams()[0];
3023 assert_eq!(head[0..5], [0, 0, 0, 1, 0]);
3024 assert_eq!(head[5], 64);
3025 assert_eq!(head[6..9], [0x88, 0xba, 0x01]);
3026 let stereo = instrument(
3027 &vec![0i16; 2 * MIN_FRAMES],
3028 &Options::new("Test").channels(2),
3029 )
3030 .unwrap();
3031 assert_eq!(stereo.stroke_streams()[0].1[6..9], [0x88, 0xba, 0x02]);
3032 assert_eq!(head[16..20], [0, 0, 0, 0]);
3033 assert_eq!([head[22], head[31], head[40]], [0x80, 0x80, 0x80]);
3034 assert_eq!(head[49..51], [0, 0]);
3035 for gap in [23..29, 32..38, 41..47] {
3036 assert!(head[gap.clone()].iter().all(|&b| b == 0), "{gap:?}");
3037 }
3038 }
3039
3040 fn zone(source: &[i16], root_key: u8, top_note: u8, global_id: u32) -> NewZone<'_> {
3041 NewZone {
3042 source,
3043 channels: 1,
3044 root_key,
3045 top_note,
3046 global_id,
3047 loops: None,
3048 secondary_start: default_secondary_start(source.len(), None),
3049 shift: None,
3050 gain: 1.0,
3051 loop_decay: DEFAULT_LOOP_DECAY,
3052 }
3053 }
3054
3055 #[test]
3056 fn statistic_a_scales_a_24_bit_reciprocal_by_the_gain() {
3057 assert_eq!(statistic_a(4096, 2, u64::from(GAIN_UNITY)), (524_288, 12));
3058 assert_eq!(
3059 statistic_a(4096, 2, u64::from(GAIN_UNITY / 2)),
3060 (262_144, 12)
3061 );
3062 assert_eq!(
3063 statistic_a(4096, 2, 2 * u64::from(GAIN_UNITY)),
3064 (1_048_576, 12)
3065 );
3066 assert_eq!(statistic_a(1225, 0, 1_436_549), (1_200_837, 11));
3067 assert_eq!(statistic_a(4195, 2, 8_378_122), (8_180_401, 11));
3068 assert_eq!(statistic_a(1225, 0, 5_557_453), (4_645_576, 11));
3069 }
3070
3071 #[test]
3074 fn statistic_a_reciprocates_the_loudest_zone_in_the_file() {
3075 let loud = sine(440.0, 12_000.0, 20_000);
3076 let quiet = sine(440.0, 3_000.0, 20_000);
3077 let file = built(
3078 &[zone(&loud, 72, 127, 1), zone(&quiet, 48, 71, 2)],
3079 "Two",
3080 Predictor::Plain,
3081 )
3082 .unwrap();
3083 let field = |s: &[u8], at: usize| u32::from_be_bytes([0, s[at], s[at + 1], s[at + 2]]);
3084 let streams = file.stroke_streams();
3085 let (mantissa, peak) = (|s| field(s, 9), |s| field(s, 13));
3086 let (first, second) = (streams[0].1, streams[1].1);
3087 assert!(peak(first) > peak(second));
3088 assert_eq!(mantissa(first), mantissa(second));
3089 assert_eq!(
3090 mantissa(second),
3091 statistic_a(peak(first), 0, u64::from(GAIN_UNITY)).0,
3092 "the quiet zone reciprocates the loud zone's peak"
3093 );
3094 assert_ne!(
3095 mantissa(second),
3096 statistic_a(peak(second), 0, u64::from(GAIN_UNITY)).0
3097 );
3098 }
3099
3100 #[test]
3101 fn a_zone_gain_scales_statistic_a_and_touches_nothing_else() {
3102 let source = sine(440.0, 12_000.0, 20_000);
3103 let unity = built(&[zone(&source, 60, 127, 1)], "Gain", Predictor::Plain).unwrap();
3104 let half = NewZone {
3105 gain: 0.5,
3106 ..zone(&source, 60, 127, 1)
3107 };
3108 let halved = built(&[half], "Gain", Predictor::Plain).unwrap();
3109 let (_, a) = unity.stroke_streams()[0];
3110 let (_, b) = halved.stroke_streams()[0];
3111 assert_eq!(a[..9], b[..9]);
3112 assert_eq!(a[12..], b[12..]);
3113 let mantissa = |s: &[u8]| u32::from_be_bytes([0, s[9], s[10], s[11]]);
3114 assert_eq!(mantissa(b), mantissa(a) / 2);
3115 assert_eq!(unity.zones().unwrap()[0].gain, GAIN_UNITY);
3116 assert_eq!(halved.zones().unwrap()[0].gain, GAIN_UNITY / 2);
3117
3118 let over = NewZone {
3119 gain: MAX_ZONE_GAIN * 2.0,
3120 ..zone(&source, 60, 127, 1)
3121 };
3122 assert!(built(&[over], "Gain", Predictor::Plain).is_err());
3123 }
3124
3125 #[test]
3126 fn every_zone_reads_back_paired_to_its_own_stroke() {
3127 let high = sine(880.0, 12_000.0, 12_000);
3128 let mid = sine(440.0, 12_000.0, 9_000);
3129 let low = sine(220.0, 12_000.0, 15_000);
3130 let file = built(
3131 &[
3132 zone(&high, 72, 96, 7),
3133 zone(&mid, 60, 65, 3),
3134 zone(&low, 48, 53, 9),
3135 ],
3136 "Three",
3137 Predictor::Plain,
3138 )
3139 .unwrap();
3140
3141 let read = super::super::from_bytes(&file.to_bytes().unwrap()).unwrap();
3142 assert_eq!(read.name().unwrap(), "Three");
3143 let zones = read.zones().unwrap();
3144 assert_eq!(
3145 zones.iter().map(|z| z.top_note).collect::<Vec<_>>(),
3146 [96, 65, 53]
3147 );
3148 assert_eq!(
3149 zones.iter().map(|z| z.stroke_id).collect::<Vec<_>>(),
3150 [7, 3, 9]
3151 );
3152 assert_eq!(
3153 read.strokes()
3154 .unwrap()
3155 .iter()
3156 .map(|s| s.root_key)
3157 .collect::<Vec<_>>(),
3158 [72, 60, 48]
3159 );
3160
3161 for (index, source) in [&high, &mid, &low].iter().enumerate() {
3162 let (at, stream) = read.zone_stream(index).unwrap();
3163 let audio = codec::decode(stream, at, codec::Layout::V2).unwrap();
3164 let plan = plan(source.len(), 1).unwrap();
3165 let q = quantise(source, &plan, None);
3166 let gain = 1i32 << q.shift;
3167 assert_eq!(audio.samples.len(), plan.fields, "zone {index}");
3168 for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3169 assert_eq!(i32::from(got), want * gain, "zone {index} field {f}");
3170 }
3171 }
3172 }
3173
3174 #[test]
3175 fn a_zone_decodes_the_same_alone_as_in_a_crowd() {
3176 let source = sine(330.0, 18_000.0, 20_000);
3177 let alone = narrow(instrument(&source, &Options::new("One").root_key(60)).unwrap());
3178 let crowd = built(
3179 &[
3180 zone(&sine(880.0, 9000.0, 8000), 72, 96, 3),
3181 zone(&source, 60, 65, 2),
3182 zone(&sine(110.0, 9000.0, 8000), 48, 53, 1),
3183 ],
3184 "Three",
3185 Predictor::default(),
3186 )
3187 .unwrap();
3188
3189 let one = alone.zone_stream(0).unwrap();
3190 let many = crowd.zone_stream(1).unwrap();
3191 assert_ne!(one.1, many.1, "the streams differ; only the audio must not");
3192 assert_eq!(
3193 codec::decode(one.1, one.0, codec::Layout::V2).unwrap(),
3194 codec::decode(many.1, many.0, codec::Layout::V2).unwrap()
3195 );
3196 }
3197
3198 #[test]
3199 fn every_stroke_is_its_own_header_length_plus_whole_packets() {
3200 let source = sine(440.0, 12_000.0, 12_000);
3201 for count in 1..=6usize {
3202 let zones: Vec<NewZone> = (0..count)
3203 .map(|i| zone(&source, 60, 120 - 10 * i as u8, i as u32 + 1))
3204 .collect();
3205 let file = built(&zones, "Ladder", Predictor::Plain).unwrap();
3206 let cat_len = section::find(&file.body.sections, section::CAT)
3207 .unwrap()
3208 .payload
3209 .len();
3210 let map_len = section::find(&file.body.sections, section::MAP)
3211 .unwrap()
3212 .payload
3213 .len();
3214 for (index, section) in file
3215 .body
3216 .sections
3217 .iter()
3218 .filter(|s| s.is(section::STK))
3219 .enumerate()
3220 {
3221 let head = super::super::stroke::header_len(
3222 Layout::V2,
3223 super::super::Chain::Library2,
3224 index,
3225 cat_len,
3226 map_len,
3227 );
3228 assert_eq!(
3229 (section.payload.len() - head) % PACKET_LEN,
3230 0,
3231 "{count} zones, stroke {index}: {} bytes over a {head}-byte header",
3232 section.payload.len()
3233 );
3234 }
3235 }
3236 }
3237
3238 #[test]
3239 fn a_zone_list_the_format_cannot_store_is_refused() {
3240 let source = vec![0i16; MIN_FRAMES];
3241 let one = |root, top, id| built(&[zone(&source, root, top, id)], "x", Predictor::Plain);
3242 assert!(built(&[], "x", Predictor::Plain).is_err());
3243 assert!(one(60, 84, 0).is_err(), "id zero names no stroke");
3244 assert!(one(60, 84, 256).is_err(), "id past the record's one byte");
3245 assert!(one(60, 128, 1).is_err());
3246 assert!(one(128, 84, 1).is_err());
3247 assert!(one(60, 84, 1).is_ok());
3248
3249 let pair = |tops: [u8; 2], ids: [u32; 2]| {
3250 built(
3251 &[
3252 zone(&source, 60, tops[0], ids[0]),
3253 zone(&source, 48, tops[1], ids[1]),
3254 ],
3255 "x",
3256 Predictor::Plain,
3257 )
3258 };
3259 assert!(pair([84, 53], [1, 1]).is_err(), "duplicate stroke id");
3260 assert!(pair([53, 84], [2, 1]).is_err(), "zones out of order");
3261 assert!(pair([84, 84], [2, 1]).is_err(), "zones overlap");
3262 assert!(pair([84, 53], [2, 1]).is_ok());
3263 }
3264
3265 #[test]
3266 fn a_looped_plan_covers_every_field_exactly_once() {
3267 for (frames, start, end) in [
3268 (88_200, 16_384, 32_768),
3269 (88_200, 4_096, 20_480),
3270 (88_200, 92, 16_476),
3271 (88_200, 43_981, 60_365),
3272 (44_100, 20_000, 44_100),
3273 ] {
3274 let plan = looped(frames, 1, Loop::new(start, end)).unwrap();
3275 let points = plan.looped.unwrap();
3276 assert_eq!(
3277 plan.warmup + CELL * plan.cells_before + plan.resync + CELL * plan.cells_after,
3278 points.at,
3279 "{start}..{end}: the pre-roll does not reach the loop"
3280 );
3281 assert_eq!(
3282 points.at + points.warmup + CELL * points.cells,
3283 plan.fields,
3284 "{start}..{end}: the loop does not reach the terminator"
3285 );
3286 assert_eq!(points.at - fields_of(start).unwrap(), points.lead);
3287 }
3288 }
3289
3290 #[test]
3291 fn a_loop_comes_back_the_length_it_asked_for() {
3292 let source = sine(220.0, 18_000.0, 88_200);
3293 for (start, end) in [
3294 (16_384, 32_768),
3295 (43_981, 60_365),
3296 (4_096, 20_480),
3297 (65_536, 81_920),
3298 ] {
3299 let file = instrument(
3300 &source,
3301 &Options::new("Looped").loops(Loop::new(start, end)),
3302 )
3303 .unwrap_or_else(|e| panic!("loop {start}..{end}: {e}"));
3304 let (at, stroke) = file.stroke_streams()[0];
3305 let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3306 let mark = walk.records.iter().find(|r| r.mark).unwrap();
3307 let frames = (walk.fields - mark.first_field) as f64 * f64::from(codec::SOURCE_RATE)
3308 / f64::from(codec::FIELD_RATE);
3309 assert!(
3310 (frames - (end - start) as f64).abs() < 1.0,
3311 "loop {start}..{end} came back {frames} frames long"
3312 );
3313 }
3314 }
3315
3316 #[test]
3317 fn the_loop_starts_a_packet_and_the_directory_says_so() {
3318 let source = sine(330.0, 14_000.0, 60_000);
3319 for (start, end) in [(8_192, 24_576), (20_000, 40_000), (4_096, 59_000)] {
3320 for predictor in [Predictor::Plain, Predictor::Minimising] {
3321 let file = instrument(
3322 &source,
3323 &Options::new("Looped")
3324 .predictor(predictor)
3325 .loops(Loop::new(start, end)),
3326 )
3327 .unwrap();
3328 let (at, stroke) = file.stroke_streams()[0];
3329 let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3330 let directory = codec::Directory::read(stroke).unwrap();
3331 let marked: Vec<_> = walk.records.iter().filter(|r| r.mark).collect();
3332 assert_eq!(marked.len(), 1, "{start}..{end} {predictor:?}");
3333 assert_eq!(
3334 codec::Directory::resolve(directory.mark, at, codec::Layout::V2),
3335 marked[0].at
3336 );
3337 assert_ne!(directory.mark, directory.terminator);
3338 assert_eq!(
3339 (walk.terminator - marked[0].at) % PACKET_WORDS,
3340 0,
3341 "{start}..{end} {predictor:?}: {} words",
3342 walk.terminator - marked[0].at
3343 );
3344 }
3345 }
3346 }
3347
3348 #[test]
3349 fn an_unlooped_stroke_marks_nothing() {
3350 let file = encoded(&sine(440.0, 9_000.0, 44_100), Predictor::Plain);
3351 let (at, stroke) = file.stroke_streams()[0];
3352 let directory = codec::Directory::read(stroke).unwrap();
3353 assert_eq!(directory.mark, directory.terminator);
3354 assert!(codec::walk(stroke, at, codec::Layout::V2)
3355 .unwrap()
3356 .records
3357 .iter()
3358 .all(|r| !r.mark));
3359 }
3360
3361 #[test]
3362 fn the_tail_repeats_the_loops_opening() {
3363 let source = sine(200.0, 20_000.0, 88_200);
3364 let plan = looped(source.len(), 1, Loop::new(16_384, 32_768)).unwrap();
3365 let points = plan.looped.unwrap();
3366 let values = quantise(&source, &plan, None).values;
3367 assert_eq!(
3368 values[plan.fields - points.lead..],
3369 values[points.at - points.lead..points.at]
3370 );
3371 }
3372
3373 const MEASURED_FADES: &[(usize, f64, usize)] = &[
3376 (8_192, 81.92, 65),
3377 (8_192, 163.84, 130),
3378 (8_192, 409.6, 325),
3379 (8_192, 819.2, 650),
3380 (8_192, 1_638.4, 1_300),
3381 (8_192, 2_048.0, 1_626),
3382 (8_192, 3_276.8, 2_601),
3383 (8_192, 4_096.0, 3_251),
3384 (8_192, 6_144.0, 4_877),
3385 (8_192, 8_192.0, 6_502),
3386 (2_048, 512.0, 406),
3387 (4_096, 1_024.0, 813),
3388 (16_384, 4_096.0, 3_251),
3389 (32_768, 8_192.0, 6_502),
3390 (7_000, 700.0, 556),
3391 (10_000, 1_000.0, 794),
3392 (4_096, 409.6, 325),
3393 (1_024, 409.6, 325),
3394 (16_384, 256.0, 203),
3395 (16_384, 1_024.0, 813),
3396 (16_384, 8_192.0, 6_502),
3397 ];
3398
3399 #[test]
3400 fn the_fade_opens_where_the_editors_own_renders_open_it() {
3401 for &(length, crossfade, want) in MEASURED_FADES {
3402 let points = Loop::new(16_384, 16_384 + length).crossfade(crossfade);
3403 let plan = looped(88_200, 1, points).unwrap();
3404 assert_eq!(
3405 plan.looped.unwrap().crossfade,
3406 want,
3407 "a {crossfade} frame fade in a {length} frame loop"
3408 );
3409 }
3410 }
3411
3412 #[test]
3413 fn the_crossfade_ramps_linearly_into_the_material_before_the_loop() {
3414 let source = sine(150.0, 22_000.0, 88_200);
3415 let points = Loop::new(16_384, 32_768);
3416 let plan = looped(source.len(), 1, points).unwrap();
3417 let faded = looped(source.len(), 1, points.crossfade(4_096.0)).unwrap();
3418 let (plain, mixed) = (
3419 quantise(&source, &plan, None).values,
3420 quantise(&source, &faded, None).values,
3421 );
3422 assert_eq!(plain.len(), mixed.len());
3423
3424 let loop_at = faded.looped.unwrap();
3425 let end = faded.fields - loop_at.lead;
3426 let length = faded.fields - loop_at.at;
3427 let span = loop_at.crossfade;
3428 assert!(span > 3_000, "the fade is {span} fields");
3429 assert_eq!(plain[..end - span], mixed[..end - span]);
3431 for k in 0..span {
3432 let f = end - span + k;
3433 let (near, far) = (f64::from(plain[f]), f64::from(plain[f - length]));
3434 let u = k as f64 / span as f64;
3435 let want = near + (far - near) * u;
3436 assert!(
3437 (f64::from(mixed[f]) - want).abs() <= 1.0,
3438 "field {f}: {} against {want}",
3439 mixed[f]
3440 );
3441 }
3442 }
3443
3444 #[test]
3445 fn a_crossfade_may_begin_before_the_loop_start() {
3446 let source = sine(150.0, 22_000.0, 60_000);
3447 let points = Loop::new(16_384, 24_576).crossfade(16_384.0);
3448 let plan = looped(source.len(), 1, points).unwrap();
3449 let looped = plan.looped.unwrap();
3450
3451 assert!(looped.crossfade > fields_of(points.end - points.start).unwrap());
3452 assert!(looped.crossfade <= fields_of(points.start).unwrap());
3453 let file = instrument(&source, &Options::new("Long fade").loops(points)).unwrap();
3454 let (at, stroke) = file.stroke_streams()[0];
3455 assert!(codec::decode(stroke, at, codec::Layout::V2).is_ok());
3456 }
3457
3458 #[test]
3459 fn a_loop_the_format_cannot_state_is_refused() {
3460 let frames = 44_100;
3461 let stated = |points| looped(frames, 1, points);
3462 assert!(stated(Loop::new(8_192, 40_000)).is_ok());
3463 assert!(stated(Loop::new(8_192, 8_192)).is_err(), "empty loop");
3464 assert!(stated(Loop::new(40_000, 8_192)).is_err(), "loop runs back");
3465 assert!(stated(Loop::new(8_192, 44_101)).is_err(), "past the audio");
3466 assert!(
3467 stated(Loop::new(8_192, 8_250)).is_err(),
3468 "shorter than a run"
3469 );
3470 assert!(
3471 stated(Loop::new(1_024, 40_000).crossfade(4_096.0)).is_err(),
3472 "nothing in front of the loop to fade from"
3473 );
3474 assert!(
3475 stated(Loop::new(8_192, 40_000).crossfade(40_000.0)).is_err(),
3476 "not enough material before the fade"
3477 );
3478 assert!(looped(MIN_FRAMES - 1, 1, Loop::new(10, 60)).is_err());
3480 }
3481
3482 #[test]
3483 fn a_looped_stroke_round_trips_through_the_decoder_exactly() {
3484 let source = sine(180.0, 16_000.0, 60_000);
3485 for predictor in [Predictor::Plain, Predictor::Minimising] {
3486 for points in [
3487 Loop::new(8_192, 40_960),
3488 Loop::new(8_192, 40_960).crossfade(4_096.0),
3489 ] {
3490 let file = instrument(
3491 &source,
3492 &Options::new("Looped").predictor(predictor).loops(points),
3493 )
3494 .unwrap();
3495 let (at, stroke) = file.stroke_streams()[0];
3496 let plan = looped(source.len(), 1, points).unwrap();
3497 let q = quantise(&source, &plan, None);
3498 let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3499 assert_eq!(audio.samples.len(), plan.fields);
3500 let gain = 1i32 << q.shift;
3501 for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3502 assert_eq!(i32::from(got), want * gain, "{predictor:?} field {f}");
3503 }
3504 }
3505 }
3506 }
3507
3508 #[test]
3522 fn the_widen_fallback_walks_past_the_regions_alignment_records() {
3523 for (layout, widths) in [
3524 (Layout::V3, [1, 1, 13, 9, 1, 1]),
3525 (Layout::V4, [1, 1, 14, 8, 1, 1]),
3526 ] {
3527 let units = Units {
3528 layout,
3529 channels: 1,
3530 };
3531 let record = Spec {
3532 one_to_one: false,
3533 width: 1,
3534 order: 0,
3535 mark: false,
3536 first: 0,
3537 count: units.cell(),
3538 };
3539 let opening = Spec {
3540 one_to_one: true,
3541 ..record
3542 };
3543 let mut specs = vec![
3544 Spec {
3545 mark: true,
3546 ..opening
3547 },
3548 opening,
3549 record,
3550 record,
3551 record,
3552 record,
3553 ];
3554 pad_to_packet(&mut specs, 0, units).unwrap();
3555 assert_eq!(
3556 specs.iter().map(|s| s.width).collect::<Vec<_>>(),
3557 widths,
3558 "{layout:?}"
3559 );
3560 let words: usize = specs.iter().map(|s| s.span(units)).sum();
3561 assert_eq!(words % units.packet_words(), 0, "{layout:?}");
3562 }
3563 }
3564
3565 #[test]
3571 fn the_widen_cap_is_the_generations_constant() {
3572 for (layout, alignment, content, spent) in [
3573 (Layout::V2, 2usize, 9usize, [13u8, 13]),
3574 (Layout::V3, 1, 2, [13, 13]),
3575 (Layout::V4, 1, 2, [14, 12]),
3576 ] {
3577 let units = Units {
3578 layout,
3579 channels: 1,
3580 };
3581 let record = Spec {
3583 one_to_one: false,
3584 width: 12,
3585 order: 0,
3586 mark: false,
3587 first: 0,
3588 count: units.cell(),
3589 };
3590 let mut specs: Vec<Spec> = (0..alignment)
3591 .map(|i| Spec {
3592 one_to_one: true,
3593 width: 3,
3594 mark: i == 0,
3595 ..record
3596 })
3597 .chain(std::iter::repeat_n(record, content))
3598 .collect();
3599 pad_to_packet(&mut specs, 0, units).unwrap();
3600
3601 let mut want = vec![3u8; alignment];
3602 want.extend(spent);
3603 want.resize(alignment + content, record.width);
3604 assert_eq!(
3605 specs.iter().map(|s| s.width).collect::<Vec<_>>(),
3606 want,
3607 "{layout:?}"
3608 );
3609 let words: usize = specs.iter().map(|s| s.span(units)).sum();
3610 assert_eq!(words % units.packet_words(), 0, "{layout:?}");
3611 }
3612 }
3613
3614 #[test]
3615 fn a_loop_that_needs_width_past_the_measured_cap_is_refused() {
3616 let units = Units {
3617 layout: Layout::V3,
3618 channels: 1,
3619 };
3620 let record = Spec {
3621 one_to_one: false,
3622 width: widen_cap(Layout::V3),
3623 order: 0,
3624 mark: false,
3625 first: 0,
3626 count: units.cell(),
3627 };
3628 let mut specs = vec![
3629 Spec {
3630 one_to_one: true,
3631 width: 1,
3632 mark: true,
3633 ..record
3634 },
3635 record,
3636 record,
3637 ];
3638 let before = specs.clone();
3639 assert!(pad_to_packet(&mut specs, 0, units).is_err());
3640 assert_eq!(specs, before);
3641 }
3642
3643 #[test]
3644 fn a_loop_lands_on_a_packet_boundary_or_is_refused() {
3645 let mut source = Vec::with_capacity(60_000);
3646 let mut state = 12_345u64;
3647 for k in 0..60_000u64 {
3648 state = state
3649 .wrapping_mul(6_364_136_223_846_793_005)
3650 .wrapping_add(1);
3651 let noise = ((state >> 40) as i32 - 8_192) / 4;
3652 let tone = (20_000.0 * (k as f64 * 0.031).sin()) as i32;
3653 source.push((tone + noise).clamp(-32_768, 32_767) as i16);
3654 }
3655
3656 let mut placed = 0usize;
3657 let mut refused = 0usize;
3658 for start in (4_096..48_000).step_by(7_919) {
3659 for length in [900, 1_500, 4_096, 11_000] {
3660 for predictor in [Predictor::Plain, Predictor::Minimising] {
3661 let points =
3662 Loop::new(start, start + length).crossfade((length / 4).min(start) as f64);
3663 let options = Options::new("Sweep").predictor(predictor).loops(points);
3664 let Ok(file) = instrument(&source, &options) else {
3665 refused += 1;
3666 continue;
3667 };
3668 let (at, stroke) = file.stroke_streams()[0];
3669 let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3670 let mark = walk.records.iter().find(|r| r.mark).unwrap();
3671 assert_eq!(
3672 (walk.terminator - mark.at) % PACKET_WORDS,
3673 0,
3674 "loop {start}..{} under {predictor:?} covers {} words",
3675 start + length,
3676 walk.terminator - mark.at
3677 );
3678 placed += 1;
3679 }
3680 }
3681 }
3682 assert!(placed > 0, "no loop was placed");
3683 assert!(refused > 0, "no loop was refused");
3684 }
3685
3686 fn stereo(hz: f64, ratio: f64, amplitude: f64, frames: usize) -> Vec<i16> {
3687 let left = sine(hz, amplitude, frames);
3688 let right = sine(hz * ratio, amplitude * 0.6, frames);
3689 left.iter()
3690 .zip(&right)
3691 .flat_map(|(&l, &r)| [l, r])
3692 .collect()
3693 }
3694
3695 #[test]
3696 fn a_stereo_plan_is_the_mono_plan_doubled() {
3697 for frames in [4096, 4409, 8192, 10_000, 44_100, 100_000, 441_000] {
3698 let mono = plan(frames, 1).unwrap();
3699 let both = plan(frames, 2).unwrap();
3700 assert_eq!(both.fields, 2 * mono.fields, "{frames} frames: T");
3701 assert_eq!(both.resync_at, 2 * mono.resync_at, "{frames} frames: R1");
3702 assert_eq!(both.warmup, 2 * mono.warmup, "{frames} frames: W");
3703 assert_eq!(both.resync, 2 * mono.resync, "{frames} frames: R");
3704 assert_eq!(both.cells_before, mono.cells_before, "{frames} frames");
3705 assert_eq!(both.cells_after, mono.cells_after, "{frames} frames");
3706 assert_eq!(
3707 both.warmup
3708 + both.cell() * both.cells_before
3709 + both.resync
3710 + both.cell() * both.cells_after,
3711 both.fields,
3712 "{frames} frames: the plan does not tile the lattice"
3713 );
3714 }
3715 }
3716
3717 #[test]
3718 fn a_stereo_stroke_round_trips_through_the_decoder_exactly() {
3719 for predictor in [Predictor::Plain, Predictor::Minimising] {
3720 let source = stereo(220.0, 1.5, 14_000.0, 30_000);
3721 let file = instrument(
3722 &source,
3723 &Options::new("Stereo").channels(2).predictor(predictor),
3724 )
3725 .unwrap();
3726 let (at, stroke) = file.stroke_streams()[0];
3727
3728 let stream = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3729 assert_eq!(stream.channels, 2, "{predictor:?}");
3730 assert_eq!(stream.cell, Some(2 * CELL), "{predictor:?}");
3731 assert_eq!(&stroke[stroke.len() - 3..], &[0x80, 0, 48]);
3732
3733 let plan = plan(30_000, 2).unwrap();
3734 let q = quantise(&source, &plan, None);
3735 let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3736 assert_eq!(audio.channels, 2);
3737 assert_eq!(audio.samples.len(), plan.fields);
3738 let gain = 1i32 << q.shift;
3739 for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3740 assert_eq!(i32::from(got), want * gain, "{predictor:?} field {f}");
3741 }
3742 }
3743 }
3744
3745 #[test]
3746 fn each_channel_predicts_against_its_own_history() {
3747 let frames = 20_000;
3748 let source: Vec<i16> = (0..frames)
3749 .flat_map(|k| {
3750 let up = (k as i32 % 2048) - 1024;
3751 [up as i16, -(up as i16)]
3752 })
3753 .collect();
3754 let file = instrument(
3755 &source,
3756 &Options::new("Ramps")
3757 .channels(2)
3758 .predictor(Predictor::Minimising),
3759 )
3760 .unwrap();
3761 let (at, stroke) = file.stroke_streams()[0];
3762 let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3763 assert!(audio.differenced > 0, "nothing chose a predictor");
3764
3765 let plan = plan(frames, 2).unwrap();
3766 let q = quantise(&source, &plan, None);
3767 let gain = 1i32 << q.shift;
3768 for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3769 assert_eq!(i32::from(got), want * gain, "field {f}");
3770 }
3771 }
3772
3773 #[test]
3774 fn the_channels_are_resampled_apart() {
3775 let frames = 12_000;
3776 let source: Vec<i16> = sine(300.0, 20_000.0, frames)
3777 .into_iter()
3778 .flat_map(|l| [l, 0])
3779 .collect();
3780 let file = instrument(&source, &Options::new("Panned").channels(2)).unwrap();
3781 let (at, stroke) = file.stroke_streams()[0];
3782 let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3783 assert!(audio.samples.iter().step_by(2).any(|&v| v.abs() > 10_000));
3784 assert!(audio.samples[1..].iter().step_by(2).all(|&v| v == 0));
3785 }
3786
3787 #[test]
3788 fn a_stereo_stroke_loops_the_way_a_mono_one_does() {
3789 let source = stereo(180.0, 1.25, 16_000.0, 60_000);
3790 let points = Loop::new(8_192, 40_960).crossfade(2_048.0);
3791 let file = instrument(&source, &Options::new("Looped").channels(2).loops(points)).unwrap();
3792 let (at, stroke) = file.stroke_streams()[0];
3793 let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3794 assert_eq!(walk.channels, 2);
3795 let mark = walk.records.iter().find(|r| r.mark).unwrap();
3796 assert_eq!((walk.terminator - mark.at) % PACKET_WORDS, 0);
3797 let frames = (walk.fields - mark.first_field) as f64 / 2.0 * f64::from(codec::SOURCE_RATE)
3798 / f64::from(codec::FIELD_RATE);
3799 assert!(
3800 (frames - 32_768.0).abs() < 1.0,
3801 "loop came back {frames} frames"
3802 );
3803
3804 let plan = looped(60_000, 2, points).unwrap();
3805 let q = quantise(&source, &plan, None);
3806 let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3807 let gain = 1i32 << q.shift;
3808 for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3809 assert_eq!(i32::from(got), want * gain, "field {f}");
3810 }
3811 }
3812
3813 #[test]
3814 fn a_channel_count_the_terminator_cannot_state_is_refused() {
3815 let source = vec![0i16; 3 * MIN_FRAMES];
3816 assert!(plan(MIN_FRAMES, 0).is_err());
3817 assert!(plan(MIN_FRAMES, 3).is_err());
3818 assert!(instrument(&source, &Options::new("x").channels(3)).is_err());
3819 assert!(instrument(
3820 &vec![0i16; 2 * MIN_FRAMES + 1],
3821 &Options::new("x").channels(2)
3822 )
3823 .is_err());
3824 assert!(instrument(&vec![0i16; 2 * MIN_FRAMES], &Options::new("x").channels(2)).is_ok());
3825 let short = vec![0i16; MIN_FRAMES];
3826 assert!(instrument(&short, &Options::new("x")).is_ok());
3827 assert!(instrument(&short, &Options::new("x").channels(2)).is_err());
3828 }
3829
3830 #[test]
3834 fn every_generation_round_trips_through_the_decoder_exactly() {
3835 for layout in [Layout::V2, Layout::V3, Layout::V4] {
3836 for channels in [1u16, 2] {
3837 let frames = 30_000;
3838 let source: Vec<i16> = match channels {
3839 1 => sine(220.0, 14_000.0, frames),
3840 _ => stereo(220.0, 1.5, 14_000.0, frames),
3841 };
3842 let file = instrument(
3843 &source,
3844 &Options::new("Round trip")
3845 .layout(layout)
3846 .channels(channels)
3847 .predictor(Predictor::Minimising),
3848 )
3849 .unwrap();
3850 let (at, stroke) = file.stroke_streams()[0];
3851 let plan = Plan::new(
3852 layout,
3853 frames,
3854 usize::from(channels),
3855 default_secondary_start(frames, None),
3856 )
3857 .unwrap();
3858 let q = quantise(&source, &plan, None);
3859 let audio = codec::decode(stroke, at, layout)
3860 .unwrap_or_else(|e| panic!("{layout:?} {channels}ch: {e}"));
3861 assert_eq!(audio.channels, channels, "{layout:?} {channels}ch");
3862 assert_eq!(audio.samples.len(), plan.fields, "{layout:?} {channels}ch");
3863 let gain = 1i32 << q.shift;
3864 for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3865 assert_eq!(
3866 i32::from(got),
3867 want * gain,
3868 "{layout:?} {channels}ch field {f}"
3869 );
3870 }
3871 }
3872 }
3873 }
3874
3875 #[test]
3876 fn a_wide_instrument_reads_back_as_one() {
3877 for (layout, version) in [(Layout::V3, 300u32), (Layout::V4, 400)] {
3878 let file = instrument(
3879 &sine(220.0, 15_000.0, 30_000),
3880 &Options::new("Encoded")
3881 .layout(layout)
3882 .root_key(48)
3883 .top_note(72),
3884 )
3885 .unwrap();
3886 let bytes = file.to_bytes().unwrap();
3887 let read = crate::from_stream(&mut std::io::Cursor::new(&bytes)).unwrap();
3888 let crate::Entity::Sample(read) = read else {
3889 panic!("{layout:?} did not read back as a sample");
3890 };
3891 assert_eq!(read.name().unwrap(), "Encoded", "{layout:?}");
3892 assert_eq!(read.layout().unwrap(), layout, "{layout:?}");
3893 assert_eq!(read.to_bytes().unwrap(), bytes, "{layout:?}");
3894 let crate::Sample::V3(read) = &read else {
3895 panic!("{layout:?} did not read back on the wide chain");
3896 };
3897 assert_eq!(read.header.version, version);
3898 let zones = read.zones().unwrap();
3899 assert_eq!(zones.len(), 1);
3900 assert_eq!(zones[0].root_key, 48);
3901 assert_eq!(zones[0].top_note, 72);
3902 assert_eq!(zones[0].low_note, Some(super::super::zone::KEY_FLOOR));
3903 assert_eq!(
3904 read.meta().unwrap().chain_len as usize,
3905 read.chain_len_before_meta()
3906 );
3907 }
3908 }
3909
3910 #[test]
3913 fn a_wide_zone_states_its_own_bottom() {
3914 let high = sine(880.0, 12_000.0, 12_000);
3915 let low = sine(220.0, 12_000.0, 15_000);
3916 let floor = super::super::zone::KEY_FLOOR;
3917 let stored = [(96, 66), (65, floor)];
3918 for layout in [Layout::V3, Layout::V4] {
3919 let file = multi_zone(
3920 made("Two", Predictor::Plain, layout),
3921 &[zone(&high, 72, 96, 2), zone(&low, 48, 65, 1)],
3922 )
3923 .unwrap();
3924 let zones = file.zones().unwrap();
3925 assert_eq!(
3926 zones
3927 .iter()
3928 .map(|z| (z.top_note, z.low_note.unwrap()))
3929 .collect::<Vec<_>>(),
3930 stored,
3931 "{layout:?}"
3932 );
3933 }
3934 }
3935
3936 #[test]
3941 fn a_zone_gain_in_decibels_is_the_word_the_editor_writes() {
3942 for (gain, word) in [
3943 (-1.0, 0x7fc0_0000u32),
3944 (0.0, 0xff80_0000),
3945 (0.01, 0xc220_0000),
3946 (0.1, 0xc1a0_0000),
3947 (0.5, 0xc0c0_a8c1),
3948 (1.0, 0x0000_0000),
3949 (1.1, 0x3f53_ee38),
3950 (1.5, 0x4061_6595),
3951 (2.0, 0x40c0_a8c1),
3952 (4.0, 0x4140_a8c1),
3953 (8.0, 0x4190_7e91),
3954 (16.0, 0x41c0_a8c1),
3955 (20.5, 0x41d1_e170),
3956 (63.75, 0x4210_5bc1),
3957 (333.33, 0x4249_d478),
3958 (1000.0, 0x4270_0000),
3959 ] {
3960 assert_eq!(gain_decibels(gain).to_bits(), word, "a gain of {gain}");
3961 }
3962 }
3963
3964 #[test]
3968 fn the_gain_statistic_a_uses_is_the_decibels_round_trip() {
3969 for (gain, delta) in [
3970 (0.01, 0i64),
3971 (1.1, 0),
3972 (15.99, 0),
3973 (16.0, -1),
3974 (20.5, -1),
3975 (24.0, 1),
3976 (33.0, 2),
3977 (48.0, -1),
3978 (63.75, -3),
3979 (100.0, 0),
3980 (333.33, 39),
3981 (1000.0, 0),
3982 ] {
3983 let plain = (gain * f64::from(GAIN_UNITY)).round() as i64;
3984 let round_trip = gain_units(gain_decibels(gain)) as i64;
3985 assert_eq!(round_trip - plain, delta, "a gain of {gain}");
3986 }
3987 }
3988
3989 #[test]
3994 fn a_map_gain_is_the_word_the_editor_writes_and_clamps_at_the_ceiling() {
3995 for (gain, units) in [
3996 (-1.0, 0x2d_18_19_u32),
3997 (0.0, 0x00_00_00),
3998 (0.0001, 0x00_00_69),
3999 (0.01, 0x00_28_f6),
4000 (0.5, 0x08_00_00),
4001 (1.0, 0x10_00_00),
4002 (1.1, 0x11_99_9a),
4003 (2.0, 0x20_00_00),
4004 (2.8125, 0x2d_00_00),
4005 (2.828125, 0x2d_18_19),
4006 (4.0, 0x2d_18_19),
4007 (16.0, 0x2d_18_19),
4008 ] {
4009 assert_eq!(map_gain_units(gain), units, "a map gain of {gain}");
4010 }
4011 }
4012
4013 #[test]
4017 fn a_zone_gain_past_sixteen_wraps_in_both_stores() {
4018 for (gain, record) in [
4019 (-1.0, 0x00_00_00_u32),
4020 (0.0, 0x00_00_00),
4021 (15.99, 0xff_d7_0a),
4022 (16.0, 0x00_00_00),
4023 (33.0, 0x10_00_00),
4024 (333.33, 0xd5_47_ae),
4025 (1000.0, 0x80_00_00),
4026 ] {
4027 assert_eq!(zone_record_gain(gain), record, "a gain of {gain}");
4028 }
4029 for (gain, mantissa) in [
4032 (-1.0, 0x00_00_00_u32),
4033 (0.0, 0x00_00_00),
4034 (15.99, 0x7f_eb_85),
4035 (16.0, 0x7f_ff_ff),
4036 (33.0, 0x08_00_01),
4037 (333.33, 0x6a_a3_ea),
4038 (1000.0, 0x40_00_00),
4039 ] {
4040 let (got, _) = statistic_a(4096, 0, gain_units(gain_decibels(gain)));
4041 assert_eq!(got, mantissa, "a gain of {gain}");
4042 }
4043 }
4044
4045 #[test]
4048 fn a_map_gain_moves_the_map_section_alone() {
4049 let source = sine(440.0, 12_000.0, 20_000);
4050 for layout in [Layout::V2, Layout::V3, Layout::V4] {
4051 let unity = made("Map", Predictor::Plain, layout);
4052 let quiet = Instrument {
4053 map_gain: 0.5,
4054 ..unity
4055 };
4056 let one = [zone(&source, 60, 127, 1)];
4057 let before = multi_zone(unity, &one).unwrap().to_bytes().unwrap();
4058 let after = multi_zone(quiet, &one).unwrap().to_bytes().unwrap();
4059 assert_eq!(before.len(), after.len(), "{layout:?}");
4060 let moved: Vec<_> = (0..before.len())
4061 .filter(|&i| before[i] != after[i])
4062 .collect();
4063 assert!(moved.len() <= 1 + 4, "{layout:?}: {moved:?}");
4065 }
4066 }
4067
4068 #[test]
4069 fn a_project_preset_reaches_each_generation_in_its_own_schema() {
4070 let source = sine(440.0, 12_000.0, 20_000);
4071 let preset = Preset {
4072 dynamics_enabled: true,
4073 velocity_to_amplitude: 2,
4074 velocity_to_timbre: 0,
4075 };
4076 for layout in [Layout::V2, Layout::V3, Layout::V4] {
4077 let instrument = Instrument {
4078 preset,
4079 ..made("Preset", Predictor::Plain, layout)
4080 };
4081 let sample = multi_zone(instrument, &[zone(&source, 60, 127, 1)]).unwrap();
4082 match sample {
4083 crate::Sample::V2(file) => {
4084 let sty = section::find(&file.body.sections, section::STY).unwrap();
4085 assert_eq!(sty.payload, [0, 1, 0, 1, 2, 0, 0, 0, 0]);
4086 }
4087 crate::Sample::V3(file) => {
4088 let sty = section::find4(&file.body.sections, section::STY4).unwrap();
4089 match layout {
4090 Layout::V3 => {
4091 assert_eq!((sty.payload[4], sty.payload[12]), (43, 74));
4092 assert_eq!((sty.payload[14], sty.payload[16]), (1, 74));
4093 }
4094 Layout::V4 => {
4095 assert_eq!((sty.payload[3], sty.payload[4]), (1, 1));
4096 assert_eq!(sty.payload[85..88], [74, 82, 90]);
4097 }
4098 Layout::V2 => unreachable!(),
4099 }
4100 }
4101 }
4102 }
4103 }
4104
4105 #[test]
4108 fn a_wide_zone_gain_lands_in_the_stroke_header() {
4109 let source = sine(440.0, 12_000.0, 20_000);
4110 for layout in [Layout::V3, Layout::V4] {
4111 let one = zone(&source, 60, 127, 1);
4112 let made = made("Gain", Predictor::Plain, layout);
4113 let unity = multi_zone(made, &[one]).unwrap();
4114 let halved = multi_zone(made, &[NewZone { gain: 0.5, ..one }]).unwrap();
4115 let (_, a) = unity.stroke_streams()[0];
4116 let (_, b) = halved.stroke_streams()[0];
4117 let mantissa = |s: &[u8]| u32::from_be_bytes([0, s[9], s[10], s[11]]);
4118 assert_eq!(mantissa(b), mantissa(a) / 2, "{layout:?}");
4119 let gain_at = codec::TAIL_FLOATS_AT[0];
4120 assert_eq!(a[..9], b[..9], "{layout:?}");
4121 assert_eq!(a[12..gain_at], b[12..gain_at], "{layout:?}");
4122 assert_eq!(a[gain_at + 4..], b[gain_at + 4..], "{layout:?}");
4123 assert_eq!(
4124 codec::zone_gain_db(b, layout),
4125 Some(gain_decibels(0.5)),
4126 "{layout:?}"
4127 );
4128 let (before, after) = (unity.to_bytes().unwrap(), halved.to_bytes().unwrap());
4131 let differing = before.iter().zip(&after).filter(|(x, y)| x != y).count();
4132 assert_eq!(before.len(), after.len(), "{layout:?}");
4133 assert!(differing <= 3 + 4 + 4, "{layout:?}: {differing} bytes");
4134 }
4135 }
4136
4137 #[test]
4140 fn a_loop_decay_lands_in_the_wide_header_and_nowhere_narrow() {
4141 let source = sine(440.0, 12_000.0, 20_000);
4142 let at = codec::TAIL_FLOATS_AT[1];
4143 for layout in [Layout::V2, Layout::V3, Layout::V4] {
4144 let one = zone(&source, 60, 127, 1);
4145 let made = made("Decay", Predictor::Plain, layout);
4146 let base = multi_zone(made, &[one]).unwrap();
4147 let slower = multi_zone(
4148 made,
4149 &[NewZone {
4150 loop_decay: 60.0,
4151 ..one
4152 }],
4153 )
4154 .unwrap();
4155 let (_, a) = base.stroke_streams()[0];
4156 let (_, b) = slower.stroke_streams()[0];
4157 let wide = layout != Layout::V2;
4158 assert_eq!(
4159 codec::loop_decay(a, layout),
4160 wide.then_some(DEFAULT_LOOP_DECAY),
4161 "{layout:?}"
4162 );
4163 assert_eq!(
4164 codec::loop_decay(b, layout),
4165 wide.then_some(60.0),
4166 "{layout:?}"
4167 );
4168 match wide {
4169 false => assert_eq!(a, b),
4170 true => {
4171 assert_eq!(a[..at], b[..at], "{layout:?}");
4172 assert_eq!(a[at + 4..], b[at + 4..], "{layout:?}");
4173 }
4174 }
4175 }
4176 }
4177
4178 #[test]
4179 fn a_zone_gain_past_the_measured_range_is_refused() {
4180 let source = sine(440.0, 12_000.0, 20_000);
4181 for layout in [Layout::V2, Layout::V3, Layout::V4] {
4182 for gain in [MAX_ZONE_GAIN * 2.0, f64::NAN, f64::INFINITY] {
4183 let loud = NewZone {
4184 gain,
4185 ..zone(&source, 60, 127, 1)
4186 };
4187 assert!(
4188 multi_zone(made("Gain", Predictor::Plain, layout), &[loud]).is_err(),
4189 "{layout:?} at {gain}"
4190 );
4191 }
4192 let wrapping = NewZone {
4193 gain: MAX_ZONE_GAIN,
4194 ..zone(&source, 60, 127, 1)
4195 };
4196 assert!(multi_zone(made("Gain", Predictor::Plain, layout), &[wrapping]).is_ok());
4197 }
4198 }
4199
4200 #[test]
4203 fn the_wide_plan_tiles_the_lattice_in_its_own_units() {
4204 for frames in [4096, 10_000, 44_100, 100_000] {
4205 for layout in [Layout::V3, Layout::V4] {
4206 let p =
4207 Plan::new(layout, frames, 1, default_secondary_start(frames, None)).unwrap();
4208 assert_eq!(p.cell(), 32, "{layout:?} {frames} frames");
4209 assert_eq!(
4210 p.warmup + p.cell() * p.cells_before + p.resync + p.cell() * p.cells_after,
4211 p.fields,
4212 "{layout:?} {frames} frames"
4213 );
4214 for run in chunks(p.warmup, p.chunk())
4215 .into_iter()
4216 .chain(chunks(p.resync, p.chunk()))
4217 {
4218 assert!(
4219 (32..=48).contains(&run),
4220 "{layout:?} {frames} frames: {run}"
4221 );
4222 }
4223 }
4224 }
4225 }
4226
4227 #[test]
4230 fn a_wide_statistic_b_carries_the_extremes_sign() {
4231 let frames = 20_000;
4232 let mut down = vec![0i16; frames];
4233 down[10_000] = -13;
4234 for layout in [Layout::V2, Layout::V3, Layout::V4] {
4235 let file = instrument(&down, &Options::new("Peak").layout(layout)).unwrap();
4236 let (_, stroke) = file.stroke_streams()[0];
4237 let want = if layout.signed_peak() { -3 } else { 3 };
4238 assert_eq!(codec::peak(stroke, layout), Some(want), "{layout:?}");
4239 }
4240 }
4241
4242 #[test]
4243 fn silence_codes_at_the_draft_width_throughout() {
4244 let file = encoded(&vec![0i16; 44_100], Predictor::Plain);
4245 let (at, stroke) = file.stroke_streams()[0];
4246 let stream = codec::walk(stroke, at, codec::Layout::V2).unwrap();
4247 assert!(stream.records.iter().all(|r| r.width == MIN_WIDTH));
4248 assert!(stream
4249 .records
4250 .iter()
4251 .all(|r| r.values.iter().all(|&v| v == 0)));
4252 assert_eq!(codec::peak(stroke, codec::Layout::V2), Some(0));
4253 assert!(codec::decode(stroke, at, codec::Layout::V2)
4254 .unwrap()
4255 .samples
4256 .iter()
4257 .all(|&s| s == 0));
4258 }
4259}