Skip to main content

nord_format/formats/npno/
mod.rs

1//! Piano libraries (`.npno`).
2//!
3//! The body is a `CNSP` stream: a metadata prefix carrying the name, a 128-entry
4//! key map and ten per-note tables; then a directory of **strokes** — one
5//! recorded note each — and the encoded audio those strokes own. [`Piano`] is the
6//! file, body verbatim and checksum verified; [`Library`] is the container
7//! parsed, a view whose writer re-lays the directory and the audio from the model
8//! it holds. [`codec`] turns one stroke's audio back into samples.
9//!
10//! Offsets below are relative to the body's first byte, and the stream's own
11//! integers are big-endian where the CBIN header's are little-endian.
12//!
13//! | body offset | field |
14//! |---|---|
15//! | `0x00` | `"CNSP"` |
16//! | `0x04` | u16 stream version — `0x450` or `0x464` |
17//! | `0x06` | u32, unique per file; meaning open |
18//! | `0x1c` | `Name#Variant`, NUL-padded to 32 bytes |
19//! | `0x3c` | the bare name, and at `0x5c` the variant — `0x464` streams only |
20//! | `0x8c` | 128-entry key map: the root note that plays each key, `0xFF` uncovered |
21//! | `0x18c` | 128-entry per-key fine tune, one of ten per-note tables from `0x10c` |
22//! | `0x61c` | u16 stream version, echoed |
23//! | `0x61e` | u16 channel count, 1 or 2 |
24//! | `0x620` | u16 stroke count `N` |
25//! | `0x622` | 128 × u16 strokes per root note, summing to `N` |
26//! | `0x732` | `N` × 118-byte stroke records, grouped in ascending root order |
27//!
28//! The prefix's individual field placements: Inferred from specimens; not
29//! confirmed on hardware. The container layout as [`Library::to_body`] writes it — a
30//! library whose directory and audio this crate re-laid, and one whose audio
31//! [`encode`] coded outright, load on the instrument and play at the original's
32//! level — and, within it, the key map's value being the recording's root note, a
33//! stroke's [`Bank`] being what it is played for, and [`Stroke::layer`] stating the
34//! softness the velocity threshold reads: Confirmed on hardware.
35//!
36//! Audio follows the directory, one span per record in the directory's own order.
37//! The first span starts at the next `1022 × channels` boundary offset by
38//! [`AUDIO_ALIGN_BIAS`] (the bias is unexplained), the gap in front of it is zero,
39//! each span abuts the one before, and the last ends at the body's end. Because a
40//! stroke carries its own predictor seeds and its blocks overlap only each other, a
41//! span is self-contained and moves verbatim — which is what makes the transforms
42//! on [`Library`] no more than a re-lay.
43//!
44//! ⚠️ Real libraries are tens of megabytes and reading one allocates the body —
45//! [`crate::cbin::inspect`] answers container questions in O(1) instead.
46//!
47//! ⚠️ The header's `location` and `aux` are unchecked here on purpose: this is a
48//! library format, where those words hold something other than a bank/slot pair, and
49//! no local specimen says what. Gating on them would refuse real files.
50
51pub mod codec;
52pub mod encode;
53/// A library laid out from a description. Test-only: behind the `synthetic` feature,
54/// and always available to this crate's own tests.
55#[cfg(any(test, feature = "synthetic"))]
56pub mod synthetic;
57
58use crate::cbin::{self, Cbin, Header, RawBody};
59use crate::error::{try_vec, Error, ParseError};
60use std::borrow::Cow;
61use std::collections::{BTreeMap, BTreeSet};
62use std::fmt;
63use std::io::{Read, Seek, Write};
64use std::ops::RangeInclusive;
65
66pub const FORMAT: &str = "npno";
67
68/// The body's stream magic.
69pub const CNSP_MAGIC: &[u8; 4] = b"CNSP";
70
71/// MIDI notes the key map, the count table and each per-note table cover.
72pub const NOTES: usize = 128;
73
74/// A key map entry for a note the library does not cover.
75pub const UNCOVERED: u8 = 0xff;
76
77/// The stream versions the prefix offsets are validated against. A body with
78/// another version still reads and writes verbatim; its fields are refused rather
79/// than read from offsets that may not hold them.
80pub const KNOWN_VERSIONS: &[u32] = &[0x450, 0x464];
81
82/// The stream version that also carries a long name and a voicing of their own.
83const VERSION_SPLIT_NAME: u16 = 0x464;
84
85const KEY_MAP_AT: usize = 0x8c;
86const FINE_TUNE_AT: usize = 0x18c;
87const VERSION_AT: usize = 0x04;
88const VERSION_ECHO_AT: usize = 0x61c;
89const CHANNELS_AT: usize = 0x61e;
90const STROKE_COUNT_AT: usize = 0x620;
91const ROOT_COUNTS_AT: usize = 0x622;
92
93/// The kind of instrument the library states; [`encode::Kind`] names the codes.
94const KIND_AT: usize = 0x18;
95
96/// A gain over the whole library, in tenths of a decibel and signed. Confirmed on
97/// hardware.
98const GAIN_AT: usize = 0x40c;
99
100/// The highest key the instrument damps at note-off; keys above it ring on. Confirmed
101/// on hardware.
102const DAMPER_TOP_AT: usize = 0x40d;
103
104/// First byte of the stroke directory, and so the length of the prefix.
105const DIRECTORY_AT: usize = 0x732;
106
107/// Bytes per stroke record.
108const RECORD: usize = 118;
109
110const REC_START: usize = 0x00;
111const REC_BANK: usize = 0x04;
112const REC_LAYER: usize = 0x05;
113const REC_FRAMES: usize = 0x06;
114const REC_BLOCKS: usize = 0x0a;
115const REC_SEEDS: usize = 0x0c;
116const REC_MARKS: usize = 0x1c;
117const REC_MARK_BLOCK: usize = 0x2c;
118const REC_DECAY: usize = 0x2e;
119/// u16 holding the layer value again in vendor records. Sweeping it moved nothing
120/// measurable. Confirmed on hardware.
121const REC_WINDOW: usize = 0x32;
122/// u16 the instrument attenuates the stroke by, one decibel per unit. Confirmed on
123/// hardware.
124const REC_TRIM: usize = 0x34;
125const REC_DECAYS: usize = 0x36;
126const REC_ID: usize = 0x6e;
127
128/// Predictor seeds a record carries per channel.
129const SEEDS: usize = 4;
130
131/// Length marks a record carries at [`REC_MARKS`].
132const MARKS: usize = 4;
133
134/// One-pole decay coefficients a record carries after the one at [`REC_DECAY`], from
135/// [`REC_DECAYS`] up to the identifier. This ladder is the decay the instrument applies
136/// over the stroke's own; it is non-decreasing across its entries, and a stroke of any
137/// bank carries it — including a release stroke, which zeroes only the coefficient at
138/// [`REC_DECAY`]. Nothing here derives them from audio. Confirmed on hardware.
139pub const DECAYS: usize = 14;
140const _: () = assert!(REC_DECAYS + DECAYS * 4 == REC_ID);
141
142/// One [`REC_DECAYS`] entry applying nothing: 1.0 in the ladder's fixed point, where
143/// the vendor's own entries sit just below it.
144pub const LADDER_UNITY: u32 = 0x0080_0000;
145
146/// The audio grid's offset from a whole number of blocks.
147///
148/// Unexplained: every library holds it and nothing in the file derives it. The grid
149/// it defines is the one the instrument reads. Confirmed on hardware. A library laid
150/// out on it plays.
151pub const AUDIO_ALIGN_BIAS: usize = 192;
152
153/// Cents one unit of [`Library::fine_tune`] is worth. Measured between 0.6 and
154/// 0.8 cents per unit; this is the midpoint. Confirmed on hardware.
155pub const FINE_TUNE_CENTS_PER_UNIT: f32 = 0.7;
156
157/// What a stroke is played for, from the record's `+0x04`.
158///
159/// Confirmed on hardware.
160#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
161pub enum Bank {
162    /// Played at note-on. Every library has these.
163    Attack,
164    /// Played in place of the attack when the sustain pedal is down at note-on, which
165    /// the panel's acoustics bit 0 enables. Only the larger libraries carry them.
166    Resonance,
167    /// Played at note-off.
168    Release,
169}
170
171impl Bank {
172    pub const ALL: [Bank; 3] = [Bank::Attack, Bank::Resonance, Bank::Release];
173
174    pub fn from_code(code: u8) -> Option<Bank> {
175        match code {
176            0 => Some(Bank::Attack),
177            1 => Some(Bank::Resonance),
178            2 => Some(Bank::Release),
179            _ => None,
180        }
181    }
182
183    pub fn code(self) -> u8 {
184        match self {
185            Bank::Attack => 0,
186            Bank::Resonance => 1,
187            Bank::Release => 2,
188        }
189    }
190
191    pub fn name(self) -> &'static str {
192        match self {
193            Bank::Attack => "attack",
194            Bank::Resonance => "resonance",
195            Bank::Release => "release",
196        }
197    }
198}
199
200impl fmt::Display for Bank {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        f.write_str(self.name())
203    }
204}
205
206/// Which velocity layers of a root to keep.
207///
208/// A root's layers are counted within one [`Bank`], since each bank indexes its
209/// own set. Nothing is renumbered, and nothing should be: selection reads the value
210/// a layer states rather than its rank among the layers left ([`Stroke::layer`]), so
211/// the survivors keep their place in the velocity range and the softest one left
212/// takes over the velocities below it. Confirmed on hardware.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub enum Layers {
215    /// The loudest `n` of each root and bank — the `n` lowest layer values.
216    Loudest(usize),
217    /// Exactly these layer values, wherever they occur.
218    Only(BTreeSet<u8>),
219}
220
221/// What a transform removed.
222#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
223pub struct Change {
224    pub strokes_removed: usize,
225    pub roots_removed: usize,
226    pub keys_uncovered: usize,
227}
228
229/// The character the `Name#Variant` field splits on. Neither half may hold it.
230pub const NAME_SEPARATOR: char = '#';
231
232/// A fixed-width, NUL-padded text field in the prefix.
233#[derive(Clone, Copy)]
234struct TextField {
235    at: usize,
236    len: usize,
237}
238
239impl TextField {
240    /// `Name#Variant`, on every stream version.
241    const COMBINED: TextField = TextField {
242        at: 0x1c,
243        len: 0x20,
244    };
245    /// The long name, present only on [`VERSION_SPLIT_NAME`] streams.
246    const LONG_NAME: TextField = TextField {
247        at: 0x3c,
248        len: 0x20,
249    };
250    /// The voicing, present only on [`VERSION_SPLIT_NAME`] streams.
251    const VOICING: TextField = TextField {
252        at: 0x5c,
253        len: 0x20,
254    };
255
256    /// Longest string the field holds, the terminator excluded.
257    const fn capacity(self) -> usize {
258        self.len - 1
259    }
260
261    fn read(self, prefix: &[u8]) -> String {
262        let field = &prefix[self.at..self.at + self.len];
263        let end = field.iter().position(|&b| b == 0).unwrap_or(field.len());
264        String::from_utf8_lossy(&field[..end]).into_owned()
265    }
266
267    /// Text any of these fields carries back as it was written. The field is a fixed
268    /// width of bytes ended by a NUL and read lossily, so a NUL, a control character
269    /// and anything outside ASCII are all refused rather than stored.
270    fn check_text(text: &str) -> Result<(), Error> {
271        match text.chars().find(|&c| !c.is_ascii_graphic() && c != ' ') {
272            None => Ok(()),
273            Some(bad) => Err(ParseError::AssertFail(format!(
274                "{text:?} holds {bad:?}, which the field would not read back as written; it \
275                 carries printable ASCII"
276            ))
277            .into()),
278        }
279    }
280
281    /// [`TextField::check_text`], and short enough to fit with its terminator.
282    fn check(self, text: &str) -> Result<(), Error> {
283        TextField::check_text(text)?;
284        if text.len() > self.capacity() {
285            return Err(ParseError::OutOfBounds {
286                value: format!("{text:?} ({} bytes)", text.len()),
287                bound: format!("at most {} bytes", self.capacity()),
288            }
289            .into());
290        }
291        Ok(())
292    }
293
294    fn write(self, prefix: &mut [u8], text: &str) -> Result<(), Error> {
295        self.check(text)?;
296        let field = &mut prefix[self.at..self.at + self.len];
297        field.fill(0);
298        field[..text.len()].copy_from_slice(text.as_bytes());
299        Ok(())
300    }
301}
302
303/// One half of `Name#Variant` as a caller supplies it. A separator inside a half
304/// would move the split, so the halves that read back would not be the ones written.
305fn check_half(what: &str, text: &str) -> Result<(), Error> {
306    if text.contains(NAME_SEPARATOR) {
307        return Err(ParseError::AssertFail(format!(
308            "the {what} {text:?} holds {NAME_SEPARATOR:?}, which is what splits the name from \
309             the variant in the field they share"
310        ))
311        .into());
312    }
313    TextField::check_text(text)
314}
315
316/// A piano library (`npno`): the CBIN container with the `CNSP` body verbatim.
317///
318/// Reads and writes byte-exactly, checksum verified. [`Piano::library`] parses the
319/// body into the model the transforms and the writer work on.
320pub struct Piano {
321    pub file: Cbin<RawBody>,
322}
323
324impl Piano {
325    pub fn new() -> Piano {
326        Piano {
327            file: Cbin {
328                header: Header::new(FORMAT, (0, 0), 0),
329                body: RawBody(Vec::new()),
330            },
331        }
332    }
333
334    pub fn read_from(reader: &mut (impl Read + Seek)) -> Result<Piano, Error> {
335        Ok(Piano {
336            file: cbin::read(reader, FORMAT)?,
337        })
338    }
339
340    pub fn write_to(&self, writer: &mut (impl Write + Seek)) -> Result<(), Error> {
341        self.file.write_to(writer)
342    }
343
344    /// The body bytes, after checking the magic and that the stream version is one
345    /// the prefix offsets are pinned to.
346    fn mapped(&self) -> Result<&[u8], Error> {
347        let body = &self.file.body.0;
348        check_mapped(body)?;
349        Ok(body)
350    }
351
352    /// The stream version at body `0x04`.
353    pub fn stream_version(&self) -> Result<u16, Error> {
354        version_of(&self.file.body.0)
355    }
356
357    /// The `(name, variant)` pair from the `Name#Variant` field — for
358    /// *Electric Grand 1 CP80*, `("Electric Grand 1", "CP80")`. The variant is
359    /// empty when the field carries none.
360    pub fn name(&self) -> Result<(String, String), Error> {
361        let body = self.mapped()?;
362        if body.len() < DIRECTORY_AT {
363            return Err(short("the prefix"));
364        }
365        Ok(split_name(&TextField::COMBINED.read(body)))
366    }
367
368    /// The 128-entry key map: for each MIDI note, the root note whose strokes play
369    /// it, or [`UNCOVERED`].
370    pub fn key_map(&self) -> Result<&[u8], Error> {
371        self.mapped()?
372            .get(KEY_MAP_AT..KEY_MAP_AT + NOTES)
373            .ok_or_else(|| short("the key map"))
374    }
375
376    /// The container parsed: the prefix, the stroke directory and each stroke's
377    /// audio span.
378    pub fn library(&self) -> Result<Library<'_>, Error> {
379        Library::parse_body(self.file.header.clone(), &self.file.body.0)
380    }
381}
382
383impl Default for Piano {
384    fn default() -> Self {
385        Self::new()
386    }
387}
388
389impl fmt::Debug for Piano {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        f.debug_struct("npno::Piano")
392            .field("header", &self.file.header)
393            .field("body_len", &self.file.body.0.len())
394            .finish()
395    }
396}
397
398/// `Name#Variant` split on its separator, as the field spells each half. A field
399/// carrying no separator is all name.
400fn raw_halves(field: &str) -> (&str, &str) {
401    field.split_once(NAME_SEPARATOR).unwrap_or((field, ""))
402}
403
404/// `Name#Variant` split on its separator, each half trimmed of the padding the
405/// vendor lays either side of it.
406fn split_name(field: &str) -> (String, String) {
407    let (name, variant) = raw_halves(field);
408    (name.trim().to_owned(), variant.trim().to_owned())
409}
410
411/// `key` as an index into a [`NOTES`]-entry table, or an error naming it.
412///
413/// Every accessor that reaches the key map or a per-note table goes through this: a
414/// `u8` runs to 255, and past the table's last entry the byte belongs to the next
415/// table.
416fn midi_key(what: &str, key: u8) -> Result<usize, Error> {
417    let index = usize::from(key);
418    if index < NOTES {
419        return Ok(index);
420    }
421    Err(ParseError::OutOfBounds {
422        value: format!("{what} {key}"),
423        bound: "a MIDI note from 0 through 127".into(),
424    }
425    .into())
426}
427
428fn short(what: &str) -> Error {
429    ParseError::AssertFail(format!("the body ends inside {what}")).into()
430}
431
432/// The stream version at body `0x04`, the `CNSP` magic checked first.
433fn version_of(body: &[u8]) -> Result<u16, Error> {
434    if body.get(..4) != Some(CNSP_MAGIC.as_slice()) {
435        return Err(ParseError::AssertFail(format!(
436            "body opens {:02x?}, not the CNSP stream",
437            body.get(..4).unwrap_or_default()
438        ))
439        .into());
440    }
441    let bytes = body
442        .get(VERSION_AT..VERSION_AT + 2)
443        .ok_or_else(|| ParseError::AssertFail("body ends inside the CNSP header".to_string()))?;
444    Ok(u16::from_be_bytes(bytes.try_into().unwrap()))
445}
446
447/// The magic, and a stream version the prefix offsets are pinned to.
448fn check_mapped(body: &[u8]) -> Result<(), Error> {
449    let version = version_of(body)?;
450    crate::formats::known_version(FORMAT, u32::from(version), KNOWN_VERSIONS)
451}
452
453fn overflow(what: &str) -> Error {
454    ParseError::OutOfBounds {
455        value: what.to_string(),
456        bound: "an offset that fits this platform's address space".into(),
457    }
458    .into()
459}
460
461fn be16(bytes: &[u8], at: usize) -> u16 {
462    u16::from_be_bytes(bytes[at..at + 2].try_into().unwrap())
463}
464
465fn be32(bytes: &[u8], at: usize) -> u32 {
466    u32::from_be_bytes(bytes[at..at + 4].try_into().unwrap())
467}
468
469/// Where the first audio span starts, given the directory's end and the block size.
470///
471/// The grid is whole blocks offset by [`AUDIO_ALIGN_BIAS`]; the bytes between the
472/// directory and it are zero.
473fn first_audio_offset(directory_end: usize, block: usize) -> Result<usize, Error> {
474    directory_end
475        .checked_add(AUDIO_ALIGN_BIAS)
476        .map(|biased| biased.div_ceil(block))
477        .and_then(|blocks| blocks.checked_mul(block))
478        .and_then(|at| at.checked_sub(AUDIO_ALIGN_BIAS))
479        .ok_or_else(|| overflow("the first audio offset"))
480}
481
482/// One recorded note: the directory record, and the audio bytes it owns.
483///
484/// The record is carried verbatim apart from its audio offset, which is a
485/// placement and is recomputed every time a library is written.
486#[derive(Clone)]
487pub struct Stroke<'a> {
488    /// The note the recording was made at. It comes from the record's position in
489    /// the count table rather than from a field of the record itself. Confirmed on
490    /// hardware.
491    pub root: u8,
492    record: [u8; RECORD],
493    audio: Cow<'a, [u8]>,
494}
495
496impl<'a> Stroke<'a> {
497    /// The `+0x04` bank byte. Specimens hold only the codes [`Bank`] names, but an
498    /// unnamed one is carried rather than refused.
499    pub fn bank_code(&self) -> u8 {
500        self.record[REC_BANK]
501    }
502
503    pub fn bank(&self) -> Option<Bank> {
504        Bank::from_code(self.bank_code())
505    }
506
507    /// Softness value within the root's bank; 0 is the loudest recording, and a
508    /// bank's values need be neither dense nor start at zero.
509    ///
510    /// A key sounds the largest value the root holds that is at most
511    /// `(127 − velocity)·31/127`, so 0 plays at the top of the velocity range and a
512    /// value above 30 ([`encode::HIGHEST_PLAYED_LAYER`]) never plays at all. Confirmed
513    /// on hardware. The 31 is measured to about ±2, so a layer sitting on the bound
514    /// switches a few velocities either side of where the formula puts it. Vendor
515    /// libraries spread a root over 0..[`encode::SOFTEST_LAYER`].
516    pub fn layer(&self) -> u8 {
517        self.record[REC_LAYER]
518    }
519
520    /// Frames the stroke owns, which is what [`codec::decode`] emits: the block
521    /// overlap is excluded.
522    pub fn frames(&self) -> u32 {
523        be32(&self.record, REC_FRAMES)
524    }
525
526    pub fn blocks(&self) -> u16 {
527        be16(&self.record, REC_BLOCKS)
528    }
529
530    /// The `+0x34` trim, in decibels the instrument attenuates the stroke by.
531    pub fn trim(&self) -> u16 {
532        be16(&self.record, REC_TRIM)
533    }
534
535    /// The `+0x2e` decay coefficient, which a release stroke zeroes.
536    pub fn decay(&self) -> u32 {
537        be32(&self.record, REC_DECAY)
538    }
539
540    /// The [`DECAYS`]-entry decay ladder from `+0x36`.
541    pub fn ladder(&self) -> [u32; DECAYS] {
542        std::array::from_fn(|entry| be32(&self.record, REC_DECAYS + entry * 4))
543    }
544
545    /// The identifier at `+0x6e`. Distinguishes a recording across libraries;
546    /// what else it means is open. Inferred from specimens; not confirmed on
547    /// hardware.
548    pub fn id(&self) -> u32 {
549        be32(&self.record, REC_ID)
550    }
551
552    /// The predictor's four seed samples per channel, oldest first. A mono
553    /// stroke's second group is unused.
554    pub fn seeds(&self) -> [[i16; SEEDS]; 2] {
555        let mut out = [[0i16; SEEDS]; 2];
556        for (channel, group) in out.iter_mut().enumerate() {
557            for (i, slot) in group.iter_mut().enumerate() {
558                *slot = be16(&self.record, REC_SEEDS + (channel * SEEDS + i) * 2) as i16;
559            }
560        }
561        out
562    }
563
564    /// The encoded audio, `blocks × 1022 × channels` bytes.
565    pub fn audio(&self) -> &[u8] {
566        &self.audio
567    }
568
569    /// The record as stored, its audio offset excluded from any meaning: the
570    /// writer replaces it.
571    pub fn record(&self) -> &[u8; RECORD] {
572        &self.record
573    }
574}
575
576impl fmt::Debug for Stroke<'_> {
577    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578        f.debug_struct("Stroke")
579            .field("root", &self.root)
580            .field("bank", &self.bank_code())
581            .field("layer", &self.layer())
582            .field("frames", &self.frames())
583            .field("blocks", &self.blocks())
584            .finish()
585    }
586}
587
588/// A piano library parsed: the prefix, and every stroke with its audio.
589///
590/// Strokes borrow their audio from the [`Piano`] they were parsed from, so a
591/// transform that drops strokes copies nothing. The fields the container derives —
592/// the stroke count, the per-root counts and every audio offset — are not stored in
593/// the model at all; [`Library::to_body`] computes them from the stroke list, which
594/// is what makes an unmodified library rebuild to the bytes it was read from.
595#[derive(Clone)]
596pub struct Library<'a> {
597    /// The container header, carried so a transform yields a whole file.
598    pub header: Header,
599    /// Body bytes before the directory. The setters edit it; the writer rewrites
600    /// the counts within it.
601    prefix: Vec<u8>,
602    channels: u16,
603    strokes: Vec<Stroke<'a>>,
604}
605
606impl<'a> Library<'a> {
607    /// A whole `.npno` file parsed over a borrowed slice: the body is taken as a
608    /// subslice, so every stroke's audio points into `file` rather than a copy of it.
609    ///
610    /// The container's checksum is not verified here — the caller has inspected the
611    /// container.
612    pub fn borrow(file: &'a [u8]) -> Result<Library<'a>, Error> {
613        let mut head: &[u8] = file;
614        let (header, _) = cbin::read_header(&mut head)?;
615        if header.tag.as_slice() != FORMAT.as_bytes() {
616            return Err(ParseError::WrongFormat {
617                expected: FORMAT,
618                got: String::from_utf8_lossy(&header.tag).into_owned(),
619            }
620            .into());
621        }
622        let start = usize::try_from(header.generation.body_start())
623            .map_err(|_| overflow("the container's header"))?;
624        let trailer = usize::try_from(header.generation.trailer_len())
625            .map_err(|_| overflow("the container's checksum trailer"))?;
626        let end = file
627            .len()
628            .checked_sub(trailer)
629            .ok_or_else(|| short("the container's checksum trailer"))?;
630        let body = file.get(start..end).ok_or_else(|| short("the header"))?;
631        Library::parse_body(header, body)
632    }
633
634    fn parse_body(header: Header, body: &'a [u8]) -> Result<Library<'a>, Error> {
635        check_mapped(body)?;
636        let prefix = body
637            .get(..DIRECTORY_AT)
638            .ok_or_else(|| short("the prefix"))?;
639
640        let version = be16(prefix, VERSION_AT);
641        let echo = be16(prefix, VERSION_ECHO_AT);
642        if echo != version {
643            return Err(ParseError::AssertFail(format!(
644                "the stream version {version:#06x} is echoed as {echo:#06x}"
645            ))
646            .into());
647        }
648
649        let channels = be16(prefix, CHANNELS_AT);
650        if !(1..=2).contains(&channels) {
651            return Err(ParseError::OutOfBounds {
652                value: format!("{channels} channels"),
653                bound: "1 or 2".into(),
654            }
655            .into());
656        }
657        let block = block_bytes(channels);
658
659        let count = usize::from(be16(prefix, STROKE_COUNT_AT));
660        let counts: Vec<u16> = (0..NOTES)
661            .map(|n| be16(prefix, ROOT_COUNTS_AT + n * 2))
662            .collect();
663        let summed: usize = counts.iter().map(|&c| usize::from(c)).sum();
664        if summed != count {
665            return Err(ParseError::AssertFail(format!(
666                "the per-root counts sum to {summed} where the stroke count is {count}"
667            ))
668            .into());
669        }
670
671        let directory_end = RECORD
672            .checked_mul(count)
673            .and_then(|len| DIRECTORY_AT.checked_add(len))
674            .ok_or_else(|| overflow("the stroke directory"))?;
675        let records = body
676            .get(DIRECTORY_AT..directory_end)
677            .ok_or_else(|| short("the stroke directory"))?;
678
679        let first = first_audio_offset(directory_end, block)?;
680        let pad = body
681            .get(directory_end..first)
682            .ok_or_else(|| short("the alignment gap before the audio"))?;
683        if pad.iter().any(|&b| b != 0) {
684            return Err(ParseError::AssertFail(
685                "the alignment gap before the audio is not zero".into(),
686            )
687            .into());
688        }
689
690        let mut strokes = Vec::new();
691        strokes
692            .try_reserve_exact(count)
693            .map_err(|_| overflow("the stroke list"))?;
694        let mut at = first;
695        let mut roots = counts
696            .iter()
697            .enumerate()
698            .flat_map(|(note, &n)| std::iter::repeat_n(note as u8, usize::from(n)));
699        for i in 0..count {
700            let mut record = [0u8; RECORD];
701            record.copy_from_slice(&records[i * RECORD..(i + 1) * RECORD]);
702            let root = roots.next().expect("the counts sum to the stroke count");
703            let start = be32(&record, REC_START);
704            if usize::try_from(start) != Ok(at) {
705                return Err(ParseError::AssertFail(format!(
706                    "stroke {i} starts at {start:#x} where the spans before it end at {at:#x}"
707                ))
708                .into());
709            }
710            let span = usize::from(be16(&record, REC_BLOCKS))
711                .checked_mul(block)
712                .ok_or_else(|| overflow("a stroke's audio span"))?;
713            let end = at.checked_add(span).ok_or_else(|| overflow("the audio"))?;
714            let audio = body
715                .get(at..end)
716                .ok_or_else(|| short("a stroke's audio span"))?;
717            strokes.push(Stroke {
718                root,
719                record,
720                audio: Cow::Borrowed(audio),
721            });
722            at = end;
723        }
724        if at != body.len() {
725            return Err(ParseError::AssertFail(format!(
726                "the audio ends at {at:#x} where the body ends at {:#x}",
727                body.len()
728            ))
729            .into());
730        }
731
732        let library = Library {
733            header,
734            prefix: prefix.to_vec(),
735            channels,
736            strokes,
737        };
738        library.check_key_map()?;
739        Ok(library)
740    }
741
742    /// Every key map entry names a root the directory holds.
743    fn check_key_map(&self) -> Result<(), Error> {
744        let roots = self.roots();
745        for (key, &root) in self.key_map().iter().enumerate() {
746            if root != UNCOVERED && !roots.contains(&root) {
747                return Err(ParseError::AssertFail(format!(
748                    "key {key} plays root {root}, which no stroke records"
749                ))
750                .into());
751            }
752        }
753        Ok(())
754    }
755
756    pub fn stream_version(&self) -> u16 {
757        be16(&self.prefix, VERSION_AT)
758    }
759
760    pub fn channels(&self) -> u16 {
761        self.channels
762    }
763
764    /// Bytes in one encoded block, `1022 × channels`.
765    pub fn block_bytes(&self) -> usize {
766        block_bytes(self.channels)
767    }
768
769    pub fn strokes(&self) -> &[Stroke<'a>] {
770        &self.strokes
771    }
772
773    /// This library's prefix and stroke records with no audio behind them: what a
774    /// [`encode::Donor::Template`] reads, and nothing [`Library::to_body`] can lay out.
775    pub fn without_audio(&self) -> Library<'static> {
776        Library {
777            header: self.header.clone(),
778            prefix: self.prefix.clone(),
779            channels: self.channels,
780            strokes: self
781                .strokes
782                .iter()
783                .map(|stroke| Stroke {
784                    root: stroke.root,
785                    record: stroke.record,
786                    audio: Cow::Owned(Vec::new()),
787                })
788                .collect(),
789        }
790    }
791
792    /// Retrim the `index`-th stroke, in the decibels [`Stroke::trim`] reads.
793    pub fn set_trim(&mut self, index: usize, decibels: u16) -> Result<(), Error> {
794        let count = self.strokes.len();
795        let stroke = self
796            .strokes
797            .get_mut(index)
798            .ok_or_else(|| ParseError::OutOfBounds {
799                value: format!("stroke {index}"),
800                bound: format!("the {count} strokes the directory holds"),
801            })?;
802        stroke.record[REC_TRIM..REC_TRIM + 2].copy_from_slice(&decibels.to_be_bytes());
803        Ok(())
804    }
805
806    /// The `(name, variant)` pair, from the same field [`Piano::name`] reads.
807    pub fn name(&self) -> (String, String) {
808        split_name(&TextField::COMBINED.read(&self.prefix))
809    }
810
811    /// The 128-entry key map: the root note that plays each key, or [`UNCOVERED`].
812    pub fn key_map(&self) -> &[u8] {
813        &self.prefix[KEY_MAP_AT..KEY_MAP_AT + NOTES]
814    }
815
816    fn key_map_mut(&mut self) -> &mut [u8] {
817        &mut self.prefix[KEY_MAP_AT..KEY_MAP_AT + NOTES]
818    }
819
820    /// The root notes the directory records, ascending.
821    pub fn roots(&self) -> BTreeSet<u8> {
822        self.strokes.iter().map(|s| s.root).collect()
823    }
824
825    /// The root note whose strokes play `key`, or `None` where the map leaves the
826    /// key uncovered.
827    pub fn key_root(&self, key: u8) -> Result<Option<u8>, Error> {
828        let root = self.key_map()[midi_key("key", key)?];
829        Ok((root != UNCOVERED).then_some(root))
830    }
831
832    /// The keys the map routes to `root`, ascending. A root the map never names —
833    /// including one outside the MIDI range — has no keys.
834    pub fn keys_for(&self, root: u8) -> Vec<u8> {
835        self.key_map()
836            .iter()
837            .enumerate()
838            .filter(|&(_, &r)| r == root)
839            .map(|(key, _)| key as u8)
840            .collect()
841    }
842
843    /// The per-key fine tune at `0x18c + key`, in units worth
844    /// [`FINE_TUNE_CENTS_PER_UNIT`] each. Confirmed on hardware.
845    pub fn fine_tune(&self, key: u8) -> Result<i8, Error> {
846        Ok(self.prefix[FINE_TUNE_AT + midi_key("key", key)?] as i8)
847    }
848
849    /// Retune one key, in the units [`Library::fine_tune`] reads.
850    ///
851    /// The unit's size and direction: Confirmed on hardware. That rewriting the byte
852    /// retunes the key: Inferred from specimens; not confirmed on hardware.
853    pub fn set_fine_tune(&mut self, key: u8, units: i8) -> Result<(), Error> {
854        let at = FINE_TUNE_AT + midi_key("key", key)?;
855        self.prefix[at] = units as u8;
856        Ok(())
857    }
858
859    /// The gain over the whole library at `0x40c`, in tenths of a decibel.
860    pub fn gain(&self) -> i8 {
861        self.prefix[GAIN_AT] as i8
862    }
863
864    pub fn set_gain(&mut self, tenths: i8) {
865        self.prefix[GAIN_AT] = tenths as u8;
866    }
867
868    /// The highest key the instrument damps at note-off, at `0x40d`.
869    pub fn damper_top(&self) -> u8 {
870        self.prefix[DAMPER_TOP_AT]
871    }
872
873    /// Move the damper limit. [`encode::ALL_KEYS_DAMPED`] leaves no key ringing; a
874    /// key past the last MIDI note is refused.
875    pub fn set_damper_top(&mut self, key: u8) -> Result<(), Error> {
876        self.prefix[DAMPER_TOP_AT] = midi_key("damper limit", key)? as u8;
877        Ok(())
878    }
879
880    /// The instrument kind the library states at `0x18`; [`encode::Kind::from_code`]
881    /// names it.
882    pub fn kind_code(&self) -> u8 {
883        self.prefix[KIND_AT]
884    }
885
886    /// File the library under another kind of instrument.
887    ///
888    /// The byte changes nothing a library sounds like. Confirmed on hardware.
889    pub fn set_kind(&mut self, kind: encode::Kind) {
890        self.prefix[KIND_AT] = kind.code();
891    }
892
893    /// The long name at `0x3c` and the voicing at `0x5c`, which only
894    /// [`VERSION_SPLIT_NAME`] streams carry. Both are `None` on the older stream.
895    ///
896    /// They are their own fields, not a split of the `Name#Variant` one: a library can
897    /// spell the long name differently from the name before the `#`, and the voicing
898    /// holds neither the padding nor the size suffix the variant does. Inferred from
899    /// specimens; not confirmed on hardware.
900    pub fn long_name(&self) -> Option<String> {
901        self.split_field(TextField::LONG_NAME)
902    }
903
904    pub fn voicing(&self) -> Option<String> {
905        self.split_field(TextField::VOICING)
906    }
907
908    fn split_field(&self, field: TextField) -> Option<String> {
909        (self.stream_version() == VERSION_SPLIT_NAME).then(|| field.read(&self.prefix))
910    }
911
912    /// Rename the library, leaving the variant alone.
913    ///
914    /// A name holding [`NAME_SEPARATOR`], or text the field would not read back, is
915    /// refused; so is one too long for the field it shares with the variant. Nothing
916    /// is written unless every field the rename touches accepts its text.
917    ///
918    /// On a stream that carries one, the long name is set to the same text: both
919    /// are the library's name, and a rename that moved only one would leave the
920    /// old name showing wherever the instrument reads the other. Which of the two it
921    /// reads: Inferred from specimens; not confirmed on hardware. That is why both
922    /// move.
923    pub fn set_name(&mut self, name: &str) -> Result<(), Error> {
924        let field = TextField::COMBINED.read(&self.prefix);
925        let variant = raw_halves(&field).1.to_owned();
926        self.set_name_and_variant(name, &variant)
927    }
928
929    /// Replace the variant — the text after [`NAME_SEPARATOR`], where the vendor
930    /// records the voicing and the library's size — leaving both names alone. A
931    /// variant holding the separator itself is refused.
932    pub fn set_variant(&mut self, variant: &str) -> Result<(), Error> {
933        check_half("variant", variant)?;
934        let field = TextField::COMBINED.read(&self.prefix);
935        let combined = format!("{}{NAME_SEPARATOR}{variant}", raw_halves(&field).0);
936        TextField::COMBINED.write(&mut self.prefix, &combined)
937    }
938
939    /// Write both halves of the `Name#Variant` field at once, which is what a caller
940    /// replacing both states.
941    ///
942    /// The name a caller gives is checked against the variant it will share the field
943    /// with rather than the one the prefix holds, so a name that fits beside its own
944    /// variant is not refused for a longer one it replaces. The long name follows the
945    /// name as it does in [`Library::set_name`].
946    fn set_name_and_variant(&mut self, name: &str, variant: &str) -> Result<(), Error> {
947        check_half("name", name)?;
948        check_half("variant", variant)?;
949        let combined = format!("{name}{NAME_SEPARATOR}{variant}");
950        let long = (self.stream_version() == VERSION_SPLIT_NAME).then_some(name);
951        TextField::COMBINED.check(&combined)?;
952        if let Some(long) = long {
953            TextField::LONG_NAME.check(long)?;
954        }
955        TextField::COMBINED.write(&mut self.prefix, &combined)?;
956        if let Some(long) = long {
957            TextField::LONG_NAME.write(&mut self.prefix, long)?;
958        }
959        Ok(())
960    }
961
962    /// Replace the voicing at `0x5c`. Refused on a stream with no such field.
963    pub fn set_voicing(&mut self, voicing: &str) -> Result<(), Error> {
964        if self.stream_version() != VERSION_SPLIT_NAME {
965            return Err(ParseError::AssertFail(format!(
966                "stream {:#06x} carries no voicing field; the variant after the \
967                 {NAME_SEPARATOR:?} is where it records one",
968                self.stream_version()
969            ))
970            .into());
971        }
972        TextField::VOICING.write(&mut self.prefix, voicing)
973    }
974
975    /// Route `key` to `root`, or to nothing when `root` is `None`.
976    ///
977    /// A root the directory does not record is refused: the instrument would have
978    /// no stroke to play.
979    ///
980    /// That the instrument follows a rewritten map — a key routed to another root, or
981    /// to nothing: Inferred from specimens; not confirmed on hardware.
982    pub fn set_key_root(&mut self, key: u8, root: Option<u8>) -> Result<(), Error> {
983        let key = midi_key("key", key)?;
984        if let Some(root) = root {
985            midi_key("root", root)?;
986            if !self.roots().contains(&root) {
987                return Err(ParseError::OutOfBounds {
988                    value: format!("root {root}"),
989                    bound: "a root the directory records".into(),
990                }
991                .into());
992            }
993        }
994        self.key_map_mut()[key] = root.unwrap_or(UNCOVERED);
995        Ok(())
996    }
997
998    /// Drop every stroke of one bank — the resonance set turns a large library into
999    /// a small one, the release set silences the note-off sample.
1000    ///
1001    /// For [`Bank::Release`], the instrument damps the note at note-off where the
1002    /// library it came from plays a release tail. Confirmed on hardware.
1003    pub fn drop_bank(&mut self, bank: Bank) -> Change {
1004        let code = bank.code();
1005        self.retain(|s| s.bank_code() != code)
1006    }
1007
1008    /// Keep only the layers `keep` selects, per root and bank.
1009    ///
1010    /// Confirmed on hardware. A library with its softest layers dropped plays the
1011    /// softest one left at the velocities they had, and is unchanged at loud ones.
1012    pub fn keep_layers(&mut self, keep: &Layers) -> Change {
1013        match keep {
1014            Layers::Only(layers) => {
1015                let layers = layers.clone();
1016                self.retain(|s| layers.contains(&s.layer()))
1017            }
1018            Layers::Loudest(n) => {
1019                let mut groups: BTreeMap<(u8, u8), BTreeSet<u8>> = BTreeMap::new();
1020                for stroke in &self.strokes {
1021                    groups
1022                        .entry((stroke.root, stroke.bank_code()))
1023                        .or_default()
1024                        .insert(stroke.layer());
1025                }
1026                let kept: BTreeSet<(u8, u8, u8)> = groups
1027                    .into_iter()
1028                    .flat_map(|((root, bank), layers)| {
1029                        layers.into_iter().take(*n).map(move |l| (root, bank, l))
1030                    })
1031                    .collect();
1032                self.retain(|s| kept.contains(&(s.root, s.bank_code(), s.layer())))
1033            }
1034        }
1035    }
1036
1037    /// Keep the strokes `keep` accepts and drop the rest, then uncover the keys whose
1038    /// root has gone.
1039    ///
1040    /// The selection every other transform here is a named case of, for a caller whose
1041    /// own is none of them — one layer on one root, say. A stroke carries its own
1042    /// predictor seeds and its blocks overlap only each other, so whichever subset is
1043    /// left re-lays into a library the writer can lay out.
1044    ///
1045    /// Inferred from specimens; not confirmed on hardware. [`Library::drop_bank`] and
1046    /// [`Library::keep_layers`] are the two selections a hardware read covers.
1047    pub fn retain_strokes(&mut self, keep: impl FnMut(&Stroke<'a>) -> bool) -> Change {
1048        self.retain(keep)
1049    }
1050
1051    /// Uncover every key outside `range`, then drop the roots nothing plays any
1052    /// more. Keys inside the range keep the roots they had.
1053    ///
1054    /// That an uncovered key falls silent rather than reaching for a neighbouring
1055    /// root: Inferred from specimens; not confirmed on hardware.
1056    pub fn cut_range(&mut self, range: RangeInclusive<u8>) -> Result<Change, Error> {
1057        midi_key("the range's lowest key", *range.start())?;
1058        midi_key("the range's highest key", *range.end())?;
1059        Ok(self.restrict(|key| range.contains(&key)))
1060    }
1061
1062    /// Two libraries, one covering the keys below `key` and one covering `key` and
1063    /// above, each cut the way [`Library::cut_range`] cuts.
1064    ///
1065    /// A root whose keys straddle `key` lands in both halves — each half has to be
1066    /// playable on its own — so the two together hold more strokes than the one they
1067    /// came from. Each half carries [`Library::cut_range`]'s provenance.
1068    pub fn split_at(&self, key: u8) -> Result<(Library<'a>, Library<'a>), Error> {
1069        midi_key("the split key", key)?;
1070        let mut low = self.clone();
1071        let mut high = self.clone();
1072        low.restrict(|k| k < key);
1073        high.restrict(|k| k >= key);
1074        Ok((low, high))
1075    }
1076
1077    /// Uncover every key `keep` rejects, then drop the roots nothing plays.
1078    fn restrict(&mut self, keep: impl Fn(u8) -> bool) -> Change {
1079        let mut uncovered = 0;
1080        for (key, slot) in self.key_map_mut().iter_mut().enumerate() {
1081            if !keep(key as u8) && *slot != UNCOVERED {
1082                *slot = UNCOVERED;
1083                uncovered += 1;
1084            }
1085        }
1086        let live: BTreeSet<u8> = self.key_map().iter().copied().collect();
1087        let mut change = self.retain(|s| live.contains(&s.root));
1088        change.keys_uncovered += uncovered;
1089        change
1090    }
1091
1092    /// Drop the strokes `keep` rejects, then uncover the keys whose root has gone.
1093    fn retain(&mut self, mut keep: impl FnMut(&Stroke<'a>) -> bool) -> Change {
1094        let strokes_before = self.strokes.len();
1095        let roots_before = self.roots().len();
1096        self.strokes.retain(|s| keep(s));
1097        let roots = self.roots();
1098        let mut keys_uncovered = 0;
1099        for slot in self.key_map_mut() {
1100            if *slot != UNCOVERED && !roots.contains(slot) {
1101                *slot = UNCOVERED;
1102                keys_uncovered += 1;
1103            }
1104        }
1105        Change {
1106            strokes_removed: strokes_before - self.strokes.len(),
1107            roots_removed: roots_before - roots.len(),
1108            keys_uncovered,
1109        }
1110    }
1111
1112    /// Bytes the body would occupy.
1113    pub fn body_len(&self) -> Result<usize, Error> {
1114        let (_, len) = self.extent()?;
1115        Ok(len)
1116    }
1117
1118    /// The first audio offset and the body length the current stroke list implies.
1119    ///
1120    /// A stroke holding anything other than the `blocks × block_bytes` its record states
1121    /// is refused: a body laid out around it is one a read of that body rejects.
1122    fn extent(&self) -> Result<(usize, usize), Error> {
1123        let directory_end = RECORD
1124            .checked_mul(self.strokes.len())
1125            .and_then(|len| DIRECTORY_AT.checked_add(len))
1126            .ok_or_else(|| overflow("the stroke directory"))?;
1127        let block = self.block_bytes();
1128        let first = first_audio_offset(directory_end, block)?;
1129        let mut len = first;
1130        for (index, stroke) in self.strokes.iter().enumerate() {
1131            let span = usize::from(stroke.blocks())
1132                .checked_mul(block)
1133                .ok_or_else(|| overflow("a stroke's audio span"))?;
1134            if stroke.audio.len() != span {
1135                return Err(ParseError::AssertFail(format!(
1136                    "stroke {index} holds {} audio bytes where the {} block(s) its record \
1137                     states span {span}",
1138                    stroke.audio.len(),
1139                    stroke.blocks()
1140                ))
1141                .into());
1142            }
1143            len = len.checked_add(span).ok_or_else(|| overflow("the audio"))?;
1144        }
1145        Ok((first, len))
1146    }
1147
1148    /// Lay the body out: the prefix with its counts rewritten, the directory with
1149    /// every audio offset recomputed, the zero gap, then the audio spans in
1150    /// directory order.
1151    ///
1152    /// Confirmed on hardware. A body laid out here, with a directory the transforms
1153    /// shortened and every span moved, is accepted by the instrument and plays at the
1154    /// level the library it came from plays at.
1155    pub fn to_body(&self) -> Result<Vec<u8>, Error> {
1156        let count = u16::try_from(self.strokes.len()).map_err(|_| ParseError::OutOfBounds {
1157            value: format!("{} strokes", self.strokes.len()),
1158            bound: "the u16 stroke count the directory holds".into(),
1159        })?;
1160        if self.strokes.windows(2).any(|w| w[0].root > w[1].root) {
1161            return Err(ParseError::AssertFail(
1162                "the strokes are not in ascending root order, which is what the per-root \
1163                 counts index them by"
1164                    .into(),
1165            )
1166            .into());
1167        }
1168
1169        let (first, len) = self.extent()?;
1170        let mut out = try_vec(len)?;
1171        out[..DIRECTORY_AT].copy_from_slice(&self.prefix);
1172        out[CHANNELS_AT..CHANNELS_AT + 2].copy_from_slice(&self.channels.to_be_bytes());
1173        out[STROKE_COUNT_AT..STROKE_COUNT_AT + 2].copy_from_slice(&count.to_be_bytes());
1174        for note in 0..NOTES {
1175            let n = self
1176                .strokes
1177                .iter()
1178                .filter(|s| usize::from(s.root) == note)
1179                .count();
1180            let n = u16::try_from(n).expect("a per-root count is at most the stroke count");
1181            let at = ROOT_COUNTS_AT + note * 2;
1182            out[at..at + 2].copy_from_slice(&n.to_be_bytes());
1183        }
1184
1185        let mut at = first;
1186        for (i, stroke) in self.strokes.iter().enumerate() {
1187            let start = u32::try_from(at).map_err(|_| ParseError::OutOfBounds {
1188                value: format!("audio offset {at:#x}"),
1189                bound: "the u32 offset a stroke record holds".into(),
1190            })?;
1191            let record = DIRECTORY_AT + i * RECORD;
1192            out[record..record + RECORD].copy_from_slice(&stroke.record);
1193            out[record + REC_START..record + REC_START + 4].copy_from_slice(&start.to_be_bytes());
1194            out[at..at + stroke.audio.len()].copy_from_slice(&stroke.audio);
1195            at += stroke.audio.len();
1196        }
1197        Ok(out)
1198    }
1199
1200    /// The library as a file, ready to write. The container recomputes its own
1201    /// checksum.
1202    ///
1203    /// The u32 at body `0x06` is unique per file and is not a checksum, a size or a
1204    /// hash of anything in it; with nothing to recompute it from, an edit carries
1205    /// it over rather than inventing a value. The hardware evidence reaches no further
1206    /// than this: a library carrying its source's word loads and plays. Confirmed on
1207    /// hardware. What the word means is open.
1208    pub fn to_piano(&self) -> Result<Piano, Error> {
1209        Ok(Piano {
1210            file: Cbin {
1211                header: self.header.clone(),
1212                body: RawBody(self.to_body()?),
1213            },
1214        })
1215    }
1216}
1217
1218impl fmt::Debug for Library<'_> {
1219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1220        let (name, variant) = self.name();
1221        f.debug_struct("npno::Library")
1222            .field("name", &name)
1223            .field("variant", &variant)
1224            .field(
1225                "stream_version",
1226                &format_args!("{:#06x}", self.stream_version()),
1227            )
1228            .field("channels", &self.channels)
1229            .field("strokes", &self.strokes.len())
1230            .field("roots", &self.roots().len())
1231            .finish()
1232    }
1233}
1234
1235fn block_bytes(channels: u16) -> usize {
1236    codec::BLOCK_WORDS * 2 * usize::from(channels)
1237}
1238
1239#[cfg(test)]
1240mod tests {
1241    use super::synthetic::{take, Build};
1242    use super::*;
1243
1244    #[test]
1245    fn the_name_field_splits_on_the_separator() {
1246        let piano = Build::new().piano();
1247        assert_eq!(piano.stream_version().unwrap(), 0x450);
1248        assert_eq!(
1249            piano.name().unwrap(),
1250            ("Test Piano".to_string(), "Variant".to_string())
1251        );
1252    }
1253
1254    #[test]
1255    fn an_unknown_stream_version_still_round_trips_but_does_not_decode() {
1256        let mut build = Build::new();
1257        build.version = 0x500;
1258        let piano = build.piano();
1259        assert_eq!(piano.stream_version().unwrap(), 0x500);
1260        assert!(
1261            piano.name().is_err(),
1262            "the name offset is only pinned on known versions"
1263        );
1264        assert!(piano.key_map().is_err());
1265        assert!(piano.library().is_err());
1266    }
1267
1268    #[test]
1269    fn a_body_without_the_magic_is_refused() {
1270        let mut piano = Build::new().piano();
1271        piano.file.body.0[0] = b'Q';
1272        assert!(piano.name().is_err(), "a non-CNSP body has no name to read");
1273    }
1274
1275    #[test]
1276    fn a_library_rebuilds_to_the_bytes_it_was_read_from() {
1277        let piano = Build::new().piano();
1278        let rebuilt = piano.library().unwrap().to_body().unwrap();
1279        assert_eq!(rebuilt, piano.file.body.0);
1280    }
1281
1282    #[test]
1283    fn the_directory_reports_each_strokes_root_bank_and_layer() {
1284        let piano = Build::new().piano();
1285        let library = piano.library().unwrap();
1286        let seen: Vec<(u8, Option<Bank>, u8)> = library
1287            .strokes()
1288            .iter()
1289            .map(|s| (s.root, s.bank(), s.layer()))
1290            .collect();
1291        assert_eq!(
1292            seen,
1293            [
1294                (60, Some(Bank::Attack), 0),
1295                (60, Some(Bank::Release), 3),
1296                (72, Some(Bank::Attack), 0),
1297            ]
1298        );
1299        assert_eq!(library.keys_for(60), [60, 61]);
1300    }
1301
1302    #[test]
1303    fn a_stroke_whose_start_does_not_abut_the_one_before_is_refused() {
1304        let mut piano = Build::new().piano();
1305        let second = DIRECTORY_AT + RECORD;
1306        let start = be32(&piano.file.body.0, second + REC_START);
1307        piano.file.body.0[second..second + 4].copy_from_slice(&(start + 2).to_be_bytes());
1308        let error = piano.library().unwrap_err().to_string();
1309        assert!(error.contains("stroke 1 starts at"), "{error}");
1310    }
1311
1312    #[test]
1313    fn a_key_routed_to_a_root_no_stroke_records_is_refused() {
1314        let mut build = Build::new();
1315        build.map.push((80, 80));
1316        let error = build.piano().library().unwrap_err().to_string();
1317        assert!(error.contains("key 80 plays root 80"), "{error}");
1318    }
1319
1320    #[test]
1321    fn a_count_table_that_does_not_sum_to_the_stroke_count_is_refused() {
1322        let mut piano = Build::new().piano();
1323        let at = ROOT_COUNTS_AT + 60 * 2;
1324        piano.file.body.0[at..at + 2].copy_from_slice(&5u16.to_be_bytes());
1325        let error = piano.library().unwrap_err().to_string();
1326        assert!(error.contains("per-root counts sum to"), "{error}");
1327    }
1328
1329    #[test]
1330    fn dropping_a_bank_relays_the_audio_and_leaves_the_rest_verbatim() {
1331        let piano = Build::new().piano();
1332        let before = piano.library().unwrap();
1333        let mut after = piano.library().unwrap();
1334        let change = after.drop_bank(Bank::Release);
1335        assert_eq!(
1336            change,
1337            Change {
1338                strokes_removed: 1,
1339                roots_removed: 0,
1340                keys_uncovered: 0
1341            }
1342        );
1343
1344        let body = after.to_body().unwrap();
1345        let trimmed = Piano {
1346            file: Cbin {
1347                header: after.header.clone(),
1348                body: RawBody(body),
1349            },
1350        };
1351        let reparsed = trimmed.library().unwrap();
1352        assert_eq!(reparsed.strokes().len(), 2);
1353        for (kept, moved) in before
1354            .strokes()
1355            .iter()
1356            .filter(|s| s.bank() != Some(Bank::Release))
1357            .zip(reparsed.strokes())
1358        {
1359            assert_eq!(kept.audio(), moved.audio(), "a span moved verbatim");
1360            assert_eq!(kept.id(), moved.id());
1361            assert_eq!(&kept.record()[REC_BANK..], &moved.record()[REC_BANK..]);
1362        }
1363    }
1364
1365    #[test]
1366    fn dropping_every_stroke_of_a_root_uncovers_the_keys_it_played() {
1367        let mut build = Build::new();
1368        build.takes = vec![
1369            take(60, Bank::Attack, 0, 1),
1370            take(72, Bank::Resonance, 0, 1),
1371        ];
1372        let piano = build.piano();
1373        let mut library = piano.library().unwrap();
1374        let change = library.drop_bank(Bank::Resonance);
1375        assert_eq!(change.strokes_removed, 1);
1376        assert_eq!(change.roots_removed, 1);
1377        assert_eq!(change.keys_uncovered, 1);
1378        assert_eq!(library.key_map()[72], UNCOVERED);
1379        library.to_body().unwrap();
1380    }
1381
1382    #[test]
1383    fn keeping_the_loudest_layer_keeps_one_per_root_and_bank() {
1384        let mut build = Build::new();
1385        build.takes = vec![
1386            take(60, Bank::Attack, 0, 1),
1387            take(60, Bank::Attack, 5, 1),
1388            take(60, Bank::Release, 26, 1),
1389            take(60, Bank::Release, 30, 1),
1390            take(72, Bank::Attack, 1, 1),
1391        ];
1392        let piano = build.piano();
1393        let mut library = piano.library().unwrap();
1394        library.keep_layers(&Layers::Loudest(1));
1395        let kept: Vec<(u8, u8, u8)> = library
1396            .strokes()
1397            .iter()
1398            .map(|s| (s.root, s.bank_code(), s.layer()))
1399            .collect();
1400        assert_eq!(kept, [(60, 0, 0), (60, 2, 26), (72, 0, 1)]);
1401    }
1402
1403    #[test]
1404    fn keeping_named_layers_takes_them_wherever_they_occur() {
1405        let mut build = Build::new();
1406        build.takes = vec![
1407            take(60, Bank::Attack, 0, 1),
1408            take(60, Bank::Attack, 5, 1),
1409            take(72, Bank::Attack, 5, 1),
1410        ];
1411        let piano = build.piano();
1412        let mut library = piano.library().unwrap();
1413        library.keep_layers(&Layers::Only([5].into_iter().collect()));
1414        let kept: Vec<(u8, u8)> = library
1415            .strokes()
1416            .iter()
1417            .map(|s| (s.root, s.layer()))
1418            .collect();
1419        assert_eq!(kept, [(60, 5), (72, 5)]);
1420    }
1421
1422    /// The stroke-level selection: one layer on one root, which no named transform
1423    /// expresses. What the predicate rejects goes, what it accepts stays verbatim, and
1424    /// a root left with no strokes at all stops answering its keys.
1425    #[test]
1426    fn retaining_strokes_drops_what_the_predicate_rejects_and_nothing_else() {
1427        let mut build = Build::new();
1428        build.takes = vec![
1429            take(60, Bank::Attack, 0, 1),
1430            take(60, Bank::Attack, 5, 1),
1431            take(72, Bank::Attack, 5, 2),
1432        ];
1433        let piano = build.piano();
1434
1435        let mut kept_all = piano.library().unwrap();
1436        let unchanged = kept_all.retain_strokes(|_| true);
1437        assert_eq!(unchanged, Change::default());
1438        assert_eq!(
1439            kept_all.to_body().unwrap(),
1440            piano.file.body.0,
1441            "a predicate that rejects nothing re-lays the body it read"
1442        );
1443
1444        let mut library = piano.library().unwrap();
1445        let change = library.retain_strokes(|s| !(s.root == 72 && s.layer() == 5));
1446        assert_eq!(
1447            change,
1448            Change {
1449                strokes_removed: 1,
1450                roots_removed: 1,
1451                keys_uncovered: 1,
1452            }
1453        );
1454        let left: Vec<(u8, u8)> = library
1455            .strokes()
1456            .iter()
1457            .map(|s| (s.root, s.layer()))
1458            .collect();
1459        assert_eq!(left, [(60, 0), (60, 5)]);
1460        assert_eq!(
1461            library.key_map()[72],
1462            UNCOVERED,
1463            "root 72 lost every stroke, so its key answers nothing"
1464        );
1465        assert_eq!(library.key_map()[60], 60, "and the other root is untouched");
1466        library.to_body().unwrap();
1467    }
1468
1469    /// The builder hands back a file, not only a body: a `.npno` another crate's tests
1470    /// can read back through the front door.
1471    #[test]
1472    fn a_synthetic_library_reads_back_as_the_file_it_was_built_as() {
1473        let bytes = Build::new().bytes().unwrap();
1474        let entity = crate::from_stream(&mut std::io::Cursor::new(&bytes)).unwrap();
1475        let crate::Entity::Piano(piano) = &entity else {
1476            panic!("{entity:?} is no piano library");
1477        };
1478        assert_eq!(
1479            piano.name().unwrap(),
1480            ("Test Piano".to_string(), "Variant".to_string())
1481        );
1482        assert_eq!(piano.library().unwrap().strokes().len(), 3);
1483        assert_eq!(crate::to_bytes(&entity).unwrap(), bytes);
1484    }
1485
1486    /// A borrowed library is a view over the caller's own bytes: tens of megabytes of
1487    /// audio stay where they were read, and what the view states is what the file
1488    /// states.
1489    #[test]
1490    fn borrowing_a_file_reads_it_without_copying_the_audio() {
1491        let bytes = Build::new().bytes().unwrap();
1492        let library = Library::borrow(&bytes).unwrap();
1493
1494        let base = bytes.as_ptr() as usize;
1495        let within = base..base + bytes.len();
1496        for stroke in library.strokes() {
1497            let at = stroke.audio().as_ptr() as usize;
1498            assert!(
1499                within.contains(&at),
1500                "{stroke:?} holds a copy of its audio, not the caller's bytes"
1501            );
1502        }
1503        assert_eq!(library.name(), ("Test Piano".into(), "Variant".into()));
1504        assert_eq!(library.strokes().len(), 3);
1505        assert_eq!(library.to_body().unwrap(), Build::new().body());
1506    }
1507
1508    #[test]
1509    fn borrowing_refuses_a_container_that_is_not_a_whole_piano_library() {
1510        let bytes = Build::new().bytes().unwrap();
1511
1512        let mut other = bytes.clone();
1513        other[0x08..0x0c].copy_from_slice(b"nsmp");
1514        let error = Library::borrow(&other).unwrap_err().to_string();
1515        assert!(error.contains("expected a npno file, got nsmp"), "{error}");
1516
1517        let error = Library::borrow(&bytes[..bytes.len() - 1])
1518            .unwrap_err()
1519            .to_string();
1520        assert!(
1521            error.contains("ends inside a stroke's audio span"),
1522            "{error}"
1523        );
1524    }
1525
1526    #[test]
1527    fn cutting_the_range_drops_the_roots_nothing_plays_any_more() {
1528        let piano = Build::new().piano();
1529        let mut library = piano.library().unwrap();
1530        let change = library.cut_range(0..=70).unwrap();
1531        assert_eq!(change.keys_uncovered, 1);
1532        assert_eq!(change.roots_removed, 1);
1533        assert_eq!(library.roots(), [60].into_iter().collect());
1534        assert_eq!(library.key_map()[72], UNCOVERED);
1535        assert_eq!(library.key_map()[60], 60);
1536    }
1537
1538    #[test]
1539    fn a_split_gives_each_half_the_roots_its_keys_play() {
1540        let piano = Build::new().piano();
1541        let (low, high) = piano.library().unwrap().split_at(70).unwrap();
1542        assert_eq!(low.roots(), [60].into_iter().collect());
1543        assert_eq!(high.roots(), [72].into_iter().collect());
1544        assert_eq!(low.keys_for(60), [60, 61]);
1545        assert_eq!(high.keys_for(72), [72]);
1546        let audio: usize = piano
1547            .library()
1548            .unwrap()
1549            .strokes()
1550            .iter()
1551            .map(|s| s.audio().len())
1552            .sum();
1553        let halves: usize = [&low, &high]
1554            .iter()
1555            .flat_map(|l| l.strokes())
1556            .map(|s| s.audio().len())
1557            .sum();
1558        assert_eq!(
1559            halves, audio,
1560            "a split shares every stroke out exactly once"
1561        );
1562    }
1563
1564    #[test]
1565    fn a_rename_carries_the_long_name_with_it_and_leaves_the_voicing_alone() {
1566        let mut build = Build::new();
1567        build.version = VERSION_SPLIT_NAME;
1568        let piano = build.piano();
1569        let mut library = piano.library().unwrap();
1570        library.set_voicing("Nordiska").unwrap();
1571        library.set_name("Renamed").unwrap();
1572        library.set_variant("Nordiska  Sml").unwrap();
1573        assert_eq!(library.name(), ("Renamed".into(), "Nordiska  Sml".into()));
1574        assert_eq!(library.long_name().as_deref(), Some("Renamed"));
1575        assert_eq!(
1576            library.voicing().as_deref(),
1577            Some("Nordiska"),
1578            "the voicing is its own field, not the variant's head"
1579        );
1580    }
1581
1582    #[test]
1583    fn the_older_stream_has_no_long_name_or_voicing_to_read_or_write() {
1584        let piano = Build::new().piano();
1585        let mut library = piano.library().unwrap();
1586        assert_eq!(library.stream_version(), 0x450);
1587        assert_eq!(library.long_name(), None);
1588        assert_eq!(library.voicing(), None);
1589        assert!(library.set_voicing("Nordiska").is_err());
1590    }
1591
1592    #[test]
1593    fn a_name_past_the_field_is_refused_without_changing_it() {
1594        let piano = Build::new().piano();
1595        let mut library = piano.library().unwrap();
1596        let too_long = "x".repeat(TextField::COMBINED.capacity());
1597        assert!(library.set_name(&too_long).is_err());
1598        assert_eq!(library.name().0, "Test Piano");
1599    }
1600
1601    #[test]
1602    fn a_remap_to_a_root_the_directory_does_not_record_is_refused() {
1603        let piano = Build::new().piano();
1604        let mut library = piano.library().unwrap();
1605        assert!(library.set_key_root(64, Some(61)).is_err());
1606        library.set_key_root(64, Some(72)).unwrap();
1607        assert_eq!(library.keys_for(72), [64, 72]);
1608        assert_eq!(library.key_root(64).unwrap(), Some(72));
1609        library.set_key_root(64, None).unwrap();
1610        assert_eq!(library.keys_for(72), [72]);
1611        assert_eq!(library.key_root(64).unwrap(), None);
1612    }
1613
1614    #[test]
1615    fn fine_tune_reads_and_writes_the_per_key_byte() {
1616        let piano = Build::new().piano();
1617        let mut library = piano.library().unwrap();
1618        assert_eq!(library.fine_tune(60).unwrap(), 0);
1619        library.set_fine_tune(60, -4).unwrap();
1620        assert_eq!(library.fine_tune(60).unwrap(), -4);
1621        assert_eq!(library.to_body().unwrap()[FINE_TUNE_AT + 60], 0xfc);
1622    }
1623
1624    /// Offsets at which two bodies of the same length differ: an edit's footprint.
1625    fn changed(before: &[u8], after: &[u8]) -> Vec<usize> {
1626        assert_eq!(before.len(), after.len(), "the body changed length");
1627        (0..before.len())
1628            .filter(|&at| before[at] != after[at])
1629            .collect()
1630    }
1631
1632    #[test]
1633    fn the_gain_and_the_damper_limit_each_write_one_byte_of_the_prefix() {
1634        let piano = Build::new().piano();
1635        let mut library = piano.library().unwrap();
1636        let before = library.to_body().unwrap();
1637
1638        library.set_gain(-20);
1639        let gained = library.to_body().unwrap();
1640        assert_eq!(library.gain(), -20);
1641        assert_eq!(gained[GAIN_AT], 0xec, "tenths of a decibel, signed");
1642        assert_eq!(changed(&before, &gained), [GAIN_AT]);
1643
1644        library.set_damper_top(90).unwrap();
1645        let damped = library.to_body().unwrap();
1646        assert_eq!(library.damper_top(), 90);
1647        assert_eq!(changed(&gained, &damped), [DAMPER_TOP_AT]);
1648    }
1649
1650    #[test]
1651    fn the_instrument_kind_writes_one_byte_and_reads_back_as_the_kind_it_was_given() {
1652        let piano = Build::new().piano();
1653        let mut library = piano.library().unwrap();
1654        let before = library.to_body().unwrap();
1655
1656        library.set_kind(encode::Kind::Wurlitzer);
1657        let filed = library.to_body().unwrap();
1658        assert_eq!(
1659            encode::Kind::from_code(library.kind_code()),
1660            Some(encode::Kind::Wurlitzer)
1661        );
1662        assert_eq!(changed(&before, &filed), [KIND_AT]);
1663    }
1664
1665    #[test]
1666    fn a_damper_limit_past_the_last_midi_note_is_refused_without_moving_the_one_held() {
1667        let piano = Build::new().piano();
1668        let mut library = piano.library().unwrap();
1669        library.set_damper_top(encode::ALL_KEYS_DAMPED).unwrap();
1670        let before = library.to_body().unwrap();
1671        assert!(library.set_damper_top(NOTES as u8).is_err());
1672        assert_eq!(library.damper_top(), encode::ALL_KEYS_DAMPED);
1673        assert_eq!(library.to_body().unwrap(), before);
1674    }
1675
1676    /// The trim is a u16, so a value that fits one byte and one that does not must each
1677    /// reach the field whole, and neither may touch the record beside it.
1678    #[test]
1679    fn a_retrim_writes_both_bytes_of_one_strokes_own_field() {
1680        let piano = Build::new().piano();
1681        let mut library = piano.library().unwrap();
1682        assert!(library.strokes().iter().all(|s| s.trim() == 0));
1683        let before = library.to_body().unwrap();
1684        let at = DIRECTORY_AT + RECORD + REC_TRIM;
1685
1686        library.set_trim(1, 7).unwrap();
1687        let low = library.to_body().unwrap();
1688        assert_eq!(library.strokes()[1].trim(), 7);
1689        assert_eq!(changed(&before, &low), [at + 1]);
1690
1691        library.set_trim(1, 0x0107).unwrap();
1692        let high = library.to_body().unwrap();
1693        assert_eq!(library.strokes()[1].trim(), 0x0107);
1694        assert_eq!(changed(&low, &high), [at]);
1695
1696        let error = library.set_trim(3, 4).unwrap_err().to_string();
1697        assert!(error.contains("stroke 3"), "{error}");
1698        assert_eq!(
1699            library.to_body().unwrap(),
1700            high,
1701            "a refused retrim leaves the directory alone"
1702        );
1703    }
1704
1705    #[test]
1706    fn a_key_above_the_last_midi_note_is_refused_by_every_entry_point() {
1707        let piano = Build::new().piano();
1708        let mut library = piano.library().unwrap();
1709        let last = (NOTES - 1) as u8;
1710        let past = NOTES as u8;
1711
1712        assert!(library.fine_tune(last).is_ok());
1713        assert!(library.key_root(last).is_ok());
1714        assert!(library.set_fine_tune(last, 1).is_ok());
1715        assert!(library.set_key_root(last, None).is_ok());
1716        assert!(library.cut_range(0..=last).is_ok());
1717        assert!(library.split_at(last).is_ok());
1718
1719        assert!(library.fine_tune(past).is_err());
1720        assert!(library.key_root(past).is_err());
1721        assert!(library.set_fine_tune(past, 1).is_err());
1722        assert!(library.set_key_root(past, None).is_err());
1723        assert!(library.set_key_root(0, Some(past)).is_err());
1724        assert!(library.cut_range(0..=past).is_err());
1725        assert!(library.cut_range(past..=past).is_err());
1726        assert!(library.split_at(past).is_err());
1727    }
1728
1729    #[test]
1730    fn a_key_past_the_tune_table_is_refused_rather_than_written_to_the_next_table() {
1731        let piano = Build::new().piano();
1732        let mut library = piano.library().unwrap();
1733        let before = library.to_body().unwrap();
1734        assert!(library.set_fine_tune(NOTES as u8, 32).is_err());
1735        assert_eq!(library.to_body().unwrap(), before);
1736    }
1737
1738    #[test]
1739    fn a_separator_in_a_name_or_a_variant_is_refused() {
1740        let piano = Build::new().piano();
1741        let mut library = piano.library().unwrap();
1742        assert!(library.set_name("Upright#2").is_err());
1743        assert!(library.set_variant("Sml#XL").is_err());
1744        assert_eq!(library.name(), ("Test Piano".into(), "Variant".into()));
1745    }
1746
1747    /// A library with its audio dropped is a donor, not a file: laying it out would
1748    /// write a directory whose block counts nothing in the body backs.
1749    #[test]
1750    fn a_stroke_holding_other_than_the_blocks_its_record_states_is_not_laid_out() {
1751        let piano = Build::new().piano();
1752        let library = piano.library().unwrap();
1753        assert!(library.to_body().is_ok());
1754
1755        let skeleton = library.without_audio();
1756        let error = skeleton
1757            .to_body()
1758            .expect_err("expected a refusal")
1759            .to_string();
1760        assert!(error.contains("stroke 0 holds 0 audio bytes"), "{error}");
1761        assert!(skeleton.body_len().is_err());
1762    }
1763
1764    /// The halves either side of the separator are the vendor's own bytes, padding and
1765    /// all: setting one leaves the other exactly as the field spells it.
1766    #[test]
1767    fn setting_one_half_of_the_name_field_leaves_the_other_as_it_was_written() {
1768        let mut piano = Build::new().piano();
1769        let at = TextField::COMBINED.at;
1770        let padded = b"Grand Imperial # Bdorf XL";
1771        piano.file.body.0[at..at + TextField::COMBINED.len].fill(0);
1772        piano.file.body.0[at..at + padded.len()].copy_from_slice(padded);
1773
1774        assert_eq!(
1775            piano.library().unwrap().name(),
1776            ("Grand Imperial".into(), "Bdorf XL".into())
1777        );
1778
1779        let mut renamed = piano.library().unwrap();
1780        renamed.set_name("Upright").unwrap();
1781        assert_eq!(
1782            TextField::COMBINED.read(&renamed.prefix),
1783            "Upright# Bdorf XL"
1784        );
1785
1786        let mut revoiced = piano.library().unwrap();
1787        revoiced.set_variant("Sml").unwrap();
1788        assert_eq!(
1789            TextField::COMBINED.read(&revoiced.prefix),
1790            "Grand Imperial #Sml"
1791        );
1792    }
1793
1794    #[test]
1795    fn text_the_field_would_not_read_back_is_refused() {
1796        let piano = Build::new().piano();
1797        let mut library = piano.library().unwrap();
1798        assert!(
1799            library.set_name("Flügel").is_err(),
1800            "the field is read as ASCII"
1801        );
1802        assert!(
1803            library.set_variant("Sml\0XL").is_err(),
1804            "a NUL ends the field, hiding everything after it"
1805        );
1806        assert_eq!(library.name(), ("Test Piano".into(), "Variant".into()));
1807    }
1808
1809    #[test]
1810    fn the_first_audio_offset_sits_on_the_block_grid_less_the_bias() {
1811        for block in [1022, 2044] {
1812            for count in [0usize, 1, 38, 2196] {
1813                let end = DIRECTORY_AT + count * RECORD;
1814                let at = first_audio_offset(end, block).unwrap();
1815                assert!(at >= end, "the audio never overlaps the directory");
1816                assert_eq!((at + AUDIO_ALIGN_BIAS) % block, 0);
1817                assert!(at - end < block, "no whole spare block in the gap");
1818            }
1819        }
1820    }
1821}