Skip to main content

nord_format/formats/nsmp/
encode.rs

1//! Building a sample instrument from PCM.
2//!
3//! The inverse of [`codec`](super::codec). What this emits is what Nord Sample Editor
4//! writes for the same input, byte for byte, apart from one residue: the resampling
5//! [`kernel`](super::kernel) is the instrument's to within a few `1e-8` per tap, and a
6//! handful of taps the editor evaluates a ulp off the closed form leave the occasional
7//! field one count from the editor's. No structural field moves with it, and neither
8//! does the pitch, the length, or anything else about what the instrument plays.
9//!
10//! The record coding the editor picks, [`Predictor::Minimising`], is the default here.
11//! [`Predictor::Plain`] opts out and states every content field outright: the same
12//! audio in a file several times larger on smooth material, and not the editor's bytes.
13//!
14//! Under either predictor a file from here **round-trips through this crate's own
15//! decoder exactly** and obeys every structural law the format is known to have.
16//!
17//! For [`Layout::V2`], the Electro 5 loads and plays one under either predictor, at
18//! the pitch the decoder renders. Confirmed on hardware. The wide generations
19//! reproduce the editor's own renders, but the Electro 5 plays only v2, so their
20//! playback: Inferred from specimens; not confirmed on hardware.
21//!
22//! ```no_run
23//! # use nord_format::formats::nsmp::encode;
24//! let samples: Vec<i16> = vec![0; 44_100];
25//! let options = encode::Options::new("Test").root_key(60);
26//! let instrument = encode::instrument(&samples, &options).unwrap();
27//! std::fs::write("test.nsmp", instrument.to_bytes().unwrap()).unwrap();
28//! ```
29//!
30//! **All three generations write from the one plan.** [`Options::layout`] picks the
31//! generation; what moves with it is the container — the narrow `NWS` chain against
32//! the wide `NSMP` one, and the section schemas inside — and the stream's units, which
33//! [`Units`] holds. The lattice, the kernel, the quantiser, the count laws and the
34//! record grammar's bit layout are the same object in all three.
35//!
36//! [`multi_zone`] is the same builder across a keyboard: one `stk` per zone, highest
37//! zone first, each zone's record naming its stroke by the global id the caller gives
38//! it. Zone counts move where a stroke's audio may start, so the allocation each stroke
39//! is packed into comes from [`stroke::header_len`](super::stroke::header_len) rather
40//! than from a constant.
41//!
42//! **Stereo is the mono plan run once per channel and interleaved.** A stereo stroke
43//! carries both channels under one header at the doubled cell, and every count-law
44//! landmark — the field total, the resync position, both 1:1 runs — is exactly its mono
45//! value doubled. So the whole of stereo, on the plan side, is a channel count: cells
46//! and 1:1 records double, the terminator states the doubled cell, and the predictor
47//! keeps a history per channel. Where the two channels' *bits* go does move with the
48//! generation: v2 and v3 alternate fields in one bitstream, v4 packs each channel's
49//! half into its own words and alternates those.
50//!
51//! For [`Layout::V2`], a stereo encode plays with its channels in order and
52//! independent. Confirmed on hardware. The wide generations: Inferred from specimens;
53//! not confirmed on hardware.
54//!
55//! A [`Loop`] truncates the stroke at its end and opens a marked record at its start,
56//! which is the whole of what the container stores about looping: the crossfade is
57//! baked into the audio here, while loop detune, the decay switch and the short loop's
58//! pitch-tracking flag reach nowhere. Wide headers carry the decay amount. The fade's
59//! frame count is the caller's to work out — a project states the long loop's in frames
60//! and the short loop's as a percentage of its length — and it arrives here already in
61//! frames, fraction and all.
62//!
63//! For [`Layout::V2`], the Electro 5 sustains a looped encode to note-off, and the
64//! seam is clean. Confirmed on hardware. The wide generations: Inferred from
65//! specimens; not confirmed on hardware.
66
67use super::codec::{self, Layout, PITCH_DEN, PITCH_NUM, WRAP};
68use super::kernel;
69use super::section::{self, Section, Section4};
70use super::stroke::packet_len;
71use super::{Sample, SampleV3};
72use crate::cbin::{Cbin, Generation, Header};
73use crate::error::{Error, ParseError};
74use crate::formats::nsmpproj;
75
76/// Content version this writes per generation: `format × 100 + revision`, at the
77/// revision the editor emits.
78const fn version(layout: Layout) -> u32 {
79    match layout {
80        Layout::V2 => 200,
81        Layout::V3 => 300,
82        Layout::V4 => 400,
83    }
84}
85
86/// The sample-instrument `aux` value, the same in every generation.
87/// Unexplained: real programs hold this, and the panel cannot produce it.
88const AUX: u32 = 0x000f_0000;
89
90/// Largest field count a record header can state, from its 14-bit count field.
91/// ⚠️ A record covers whole cells, so how many *cells* that is halves on a stereo
92/// stroke — the count is a field count, and a stereo cell holds two channels' worth.
93const MAX_COUNT: usize = (1 << 14) - 1;
94
95/// Widest field a stroke's peak may take: quantisation shifts until it fits. On a
96/// stereo stroke this is the whole of the shift rule.
97const PEAK_WIDTH: u8 = 14;
98
99/// The stream units one stroke is written in: the generation's word and cell sizes,
100/// scaled by how many channels share the stroke.
101///
102/// Everything else about the encoder is generation-independent — the lattice, the
103/// kernel, the quantiser and the record grammar's bit layout do not move — so this is
104/// the whole of what a generation changes about a stream.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106struct Units {
107    layout: Layout,
108    /// 1 or 2.
109    channels: usize,
110}
111
112impl Units {
113    const fn word(self) -> usize {
114        self.layout.word()
115    }
116
117    const fn word_bits(self) -> usize {
118        self.layout.word() * 8
119    }
120
121    /// Fields one content cell covers: the generation's cell per channel.
122    const fn cell(self) -> usize {
123        self.layout.cell() * self.channels
124    }
125
126    /// Fields one 1:1 record covers at most: the generation's RMAX per channel.
127    const fn chunk(self) -> usize {
128        self.layout.rmax() * self.channels
129    }
130
131    /// Whether a record's two channels occupy alternating, independently padded
132    /// words rather than alternating fields in one bitstream.
133    const fn splits(self) -> bool {
134        self.channels == 2 && self.layout.splits_wide_openings()
135    }
136
137    /// Words one channel's half of a split record occupies.
138    const fn half(self, count: usize, width: u8) -> usize {
139        (count / 2 * width as usize).div_ceil(self.word_bits())
140    }
141
142    /// Words one record occupies, header included.
143    ///
144    /// A split record pays for each channel's own padding; a content record tiles
145    /// whole words either way, so only the 1:1 regime is ever wider for it.
146    const fn span(self, count: usize, width: u8) -> usize {
147        if self.splits() {
148            1 + 2 * self.half(count, width)
149        } else {
150            (self.word_bits() + count * width as usize).div_ceil(self.word_bits())
151        }
152    }
153
154    /// Words in one packet of allocation.
155    const fn packet_words(self) -> usize {
156        packet_len(self.layout) / self.word()
157    }
158
159    /// Words of slack the allocation keeps ahead of the chain's first record.
160    ///
161    /// The chain is right-aligned in whole packets either way; the wide chain buys a
162    /// further packet rather than let the lead fall below this, so its strokes carry
163    /// 7 to 38 words of slack where a narrow one carries 0 to 126.
164    ///
165    /// Inferred from specimens; not confirmed on hardware.
166    const fn min_lead(self) -> usize {
167        match self.layout {
168            Layout::V2 => 0,
169            Layout::V3 | Layout::V4 => 7,
170        }
171    }
172
173    /// Absolute field ceiling imposed by the stream directory and minimum width.
174    const fn max_fields(self) -> usize {
175        MAX_STREAM_WORDS * self.word_bits() / MIN_WIDTH as usize
176    }
177}
178
179/// Last-record field counts, per channel, that do not carry the extra quantiser bit —
180/// `None` for a generation whose mono strokes never spend it.
181///
182/// A run's records are RMAX-sized until a remainder, so the range a last record can
183/// take is the generation's: 24..=32 fields at v2 and 32..=48 at v3. Every width in
184/// both ranges has been read off a render, and these are the ones that never buy the
185/// bit. There is no arithmetic behind either set and no correspondence between them.
186///
187/// Inferred from specimens; not confirmed on hardware.
188const fn dead_last_record(layout: Layout) -> Option<&'static [usize]> {
189    match layout {
190        Layout::V2 => Some(&[24, 29, 32]),
191        Layout::V3 => Some(&[32, 41, 43, 45, 47, 48]),
192        Layout::V4 => None,
193    }
194}
195
196/// Whether a stroke spends one more quantiser bit than its peak needs, narrowing its
197/// widest field a bit under [`PEAK_WIDTH`] and shrinking the stream.
198///
199/// `values` are the stroke's fields before any shift. Read them at the smallest shift
200/// that fits the peak in [`PEAK_WIDTH`] bits: the bit is spent when a field still
201/// outside the signed 13-bit range there falls inside the **last record of one of the
202/// stroke's 1:1 runs** and that record's field count is not one [`dead_last_record`]
203/// names. A field in an earlier record of a run, or out in the content cells, never
204/// buys it, and no run's length is otherwise consulted.
205///
206/// ⚠️ Every 1:1 run counts, the loop's included — a marked record opens a run of its
207/// own past the resync, and a field landing in its last record buys the bit exactly
208/// as one in the opening or resync run does.
209///
210/// A stereo stroke never spends the bit, in any generation, and neither does a v4 mono
211/// one: both quantise at the peak term alone.
212///
213/// Inferred from specimens; not confirmed on hardware. The Electro 5 plays v2 only.
214fn spends_extra_bit(values: &[i64], plan: &Plan) -> bool {
215    if plan.channels != 1 {
216        return false;
217    }
218    let Some(dead) = dead_last_record(plan.layout) else {
219        return false;
220    };
221    let over = 1i64 << (PEAK_WIDTH - 2);
222    let shift = peak_shift(values, PEAK_WIDTH);
223    [
224        Some((0, plan.warmup)),
225        Some((plan.resync_at, plan.resync)),
226        plan.looped.map(|points| (points.at, points.warmup)),
227    ]
228    .into_iter()
229    .flatten()
230    .any(|(base, run)| {
231        let Some(&last) = chunks(run, plan.chunk()).last() else {
232            return false;
233        };
234        !dead.contains(&(last / plan.channels))
235            && values[base + run - last..base + run].iter().any(|&v| {
236                let v = v >> shift;
237                v < -over || v >= over
238            })
239    })
240}
241
242/// The smallest nonnegative shift fitting every value in `width` bits.
243fn peak_shift(values: &[i64], width: u8) -> i32 {
244    let low = values.iter().copied().min().unwrap_or(0);
245    let high = values.iter().copied().max().unwrap_or(0);
246    let mut shift = 0i32;
247    while width_of(low >> shift, high >> shift) > width {
248        shift += 1;
249    }
250    shift
251}
252
253/// Widest field a record header can declare, from its four-bit width. Padding stores
254/// values wider than they need, which sign-extend back to themselves.
255const MAX_STORED_WIDTH: u8 = 16;
256
257/// Narrowest field. Width 2 is the draft the encoder codes everything at before it
258/// promotes anything, and a width-1 flag-1 record is the terminator.
259const MIN_WIDTH: u8 = 2;
260
261/// Channels one stroke may carry. The terminator states the cell size, and one bit of
262/// doubling is all it can say.
263const MAX_CHANNELS: usize = 2;
264
265/// Zones one instrument may hold, from the `map` section's single count byte.
266const MAX_ZONES: usize = u8::MAX as usize;
267
268/// The widest stroke id a zone record can name: the field is one byte, and zero is
269/// not an id the editor issues.
270const MAX_STROKE_ID: u32 = u8::MAX as u32;
271
272/// Fields an unlooped stroke carries past the end of its source, every one of which
273/// stores zero: the kernel's ring past the last sample is cut, not coded.
274const RING_OUT: usize = 127;
275
276/// Fields the stream's opening ramp lasts, per channel: field `f` of each channel is
277/// scaled by `(f / RAMP_IN)³`, truncated, until the ramp reaches 1.
278/// Inferred from specimens; not confirmed on hardware.
279const RAMP_IN: usize = 35;
280
281/// Shortest input the editor encodes: below it, it clamps a project's own extent
282/// rather than laying a shorter stream out. The opening, the count laws and the
283/// resync are the same object all the way down to it.
284pub const MIN_FRAMES: usize = 92;
285
286/// Fields per channel a looped stroke carries past its loop end, repeating the loop's
287/// own opening so that playback is unchanged. The mark clears the loop start by the
288/// same amount, which is why the loop's length survives it.
289const LOOP_LEAD: usize = 5;
290
291/// Fields per channel a loop's marked record clears the **resync point** by, at least.
292/// A loop whose ordinary [`LOOP_LEAD`] would land the mark nearer than this is pushed
293/// back by repeating more of itself, which moves the whole stream's length with it.
294///
295/// The floor is on the gap from the resync point, not on the mark's own position and
296/// not on the room left between the mark and the run in front of it: a resync run may
297/// reach the mark record with nothing between them.
298///
299/// Inferred from specimens; not confirmed on hardware.
300const fn min_resync_gap(layout: Layout) -> usize {
301    match layout {
302        Layout::V2 => 72,
303        Layout::V3 | Layout::V4 => 64,
304    }
305}
306
307/// Longest input the stroke header's 16-bit word directory can address unambiguously.
308const MAX_STREAM_WORDS: usize = WRAP;
309
310/// Backward-difference coefficients for predictor orders 0 to 4.
311const DIFFERENCE: [&[i32]; 5] = [
312    &[1],
313    &[1, -1],
314    &[1, -2, 1],
315    &[1, -3, 3, -1],
316    &[1, -4, 6, -4, 1],
317];
318
319/// How content records code their fields.
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
321pub enum Predictor {
322    /// Store every content field outright at order zero.
323    Plain,
324    /// Choose the narrowest predictor per cell, the lowest order among equals — the
325    /// editor's own choice. Smaller than plain records and exact through this crate's
326    /// decoder.
327    #[default]
328    Minimising,
329}
330
331/// A sustain loop, in source frames.
332///
333/// The container stores a loop as two things and nothing else: the stroke stops at
334/// [`end`](Loop::end), and the record the loop starts at carries the mark bit. Loop
335/// detune, loop decay, and whether the editor called this a short loop or a long one
336/// are not stored anywhere, so a caller that needs them cannot have them.
337#[derive(Debug, Clone, Copy, PartialEq)]
338pub struct Loop {
339    /// First frame of the loop.
340    pub start: usize,
341    /// One past its last frame. Audio after it is not encoded.
342    pub end: usize,
343    /// Frames of the loop's tail that fade into the frames before [`start`](Loop::start).
344    /// The fade is applied to the samples here, because that is where the instrument
345    /// reads it from. Fractional, because a project can state it as a percentage of the
346    /// loop rather than a frame count, and dropping the fraction moves the fade a field.
347    /// Inferred from specimens; not confirmed on hardware.
348    pub crossfade: f64,
349}
350
351impl Loop {
352    /// A loop over `start..end` with no crossfade.
353    pub fn new(start: usize, end: usize) -> Loop {
354        Loop {
355            start,
356            end,
357            crossfade: 0.0,
358        }
359    }
360
361    pub fn crossfade(mut self, frames: f64) -> Loop {
362        self.crossfade = frames;
363        self
364    }
365}
366
367/// What to build around the audio.
368#[derive(Debug, Clone)]
369pub struct Options {
370    name: String,
371    root_key: u8,
372    top_note: Option<u8>,
373    predictor: Predictor,
374    loops: Option<Loop>,
375    channels: u16,
376    secondary_start: Option<f64>,
377    shift: Option<u8>,
378    layout: Layout,
379}
380
381impl Options {
382    /// Defaults: the name given, root key C4, the editor's own top note, the editor's
383    /// record coding, no loop, the v2 generation.
384    pub fn new(name: impl Into<String>) -> Options {
385        Options {
386            name: name.into(),
387            root_key: 60,
388            top_note: None,
389            predictor: Predictor::default(),
390            loops: None,
391            channels: 1,
392            secondary_start: None,
393            shift: None,
394            layout: Layout::V2,
395        }
396    }
397
398    /// Which generation to write: `.nsmp`, `.nsmp3` or `.nsmp4`. The audio is the same
399    /// object in all three — what moves is the container and the stream's units.
400    pub fn layout(mut self, layout: Layout) -> Options {
401        self.layout = layout;
402        self
403    }
404
405    /// Resynchronise the stream at `frames` source frames from the first one — a
406    /// project's `m_startSecondary`, measured from its `m_start`. Unset, the stream
407    /// resynchronises where a fresh project would put it: [`default_secondary_start`].
408    pub fn secondary_start(mut self, frames: f64) -> Options {
409        self.secondary_start = Some(frames);
410        self
411    }
412
413    /// How many channels the PCM interleaves — 1 or 2. Anything else is refused when
414    /// the instrument is built.
415    pub fn channels(mut self, channels: u16) -> Options {
416        self.channels = channels;
417        self
418    }
419
420    /// Quantise at `bits` of shift instead of what the shift rule picks. Experimental: a
421    /// lever for laying the same stroke out at neighbouring shifts, not a setting the
422    /// editor exposes.
423    pub fn shift(mut self, bits: u8) -> Options {
424        self.shift = Some(bits);
425        self
426    }
427
428    /// Loop the stroke, which also truncates it at [`Loop::end`].
429    pub fn loops(mut self, points: Loop) -> Options {
430        self.loops = Some(points);
431        self
432    }
433
434    /// The MIDI note the sample plays untransposed at.
435    pub fn root_key(mut self, note: u8) -> Options {
436        self.root_key = note;
437        self
438    }
439
440    /// The highest note the zone covers. Defaults to two octaves above the root, which
441    /// is the layout the editor lays down for a single zone.
442    pub fn top_note(mut self, note: u8) -> Options {
443        self.top_note = Some(note);
444        self
445    }
446
447    pub fn predictor(mut self, predictor: Predictor) -> Options {
448        self.predictor = predictor;
449        self
450    }
451
452    fn resolved_top_note(&self) -> u8 {
453        self.top_note
454            .unwrap_or_else(|| self.root_key.saturating_add(24).min(127))
455    }
456}
457
458/// Where a loop lands on the field lattice. Every count is in stream fields, so on a
459/// stereo stroke each is twice what one channel sees.
460#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461pub struct Looped {
462    /// Field the marked record opens at.
463    pub at: usize,
464    /// Fields repeated past the loop end, which is also how far `at` clears the loop
465    /// start: [`LOOP_LEAD`] per channel, or more when the mark is pushed off the
466    /// resync point by [`min_resync_gap`].
467    pub lead: usize,
468    /// Fields of the loop's tail the crossfade rewrites.
469    pub crossfade: usize,
470    /// Fields in the 1:1 run the loop opens with.
471    pub warmup: usize,
472    /// Content cells between that run and the terminator.
473    pub cells: usize,
474}
475
476/// Stroke landmarks derived from the source frame count.
477///
478/// Every field count here is a **stream** count: on a stereo stroke the two channels
479/// interleave, so each is twice the per-channel number the mono laws state. [`cell`] and
480/// [`chunk`] scale with it, which is the whole of what stereo changes about the plan.
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482pub struct Plan {
483    /// Which generation's units the stream is written in.
484    pub layout: Layout,
485    /// Channels interleaved into the stream: 1 or 2.
486    pub channels: usize,
487    /// Fields in the stream — the source plus a ring-out past its end, or, when the
488    /// stroke loops, the source up to the loop end plus the repeated lead.
489    pub fields: usize,
490    /// Field the resync record starts at.
491    pub resync_at: usize,
492    /// Fields in the opening 1:1 run.
493    pub warmup: usize,
494    /// Fields in the resync 1:1 run.
495    pub resync: usize,
496    /// Content cells between the warmup and the resync.
497    pub cells_before: usize,
498    /// Content cells between the resync and the loop start, or the terminator.
499    pub cells_after: usize,
500    /// The loop, once it is on the lattice.
501    pub looped: Option<Looped>,
502}
503
504impl Plan {
505    const fn units(&self) -> Units {
506        Units {
507            layout: self.layout,
508            channels: self.channels,
509        }
510    }
511
512    /// Fields one content cell covers — the generation's cell per channel.
513    pub const fn cell(&self) -> usize {
514        self.units().cell()
515    }
516
517    /// Fields one 1:1 record covers at most — the generation's RMAX per channel.
518    const fn chunk(&self) -> usize {
519        self.units().chunk()
520    }
521}
522
523/// Source frames onto the field lattice.
524fn fields_of(frames: usize) -> Option<usize> {
525    let frames = u64::try_from(frames).ok()?;
526    frames
527        .checked_mul(u64::from(PITCH_DEN))
528        .and_then(|n| round_ratio(n, u64::from(PITCH_NUM)))
529}
530
531/// The same lattice, for a landmark that falls between two frames — a fade a project
532/// states as a percentage of its loop rather than as a frame count. Rounding such a
533/// value to a whole frame before it reaches the lattice opens the ramp a field early.
534fn fields_at(frames: f64) -> Option<usize> {
535    let fields = frames * f64::from(PITCH_DEN) / f64::from(PITCH_NUM);
536    (fields.is_finite() && (0.0..=f64::from(u32::MAX)).contains(&fields))
537        .then_some(fields.round() as usize)
538}
539
540impl Plan {
541    /// The layout for `frames` source frames of `channels`-channel audio, no loop,
542    /// resynchronising at `secondary_start` source frames from the first — the
543    /// project's `m_startSecondary` measured from its `m_start`, or
544    /// [`default_secondary_start`] for audio no project describes.
545    ///
546    /// Refuses a secondary start the stream cannot resynchronise at: off the lattice,
547    /// or too close to either end for the 1:1 runs around it.
548    pub fn new(
549        layout: Layout,
550        frames: usize,
551        channels: usize,
552        secondary_start: f64,
553    ) -> Result<Plan, Error> {
554        Plan::modelled(frames, channels)?;
555        let fields = fields_of(frames)
556            .and_then(|f| f.checked_add(RING_OUT))
557            .and_then(|f| f.checked_mul(channels))
558            .ok_or_else(|| size_error(frames))?;
559        let resync_at = Plan::resync_at(secondary_start, channels)?;
560        Plan::lay_out(layout, frames, channels, fields, None, resync_at)
561    }
562
563    /// The layout for a stroke that loops: `frames` source samples truncated at
564    /// [`Loop::end`], with the loop's own opening repeated past it, resynchronising at
565    /// `secondary_start` as [`new`](Plan::new) does.
566    ///
567    /// The marked record sits [`LOOP_LEAD`] fields per channel past the loop start, or
568    /// [`min_resync_gap`] past the resync point when that is further: a loop starting
569    /// near the resync is pushed back, and the stream grows by what it is pushed.
570    ///
571    /// Refuses a loop the format cannot state — one outside the audio, one shorter than
572    /// the run it has to open with, or a crossfade with no material in front of the loop
573    /// to fade from — and a secondary start past the loop start, which a project's own
574    /// is repaired to never be.
575    pub fn looped(
576        layout: Layout,
577        frames: usize,
578        channels: usize,
579        points: Loop,
580        secondary_start: f64,
581    ) -> Result<Plan, Error> {
582        Plan::modelled(points.end, channels)?;
583        if points.start >= points.end || points.end > frames {
584            return Err(ParseError::OutOfBounds {
585                value: format!("a loop over frames {}..{}", points.start, points.end),
586                bound: format!("a non-empty region of the {frames} frames given"),
587            }
588            .into());
589        }
590        // Everything below is laid out per channel and scaled at the end, because that
591        // is what the encoder does: one plan, interleaved.
592        let lattice = |n: usize| fields_of(n).and_then(|f| f.checked_mul(channels));
593        let lattice_at = |n: f64| fields_at(n).and_then(|f| f.checked_mul(channels));
594        let start = lattice(points.start).ok_or_else(|| size_error(points.start))?;
595        // The loop's length is what has to survive, so it is put on the lattice as a
596        // length. Rounding its two ends separately can cost it a field.
597        let span = points.end - points.start;
598        let length = lattice(span).ok_or_else(|| size_error(points.end))?;
599        let end = start
600            .checked_add(length)
601            .ok_or_else(|| size_error(points.end))?;
602        let units = Units { layout, channels };
603        let (cell, chunk) = (units.cell(), units.chunk());
604        let resync_at = Plan::resync_at(secondary_start, channels)?;
605        if resync_at > start {
606            return Err(ParseError::OutOfBounds {
607                value: format!("a secondary start at field {resync_at}"),
608                bound: format!(
609                    "field {start}, where the loop starts, or earlier — the marked \
610                     record clears the resync point, so the loop cannot open ahead of it"
611                ),
612            }
613            .into());
614        }
615        // The mark clears the resync point by the generation's floor, so a loop that
616        // starts too near it is pushed back by repeating more of itself.
617        let at = start
618            .checked_add(LOOP_LEAD * channels)
619            .zip(resync_at.checked_add(min_resync_gap(layout) * channels))
620            .map(|(ideal, floor)| ideal.max(floor))
621            .ok_or_else(|| size_error(points.start))?;
622        let lead = at - start;
623        let fields = end
624            .checked_add(lead)
625            .ok_or_else(|| size_error(points.end))?;
626        let warmup = band(length, cell, chunk);
627        if length < warmup.saturating_add(cell) {
628            return Err(ParseError::OutOfBounds {
629                value: format!("a {length}-field loop"),
630                bound: format!(
631                    "a loop long enough for the {warmup}-field 1:1 run it opens with and \
632                     one {cell}-field cell after it"
633                ),
634            }
635            .into());
636        }
637        if !(0.0..=points.start as f64).contains(&points.crossfade) {
638            return Err(ParseError::OutOfBounds {
639                value: format!("a {} frame crossfade", points.crossfade),
640                bound: format!(
641                    "the {} frames before the loop starts — the fade compares \
642                     each frame with the material one loop length behind it",
643                    points.start,
644                ),
645            }
646            .into());
647        }
648        // Put the fade's opening on the loop-relative lattice. Above 100% it begins
649        // before the loop start, so its distance is added to the loop length.
650        let crossfade = if points.crossfade <= span as f64 {
651            let opens =
652                lattice_at(span as f64 - points.crossfade).ok_or_else(|| size_error(span))?;
653            length.checked_sub(opens).ok_or_else(|| size_error(span))?
654        } else {
655            let before = lattice_at(points.crossfade - span as f64)
656                .ok_or_else(|| size_error(points.start))?;
657            length
658                .checked_add(before)
659                .ok_or_else(|| size_error(points.end))?
660        };
661        if crossfade > start {
662            return Err(ParseError::OutOfBounds {
663                value: format!("a {} frame crossfade", points.crossfade),
664                bound: format!(
665                    "the {} frames before the loop starts — the field lattice \
666                     leaves no earlier material to compare",
667                    points.start,
668                ),
669            }
670            .into());
671        }
672        Plan::lay_out(
673            layout,
674            frames,
675            channels,
676            fields,
677            Some(Looped {
678                at,
679                lead,
680                crossfade,
681                warmup,
682                cells: (length - warmup) / cell,
683            }),
684            resync_at,
685        )
686    }
687
688    /// The secondary start on the lattice — a per-channel position, doubled like every
689    /// other landmark when the two channels interleave.
690    fn resync_at(secondary_start: f64, channels: usize) -> Result<usize, Error> {
691        fields_at(secondary_start)
692            .and_then(|f| f.checked_mul(channels))
693            .ok_or_else(|| {
694                ParseError::OutOfBounds {
695                    value: format!("a secondary start at frame {secondary_start}"),
696                    bound: "a position on the field lattice".into(),
697                }
698                .into()
699            })
700    }
701
702    fn modelled(frames: usize, channels: usize) -> Result<(), Error> {
703        if !(1..=MAX_CHANNELS).contains(&channels) {
704            return Err(ParseError::OutOfBounds {
705                value: format!("{channels} channels"),
706                bound: format!(
707                    "1 or {MAX_CHANNELS} — the terminator states one cell size, and all \
708                     it can say is whether the cell is doubled"
709                ),
710            }
711            .into());
712        }
713        if frames >= MIN_FRAMES {
714            return Ok(());
715        }
716        Err(ParseError::OutOfBounds {
717            value: format!("{frames} frames"),
718            bound: format!(
719                "the modelled range: at least {MIN_FRAMES} frames, below which the \
720                 stream opens a way this crate has not modelled"
721            ),
722        }
723        .into())
724    }
725
726    /// Place the warmup, the resync and the cells between them across everything ahead
727    /// of the loop — or across the whole stream when there is none.
728    fn lay_out(
729        layout: Layout,
730        frames: usize,
731        channels: usize,
732        fields: usize,
733        looped: Option<Looped>,
734        resync_at: usize,
735    ) -> Result<Plan, Error> {
736        let units = Units { layout, channels };
737        if fields > units.max_fields() {
738            return Err(size_error(frames).into());
739        }
740        let (cell, chunk) = (units.cell(), units.chunk());
741        let band = |r: usize| band(r, cell, chunk);
742        let head = looped.map_or(fields, |l| l.at);
743        let warmup = band(resync_at);
744        let fits = resync_at >= warmup
745            && head
746                .checked_sub(warmup)
747                .and_then(|rest| resync_at.checked_add(band(rest)))
748                .is_some_and(|end| head >= end);
749        if !fits {
750            return Err(ParseError::OutOfBounds {
751                value: format!("a secondary start at field {resync_at}"),
752                bound: format!(
753                    "the {head} fields ahead of the {}, less the 1:1 run at each end",
754                    if looped.is_some() {
755                        "loop"
756                    } else {
757                        "terminator"
758                    }
759                ),
760            }
761            .into());
762        }
763        let resync = band(head - warmup);
764        Ok(Plan {
765            layout,
766            channels,
767            fields,
768            resync_at,
769            warmup,
770            resync,
771            cells_before: (resync_at - warmup) / cell,
772            cells_after: (head - resync_at - resync) / cell,
773            looped,
774        })
775    }
776}
777
778/// Where a fresh project would put the resync in `frames` untrimmed source frames: the
779/// `m_startSecondary` [`nsmpproj::default_secondary_start`] states, repaired around
780/// `loops` the way the editor repairs a project it loads.
781pub fn default_secondary_start(frames: usize, loops: Option<Loop>) -> f64 {
782    let stop = frames as f64;
783    nsmpproj::repaired_secondary_start(
784        nsmpproj::default_secondary_start(stop),
785        stop,
786        loops.map(|l| nsmpproj::repaired_loop_start(l.start as f64)),
787    )
788}
789
790/// `round(num/den)`, half away from zero, on non-negative integers.
791fn round_ratio(num: u64, den: u64) -> Option<usize> {
792    num.checked_add(den / 2)
793        .and_then(|n| usize::try_from(n / den).ok())
794}
795
796/// Frames in interleaved PCM, refusing a buffer that is not whole frames.
797fn frames_of(source: &[i16], channels: usize) -> Result<usize, Error> {
798    if channels == 0 || !source.len().is_multiple_of(channels) {
799        return Err(ParseError::AssertFail(format!(
800            "{} sample(s) is not a whole number of {channels}-channel frames",
801            source.len()
802        ))
803        .into());
804    }
805    Ok(source.len() / channels)
806}
807
808fn size_error(frames: usize) -> ParseError {
809    ParseError::OutOfBounds {
810        value: format!("{frames} frames"),
811        bound: format!("audio whose encoded stream fits {MAX_STREAM_WORDS} words"),
812    }
813}
814
815/// The 1:1 run that preserves a landmark's cell phase — constructive, and the same
816/// statement at either channel count.
817///
818/// A run of `j` records covers between `j*cell` and `j*rmax` fields, so the reachable
819/// lengths come in windows with gaps between them: 24..=32, 48..=64, 72..=96 at the mono
820/// pair, and everything doubled at the stereo one. `band(r)` is the smallest reachable
821/// length at or above `cell` that is congruent to `r`, which at `r ≡ 0` is `cell` itself.
822fn band(r: usize, cell: usize, rmax: usize) -> usize {
823    let residue = if r.is_multiple_of(cell) {
824        cell
825    } else {
826        r % cell
827    };
828    let mut length = if residue == cell {
829        cell
830    } else {
831        residue + cell
832    };
833    // The windows overlap from `j = 3` at 24/32 and from `j = 2` at 32/48, so this
834    // settles within a few steps; the bound is a guard, not a limit anything reaches.
835    while length <= 64 * cell {
836        if (1..=8).any(|j| j * cell <= length && length <= j * rmax) {
837            return length;
838        }
839        length += cell;
840    }
841    length
842}
843
844/// Split a 1:1 run into records of at most `chunk` fields. [`band`] is what guarantees
845/// the remainder is a legal record rather than a stub.
846fn chunks(mut n: usize, chunk: usize) -> Vec<usize> {
847    let mut out = Vec::new();
848    while n > chunk {
849        out.push(chunk);
850        n -= chunk;
851    }
852    out.push(n);
853    out
854}
855
856/// The source on the lattice, quantised — the stream's field values and the two
857/// header statistics that describe them.
858#[derive(Debug, Clone)]
859struct Quantised {
860    /// One stored value per field, sign-extended and within the stream's maximum width.
861    values: Vec<i32>,
862    /// Bits the values were shifted right by. Dequantising shifts back.
863    shift: i32,
864    /// Statistic B: the content field of largest magnitude, taken at a fixed shift of 2.
865    /// Carries the extreme's sign where the generation stores one; a magnitude at v2.
866    peak: i32,
867}
868
869/// Largest magnitude statistic B's 24 bits hold once a sign is allowed for. A field
870/// is the source's own 16-bit unit taken at a shift of two, so nothing reaches it.
871const MAX_PEAK: i64 = (1 << 23) - 1;
872
873/// The opening ramp: the first [`RAMP_IN`] fields of a channel rise as the cube of
874/// their position, toward zero like everything else the encoder quantises.
875fn ramp_in(fields: &mut [i64]) {
876    let cube = |n: usize| (n * n * n) as i64;
877    for (f, value) in fields.iter_mut().enumerate().take(RAMP_IN) {
878        *value = *value * cube(f) / cube(RAMP_IN);
879    }
880}
881
882/// Ramp the loop's tail into the material one loop length behind it, then repeat the
883/// loop's opening past its end.
884///
885/// One channel at a time, so every count here is a per-channel one.
886///
887/// The ramp is linear across the crossfade, which is what the editor's own crossfade
888/// ladder measures out.
889///
890/// Inferred from specimens; not confirmed on hardware.
891fn bake_loop(raw: &mut [i64], at: usize, lead: usize, crossfade: usize) {
892    let fields = raw.len();
893    let end = fields - lead;
894    let length = fields - at;
895    let span = crossfade as i64;
896    for k in 0..crossfade {
897        let f = end - crossfade + k;
898        let (near, far) = (raw[f], raw[f - length]);
899        let step = (far - near) * k as i64;
900        raw[f] = near + (2 * step + span * step.signum()) / (2 * span);
901    }
902    // The repeated fields are the loop's own opening, so the loop plays the same region
903    // however far the mark clears its start.
904    for k in 0..lead {
905        raw[end + k] = raw[at - lead + k];
906    }
907}
908
909/// Resample and choose the smallest nonnegative shift that fits the stroke's peak into
910/// [`PEAK_WIDTH`] bits, plus the further bit a mono stroke spends when
911/// [`spends_extra_bit`] says so. `forced` lays the stroke out at that shift instead.
912///
913/// Each channel is resampled on its own lattice and the results interleaved, because
914/// that is what the stream carries; the shift and statistic B are one pair for the
915/// stroke, taken across both.
916fn quantise(source: &[i16], plan: &Plan, forced: Option<u8>) -> Quantised {
917    let channels = plan.channels;
918    let per = plan.fields / channels;
919    let mut raw = vec![0i64; plan.fields];
920    // The sums each field truncates from. Statistic B ranks fields on these, so two
921    // fields that truncate alike still order.
922    let mut sums = vec![0f64; plan.fields];
923    let mut lane: Vec<i16> = Vec::with_capacity(source.len().div_ceil(channels));
924    for channel in 0..channels {
925        lane.clear();
926        lane.extend(source.iter().skip(channel).step_by(channels).copied());
927        let accumulated: Vec<f64> = (0..per).map(|f| kernel::accumulate(&lane, f)).collect();
928        let mut fields: Vec<i64> = accumulated.iter().map(|sum| sum.trunc() as i64).collect();
929        ramp_in(&mut fields);
930        match &plan.looped {
931            Some(points) => bake_loop(
932                &mut fields,
933                points.at / channels,
934                points.lead / channels,
935                points.crossfade / channels,
936            ),
937            None => fields[per - RING_OUT..].fill(0),
938        }
939        for (f, (value, sum)) in fields.into_iter().zip(accumulated).enumerate() {
940            let at = f * channels + channel;
941            raw[at] = value;
942            // A field the ramp, the loop or the ring-out rewrote ranks by what it holds.
943            sums[at] = if value == sum.trunc() as i64 {
944                sum
945            } else {
946                value as f64
947            };
948        }
949    }
950    let mut shift = peak_shift(&raw, PEAK_WIDTH);
951    if spends_extra_bit(&raw, plan) {
952        shift += 1;
953    }
954    if let Some(bits) = forced {
955        shift = i32::from(bits);
956    }
957
958    // Statistic B is the content field of largest magnitude at a fixed shift of two —
959    // a negative extreme therefore rounds away from zero — and a later field takes the
960    // extreme only by exceeding it. Content only, which is why a value the 1:1 regime
961    // carries never sets it.
962    let opening = plan.looped.map(|l| l.at..l.at + l.warmup);
963    let content = |f: usize| {
964        ((f >= plan.warmup && f < plan.resync_at) || f >= plan.resync_at + plan.resync)
965            && !opening.as_ref().is_some_and(|run| run.contains(&f))
966    };
967    let extreme = (0..plan.fields)
968        .filter(|&f| content(f))
969        .fold(None, |best: Option<usize>, f| match best {
970            Some(b) if sums[f].abs() <= sums[b].abs() => Some(b),
971            _ => Some(f),
972        });
973    let signed = extreme
974        .map_or(0, |f| raw[f] >> 2)
975        .clamp(-MAX_PEAK - 1, MAX_PEAK) as i32;
976    let peak = match plan.layout.signed_peak() {
977        true => signed,
978        false => signed.abs(),
979    };
980
981    Quantised {
982        values: raw.iter().map(|&v| (v >> shift) as i32).collect(),
983        shift,
984        peak,
985    }
986}
987
988/// Bits a two's-complement field needs to hold everything in `low..=high`, floored at
989/// [`MIN_WIDTH`].
990fn width_of(low: i64, high: i64) -> u8 {
991    let mut w = MIN_WIDTH;
992    while i128::from(low) < -(1i128 << (w - 1)) || i128::from(high) > (1i128 << (w - 1)) - 1 {
993        w += 1;
994    }
995    w
996}
997
998/// One record, before it becomes words.
999#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1000struct Spec {
1001    one_to_one: bool,
1002    width: u8,
1003    order: u8,
1004    /// Set on the record a loop starts at, and on no other.
1005    mark: bool,
1006    first: usize,
1007    count: usize,
1008}
1009
1010impl Spec {
1011    /// Words this record occupies, header included.
1012    fn span(&self, units: Units) -> usize {
1013        units.span(self.count, self.width)
1014    }
1015}
1016
1017/// The Nth backward difference at `at`, across record boundaries.
1018///
1019/// ⚠️ **`stride` is the channel count**: the predictor runs per channel, so a stereo
1020/// field differences against the field two slots back, not the other channel's.
1021fn residual(values: &[i32], at: usize, order: u8, stride: usize) -> i64 {
1022    DIFFERENCE[usize::from(order)]
1023        .iter()
1024        .enumerate()
1025        .map(|(j, &c)| match at.checked_sub(j * stride) {
1026            Some(k) => i64::from(c) * i64::from(values[k]),
1027            None => 0,
1028        })
1029        .sum()
1030}
1031
1032/// The width one cell needs at `order`, and the sum of the residuals it would store.
1033/// One cell is `stride` channels' worth, and a record declares one width for both.
1034fn width_at(values: &[i32], first: usize, order: u8, cell: usize, stride: usize) -> u8 {
1035    let mut low = 0i64;
1036    let mut high = 0i64;
1037    for at in first..first + cell {
1038        let e = residual(values, at, order, stride);
1039        low = low.min(e);
1040        high = high.max(e);
1041    }
1042    width_of(low, high)
1043}
1044
1045/// The width each predictor order codes one cell at, indexed by order — order 0 alone
1046/// under [`Predictor::Plain`].
1047fn widths_at(
1048    values: &[i32],
1049    first: usize,
1050    predictor: Predictor,
1051    cell: usize,
1052    stride: usize,
1053) -> Vec<u8> {
1054    let orders = match predictor {
1055        Predictor::Plain => 1,
1056        Predictor::Minimising => DIFFERENCE.len(),
1057    };
1058    (0..orders as u8)
1059        .map(|order| width_at(values, first, order, cell, stride))
1060        .collect()
1061}
1062
1063/// The order and width a cell is coded at, given the widths each order needs: the
1064/// record being extended, `(order, width)`, keeps its order while the cell's narrowest
1065/// width is still the record's and that order still reaches it; otherwise the lowest
1066/// order that reaches the narrowest width.
1067fn choose_order(widths: &[u8], extending: Option<(u8, u8)>) -> (u8, u8) {
1068    let narrowest = *widths.iter().min().unwrap_or(&MIN_WIDTH);
1069    let reaches = |order: u8| widths.get(usize::from(order)) == Some(&narrowest);
1070    let order = extending
1071        .filter(|&(order, width)| width == narrowest && reaches(order))
1072        .map_or_else(
1073            || (0..widths.len() as u8).find(|&o| reaches(o)).unwrap_or(0),
1074            |(order, _)| order,
1075        );
1076    (order, narrowest)
1077}
1078
1079/// Partition 1:1 values and like-coded content cells into records, with the index of
1080/// the record the resync run opens at — what the header's second pointer names.
1081///
1082/// A loop appends a third regime — its own 1:1 run, marked, and the content after it —
1083/// grown to a whole number of packets by [`pad_to_packet`].
1084fn records(values: &[i32], plan: &Plan, predictor: Predictor) -> Result<(Vec<Spec>, usize), Error> {
1085    let mut out = Vec::new();
1086    let mut at = 0usize;
1087    let (cell, chunk, stride) = (plan.cell(), plan.chunk(), plan.channels);
1088
1089    let one_to_one = |out: &mut Vec<Spec>, at: &mut usize, fields: usize| {
1090        for count in chunks(fields, chunk) {
1091            let mut low = 0i64;
1092            let mut high = 0i64;
1093            for &v in &values[*at..*at + count] {
1094                low = low.min(i64::from(v));
1095                high = high.max(i64::from(v));
1096            }
1097            out.push(Spec {
1098                one_to_one: true,
1099                width: width_of(low, high),
1100                order: 0,
1101                mark: false,
1102                first: *at,
1103                count,
1104            });
1105            *at += count;
1106        }
1107    };
1108
1109    // A record runs on while each cell's narrowest width is still the record's and the
1110    // record's own order still reaches it; the first cell that breaks either opens a new
1111    // record at the lowest order that reaches its width.
1112    let content = |out: &mut Vec<Spec>, at: &mut usize, cells: usize| {
1113        let mut run: Option<Spec> = None;
1114        for index in 0..cells {
1115            let first = *at + index * cell;
1116            let widths = widths_at(values, first, predictor, cell, stride);
1117            let (order, width) = choose_order(&widths, run.map(|r| (r.order, r.width)));
1118            match run {
1119                Some(ref mut record)
1120                    if (record.order, record.width) == (order, width)
1121                        && record.count + cell <= MAX_COUNT =>
1122                {
1123                    record.count += cell;
1124                }
1125                _ => {
1126                    out.extend(run.take());
1127                    run = Some(Spec {
1128                        one_to_one: false,
1129                        width,
1130                        order,
1131                        mark: false,
1132                        first,
1133                        count: cell,
1134                    });
1135                }
1136            }
1137        }
1138        out.extend(run);
1139        *at += cells * cell;
1140    };
1141
1142    one_to_one(&mut out, &mut at, plan.warmup);
1143    content(&mut out, &mut at, plan.cells_before);
1144    let resync_record = out.len();
1145    one_to_one(&mut out, &mut at, plan.resync);
1146    content(&mut out, &mut at, plan.cells_after);
1147    if let Some(points) = &plan.looped {
1148        let opening = out.len();
1149        one_to_one(&mut out, &mut at, points.warmup);
1150        out[opening].mark = true;
1151        content(&mut out, &mut at, points.cells);
1152        pad_to_packet(&mut out, opening, plan.units())?;
1153    }
1154    if at != plan.fields {
1155        return Err(ParseError::AssertFail(format!(
1156            "the record plan covered {at} of {} fields",
1157            plan.fields
1158        ))
1159        .into());
1160    }
1161    Ok((out, resync_record))
1162}
1163
1164/// Pad the loop region out to whole packets: sweep its content records front to back,
1165/// halving each one that covers more than one cell — the smaller half first — and
1166/// carrying on into the second half, pass after pass, until the words fit.
1167///
1168/// A region with nothing left to split is widened instead, front to back, spending
1169/// each content record up to [`widen_cap`] before moving on, so the last one widened
1170/// takes only the words still owed. A 1:1 record is walked past by either sweep,
1171/// whatever room it has, the marked one the region opens at included.
1172///
1173/// Inferred from specimens; not confirmed on hardware.
1174fn pad_to_packet(specs: &mut Vec<Spec>, opening: usize, units: Units) -> Result<(), Error> {
1175    let cell = units.cell();
1176    let packet = units.packet_words();
1177    let words = |specs: &[Spec]| specs.iter().map(|s| s.span(units)).sum::<usize>();
1178    let mut pad = (packet - words(&specs[opening..]) % packet) % packet;
1179
1180    let splittable = |spec: &Spec| !spec.one_to_one && spec.count > cell;
1181    while pad > 0 && specs[opening..].iter().any(splittable) {
1182        let mut at = opening;
1183        while pad > 0 && at < specs.len() {
1184            let spec = specs[at];
1185            if splittable(&spec) {
1186                let head = spec.count / cell / 2 * cell;
1187                specs[at].count = head;
1188                specs.insert(
1189                    at + 1,
1190                    Spec {
1191                        first: spec.first + head,
1192                        count: spec.count - head,
1193                        ..spec
1194                    },
1195                );
1196                pad -= 1;
1197            }
1198            at += 1;
1199        }
1200    }
1201
1202    let cap = widen_cap(units.layout);
1203    for spec in specs[opening..].iter_mut() {
1204        if pad == 0 {
1205            break;
1206        }
1207        if spec.one_to_one {
1208            continue;
1209        }
1210        let count = spec.count;
1211        let step = |width: u8| units.span(count, width + 1) - units.span(count, width);
1212        while spec.width < cap && step(spec.width) <= pad {
1213            pad -= step(spec.width);
1214            spec.width += 1;
1215        }
1216    }
1217    if pad > 0 {
1218        return Err(ParseError::OutOfBounds {
1219            value: format!("a loop of {} record(s)", specs.len() - opening),
1220            bound: format!(
1221                "a loop with {pad} more word(s) of room in it — the encoded loop has to \
1222                 be whole packets long, and no record of this one may be widened past \
1223                 {cap}"
1224            ),
1225        }
1226        .into());
1227    }
1228    Ok(())
1229}
1230
1231/// Widest the padding sweep writes a content record at, per generation. It is the
1232/// generation's own constant and not a property of the region: a record already one
1233/// width under the cap is still widened past itself, up to the cap, and a record
1234/// holding room under the cap is never left unspent.
1235///
1236/// v4 stops one width above the narrow chain and v3, so this is a table rather than a
1237/// constant. Nothing derives one entry from another.
1238///
1239/// Inferred from specimens; not confirmed on hardware.
1240const fn widen_cap(layout: Layout) -> u8 {
1241    match layout {
1242        Layout::V2 | Layout::V3 => 13,
1243        Layout::V4 => 14,
1244    }
1245}
1246
1247/// A packed stroke stream: the words, and where the header's directory points.
1248struct Stream {
1249    words: Vec<u8>,
1250    first_record: usize,
1251    resync: usize,
1252    /// The marked record a loop starts at, when the stroke loops.
1253    mark: Option<usize>,
1254    terminator: usize,
1255}
1256
1257/// Right-align records in the allocation the preamble law gives this stroke:
1258/// `preamble` bytes of payload, then whole packets until the chain fits.
1259///
1260/// `preamble` is [`stroke::header_len`](super::stroke::header_len), which a zone table
1261/// can drive below the stroke header — the first packet then starts inside what would
1262/// otherwise be header, and the loop repays the difference.
1263fn pack(
1264    specs: &[Spec],
1265    values: &[i32],
1266    resync_record: usize,
1267    preamble: usize,
1268    plan: &Plan,
1269) -> Result<Stream, Error> {
1270    let units = plan.units();
1271    let (word, header) = (units.word(), plan.layout.header_len());
1272    let chain: usize = specs.iter().map(|s| s.span(units)).sum::<usize>() + 1;
1273    let need = (chain + units.min_lead())
1274        .checked_mul(word)
1275        .and_then(|bytes| bytes.checked_add(header))
1276        .ok_or_else(|| ParseError::OutOfBounds {
1277            value: format!("a chain of {chain} words"),
1278            bound: "a stroke payload of addressable length".into(),
1279        })?;
1280    let mut payload = preamble;
1281    while payload < need {
1282        payload += packet_len(plan.layout);
1283    }
1284    if !(payload - header).is_multiple_of(word) {
1285        return Err(ParseError::AssertFail(format!(
1286            "a {preamble}-byte preamble puts the word stream off a word boundary; the \
1287             sections in front of the stroke are not whole words"
1288        ))
1289        .into());
1290    }
1291    let total = (payload - header) / word;
1292    if total > MAX_STREAM_WORDS {
1293        return Err(ParseError::OutOfBounds {
1294            value: format!("a stream of {total} words"),
1295            bound: format!(
1296                "{MAX_STREAM_WORDS} words, the reach of the stroke header's 16-bit word \
1297                 directory"
1298            ),
1299        }
1300        .into());
1301    }
1302
1303    let mut words = vec![0u8; total * word];
1304    let lead = total - chain;
1305    let mut at = lead;
1306    let mut resync = lead;
1307    let mut mark = None;
1308    for (index, spec) in specs.iter().enumerate() {
1309        if index == resync_record {
1310            resync = at;
1311        }
1312        if spec.mark {
1313            mark = Some(at);
1314        }
1315        write_record(&mut words, at, spec, values, units);
1316        at += spec.span(units);
1317    }
1318    // The terminator states the cell size, which is what says how many channels the
1319    // stroke carries: twice the layout's cell and a reader de-interleaves.
1320    if at.checked_add(1) != Some(total) {
1321        return Err(ParseError::AssertFail(format!(
1322            "the record chain ended at word {at} of {total}"
1323        ))
1324        .into());
1325    }
1326    let terminator = (1u32 << 23) | plan.cell() as u32;
1327    words[at * word..(at + 1) * word].copy_from_slice(&terminator.to_be_bytes()[4 - word..]);
1328
1329    Ok(Stream {
1330        words,
1331        first_record: lead,
1332        resync,
1333        mark,
1334        terminator: at,
1335    })
1336}
1337
1338/// Writes one record: its header word, then its fields, which start at the first bit
1339/// after it. Any alignment tail is left zero at the end of the segment.
1340///
1341/// v2 and v3 store a stereo stroke's channels as **alternating fields**, which is the
1342/// order `values` is already in, so the fields go down in stream order; v4 gives each
1343/// channel its own word stream and alternates the words, so its halves are packed
1344/// apart and then interleaved. Only the residual's reach moves with the channel count.
1345fn write_record(words: &mut [u8], at: usize, spec: &Spec, values: &[i32], units: Units) {
1346    let (word, bits) = (units.word(), units.word_bits());
1347    let head = (u32::from(spec.one_to_one) << 23)
1348        | (u32::from(spec.width - 1) << 19)
1349        | (u32::from(spec.mark) << 18)
1350        | (u32::from(spec.order) << 14)
1351        | spec.count as u32;
1352    words[at * word..(at + 1) * word].copy_from_slice(&head.to_be_bytes()[4 - word..]);
1353
1354    let stored = |k: usize| -> u64 {
1355        let value = residual(values, spec.first + k, spec.order, units.channels);
1356        (value as u64) & ((1u64 << spec.width) - 1)
1357    };
1358    let put = |words: &mut [u8], mut bit: usize, raw: u64| {
1359        for b in (0..spec.width).rev() {
1360            if raw >> b & 1 != 0 {
1361                words[bit / 8] |= 1 << (7 - bit % 8);
1362            }
1363            bit += 1;
1364        }
1365    };
1366
1367    if !units.splits() {
1368        for k in 0..spec.count {
1369            put(
1370                words,
1371                (at + 1) * bits + k * usize::from(spec.width),
1372                stored(k),
1373            );
1374        }
1375        return;
1376    }
1377    // Each channel is packed into its own contiguous words first, because the two
1378    // halves are padded apart; the words then alternate from the header on.
1379    let per = spec.count / 2;
1380    let half = units.half(spec.count, spec.width);
1381    let mut packed = vec![0u8; half * word];
1382    for channel in 0..2 {
1383        packed.fill(0);
1384        for k in 0..per {
1385            put(
1386                &mut packed,
1387                k * usize::from(spec.width),
1388                stored(2 * k + channel),
1389            );
1390        }
1391        for w in 0..half {
1392            let to = (at + 1 + 2 * w + channel) * word;
1393            words[to..to + word].copy_from_slice(&packed[w * word..(w + 1) * word]);
1394        }
1395    }
1396}
1397
1398/// Encode `A = gain · 2^(41+s)/peak` as `(mantissa, exponent)`: the exponent carries the
1399/// quantiser shift, the mantissa is `1/peak` to 20 bits scaled by the zone's gain
1400/// ([`zone::GAIN_UNITY`](super::zone::GAIN_UNITY) is 1.0). The reciprocal is held as a
1401/// 24-bit fraction in `[½, 1)` — three bits finer than the mantissa — before the gain
1402/// multiplies it, and one floor follows; the mantissa leaves its normalised range
1403/// freely in either direction, and the exponent never moves with it.
1404///
1405/// ⚠️ **`gain` is the decibel field's round trip, not the project's own float.** The
1406/// two agree below `2^24` and part above it, where the mantissa wraps into its field
1407/// and the file states a level far quieter than the project asked for. That is what
1408/// the instrument plays; a caller that means to warn about it owns the warning.
1409///
1410/// ⚠️ **`peak` is the file's, not the stroke's.** Every stroke of a multi-zone
1411/// instrument reciprocates the largest statistic B in the file; only the shift and the
1412/// zone's own gain are the stroke's. Reciprocating each stroke's own peak instead
1413/// leaves every zone but the loudest playing at the wrong level.
1414fn statistic_a(peak: u32, shift: i32, gain: u64) -> (u32, u8) {
1415    let peak = u64::from(peak.max(1));
1416    let bits = 64 - peak.leading_zeros() as i32;
1417    let exact_power = i32::from(peak.is_power_of_two());
1418    let reciprocal = (1u64 << (21 + bits + (1 - exact_power))) / peak;
1419    let mantissa = (reciprocal * gain) >> (super::zone::GAIN_BITS + 3);
1420    (
1421        (mantissa % (1 << 24)) as u32,
1422        (22 + shift - bits + exact_power) as u8,
1423    )
1424}
1425
1426/// Build the fixed header and its body-relative, wrapping word directory.
1427fn stroke_header(
1428    layout: Layout,
1429    zone: &NewZone<'_>,
1430    encoded: &Encoded,
1431    body_at: usize,
1432    file_peak: u32,
1433) -> Vec<u8> {
1434    let (q, stream) = (&encoded.q, &encoded.stream);
1435    let mut head = vec![0u8; layout.header_len()];
1436    head[0..4].copy_from_slice(&zone.global_id.to_be_bytes());
1437    head[super::stroke::ROOT_KEY] = zone.root_key;
1438    // Unexplained: real programs hold this, and the panel cannot produce it.
1439    head[6..8].copy_from_slice(&[0x88, 0xba]);
1440    // The channel count, stated a second time — the terminator's cell size says it too,
1441    // and a reader takes the terminator because that is what the record sizes follow.
1442    head[8] = zone.channels as u8;
1443
1444    let (mantissa, exponent) =
1445        statistic_a(file_peak, q.shift, gain_units(gain_decibels(zone.gain)));
1446    head[codec::MANTISSA_AT..codec::MANTISSA_AT + 3].copy_from_slice(&mantissa.to_be_bytes()[1..]);
1447    head[codec::STAT_A_EXP_AT] = exponent;
1448    head[codec::PEAK_AT..codec::PEAK_AT + 3].copy_from_slice(&(q.peak as u32).to_be_bytes()[1..]);
1449
1450    let base = (body_at + layout.header_len()) / layout.word() % WRAP;
1451    let pointer = |word: usize| ((base + word) % WRAP) as u16;
1452    // The third pointer names the loop's marked record; aimed at the terminator it says
1453    // the stroke does not loop.
1454    let directory = [
1455        pointer(stream.first_record),
1456        pointer(stream.resync),
1457        pointer(stream.mark.unwrap_or(stream.terminator)),
1458        pointer(stream.terminator),
1459    ];
1460    for (i, p) in directory.iter().enumerate() {
1461        let at = codec::SEEK_AT + codec::SEEK_STRIDE * i;
1462        head[at..at + 2].copy_from_slice(&p.to_be_bytes());
1463        // Unexplained: real programs hold this, and the panel cannot produce it.
1464        if i < 3 {
1465            head[at + 2] = 0x80;
1466        }
1467    }
1468    // The wide header's two float32 tails; the narrow header is too short to hold them.
1469    let tails = [gain_decibels(zone.gain), zone.loop_decay];
1470    for (at, value) in codec::TAIL_FLOATS_AT.iter().zip(tails) {
1471        if let Some(slot) = head.get_mut(*at..at + 4) {
1472            slot.copy_from_slice(&value.to_be_bytes());
1473        }
1474    }
1475    head
1476}
1477
1478/// The loop decay amount a project carries until something sets one.
1479pub const DEFAULT_LOOP_DECAY: f32 = 20.0;
1480
1481/// One zone's stream, and the quantiser statistics describing it.
1482///
1483/// A stroke header cannot be written until every zone is here: statistic A
1484/// reciprocates the file's peak, so the last zone's audio decides the first zone's
1485/// header.
1486struct Encoded {
1487    q: Quantised,
1488    stream: Stream,
1489}
1490
1491/// Lay out and pack one zone's stream, into `preamble` bytes plus whole packets.
1492fn encode_stroke(
1493    layout: Layout,
1494    zone: &NewZone<'_>,
1495    preamble: usize,
1496    predictor: Predictor,
1497) -> Result<Encoded, Error> {
1498    let channels = usize::from(zone.channels);
1499    let frames = frames_of(zone.source, channels)?;
1500    let plan = match zone.loops {
1501        Some(points) => Plan::looped(layout, frames, channels, points, zone.secondary_start)?,
1502        None => Plan::new(layout, frames, channels, zone.secondary_start)?,
1503    };
1504    if let Some(bits) = zone.shift {
1505        if i32::from(bits) > codec::SHIFT_LIMIT {
1506            return Err(ParseError::OutOfBounds {
1507                value: format!("a quantiser shift of {bits} bits"),
1508                bound: format!("0 through {} bits", codec::SHIFT_LIMIT),
1509            }
1510            .into());
1511        }
1512    }
1513    let q = quantise(zone.source, &plan, zone.shift);
1514    let low = q.values.iter().copied().min().unwrap_or(0);
1515    let high = q.values.iter().copied().max().unwrap_or(0);
1516    if width_of(i64::from(low), i64::from(high)) > MAX_STORED_WIDTH {
1517        return Err(ParseError::OutOfBounds {
1518            value: format!(
1519                "a quantiser shift of {} bits for fields spanning {low}..={high}",
1520                q.shift
1521            ),
1522            bound: format!("values that fit the stream's {MAX_STORED_WIDTH}-bit fields"),
1523        }
1524        .into());
1525    }
1526    let (specs, resync_record) = records(&q.values, &plan, predictor)?;
1527    let stream = pack(&specs, &q.values, resync_record, preamble, &plan)?;
1528    Ok(Encoded { q, stream })
1529}
1530
1531/// Every zone's stream in order, and the peak each of their headers reciprocates.
1532fn encode_strokes(
1533    layout: Layout,
1534    zones: &[NewZone<'_>],
1535    predictor: Predictor,
1536    cat_len: usize,
1537    map_len: usize,
1538) -> Result<(Vec<Encoded>, u32), Error> {
1539    let encoded = zones
1540        .iter()
1541        .enumerate()
1542        .map(|(index, zone)| {
1543            let chain = super::Chain::written_for(layout);
1544            let preamble = super::stroke::header_len(layout, chain, index, cat_len, map_len);
1545            encode_stroke(layout, zone, preamble, predictor)
1546        })
1547        .collect::<Result<Vec<_>, Error>>()?;
1548    let peak = encoded
1549        .iter()
1550        .map(|e| e.q.peak.unsigned_abs())
1551        .max()
1552        .unwrap_or(1);
1553    Ok((encoded, peak))
1554}
1555
1556/// One zone's `stk` payload at body offset `body_at`.
1557///
1558/// `body_at` comes from the sections already sized in front of this stroke, so only
1559/// the chain builders can supply it: it is the base the word directory is written
1560/// against, and a wrong one produces a file whose directory names records that are
1561/// not there.
1562fn stroke_payload(
1563    layout: Layout,
1564    zone: &NewZone<'_>,
1565    encoded: &Encoded,
1566    body_at: usize,
1567    file_peak: u32,
1568) -> Result<Vec<u8>, Error> {
1569    midi_note("root key", zone.root_key)?;
1570    body_at
1571        .checked_add(layout.header_len())
1572        .ok_or_else(|| ParseError::OutOfBounds {
1573            value: format!("body offset {body_at}"),
1574            bound: "an addressable stroke header".into(),
1575        })?;
1576    let mut payload = stroke_header(layout, zone, encoded, body_at, file_peak);
1577    payload.extend_from_slice(&encoded.stream.words);
1578    Ok(payload)
1579}
1580
1581/// Section schema versions the narrow chain writes. They track the section's own
1582/// schema rather than the content version.
1583const HDR_VERSION: u8 = 9;
1584const CAT_VERSION: u8 = 5;
1585const STK_VERSION: u8 = 9;
1586const STY_VERSION: u8 = 5;
1587const CONTAINER_VERSION: u8 = 11;
1588
1589/// The category every chain's `cat` section opens with.
1590/// Unexplained: real programs hold this, and the panel cannot produce it.
1591const CATEGORY: u8 = 0x0f;
1592
1593/// The `hdr` section: a fixed prefix, then the instrument name NUL-padded.
1594fn hdr(name: &str) -> Result<Section, Error> {
1595    let mut payload = vec![0u8; 111];
1596    // Unexplained: real programs hold this, and the panel cannot produce it.
1597    payload[0..6].copy_from_slice(&[0x00, 0x01, 0xb4, 0x00, 0x06, 0x50]);
1598    super::StringField::NAME.write(&mut payload, name)?;
1599    Ok(Section {
1600        tag: *section::HDR,
1601        version: HDR_VERSION,
1602        payload,
1603    })
1604}
1605
1606/// The `cat` section: a short prefix and two length-prefixed labels.
1607fn cat() -> Section {
1608    let mut payload = vec![CATEGORY, 0x00, 0x00, 0x00, 0x01];
1609    for label in [&b"Production"[..], &b"Origin"[..]] {
1610        payload.push(label.len() as u8);
1611        payload.extend_from_slice(label);
1612    }
1613    // Every section payload is a whole number of 24-bit words; the labels are
1614    // padded out to one.
1615    while !payload.len().is_multiple_of(3) {
1616        payload.push(0);
1617    }
1618    Section {
1619        tag: *section::CAT,
1620        version: CAT_VERSION,
1621        payload,
1622    }
1623}
1624
1625/// Build a neutral keyboard map — unity gain and no detune at every key —
1626/// and the zone table behind it.
1627///
1628/// `zones` is one record per zone, already high to low.
1629fn map(map_gain: u32, zones: &[ZoneRecord]) -> Result<Section, Error> {
1630    let mut payload = vec![0u8; super::zone::RECORDS_AT + super::zone::RECORD_LEN * zones.len()];
1631    let mut keys = super::keymap::KeyTable::NEUTRAL;
1632    keys.instrument = super::keymap::Level::new(map_gain, 0)?;
1633    payload[..super::zone::COUNT_AT].copy_from_slice(&keys.prefix());
1634    payload[super::zone::COUNT_AT] = zones.len() as u8;
1635    // Zones are stored high to low by top note.
1636    for (index, record) in zones.iter().enumerate() {
1637        let at = super::zone::RECORDS_AT + super::zone::RECORD_LEN * index;
1638        payload[at + 2] = record.id;
1639        // Nothing here says whether the zone loops: a zone record is byte-identical
1640        // either way, and the loop lives in the stroke's own word directory.
1641        payload[at + 3..at + 6].copy_from_slice(&record.gain.to_be_bytes()[1..]);
1642        payload[at + 9] = record.top_note;
1643        // One sample in the zone, so the playing stroke sits at the bottom of the
1644        // strength axis. A stack positions its enabled stroke higher; nothing the
1645        // builder produces has one.
1646        payload[at + 10..at + 12].copy_from_slice(&super::zone::REL_STRENGTH_DEFAULT.to_be_bytes());
1647    }
1648    Ok(Section {
1649        tag: *section::MAP,
1650        version: super::keymap::VERSION,
1651        payload,
1652    })
1653}
1654
1655/// The narrow `sty` preset, including every project value its schema stores.
1656fn sty(preset: Preset) -> Result<Section, Error> {
1657    if preset.velocity_to_amplitude >= super::sty::VELOCITY_LEVELS
1658        || preset.velocity_to_timbre >= super::sty::VELOCITY_LEVELS
1659    {
1660        return Err(ParseError::OutOfBounds {
1661            value: format!(
1662                "velocity levels {} and {}",
1663                preset.velocity_to_amplitude, preset.velocity_to_timbre
1664            ),
1665            bound: format!("levels below {}", super::sty::VELOCITY_LEVELS),
1666        }
1667        .into());
1668    }
1669    let mut payload = vec![0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00];
1670    payload[3] = u8::from(preset.dynamics_enabled);
1671    payload[4] = preset.velocity_to_amplitude;
1672    payload[5] = preset.velocity_to_timbre;
1673    Ok(Section {
1674        tag: *section::STY,
1675        version: STY_VERSION,
1676        payload,
1677    })
1678}
1679
1680/// Everything a `.nsmp3` chain and a `.nsmp4` chain do not share.
1681///
1682/// The section versions track their own schemas, so they move independently of the
1683/// content version and of each other. The payloads named here are constant across
1684/// every render of a project that does not reach them.
1685///
1686/// Inferred from specimens; not confirmed on hardware.
1687struct WideSchema {
1688    container: u32,
1689    /// The `NSMP` payload. Constant per generation and unrelated to the stroke count.
1690    /// Unexplained: real programs hold this, and the panel cannot produce it.
1691    container_payload: [u8; 4],
1692    hdr: u32,
1693    map: u32,
1694    /// Bytes one per-key record takes: the level alone, or the level and the partner
1695    /// quad the wider schema puts behind it.
1696    key_stride: usize,
1697    /// The unexplained run between the per-key table and the zone count.
1698    map_gap: &'static [u8],
1699    /// The unexplained run behind the last zone record.
1700    map_tail: &'static [u8],
1701    sty: u32,
1702    /// The preset a project that touches none renders as.
1703    /// Unexplained: real programs hold this, and the panel cannot produce it.
1704    sty_payload: &'static [u8],
1705    /// Where the category's dynamics curve writes into that payload, and what.
1706    sty_dynamics: &'static [(usize, u8)],
1707}
1708
1709/// One gain-and-detune unit at `gain`, with no detune. It opens the `map` section as
1710/// the instrument's own level and then repeats once per key.
1711fn level(gain: u32) -> [u8; super::keymap::RECORD_LEN] {
1712    let mut out = [0u8; super::keymap::RECORD_LEN];
1713    out[..3].copy_from_slice(&gain.to_be_bytes()[1..]);
1714    out
1715}
1716
1717const STY_V3_PAYLOAD: [u8; super::sty::V3_LEN] = [
1718    0x00, 0x00, 0x7f, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x02, 0x00,
1719    0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
1720];
1721
1722const STY_V4_PAYLOAD: [u8; super::sty::V4_LEN_LONG] = [
1723    0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1724    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1725    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1726    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1727    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e,
1728    0x1e, 0x1e, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
1729    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1730];
1731
1732const STY_V3_DYNAMICS: [(usize, u8); 4] = [(4, 43), (12, 74), (14, 1), (16, 74)];
1733const STY_V4_DYNAMICS: [(usize, u8); 5] = [(3, 1), (4, 1), (85, 74), (86, 82), (87, 90)];
1734
1735/// The schema for a wide generation, `None` for the narrow chain.
1736fn wide_schema(layout: Layout) -> Option<WideSchema> {
1737    match layout {
1738        Layout::V2 => None,
1739        Layout::V3 => Some(WideSchema {
1740            container: 30,
1741            container_payload: [0x00, 0x02, 0x00, 0x0c],
1742            hdr: 10,
1743            map: 14,
1744            key_stride: super::keymap::RECORD_LEN,
1745            map_gap: &[],
1746            map_tail: &[0x00],
1747            sty: super::sty::VERSION_V3,
1748            sty_payload: &STY_V3_PAYLOAD,
1749            sty_dynamics: &STY_V3_DYNAMICS,
1750        }),
1751        Layout::V4 => Some(WideSchema {
1752            container: 40,
1753            container_payload: [0x00, 0x02, 0x00, 0x05],
1754            hdr: 11,
1755            map: 21,
1756            key_stride: super::keymap::RECORD_LEN + 4,
1757            map_gap: &[
1758                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
1759                0x02, 0x02, 0x02, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00,
1760                0x00, 0x00, 0x00,
1761            ],
1762            map_tail: &[0x00, 0x00, 0x00, 0x01, 0x00, 0x00],
1763            sty: super::sty::VERSION_V4,
1764            sty_payload: &STY_V4_PAYLOAD,
1765            sty_dynamics: &STY_V4_DYNAMICS,
1766        }),
1767    }
1768}
1769
1770/// The wide `hdr` section: the same prefix at a wider name field, with the sub-name
1771/// the vendor's filenames append left empty.
1772fn hdr4(schema: &WideSchema, name: &str) -> Result<Section4, Error> {
1773    let mut payload = vec![0u8; 112];
1774    // Unexplained: real programs hold this, and the panel cannot produce it.
1775    payload[4..6].copy_from_slice(&[0x06, 0x50]);
1776    super::StringField::NAME_V3.write(&mut payload, name)?;
1777    Ok(Section4 {
1778        tag: *section::HDR4,
1779        version: schema.hdr,
1780        payload,
1781    })
1782}
1783
1784/// The wide `cat` section: the category alone, where the narrow chain also spells
1785/// out its labels.
1786fn cat4() -> Section4 {
1787    let mut payload = vec![0u8; 8];
1788    payload[0] = CATEGORY;
1789    Section4 {
1790        tag: *section::CAT4,
1791        version: 7,
1792        payload,
1793    }
1794}
1795
1796/// The wide `map` section: a per-key table at unity gain and no detune, then the
1797/// zone records behind their count.
1798///
1799/// The wider schema's per-key record carries a partner quad as well as the level.
1800/// The editor writes the identity there whatever the zone layout — only the vendor's
1801/// own builder fills it in — so every quad names its own key.
1802fn map4(schema: &WideSchema, map_gain: u32, zones: &[WideZoneRecord]) -> Section4 {
1803    let mut payload = Vec::with_capacity(
1804        super::keymap::RECORD_LEN
1805            + super::keymap::KEYS * schema.key_stride
1806            + schema.map_gap.len()
1807            + 1
1808            + super::zone::WIDE_RECORD_LEN * zones.len()
1809            + schema.map_tail.len(),
1810    );
1811    payload.extend_from_slice(&level(map_gain));
1812    for key in 0..super::keymap::KEYS as u8 {
1813        payload.extend_from_slice(&level(super::zone::GAIN_UNITY));
1814        payload.extend(std::iter::repeat_n(
1815            key,
1816            schema.key_stride - super::keymap::RECORD_LEN,
1817        ));
1818    }
1819    payload.extend_from_slice(schema.map_gap);
1820    payload.push(zones.len() as u8);
1821    for record in zones {
1822        payload.extend_from_slice(&record.bytes());
1823    }
1824    payload.extend_from_slice(schema.map_tail);
1825    Section4 {
1826        tag: *section::MAP4,
1827        version: schema.map,
1828        payload,
1829    }
1830}
1831
1832/// The wide `sty` preset, including the dynamics group a project controls.
1833fn sty4(schema: &WideSchema, preset: Preset) -> Section4 {
1834    let mut payload = schema.sty_payload.to_vec();
1835    if preset.dynamics_enabled {
1836        for &(at, value) in schema.sty_dynamics {
1837            payload[at] = value;
1838        }
1839    }
1840    Section4 {
1841        tag: *section::STY4,
1842        version: schema.sty,
1843        payload,
1844    }
1845}
1846
1847/// The `meta` section: the length of everything ahead of it, which is the only place
1848/// a wide file states its own size.
1849fn meta4(chain_len: usize) -> Section4 {
1850    let mut payload = vec![0u8; super::meta::LEN];
1851    payload[0..2].copy_from_slice(&2u16.to_be_bytes());
1852    payload[2..6].copy_from_slice(&(chain_len as u32).to_be_bytes());
1853    Section4 {
1854        tag: *section::META4,
1855        version: super::meta::VERSION,
1856        payload,
1857    }
1858}
1859
1860/// One zone to build: its audio, where it sits on the keyboard, and the id its
1861/// record names its stroke by.
1862#[derive(Debug, Clone, Copy, PartialEq)]
1863pub struct NewZone<'a> {
1864    /// PCM at [`codec::SOURCE_RATE`], already trimmed to what the zone plays and
1865    /// **interleaved** when it has more than one channel.
1866    pub source: &'a [i16],
1867    /// Channels [`source`](NewZone::source) interleaves: 1 or 2.
1868    pub channels: u16,
1869    /// The note this sample plays untransposed at.
1870    pub root_key: u8,
1871    /// Highest note this zone answers to. Stored as given — the file keeps top notes,
1872    /// it does not derive them from the root keys.
1873    pub top_note: u8,
1874    /// The stroke's global id, 1 through [`MAX_STROKE_ID`]. Zones name their strokes
1875    /// by it rather than by position, so it need not run parallel to the sections.
1876    pub global_id: u32,
1877    /// The zone's sustain loop, which truncates its audio at [`Loop::end`].
1878    pub loops: Option<Loop>,
1879    /// Where the stream resynchronises: the project's `m_startSecondary` in source
1880    /// frames from the first frame of [`source`](NewZone::source), after the repair
1881    /// the editor applies on load ([`nsmpproj::Stroke::encoded_secondary_start`]).
1882    pub secondary_start: f64,
1883    /// Quantiser shift to lay the stroke out at instead of the rule's choice, or `None`
1884    /// for the rule. Experimental — see [`Options::shift`].
1885    pub shift: Option<u8>,
1886    /// The stroke's loop decay amount — a project's `m_loopDecay` — in the project's
1887    /// own units, [`DEFAULT_LOOP_DECAY`] until something sets one.
1888    ///
1889    /// ⚠️ A wide stroke header carries it whether or not the stroke loops and whether
1890    /// or not the decay is switched on; nothing in the file says which. The narrow
1891    /// chain drops the field altogether.
1892    pub loop_decay: f32,
1893    /// Playback gain as a linear ratio, 1.0 for unity, below [`MAX_ZONE_GAIN`]. Not
1894    /// applied to the audio: the instrument applies it when it plays.
1895    ///
1896    /// Where it is stored moves with the generation, and the stroke's statistic A
1897    /// carries it in every one. The narrow zone record holds it linearly to 20
1898    /// fractional bits; a wide stroke header holds `20·log10(gain)` as a float32 and
1899    /// no byte of a wide zone record moves with it.
1900    pub gain: f64,
1901}
1902
1903/// Build a one-zone instrument from PCM at [`codec::SOURCE_RATE`], mono or stereo
1904/// interleaved per [`Options::channels`], in the generation [`Options::layout`] names.
1905/// Refuses unmodelled lengths, invalid metadata, and streams past the directory limit.
1906pub fn instrument(source: &[i16], options: &Options) -> Result<crate::Sample, Error> {
1907    midi_note("root key", options.root_key)?;
1908    let frames = frames_of(source, usize::from(options.channels))?;
1909    let secondary_start = options
1910        .secondary_start
1911        .unwrap_or_else(|| default_secondary_start(frames, options.loops));
1912    multi_zone(
1913        Instrument {
1914            name: &options.name,
1915            map_gain: 1.0,
1916            predictor: options.predictor,
1917            layout: options.layout,
1918            preset: Preset::default(),
1919        },
1920        &[NewZone {
1921            source,
1922            channels: options.channels,
1923            root_key: options.root_key,
1924            top_note: options.resolved_top_note(),
1925            global_id: 1,
1926            loops: options.loops,
1927            secondary_start,
1928            shift: options.shift,
1929            gain: 1.0,
1930            loop_decay: DEFAULT_LOOP_DECAY,
1931        }],
1932    )
1933}
1934
1935/// Everything an instrument states apart from its zones.
1936#[derive(Debug, Clone, Copy)]
1937pub struct Instrument<'a> {
1938    /// The name the `hdr` section carries.
1939    pub name: &'a str,
1940    /// The instrument's own playing gain, a linear ratio on top of every zone's. It
1941    /// opens the `map` section in all three generations, and it is the one gain field
1942    /// that clamps: [`MAX_MAP_GAIN_DB`] and no higher, whatever the caller asks for.
1943    pub map_gain: f64,
1944    /// How content records code their fields.
1945    pub predictor: Predictor,
1946    /// Which generation to write: `.nsmp`, `.nsmp3` or `.nsmp4`.
1947    pub layout: Layout,
1948    /// The sound preset values a project can carry into the instrument.
1949    pub preset: Preset,
1950}
1951
1952/// Project preset values with a decoded destination in at least one generation.
1953#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1954pub struct Preset {
1955    /// Whether the instrument loads with its category's dynamics curve.
1956    pub dynamics_enabled: bool,
1957    /// The narrow preset's velocity-to-amplitude level.
1958    pub velocity_to_amplitude: u8,
1959    /// The narrow preset's velocity-to-timbre level.
1960    pub velocity_to_timbre: u8,
1961}
1962
1963impl Default for Preset {
1964    fn default() -> Preset {
1965        Preset {
1966            dynamics_enabled: false,
1967            velocity_to_amplitude: 1,
1968            velocity_to_timbre: 1,
1969        }
1970    }
1971}
1972
1973/// Build an instrument that spans the keyboard: one `stk` per zone, in the order
1974/// given, which must be highest zone first.
1975///
1976/// Refuses an empty or overlapping zone list, a duplicate or unnameable stroke id,
1977/// and everything [`instrument`] refuses about one zone's audio.
1978pub fn multi_zone(
1979    instrument: Instrument<'_>,
1980    zones: &[NewZone<'_>],
1981) -> Result<crate::Sample, Error> {
1982    match wide_schema(instrument.layout) {
1983        Some(schema) => wide_chain(instrument, zones, &schema).map(crate::Sample::V3),
1984        None => narrow_chain(instrument, zones).map(crate::Sample::V2),
1985    }
1986}
1987
1988/// The CBIN header every generation writes, at its own content version.
1989fn container(layout: Layout) -> Header {
1990    Header {
1991        generation: Generation::V1,
1992        tag: *b"nsmp",
1993        location: 0xFFFF_FFFF,
1994        aux: AUX,
1995        version: version(layout),
1996    }
1997}
1998
1999fn narrow_chain(instrument: Instrument<'_>, zones: &[NewZone<'_>]) -> Result<Cbin<Sample>, Error> {
2000    let table = zone_table(zones)?;
2001    let hdr = hdr(instrument.name)?;
2002    let cat = cat();
2003    let map = map(map_gain_units(instrument.map_gain), &table)?;
2004    // The directory a stroke carries counts words from the start of the body, and these
2005    // two decide where the first packet may start, so both are sized before any stream
2006    // is written.
2007    let cat_len = cat.payload.len();
2008    let map_len = map.payload.len();
2009
2010    let mut sections = vec![
2011        Section {
2012            tag: *section::CONTAINER,
2013            version: CONTAINER_VERSION,
2014            payload: Vec::new(),
2015        },
2016        hdr,
2017        cat,
2018        map,
2019    ];
2020    let (encoded, file_peak) =
2021        encode_strokes(Layout::V2, zones, instrument.predictor, cat_len, map_len)?;
2022    let mut body_at: usize = sections.iter().map(Section::encoded_len).sum();
2023    for (zone, stroke) in zones.iter().zip(&encoded) {
2024        let payload = stroke_payload(
2025            Layout::V2,
2026            zone,
2027            stroke,
2028            body_at + section::HEADER_LEN,
2029            file_peak,
2030        )?;
2031        body_at += section::HEADER_LEN + payload.len();
2032        sections.push(Section {
2033            tag: *section::STK,
2034            version: STK_VERSION,
2035            payload,
2036        });
2037    }
2038    sections.push(sty(instrument.preset)?);
2039
2040    Ok(Cbin {
2041        header: container(Layout::V2),
2042        body: Sample { sections },
2043    })
2044}
2045
2046/// The `stk` schema version both wide generations carry.
2047const STK4_VERSION: u32 = 11;
2048
2049fn wide_chain(
2050    instrument: Instrument<'_>,
2051    zones: &[NewZone<'_>],
2052    schema: &WideSchema,
2053) -> Result<Cbin<SampleV3>, Error> {
2054    let layout = instrument.layout;
2055    let table = wide_zone_table(zones)?;
2056    let hdr = hdr4(schema, instrument.name)?;
2057    let cat = cat4();
2058    let map = map4(schema, map_gain_units(instrument.map_gain), &table);
2059    let cat_len = cat.payload.len();
2060    let map_len = map.payload.len();
2061
2062    let mut sections = vec![
2063        Section4 {
2064            tag: *section::CONTAINER4,
2065            version: schema.container,
2066            payload: schema.container_payload.to_vec(),
2067        },
2068        hdr,
2069        cat,
2070        map,
2071    ];
2072    let (encoded, file_peak) =
2073        encode_strokes(layout, zones, instrument.predictor, cat_len, map_len)?;
2074    let mut body_at: usize = sections.iter().map(Section4::encoded_len).sum();
2075    for (zone, stroke) in zones.iter().zip(&encoded) {
2076        let payload = stroke_payload(
2077            layout,
2078            zone,
2079            stroke,
2080            body_at + section::HEADER4_LEN,
2081            file_peak,
2082        )?;
2083        body_at += section::HEADER4_LEN + payload.len();
2084        sections.push(Section4 {
2085            tag: *section::STK4,
2086            version: STK4_VERSION,
2087            payload,
2088        });
2089    }
2090    sections.push(sty4(schema, instrument.preset));
2091    let chain_len: usize = sections.iter().map(Section4::encoded_len).sum();
2092    sections.push(meta4(chain_len));
2093
2094    Ok(Cbin {
2095        header: container(layout),
2096        body: SampleV3 { sections },
2097    })
2098}
2099
2100/// What a wide `map` section stores per zone.
2101struct WideZoneRecord {
2102    root_key: u8,
2103    top_note: u8,
2104    low_note: u8,
2105    global_id: u32,
2106}
2107
2108impl WideZoneRecord {
2109    fn bytes(&self) -> [u8; super::zone::WIDE_RECORD_LEN] {
2110        let mut r = [0u8; super::zone::WIDE_RECORD_LEN];
2111        r[0] = self.root_key;
2112        r[1] = self.top_note;
2113        r[2] = self.low_note;
2114        // Unexplained: real programs hold this, and the panel cannot produce it.
2115        r[7] = 1;
2116        r[8..12].copy_from_slice(&self.global_id.to_be_bytes());
2117        // One sample in the zone, so the playing stroke sits at the bottom of the
2118        // strength axis.
2119        r[12..14].copy_from_slice(&super::zone::REL_STRENGTH_DEFAULT.to_be_bytes());
2120        let full = super::zone::VelocityWindow::FULL;
2121        r[14] = full.low;
2122        r[15] = full.high;
2123        r
2124    }
2125}
2126
2127/// Validate the zone list and reduce it to the records a wide `map` stores.
2128///
2129/// A wide zone states its own bottom as well as its top, and zones tile: each reaches
2130/// down to one above the zone below it, and the lowest reaches the keyboard's floor.
2131fn wide_zone_table(zones: &[NewZone<'_>]) -> Result<Vec<WideZoneRecord>, Error> {
2132    let table = zone_table(zones)?;
2133    Ok(table
2134        .iter()
2135        .enumerate()
2136        .map(|(index, record)| WideZoneRecord {
2137            root_key: zones[index].root_key,
2138            top_note: record.top_note,
2139            low_note: match table.get(index + 1) {
2140                Some(below) => below.top_note.saturating_add(1),
2141                None => super::zone::KEY_FLOOR,
2142            },
2143            global_id: zones[index].global_id,
2144        })
2145        .collect())
2146}
2147
2148/// What the `map` section stores per zone.
2149struct ZoneRecord {
2150    id: u8,
2151    top_note: u8,
2152    gain: u32,
2153}
2154
2155/// Validate the zone list and reduce it to the records the `map` section stores.
2156fn zone_table(zones: &[NewZone<'_>]) -> Result<Vec<ZoneRecord>, Error> {
2157    if zones.is_empty() || zones.len() > MAX_ZONES {
2158        return Err(ParseError::OutOfBounds {
2159            value: format!("{} zones", zones.len()),
2160            bound: format!("1 through {MAX_ZONES}, the map section's own count byte"),
2161        }
2162        .into());
2163    }
2164    let mut table = Vec::with_capacity(zones.len());
2165    for (index, zone) in zones.iter().enumerate() {
2166        midi_note("root key", zone.root_key)?;
2167        midi_note("top note", zone.top_note)?;
2168        if !(1..=MAX_STROKE_ID).contains(&zone.global_id) {
2169            return Err(ParseError::OutOfBounds {
2170                value: format!("stroke id {}", zone.global_id),
2171                bound: format!("1 through {MAX_STROKE_ID}, what a zone record can name"),
2172            }
2173            .into());
2174        }
2175        if !zone.gain.is_finite() || zone.gain > MAX_ZONE_GAIN {
2176            return Err(ParseError::OutOfBounds {
2177                value: format!("zone {index} gain {}", zone.gain),
2178                bound: format!("a finite gain up to {MAX_ZONE_GAIN}"),
2179            }
2180            .into());
2181        }
2182        let id = zone.global_id as u8;
2183        if table.iter().any(|seen: &ZoneRecord| seen.id == id) {
2184            return Err(ParseError::AssertFail(format!(
2185                "two zones claim stroke id {id}, and a zone record names its stroke by id"
2186            ))
2187            .into());
2188        }
2189        if index > 0 && zone.top_note >= zones[index - 1].top_note {
2190            return Err(ParseError::AssertFail(format!(
2191                "zone {index} reaches up to note {} but the zone before it stops at {}; \
2192                 zones are stored highest first and may not overlap",
2193                zone.top_note,
2194                zones[index - 1].top_note
2195            ))
2196            .into());
2197        }
2198        table.push(ZoneRecord {
2199            id,
2200            top_note: zone.top_note,
2201            gain: zone_record_gain(zone.gain),
2202        });
2203    }
2204    Ok(table)
2205}
2206
2207/// The ceiling the `map`'s own gain clamps at, in decibels. A project asking for more
2208/// renders at this and is not repaired.
2209pub const MAX_MAP_GAIN_DB: f64 = 9.0;
2210
2211/// Largest zone gain whose stores this reproduces. Past it the u24s' wrap count is
2212/// unmeasured; below it the wrap is the format's, not a mistake.
2213pub const MAX_ZONE_GAIN: f64 = 1000.0;
2214
2215/// The zone's playing gain in decibels — the number a wide stroke header stores, and
2216/// the number every other gain field is derived through.
2217///
2218/// The logarithm is evaluated wider than the field and rounded once; computing it in
2219/// float32 throughout moves the last byte on the powers of two. Neither clamped nor
2220/// gridded: silence is `-inf` and a negative gain is the default quiet NaN, which is
2221/// what the map gain's ceiling comparison then fails against.
2222fn gain_decibels(gain: f64) -> f32 {
2223    let decibels = 20.0 * gain.log10();
2224    match decibels.is_nan() {
2225        true => f32::from_bits(0x7fc0_0000),
2226        false => decibels as f32,
2227    }
2228}
2229
2230/// The gain back from its decibel, linear with
2231/// [`zone::GAIN_BITS`](super::zone::GAIN_BITS) fractional bits, exponentiated wider
2232/// than the decibel and rounded once.
2233///
2234/// ⚠️ **Not the identity on the linear gain it came from.** Below `2^24` the decibel's
2235/// own precision is worth less than half a step and the two agree; above it they part
2236/// by tens of steps, and it is this value — not the project's — that statistic A is
2237/// built from.
2238fn gain_units(decibels: f32) -> u64 {
2239    let units = 10f64.powf(f64::from(decibels) / 20.0) * f64::from(super::zone::GAIN_UNITY);
2240    units.round() as u64
2241}
2242
2243/// The `map`'s own gain as the section's opening u24. The one gain field that clamps.
2244fn map_gain_units(gain: f64) -> u32 {
2245    let ceiling = MAX_MAP_GAIN_DB as f32;
2246    let decibels = gain_decibels(gain);
2247    // The comparison, not the value, is what the ceiling is: a NaN decibel — which is
2248    // what a negative gain gives — fails it and takes the ceiling rather than the floor.
2249    let clamped = if decibels < ceiling {
2250        decibels
2251    } else {
2252        ceiling
2253    };
2254    gain_units(clamped) as u32
2255}
2256
2257/// A zone gain as the narrow zone record stores it: the project's own float, wrapping
2258/// mod `2^24`, with a negative converting to zero rather than masking.
2259///
2260/// ⚠️ The record and statistic A part company here. The record takes the project's
2261/// float and the mantissa takes the decibel round trip, so past a gain of 16 the two
2262/// u24s in one file disagree and the record's reads back as a plausible quieter gain.
2263fn zone_record_gain(gain: f64) -> u32 {
2264    let units = (gain * f64::from(super::zone::GAIN_UNITY)).round() as u64;
2265    (units % (1 << 24)) as u32
2266}
2267
2268fn midi_note(name: &str, note: u8) -> Result<(), Error> {
2269    if note <= 127 {
2270        return Ok(());
2271    }
2272    Err(ParseError::OutOfBounds {
2273        value: format!("{name} {note}"),
2274        bound: "a MIDI note from 0 through 127".into(),
2275    }
2276    .into())
2277}
2278
2279#[cfg(test)]
2280mod tests {
2281    use super::super::codec;
2282    use super::super::zone::GAIN_UNITY;
2283    use super::*;
2284
2285    /// The narrow chain's own units, which most of these tests are written against.
2286    const CELL: usize = Layout::V2.cell();
2287    const CHUNK: usize = Layout::V2.rmax();
2288    const HEADER_LEN: usize = Layout::V2.header_len();
2289    const PACKET_LEN: usize = packet_len(Layout::V2);
2290    const VERSION: u32 = version(Layout::V2);
2291    const MONO: Units = Units {
2292        layout: Layout::V2,
2293        channels: 1,
2294    };
2295    const PACKET_WORDS: usize = MONO.packet_words();
2296
2297    /// The narrow body an encode produced. These tests build no wide one.
2298    fn narrow(sample: crate::Sample) -> Cbin<Sample> {
2299        match sample {
2300            crate::Sample::V2(file) => file,
2301            crate::Sample::V3(_) => panic!("the narrow chain was asked for"),
2302        }
2303    }
2304
2305    fn built(
2306        zones: &[NewZone<'_>],
2307        name: &str,
2308        predictor: Predictor,
2309    ) -> Result<Cbin<Sample>, Error> {
2310        multi_zone(made(name, predictor, Layout::V2), zones).map(narrow)
2311    }
2312
2313    /// An instrument at unity map gain, which is what all but one test wants.
2314    fn made(name: &str, predictor: Predictor, layout: Layout) -> Instrument<'_> {
2315        Instrument {
2316            name,
2317            map_gain: 1.0,
2318            predictor,
2319            layout,
2320            preset: Preset::default(),
2321        }
2322    }
2323
2324    fn plan(frames: usize, channels: usize) -> Result<Plan, Error> {
2325        Plan::new(
2326            Layout::V2,
2327            frames,
2328            channels,
2329            default_secondary_start(frames, None),
2330        )
2331    }
2332
2333    fn looped(frames: usize, channels: usize, points: Loop) -> Result<Plan, Error> {
2334        Plan::looped(
2335            Layout::V2,
2336            frames,
2337            channels,
2338            points,
2339            default_secondary_start(frames, Some(points)),
2340        )
2341    }
2342
2343    fn sine(hz: f64, amplitude: f64, frames: usize) -> Vec<i16> {
2344        (0..frames)
2345            .map(|k| {
2346                let t = k as f64 / f64::from(codec::SOURCE_RATE);
2347                (amplitude * (2.0 * std::f64::consts::PI * hz * t).sin()).round() as i16
2348            })
2349            .collect()
2350    }
2351
2352    fn encoded(source: &[i16], predictor: Predictor) -> Cbin<Sample> {
2353        narrow(instrument(source, &Options::new("Test").predictor(predictor)).unwrap())
2354    }
2355
2356    #[test]
2357    fn the_band_is_the_shortest_run_a_whole_number_of_records_can_cover() {
2358        for channels in [1usize, 2] {
2359            let (cell, rmax) = (CELL * channels, CHUNK * channels);
2360            for r in 0..2000usize {
2361                let b = band(r, cell, rmax);
2362                assert_eq!(b % cell, r % cell, "{channels}ch r {r}");
2363                assert!(b >= cell, "band({r}) = {b}");
2364                let records = (1..=8).find(|j| j * cell <= b && b <= j * rmax);
2365                assert!(records.is_some(), "{channels}ch band({r}) = {b}");
2366                for shorter in (cell..b).filter(|s| s % cell == b % cell) {
2367                    assert!(
2368                        !(1..=8).any(|j| j * cell <= shorter && shorter <= j * rmax),
2369                        "{channels}ch band({r}) = {b}, but {shorter} is reachable"
2370                    );
2371                }
2372            }
2373            assert_eq!(band(0, cell, rmax), cell);
2374            assert_eq!(band(cell, cell, rmax), cell);
2375        }
2376    }
2377
2378    #[test]
2379    fn every_one_to_one_chunk_is_a_legal_count() {
2380        for channels in [1usize, 2] {
2381            let (cell, rmax) = (CELL * channels, CHUNK * channels);
2382            for r in 0..2000usize {
2383                let run = band(r, cell, rmax);
2384                let split = chunks(run, rmax);
2385                assert_eq!(split.iter().sum::<usize>(), run, "band({r})");
2386                for c in split {
2387                    assert!((cell..=rmax).contains(&c), "band({r}) chunk {c}");
2388                }
2389            }
2390        }
2391    }
2392
2393    #[test]
2394    fn the_plan_covers_every_field_exactly_once() {
2395        for frames in [4096, 8192, 10_000, 44_100, 100_000, 441_000] {
2396            let p = plan(frames, 1).unwrap();
2397            assert_eq!(
2398                p.warmup + CELL * p.cells_before + p.resync + CELL * p.cells_after,
2399                p.fields,
2400                "{frames} frames"
2401            );
2402            assert_eq!(p.warmup + CELL * p.cells_before, p.resync_at);
2403        }
2404    }
2405
2406    // Landmarks read off Nord Sample Editor renders of self-generated audio whose
2407    // projects state the fresh default, `m_startSecondary = m_stop / 8`, from
2408    // `m_start = 1`: a 44 100-frame mono sine and a 30 870-frame stereo pair.
2409    #[test]
2410    fn the_resync_lands_where_the_projects_secondary_start_says() {
2411        let mono = Plan::new(Layout::V2, 44_099, 1, 5_512.5 - 1.0).unwrap();
2412        assert_eq!(
2413            (mono.fields, mono.warmup, mono.resync_at, mono.resync),
2414            (35_128, 30, 4_374, 58)
2415        );
2416        let both = Plan::new(Layout::V2, 30_869, 2, 3_858.75 - 1.0).unwrap();
2417        assert_eq!(
2418            (both.fields, both.warmup, both.resync_at, both.resync),
2419            (49_256, 124, 6_124, 124)
2420        );
2421        // Half-up on the lattice: 11 025 frames land on exactly 8 750.5 fields.
2422        assert_eq!(
2423            Plan::new(Layout::V2, 88_200, 1, 11_025.0)
2424                .unwrap()
2425                .resync_at,
2426            8_751
2427        );
2428    }
2429
2430    #[test]
2431    fn a_secondary_start_the_stream_cannot_resync_at_is_refused() {
2432        for at in [0.0, 20.0, 50_000.0, -1.0, f64::NAN, f64::INFINITY] {
2433            assert!(
2434                Plan::new(Layout::V2, 44_100, 1, at).is_err(),
2435                "secondary start {at}"
2436            );
2437        }
2438        let looped = |at| Plan::looped(Layout::V2, 44_100, 1, Loop::new(8_192, 40_000), at);
2439        assert!(looped(8_193.0).is_err(), "past the loop start");
2440        assert!(looped(8_192.0).is_ok(), "at the loop start, mark pushed");
2441        assert!(looped(4_096.0).is_ok());
2442    }
2443
2444    /// The mark's two anchors, on a loop that clears the resync point and on ones that
2445    /// do not, at both channel counts.
2446    #[test]
2447    fn a_loop_mark_clears_the_resync_point_by_the_generations_floor() {
2448        // Loop start and secondary start in frames, then the field the mark lands on
2449        // at V2, V3 and V4.
2450        for (start, secondary, channels, marks) in [
2451            (92, 92.0, 1, [145, 137, 137]),
2452            (200, 150.0, 1, [191, 183, 183]),
2453            (600, 500.0, 1, [481, 481, 481]),
2454            (92, 92.0, 2, [290, 274, 274]),
2455        ] {
2456            let points = Loop::new(start, start + 16_384);
2457            for (layout, mark) in [Layout::V2, Layout::V3, Layout::V4].into_iter().zip(marks) {
2458                let plan = Plan::looped(layout, 88_200, channels, points, secondary).unwrap();
2459                let looped = plan.looped.unwrap();
2460                assert_eq!(
2461                    looped.at, mark,
2462                    "{layout:?} {channels}ch: a loop at frame {start} resyncing at {secondary}"
2463                );
2464                assert_eq!(looped.lead, mark - fields_of(start).unwrap() * channels);
2465                assert_eq!(plan.fields, mark + fields_of(16_384).unwrap() * channels);
2466            }
2467        }
2468    }
2469
2470    #[test]
2471    fn audio_without_a_project_resyncs_where_a_fresh_project_would() {
2472        assert_eq!(default_secondary_start(44_100, None), 5_512.5);
2473        assert_eq!(
2474            default_secondary_start(44_100, Some(Loop::new(1_000, 40_000))),
2475            500.0
2476        );
2477        assert_eq!(
2478            default_secondary_start(44_100, Some(Loop::new(0, 40_000))),
2479            nsmpproj::MIN_SECONDARY_START
2480        );
2481        let stated = instrument(
2482            &vec![0i16; 44_100],
2483            &Options::new("Stated").secondary_start(5_521.281862),
2484        )
2485        .unwrap();
2486        let fresh = instrument(&vec![0i16; 44_100], &Options::new("Stated")).unwrap();
2487        assert_ne!(stated.stroke_streams()[0].1, fresh.stroke_streams()[0].1);
2488    }
2489
2490    #[test]
2491    fn the_stream_opens_on_a_cubic_ramp() {
2492        let mut fields = vec![-4_000i64; 40];
2493        ramp_in(&mut fields);
2494        assert_eq!(fields[0], 0);
2495        assert_eq!(fields[7], -4_000 * 343 / 42_875);
2496        assert_eq!(fields[34], -4_000 * 39_304 / 42_875);
2497        assert!(fields[..RAMP_IN].windows(2).all(|w| w[0] >= w[1]));
2498        assert!(fields[RAMP_IN..].iter().all(|&v| v == -4_000));
2499    }
2500
2501    #[test]
2502    fn a_width_tie_goes_to_the_lowest_order_unless_a_record_already_holds_it() {
2503        let widths = [13, 10, 7, 4, 4];
2504        assert_eq!(choose_order(&widths, None), (3, 4));
2505        assert_eq!(choose_order(&widths, Some((4, 4))), (4, 4));
2506        assert_eq!(choose_order(&widths, Some((4, 3))), (3, 4), "width changed");
2507        assert_eq!(choose_order(&widths, Some((2, 4))), (3, 4));
2508        assert_eq!(choose_order(&[9], Some((3, 9))), (0, 9));
2509        // C(k, 3): the third difference is 1 everywhere and the fourth is 0, so orders
2510        // 3 and 4 both fit width 2.
2511        let values: Vec<i32> = (0..48).map(|k| k * (k - 1) * (k - 2) / 6).collect();
2512        assert_eq!(
2513            widths_at(&values, 8, Predictor::Minimising, CELL, 1)[3..],
2514            [MIN_WIDTH, MIN_WIDTH]
2515        );
2516        assert_eq!(widths_at(&values, 8, Predictor::Plain, CELL, 1).len(), 1);
2517    }
2518
2519    #[test]
2520    fn short_input_is_refused_rather_than_guessed_at() {
2521        assert!(plan(MIN_FRAMES - 1, 1).is_err());
2522        assert!(plan(MIN_FRAMES, 1).is_ok());
2523        assert!(plan(usize::MAX, 1).is_err());
2524        assert!(instrument(&[0i16; MIN_FRAMES - 1], &Options::new("Test")).is_err());
2525        assert!(instrument(&[0i16; MIN_FRAMES], &Options::new("Test")).is_ok());
2526    }
2527
2528    #[test]
2529    fn forced_shifts_that_cannot_be_encoded_are_refused() {
2530        let mut step = vec![i16::MIN; MIN_FRAMES];
2531        step[MIN_FRAMES / 2..].fill(i16::MAX);
2532        assert!(instrument(&step, &Options::new("Test").shift(0)).is_err());
2533        assert!(instrument(
2534            &[0i16; MIN_FRAMES],
2535            &Options::new("Test").shift(codec::SHIFT_LIMIT as u8 + 1)
2536        )
2537        .is_err());
2538    }
2539
2540    #[test]
2541    fn midi_notes_outside_the_wire_range_are_refused() {
2542        let source = vec![0i16; MIN_FRAMES];
2543        assert!(instrument(&source, &Options::new("Test").root_key(128)).is_err());
2544        assert!(instrument(&source, &Options::new("Test").top_note(255)).is_err());
2545        let bad_root = NewZone {
2546            root_key: 128,
2547            ..zone(&source, 60, 127, 1)
2548        };
2549        let encoded = encode_stroke(Layout::V2, &bad_root, 165, Predictor::Plain).unwrap();
2550        assert!(stroke_payload(Layout::V2, &bad_root, &encoded, 0, 1).is_err());
2551    }
2552
2553    #[test]
2554    fn the_allocation_is_whole_packets_with_the_chain_at_the_end() {
2555        let file = encoded(&sine(440.0, 8000.0, 44_100), Predictor::Plain);
2556        let map_len = section::find(&file.body.sections, section::MAP)
2557            .unwrap()
2558            .payload
2559            .len();
2560        let cat_len = section::find(&file.body.sections, section::CAT)
2561            .unwrap()
2562            .payload
2563            .len();
2564        let stroke = section::find(&file.body.sections, section::STK).unwrap();
2565        let head = super::super::stroke::header_len(
2566            Layout::V2,
2567            super::super::Chain::Library2,
2568            0,
2569            cat_len,
2570            map_len,
2571        );
2572        assert_eq!((stroke.payload.len() - head) % PACKET_LEN, 0);
2573        assert_eq!(&stroke.payload[stroke.payload.len() - 3..], &[0x80, 0, 24]);
2574    }
2575
2576    #[test]
2577    fn every_predictor_round_trips_through_the_decoder_exactly() {
2578        let mut differenced = 0usize;
2579        for predictor in [Predictor::Plain, Predictor::Minimising] {
2580            for source in [
2581                sine(440.0, 12_000.0, 44_100),
2582                sine(30.0, 32_000.0, 20_000),
2583                vec![0i16; 8192],
2584                vec![9000i16; 8192],
2585            ] {
2586                let file = encoded(&source, predictor);
2587                let (at, stroke) = file.stroke_streams()[0];
2588                let plan = plan(source.len(), 1).unwrap();
2589                let q = quantise(&source, &plan, None);
2590
2591                let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
2592                assert_eq!(audio.samples.len(), plan.fields);
2593                if predictor == Predictor::Plain {
2594                    assert_eq!(audio.differenced, 0);
2595                } else {
2596                    differenced += audio.differenced;
2597                }
2598                let gain = 1i32 << q.shift;
2599                for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
2600                    assert_eq!(i32::from(got), want * gain, "{predictor:?} field {f}");
2601                }
2602            }
2603        }
2604        assert!(differenced > 0, "minimising never chose a predictor");
2605    }
2606
2607    #[test]
2608    fn a_sine_comes_back_a_sine() {
2609        let source = sine(440.0, 20_000.0, 44_100);
2610        let file = encoded(&source, Predictor::Plain);
2611        let (at, stroke) = file.stroke_streams()[0];
2612        let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
2613        // Well inside the source, away from the ends the kernel rings at.
2614        let window = &audio.samples[10_000..20_000];
2615        let peak = window.iter().map(|&v| i32::from(v).abs()).max().unwrap();
2616        assert!((19_000..=21_000).contains(&peak), "peak {peak}");
2617        let zero_crossings = window.windows(2).filter(|w| w[0] < 0 && w[1] >= 0).count();
2618        // 10000 fields at 35002 Hz is 0.2857 s, which holds 125.7 cycles of 440 Hz.
2619        assert!((124..=127).contains(&zero_crossings), "{zero_crossings}");
2620    }
2621
2622    #[test]
2623    fn a_records_fields_start_right_after_its_header() {
2624        // 30 fields of 13 bits is 390, leaving 18 spare bits in 18 words.
2625        let spec = Spec {
2626            one_to_one: true,
2627            width: 13,
2628            order: 0,
2629            mark: false,
2630            first: 0,
2631            count: 30,
2632        };
2633        let tail = spec.span(MONO) * 24 - 24 - spec.count * usize::from(spec.width);
2634        assert_eq!(tail, 18, "this spec is chosen to leave a tail");
2635
2636        let values: Vec<i32> = (0..30).map(|k| k * 7 - 40).collect();
2637        let mut words = vec![0u8; spec.span(MONO) * 3];
2638        write_record(&mut words, 0, &spec, &values, MONO);
2639
2640        // The tail is the last `tail` bits of the segment, and nothing is in it.
2641        let total = spec.span(MONO) * 24;
2642        for bit in total - tail..total {
2643            assert_eq!(
2644                words[bit / 8] >> (7 - bit % 8) & 1,
2645                0,
2646                "bit {bit} is in the alignment tail and should be clear"
2647            );
2648        }
2649        // And the reader agrees about where the values are.
2650        let mut stroke = vec![0u8; HEADER_LEN];
2651        stroke.extend_from_slice(&words);
2652        stroke.extend_from_slice(&[0x80, 0x00, 0x18]);
2653        let end = (HEADER_LEN / 3 + spec.span(MONO)) as u16;
2654        for (i, p) in [HEADER_LEN as u16 / 3, 0, end, end].iter().enumerate() {
2655            let at = codec::SEEK_AT + codec::SEEK_STRIDE * i;
2656            stroke[at..at + 2].copy_from_slice(&p.to_be_bytes());
2657        }
2658        let walked = codec::walk(&stroke, 0, codec::Layout::V2).unwrap();
2659        assert_eq!(walked.records[0].values, values);
2660    }
2661
2662    #[test]
2663    fn the_instrument_reads_back_as_one() {
2664        let file = instrument(
2665            &sine(220.0, 15_000.0, 30_000),
2666            &Options::new("Encoded").root_key(48).top_note(72),
2667        )
2668        .unwrap();
2669        let bytes = file.to_bytes().unwrap();
2670        let read = super::super::from_bytes(&bytes).unwrap();
2671        assert_eq!(read.name().unwrap(), "Encoded");
2672        assert_eq!(read.header.version, VERSION);
2673        let zones = read.zones().unwrap();
2674        assert_eq!(zones.len(), 1);
2675        assert_eq!(zones[0].top_note, 72);
2676        assert_eq!(read.strokes().unwrap()[0].root_key, 48);
2677        assert_eq!(read.to_bytes().unwrap(), bytes);
2678    }
2679
2680    #[test]
2681    fn the_directory_names_the_records_the_walk_finds() {
2682        let file = encoded(&sine(300.0, 9000.0, 50_000), Predictor::Plain);
2683        let (at, stroke) = file.stroke_streams()[0];
2684        let stream = codec::walk(stroke, at, codec::Layout::V2).unwrap();
2685        let directory = codec::Directory::read(stroke).unwrap();
2686        assert_eq!(
2687            codec::Directory::resolve(directory.first_record, at, codec::Layout::V2),
2688            stream.first_record
2689        );
2690        assert_eq!(
2691            codec::Directory::resolve(directory.terminator, at, codec::Layout::V2),
2692            stream.terminator
2693        );
2694        let resync = codec::Directory::resolve(directory.resync, at, codec::Layout::V2);
2695        let record = stream.records.iter().find(|r| r.at == resync).unwrap();
2696        assert!(record.one_to_one);
2697        assert_eq!(record.first_field, plan(50_000, 1).unwrap().resync_at);
2698    }
2699
2700    #[test]
2701    fn the_header_states_the_shift_it_quantised_at() {
2702        for amplitude in [40.0, 900.0, 8000.0, 32_000.0] {
2703            let source = sine(440.0, amplitude, 20_000);
2704            let plan = plan(source.len(), 1).unwrap();
2705            let file = encoded(&source, Predictor::Plain);
2706            let (_, stroke) = file.stroke_streams()[0];
2707            let q = quantise(&source, &plan, None);
2708            assert_eq!(
2709                codec::shift(stroke, codec::Layout::V2),
2710                Some(q.shift),
2711                "amplitude {amplitude}"
2712            );
2713            assert_eq!(codec::peak(stroke, codec::Layout::V2), Some(q.peak));
2714            assert!(q.shift >= 0);
2715        }
2716    }
2717
2718    #[test]
2719    fn the_shift_tracks_how_loud_the_content_is() {
2720        let quiet = plan(20_000, 1)
2721            .map(|p| quantise(&sine(440.0, 500.0, 20_000), &p, None).shift)
2722            .unwrap();
2723        let loud = plan(20_000, 1)
2724            .map(|p| quantise(&sine(440.0, 32_000.0, 20_000), &p, None).shift)
2725            .unwrap();
2726        assert_eq!(quiet, 0);
2727        assert!(loud > quiet, "loud {loud} vs quiet {quiet}");
2728    }
2729
2730    #[test]
2731    fn a_stereo_stroke_stops_shifting_where_its_peak_fits() {
2732        let frames = 30_000;
2733        let left = sine(220.0, 12_000.0, frames);
2734        let right = sine(330.0, 12_000.0, frames);
2735        let both: Vec<i16> = left
2736            .iter()
2737            .zip(&right)
2738            .flat_map(|(&l, &r)| [l, r])
2739            .collect();
2740        let mono = quantise(&left, &plan(frames, 1).unwrap(), None);
2741        let stereo = quantise(&both, &plan(frames, 2).unwrap(), None);
2742        assert_eq!(stereo.shift, 1);
2743        let widest = stereo
2744            .values
2745            .iter()
2746            .map(|v| width_of(i64::from(*v), i64::from(*v)))
2747            .max()
2748            .unwrap();
2749        assert_eq!(widest, PEAK_WIDTH);
2750        // The mono stroke sees the same peak and may spend one further bit on top.
2751        assert!((stereo.shift..=stereo.shift + 1).contains(&mono.shift));
2752    }
2753
2754    /// A stroke whose only loud field sits at `field`, resynchronising at `resync`.
2755    fn probe(layout: Layout, resync: usize, field: usize) -> (Plan, Vec<i64>) {
2756        let frames = 100_000;
2757        let secondary = resync as f64 * f64::from(PITCH_NUM) / f64::from(PITCH_DEN);
2758        let plan = Plan::new(layout, frames, 1, secondary).unwrap();
2759        assert_eq!(plan.resync_at, resync);
2760        let mut values = vec![0i64; plan.fields];
2761        values[field] = 1 << (PEAK_WIDTH - 2);
2762        (plan, values)
2763    }
2764
2765    #[test]
2766    fn only_a_run_s_last_record_buys_the_extra_bit() {
2767        // The resync run at 5464 is 89 fields: [0, 32), [32, 64), [64, 89).
2768        let (plan, values) = probe(Layout::V2, 5464, 5464 + 76);
2769        assert!(spends_extra_bit(&values, &plan));
2770        for offset in [12, 61, 89, 95] {
2771            let (plan, values) = probe(Layout::V2, 5464, 5464 + offset);
2772            assert!(!spends_extra_bit(&values, &plan), "run offset {offset}");
2773        }
2774    }
2775
2776    /// A mono stroke that is one 1:1 run ending in a `last`-field record, with `value`
2777    /// in that record's final field and nothing anywhere else.
2778    fn opening_run(layout: Layout, last: usize, value: i64) -> (Plan, Vec<i64>) {
2779        let chunk = layout.rmax();
2780        let warmup = if last == chunk { chunk } else { chunk + last };
2781        let plan = Plan {
2782            layout,
2783            channels: 1,
2784            fields: warmup,
2785            resync_at: warmup,
2786            warmup,
2787            resync: 0,
2788            cells_before: 0,
2789            cells_after: 0,
2790            looped: None,
2791        };
2792        let mut values = vec![0; warmup];
2793        values[warmup - 1] = value;
2794        (plan, values)
2795    }
2796
2797    #[test]
2798    fn each_last_record_width_obeys_the_measured_rule() {
2799        for (last, buys) in [
2800            (24, false),
2801            (25, true),
2802            (26, true),
2803            (27, true),
2804            (28, true),
2805            (29, false),
2806            (30, true),
2807            (31, true),
2808            (32, false),
2809        ] {
2810            let (plan, values) = opening_run(Layout::V2, last, 1 << (PEAK_WIDTH - 2));
2811            assert_eq!(spends_extra_bit(&values, &plan), buys, "width {last}");
2812        }
2813    }
2814
2815    #[test]
2816    fn the_extra_bit_uses_signed_thirteen_bit_bounds() {
2817        for (value, buys) in [(-4097, true), (-4096, false), (4095, false), (4096, true)] {
2818            let (plan, values) = opening_run(Layout::V2, 25, value);
2819            assert_eq!(spends_extra_bit(&values, &plan), buys, "value {value}");
2820        }
2821    }
2822
2823    /// The last record of a v3 run is 32..=48 fields, and these eleven of the
2824    /// seventeen buy the bit.
2825    const V3_LIVE: [usize; 11] = [33, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46];
2826
2827    #[test]
2828    fn six_of_the_seventeen_v3_last_record_widths_never_buy_it() {
2829        for last in 32..=48 {
2830            let (plan, values) = opening_run(Layout::V3, last, 1 << (PEAK_WIDTH - 2));
2831            assert_eq!(
2832                spends_extra_bit(&values, &plan),
2833                V3_LIVE.contains(&last),
2834                "width {last}"
2835            );
2836        }
2837    }
2838
2839    #[test]
2840    fn a_v4_mono_stroke_never_buys_the_extra_bit() {
2841        for last in 32..=48 {
2842            let (plan, values) = opening_run(Layout::V4, last, 1 << (PEAK_WIDTH - 2));
2843            assert!(!spends_extra_bit(&values, &plan), "width {last}");
2844        }
2845    }
2846
2847    /// A looped mono stroke whose only loud field sits in the last record of the run
2848    /// the mark opens. The opening run is a full RMAX record, a width both generations
2849    /// call dead, so nothing but the loop's run can buy the bit.
2850    fn loop_run(layout: Layout, last: usize) -> (Plan, Vec<i64>) {
2851        let at = layout.rmax();
2852        let fields = at + last;
2853        let plan = Plan {
2854            layout,
2855            channels: 1,
2856            fields,
2857            resync_at: at,
2858            warmup: at,
2859            resync: 0,
2860            cells_before: 0,
2861            cells_after: 0,
2862            looped: Some(Looped {
2863                at,
2864                lead: 0,
2865                crossfade: 0,
2866                warmup: last,
2867                cells: 0,
2868            }),
2869        };
2870        let mut values = vec![0; fields];
2871        values[fields - 1] = 1 << (PEAK_WIDTH - 2);
2872        (plan, values)
2873    }
2874
2875    #[test]
2876    fn the_run_a_loop_mark_opens_buys_the_extra_bit() {
2877        for (layout, live, dead) in [(Layout::V2, 25, 29), (Layout::V3, 33, 41)] {
2878            let (plan, values) = loop_run(layout, live);
2879            assert!(spends_extra_bit(&values, &plan), "{layout:?} live");
2880
2881            let unmarked = Plan {
2882                looped: None,
2883                ..plan
2884            };
2885            assert!(!spends_extra_bit(&values, &unmarked), "{layout:?} unlooped");
2886
2887            let (plan, values) = loop_run(layout, dead);
2888            assert!(!spends_extra_bit(&values, &plan), "{layout:?} dead");
2889        }
2890    }
2891
2892    #[test]
2893    fn the_extra_bit_narrows_the_stroke_the_header_declares() {
2894        let loud = header_shift(&sine(440.0, 12_000.0, 44_100), 1);
2895        let quiet = header_shift(&sine(440.0, 3_000.0, 44_100), 1);
2896        assert_eq!(loud - quiet, 2);
2897    }
2898
2899    fn header_shift(source: &[i16], channels: u16) -> i32 {
2900        let options = Options::new("Shift")
2901            .channels(channels)
2902            .predictor(Predictor::Minimising);
2903        let file = instrument(source, &options).unwrap();
2904        let (_, stroke) = file.stroke_streams()[0];
2905        codec::shift(stroke, codec::Layout::V2).unwrap()
2906    }
2907
2908    #[test]
2909    fn statistic_b_takes_the_sign_of_the_extreme_field() {
2910        let frames = 20_000;
2911        let mut up = vec![0i16; frames];
2912        up[10_000] = 13;
2913        let down: Vec<i16> = up.iter().map(|v| -v).collect();
2914        let positive = quantise(&up, &plan(frames, 1).unwrap(), None).peak;
2915        let negative = quantise(&down, &plan(frames, 1).unwrap(), None).peak;
2916        assert_eq!(positive, 2);
2917        assert_eq!(negative, 3);
2918        let opposed: Vec<i16> = up.iter().zip(&down).flat_map(|(&l, &r)| [l, r]).collect();
2919        let stereo = quantise(&opposed, &plan(frames, 2).unwrap(), None).peak;
2920        assert_eq!(stereo, positive);
2921    }
2922
2923    #[test]
2924    fn no_field_overflows_the_width_its_record_declares() {
2925        for predictor in [Predictor::Plain, Predictor::Minimising] {
2926            let source = sine(440.0, 32_000.0, 30_000);
2927            let plan = plan(source.len(), 1).unwrap();
2928            let q = quantise(&source, &plan, None);
2929            let (specs, _) = records(&q.values, &plan, predictor).unwrap();
2930            for spec in specs {
2931                let limit = 1i64 << (spec.width - 1);
2932                for k in 0..spec.count {
2933                    let v = residual(&q.values, spec.first + k, spec.order, 1);
2934                    assert!((-limit..limit).contains(&v), "{spec:?} field {k} = {v}");
2935                }
2936                assert!(spec.width <= PEAK_WIDTH || spec.order > 0);
2937            }
2938        }
2939    }
2940
2941    #[test]
2942    fn records_tile_the_lattice_the_way_the_laws_say() {
2943        let source = sine(440.0, 20_000.0, 60_000);
2944        let plan = plan(source.len(), 1).unwrap();
2945        let q = quantise(&source, &plan, None);
2946        let (specs, _) = records(&q.values, &plan, Predictor::Plain).unwrap();
2947
2948        let mut at = 0;
2949        for spec in &specs {
2950            assert_eq!(spec.first, at);
2951            if !spec.one_to_one {
2952                assert_eq!(spec.count % CELL, 0);
2953                assert!(spec.count <= MAX_COUNT);
2954            }
2955            at += spec.count;
2956        }
2957        assert_eq!(at, plan.fields);
2958        let one_to_one: usize = specs.iter().filter(|s| s.one_to_one).map(|s| s.count).sum();
2959        assert_eq!(one_to_one, plan.warmup + plan.resync);
2960    }
2961
2962    #[test]
2963    fn the_minimising_predictor_narrows_smooth_material() {
2964        let source = sine(60.0, 30_000.0, 60_000);
2965        let plan = plan(source.len(), 1).unwrap();
2966        let q = quantise(&source, &plan, None);
2967        let (plain, _) = records(&q.values, &plan, Predictor::Plain).unwrap();
2968        let (minimised, _) = records(&q.values, &plan, Predictor::Minimising).unwrap();
2969
2970        let bits = |specs: &[Spec]| -> usize { specs.iter().map(|s| s.span(MONO)).sum() };
2971        assert!(
2972            bits(&minimised) < bits(&plain),
2973            "{} words vs {}",
2974            bits(&minimised),
2975            bits(&plain)
2976        );
2977        assert!(minimised.iter().any(|s| s.order > 0));
2978        // The 1:1 regime never predicts.
2979        assert!(minimised.iter().all(|s| !s.one_to_one || s.order == 0));
2980    }
2981
2982    #[test]
2983    fn a_residual_integrates_back_to_the_field_it_came_from() {
2984        let values: Vec<i32> = (0..200).map(|k| (k * k / 7) % 501 - 250).collect();
2985        for order in 1..DIFFERENCE.len() as u8 {
2986            for at in usize::from(order)..values.len() {
2987                let mut v = residual(&values, at, order, 1);
2988                for (j, &c) in DIFFERENCE[usize::from(order)].iter().enumerate().skip(1) {
2989                    v -= i64::from(c) * i64::from(values[at - j]);
2990                }
2991                assert_eq!(v, i64::from(values[at]), "order {order} at {at}");
2992            }
2993        }
2994    }
2995
2996    #[test]
2997    fn statistic_a_round_trips_the_shift() {
2998        for peak in [0u32, 1, 2, 255, 4095, 4096, 8191, 8192] {
2999            for shift in 0..6 {
3000                let (mantissa, exponent) = statistic_a(peak, shift, u64::from(GAIN_UNITY));
3001                let mut stroke = vec![0u8; HEADER_LEN];
3002                stroke[codec::STAT_A_EXP_AT] = exponent;
3003                stroke[codec::PEAK_AT..codec::PEAK_AT + 3]
3004                    .copy_from_slice(&peak.to_be_bytes()[1..]);
3005                assert_eq!(
3006                    codec::shift(&stroke, codec::Layout::V2),
3007                    Some(shift),
3008                    "peak {peak}"
3009                );
3010                assert!((1 << 19..1 << 20).contains(&mantissa) || peak == 0);
3011            }
3012        }
3013    }
3014
3015    #[test]
3016    fn the_stroke_header_holds_the_fixed_bytes_where_the_format_puts_them() {
3017        let file = instrument(
3018            &sine(440.0, 9000.0, 20_000),
3019            &Options::new("Test").root_key(64),
3020        )
3021        .unwrap();
3022        let (_, head) = file.stroke_streams()[0];
3023        assert_eq!(head[0..5], [0, 0, 0, 1, 0]);
3024        assert_eq!(head[5], 64);
3025        assert_eq!(head[6..9], [0x88, 0xba, 0x01]);
3026        let stereo = instrument(
3027            &vec![0i16; 2 * MIN_FRAMES],
3028            &Options::new("Test").channels(2),
3029        )
3030        .unwrap();
3031        assert_eq!(stereo.stroke_streams()[0].1[6..9], [0x88, 0xba, 0x02]);
3032        assert_eq!(head[16..20], [0, 0, 0, 0]);
3033        assert_eq!([head[22], head[31], head[40]], [0x80, 0x80, 0x80]);
3034        assert_eq!(head[49..51], [0, 0]);
3035        for gap in [23..29, 32..38, 41..47] {
3036            assert!(head[gap.clone()].iter().all(|&b| b == 0), "{gap:?}");
3037        }
3038    }
3039
3040    fn zone(source: &[i16], root_key: u8, top_note: u8, global_id: u32) -> NewZone<'_> {
3041        NewZone {
3042            source,
3043            channels: 1,
3044            root_key,
3045            top_note,
3046            global_id,
3047            loops: None,
3048            secondary_start: default_secondary_start(source.len(), None),
3049            shift: None,
3050            gain: 1.0,
3051            loop_decay: DEFAULT_LOOP_DECAY,
3052        }
3053    }
3054
3055    #[test]
3056    fn statistic_a_scales_a_24_bit_reciprocal_by_the_gain() {
3057        assert_eq!(statistic_a(4096, 2, u64::from(GAIN_UNITY)), (524_288, 12));
3058        assert_eq!(
3059            statistic_a(4096, 2, u64::from(GAIN_UNITY / 2)),
3060            (262_144, 12)
3061        );
3062        assert_eq!(
3063            statistic_a(4096, 2, 2 * u64::from(GAIN_UNITY)),
3064            (1_048_576, 12)
3065        );
3066        assert_eq!(statistic_a(1225, 0, 1_436_549), (1_200_837, 11));
3067        assert_eq!(statistic_a(4195, 2, 8_378_122), (8_180_401, 11));
3068        assert_eq!(statistic_a(1225, 0, 5_557_453), (4_645_576, 11));
3069    }
3070
3071    /// Every zone of an instrument reciprocates the same peak — the file's — so a
3072    /// quiet zone plays quietly rather than being normalised up to the loud one.
3073    #[test]
3074    fn statistic_a_reciprocates_the_loudest_zone_in_the_file() {
3075        let loud = sine(440.0, 12_000.0, 20_000);
3076        let quiet = sine(440.0, 3_000.0, 20_000);
3077        let file = built(
3078            &[zone(&loud, 72, 127, 1), zone(&quiet, 48, 71, 2)],
3079            "Two",
3080            Predictor::Plain,
3081        )
3082        .unwrap();
3083        let field = |s: &[u8], at: usize| u32::from_be_bytes([0, s[at], s[at + 1], s[at + 2]]);
3084        let streams = file.stroke_streams();
3085        let (mantissa, peak) = (|s| field(s, 9), |s| field(s, 13));
3086        let (first, second) = (streams[0].1, streams[1].1);
3087        assert!(peak(first) > peak(second));
3088        assert_eq!(mantissa(first), mantissa(second));
3089        assert_eq!(
3090            mantissa(second),
3091            statistic_a(peak(first), 0, u64::from(GAIN_UNITY)).0,
3092            "the quiet zone reciprocates the loud zone's peak"
3093        );
3094        assert_ne!(
3095            mantissa(second),
3096            statistic_a(peak(second), 0, u64::from(GAIN_UNITY)).0
3097        );
3098    }
3099
3100    #[test]
3101    fn a_zone_gain_scales_statistic_a_and_touches_nothing_else() {
3102        let source = sine(440.0, 12_000.0, 20_000);
3103        let unity = built(&[zone(&source, 60, 127, 1)], "Gain", Predictor::Plain).unwrap();
3104        let half = NewZone {
3105            gain: 0.5,
3106            ..zone(&source, 60, 127, 1)
3107        };
3108        let halved = built(&[half], "Gain", Predictor::Plain).unwrap();
3109        let (_, a) = unity.stroke_streams()[0];
3110        let (_, b) = halved.stroke_streams()[0];
3111        assert_eq!(a[..9], b[..9]);
3112        assert_eq!(a[12..], b[12..]);
3113        let mantissa = |s: &[u8]| u32::from_be_bytes([0, s[9], s[10], s[11]]);
3114        assert_eq!(mantissa(b), mantissa(a) / 2);
3115        assert_eq!(unity.zones().unwrap()[0].gain, GAIN_UNITY);
3116        assert_eq!(halved.zones().unwrap()[0].gain, GAIN_UNITY / 2);
3117
3118        let over = NewZone {
3119            gain: MAX_ZONE_GAIN * 2.0,
3120            ..zone(&source, 60, 127, 1)
3121        };
3122        assert!(built(&[over], "Gain", Predictor::Plain).is_err());
3123    }
3124
3125    #[test]
3126    fn every_zone_reads_back_paired_to_its_own_stroke() {
3127        let high = sine(880.0, 12_000.0, 12_000);
3128        let mid = sine(440.0, 12_000.0, 9_000);
3129        let low = sine(220.0, 12_000.0, 15_000);
3130        let file = built(
3131            &[
3132                zone(&high, 72, 96, 7),
3133                zone(&mid, 60, 65, 3),
3134                zone(&low, 48, 53, 9),
3135            ],
3136            "Three",
3137            Predictor::Plain,
3138        )
3139        .unwrap();
3140
3141        let read = super::super::from_bytes(&file.to_bytes().unwrap()).unwrap();
3142        assert_eq!(read.name().unwrap(), "Three");
3143        let zones = read.zones().unwrap();
3144        assert_eq!(
3145            zones.iter().map(|z| z.top_note).collect::<Vec<_>>(),
3146            [96, 65, 53]
3147        );
3148        assert_eq!(
3149            zones.iter().map(|z| z.stroke_id).collect::<Vec<_>>(),
3150            [7, 3, 9]
3151        );
3152        assert_eq!(
3153            read.strokes()
3154                .unwrap()
3155                .iter()
3156                .map(|s| s.root_key)
3157                .collect::<Vec<_>>(),
3158            [72, 60, 48]
3159        );
3160
3161        for (index, source) in [&high, &mid, &low].iter().enumerate() {
3162            let (at, stream) = read.zone_stream(index).unwrap();
3163            let audio = codec::decode(stream, at, codec::Layout::V2).unwrap();
3164            let plan = plan(source.len(), 1).unwrap();
3165            let q = quantise(source, &plan, None);
3166            let gain = 1i32 << q.shift;
3167            assert_eq!(audio.samples.len(), plan.fields, "zone {index}");
3168            for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3169                assert_eq!(i32::from(got), want * gain, "zone {index} field {f}");
3170            }
3171        }
3172    }
3173
3174    #[test]
3175    fn a_zone_decodes_the_same_alone_as_in_a_crowd() {
3176        let source = sine(330.0, 18_000.0, 20_000);
3177        let alone = narrow(instrument(&source, &Options::new("One").root_key(60)).unwrap());
3178        let crowd = built(
3179            &[
3180                zone(&sine(880.0, 9000.0, 8000), 72, 96, 3),
3181                zone(&source, 60, 65, 2),
3182                zone(&sine(110.0, 9000.0, 8000), 48, 53, 1),
3183            ],
3184            "Three",
3185            Predictor::default(),
3186        )
3187        .unwrap();
3188
3189        let one = alone.zone_stream(0).unwrap();
3190        let many = crowd.zone_stream(1).unwrap();
3191        assert_ne!(one.1, many.1, "the streams differ; only the audio must not");
3192        assert_eq!(
3193            codec::decode(one.1, one.0, codec::Layout::V2).unwrap(),
3194            codec::decode(many.1, many.0, codec::Layout::V2).unwrap()
3195        );
3196    }
3197
3198    #[test]
3199    fn every_stroke_is_its_own_header_length_plus_whole_packets() {
3200        let source = sine(440.0, 12_000.0, 12_000);
3201        for count in 1..=6usize {
3202            let zones: Vec<NewZone> = (0..count)
3203                .map(|i| zone(&source, 60, 120 - 10 * i as u8, i as u32 + 1))
3204                .collect();
3205            let file = built(&zones, "Ladder", Predictor::Plain).unwrap();
3206            let cat_len = section::find(&file.body.sections, section::CAT)
3207                .unwrap()
3208                .payload
3209                .len();
3210            let map_len = section::find(&file.body.sections, section::MAP)
3211                .unwrap()
3212                .payload
3213                .len();
3214            for (index, section) in file
3215                .body
3216                .sections
3217                .iter()
3218                .filter(|s| s.is(section::STK))
3219                .enumerate()
3220            {
3221                let head = super::super::stroke::header_len(
3222                    Layout::V2,
3223                    super::super::Chain::Library2,
3224                    index,
3225                    cat_len,
3226                    map_len,
3227                );
3228                assert_eq!(
3229                    (section.payload.len() - head) % PACKET_LEN,
3230                    0,
3231                    "{count} zones, stroke {index}: {} bytes over a {head}-byte header",
3232                    section.payload.len()
3233                );
3234            }
3235        }
3236    }
3237
3238    #[test]
3239    fn a_zone_list_the_format_cannot_store_is_refused() {
3240        let source = vec![0i16; MIN_FRAMES];
3241        let one = |root, top, id| built(&[zone(&source, root, top, id)], "x", Predictor::Plain);
3242        assert!(built(&[], "x", Predictor::Plain).is_err());
3243        assert!(one(60, 84, 0).is_err(), "id zero names no stroke");
3244        assert!(one(60, 84, 256).is_err(), "id past the record's one byte");
3245        assert!(one(60, 128, 1).is_err());
3246        assert!(one(128, 84, 1).is_err());
3247        assert!(one(60, 84, 1).is_ok());
3248
3249        let pair = |tops: [u8; 2], ids: [u32; 2]| {
3250            built(
3251                &[
3252                    zone(&source, 60, tops[0], ids[0]),
3253                    zone(&source, 48, tops[1], ids[1]),
3254                ],
3255                "x",
3256                Predictor::Plain,
3257            )
3258        };
3259        assert!(pair([84, 53], [1, 1]).is_err(), "duplicate stroke id");
3260        assert!(pair([53, 84], [2, 1]).is_err(), "zones out of order");
3261        assert!(pair([84, 84], [2, 1]).is_err(), "zones overlap");
3262        assert!(pair([84, 53], [2, 1]).is_ok());
3263    }
3264
3265    #[test]
3266    fn a_looped_plan_covers_every_field_exactly_once() {
3267        for (frames, start, end) in [
3268            (88_200, 16_384, 32_768),
3269            (88_200, 4_096, 20_480),
3270            (88_200, 92, 16_476),
3271            (88_200, 43_981, 60_365),
3272            (44_100, 20_000, 44_100),
3273        ] {
3274            let plan = looped(frames, 1, Loop::new(start, end)).unwrap();
3275            let points = plan.looped.unwrap();
3276            assert_eq!(
3277                plan.warmup + CELL * plan.cells_before + plan.resync + CELL * plan.cells_after,
3278                points.at,
3279                "{start}..{end}: the pre-roll does not reach the loop"
3280            );
3281            assert_eq!(
3282                points.at + points.warmup + CELL * points.cells,
3283                plan.fields,
3284                "{start}..{end}: the loop does not reach the terminator"
3285            );
3286            assert_eq!(points.at - fields_of(start).unwrap(), points.lead);
3287        }
3288    }
3289
3290    #[test]
3291    fn a_loop_comes_back_the_length_it_asked_for() {
3292        let source = sine(220.0, 18_000.0, 88_200);
3293        for (start, end) in [
3294            (16_384, 32_768),
3295            (43_981, 60_365),
3296            (4_096, 20_480),
3297            (65_536, 81_920),
3298        ] {
3299            let file = instrument(
3300                &source,
3301                &Options::new("Looped").loops(Loop::new(start, end)),
3302            )
3303            .unwrap_or_else(|e| panic!("loop {start}..{end}: {e}"));
3304            let (at, stroke) = file.stroke_streams()[0];
3305            let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3306            let mark = walk.records.iter().find(|r| r.mark).unwrap();
3307            let frames = (walk.fields - mark.first_field) as f64 * f64::from(codec::SOURCE_RATE)
3308                / f64::from(codec::FIELD_RATE);
3309            assert!(
3310                (frames - (end - start) as f64).abs() < 1.0,
3311                "loop {start}..{end} came back {frames} frames long"
3312            );
3313        }
3314    }
3315
3316    #[test]
3317    fn the_loop_starts_a_packet_and_the_directory_says_so() {
3318        let source = sine(330.0, 14_000.0, 60_000);
3319        for (start, end) in [(8_192, 24_576), (20_000, 40_000), (4_096, 59_000)] {
3320            for predictor in [Predictor::Plain, Predictor::Minimising] {
3321                let file = instrument(
3322                    &source,
3323                    &Options::new("Looped")
3324                        .predictor(predictor)
3325                        .loops(Loop::new(start, end)),
3326                )
3327                .unwrap();
3328                let (at, stroke) = file.stroke_streams()[0];
3329                let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3330                let directory = codec::Directory::read(stroke).unwrap();
3331                let marked: Vec<_> = walk.records.iter().filter(|r| r.mark).collect();
3332                assert_eq!(marked.len(), 1, "{start}..{end} {predictor:?}");
3333                assert_eq!(
3334                    codec::Directory::resolve(directory.mark, at, codec::Layout::V2),
3335                    marked[0].at
3336                );
3337                assert_ne!(directory.mark, directory.terminator);
3338                assert_eq!(
3339                    (walk.terminator - marked[0].at) % PACKET_WORDS,
3340                    0,
3341                    "{start}..{end} {predictor:?}: {} words",
3342                    walk.terminator - marked[0].at
3343                );
3344            }
3345        }
3346    }
3347
3348    #[test]
3349    fn an_unlooped_stroke_marks_nothing() {
3350        let file = encoded(&sine(440.0, 9_000.0, 44_100), Predictor::Plain);
3351        let (at, stroke) = file.stroke_streams()[0];
3352        let directory = codec::Directory::read(stroke).unwrap();
3353        assert_eq!(directory.mark, directory.terminator);
3354        assert!(codec::walk(stroke, at, codec::Layout::V2)
3355            .unwrap()
3356            .records
3357            .iter()
3358            .all(|r| !r.mark));
3359    }
3360
3361    #[test]
3362    fn the_tail_repeats_the_loops_opening() {
3363        let source = sine(200.0, 20_000.0, 88_200);
3364        let plan = looped(source.len(), 1, Loop::new(16_384, 32_768)).unwrap();
3365        let points = plan.looped.unwrap();
3366        let values = quantise(&source, &plan, None).values;
3367        assert_eq!(
3368            values[plan.fields - points.lead..],
3369            values[points.at - points.lead..points.at]
3370        );
3371    }
3372
3373    // (loop length, crossfade frames, fields the ramp covers).
3374    // Inferred from specimens; not confirmed on hardware.
3375    const MEASURED_FADES: &[(usize, f64, usize)] = &[
3376        (8_192, 81.92, 65),
3377        (8_192, 163.84, 130),
3378        (8_192, 409.6, 325),
3379        (8_192, 819.2, 650),
3380        (8_192, 1_638.4, 1_300),
3381        (8_192, 2_048.0, 1_626),
3382        (8_192, 3_276.8, 2_601),
3383        (8_192, 4_096.0, 3_251),
3384        (8_192, 6_144.0, 4_877),
3385        (8_192, 8_192.0, 6_502),
3386        (2_048, 512.0, 406),
3387        (4_096, 1_024.0, 813),
3388        (16_384, 4_096.0, 3_251),
3389        (32_768, 8_192.0, 6_502),
3390        (7_000, 700.0, 556),
3391        (10_000, 1_000.0, 794),
3392        (4_096, 409.6, 325),
3393        (1_024, 409.6, 325),
3394        (16_384, 256.0, 203),
3395        (16_384, 1_024.0, 813),
3396        (16_384, 8_192.0, 6_502),
3397    ];
3398
3399    #[test]
3400    fn the_fade_opens_where_the_editors_own_renders_open_it() {
3401        for &(length, crossfade, want) in MEASURED_FADES {
3402            let points = Loop::new(16_384, 16_384 + length).crossfade(crossfade);
3403            let plan = looped(88_200, 1, points).unwrap();
3404            assert_eq!(
3405                plan.looped.unwrap().crossfade,
3406                want,
3407                "a {crossfade} frame fade in a {length} frame loop"
3408            );
3409        }
3410    }
3411
3412    #[test]
3413    fn the_crossfade_ramps_linearly_into_the_material_before_the_loop() {
3414        let source = sine(150.0, 22_000.0, 88_200);
3415        let points = Loop::new(16_384, 32_768);
3416        let plan = looped(source.len(), 1, points).unwrap();
3417        let faded = looped(source.len(), 1, points.crossfade(4_096.0)).unwrap();
3418        let (plain, mixed) = (
3419            quantise(&source, &plan, None).values,
3420            quantise(&source, &faded, None).values,
3421        );
3422        assert_eq!(plain.len(), mixed.len());
3423
3424        let loop_at = faded.looped.unwrap();
3425        let end = faded.fields - loop_at.lead;
3426        let length = faded.fields - loop_at.at;
3427        let span = loop_at.crossfade;
3428        assert!(span > 3_000, "the fade is {span} fields");
3429        // Untouched in front of the fade, and the fade itself is the ramp.
3430        assert_eq!(plain[..end - span], mixed[..end - span]);
3431        for k in 0..span {
3432            let f = end - span + k;
3433            let (near, far) = (f64::from(plain[f]), f64::from(plain[f - length]));
3434            let u = k as f64 / span as f64;
3435            let want = near + (far - near) * u;
3436            assert!(
3437                (f64::from(mixed[f]) - want).abs() <= 1.0,
3438                "field {f}: {} against {want}",
3439                mixed[f]
3440            );
3441        }
3442    }
3443
3444    #[test]
3445    fn a_crossfade_may_begin_before_the_loop_start() {
3446        let source = sine(150.0, 22_000.0, 60_000);
3447        let points = Loop::new(16_384, 24_576).crossfade(16_384.0);
3448        let plan = looped(source.len(), 1, points).unwrap();
3449        let looped = plan.looped.unwrap();
3450
3451        assert!(looped.crossfade > fields_of(points.end - points.start).unwrap());
3452        assert!(looped.crossfade <= fields_of(points.start).unwrap());
3453        let file = instrument(&source, &Options::new("Long fade").loops(points)).unwrap();
3454        let (at, stroke) = file.stroke_streams()[0];
3455        assert!(codec::decode(stroke, at, codec::Layout::V2).is_ok());
3456    }
3457
3458    #[test]
3459    fn a_loop_the_format_cannot_state_is_refused() {
3460        let frames = 44_100;
3461        let stated = |points| looped(frames, 1, points);
3462        assert!(stated(Loop::new(8_192, 40_000)).is_ok());
3463        assert!(stated(Loop::new(8_192, 8_192)).is_err(), "empty loop");
3464        assert!(stated(Loop::new(40_000, 8_192)).is_err(), "loop runs back");
3465        assert!(stated(Loop::new(8_192, 44_101)).is_err(), "past the audio");
3466        assert!(
3467            stated(Loop::new(8_192, 8_250)).is_err(),
3468            "shorter than a run"
3469        );
3470        assert!(
3471            stated(Loop::new(1_024, 40_000).crossfade(4_096.0)).is_err(),
3472            "nothing in front of the loop to fade from"
3473        );
3474        assert!(
3475            stated(Loop::new(8_192, 40_000).crossfade(40_000.0)).is_err(),
3476            "not enough material before the fade"
3477        );
3478        // Below the shortest stroke the editor encodes, whatever the loop says.
3479        assert!(looped(MIN_FRAMES - 1, 1, Loop::new(10, 60)).is_err());
3480    }
3481
3482    #[test]
3483    fn a_looped_stroke_round_trips_through_the_decoder_exactly() {
3484        let source = sine(180.0, 16_000.0, 60_000);
3485        for predictor in [Predictor::Plain, Predictor::Minimising] {
3486            for points in [
3487                Loop::new(8_192, 40_960),
3488                Loop::new(8_192, 40_960).crossfade(4_096.0),
3489            ] {
3490                let file = instrument(
3491                    &source,
3492                    &Options::new("Looped").predictor(predictor).loops(points),
3493                )
3494                .unwrap();
3495                let (at, stroke) = file.stroke_streams()[0];
3496                let plan = looped(source.len(), 1, points).unwrap();
3497                let q = quantise(&source, &plan, None);
3498                let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3499                assert_eq!(audio.samples.len(), plan.fields);
3500                let gain = 1i32 << q.shift;
3501                for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3502                    assert_eq!(i32::from(got), want * gain, "{predictor:?} field {f}");
3503                }
3504            }
3505        }
3506    }
3507
3508    // Full-scale broadband material can exhaust the three spare bits per field before
3509    // a short loop reaches the next packet boundary.
3510    /// A loop region with nothing left to split is widened forward, each content record
3511    /// spent up to the cap before the next is touched, so the last one widened takes
3512    /// only the words still owed. Widening from the back instead finishes in fewer,
3513    /// wider records, which is not what the editor writes.
3514    ///
3515    /// The alignment run the region opens with is walked past however much room it has,
3516    /// and however many records it takes — the marked one here is followed by a second.
3517    ///
3518    /// The two wide generations lay the same mono region out in the same words, so the
3519    /// widths they finish on differ only by the cap: v3 stops one width below v4 and
3520    /// the deficit runs on into the next record.
3521    #[test]
3522    fn the_widen_fallback_walks_past_the_regions_alignment_records() {
3523        for (layout, widths) in [
3524            (Layout::V3, [1, 1, 13, 9, 1, 1]),
3525            (Layout::V4, [1, 1, 14, 8, 1, 1]),
3526        ] {
3527            let units = Units {
3528                layout,
3529                channels: 1,
3530            };
3531            let record = Spec {
3532                one_to_one: false,
3533                width: 1,
3534                order: 0,
3535                mark: false,
3536                first: 0,
3537                count: units.cell(),
3538            };
3539            let opening = Spec {
3540                one_to_one: true,
3541                ..record
3542            };
3543            let mut specs = vec![
3544                Spec {
3545                    mark: true,
3546                    ..opening
3547                },
3548                opening,
3549                record,
3550                record,
3551                record,
3552                record,
3553            ];
3554            pad_to_packet(&mut specs, 0, units).unwrap();
3555            assert_eq!(
3556                specs.iter().map(|s| s.width).collect::<Vec<_>>(),
3557                widths,
3558                "{layout:?}"
3559            );
3560            let words: usize = specs.iter().map(|s| s.span(units)).sum();
3561            assert_eq!(words % units.packet_words(), 0, "{layout:?}");
3562        }
3563    }
3564
3565    /// The cap is the generation's own constant, so a content record sitting one width
3566    /// under it is widened past itself before the next record is reached — and only as
3567    /// far as the cap, whatever room the record still has. Each region here opens with
3568    /// its alignment run and is two words short of a whole packet: the narrow chain and
3569    /// v3 spend those two words one to a record, v4 spends both on the first.
3570    #[test]
3571    fn the_widen_cap_is_the_generations_constant() {
3572        for (layout, alignment, content, spent) in [
3573            (Layout::V2, 2usize, 9usize, [13u8, 13]),
3574            (Layout::V3, 1, 2, [13, 13]),
3575            (Layout::V4, 1, 2, [14, 12]),
3576        ] {
3577            let units = Units {
3578                layout,
3579                channels: 1,
3580            };
3581            // Cell-sized, so the region has nothing left to split and must be widened.
3582            let record = Spec {
3583                one_to_one: false,
3584                width: 12,
3585                order: 0,
3586                mark: false,
3587                first: 0,
3588                count: units.cell(),
3589            };
3590            let mut specs: Vec<Spec> = (0..alignment)
3591                .map(|i| Spec {
3592                    one_to_one: true,
3593                    width: 3,
3594                    mark: i == 0,
3595                    ..record
3596                })
3597                .chain(std::iter::repeat_n(record, content))
3598                .collect();
3599            pad_to_packet(&mut specs, 0, units).unwrap();
3600
3601            let mut want = vec![3u8; alignment];
3602            want.extend(spent);
3603            want.resize(alignment + content, record.width);
3604            assert_eq!(
3605                specs.iter().map(|s| s.width).collect::<Vec<_>>(),
3606                want,
3607                "{layout:?}"
3608            );
3609            let words: usize = specs.iter().map(|s| s.span(units)).sum();
3610            assert_eq!(words % units.packet_words(), 0, "{layout:?}");
3611        }
3612    }
3613
3614    #[test]
3615    fn a_loop_that_needs_width_past_the_measured_cap_is_refused() {
3616        let units = Units {
3617            layout: Layout::V3,
3618            channels: 1,
3619        };
3620        let record = Spec {
3621            one_to_one: false,
3622            width: widen_cap(Layout::V3),
3623            order: 0,
3624            mark: false,
3625            first: 0,
3626            count: units.cell(),
3627        };
3628        let mut specs = vec![
3629            Spec {
3630                one_to_one: true,
3631                width: 1,
3632                mark: true,
3633                ..record
3634            },
3635            record,
3636            record,
3637        ];
3638        let before = specs.clone();
3639        assert!(pad_to_packet(&mut specs, 0, units).is_err());
3640        assert_eq!(specs, before);
3641    }
3642
3643    #[test]
3644    fn a_loop_lands_on_a_packet_boundary_or_is_refused() {
3645        let mut source = Vec::with_capacity(60_000);
3646        let mut state = 12_345u64;
3647        for k in 0..60_000u64 {
3648            state = state
3649                .wrapping_mul(6_364_136_223_846_793_005)
3650                .wrapping_add(1);
3651            let noise = ((state >> 40) as i32 - 8_192) / 4;
3652            let tone = (20_000.0 * (k as f64 * 0.031).sin()) as i32;
3653            source.push((tone + noise).clamp(-32_768, 32_767) as i16);
3654        }
3655
3656        let mut placed = 0usize;
3657        let mut refused = 0usize;
3658        for start in (4_096..48_000).step_by(7_919) {
3659            for length in [900, 1_500, 4_096, 11_000] {
3660                for predictor in [Predictor::Plain, Predictor::Minimising] {
3661                    let points =
3662                        Loop::new(start, start + length).crossfade((length / 4).min(start) as f64);
3663                    let options = Options::new("Sweep").predictor(predictor).loops(points);
3664                    let Ok(file) = instrument(&source, &options) else {
3665                        refused += 1;
3666                        continue;
3667                    };
3668                    let (at, stroke) = file.stroke_streams()[0];
3669                    let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3670                    let mark = walk.records.iter().find(|r| r.mark).unwrap();
3671                    assert_eq!(
3672                        (walk.terminator - mark.at) % PACKET_WORDS,
3673                        0,
3674                        "loop {start}..{} under {predictor:?} covers {} words",
3675                        start + length,
3676                        walk.terminator - mark.at
3677                    );
3678                    placed += 1;
3679                }
3680            }
3681        }
3682        assert!(placed > 0, "no loop was placed");
3683        assert!(refused > 0, "no loop was refused");
3684    }
3685
3686    fn stereo(hz: f64, ratio: f64, amplitude: f64, frames: usize) -> Vec<i16> {
3687        let left = sine(hz, amplitude, frames);
3688        let right = sine(hz * ratio, amplitude * 0.6, frames);
3689        left.iter()
3690            .zip(&right)
3691            .flat_map(|(&l, &r)| [l, r])
3692            .collect()
3693    }
3694
3695    #[test]
3696    fn a_stereo_plan_is_the_mono_plan_doubled() {
3697        for frames in [4096, 4409, 8192, 10_000, 44_100, 100_000, 441_000] {
3698            let mono = plan(frames, 1).unwrap();
3699            let both = plan(frames, 2).unwrap();
3700            assert_eq!(both.fields, 2 * mono.fields, "{frames} frames: T");
3701            assert_eq!(both.resync_at, 2 * mono.resync_at, "{frames} frames: R1");
3702            assert_eq!(both.warmup, 2 * mono.warmup, "{frames} frames: W");
3703            assert_eq!(both.resync, 2 * mono.resync, "{frames} frames: R");
3704            assert_eq!(both.cells_before, mono.cells_before, "{frames} frames");
3705            assert_eq!(both.cells_after, mono.cells_after, "{frames} frames");
3706            assert_eq!(
3707                both.warmup
3708                    + both.cell() * both.cells_before
3709                    + both.resync
3710                    + both.cell() * both.cells_after,
3711                both.fields,
3712                "{frames} frames: the plan does not tile the lattice"
3713            );
3714        }
3715    }
3716
3717    #[test]
3718    fn a_stereo_stroke_round_trips_through_the_decoder_exactly() {
3719        for predictor in [Predictor::Plain, Predictor::Minimising] {
3720            let source = stereo(220.0, 1.5, 14_000.0, 30_000);
3721            let file = instrument(
3722                &source,
3723                &Options::new("Stereo").channels(2).predictor(predictor),
3724            )
3725            .unwrap();
3726            let (at, stroke) = file.stroke_streams()[0];
3727
3728            let stream = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3729            assert_eq!(stream.channels, 2, "{predictor:?}");
3730            assert_eq!(stream.cell, Some(2 * CELL), "{predictor:?}");
3731            assert_eq!(&stroke[stroke.len() - 3..], &[0x80, 0, 48]);
3732
3733            let plan = plan(30_000, 2).unwrap();
3734            let q = quantise(&source, &plan, None);
3735            let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3736            assert_eq!(audio.channels, 2);
3737            assert_eq!(audio.samples.len(), plan.fields);
3738            let gain = 1i32 << q.shift;
3739            for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3740                assert_eq!(i32::from(got), want * gain, "{predictor:?} field {f}");
3741            }
3742        }
3743    }
3744
3745    #[test]
3746    fn each_channel_predicts_against_its_own_history() {
3747        let frames = 20_000;
3748        let source: Vec<i16> = (0..frames)
3749            .flat_map(|k| {
3750                let up = (k as i32 % 2048) - 1024;
3751                [up as i16, -(up as i16)]
3752            })
3753            .collect();
3754        let file = instrument(
3755            &source,
3756            &Options::new("Ramps")
3757                .channels(2)
3758                .predictor(Predictor::Minimising),
3759        )
3760        .unwrap();
3761        let (at, stroke) = file.stroke_streams()[0];
3762        let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3763        assert!(audio.differenced > 0, "nothing chose a predictor");
3764
3765        let plan = plan(frames, 2).unwrap();
3766        let q = quantise(&source, &plan, None);
3767        let gain = 1i32 << q.shift;
3768        for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3769            assert_eq!(i32::from(got), want * gain, "field {f}");
3770        }
3771    }
3772
3773    #[test]
3774    fn the_channels_are_resampled_apart() {
3775        let frames = 12_000;
3776        let source: Vec<i16> = sine(300.0, 20_000.0, frames)
3777            .into_iter()
3778            .flat_map(|l| [l, 0])
3779            .collect();
3780        let file = instrument(&source, &Options::new("Panned").channels(2)).unwrap();
3781        let (at, stroke) = file.stroke_streams()[0];
3782        let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3783        assert!(audio.samples.iter().step_by(2).any(|&v| v.abs() > 10_000));
3784        assert!(audio.samples[1..].iter().step_by(2).all(|&v| v == 0));
3785    }
3786
3787    #[test]
3788    fn a_stereo_stroke_loops_the_way_a_mono_one_does() {
3789        let source = stereo(180.0, 1.25, 16_000.0, 60_000);
3790        let points = Loop::new(8_192, 40_960).crossfade(2_048.0);
3791        let file = instrument(&source, &Options::new("Looped").channels(2).loops(points)).unwrap();
3792        let (at, stroke) = file.stroke_streams()[0];
3793        let walk = codec::walk(stroke, at, codec::Layout::V2).unwrap();
3794        assert_eq!(walk.channels, 2);
3795        let mark = walk.records.iter().find(|r| r.mark).unwrap();
3796        assert_eq!((walk.terminator - mark.at) % PACKET_WORDS, 0);
3797        let frames = (walk.fields - mark.first_field) as f64 / 2.0 * f64::from(codec::SOURCE_RATE)
3798            / f64::from(codec::FIELD_RATE);
3799        assert!(
3800            (frames - 32_768.0).abs() < 1.0,
3801            "loop came back {frames} frames"
3802        );
3803
3804        let plan = looped(60_000, 2, points).unwrap();
3805        let q = quantise(&source, &plan, None);
3806        let audio = codec::decode(stroke, at, codec::Layout::V2).unwrap();
3807        let gain = 1i32 << q.shift;
3808        for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3809            assert_eq!(i32::from(got), want * gain, "field {f}");
3810        }
3811    }
3812
3813    #[test]
3814    fn a_channel_count_the_terminator_cannot_state_is_refused() {
3815        let source = vec![0i16; 3 * MIN_FRAMES];
3816        assert!(plan(MIN_FRAMES, 0).is_err());
3817        assert!(plan(MIN_FRAMES, 3).is_err());
3818        assert!(instrument(&source, &Options::new("x").channels(3)).is_err());
3819        assert!(instrument(
3820            &vec![0i16; 2 * MIN_FRAMES + 1],
3821            &Options::new("x").channels(2)
3822        )
3823        .is_err());
3824        assert!(instrument(&vec![0i16; 2 * MIN_FRAMES], &Options::new("x").channels(2)).is_ok());
3825        let short = vec![0i16; MIN_FRAMES];
3826        assert!(instrument(&short, &Options::new("x")).is_ok());
3827        assert!(instrument(&short, &Options::new("x").channels(2)).is_err());
3828    }
3829
3830    /// Every generation, mono and stereo, through this crate's own decoder. v4 stereo
3831    /// is the one that packs each channel's half into its own words, so it is the one
3832    /// this would catch.
3833    #[test]
3834    fn every_generation_round_trips_through_the_decoder_exactly() {
3835        for layout in [Layout::V2, Layout::V3, Layout::V4] {
3836            for channels in [1u16, 2] {
3837                let frames = 30_000;
3838                let source: Vec<i16> = match channels {
3839                    1 => sine(220.0, 14_000.0, frames),
3840                    _ => stereo(220.0, 1.5, 14_000.0, frames),
3841                };
3842                let file = instrument(
3843                    &source,
3844                    &Options::new("Round trip")
3845                        .layout(layout)
3846                        .channels(channels)
3847                        .predictor(Predictor::Minimising),
3848                )
3849                .unwrap();
3850                let (at, stroke) = file.stroke_streams()[0];
3851                let plan = Plan::new(
3852                    layout,
3853                    frames,
3854                    usize::from(channels),
3855                    default_secondary_start(frames, None),
3856                )
3857                .unwrap();
3858                let q = quantise(&source, &plan, None);
3859                let audio = codec::decode(stroke, at, layout)
3860                    .unwrap_or_else(|e| panic!("{layout:?} {channels}ch: {e}"));
3861                assert_eq!(audio.channels, channels, "{layout:?} {channels}ch");
3862                assert_eq!(audio.samples.len(), plan.fields, "{layout:?} {channels}ch");
3863                let gain = 1i32 << q.shift;
3864                for (f, (&want, &got)) in q.values.iter().zip(&audio.samples).enumerate() {
3865                    assert_eq!(
3866                        i32::from(got),
3867                        want * gain,
3868                        "{layout:?} {channels}ch field {f}"
3869                    );
3870                }
3871            }
3872        }
3873    }
3874
3875    #[test]
3876    fn a_wide_instrument_reads_back_as_one() {
3877        for (layout, version) in [(Layout::V3, 300u32), (Layout::V4, 400)] {
3878            let file = instrument(
3879                &sine(220.0, 15_000.0, 30_000),
3880                &Options::new("Encoded")
3881                    .layout(layout)
3882                    .root_key(48)
3883                    .top_note(72),
3884            )
3885            .unwrap();
3886            let bytes = file.to_bytes().unwrap();
3887            let read = crate::from_stream(&mut std::io::Cursor::new(&bytes)).unwrap();
3888            let crate::Entity::Sample(read) = read else {
3889                panic!("{layout:?} did not read back as a sample");
3890            };
3891            assert_eq!(read.name().unwrap(), "Encoded", "{layout:?}");
3892            assert_eq!(read.layout().unwrap(), layout, "{layout:?}");
3893            assert_eq!(read.to_bytes().unwrap(), bytes, "{layout:?}");
3894            let crate::Sample::V3(read) = &read else {
3895                panic!("{layout:?} did not read back on the wide chain");
3896            };
3897            assert_eq!(read.header.version, version);
3898            let zones = read.zones().unwrap();
3899            assert_eq!(zones.len(), 1);
3900            assert_eq!(zones[0].root_key, 48);
3901            assert_eq!(zones[0].top_note, 72);
3902            assert_eq!(zones[0].low_note, Some(super::super::zone::KEY_FLOOR));
3903            assert_eq!(
3904                read.meta().unwrap().chain_len as usize,
3905                read.chain_len_before_meta()
3906            );
3907        }
3908    }
3909
3910    /// Zones tile: each reaches down to one above the one below it, and the lowest to
3911    /// the keyboard's floor. Records are stored high to low in every generation.
3912    #[test]
3913    fn a_wide_zone_states_its_own_bottom() {
3914        let high = sine(880.0, 12_000.0, 12_000);
3915        let low = sine(220.0, 12_000.0, 15_000);
3916        let floor = super::super::zone::KEY_FLOOR;
3917        let stored = [(96, 66), (65, floor)];
3918        for layout in [Layout::V3, Layout::V4] {
3919            let file = multi_zone(
3920                made("Two", Predictor::Plain, layout),
3921                &[zone(&high, 72, 96, 2), zone(&low, 48, 65, 1)],
3922            )
3923            .unwrap();
3924            let zones = file.zones().unwrap();
3925            assert_eq!(
3926                zones
3927                    .iter()
3928                    .map(|z| (z.top_note, z.low_note.unwrap()))
3929                    .collect::<Vec<_>>(),
3930                stored,
3931                "{layout:?}"
3932            );
3933        }
3934    }
3935
3936    /// Decibel words read off editor renders of one project at sixteen stroke gains,
3937    /// four of them predicted before the render and landing on it. The logarithm is
3938    /// evaluated wider than the field and rounded once: computing it in float32
3939    /// throughout moves the last byte on the powers of two.
3940    #[test]
3941    fn a_zone_gain_in_decibels_is_the_word_the_editor_writes() {
3942        for (gain, word) in [
3943            (-1.0, 0x7fc0_0000u32),
3944            (0.0, 0xff80_0000),
3945            (0.01, 0xc220_0000),
3946            (0.1, 0xc1a0_0000),
3947            (0.5, 0xc0c0_a8c1),
3948            (1.0, 0x0000_0000),
3949            (1.1, 0x3f53_ee38),
3950            (1.5, 0x4061_6595),
3951            (2.0, 0x40c0_a8c1),
3952            (4.0, 0x4140_a8c1),
3953            (8.0, 0x4190_7e91),
3954            (16.0, 0x41c0_a8c1),
3955            (20.5, 0x41d1_e170),
3956            (63.75, 0x4210_5bc1),
3957            (333.33, 0x4249_d478),
3958            (1000.0, 0x4270_0000),
3959        ] {
3960            assert_eq!(gain_decibels(gain).to_bits(), word, "a gain of {gain}");
3961        }
3962    }
3963
3964    /// Statistic A's mantissa is built from the decibel and not from the project's
3965    /// float. The two part company only past `2^24`, and these deltas are what the
3966    /// editor writes there.
3967    #[test]
3968    fn the_gain_statistic_a_uses_is_the_decibels_round_trip() {
3969        for (gain, delta) in [
3970            (0.01, 0i64),
3971            (1.1, 0),
3972            (15.99, 0),
3973            (16.0, -1),
3974            (20.5, -1),
3975            (24.0, 1),
3976            (33.0, 2),
3977            (48.0, -1),
3978            (63.75, -3),
3979            (100.0, 0),
3980            (333.33, 39),
3981            (1000.0, 0),
3982        ] {
3983            let plain = (gain * f64::from(GAIN_UNITY)).round() as i64;
3984            let round_trip = gain_units(gain_decibels(gain)) as i64;
3985            assert_eq!(round_trip - plain, delta, "a gain of {gain}");
3986        }
3987    }
3988
3989    /// Fixed-point words read off editor renders of one project at eleven map gains.
3990    /// The ceiling is a clamp on the decibel: the knee sits on a round +9.000 dB
3991    /// rather than on a round linear number, and a negative gain — whose decibel is a
3992    /// NaN — fails the comparison and takes the ceiling rather than the floor.
3993    #[test]
3994    fn a_map_gain_is_the_word_the_editor_writes_and_clamps_at_the_ceiling() {
3995        for (gain, units) in [
3996            (-1.0, 0x2d_18_19_u32),
3997            (0.0, 0x00_00_00),
3998            (0.0001, 0x00_00_69),
3999            (0.01, 0x00_28_f6),
4000            (0.5, 0x08_00_00),
4001            (1.0, 0x10_00_00),
4002            (1.1, 0x11_99_9a),
4003            (2.0, 0x20_00_00),
4004            (2.8125, 0x2d_00_00),
4005            (2.828125, 0x2d_18_19),
4006            (4.0, 0x2d_18_19),
4007            (16.0, 0x2d_18_19),
4008        ] {
4009            assert_eq!(map_gain_units(gain), units, "a map gain of {gain}");
4010        }
4011    }
4012
4013    /// A zone gain past 16 overflows both u24 stores, by different rules: the record
4014    /// takes the project's float and wraps, and the mantissa takes the decibel's round
4015    /// trip and truncates into its field. Words read off editor renders.
4016    #[test]
4017    fn a_zone_gain_past_sixteen_wraps_in_both_stores() {
4018        for (gain, record) in [
4019            (-1.0, 0x00_00_00_u32),
4020            (0.0, 0x00_00_00),
4021            (15.99, 0xff_d7_0a),
4022            (16.0, 0x00_00_00),
4023            (33.0, 0x10_00_00),
4024            (333.33, 0xd5_47_ae),
4025            (1000.0, 0x80_00_00),
4026        ] {
4027            assert_eq!(zone_record_gain(gain), record, "a gain of {gain}");
4028        }
4029        // `WG-base`'s peak is 4096, so the reciprocal is 2^22 and every step below is
4030        // exact in integers.
4031        for (gain, mantissa) in [
4032            (-1.0, 0x00_00_00_u32),
4033            (0.0, 0x00_00_00),
4034            (15.99, 0x7f_eb_85),
4035            (16.0, 0x7f_ff_ff),
4036            (33.0, 0x08_00_01),
4037            (333.33, 0x6a_a3_ea),
4038            (1000.0, 0x40_00_00),
4039        ] {
4040            let (got, _) = statistic_a(4096, 0, gain_units(gain_decibels(gain)));
4041            assert_eq!(got, mantissa, "a gain of {gain}");
4042        }
4043    }
4044
4045    /// The map gain opens the `map` section and reaches nothing else — not the zone
4046    /// records, not statistic A, not a stream byte.
4047    #[test]
4048    fn a_map_gain_moves_the_map_section_alone() {
4049        let source = sine(440.0, 12_000.0, 20_000);
4050        for layout in [Layout::V2, Layout::V3, Layout::V4] {
4051            let unity = made("Map", Predictor::Plain, layout);
4052            let quiet = Instrument {
4053                map_gain: 0.5,
4054                ..unity
4055            };
4056            let one = [zone(&source, 60, 127, 1)];
4057            let before = multi_zone(unity, &one).unwrap().to_bytes().unwrap();
4058            let after = multi_zone(quiet, &one).unwrap().to_bytes().unwrap();
4059            assert_eq!(before.len(), after.len(), "{layout:?}");
4060            let moved: Vec<_> = (0..before.len())
4061                .filter(|&i| before[i] != after[i])
4062                .collect();
4063            // The gain's own top byte — 0x10 against 0x08 — and the container checksum.
4064            assert!(moved.len() <= 1 + 4, "{layout:?}: {moved:?}");
4065        }
4066    }
4067
4068    #[test]
4069    fn a_project_preset_reaches_each_generation_in_its_own_schema() {
4070        let source = sine(440.0, 12_000.0, 20_000);
4071        let preset = Preset {
4072            dynamics_enabled: true,
4073            velocity_to_amplitude: 2,
4074            velocity_to_timbre: 0,
4075        };
4076        for layout in [Layout::V2, Layout::V3, Layout::V4] {
4077            let instrument = Instrument {
4078                preset,
4079                ..made("Preset", Predictor::Plain, layout)
4080            };
4081            let sample = multi_zone(instrument, &[zone(&source, 60, 127, 1)]).unwrap();
4082            match sample {
4083                crate::Sample::V2(file) => {
4084                    let sty = section::find(&file.body.sections, section::STY).unwrap();
4085                    assert_eq!(sty.payload, [0, 1, 0, 1, 2, 0, 0, 0, 0]);
4086                }
4087                crate::Sample::V3(file) => {
4088                    let sty = section::find4(&file.body.sections, section::STY4).unwrap();
4089                    match layout {
4090                        Layout::V3 => {
4091                            assert_eq!((sty.payload[4], sty.payload[12]), (43, 74));
4092                            assert_eq!((sty.payload[14], sty.payload[16]), (1, 74));
4093                        }
4094                        Layout::V4 => {
4095                            assert_eq!((sty.payload[3], sty.payload[4]), (1, 1));
4096                            assert_eq!(sty.payload[85..88], [74, 82, 90]);
4097                        }
4098                        Layout::V2 => unreachable!(),
4099                    }
4100                }
4101            }
4102        }
4103    }
4104
4105    /// A wide zone gain reaches the stroke header's decibel field and statistic A, and
4106    /// nothing else: no byte of the 16-byte zone record moves with it.
4107    #[test]
4108    fn a_wide_zone_gain_lands_in_the_stroke_header() {
4109        let source = sine(440.0, 12_000.0, 20_000);
4110        for layout in [Layout::V3, Layout::V4] {
4111            let one = zone(&source, 60, 127, 1);
4112            let made = made("Gain", Predictor::Plain, layout);
4113            let unity = multi_zone(made, &[one]).unwrap();
4114            let halved = multi_zone(made, &[NewZone { gain: 0.5, ..one }]).unwrap();
4115            let (_, a) = unity.stroke_streams()[0];
4116            let (_, b) = halved.stroke_streams()[0];
4117            let mantissa = |s: &[u8]| u32::from_be_bytes([0, s[9], s[10], s[11]]);
4118            assert_eq!(mantissa(b), mantissa(a) / 2, "{layout:?}");
4119            let gain_at = codec::TAIL_FLOATS_AT[0];
4120            assert_eq!(a[..9], b[..9], "{layout:?}");
4121            assert_eq!(a[12..gain_at], b[12..gain_at], "{layout:?}");
4122            assert_eq!(a[gain_at + 4..], b[gain_at + 4..], "{layout:?}");
4123            assert_eq!(
4124                codec::zone_gain_db(b, layout),
4125                Some(gain_decibels(0.5)),
4126                "{layout:?}"
4127            );
4128            // Statistic A's mantissa, the decibel word and the container checksum are
4129            // the whole of what a zone gain moves; the zone record does not.
4130            let (before, after) = (unity.to_bytes().unwrap(), halved.to_bytes().unwrap());
4131            let differing = before.iter().zip(&after).filter(|(x, y)| x != y).count();
4132            assert_eq!(before.len(), after.len(), "{layout:?}");
4133            assert!(differing <= 3 + 4 + 4, "{layout:?}: {differing} bytes");
4134        }
4135    }
4136
4137    /// The loop decay amount is the wide header's second float32, verbatim in the
4138    /// project's own units, and the narrow header is too short to hold it at all.
4139    #[test]
4140    fn a_loop_decay_lands_in_the_wide_header_and_nowhere_narrow() {
4141        let source = sine(440.0, 12_000.0, 20_000);
4142        let at = codec::TAIL_FLOATS_AT[1];
4143        for layout in [Layout::V2, Layout::V3, Layout::V4] {
4144            let one = zone(&source, 60, 127, 1);
4145            let made = made("Decay", Predictor::Plain, layout);
4146            let base = multi_zone(made, &[one]).unwrap();
4147            let slower = multi_zone(
4148                made,
4149                &[NewZone {
4150                    loop_decay: 60.0,
4151                    ..one
4152                }],
4153            )
4154            .unwrap();
4155            let (_, a) = base.stroke_streams()[0];
4156            let (_, b) = slower.stroke_streams()[0];
4157            let wide = layout != Layout::V2;
4158            assert_eq!(
4159                codec::loop_decay(a, layout),
4160                wide.then_some(DEFAULT_LOOP_DECAY),
4161                "{layout:?}"
4162            );
4163            assert_eq!(
4164                codec::loop_decay(b, layout),
4165                wide.then_some(60.0),
4166                "{layout:?}"
4167            );
4168            match wide {
4169                false => assert_eq!(a, b),
4170                true => {
4171                    assert_eq!(a[..at], b[..at], "{layout:?}");
4172                    assert_eq!(a[at + 4..], b[at + 4..], "{layout:?}");
4173                }
4174            }
4175        }
4176    }
4177
4178    #[test]
4179    fn a_zone_gain_past_the_measured_range_is_refused() {
4180        let source = sine(440.0, 12_000.0, 20_000);
4181        for layout in [Layout::V2, Layout::V3, Layout::V4] {
4182            for gain in [MAX_ZONE_GAIN * 2.0, f64::NAN, f64::INFINITY] {
4183                let loud = NewZone {
4184                    gain,
4185                    ..zone(&source, 60, 127, 1)
4186                };
4187                assert!(
4188                    multi_zone(made("Gain", Predictor::Plain, layout), &[loud]).is_err(),
4189                    "{layout:?} at {gain}"
4190                );
4191            }
4192            let wrapping = NewZone {
4193                gain: MAX_ZONE_GAIN,
4194                ..zone(&source, 60, 127, 1)
4195            };
4196            assert!(multi_zone(made("Gain", Predictor::Plain, layout), &[wrapping]).is_ok());
4197        }
4198    }
4199
4200    /// The wide terminator states 32 fields per channel where the narrow one states 24,
4201    /// and a 1:1 run reaches 48 fields per channel rather than 32.
4202    #[test]
4203    fn the_wide_plan_tiles_the_lattice_in_its_own_units() {
4204        for frames in [4096, 10_000, 44_100, 100_000] {
4205            for layout in [Layout::V3, Layout::V4] {
4206                let p =
4207                    Plan::new(layout, frames, 1, default_secondary_start(frames, None)).unwrap();
4208                assert_eq!(p.cell(), 32, "{layout:?} {frames} frames");
4209                assert_eq!(
4210                    p.warmup + p.cell() * p.cells_before + p.resync + p.cell() * p.cells_after,
4211                    p.fields,
4212                    "{layout:?} {frames} frames"
4213                );
4214                for run in chunks(p.warmup, p.chunk())
4215                    .into_iter()
4216                    .chain(chunks(p.resync, p.chunk()))
4217                {
4218                    assert!(
4219                        (32..=48).contains(&run),
4220                        "{layout:?} {frames} frames: {run}"
4221                    );
4222                }
4223            }
4224        }
4225    }
4226
4227    /// A silent wide stroke stores statistic B as a signed extreme, so it reads back
4228    /// through the codec's own sign rule rather than as a 24-bit magnitude.
4229    #[test]
4230    fn a_wide_statistic_b_carries_the_extremes_sign() {
4231        let frames = 20_000;
4232        let mut down = vec![0i16; frames];
4233        down[10_000] = -13;
4234        for layout in [Layout::V2, Layout::V3, Layout::V4] {
4235            let file = instrument(&down, &Options::new("Peak").layout(layout)).unwrap();
4236            let (_, stroke) = file.stroke_streams()[0];
4237            let want = if layout.signed_peak() { -3 } else { 3 };
4238            assert_eq!(codec::peak(stroke, layout), Some(want), "{layout:?}");
4239        }
4240    }
4241
4242    #[test]
4243    fn silence_codes_at_the_draft_width_throughout() {
4244        let file = encoded(&vec![0i16; 44_100], Predictor::Plain);
4245        let (at, stroke) = file.stroke_streams()[0];
4246        let stream = codec::walk(stroke, at, codec::Layout::V2).unwrap();
4247        assert!(stream.records.iter().all(|r| r.width == MIN_WIDTH));
4248        assert!(stream
4249            .records
4250            .iter()
4251            .all(|r| r.values.iter().all(|&v| v == 0)));
4252        assert_eq!(codec::peak(stroke, codec::Layout::V2), Some(0));
4253        assert!(codec::decode(stroke, at, codec::Layout::V2)
4254            .unwrap()
4255            .samples
4256            .iter()
4257            .all(|&s| s == 0));
4258    }
4259}