nord-format 0.6.0

Read and write Nord keyboard files from Rust, byte for byte
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
//! Range-checked integers for bit-packed fields — an out-of-range stored
//! value is a decode error, never a silent wrap.

use std::fmt::{Debug, Formatter};

use crate::bank::Location;
use crate::bits::{bits_for, Packed};
use crate::error::ParseError;
use crate::fields::{ControlKind, Unit};

/// An i8 value that is bounded by MIN and MAX and can be converted to a u8 by adding OFFSET.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct RangedI8<const OFFSET: u8, const MIN: i8, const MAX: i8> {
    inner: i8,
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> RangedI8<OFFSET, MIN, MAX> {
    const VALID: () = {
        assert!(MIN <= MAX, "MIN must not exceed MAX");
        assert!(
            MIN as i16 + OFFSET as i16 >= 0,
            "MIN + OFFSET must fit in u8",
        );
        assert!(
            MAX as i16 + OFFSET as i16 <= u8::MAX as i16,
            "MAX + OFFSET must fit in u8",
        );
    };
    const DEFAULT_VALID: () = {
        let () = Self::VALID;
        assert!(MIN <= 0 && 0 <= MAX, "the default value must be in range");
    };

    pub fn as_u8(&self) -> u8 {
        let () = Self::VALID;
        (i16::from(self.inner) + i16::from(OFFSET)) as u8
    }

    pub fn inner(&self) -> i8 {
        self.inner
    }
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> Default for RangedI8<OFFSET, MIN, MAX> {
    fn default() -> Self {
        let () = Self::DEFAULT_VALID;
        Self { inner: 0 }
    }
}

/// Stored biased by `OFFSET`, so the widest encoding is `MAX + OFFSET`.
impl<const OFFSET: u8, const MIN: i8, const MAX: i8> Packed for RangedI8<OFFSET, MIN, MAX> {
    const MAX_BITS: u32 = {
        let () = Self::VALID;
        bits_for((MAX as i16 + OFFSET as i16) as u64)
    };
    const DECODE_BITS: u32 = u8::BITS;
    /// A signed shift. ⚠️ The unit is the model's — octaves for an octave shift,
    /// semitones for a transpose — and the alias does not carry it, so the kind says
    /// only that the control is signed.
    const CONTROL: ControlKind = ControlKind::Shift(Unit::None);
    type Error = ParseError;

    fn from_bits(bits: u64) -> Result<Self, ParseError> {
        (bits as u8).try_into()
    }

    fn to_bits(&self) -> u64 {
        self.as_u8() as u64
    }
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> Debug for RangedI8<OFFSET, MIN, MAX> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.inner)
    }
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> TryFrom<u8> for RangedI8<OFFSET, MIN, MAX> {
    type Error = ParseError;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        let () = Self::VALID;
        // Widened first: a stored byte above `i8::MAX` would otherwise wrap before the
        // range check sees it.
        let unbiased = i16::from(value) - i16::from(OFFSET);
        match i8::try_from(unbiased) {
            Ok(value) => value.try_into(),
            Err(_) => Err(ParseError::OutOfBounds {
                value: format!("{unbiased}"),
                bound: format!("{MIN}..={MAX}"),
            }),
        }
    }
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> TryFrom<i8> for RangedI8<OFFSET, MIN, MAX> {
    type Error = ParseError;

    fn try_from(value: i8) -> Result<Self, Self::Error> {
        let () = Self::VALID;
        if value < MIN || value > MAX {
            return Err(ParseError::OutOfBounds {
                value: format!("{value}"),
                bound: format!("{MIN}..={MAX}"),
            });
        }

        Ok(RangedI8 { inner: value })
    }
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> PartialEq<u8> for RangedI8<OFFSET, MIN, MAX> {
    fn eq(&self, other: &u8) -> bool {
        self.as_u8() == *other
    }
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> PartialEq<i8> for RangedI8<OFFSET, MIN, MAX> {
    fn eq(&self, other: &i8) -> bool {
        self.inner == *other
    }
}

impl<const OFFSET: u8, const MIN: i8, const MAX: i8> PartialEq<i32> for RangedI8<OFFSET, MIN, MAX> {
    fn eq(&self, other: &i32) -> bool {
        (self.inner as i32) == *other
    }
}

/// An unsigned `0..=MAX` value over `$inner`, named `$as_inner` where a caller wants the
/// plain integer back.
macro_rules! ranged_unsigned {
    ($(#[$doc:meta])* $name:ident, $inner:ident, $as_inner:ident $(,)?) => {
        $(#[$doc])*
        #[derive(Copy, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub struct $name<const MAX: $inner> {
            inner: $inner,
        }

        impl<const MAX: $inner> $name<MAX> {
            /// The largest value this type can hold.
            pub const MAX: $inner = MAX;

            pub fn new(value: $inner) -> Result<Self, ParseError> {
                value.try_into()
            }

            pub fn $as_inner(&self) -> $inner {
                self.inner
            }

            pub fn inner(&self) -> $inner {
                self.inner
            }
        }

        impl<const MAX: $inner> Packed for $name<MAX> {
            const MAX_BITS: u32 = bits_for(MAX as u64);
            const DECODE_BITS: u32 = $inner::BITS;
            type Error = ParseError;

            fn from_bits(bits: u64) -> Result<Self, ParseError> {
                (bits as $inner).try_into()
            }

            fn to_bits(&self) -> u64 {
                self.inner as u64
            }
        }

        impl<const MAX: $inner> TryFrom<$inner> for $name<MAX> {
            type Error = ParseError;

            fn try_from(value: $inner) -> Result<Self, ParseError> {
                if value > MAX {
                    return Err(ParseError::OutOfBounds {
                        value: format!("{value}"),
                        bound: format!("0..={MAX}"),
                    });
                }
                Ok($name { inner: value })
            }
        }

        impl<const MAX: $inner> From<$name<MAX>> for $inner {
            fn from(value: $name<MAX>) -> $inner {
                value.inner
            }
        }

        impl<const MAX: $inner> Debug for $name<MAX> {
            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.inner)
            }
        }

        impl<const MAX: $inner> std::fmt::Display for $name<MAX> {
            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.inner)
            }
        }

        impl<const MAX: $inner> PartialEq<$inner> for $name<MAX> {
            fn eq(&self, other: &$inner) -> bool {
                self.inner == *other
            }
        }
    };
}

ranged_unsigned! {
    /// An unsigned value constrained to `0..=MAX`.
    ///
    /// The counterpart to [`RangedI8`] for fields that are still plain integers — knob
    /// positions, model slots, selectors. Expressing the bound in the type means the value
    /// cannot be built too wide for its slot, so encoding it can never fail.
    ///
    /// `MAX` is what the *slot* holds, not what the instrument uses: tightening it to the
    /// real range would reject files this decoder currently accepts.
    RangedU8, u8, as_u8
}

ranged_unsigned! {
    /// An unsigned value constrained to `0..=MAX`, for a slot wider than a byte.
    ///
    /// [`RangedU8`] with a wider inner type, and the same rule about `MAX`: it is the
    /// slot's bound, not the instrument's.
    RangedU16, u16, as_u16
}

/// A pair of u16 coordinates over an `X_COUNT` × `Y_COUNT` space.
///
/// Both parameters are **counts**, so the valid coordinates are `0..X_COUNT` and
/// `0..Y_COUNT`. The pair packs into a single u16 as `x * Y_COUNT + y`, which is a
/// bijection onto `0..X_COUNT * Y_COUNT` exactly because `Y_COUNT` is the stride.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct RangedU16Pair<const X_COUNT: u16, const Y_COUNT: u16> {
    inner: (u16, u16),
}

impl<const X_COUNT: u16, const Y_COUNT: u16> RangedU16Pair<X_COUNT, Y_COUNT> {
    const VALID: () = {
        assert!(
            X_COUNT > 0 && Y_COUNT > 0,
            "coordinate counts must be nonzero"
        );
        assert!(
            X_COUNT as u32 * Y_COUNT as u32 <= u16::MAX as u32 + 1,
            "coordinate space must fit in u16",
        );
    };

    pub fn new(x: u16, y: u16) -> Result<Self, ParseError> {
        let () = Self::VALID;
        if x >= X_COUNT {
            return Err(ParseError::OutOfBounds {
                value: format!("{x}"),
                bound: format!("0..{X_COUNT}"),
            });
        }

        if y >= Y_COUNT {
            return Err(ParseError::OutOfBounds {
                value: format!("{y}"),
                bound: format!("0..{Y_COUNT}"),
            });
        }

        Ok(RangedU16Pair { inner: (x, y) })
    }

    pub fn from_u16(value: u16) -> Result<Self, ParseError> {
        let () = Self::VALID;
        (value / Y_COUNT, value % Y_COUNT).try_into()
    }

    pub fn inner(&self) -> (u16, u16) {
        self.inner
    }

    pub fn as_u16(&self) -> u16 {
        let () = Self::VALID;
        (self.inner.0 * Y_COUNT) + self.inner.1
    }

    pub fn x(&self) -> u16 {
        self.inner.0
    }

    pub fn y(&self) -> u16 {
        self.inner.1
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> Default for RangedU16Pair<X_COUNT, Y_COUNT> {
    fn default() -> Self {
        let () = Self::VALID;
        Self { inner: (0, 0) }
    }
}

/// The widest encoding is the last location, `X_COUNT * Y_COUNT - 1`.
impl<const X_COUNT: u16, const Y_COUNT: u16> Packed for RangedU16Pair<X_COUNT, Y_COUNT> {
    const MAX_BITS: u32 = {
        let () = Self::VALID;
        bits_for(X_COUNT as u64 * Y_COUNT as u64 - 1)
    };
    const DECODE_BITS: u32 = u16::BITS;
    type Error = ParseError;

    fn from_bits(bits: u64) -> Result<Self, ParseError> {
        Self::from_u16(bits as u16)
    }

    fn to_bits(&self) -> u64 {
        self.as_u16() as u64
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> Debug for RangedU16Pair<X_COUNT, Y_COUNT> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {})", self.inner.0, self.inner.1)
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> Location for RangedU16Pair<X_COUNT, Y_COUNT> {
    fn inner(&self) -> (u16, u16) {
        self.inner()
    }

    fn as_u16(&self) -> u16 {
        self.as_u16()
    }

    fn x(&self) -> u16 {
        self.x()
    }

    fn y(&self) -> u16 {
        self.y()
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> TryFrom<(u16, u16)>
    for RangedU16Pair<X_COUNT, Y_COUNT>
{
    type Error = ParseError;

    fn try_from(value: (u16, u16)) -> Result<Self, Self::Error> {
        RangedU16Pair::new(value.0, value.1)
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> TryFrom<u16> for RangedU16Pair<X_COUNT, Y_COUNT> {
    type Error = ParseError;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        RangedU16Pair::from_u16(value)
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> PartialEq<u16> for RangedU16Pair<X_COUNT, Y_COUNT> {
    fn eq(&self, other: &u16) -> bool {
        self.as_u16() == *other
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> PartialEq<(u16, u16)>
    for RangedU16Pair<X_COUNT, Y_COUNT>
{
    fn eq(&self, other: &(u16, u16)) -> bool {
        self.inner == *other
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> From<RangedU16Pair<X_COUNT, Y_COUNT>> for u16 {
    fn from(value: RangedU16Pair<X_COUNT, Y_COUNT>) -> u16 {
        value.as_u16()
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> From<RangedU16Pair<X_COUNT, Y_COUNT>> for u32 {
    fn from(value: RangedU16Pair<X_COUNT, Y_COUNT>) -> u32 {
        value.as_u16() as u32
    }
}

impl<const X_COUNT: u16, const Y_COUNT: u16> From<RangedU16Pair<X_COUNT, Y_COUNT>> for u64 {
    fn from(value: RangedU16Pair<X_COUNT, Y_COUNT>) -> u64 {
        value.as_u16() as u64
    }
}

#[cfg(test)]
mod tests {
    use super::RangedU16Pair;

    #[test]
    fn ranged_tuple_can_convert_to_u16() {
        let ranged_tuple: RangedU16Pair<5, 10> = (1, 2).try_into().unwrap();
        assert_eq!(ranged_tuple.as_u16(), 12);
    }

    #[test]
    fn ranged_tuple_can_be_created_from_u16() {
        let ranged_tuple: RangedU16Pair<5, 10> = 12_u16.try_into().unwrap();
        assert_eq!(ranged_tuple, (1, 2));
    }

    /// Both parameters are counts: the last location of an 8×50 space is `(7, 49)`, and
    /// a coordinate equal to its count is one past the end.
    #[test]
    fn the_pair_parameters_are_counts_not_maxima() {
        type Location = RangedU16Pair<8, 50>;

        assert!(Location::try_from((7, 49)).is_ok());
        assert!(Location::try_from((8, 0)).is_err());
        assert!(Location::try_from((0, 50)).is_err());
    }

    /// Packing at the `Y_COUNT` stride is a bijection, so no two locations share a u16.
    #[test]
    fn packing_round_trips_without_collision() {
        type Location = RangedU16Pair<8, 50>;

        assert_eq!(Location::try_from((1, 0)).unwrap().as_u16(), 50);
        assert_eq!(Location::from_u16(50).unwrap(), (1, 0));
        assert_eq!(Location::try_from((7, 49)).unwrap().as_u16(), 399);
        assert!(Location::from_u16(400).is_err());

        let mut seen = std::collections::HashSet::new();
        for x in 0..8 {
            for y in 0..50 {
                let packed = Location::try_from((x, y)).unwrap().as_u16();
                assert!(seen.insert(packed), "({x}, {y}) collides at {packed}");
                assert_eq!(Location::from_u16(packed).unwrap(), (x, y));
            }
        }
    }
}