1use super::codec::{self, MAX_ORDER, MAX_WIDTH, MIN_WIDTH, OVERLAP};
76use super::{
77 be32, block_bytes, midi_key, Bank, Library, Stroke, CNSP_MAGIC, DAMPER_TOP_AT, DECAYS,
78 DIRECTORY_AT, FINE_TUNE_AT, FORMAT, GAIN_AT, KEY_MAP_AT, KIND_AT, LADDER_UNITY, MARKS, NOTES,
79 RECORD, REC_BANK, REC_BLOCKS, REC_DECAY, REC_DECAYS, REC_FRAMES, REC_ID, REC_LAYER, REC_MARKS,
80 REC_MARK_BLOCK, REC_SEEDS, REC_START, REC_TRIM, REC_WINDOW, SEEDS, UNCOVERED, VERSION_AT,
81 VERSION_ECHO_AT,
82};
83use crate::cbin::Header;
84use crate::error::{Error, ParseError};
85use crate::formats::nsmp::kernel;
86use crate::formats::predictor;
87use std::borrow::Cow;
88use std::collections::{BTreeMap, BTreeSet};
89
90const FULL_SCALE: f64 = 8192.0;
92
93const WIDTHS: usize = MAX_WIDTH as usize + 1;
95
96#[derive(Debug, Clone)]
100pub struct Options {
101 pub name: String,
103 pub variant: String,
105}
106
107impl Options {
108 pub fn new(name: &str) -> Options {
109 Options {
110 name: name.to_owned(),
111 variant: String::new(),
112 }
113 }
114
115 pub fn variant(mut self, variant: &str) -> Options {
116 self.variant = variant.to_owned();
117 self
118 }
119}
120
121#[derive(Debug, Clone)]
123pub enum Donor<'a> {
124 Template(&'a Library<'a>),
127 Rules(Rules),
130}
131
132#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
138pub enum Kind {
139 ElectricGrand,
140 ElectricPiano,
142 Wurlitzer,
144 Clavinet,
145 #[default]
146 Grand,
147 Upright,
148 Harpsichord,
149 DigitalPiano,
150 Hybrid,
152 Mallet,
153}
154
155impl Kind {
156 pub const ALL: [Kind; 10] = [
157 Kind::ElectricGrand,
158 Kind::ElectricPiano,
159 Kind::Wurlitzer,
160 Kind::Clavinet,
161 Kind::Grand,
162 Kind::Upright,
163 Kind::Harpsichord,
164 Kind::DigitalPiano,
165 Kind::Hybrid,
166 Kind::Mallet,
167 ];
168
169 pub fn from_code(code: u8) -> Option<Kind> {
170 match code {
171 1 => Some(Kind::ElectricGrand),
172 2 => Some(Kind::ElectricPiano),
173 3 => Some(Kind::Wurlitzer),
174 4 => Some(Kind::Clavinet),
175 5 => Some(Kind::Grand),
176 6 => Some(Kind::Upright),
177 7 => Some(Kind::Harpsichord),
178 14 => Some(Kind::DigitalPiano),
179 15 => Some(Kind::Hybrid),
180 16 => Some(Kind::Mallet),
181 _ => None,
182 }
183 }
184
185 pub fn code(self) -> u8 {
186 match self {
187 Kind::ElectricGrand => 1,
188 Kind::ElectricPiano => 2,
189 Kind::Wurlitzer => 3,
190 Kind::Clavinet => 4,
191 Kind::Grand => 5,
192 Kind::Upright => 6,
193 Kind::Harpsichord => 7,
194 Kind::DigitalPiano => 14,
195 Kind::Hybrid => 15,
196 Kind::Mallet => 16,
197 }
198 }
199
200 pub fn damper_top(self) -> u8 {
204 match self {
205 Kind::Grand | Kind::Upright => 90,
206 Kind::Wurlitzer => 97,
207 _ => ALL_KEYS_DAMPED,
208 }
209 }
210}
211
212pub const ALL_KEYS_DAMPED: u8 = 109;
215
216pub const DEFAULT_GAIN: i8 = 50;
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub struct Rules {
227 pub kind: Kind,
228 pub gain: i8,
231 pub damper_top: u8,
234}
235
236impl Rules {
237 pub fn new(kind: Kind) -> Rules {
239 Rules {
240 kind,
241 gain: DEFAULT_GAIN,
242 damper_top: kind.damper_top(),
243 }
244 }
245}
246
247impl Default for Rules {
248 fn default() -> Rules {
249 Rules::new(Kind::default())
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct Recording {
256 pub root: u8,
258 pub bank: Bank,
259 pub layer: u8,
264 pub channels: Vec<Vec<i16>>,
271}
272
273pub const SOFTEST_LAYER: u8 = 27;
276
277pub const HIGHEST_PLAYED_LAYER: u8 = ((127 - 1) * 31 / 127) as u8;
285
286pub fn layer_value(index: usize, layers: usize) -> u8 {
294 let last = layers.saturating_sub(1);
295 if last == 0 {
296 return 0;
297 }
298 let index = index.min(last);
299 let scale = usize::from(SOFTEST_LAYER);
300 ((index * scale * 2 + last) / (last * 2)) as u8
301}
302
303#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
305pub enum LayerTag {
306 Index(u8),
309 Value(u8),
311}
312
313#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316pub enum Stem {
317 None,
319 Any,
321}
322
323pub fn parse_stroke_name(name: &str, stem: Stem) -> Option<(u8, Bank, LayerTag)> {
328 let mut parts = name.rsplit('-');
329 let third = parts.next()?;
330 let layer = match (third.strip_prefix('l'), third.strip_prefix('v')) {
331 (Some(index), _) => LayerTag::Index(index.parse().ok()?),
332 (None, Some(value)) => LayerTag::Value(value.parse().ok()?),
333 (None, None) => return None,
334 };
335 let bank = Bank::from_code(parts.next()?.strip_prefix('b')?.parse().ok()?)?;
336 let root: u8 = parts.next()?.parse().ok()?;
337 if stem == Stem::None && parts.next().is_some() {
338 return None;
339 }
340 (usize::from(root) < NOTES).then_some((root, bank, layer))
341}
342
343#[derive(Clone, Copy, Debug, PartialEq, Eq)]
345pub struct LayerClash {
346 pub root: u8,
347 pub bank: Bank,
348 pub how: Clash,
349}
350
351#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353pub enum Clash {
354 BothForms,
358 Twice,
360}
361
362pub fn layer_values(strokes: &[(u8, Bank, LayerTag)]) -> Result<Vec<u8>, LayerClash> {
367 let mut groups: BTreeMap<(u8, Bank), Vec<usize>> = BTreeMap::new();
368 for (index, &(root, bank, _)) in strokes.iter().enumerate() {
369 groups.entry((root, bank)).or_default().push(index);
370 }
371
372 let mut values = vec![0u8; strokes.len()];
373 for ((root, bank), mut members) in groups {
374 let clash = |how| LayerClash { root, bank, how };
375 let stated = members
376 .iter()
377 .filter(|&&i| matches!(strokes[i].2, LayerTag::Value(_)))
378 .count();
379 if stated != 0 && stated != members.len() {
380 return Err(clash(Clash::BothForms));
381 }
382 members.sort_by_key(|&i| strokes[i].2);
383 if members
384 .windows(2)
385 .any(|pair| strokes[pair[0]].2 == strokes[pair[1]].2)
386 {
387 return Err(clash(Clash::Twice));
388 }
389 let layers = members.len();
390 for (rank, index) in members.into_iter().enumerate() {
391 values[index] = match strokes[index].2 {
392 LayerTag::Value(value) => value,
393 LayerTag::Index(_) => layer_value(rank, layers),
394 };
395 }
396 }
397 Ok(values)
398}
399
400pub struct Rebuilt {
403 pub library: Library<'static>,
404 pub strokes: Vec<Recoded>,
406}
407
408#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
410pub struct Recoded {
411 pub blocks: usize,
413 pub identical: usize,
415 pub restated: usize,
417}
418
419impl Recoded {
420 pub fn recoded(&self) -> usize {
423 self.blocks - self.identical - self.restated
424 }
425}
426
427pub fn build(
435 donor: &Donor<'_>,
436 options: &Options,
437 recordings: &[Recording],
438) -> Result<Library<'static>, Error> {
439 let channels = check_recordings(recordings)?;
440
441 let (header, mut prefix) = match donor {
442 Donor::Template(template) => (template.header.clone(), template.prefix.clone()),
443 Donor::Rules(rules) => (
444 Header::new(FORMAT, (0, 0), CONTENT_VERSION),
445 rules_prefix(rules),
446 ),
447 };
448 prefix[FINE_TUNE_AT..FINE_TUNE_AT + NOTES].fill(0);
449 let roots: BTreeSet<u8> = recordings.iter().map(|r| r.root).collect();
450 prefix[KEY_MAP_AT..KEY_MAP_AT + NOTES].copy_from_slice(&key_map(&roots));
451
452 let mut library = Library {
453 header,
454 prefix,
455 channels,
456 strokes: Vec::new(),
457 };
458 library.set_name_and_variant(&options.name, &options.variant)?;
459
460 let mut order: Vec<&Recording> = recordings.iter().collect();
461 order.sort_by_key(|r| (r.root, r.bank.code(), r.layer));
462
463 let mut donors = Vec::with_capacity(order.len());
464 for (index, recording) in order.iter().enumerate() {
465 donors.push(match donor {
466 Donor::Template(template) => *donor_record(template, recording)?,
467 Donor::Rules(_) => rules_record(recording, index),
468 });
469 }
470 let unique: BTreeSet<u32> = donors.iter().map(|d| be32(d, REC_ID)).collect();
473 let keep_ids = unique.len() == donors.len();
474
475 for (index, (recording, donor)) in order.iter().zip(&donors).enumerate() {
476 let seeds = seeds_for(&recording.channels);
477 let target = recording.channels[0].len();
478 let coded = code(&recording.channels, &seeds, target)?;
479 let id = if keep_ids {
480 be32(donor, REC_ID)
481 } else {
482 index as u32 + 1
483 };
484 library.strokes.push(Stroke {
485 root: recording.root,
486 record: record(
487 donor,
488 &coded,
489 recording.bank.code(),
490 recording.layer,
491 &seeds,
492 id,
493 )?,
494 audio: Cow::Owned(coded.audio),
495 });
496 }
497 Ok(library)
498}
499
500const CONTENT_VERSION: u32 = 540;
505
506const RULES_VERSION: u16 = 0x450;
509
510const FILE_ID_AT: usize = 0x06;
513const FILE_ID: u32 = 1;
514
515const VERSION_REPEAT_AT: usize = 0x16;
517
518const KIND_TRAILER: [u8; 3] = [0, 0, 2];
522
523const PER_NOTE_TABLES: [(usize, u8); 6] = [
528 (0x10c, 0),
529 (FINE_TUNE_AT, 0),
530 (0x20c, 57),
531 (0x28c, 0),
532 (0x30c, 0),
533 (0x38c, 0),
534];
535
536const PARAMETERS: std::ops::Range<usize> = 0x40c..0x60f;
538const PARAMETER_TAIL_AT: usize = 0x40e;
541const PARAMETER_TAIL: [u8; 3] = [10, 108, 1];
542const BEFORE_DAMPER_CUT_AT: usize = 0x489;
545const BEFORE_DAMPER_CUT: [u8; 19] = [128; 19];
546const DAMPER_CUT_AT: usize = 0x49d;
548const _: () = assert!(DAMPER_CUT_AT + NOTES <= PARAMETERS.end);
549
550fn rules_prefix(rules: &Rules) -> Vec<u8> {
557 let mut prefix = vec![0u8; DIRECTORY_AT];
558 prefix[..CNSP_MAGIC.len()].copy_from_slice(CNSP_MAGIC);
559 for at in [VERSION_AT, VERSION_REPEAT_AT, VERSION_ECHO_AT] {
560 prefix[at..at + 2].copy_from_slice(&RULES_VERSION.to_be_bytes());
561 }
562 prefix[FILE_ID_AT..FILE_ID_AT + 4].copy_from_slice(&FILE_ID.to_be_bytes());
563 prefix[KIND_AT] = rules.kind.code();
564 prefix[KIND_AT + 1..KIND_AT + 1 + KIND_TRAILER.len()].copy_from_slice(&KIND_TRAILER);
565 for (at, value) in PER_NOTE_TABLES {
566 prefix[at..at + NOTES].fill(value);
567 }
568 prefix[GAIN_AT] = rules.gain as u8;
569 prefix[DAMPER_TOP_AT] = rules.damper_top;
570 prefix[PARAMETER_TAIL_AT..PARAMETER_TAIL_AT + PARAMETER_TAIL.len()]
571 .copy_from_slice(&PARAMETER_TAIL);
572 prefix[BEFORE_DAMPER_CUT_AT..BEFORE_DAMPER_CUT_AT + BEFORE_DAMPER_CUT.len()]
573 .copy_from_slice(&BEFORE_DAMPER_CUT);
574 for note in 0..NOTES {
575 prefix[DAMPER_CUT_AT + note] = damper_cut(note);
576 }
577 prefix
578}
579
580fn damper_cut(note: usize) -> u8 {
587 const FLAT_TO: usize = 24;
589 const TOP: usize = 108;
590 const PLATEAU: f64 = 79.0;
591 const FALL: f64 = 59.0;
592 const PAST_TOP: u8 = 30;
594
595 if note < FLAT_TO {
596 PLATEAU as u8
597 } else if note <= TOP {
598 (PLATEAU - FALL * (note - FLAT_TO) as f64 / (TOP - FLAT_TO) as f64).round_ties_even() as u8
599 } else {
600 PAST_TOP
601 }
602}
603
604fn rules_record(recording: &Recording, index: usize) -> [u8; RECORD] {
611 let mut out = [0u8; RECORD];
612 let (window, trim) = velocity_window(recording.bank, recording.layer);
613 out[REC_WINDOW..REC_WINDOW + 2].copy_from_slice(&window.to_be_bytes());
614 out[REC_TRIM..REC_TRIM + 2].copy_from_slice(&trim.to_be_bytes());
615 for entry in 0..DECAYS {
616 let at = REC_DECAYS + entry * 4;
617 out[at..at + 4].copy_from_slice(&LADDER_UNITY.to_be_bytes());
618 }
619 out[REC_ID..REC_ID + 4].copy_from_slice(&(index as u32 + 1).to_be_bytes());
620 out
621}
622
623const RELEASE_TRIM: u16 = 12;
625
626const WIDEST_TRIM: u16 = 31;
629
630fn velocity_window(bank: Bank, layer: u8) -> (u16, u16) {
637 match bank {
638 Bank::Release => (0, RELEASE_TRIM),
639 Bank::Attack | Bank::Resonance => (
640 u16::from(layer),
641 u16::from(layer).saturating_add(3).min(WIDEST_TRIM),
642 ),
643 }
644}
645
646pub fn rebuild(library: &Library<'_>) -> Result<Rebuilt, Error> {
654 let block = library.block_bytes();
655 let mut strokes = Vec::new();
656 let mut report = Vec::new();
657 for stroke in library.strokes() {
658 let audio = codec::decode(stroke, library.channels())?;
659 if audio.clipped > 0 {
660 return Err(refuse(format!(
661 "{stroke:?}: {} sample(s) left int16 in the decode; coding a stroke this \
662 codec does not describe would write the saturated frames as new audio",
663 audio.clipped
664 )));
665 }
666 let target = audio.frames();
667 let mut source = audio.lanes;
668 for (channel, tail) in source.iter_mut().zip(&audio.tail) {
669 channel.extend_from_slice(tail);
670 }
671 let seeds = stroke.seeds();
672 let coded = code(&source, &seeds, target)?;
673 report.push(compare(stroke.audio(), &coded.audio, block));
674 strokes.push(Stroke {
675 root: stroke.root,
676 record: record(
677 stroke.record(),
678 &coded,
679 stroke.bank_code(),
680 stroke.layer(),
681 &seeds,
682 stroke.id(),
683 )?,
684 audio: Cow::Owned(coded.audio),
685 });
686 }
687 Ok(Rebuilt {
688 library: Library {
689 header: library.header.clone(),
690 prefix: library.prefix.clone(),
691 channels: library.channels(),
692 strokes,
693 },
694 strokes: report,
695 })
696}
697
698pub struct Resampled {
700 pub channels: Vec<Vec<i16>>,
702 pub clipped: usize,
704}
705
706pub fn resample(samples: &[i16], channels: usize, rate: u32) -> Result<Resampled, Error> {
720 if channels == 0 || rate == 0 || !samples.len().is_multiple_of(channels) {
721 return Err(ParseError::OutOfBounds {
722 value: format!(
723 "{} sample(s) of {channels} channel(s) at {rate} Hz",
724 samples.len()
725 ),
726 bound: "whole frames of at least one channel at a positive rate".into(),
727 }
728 .into());
729 }
730 if rate == codec::RATE {
731 let mut lanes = vec![Vec::new(); channels];
732 for (i, &sample) in samples.iter().enumerate() {
733 lanes[i % channels].push(sample);
734 }
735 return Ok(Resampled {
736 channels: lanes,
737 clipped: 0,
738 });
739 }
740
741 let frames = samples.len() / channels;
742 let stretched = frames as u128 * u128::from(codec::RATE) / u128::from(rate);
743 let fields = usize::try_from(stretched)
744 .ok()
745 .filter(|&fields| u32::try_from(fields).is_ok())
746 .ok_or_else(|| ParseError::OutOfBounds {
747 value: format!("{frames} frame(s) at {rate} Hz, which is {stretched} on the lattice"),
748 bound: "the u32 frame count a stroke record holds".into(),
749 })?;
750 let kernel = kernel::Kernel::new(rate, codec::RATE);
751 let mut clipped = 0;
752 let mut lanes = Vec::with_capacity(channels);
753 for channel in 0..channels {
754 let lane: Vec<i16> = samples
755 .iter()
756 .skip(channel)
757 .step_by(channels)
758 .copied()
759 .collect();
760 let mut out = Vec::new();
761 out.try_reserve_exact(fields)
762 .map_err(|_| ParseError::OutOfBounds {
763 value: format!("{fields} frame(s)"),
764 bound: "an allocation that fits memory".into(),
765 })?;
766 out.extend((0..fields).map(|f| {
767 let value = kernel.field(&lane, f);
768 let narrow = value.clamp(i64::from(i16::MIN), i64::from(i16::MAX)) as i16;
769 clipped += usize::from(i64::from(narrow) != value);
770 narrow
771 }));
772 lanes.push(out);
773 }
774 Ok(Resampled {
775 channels: lanes,
776 clipped,
777 })
778}
779
780fn check_recordings(recordings: &[Recording]) -> Result<u16, Error> {
783 let Some(first) = recordings.first() else {
784 return Err(refuse(
785 "a library with no recordings at all has nothing to play",
786 ));
787 };
788 let channels = first.channels.len();
789 if !(1..=2).contains(&channels) {
790 return Err(ParseError::OutOfBounds {
791 value: format!("{channels} channels"),
792 bound: "1 or 2, which is what a library states".into(),
793 }
794 .into());
795 }
796
797 let mut seen = BTreeSet::new();
798 for recording in recordings {
799 let what = describe(recording);
800 midi_key("root", recording.root)?;
801 if recording.channels.len() != channels {
802 return Err(refuse(format!(
803 "{what} has {} channel(s) where another has {channels}; one library plays \
804 one channel count",
805 recording.channels.len()
806 )));
807 }
808 let frames = recording.channels[0].len();
809 if recording.channels.iter().any(|c| c.len() != frames) {
810 return Err(refuse(format!(
811 "{what} has channels of unequal length; a frame is one sample of each"
812 )));
813 }
814 if frames == 0 {
815 return Err(refuse(format!("{what} has no frames")));
816 }
817 if recording.layer > HIGHEST_PLAYED_LAYER {
818 return Err(refuse(format!(
819 "{what} states a layer value no velocity selects; {HIGHEST_PLAYED_LAYER} is \
820 the largest a key ever sounds"
821 )));
822 }
823 if !seen.insert((recording.root, recording.bank.code(), recording.layer)) {
824 return Err(refuse(format!(
825 "{what} is recorded twice; a root's layers are numbered within one bank"
826 )));
827 }
828 }
829 Ok(channels as u16)
830}
831
832fn describe(recording: &Recording) -> String {
833 format!(
834 "root {} {} layer {}",
835 recording.root, recording.bank, recording.layer
836 )
837}
838
839fn refuse(what: impl Into<String>) -> Error {
840 ParseError::AssertFail(what.into()).into()
841}
842
843fn key_map(roots: &BTreeSet<u8>) -> [u8; NOTES] {
848 let mut map = [UNCOVERED; NOTES];
849 for (key, slot) in map.iter_mut().enumerate() {
850 if let Some(&root) = roots.range((key as u8).saturating_sub(1)..).next() {
851 *slot = root;
852 }
853 }
854 map
855}
856
857fn donor_record<'a>(
864 template: &'a Library<'_>,
865 recording: &Recording,
866) -> Result<&'a [u8; RECORD], Error> {
867 let release = Bank::Release.code();
868 let wanted = recording.bank.code();
869 let same: Vec<&Stroke<'_>> = template
870 .strokes()
871 .iter()
872 .filter(|s| s.bank_code() == wanted)
873 .collect();
874 let pool: Vec<&Stroke<'_>> = match (same.is_empty(), recording.bank) {
875 (false, _) => same,
876 (true, Bank::Release) => template.strokes().iter().collect(),
877 (true, _) => template
878 .strokes()
879 .iter()
880 .filter(|s| s.bank_code() != release)
881 .collect(),
882 };
883 pool.iter()
884 .min_by_key(|s| {
885 (
886 s.root.abs_diff(recording.root),
887 s.layer().abs_diff(recording.layer),
888 )
889 })
890 .map(|s| s.record())
891 .ok_or_else(|| {
892 refuse(format!(
893 "the template records no stroke to take {}'s length marks and decay \
894 coefficients from, and nothing in the audio predicts them",
895 describe(recording)
896 ))
897 })
898}
899
900struct Coded {
902 audio: Vec<u8>,
903 owned: usize,
905 starts: Vec<usize>,
907}
908
909#[derive(Debug, Clone, Copy)]
911struct Placed {
912 at: usize,
913 order: u8,
914 width: u8,
915}
916
917fn code(source: &[Vec<i16>], seeds: &[[i16; SEEDS]; 2], target: usize) -> Result<Coded, Error> {
927 let channels = source.len();
928 let block = block_bytes(channels as u16);
929 let counts = frame_counts(block, channels);
930 let widest = counts[usize::from(MIN_WIDTH)];
931 let total = target
932 .checked_add(widest)
933 .and_then(|total| total.checked_mul(channels).map(|_| total))
934 .ok_or_else(|| refuse("a stroke longer than this platform can address"))?;
935 let planes = planes(source, seeds, total)?;
936
937 let mut placed = lay_capped(&planes, channels, &counts, target);
938 if placed.is_empty() || owned_by(&placed, &counts) != target {
939 placed = lay_uncapped(&planes, channels, &counts, target);
940 }
941
942 let mut audio = Vec::new();
943 for block_at in &placed {
944 let frames = counts[usize::from(block_at.width)];
945 let span = block_at.at * channels..(block_at.at + frames) * channels;
946 let peak = planes[0][span.clone()]
947 .iter()
948 .map(|&v| i64::from(v).abs())
949 .max()
950 .unwrap_or(0);
951 pack(
952 &mut audio,
953 block_at.order,
954 block_at.width,
955 attenuation(peak),
956 &planes[usize::from(block_at.order)][span],
957 block,
958 );
959 }
960 Ok(Coded {
961 audio,
962 owned: owned_by(&placed, &counts),
963 starts: placed.iter().map(|b| b.at).collect(),
964 })
965}
966
967fn owned_by(placed: &[Placed], counts: &[usize; WIDTHS]) -> usize {
969 placed
970 .last()
971 .map_or(0, |b| b.at + counts[usize::from(b.width)] - OVERLAP)
972}
973
974fn lay_capped(
979 planes: &[Vec<i32>],
980 channels: usize,
981 counts: &[usize; WIDTHS],
982 target: usize,
983) -> Vec<Placed> {
984 let shortest = shortest_block(counts);
985 let mut out = Vec::new();
986 let mut at = 0usize;
987 while target - at >= shortest {
988 let block = place(planes, at, channels, counts, Some(target - at));
989 at += counts[usize::from(block.width)] - OVERLAP;
990 out.push(block);
991 }
992 out
993}
994
995fn lay_uncapped(
1003 planes: &[Vec<i32>],
1004 channels: usize,
1005 counts: &[usize; WIDTHS],
1006 target: usize,
1007) -> Vec<Placed> {
1008 let mut out = Vec::new();
1009 let mut at = 0usize;
1010 loop {
1011 let block = place(planes, at, channels, counts, None);
1012 at += counts[usize::from(block.width)] - OVERLAP;
1013 out.push(block);
1014 if at >= target {
1015 return out;
1016 }
1017 }
1018}
1019
1020fn place(
1022 planes: &[Vec<i32>],
1023 at: usize,
1024 channels: usize,
1025 counts: &[usize; WIDTHS],
1026 room: Option<usize>,
1027) -> Placed {
1028 let (order, width) = choose(planes, at, channels, counts, room)
1029 .expect("order zero states a sample outright, which always fits sixteen bits");
1030 Placed { at, order, width }
1031}
1032
1033fn shortest_block(counts: &[usize; WIDTHS]) -> usize {
1036 counts[usize::from(MAX_WIDTH)] - OVERLAP
1037}
1038
1039fn frame_counts(block: usize, channels: usize) -> [usize; WIDTHS] {
1041 let mut out = [0; WIDTHS];
1042 for width in MIN_WIDTH..=MAX_WIDTH {
1043 out[usize::from(width)] = codec::block_frames(width, block, channels);
1044 }
1045 out
1046}
1047
1048fn planes(
1052 source: &[Vec<i16>],
1053 seeds: &[[i16; SEEDS]; 2],
1054 total: usize,
1055) -> Result<Vec<Vec<i32>>, Error> {
1056 let channels = source.len();
1057 let sample = |channel: usize, n: isize| -> i64 {
1058 match usize::try_from(n) {
1059 Ok(n) => i64::from(source[channel].get(n).copied().unwrap_or(0)),
1060 Err(_) => i64::from(seeds[channel][(SEEDS as isize + n) as usize]),
1061 }
1062 };
1063 let mut out = Vec::with_capacity(MAX_ORDER + 1);
1064 for order in 0..=MAX_ORDER {
1065 let mut plane = residuals(total * channels)?;
1066 for n in 0..total {
1067 for channel in 0..channels {
1068 let mut acc = 0i64;
1069 for j in 0..=order {
1070 let term =
1071 predictor::binomial(order, j) * sample(channel, n as isize - j as isize);
1072 acc += if j.is_multiple_of(2) { term } else { -term };
1073 }
1074 plane[n * channels + channel] = acc as i32;
1075 }
1076 }
1077 out.push(plane);
1078 }
1079 Ok(out)
1080}
1081
1082fn residuals(len: usize) -> Result<Vec<i32>, Error> {
1083 let mut out = Vec::new();
1084 out.try_reserve_exact(len)
1085 .map_err(|_| ParseError::OutOfBounds {
1086 value: format!("{len} residual(s)"),
1087 bound: "an allocation that fits memory".into(),
1088 })?;
1089 out.resize(len, 0);
1090 Ok(out)
1091}
1092
1093fn choose(
1101 planes: &[Vec<i32>],
1102 at: usize,
1103 channels: usize,
1104 counts: &[usize; WIDTHS],
1105 owned_left: Option<usize>,
1106) -> Option<(u8, u8)> {
1107 let mut best: Option<(u8, u8)> = None;
1108 for (order, plane) in planes.iter().enumerate() {
1109 let mut lo = 0i32;
1110 let mut hi = 0i32;
1111 let mut scanned = at;
1112 let mut narrowest = None;
1113 for width in (MIN_WIDTH..=MAX_WIDTH).rev() {
1116 let frames = counts[usize::from(width)];
1117 for &value in &plane[scanned * channels..(at + frames) * channels] {
1118 lo = lo.min(value);
1119 hi = hi.max(value);
1120 }
1121 scanned = at + frames;
1122 let bound = 1i32 << (width - 1);
1123 if lo < -bound || hi >= bound {
1124 break;
1125 }
1126 if owned_left.is_none_or(|left| frames - OVERLAP <= left) {
1127 narrowest = Some(width);
1128 }
1129 }
1130 if let Some(width) = narrowest {
1131 if best.is_none_or(|(_, reached)| width < reached) {
1132 best = Some((order as u8, width));
1133 }
1134 }
1135 }
1136 best
1137}
1138
1139fn pack(out: &mut Vec<u8>, order: u8, width: u8, stat: u8, residuals: &[i32], block: usize) {
1142 let start = out.len();
1143 let header = (u16::from(stat) << 8) | (u16::from(order) << 5) | u16::from(width);
1144 out.extend_from_slice(&header.to_be_bytes());
1145 let mask = (1u64 << width) - 1;
1146 let mut reservoir = 0u64;
1147 let mut held = 0u32;
1148 for &value in residuals {
1149 reservoir |= (i64::from(value) as u64 & mask) << held;
1150 held += u32::from(width);
1151 while held >= 16 {
1152 out.extend_from_slice(&((reservoir & 0xffff) as u16).to_be_bytes());
1153 reservoir >>= 16;
1154 held -= 16;
1155 }
1156 }
1157 if held > 0 {
1158 out.extend_from_slice(&((reservoir & 0xffff) as u16).to_be_bytes());
1159 }
1160 out.resize(start + block, 0);
1161}
1162
1163fn attenuation(peak: i64) -> u8 {
1169 if peak == 0 {
1170 return 100;
1171 }
1172 let db = -20.0 * (peak as f64 / FULL_SCALE).log10();
1173 (db + 0.5).floor().clamp(0.0, 100.0) as u8
1174}
1175
1176fn seeds_for(source: &[Vec<i16>]) -> [[i16; SEEDS]; 2] {
1183 let mut out = [[0i16; SEEDS]; 2];
1184 for (channel, group) in source.iter().zip(out.iter_mut()) {
1185 for (i, slot) in group.iter_mut().skip(1).enumerate() {
1186 *slot = channel.get(i).copied().unwrap_or(0);
1187 }
1188 }
1189 out
1190}
1191
1192fn record(
1200 donor: &[u8; RECORD],
1201 coded: &Coded,
1202 bank: u8,
1203 layer: u8,
1204 seeds: &[[i16; SEEDS]; 2],
1205 id: u32,
1206) -> Result<[u8; RECORD], Error> {
1207 let owned = u32::try_from(coded.owned).map_err(|_| ParseError::OutOfBounds {
1208 value: format!("{} frames", coded.owned),
1209 bound: "the u32 frame count a stroke record holds".into(),
1210 })?;
1211 let blocks = u16::try_from(coded.starts.len()).map_err(|_| ParseError::OutOfBounds {
1212 value: format!("{} blocks", coded.starts.len()),
1213 bound: "the u16 block count a stroke record holds".into(),
1214 })?;
1215
1216 let mut out = *donor;
1217 out[REC_START..REC_START + 4].fill(0);
1218 out[REC_BANK] = bank;
1219 out[REC_LAYER] = layer;
1220 out[REC_FRAMES..REC_FRAMES + 4].copy_from_slice(&owned.to_be_bytes());
1221 out[REC_BLOCKS..REC_BLOCKS + 2].copy_from_slice(&blocks.to_be_bytes());
1222 for (channel, group) in seeds.iter().enumerate() {
1223 for (i, seed) in group.iter().enumerate() {
1224 let at = REC_SEEDS + (channel * SEEDS + i) * 2;
1225 out[at..at + 2].copy_from_slice(&seed.to_be_bytes());
1226 }
1227 }
1228
1229 let silent = bank == Bank::Release.code();
1230 let donor_frames = be32(donor, REC_FRAMES);
1231 let mut first = 0u32;
1232 for mark in 0..MARKS {
1233 let at = REC_MARKS + mark * 4;
1234 let scaled = match (silent, donor_frames) {
1235 (true, _) | (_, 0) => 0,
1236 _ => rescale(be32(donor, at), owned, donor_frames),
1237 };
1238 out[at..at + 4].copy_from_slice(&scaled.to_be_bytes());
1239 if mark == 0 {
1240 first = scaled;
1241 }
1242 }
1243 let holding = coded
1244 .starts
1245 .iter()
1246 .rposition(|&start| start as u64 <= u64::from(first))
1247 .unwrap_or(0);
1248 out[REC_MARK_BLOCK..REC_MARK_BLOCK + 2].copy_from_slice(&(holding as u16).to_be_bytes());
1249 if silent {
1250 out[REC_DECAY..REC_DECAY + 4].fill(0);
1251 }
1252 out[REC_ID..REC_ID + 4].copy_from_slice(&id.to_be_bytes());
1253 Ok(out)
1254}
1255
1256fn rescale(mark: u32, owned: u32, donor_frames: u32) -> u32 {
1258 let moved = u64::from(mark) * u64::from(owned) / u64::from(donor_frames);
1259 moved.min(u64::from(owned.saturating_sub(1))) as u32
1260}
1261
1262fn compare(before: &[u8], coded: &[u8], block: usize) -> Recoded {
1264 let mut out = Recoded {
1265 blocks: coded.len() / block,
1266 ..Recoded::default()
1267 };
1268 for (index, now) in coded.chunks_exact(block).enumerate() {
1269 let Some(was) = before.get(index * block..(index + 1) * block) else {
1270 continue;
1271 };
1272 if was == now {
1273 out.identical += 1;
1274 } else if was[1..] == now[1..] {
1275 out.restated += 1;
1276 }
1277 }
1278 out
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283 use super::super::synthetic::{take, Build};
1284 use super::*;
1285 use crate::formats::npno::{be16, Piano, DECAYS};
1286
1287 fn template(channels: u16) -> Piano {
1291 let mut build = Build::new();
1292 build.channels = channels;
1293 build.map = vec![(60, 60)];
1294 build.takes = vec![take(60, Bank::Attack, 0, 1)
1295 .marks(std::array::from_fn(|mark| (mark as u32 + 6) * 100))
1296 .decay(0x0000_2000)
1297 .ladder(std::array::from_fn(|entry| 0x0000_1000 + entry as u32))
1298 .id(77)
1299 .silent()];
1300 build.piano()
1301 }
1302
1303 fn tone(frames: usize, hertz: f64, channels: usize) -> Vec<Vec<i16>> {
1305 (0..channels)
1306 .map(|c| {
1307 (0..frames)
1308 .map(|n| {
1309 let t = n as f64 / f64::from(codec::RATE);
1310 let envelope = (-3.0 * t).exp() * (1.0 - (-400.0 * t).exp());
1311 let phase = std::f64::consts::TAU * hertz * (c as f64 * 0.01 + 1.0) * t;
1312 (9000.0 * envelope * phase.sin()) as i16
1313 })
1314 .collect()
1315 })
1316 .collect()
1317 }
1318
1319 fn one(root: u8, bank: Bank, layer: u8, channels: Vec<Vec<i16>>) -> Recording {
1320 Recording {
1321 root,
1322 bank,
1323 layer,
1324 channels,
1325 }
1326 }
1327
1328 fn round_trip(channels: u16, recordings: &[Recording]) -> Piano {
1331 let donor = template(channels);
1332 let built = build(
1333 &Donor::Template(&donor.library().unwrap()),
1334 &Options::new("Synth").variant("Test"),
1335 recordings,
1336 )
1337 .unwrap();
1338 let bytes = {
1339 let piano = built.to_piano().unwrap();
1340 let mut out = std::io::Cursor::new(Vec::new());
1341 piano.write_to(&mut out).unwrap();
1342 out.into_inner()
1343 };
1344 Piano::read_from(&mut std::io::Cursor::new(bytes)).unwrap()
1345 }
1346
1347 #[test]
1351 fn a_built_library_decodes_back_to_the_frames_it_was_given() {
1352 let source = tone(20_000, 220.0, 2);
1353 let piano = round_trip(2, &[one(60, Bank::Attack, 0, source.clone())]);
1354 let library = piano.library().unwrap();
1355 assert_eq!(library.name(), ("Synth".into(), "Test".into()));
1356 assert_eq!(library.channels(), 2);
1357
1358 let stroke = &library.strokes()[0];
1359 let audio = codec::decode(stroke, 2).unwrap();
1360 assert_eq!(audio.clipped, 0);
1361 let longest = codec::block_frames(MIN_WIDTH, library.block_bytes(), 2) - OVERLAP;
1362 let padding = audio
1363 .frames()
1364 .checked_sub(source[0].len())
1365 .unwrap_or_else(|| {
1366 panic!(
1367 "the stroke states {} frames of a {} frame recording",
1368 audio.frames(),
1369 source[0].len()
1370 )
1371 });
1372 assert!(
1373 padding < longest,
1374 "the stroke states {padding} frames of silence, a whole block or more"
1375 );
1376 for (channel, given) in audio.lanes.iter().zip(&source) {
1377 assert_eq!(&channel[..given.len()], &given[..]);
1378 assert!(channel[given.len()..].iter().all(|&s| s == 0));
1379 }
1380 }
1381
1382 #[test]
1388 fn a_recording_that_does_not_fill_its_last_block_keeps_every_frame() {
1389 let mut late = vec![vec![0i16; 892]; 2];
1390 for (channel, lane) in late.iter_mut().enumerate() {
1391 let signal = tone(64, 262.0, 2);
1392 lane[892 - 64..].copy_from_slice(&signal[channel]);
1393 }
1394 let cases: [(&str, Vec<Vec<i16>>); 3] = [
1395 ("a recording a block and a half long", tone(700, 262.0, 2)),
1396 (
1397 "a recording cut between block lengths",
1398 tone(9_133, 440.0, 2),
1399 ),
1400 ("a recording whose signal is all at the end", late),
1401 ];
1402
1403 for (what, source) in cases {
1404 let frames = source[0].len();
1405 let signal: i64 = source[0].iter().map(|&s| i64::from(s).abs()).sum();
1406 assert!(signal > 0, "{what}: the case states no signal");
1407 let piano = round_trip(2, &[one(60, Bank::Attack, 0, source.clone())]);
1408 let library = piano.library().unwrap();
1409 let audio = codec::decode(&library.strokes()[0], 2).unwrap();
1410 assert!(
1411 audio.frames() >= frames,
1412 "{what}: the stroke states {} of {frames} frames",
1413 audio.frames()
1414 );
1415 for (channel, given) in audio.lanes.iter().zip(&source) {
1416 assert_eq!(
1417 &channel[..frames],
1418 &given[..],
1419 "{what}: frames came back changed"
1420 );
1421 assert!(
1422 channel[frames..].iter().all(|&s| s == 0),
1423 "{what}: the stroke states something other than silence past the recording"
1424 );
1425 }
1426
1427 let again = rebuild(&library).unwrap();
1428 for recoded in &again.strokes {
1429 assert_eq!(
1430 (recoded.identical, recoded.recoded()),
1431 (recoded.blocks, 0),
1432 "{what}: the rebuild laid the stroke out differently"
1433 );
1434 }
1435 assert_eq!(
1436 again.library.to_body().unwrap(),
1437 piano.file.body.0,
1438 "{what}: the rebuild is a different file"
1439 );
1440 }
1441 }
1442
1443 #[test]
1447 fn a_recording_of_any_length_codes_to_a_stroke_that_holds_it() {
1448 for frames in [
1449 1, 63, 64, 65, 445, 446, 447, 509, 891, 892, 893, 1_102, 2_658,
1450 ] {
1451 for channels in [1u16, 2] {
1452 let source = tone(frames, 262.0, usize::from(channels));
1453 let piano = round_trip(channels, &[one(60, Bank::Attack, 0, source.clone())]);
1454 let library = piano.library().unwrap();
1455 let audio = codec::decode(&library.strokes()[0], channels).unwrap();
1456 let what = format!("{frames} frame(s) over {channels} channel(s)");
1457 assert!(
1458 audio.frames() >= frames,
1459 "{what}: the stroke states {}",
1460 audio.frames()
1461 );
1462 for (channel, given) in audio.lanes.iter().zip(&source) {
1463 assert_eq!(&channel[..frames], &given[..], "{what}: frames changed");
1464 assert!(
1465 channel[frames..].iter().all(|&s| s == 0),
1466 "{what}: not silent"
1467 );
1468 }
1469 assert_eq!(
1470 rebuild(&library).unwrap().library.to_body().unwrap(),
1471 piano.file.body.0,
1472 "{what}: the rebuild is a different file"
1473 );
1474 }
1475 }
1476 }
1477
1478 #[test]
1482 fn a_stroke_whose_decode_saturates_is_not_coded_again() {
1483 let donor = template(1);
1484 let mut library = donor.library().unwrap();
1485 let block = library.block_bytes();
1486 let frames = codec::block_frames(MAX_WIDTH, block, 1);
1487 let mut audio = Vec::new();
1490 pack(&mut audio, 1, MAX_WIDTH, 0, &vec![20_000i32; frames], block);
1491 library.strokes[0].audio = Cow::Owned(audio);
1492
1493 let decoded = codec::decode(&library.strokes()[0], 1).unwrap();
1494 assert!(decoded.clipped > 0, "the case does not saturate");
1495
1496 let error = match rebuild(&library) {
1497 Err(error) => error.to_string(),
1498 Ok(_) => panic!("expected a refusal"),
1499 };
1500 assert!(error.contains("left int16"), "{error}");
1501 assert!(
1502 error.contains("coding a stroke this codec does not describe"),
1503 "the refusal does not read as the sentence it states: {error}"
1504 );
1505 }
1506
1507 #[test]
1508 fn a_mono_library_codes_and_decodes_on_its_own_block_size() {
1509 let source = tone(9_000, 440.0, 1);
1510 let piano = round_trip(1, &[one(48, Bank::Attack, 0, source.clone())]);
1511 let library = piano.library().unwrap();
1512 assert_eq!(library.channels(), 1);
1513 let audio = codec::decode(&library.strokes()[0], 1).unwrap();
1514 assert_eq!(audio.lanes[0][..source[0].len()], source[0][..]);
1515 assert!(audio.lanes[0][source[0].len()..].iter().all(|&s| s == 0));
1516 }
1517
1518 #[test]
1519 fn the_directory_orders_strokes_by_root_then_bank_then_layer() {
1520 let short = tone(6_000, 300.0, 1);
1521 let piano = round_trip(
1522 1,
1523 &[
1524 one(72, Bank::Attack, 0, short.clone()),
1525 one(60, Bank::Release, 0, short.clone()),
1526 one(60, Bank::Attack, 4, short.clone()),
1527 one(60, Bank::Attack, 0, short.clone()),
1528 ],
1529 );
1530 let library = piano.library().unwrap();
1531 let seen: Vec<(u8, Option<Bank>, u8)> = library
1532 .strokes()
1533 .iter()
1534 .map(|s| (s.root, s.bank(), s.layer()))
1535 .collect();
1536 assert_eq!(
1537 seen,
1538 [
1539 (60, Some(Bank::Attack), 0),
1540 (60, Some(Bank::Attack), 4),
1541 (60, Some(Bank::Release), 0),
1542 (72, Some(Bank::Attack), 0),
1543 ]
1544 );
1545 }
1546
1547 #[test]
1550 fn the_default_layer_values_spread_a_root_over_the_selection_range() {
1551 let spread = |layers| {
1552 (0..layers)
1553 .map(|i| layer_value(i, layers))
1554 .collect::<Vec<_>>()
1555 };
1556 assert_eq!(spread(1), vec![0], "a lone layer plays at every velocity");
1557 assert_eq!(spread(2), vec![0, 27]);
1558 assert_eq!(spread(3), vec![0, 14, 27]);
1559 assert_eq!(spread(9), vec![0, 3, 7, 10, 14, 17, 20, 24, 27]);
1560 assert_eq!(layer_value(9, 9), 27, "an index past the last is the last");
1561 assert_eq!(layer_value(0, 0), 0);
1562 }
1563
1564 #[test]
1567 fn a_rebuild_keeps_the_layer_value_every_stroke_states() {
1568 let short = tone(6_000, 300.0, 1);
1569 let piano = round_trip(
1570 1,
1571 &[
1572 one(60, Bank::Attack, 0, short.clone()),
1573 one(60, Bank::Attack, 6, short.clone()),
1574 one(60, Bank::Attack, 12, short),
1575 ],
1576 );
1577 let library = piano.library().unwrap();
1578 let again = rebuild(&library).unwrap();
1579 let values: Vec<u8> = again.library.strokes().iter().map(|s| s.layer()).collect();
1580 assert_eq!(values, [0, 6, 12]);
1581 }
1582
1583 #[test]
1587 fn a_release_stroke_declares_no_marks_and_zeroes_one_decay_coefficient() {
1588 let short = tone(6_000, 300.0, 1);
1589 let piano = round_trip(
1590 1,
1591 &[
1592 one(60, Bank::Attack, 0, short.clone()),
1593 one(60, Bank::Release, 0, short.clone()),
1594 ],
1595 );
1596 let library = piano.library().unwrap();
1597 let donated: [u32; DECAYS] = std::array::from_fn(|c| 0x0000_1000u32 + c as u32);
1598 for stroke in library.strokes() {
1599 let record = stroke.record();
1600 let marks: Vec<u32> = (0..MARKS)
1601 .map(|m| be32(record, REC_MARKS + m * 4))
1602 .collect();
1603 assert_eq!(
1604 stroke.ladder(),
1605 donated,
1606 "a stroke of any bank inherits the donor's ladder unchanged"
1607 );
1608 if stroke.bank() == Some(Bank::Release) {
1609 assert_eq!(marks, [0; MARKS], "a release stroke declares no marks");
1610 assert_eq!(
1611 stroke.decay(),
1612 0,
1613 "a release stroke zeroes the coefficient at +0x2e"
1614 );
1615 } else {
1616 assert!(marks.iter().all(|&m| m > 0 && m < stroke.frames()));
1617 assert_ne!(
1618 stroke.decay(),
1619 0,
1620 "a stroke of another bank inherits the donor's coefficient at +0x2e"
1621 );
1622 }
1623 }
1624 }
1625
1626 #[test]
1629 fn every_kind_reads_back_from_the_code_it_writes() {
1630 for kind in Kind::ALL {
1631 assert_eq!(Kind::from_code(kind.code()), Some(kind));
1632 }
1633 let named: Vec<Kind> = (0..=u8::MAX).filter_map(Kind::from_code).collect();
1634 assert_eq!(named, Kind::ALL, "a code names a kind ALL does not list");
1635 }
1636
1637 #[test]
1643 fn a_library_built_from_rules_states_them_and_needs_no_template() {
1644 let short = tone(6_000, 300.0, 1);
1645 let rules = Rules {
1646 kind: Kind::Wurlitzer,
1647 gain: -20,
1648 damper_top: 97,
1649 };
1650 let built = build(
1651 &Donor::Rules(rules),
1652 &Options::new("Reeds"),
1653 &[
1654 one(60, Bank::Attack, 0, short.clone()),
1655 one(60, Bank::Attack, 17, short.clone()),
1656 one(60, Bank::Release, 0, short.clone()),
1657 ],
1658 )
1659 .unwrap();
1660
1661 assert_eq!(Kind::from_code(built.kind_code()), Some(Kind::Wurlitzer));
1662 assert_eq!(built.gain(), -20);
1663 assert_eq!(built.damper_top(), 97);
1664 assert_eq!(built.stream_version(), RULES_VERSION);
1665 assert_eq!(
1666 Rules::new(Kind::Wurlitzer).damper_top,
1667 rules.damper_top,
1668 "the kind names its own damper limit"
1669 );
1670
1671 let stated: Vec<(u8, u16, u16, u32)> = built
1672 .strokes()
1673 .iter()
1674 .map(|s| {
1675 let record = s.record();
1676 (
1677 s.layer(),
1678 be16(record, REC_WINDOW),
1679 s.trim(),
1680 be32(record, REC_ID),
1681 )
1682 })
1683 .collect();
1684 assert_eq!(stated, [(0, 0, 3, 1), (17, 17, 20, 2), (0, 0, 12, 3)]);
1685 for stroke in built.strokes() {
1686 let record = stroke.record();
1687 assert!((0..MARKS).all(|m| be32(record, REC_MARKS + m * 4) == 0));
1688 assert_eq!(
1689 (stroke.decay(), stroke.ladder()),
1690 (0, [LADDER_UNITY; DECAYS]),
1691 "a rule-written stroke of any bank applies no decay over the recording"
1692 );
1693 }
1694
1695 let again = rebuild(&built).unwrap();
1696 assert_eq!(
1697 again.library.to_body().unwrap(),
1698 built.to_body().unwrap(),
1699 "a rule-written library is not a fixed point of a recode"
1700 );
1701 }
1702
1703 #[test]
1706 fn the_damper_cut_is_a_plateau_then_a_straight_fall_to_the_top_key() {
1707 let curve: Vec<u8> = (0..NOTES).map(damper_cut).collect();
1708 assert_eq!(curve[..25], [79; 25]);
1709 assert_eq!((curve[66], curve[108], curve[109]), (50, 20, 30));
1710 assert!(
1711 curve[24..=108].windows(2).all(|w| w[0] >= w[1]),
1712 "the fall never rises"
1713 );
1714 assert!(curve[109..].iter().all(|&v| v == 30));
1715 }
1716
1717 #[test]
1720 fn the_highest_played_layer_is_the_rule_at_the_softest_velocity() {
1721 let selected = |velocity: u32| ((127 - velocity) * 31 / 127) as u8;
1722 assert_eq!(HIGHEST_PLAYED_LAYER, selected(1));
1723 assert!((1..=127).all(|v| selected(v) <= HIGHEST_PLAYED_LAYER));
1724 const { assert!(SOFTEST_LAYER <= HIGHEST_PLAYED_LAYER) };
1725
1726 let donor = template(1);
1727 let library = donor.library().unwrap();
1728 let short = tone(6_000, 300.0, 1);
1729 build(
1730 &Donor::Template(&library),
1731 &Options::new("Synth"),
1732 &[one(60, Bank::Attack, HIGHEST_PLAYED_LAYER, short)],
1733 )
1734 .expect("the bound itself is a value a key sounds");
1735 }
1736
1737 #[test]
1738 fn a_wav_name_states_its_root_bank_and_layer() {
1739 use LayerTag::{Index, Value};
1740 assert_eq!(
1741 parse_stroke_name("060-b0-l00", Stem::None),
1742 Some((60, Bank::Attack, Index(0)))
1743 );
1744 assert_eq!(
1745 parse_stroke_name("36-b2-l7", Stem::None),
1746 Some((36, Bank::Release, Index(7)))
1747 );
1748 assert_eq!(
1749 parse_stroke_name("101-b1-v12", Stem::None),
1750 Some((101, Bank::Resonance, Value(12)))
1751 );
1752 assert_eq!(
1753 parse_stroke_name("127-b0-l00", Stem::None),
1754 Some((127, Bank::Attack, Index(0)))
1755 );
1756 for bad in [
1757 "060-b3-l00",
1758 "300-b0-l00",
1759 "128-b0-l00",
1760 "060-0-l00",
1761 "060-b0-x2",
1762 "060-b0",
1763 "060-b0-l00-take2",
1764 "C4-b0-l00",
1765 ] {
1766 assert_eq!(parse_stroke_name(bad, Stem::None), None, "{bad}");
1767 }
1768 }
1769
1770 #[test]
1774 fn a_stem_before_the_stroke_is_taken_only_where_the_caller_takes_one() {
1775 assert_eq!(
1776 parse_stroke_name("Grand-060-b0-l00", Stem::Any),
1777 Some((60, Bank::Attack, LayerTag::Index(0)))
1778 );
1779 assert_eq!(parse_stroke_name("Grand-060-b0-l00", Stem::None), None);
1780 assert_eq!(
1781 parse_stroke_name("060-b0-l00", Stem::Any),
1782 Some((60, Bank::Attack, LayerTag::Index(0))),
1783 "a name with no stem states the same stroke either way"
1784 );
1785 assert_eq!(parse_stroke_name("Grand-060-b0-take2", Stem::Any), None);
1786 }
1787
1788 #[test]
1791 fn indexed_layers_spread_over_their_own_root_and_bank() {
1792 let named = [
1793 (60, Bank::Attack, LayerTag::Index(2)),
1794 (60, Bank::Attack, LayerTag::Index(0)),
1795 (60, Bank::Attack, LayerTag::Index(1)),
1796 (60, Bank::Release, LayerTag::Index(0)),
1797 (72, Bank::Attack, LayerTag::Index(0)),
1798 (72, Bank::Attack, LayerTag::Index(1)),
1799 ];
1800 assert_eq!(
1801 layer_values(&named).unwrap(),
1802 [27, 0, 14, 0, 0, 27],
1803 "the order given is kept; the rank is the layer's own"
1804 );
1805 }
1806
1807 #[test]
1808 fn a_named_layer_value_is_written_as_it_stands() {
1809 let named = [
1810 (60, Bank::Attack, LayerTag::Value(0)),
1811 (60, Bank::Attack, LayerTag::Value(6)),
1812 (60, Bank::Attack, LayerTag::Value(12)),
1813 ];
1814 assert_eq!(layer_values(&named).unwrap(), [0, 6, 12]);
1815 }
1816
1817 #[test]
1821 fn one_roots_bank_names_its_layers_one_way_and_each_of_them_once() {
1822 let mixed = [
1823 (60, Bank::Attack, LayerTag::Index(0)),
1824 (60, Bank::Attack, LayerTag::Value(12)),
1825 ];
1826 assert_eq!(
1827 layer_values(&mixed),
1828 Err(LayerClash {
1829 root: 60,
1830 bank: Bank::Attack,
1831 how: Clash::BothForms,
1832 })
1833 );
1834 let twice = [
1835 (60, Bank::Attack, LayerTag::Index(0)),
1836 (60, Bank::Attack, LayerTag::Index(0)),
1837 ];
1838 assert_eq!(
1839 layer_values(&twice),
1840 Err(LayerClash {
1841 root: 60,
1842 bank: Bank::Attack,
1843 how: Clash::Twice,
1844 })
1845 );
1846 let apart = [
1847 (60, Bank::Attack, LayerTag::Index(0)),
1848 (60, Bank::Release, LayerTag::Value(12)),
1849 ];
1850 assert_eq!(
1851 layer_values(&apart).unwrap(),
1852 [0, 12],
1853 "a bank of its own names its layers its own way"
1854 );
1855 }
1856
1857 #[test]
1858 fn every_key_up_to_the_highest_roots_own_plays_the_root_above_it() {
1859 let roots: BTreeSet<u8> = [25, 30, 60].into_iter().collect();
1860 let map = key_map(&roots);
1861 assert_eq!(map[0], 25, "the lowest root takes everything under it");
1862 assert_eq!(map[26], 25, "a key one semitone above its root");
1863 assert_eq!(map[27], 30, "and the next one belongs to the root above");
1864 assert_eq!(map[31], 30, "a root reaches one semitone above itself");
1865 assert_eq!(map[32], 60, "and the key after that is the next root's");
1866 assert_eq!(map[61], 60, "the highest root reaches one semitone up");
1867 assert_eq!(map[62], UNCOVERED);
1868 assert_eq!(map[NOTES - 1], UNCOVERED);
1869 }
1870
1871 #[test]
1872 fn the_attenuation_states_decibels_below_full_scale() {
1873 assert_eq!(attenuation(8192), 0);
1874 assert_eq!(
1875 attenuation(9000),
1876 0,
1877 "louder than full scale clamps at zero"
1878 );
1879 assert_eq!(attenuation(819), 20);
1880 assert_eq!(attenuation(82), 40);
1881 assert_eq!(attenuation(1), 78);
1882 assert_eq!(attenuation(0), 100, "silence is not 78 dB down");
1883 }
1884
1885 #[test]
1886 fn a_recording_a_library_cannot_state_is_refused_by_name() {
1887 let donor = template(1);
1888 let library = donor.library().unwrap();
1889 let options = Options::new("Synth");
1890 let short = tone(6_000, 300.0, 1);
1891 let error = |recordings: &[Recording]| {
1892 build(&Donor::Template(&library), &options, recordings)
1893 .expect_err("expected a refusal")
1894 .to_string()
1895 };
1896
1897 assert!(error(&[]).contains("no recordings"));
1898 assert!(error(&[one(60, Bank::Attack, 0, vec![])]).contains("1 or 2"));
1899 assert!(error(&[one(60, Bank::Attack, 0, vec![vec![]])]).contains("no frames"));
1900 assert!(
1901 error(&[one(60, Bank::Attack, 0, vec![short[0].clone(), vec![0; 3]])])
1902 .contains("unequal length")
1903 );
1904 assert!(error(&[
1905 one(60, Bank::Attack, 0, short.clone()),
1906 one(60, Bank::Attack, 0, short.clone()),
1907 ])
1908 .contains("recorded twice"));
1909 let unplayable = error(&[one(
1910 60,
1911 Bank::Attack,
1912 HIGHEST_PLAYED_LAYER + 1,
1913 short.clone(),
1914 )]);
1915 assert!(unplayable.contains("no velocity selects"), "{unplayable}");
1916 assert!(
1917 unplayable.contains("30 is the largest a key ever sounds"),
1918 "{unplayable}"
1919 );
1920 assert!(error(&[
1921 one(60, Bank::Attack, 0, short.clone()),
1922 one(
1923 60,
1924 Bank::Attack,
1925 1,
1926 vec![short[0].clone(), short[0].clone()]
1927 ),
1928 ])
1929 .contains("one channel count"));
1930 }
1931
1932 #[test]
1935 fn a_template_donates_the_same_library_with_its_audio_dropped() {
1936 let donor = template(1);
1937 let library = donor.library().unwrap();
1938 let skeleton = library.without_audio();
1939 assert!(skeleton.strokes().iter().all(|s| s.audio().is_empty()));
1940
1941 let options = Options::new("Synth").variant("Test");
1942 let recordings = [
1943 one(60, Bank::Attack, 0, tone(6_000, 300.0, 1)),
1944 one(72, Bank::Release, 4, tone(3_000, 500.0, 1)),
1945 ];
1946 let whole = build(&Donor::Template(&library), &options, &recordings).unwrap();
1947 let stripped = build(&Donor::Template(&skeleton), &options, &recordings).unwrap();
1948 assert_eq!(stripped.to_body().unwrap(), whole.to_body().unwrap());
1949 }
1950
1951 #[test]
1954 fn a_name_that_fits_beside_the_variant_it_is_given_is_built() {
1955 let name = "Studio Nine";
1956 let donated = "Concert Grand Sml XL";
1957 let donor = template(1);
1958 let mut library = donor.library().unwrap();
1959 library.set_variant(donated).unwrap();
1960 assert!(
1961 library.clone().set_name(name).is_err(),
1962 "the name fits beside the template's variant, so the case states nothing"
1963 );
1964
1965 let built = build(
1966 &Donor::Template(&library),
1967 &Options::new(name),
1968 &[one(60, Bank::Attack, 0, tone(6_000, 300.0, 1))],
1969 )
1970 .expect("a name and an empty variant that fit the field they share");
1971 assert_eq!(built.name(), (name.to_string(), String::new()));
1972 }
1973
1974 #[test]
1975 fn a_template_with_only_release_strokes_cannot_donate_to_an_attack_stroke() {
1976 let donor = template(1);
1977 let mut library = donor.library().unwrap();
1978 library.strokes[0].record[REC_BANK] = Bank::Release.code();
1979 let short = tone(6_000, 300.0, 1);
1980 let error = build(
1981 &Donor::Template(&library),
1982 &Options::new("Synth"),
1983 &[one(60, Bank::Attack, 0, short)],
1984 )
1985 .expect_err("expected a refusal")
1986 .to_string();
1987 assert!(
1988 error.contains("length marks and decay coefficients"),
1989 "{error}"
1990 );
1991 }
1992
1993 fn trailing_silence(frames: usize, silence: usize, channels: usize) -> Vec<Vec<i16>> {
1996 let mut source = tone(frames, 262.0, channels);
1997 for channel in &mut source {
1998 channel.resize(frames + silence, 0);
1999 }
2000 source
2001 }
2002
2003 #[test]
2012 fn rebuilding_a_library_this_module_wrote_reproduces_every_block() {
2013 let cases: [(&str, u16, Vec<Recording>); 4] = [
2014 (
2015 "a source shorter than one block",
2016 2,
2017 vec![one(60, Bank::Attack, 0, tone(509, 262.0, 2))],
2018 ),
2019 (
2020 "two stereo strokes cut mid-decay",
2021 2,
2022 vec![
2023 one(60, Bank::Attack, 0, tone(12_000, 262.0, 2)),
2024 one(72, Bank::Attack, 0, tone(9_000, 523.0, 2)),
2025 ],
2026 ),
2027 (
2028 "a mono stroke cut mid-decay",
2029 1,
2030 vec![one(48, Bank::Attack, 0, tone(9_133, 440.0, 1))],
2031 ),
2032 (
2033 "a stroke that reaches silence before its source ends",
2034 2,
2035 vec![one(60, Bank::Attack, 0, trailing_silence(6_000, 5_000, 2))],
2036 ),
2037 ];
2038
2039 for (what, channels, recordings) in cases {
2040 let piano = round_trip(channels, &recordings);
2041 let library = piano.library().unwrap();
2042 let again = rebuild(&library).unwrap();
2043 assert_eq!(again.strokes.len(), recordings.len());
2044 for (index, recoded) in again.strokes.iter().enumerate() {
2045 assert_eq!(
2046 (recoded.identical, recoded.recoded()),
2047 (recoded.blocks, 0),
2048 "{what}: stroke {index} came back with different blocks"
2049 );
2050 }
2051 assert_eq!(
2052 again.library.to_body().unwrap(),
2053 piano.file.body.0,
2054 "{what}: the library came back a different file"
2055 );
2056 }
2057 }
2058
2059 #[test]
2060 fn the_resampler_leaves_audio_already_on_the_lattice_alone() {
2061 let interleaved: Vec<i16> = (0..64).map(|n| (n * 100 - 3000) as i16).collect();
2062 let out = resample(&interleaved, 2, codec::RATE).unwrap();
2063 assert_eq!(out.clipped, 0);
2064 assert_eq!(out.channels[0][..3], [-3000, -2800, -2600]);
2065 assert_eq!(out.channels[1][..3], [-2900, -2700, -2500]);
2066
2067 assert!(resample(&[1, 2, 3], 2, codec::RATE).is_err());
2068 assert!(resample(&[1, 2], 1, 0).is_err());
2069 }
2070
2071 #[test]
2075 fn a_rate_that_stretches_a_source_past_a_strokes_frame_count_is_refused() {
2076 let frames = u32::MAX as usize / codec::RATE as usize + 1;
2077 let error = match resample(&vec![0i16; frames], 1, 1) {
2078 Err(error) => error.to_string(),
2079 Ok(_) => panic!("expected a refusal"),
2080 };
2081 assert!(error.contains("at 1 Hz"), "{error}");
2082 assert!(error.contains("u32 frame count"), "{error}");
2083 }
2084
2085 #[test]
2090 fn resampling_a_faster_source_drops_what_the_lattice_cannot_hold() {
2091 let rate = 96_000;
2092 let tone_at = |hertz: f64| -> Vec<i16> {
2093 (0..rate as usize / 4)
2094 .map(|n| {
2095 let t = n as f64 / f64::from(rate);
2096 (8000.0 * (std::f64::consts::TAU * hertz * t).sin()) as i16
2097 })
2098 .collect()
2099 };
2100 let peak = |lane: &[i16]| {
2102 lane[400..lane.len() - 400]
2103 .iter()
2104 .map(|&s| i32::from(s).abs())
2105 .max()
2106 .unwrap_or(0)
2107 };
2108
2109 let above = resample(&tone_at(24_000.0), 1, rate).unwrap();
2110 assert_eq!(above.clipped, 0);
2111 let level = peak(&above.channels[0]);
2112 assert!(level < 400, "a 24 kHz tone came through at {level} of 8000");
2113
2114 let inside = resample(&tone_at(1_000.0), 1, rate).unwrap();
2115 let level = peak(&inside.channels[0]);
2116 assert!(
2117 level > 7_900,
2118 "a 1 kHz tone came through at {level} of 8000"
2119 );
2120 }
2121
2122 #[test]
2123 fn resampling_a_slower_source_stretches_it_onto_the_lattice() {
2124 let rate = 22_050;
2125 let frames = 4_000;
2126 let source: Vec<i16> = (0..frames)
2127 .map(|n| {
2128 let t = n as f64 / f64::from(rate);
2129 (8000.0 * (std::f64::consts::TAU * 100.0 * t).sin()) as i16
2130 })
2131 .collect();
2132 let out = resample(&source, 1, rate).unwrap();
2133 assert_eq!(
2134 out.channels[0].len(),
2135 frames * codec::RATE as usize / rate as usize
2136 );
2137 let crossings = |signal: &[i16]| {
2140 signal
2141 .windows(2)
2142 .filter(|w| (w[0] < 0) != (w[1] < 0))
2143 .count()
2144 };
2145 assert_eq!(
2146 crossings(&out.channels[0][100..]),
2147 crossings(&source[100..])
2148 );
2149 }
2150}