Skip to main content

ot_tools_io/arrangements/
row.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2026 Mike Robeson [dijksterhuis]
4*/
5
6use super::{is_not_valid_ascii_byte, ArrangementError};
7use crate::{Defaults, OtToolsIoError};
8use ot_tools_io_derive::IsDefaultCheck;
9use ot_tools_io_derive::{ArrayDefaults, AsMutDerive, AsRefDerive, BoxedArrayDefaults};
10use serde::{Deserialize, Serialize};
11use std::array::from_fn;
12
13/// Wrapper type holding data in an arrangement row within an arrangement
14/// state.
15///
16/// # Rant
17///
18/// On the Octatrack, changing an arrangement row variant in an Arrangement
19/// **does not** clear the bytes for that row.
20/// All it does is change the byte indicating the type of row.
21///
22/// This would be fine if the checksum calculation didn't sum these irrelevant
23/// bytes.
24/// But it does.
25///
26/// This means we have to read/write all 21 row bytes, no matter the row type
27/// or contents.
28/// Got a reminder row variant with an empty string?
29/// Still need to collect all 21 bytes because there **MIGHT** be leftover
30/// bytes from when you previously had that row as a pattern row.
31#[derive(
32    Copy,
33    Clone,
34    Debug,
35    Eq,
36    Hash,
37    Ord,
38    PartialEq,
39    PartialOrd,
40    Deserialize,
41    Serialize,
42    ArrayDefaults,
43    BoxedArrayDefaults,
44    AsRefDerive,
45    AsMutDerive,
46    IsDefaultCheck,
47)]
48#[serde(rename_all = "snake_case")]
49pub enum ArrangeRow {
50    /// New type variant for [`PatternRow`]
51    PatternRow(PatternRow),
52    /// New type variant for [`LoopOrJumpOrHaltRow`]
53    LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow),
54    /// New type variant for [`ReminderRow`]
55    ReminderRow(ReminderRow),
56    /// The last `256 - n_rows` rows are populated with "empty rows" to indicate
57    /// these rows are not in use (but we need to keep the bytes)
58    EmptyRow([u8; 22]),
59}
60
61impl Default for ArrangeRow {
62    fn default() -> Self {
63        Self::EmptyRow(from_fn(|_| 0))
64    }
65}
66
67impl ArrangeRow {
68    /// Delegates through to the variant and runs the `validate()` method,
69    /// except for the [`ArrangeRow::EmptyRow`] variant which just returns `Ok`
70    ///
71    /// ```
72    /// # use ot_tools_io::OtToolsIoError;
73    /// # use ot_tools_io::arrangements::ArrangementError;
74    /// # use ot_tools_io::types::{ArrangeRow, ReminderRow, LoopOrJumpOrHaltRow};
75    /// # fn main() -> Result<(), OtToolsIoError> {
76    /// // all valid rows
77    /// ArrangeRow::ReminderRow(ReminderRow::new_from_string("HELLO WORLD!   ")?).validate()?;
78    /// ArrangeRow::ReminderRow(ReminderRow::new_from_string("               ")?).validate()?;
79    /// ArrangeRow::ReminderRow(ReminderRow::new_without_unused(Default::default())).validate()?;
80    /// ArrangeRow::EmptyRow(Default::default()).validate()?;
81    ///
82    /// // `loop_count` cannot be > 100
83    /// let loop_result = ArrangeRow::LoopOrJumpOrHaltRow(
84    ///     LoopOrJumpOrHaltRow::new_without_unused(200, 10)
85    /// ).validate();
86    ///
87    /// assert_eq!(
88    ///     loop_result.err().unwrap().to_string(),
89    ///     OtToolsIoError::Arrangement(ArrangementError::LoopCount(200)).to_string()
90    /// );
91    /// # Ok(()) }
92    /// ```
93    pub fn validate(&self) -> Result<(), OtToolsIoError> {
94        match self {
95            Self::EmptyRow(_) => Ok(()),
96            Self::LoopOrJumpOrHaltRow(x) => x.validate(),
97            Self::PatternRow(x) => x.validate(),
98            Self::ReminderRow(x) => x.validate(),
99        }
100    }
101
102    pub(super) fn parse_to_bytes(&self) -> Result<Vec<u8>, OtToolsIoError> {
103        let mut v = vec![];
104        match self {
105            Self::EmptyRow(x) => v.append(&mut x.to_vec()),
106            Self::PatternRow(pattern_row) => {
107                pattern_row.validate()?;
108                let PatternRow {
109                    pattern_id,
110                    repetitions,
111                    unused_1,
112                    mute_mask,
113                    unused_2,
114                    tempo_1,
115                    tempo_2,
116                    scene_a,
117                    scene_b,
118                    unused_3,
119                    offset,
120                    unused_4,
121                    length,
122                    midi_transpose,
123                } = *pattern_row;
124
125                v.append(&mut vec![0]); // row type
126                v.append(&mut vec![pattern_id]);
127                v.append(&mut vec![repetitions]);
128                v.append(&mut vec![unused_1]);
129                v.append(&mut vec![mute_mask]);
130                v.append(&mut vec![unused_2]);
131                v.append(&mut vec![tempo_1]);
132                v.append(&mut vec![tempo_2]);
133                v.append(&mut vec![scene_a]);
134                v.append(&mut vec![scene_b]);
135                v.append(&mut vec![unused_3]);
136                v.append(&mut vec![offset]);
137                v.append(&mut vec![unused_4]);
138                v.append(&mut vec![length]);
139                v.append(&mut midi_transpose.to_vec());
140            }
141            Self::LoopOrJumpOrHaltRow(loop_row) => {
142                loop_row.validate()?;
143                let LoopOrJumpOrHaltRow {
144                    loop_count,
145                    row_target,
146                    unused,
147                } = *loop_row;
148                v.append(&mut vec![1]); // row type
149                v.append(&mut vec![loop_count]);
150                v.append(&mut vec![row_target]);
151                v.append(&mut unused.to_vec());
152            }
153            Self::ReminderRow(reminder_row) => {
154                reminder_row.validate()?;
155                let ReminderRow { reminder, unused } = *reminder_row;
156                v.append(&mut vec![2]); // row type
157                v.append(&mut reminder.to_vec());
158                v.append(&mut unused.to_vec());
159            }
160        }
161        Ok(v)
162    }
163
164    pub(super) fn parse_from_bytes(bytes: &[u8; 22]) -> Result<Self, OtToolsIoError> {
165        let (row_type, row_data) = bytes.split_first().unwrap();
166
167        // note: we should never parse an empty row here
168        // TODO: Enum Variant with values to match `row_type`?
169        //       (can't remember its official name)
170        match row_type {
171            // pattern row
172            0 => {
173                let pattern_id = row_data[0];
174                let repetitions = row_data[1];
175                let unused_1 = row_data[2];
176                // TODO: mute mask across Audio and MIDI tracks (again)
177                let mute_mask = row_data[3];
178                let unused_2 = row_data[4];
179                // TODO: tempo mask (again)
180                let tempo_1 = row_data[5];
181                let tempo_2 = row_data[6];
182                let scene_a = row_data[7];
183                let scene_b = row_data[8];
184                let unused_3 = row_data[9];
185                // TODO: what's the max for this? 64?
186                let offset = row_data[10];
187                let unused_4 = row_data[11];
188                // TODO: this has a max of something like 512 somehow?
189                // TODO: The value for length changes on device depending on what the offset
190                //       value is. With a length of 256, adding a 64 offset changes the
191                //       length to 192.
192                let length = row_data[12];
193                let midi_transpose: [u8; 8] = from_fn(|x| row_data[x + 13]);
194
195                let pattern = PatternRow {
196                    pattern_id,
197                    repetitions,
198                    mute_mask,
199                    tempo_1,
200                    tempo_2,
201                    scene_a,
202                    scene_b,
203                    offset,
204                    length,
205                    midi_transpose,
206                    unused_1,
207                    unused_2,
208                    unused_3,
209                    unused_4,
210                };
211
212                pattern.validate()?;
213                Ok(ArrangeRow::PatternRow(pattern))
214            }
215            // loop row
216            1 => {
217                let loop_count = row_data[0];
218                let row_target = row_data[1];
219                let unused: [u8; 19] = from_fn(|x| row_data[x + 2]);
220
221                let loop_row = LoopOrJumpOrHaltRow {
222                    loop_count,
223                    row_target,
224                    unused,
225                };
226                loop_row.validate()?;
227
228                Ok(ArrangeRow::LoopOrJumpOrHaltRow(loop_row))
229            }
230            // reminder row
231            2 => {
232                let reminder = from_fn(|x| row_data[x]);
233                let unused: [u8; 6] = from_fn(|x| row_data[x + 15]);
234                let reminder_row = ReminderRow { reminder, unused };
235                reminder_row.validate()?;
236                Ok(ArrangeRow::ReminderRow(reminder_row))
237            }
238            // invalid
239            _ => Err(ArrangementError::RowType(*row_type).into()),
240        }
241    }
242}
243
244/// Pattern Row = play this pattern, with these settings, when playing this row
245/// of the arrangement.
246/// Pretty simple.
247///
248/// Does not have any `new` methods -- create the struct by hand.
249#[derive(
250    Copy,
251    Clone,
252    Debug,
253    Eq,
254    Hash,
255    Ord,
256    PartialEq,
257    PartialOrd,
258    Serialize,
259    Deserialize,
260    AsRefDerive,
261    AsMutDerive,
262)]
263pub struct PatternRow {
264    /// Which Pattern should be played at this point. Patterns are indexed from 0 (A01) -> 256 (P16).
265    ///
266    /// # Validation
267    ///
268    /// Checked by the [`Self::validate`] method
269    pub pattern_id: u8,
270    /// How many times to play this arrangement row.
271    ///
272    /// # Validation
273    ///
274    /// Checked by the [`Self::validate`] method
275    pub repetitions: u8,
276    /// unused -- have to retain this, see rant in [`Self`]
277    ///
278    /// # Validation
279    ///
280    /// Not checked by the [`Self::validate`] method
281    pub unused_1: u8,
282    /// How track muting is applied during this arrangement row.
283    ///
284    /// # Validation
285    ///
286    /// Not checked by the [`Self::validate`] method
287    pub mute_mask: u8,
288    /// unused -- have to retain this, see rant in [`Self`]
289    ///
290    /// # Validation
291    ///
292    /// Not checked by the [`Self::validate`] method
293    pub unused_2: u8,
294    /// First part of the Tempo mask for this row.
295    /// Needs to be combined with `tempo_2` to work out the actual tempo (not sure how it works yet).
296    ///
297    /// # Validation
298    ///
299    /// Not checked by the [`Self::validate`] method
300    pub tempo_1: u8,
301    /// Second part of the Tempo mask for this row.
302    /// Needs to be combined with `tempo_1` to work out the actual tempo (not sure how it works yet).
303    ///
304    /// # Validation
305    ///
306    /// Not checked by the [`Self::validate`] method
307    pub tempo_2: u8,
308    /// Which scene is assigned to Scene slot A when this arrangement row is playing.
309    ///
310    /// # Validation
311    ///
312    /// Checked by the [`Self::validate`] method
313    pub scene_a: u8,
314    /// Which scene is assigned to Scene slot B when this arrangement row is playing.
315    ///
316    /// # Validation
317    ///
318    /// Checked by the [`Self::validate`] method
319    pub scene_b: u8,
320    /// unused -- have to retain this, see rant in [`Self`]
321    ///
322    /// # Validation
323    ///
324    /// Not checked by the [`Self::validate`] method
325    pub unused_3: u8,
326    /// Which trig to start Playing the pattern on.
327    /// todo: is there a maximum for this?
328    ///
329    /// # Validation
330    ///
331    /// Not (currently) checked by the [`Self::validate`] method
332    pub offset: u8,
333    /// unused -- have to retain this, see rant in [`Self`]
334    ///
335    /// # Validation
336    ///
337    /// Not checked by the [`Self::validate`] method
338    pub unused_4: u8,
339    /// How many trigs to play the pattern for.
340    /// Note that this value always has `offset` added to it.
341    /// So a length on the machine display of 64 when the offset is 32 will result in a value of 96 in the file data.
342    /// todo: is there a maximum for this?
343    ///
344    /// # Validation
345    ///
346    /// Not (currently) checked by the [`Self::validate`] method
347    pub length: u8,
348    /// MIDI Track transposes for all 8 midi channels.
349    /// - 0 -> no transpose
350    /// - 1 -> 48 values are positive transpose settings
351    /// - 255 (-1) -> 207 (-48) values are negative transpose settings.
352    ///
353    /// # Validation
354    ///
355    /// Checked by the [`Self::validate`] method
356    pub midi_transpose: [u8; 8],
357}
358
359impl PatternRow {
360    /// Run validation on relevant fields, see the documentation on fields in
361    /// [`Self`] to find out what is actually validated.
362    pub fn validate(&self) -> Result<(), OtToolsIoError> {
363        if self.repetitions > 63 {
364            Err(ArrangementError::RepetitionCount(self.repetitions).into())
365        } else if self.scene_a > 15 && self.scene_a != 255 {
366            Err(ArrangementError::Scene(self.scene_a).into())
367        } else if self.scene_b > 15 && self.scene_b != 255 {
368            Err(ArrangementError::Scene(self.scene_b).into())
369        } else if self.midi_transpose.iter().any(|x| (49..208).contains(x)) {
370            Err(ArrangementError::MidiTranspose(self.midi_transpose).into())
371        } else {
372            Ok(())
373        }
374    }
375}
376
377#[cfg(test)]
378mod pattern_row {
379    mod validate {
380        use crate::arrangements::{ArrangementError, PatternRow};
381        use crate::OtToolsIoError;
382
383        #[test]
384        fn ok_maxish_values() {
385            let x = PatternRow {
386                pattern_id: 255,
387                repetitions: 63,
388                unused_1: 0,
389                mute_mask: 0, // todo
390                unused_2: 0,
391                tempo_1: 0, // todo
392                tempo_2: 0, // todo
393                scene_a: 15,
394                scene_b: 15,
395                unused_3: 0,
396                offset: 0, // todo
397                unused_4: 0,
398                length: 0, // todo
399                midi_transpose: [255, 255, 255, 255, 255, 255, 255, 255],
400            };
401            assert!(x.validate().is_ok());
402        }
403
404        #[test]
405        fn invalid_repetitions() {
406            let x = PatternRow {
407                pattern_id: 0,
408                repetitions: 64,
409                unused_1: 0,
410                mute_mask: 0,
411                unused_2: 0,
412                tempo_1: 0,
413                tempo_2: 0,
414                scene_a: 0,
415                scene_b: 0,
416                unused_3: 0,
417                offset: 0,
418                unused_4: 0,
419                length: 0,
420                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
421            };
422            assert!(x.validate().is_err());
423            assert_eq!(
424                x.validate().err().unwrap().to_string(),
425                OtToolsIoError::Arrangement(ArrangementError::RepetitionCount(64)).to_string(),
426            );
427        }
428
429        #[test]
430        fn valid_scene_a_off() {
431            let x = PatternRow {
432                pattern_id: 0,
433                repetitions: 0,
434                unused_1: 0,
435                mute_mask: 0,
436                unused_2: 0,
437                tempo_1: 0,
438                tempo_2: 0,
439                scene_a: 255,
440                scene_b: 0,
441                unused_3: 0,
442                offset: 0,
443                unused_4: 0,
444                length: 0,
445                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
446            };
447            assert!(x.validate().is_ok());
448        }
449
450        #[test]
451        fn valid_scene_b_off() {
452            let x = PatternRow {
453                pattern_id: 0,
454                repetitions: 0,
455                unused_1: 0,
456                mute_mask: 0,
457                unused_2: 0,
458                tempo_1: 0,
459                tempo_2: 0,
460                scene_a: 0,
461                scene_b: 255,
462                unused_3: 0,
463                offset: 0,
464                unused_4: 0,
465                length: 0,
466                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
467            };
468            assert!(x.validate().is_ok());
469        }
470
471        #[test]
472        fn valid_scene_a_max() {
473            let x = PatternRow {
474                pattern_id: 0,
475                repetitions: 0,
476                unused_1: 0,
477                mute_mask: 0,
478                unused_2: 0,
479                tempo_1: 0,
480                tempo_2: 0,
481                scene_a: 15,
482                scene_b: 0,
483                unused_3: 0,
484                offset: 0,
485                unused_4: 0,
486                length: 0,
487                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
488            };
489            assert!(x.validate().is_ok());
490        }
491
492        #[test]
493        fn valid_scene_b_max() {
494            let x = PatternRow {
495                pattern_id: 0,
496                repetitions: 0,
497                unused_1: 0,
498                mute_mask: 0,
499                unused_2: 0,
500                tempo_1: 0,
501                tempo_2: 0,
502                scene_a: 0,
503                scene_b: 15,
504                unused_3: 0,
505                offset: 0,
506                unused_4: 0,
507                length: 0,
508                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
509            };
510            assert!(x.validate().is_ok());
511        }
512
513        #[test]
514        fn invalid_scene_a() {
515            let x = PatternRow {
516                pattern_id: 0,
517                repetitions: 0,
518                unused_1: 0,
519                mute_mask: 0,
520                unused_2: 0,
521                tempo_1: 0,
522                tempo_2: 0,
523                scene_a: 16,
524                scene_b: 0,
525                unused_3: 0,
526                offset: 0,
527                unused_4: 0,
528                length: 0,
529                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
530            };
531            assert!(x.validate().is_err());
532            assert_eq!(
533                x.validate().err().unwrap().to_string(),
534                OtToolsIoError::Arrangement(ArrangementError::Scene(16)).to_string(),
535            );
536        }
537
538        #[test]
539        fn invalid_scene_b() {
540            let x = PatternRow {
541                pattern_id: 0,
542                repetitions: 0,
543                unused_1: 0,
544                mute_mask: 0,
545                unused_2: 0,
546                tempo_1: 0,
547                tempo_2: 0,
548                scene_a: 0,
549                scene_b: 16,
550                unused_3: 0,
551                offset: 0,
552                unused_4: 0,
553                length: 0,
554                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
555            };
556            assert!(x.validate().is_err());
557            assert_eq!(
558                x.validate().err().unwrap().to_string(),
559                OtToolsIoError::Arrangement(ArrangementError::Scene(16)).to_string(),
560            );
561        }
562
563        #[test]
564        fn invalid_midi_transpose_lower() {
565            let x = PatternRow {
566                pattern_id: 0,
567                repetitions: 0,
568                unused_1: 0,
569                mute_mask: 0,
570                unused_2: 0,
571                tempo_1: 0,
572                tempo_2: 0,
573                scene_a: 0,
574                scene_b: 0,
575                unused_3: 0,
576                offset: 0,
577                unused_4: 0,
578                length: 0,
579                midi_transpose: [49, 49, 49, 49, 49, 49, 49, 49],
580            };
581            assert!(x.validate().is_err());
582            assert_eq!(
583                x.validate().err().unwrap().to_string(),
584                OtToolsIoError::Arrangement(ArrangementError::MidiTranspose([
585                    49, 49, 49, 49, 49, 49, 49, 49
586                ]))
587                .to_string(),
588            );
589        }
590
591        #[test]
592        fn invalid_midi_transpose_upper() {
593            let x = PatternRow {
594                pattern_id: 0,
595                repetitions: 0,
596                unused_1: 0,
597                mute_mask: 0,
598                unused_2: 0,
599                tempo_1: 0,
600                tempo_2: 0,
601                scene_a: 0,
602                scene_b: 0,
603                unused_3: 0,
604                offset: 0,
605                unused_4: 0,
606                length: 0,
607                midi_transpose: [207, 207, 207, 207, 207, 207, 207, 207],
608            };
609            assert!(x.validate().is_err());
610            assert_eq!(
611                x.validate().err().unwrap().to_string(),
612                OtToolsIoError::Arrangement(ArrangementError::MidiTranspose([
613                    207, 207, 207, 207, 207, 207, 207, 207
614                ]))
615                .to_string(),
616            );
617        }
618    }
619}
620
621/// Used to either:
622/// - Loop from this row of the arrangement back to another row (`row_target` < current row index)
623/// - Jump from this row to a later row (`row_target` > current row index)
624/// - Stop the arrangement from playing (`row_target` = current row index)
625#[derive(
626    Copy,
627    Clone,
628    Debug,
629    Eq,
630    Hash,
631    Ord,
632    PartialEq,
633    PartialOrd,
634    Serialize,
635    Deserialize,
636    AsRefDerive,
637    AsMutDerive,
638)]
639pub struct LoopOrJumpOrHaltRow {
640    /// How many times to loop to the `row_target`. Only applies to loops.
641    ///
642    /// # Validation
643    ///
644    /// Checked by the [`Self::validate`] method. Cannot be greater than `100`.
645    pub loop_count: u8,
646    /// The row number to loop back to, jump to, or end at.
647    ///
648    /// # Validation
649    ///
650    /// Not checked by the [`Self::validate`] method. Can be any non-empty row.
651    pub row_target: u8,
652    /// unused -- have to retain this, see rant in [`ArrangeRow`]
653    ///
654    /// # Validation
655    ///
656    /// Not checked by the [`Self::validate`] method
657    pub unused: [u8; 19],
658}
659
660impl LoopOrJumpOrHaltRow {
661    /// create a new [`Self`]
662    pub fn new(loop_count: u8, row_target: u8, unused_bytes: [u8; 19]) -> Self {
663        Self {
664            loop_count,
665            row_target,
666            unused: unused_bytes,
667        }
668    }
669
670    /// create a new instance without having to provide `unused_bytes`
671    /// (they are set to `Default::default()` -- so a zeroed array)
672    pub fn new_without_unused(loop_count: u8, row_target: u8) -> Self {
673        Self::new(loop_count, row_target, Default::default())
674    }
675
676    /// Run validation on relevant fields, see the documentation on fields in
677    /// [`Self`] to find out what is actually validated.
678    /// ```
679    /// # use ot_tools_io::OtToolsIoError;
680    /// # use ot_tools_io::arrangements::ArrangementError;
681    /// # use ot_tools_io::types::{ArrangeRow, ReminderRow, LoopOrJumpOrHaltRow};
682    /// # fn main() -> Result<(), OtToolsIoError> {
683    /// // all valid rows
684    /// ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow::new_without_unused(0, 0)).validate()?;
685    /// ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow::new_without_unused(0, 255)).validate()?;
686    /// ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow::new_without_unused(100, 0)).validate()?;
687    /// ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow::new_without_unused(100, 255)).validate()?;
688    ///
689    /// // `loop_count` cannot be > 100
690    /// let loop_result = ArrangeRow::LoopOrJumpOrHaltRow(
691    ///     LoopOrJumpOrHaltRow::new_without_unused(101, 10)
692    /// ).validate();
693    ///
694    /// assert_eq!(
695    ///     loop_result.err().unwrap().to_string(),
696    ///     OtToolsIoError::Arrangement(ArrangementError::LoopCount(101)).to_string()
697    /// );
698    /// # Ok(()) }
699    /// ```
700    pub fn validate(&self) -> Result<(), OtToolsIoError> {
701        if self.loop_count > 100 {
702            Err(ArrangementError::LoopCount(self.loop_count).into())
703        } else {
704            Ok(())
705        }
706    }
707}
708
709#[cfg(test)]
710mod loop_or_jump_or_halt_row {
711    mod new {
712        use crate::arrangements::LoopOrJumpOrHaltRow;
713
714        #[test]
715        fn valid() {
716            let x = LoopOrJumpOrHaltRow::new(1, 1, Default::default());
717            assert_eq!(x.loop_count, 1);
718            assert_eq!(x.row_target, 1);
719            assert_eq!(x.unused, std::array::from_fn(|_| 0));
720        }
721    }
722
723    mod new_without_unused {
724        use crate::arrangements::LoopOrJumpOrHaltRow;
725
726        #[test]
727        fn valid() {
728            let x = LoopOrJumpOrHaltRow::new_without_unused(1, 1);
729            assert_eq!(x.loop_count, 1);
730            assert_eq!(x.row_target, 1);
731            assert_eq!(x.unused, std::array::from_fn(|_| 0));
732        }
733    }
734
735    mod validate {
736        use crate::arrangements::{ArrangementError, LoopOrJumpOrHaltRow};
737        use crate::OtToolsIoError;
738
739        #[test]
740        fn valid() {
741            let x = LoopOrJumpOrHaltRow {
742                loop_count: 1,
743                row_target: 1,
744                unused: std::array::from_fn(|_| 0),
745            };
746            let r = x.validate();
747            assert!(r.is_ok());
748        }
749
750        #[test]
751        fn invalid_loop_count() {
752            let x = LoopOrJumpOrHaltRow {
753                loop_count: 101,
754                row_target: 1,
755                unused: std::array::from_fn(|_| 0),
756            };
757            assert!(x.validate().is_err());
758            assert_eq!(
759                x.validate().err().unwrap().to_string(),
760                OtToolsIoError::Arrangement(ArrangementError::LoopCount(101)).to_string(),
761            );
762        }
763    }
764}
765
766/// A reminder row is a string of ASCII only characters with a maximum length of
767/// 15 characters.
768///
769/// Bear in mind it is possible to change a row to be an empty reminder row
770/// (zero length string), while the bytes for the old row (e.g. a pattern row)
771/// occupy the actual data in the row.
772///
773/// So you can end up with a reminder row filled with completely non-ASCII bytes
774/// and it's still valid ... somehow.
775///
776/// See rant on [`ArrangeRow`].
777#[derive(
778    Copy,
779    Clone,
780    Debug,
781    Eq,
782    Hash,
783    Ord,
784    PartialEq,
785    PartialOrd,
786    Serialize,
787    Deserialize,
788    AsRefDerive,
789    AsMutDerive,
790)]
791pub struct ReminderRow {
792    /// Can't do [`String`] here because we can have a bunch of leftover
793    /// bytes that the Octatrack doesn't actually read.
794    ///
795    /// # Validation
796    ///
797    /// Not checked by the [`Self::validate`] method
798    pub reminder: [u8; 15],
799    /// unused -- have to retain this, see rant in [`ArrangeRow`]
800    ///
801    /// # Validation
802    ///
803    /// Not checked by the [`Self::validate`] method
804    pub unused: [u8; 6],
805}
806
807impl TryFrom<String> for ReminderRow {
808    type Error = OtToolsIoError;
809    fn try_from(value: String) -> Result<Self, Self::Error> {
810        let string = value.clone();
811        let bytes = string
812            .as_bytes()
813            .as_array::<15>()
814            .ok_or(ArrangementError::ReminderStrLen(value))?;
815
816        if bytes.iter().any(|x| is_not_valid_ascii_byte(*x)) {
817            Err(ArrangementError::ReminderAscii(*bytes).into())
818        } else {
819            Ok(Self {
820                reminder: *bytes,
821                unused: Default::default(),
822            })
823        }
824    }
825}
826
827// note: gets tested as part of `ReminderRow::new_from_string`
828impl std::fmt::Display for ReminderRow {
829    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
830        // will write an empty string when no valid ascii characters
831        write!(f, "{}", super::bytes_to_ascii_string(&self.reminder))
832    }
833}
834
835impl ReminderRow {
836    /// Create a new instance
837    pub fn new(reminder: [u8; 15], unused: [u8; 6]) -> Self {
838        Self { reminder, unused }
839    }
840    /// Create a new instance without having to provide `unused` byte array
841    /// (set to `Default::default()` -- i.e. a zeroed array)
842    pub fn new_without_unused(reminder: [u8; 15]) -> Self {
843        Self::new(reminder, Default::default())
844    }
845
846    /// Create a new instance from some string.
847    ///
848    /// The string provided here must be exactly 15 characters in length
849    /// and only contain valid ASCII characters
850    ///
851    /// ```
852    /// # use ot_tools_io::OtToolsIoError;
853    /// # use ot_tools_io::arrangements::ArrangementError;
854    /// # use ot_tools_io::types::{ArrangeRow, ReminderRow, LoopOrJumpOrHaltRow};
855    /// # fn main() -> Result<(), OtToolsIoError> {
856    /// ReminderRow::new_from_string("HELLO WORLD!   ")?;
857    /// ReminderRow::new_from_string("               ")?;
858    /// ReminderRow::new_from_string("123456789012345")?;
859    /// ReminderRow::new_from_string("!!!!!!!!!!!!!!!")?;
860    ///
861    /// let too_short = String::from("too short");
862    /// assert_eq!(
863    ///     ReminderRow::new_from_string(&too_short).err().unwrap().to_string(),
864    ///     OtToolsIoError::Arrangement(ArrangementError::ReminderStrLen(too_short)).to_string()
865    /// );
866    ///
867    /// let too_long = String::from("too long                  ");
868    /// assert_eq!(
869    ///     ReminderRow::new_from_string(&too_long).err().unwrap().to_string(),
870    ///     OtToolsIoError::Arrangement(ArrangementError::ReminderStrLen(too_long)).to_string()
871    /// );
872    /// # Ok(()) }
873    /// ```
874    pub fn new_from_string<S: AsRef<str>>(reminder: S) -> Result<Self, OtToolsIoError> {
875        Self::try_from(reminder.as_ref().to_string())
876    }
877
878    /// Doesn't actually run any validation currently. See the long note in
879    /// the [`Self`] to understand why.
880    pub fn validate(&self) -> Result<(), OtToolsIoError> {
881        Ok(())
882    }
883}
884
885#[cfg(test)]
886mod reminder_row {
887
888    // "HELLO WORLD    "
889    const HELLO_WORLD_BYTES: [u8; 15] =
890        [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 32, 32, 32, 32];
891    // "               "
892    const ONLY_SPACES_BYTES: [u8; 15] =
893        [32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32];
894
895    const INVALID_BYTES: [u8; 15] = [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9];
896
897    mod new_without_unused {
898        use crate::arrangements::ReminderRow;
899        #[test]
900        fn ok_hello_world() {
901            let rem_row = ReminderRow::new_without_unused(super::HELLO_WORLD_BYTES);
902            assert_eq!(super::HELLO_WORLD_BYTES, rem_row.reminder);
903            assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
904        }
905
906        #[test]
907        fn ok_only_spaces() {
908            let rem_row = ReminderRow::new_without_unused(super::ONLY_SPACES_BYTES);
909            assert_eq!(super::ONLY_SPACES_BYTES, rem_row.reminder);
910            assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
911        }
912
913        #[test]
914        fn ok_invalid() {
915            let rem_row = ReminderRow::new_without_unused(super::INVALID_BYTES);
916            assert_eq!(super::INVALID_BYTES, rem_row.reminder);
917            assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
918        }
919    }
920
921    mod new_from_string {
922        use crate::arrangements::ReminderRow;
923        use crate::OtToolsIoError;
924
925        #[test]
926        fn ok_hello_world() -> Result<(), OtToolsIoError> {
927            let rem = String::from("HELLO WORLD    ");
928            let rem_row = ReminderRow::new_from_string(rem)?;
929            assert_eq!(super::HELLO_WORLD_BYTES, rem_row.reminder);
930            assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
931            Ok(())
932        }
933        #[test]
934        fn ok_only_spaces() -> Result<(), OtToolsIoError> {
935            let rem = String::from("               ");
936            let rem_row = ReminderRow::new_from_string(rem)?;
937            assert_eq!(super::ONLY_SPACES_BYTES, rem_row.reminder);
938            assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
939            Ok(())
940        }
941    }
942
943    mod validate {
944        use crate::arrangements::ReminderRow;
945        #[test]
946        fn valid_string() {
947            let x = ReminderRow {
948                reminder: super::HELLO_WORLD_BYTES,
949                unused: [0, 0, 0, 0, 0, 0],
950            };
951            assert!(x.validate().is_ok())
952        }
953
954        #[test]
955        fn empty_string() {
956            let x = ReminderRow {
957                reminder: super::ONLY_SPACES_BYTES,
958                unused: [0, 0, 0, 0, 0, 0],
959            };
960            assert!(x.validate().is_ok())
961        }
962
963        #[test]
964        fn invalid_ascii() {
965            let x = ReminderRow {
966                reminder: super::INVALID_BYTES,
967                unused: [0, 0, 0, 0, 0, 0],
968            };
969            assert!(x.validate().is_ok())
970        }
971    }
972
973    mod display {
974        use crate::arrangements::ReminderRow;
975
976        #[test]
977        fn hello_world() {
978            let x = ReminderRow {
979                reminder: super::HELLO_WORLD_BYTES,
980                unused: [0, 0, 0, 0, 0, 0],
981            };
982            assert_eq!(format!["{x}"], "HELLO WORLD    ");
983        }
984
985        #[test]
986        fn empty_string() {
987            let x = ReminderRow {
988                reminder: super::ONLY_SPACES_BYTES,
989                unused: [0, 0, 0, 0, 0, 0],
990            };
991            assert_eq!(format!["{x}"], "               ");
992        }
993
994        #[test]
995        fn invalid_ascii() {
996            let x = ReminderRow {
997                reminder: super::INVALID_BYTES,
998                unused: [0, 0, 0, 0, 0, 0],
999            };
1000            assert_eq!(format!["{x}"], "");
1001        }
1002    }
1003}
1004
1005#[cfg(test)]
1006mod parse_to_bytes {
1007    mod pattern {
1008        use crate::arrangements::{ArrangeRow, PatternRow};
1009
1010        #[test]
1011        fn valid() {
1012            let x = ArrangeRow::PatternRow(PatternRow {
1013                pattern_id: 0,
1014                repetitions: 0,
1015                unused_1: 0,
1016                mute_mask: 0,
1017                unused_2: 0,
1018                tempo_1: 0,
1019                tempo_2: 0,
1020                scene_a: 0,
1021                scene_b: 0,
1022                unused_3: 0,
1023                offset: 0,
1024                unused_4: 0,
1025                length: 0,
1026                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
1027            });
1028            let r = x.parse_to_bytes();
1029            assert!(r.is_ok());
1030            assert_eq!(r.unwrap().len(), 22);
1031        }
1032    }
1033    mod loop_or_jump_or_halt {
1034        use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
1035        #[test]
1036        fn valid() {
1037            let x = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
1038                loop_count: 1,
1039                row_target: 1,
1040                unused: std::array::from_fn(|_| 0),
1041            });
1042            let r = x.parse_to_bytes();
1043            assert!(r.is_ok());
1044        }
1045    }
1046
1047    mod reminder {
1048        use crate::arrangements::{ArrangeRow, ReminderRow};
1049
1050        #[test]
1051        fn valid_string() {
1052            let x = ArrangeRow::ReminderRow(ReminderRow {
1053                // reminder: String::from("HELLO WORLD"),
1054                reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
1055                unused: [0, 0, 0, 0, 0, 0],
1056            });
1057            let r = x.parse_to_bytes();
1058            println!("{r:?}");
1059            assert!(r.is_ok());
1060            println!("{r:?}");
1061            assert_eq!(r.unwrap().len(), 22);
1062        }
1063
1064        #[test]
1065        fn empty_string() {
1066            let x = ArrangeRow::ReminderRow(ReminderRow {
1067                // reminder: String::from("HELLO WORLD"),
1068                reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
1069                unused: [0, 0, 0, 0, 0, 0],
1070            });
1071            let r = x.parse_to_bytes();
1072            println!("{r:?}");
1073            assert!(r.is_ok());
1074            assert_eq!(r.unwrap().len(), 22);
1075        }
1076    }
1077    mod empty_row {
1078        use crate::arrangements::ArrangeRow;
1079
1080        #[test]
1081        fn valid() {
1082            let x = ArrangeRow::default();
1083            let r = x.parse_to_bytes();
1084            println!("{r:?}");
1085            assert!(r.is_ok());
1086            assert_eq!(r.unwrap().len(), 22);
1087        }
1088    }
1089}
1090
1091#[cfg(test)]
1092mod parse_from_bytes {
1093    use crate::arrangements::ArrangeRow;
1094
1095    #[test]
1096    fn invalid_row_type() {
1097        let b = [
1098            3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1099        ];
1100        let r = ArrangeRow::parse_from_bytes(&b);
1101        assert!(r.is_err());
1102    }
1103
1104    mod empty_row {
1105        /*
1106        IMPORTANT: DON'T FIX: @dijksterhuis: It's not possible to work out if a
1107        row should be an EmptyRow exclusively from the bytes for that row.
1108
1109        An EmptyRow variant is only used when the current row's index is greater
1110        than the number of total rows in an ArrangementBlock.
1111
1112        This test is to make sure this is true and no-one tries to fix it.
1113        */
1114        use crate::arrangements::{ArrangeRow, PatternRow};
1115
1116        #[test]
1117        fn row_index_matters() {
1118            let not_expected = ArrangeRow::default();
1119            let expected = ArrangeRow::PatternRow(PatternRow {
1120                pattern_id: 0,
1121                repetitions: 0,
1122                unused_1: 0,
1123                mute_mask: 0,
1124                unused_2: 0,
1125                tempo_1: 0,
1126                tempo_2: 0,
1127                scene_a: 0,
1128                scene_b: 0,
1129                unused_3: 0,
1130                offset: 0,
1131                unused_4: 0,
1132                length: 0,
1133                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
1134            });
1135
1136            let b = [
1137                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1138            ];
1139            let r = ArrangeRow::parse_from_bytes(&b);
1140            println!("{r:?}");
1141            assert!(r.is_ok());
1142            let x = r.unwrap();
1143            assert_ne!(not_expected, x);
1144            assert_eq!(expected, x);
1145        }
1146    }
1147
1148    mod reminder_row {
1149        use crate::arrangements::{ArrangeRow, ReminderRow};
1150
1151        #[test]
1152        fn valid() {
1153            let expected = ArrangeRow::ReminderRow(ReminderRow {
1154                // "CCCCCCCCCCCCCCC"
1155                reminder: [43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43],
1156                unused: [0, 0, 0, 0, 0, 0],
1157            });
1158            let b = [
1159                2, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 0, 0, 0, 0, 0, 0,
1160            ];
1161            let r = ArrangeRow::parse_from_bytes(&b);
1162            assert_eq!(expected, r.unwrap());
1163        }
1164
1165        #[test]
1166        fn valid_drop_excess_characters() {
1167            let expected = ArrangeRow::ReminderRow(ReminderRow {
1168                // "CCCCCCCCCCCCCCC"
1169                reminder: [43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43],
1170                unused: [0, 0, 0, 0, 0, 0],
1171            });
1172            let b = [
1173                2, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 0, 0, 0, 0, 0, 0,
1174            ];
1175            let r = ArrangeRow::parse_from_bytes(&b);
1176            assert_eq!(expected, r.unwrap());
1177        }
1178    }
1179
1180    mod loop_jump_or_halt_row {
1181        use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
1182
1183        #[test]
1184        fn valid() {
1185            let expected = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
1186                loop_count: 1,
1187                row_target: 1,
1188                unused: std::array::from_fn(|_| 0),
1189            });
1190            let b = [
1191                1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1192            ];
1193            let r = ArrangeRow::parse_from_bytes(&b);
1194            assert_eq!(expected, r.unwrap());
1195        }
1196
1197        #[test]
1198        fn invalid_loop_count() {
1199            let b = [
1200                1, 101, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1201            ];
1202            let r = ArrangeRow::parse_from_bytes(&b);
1203            assert!(r.is_err());
1204        }
1205    }
1206
1207    mod pattern_row {
1208        use crate::arrangements::{ArrangeRow, PatternRow};
1209
1210        #[test]
1211        fn valid_pattern_id_only() {
1212            let expected = ArrangeRow::PatternRow(PatternRow {
1213                pattern_id: 1,
1214                repetitions: 0,
1215                unused_1: 0,
1216                mute_mask: 0,
1217                unused_2: 0,
1218                tempo_1: 0,
1219                tempo_2: 0,
1220                scene_a: 0,
1221                scene_b: 0,
1222                unused_3: 0,
1223                offset: 0,
1224                unused_4: 0,
1225                length: 0,
1226                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
1227            });
1228            let b = [
1229                0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1230            ];
1231            let r = ArrangeRow::parse_from_bytes(&b);
1232            assert_eq!(expected, r.unwrap());
1233        }
1234
1235        #[test]
1236        fn valid_last_midi_transpose_only() {
1237            let expected = ArrangeRow::PatternRow(PatternRow {
1238                pattern_id: 0,
1239                repetitions: 0,
1240                unused_1: 0,
1241                mute_mask: 0,
1242                unused_2: 0,
1243                tempo_1: 0,
1244                tempo_2: 0,
1245                scene_a: 0,
1246                scene_b: 0,
1247                unused_3: 0,
1248                offset: 0,
1249                unused_4: 0,
1250                length: 0,
1251                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 8],
1252            });
1253            let b = [
1254                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
1255            ];
1256            let r = ArrangeRow::parse_from_bytes(&b);
1257            assert_eq!(expected, r.unwrap());
1258        }
1259
1260        #[test]
1261        fn invalid_too_many_repetitions() {
1262            let b = [
1263                0, 1, 64, 1, 1, 1, 1, 1, 15, 15, 1, 1, 1, 64, 3, 3, 3, 3, 3, 3, 3, 3,
1264            ];
1265            let r = ArrangeRow::parse_from_bytes(&b);
1266            assert!(r.is_err());
1267        }
1268
1269        #[test]
1270        fn valid_all() {
1271            let expected = ArrangeRow::PatternRow(PatternRow {
1272                pattern_id: 1,
1273                repetitions: 10,
1274                unused_1: 55,
1275                mute_mask: 1,
1276                unused_2: 55,
1277                tempo_1: 1,
1278                tempo_2: 1,
1279                scene_a: 15,
1280                scene_b: 15,
1281                unused_3: 55,
1282                offset: 1,
1283                unused_4: 55,
1284                length: 64,
1285                midi_transpose: [3, 3, 3, 3, 3, 3, 3, 3],
1286            });
1287            let b = [
1288                0, 1, 10, 55, 1, 55, 1, 1, 15, 15, 55, 1, 55, 64, 3, 3, 3, 3, 3, 3, 3, 3,
1289            ];
1290            let r = ArrangeRow::parse_from_bytes(&b);
1291            assert_eq!(expected, r.unwrap());
1292        }
1293
1294        #[test]
1295        fn zero_valued_is_still_pattern_row() {
1296            let expected = ArrangeRow::PatternRow(PatternRow {
1297                pattern_id: 0,
1298                repetitions: 0,
1299                unused_1: 0,
1300                mute_mask: 0,
1301                unused_2: 0,
1302                tempo_1: 0,
1303                tempo_2: 0,
1304                scene_a: 0,
1305                scene_b: 0,
1306                unused_3: 0,
1307                offset: 0,
1308                unused_4: 0,
1309                length: 0,
1310                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
1311            });
1312            let b = [
1313                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1314            ];
1315            let r = ArrangeRow::parse_from_bytes(&b);
1316            assert_eq!(expected, r.unwrap());
1317        }
1318    }
1319}
1320
1321#[cfg(test)]
1322mod deserialize {
1323
1324    mod pattern {
1325        // todo
1326    }
1327
1328    mod empty {
1329        use crate::arrangements::ArrangeRow;
1330
1331        #[test]
1332        fn valid_yaml() {
1333            let expected = ArrangeRow::default();
1334            let s = "!empty_row\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n";
1335            let r = serde_norway::from_str::<ArrangeRow>(s);
1336            println!("{r:?}");
1337            assert!(r.is_ok());
1338            assert_eq!(expected, r.unwrap());
1339        }
1340
1341        #[test]
1342        fn valid_json() {
1343            let expected = ArrangeRow::default();
1344            let s =
1345                "{\"empty_row\":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}";
1346            let r = serde_json::from_str::<ArrangeRow>(s);
1347            println!("{r:?}");
1348            assert!(r.is_ok());
1349            assert_eq!(expected, r.unwrap());
1350        }
1351    }
1352
1353    mod loop_jump_or_halt {
1354        use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
1355
1356        #[test]
1357        fn valid_yaml() {
1358            let expected = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
1359                loop_count: 1,
1360                row_target: 1,
1361                unused: std::array::from_fn(|_| 0),
1362            });
1363
1364            let s = "!loop_or_jump_or_halt_row\nloop_count: 1\nrow_target: 1\nunused:\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n";
1365            let r = serde_norway::from_str::<ArrangeRow>(s);
1366            assert_eq!(expected, r.unwrap());
1367        }
1368
1369        #[test]
1370        fn valid_json() {
1371            let expected = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
1372                loop_count: 1,
1373                row_target: 1,
1374                unused: std::array::from_fn(|_| 0),
1375            });
1376
1377            let s = "{\"loop_or_jump_or_halt_row\":{\"loop_count\": 1, \"row_target\": 1, \"unused\": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}";
1378            let r = serde_json::from_str::<ArrangeRow>(s);
1379            assert_eq!(expected, r.unwrap());
1380        }
1381    }
1382
1383    mod reminder {
1384        use crate::arrangements::{ArrangeRow, ReminderRow};
1385
1386        #[test]
1387        fn valid_json() {
1388            let expected = ArrangeRow::ReminderRow(ReminderRow {
1389                reminder: [67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67],
1390                unused: [0, 0, 0, 0, 0, 0],
1391            });
1392
1393            let s = "{\"reminder_row\":{\"reminder\":[67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67], \"unused\": [0, 0, 0, 0, 0, 0]}}";
1394            let r = serde_json::from_str::<ArrangeRow>(s);
1395            assert_eq!(expected, r.unwrap());
1396        }
1397
1398        #[test]
1399        fn valid_yaml() {
1400            let expected = ArrangeRow::ReminderRow(ReminderRow {
1401                reminder: [67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67],
1402                unused: [0, 0, 0, 0, 0, 0],
1403            });
1404
1405            let s = "!reminder_row\nreminder:\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\n- 67\nunused:\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n";
1406            let r = serde_norway::from_str::<ArrangeRow>(s);
1407            assert_eq!(expected, r.unwrap());
1408        }
1409    }
1410}
1411
1412#[cfg(test)]
1413mod arrange_row {
1414    mod pattern {
1415        use crate::arrangements::{ArrangeRow, PatternRow};
1416
1417        #[test]
1418        fn valid_yaml() {
1419            let x = ArrangeRow::PatternRow(PatternRow {
1420                pattern_id: 0,
1421                repetitions: 0,
1422                unused_1: 0,
1423                mute_mask: 0,
1424                unused_2: 0,
1425                tempo_1: 0,
1426                tempo_2: 0,
1427                scene_a: 0,
1428                scene_b: 0,
1429                unused_3: 0,
1430                offset: 0,
1431                unused_4: 0,
1432                length: 0,
1433                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
1434            });
1435            let r = serde_norway::to_string(&x);
1436            println!("{r:?}");
1437            assert!(r.is_ok());
1438            assert_eq!(r.unwrap(), "!pattern_row\npattern_id: 0\nrepetitions: 0\nunused_1: 0\nmute_mask: 0\nunused_2: 0\ntempo_1: 0\ntempo_2: 0\nscene_a: 0\nscene_b: 0\nunused_3: 0\noffset: 0\nunused_4: 0\nlength: 0\nmidi_transpose:\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n");
1439        }
1440
1441        #[test]
1442        fn valid_json() {
1443            let x = ArrangeRow::PatternRow(PatternRow {
1444                pattern_id: 0,
1445                repetitions: 0,
1446                unused_1: 0,
1447                mute_mask: 0,
1448                unused_2: 0,
1449                tempo_1: 0,
1450                tempo_2: 0,
1451                scene_a: 0,
1452                scene_b: 0,
1453                unused_3: 0,
1454                offset: 0,
1455                unused_4: 0,
1456                length: 0,
1457                midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
1458            });
1459            let r = serde_json::to_string(&x);
1460            println!("{r:?}");
1461            assert!(r.is_ok());
1462            assert_eq!(r.unwrap(), "{\"pattern_row\":{\"pattern_id\":0,\"repetitions\":0,\"unused_1\":0,\"mute_mask\":0,\"unused_2\":0,\"tempo_1\":0,\"tempo_2\":0,\"scene_a\":0,\"scene_b\":0,\"unused_3\":0,\"offset\":0,\"unused_4\":0,\"length\":0,\"midi_transpose\":[0,0,0,0,0,0,0,0]}}");
1463        }
1464    }
1465
1466    mod loop_or_jump_or_halt {
1467        use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
1468        use std::array::from_fn;
1469
1470        #[test]
1471        fn valid_yaml() {
1472            let x = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
1473                loop_count: 1,
1474                row_target: 1,
1475                unused: from_fn(|_| 0),
1476            });
1477            let r = serde_norway::to_string(&x);
1478            println!("{r:?}");
1479            assert!(r.is_ok());
1480            assert_eq!(r.unwrap(), "!loop_or_jump_or_halt_row\nloop_count: 1\nrow_target: 1\nunused:\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n");
1481        }
1482
1483        #[test]
1484        fn valid_json() {
1485            let x = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
1486                loop_count: 1,
1487                row_target: 1,
1488                unused: from_fn(|_| 0),
1489            });
1490            let r = serde_json::to_string(&x);
1491            println!("{r:?}");
1492            assert!(r.is_ok());
1493            assert_eq!(r.unwrap(), "{\"loop_or_jump_or_halt_row\":{\"loop_count\":1,\"row_target\":1,\"unused\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}}");
1494        }
1495    }
1496
1497    mod reminder_row {
1498        use crate::arrangements::{ArrangeRow, ReminderRow};
1499
1500        #[test]
1501        fn valid_string() {
1502            let x = ArrangeRow::ReminderRow(ReminderRow {
1503                // reminder: String::from("HELLO WORLD"),
1504                reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
1505                unused: [0, 0, 0, 0, 0, 0],
1506            });
1507            let r = x.parse_to_bytes();
1508            println!("{r:?}");
1509            assert!(r.is_ok());
1510            println!("{r:?}");
1511            assert_eq!(r.unwrap().len(), 22);
1512        }
1513
1514        #[test]
1515        fn valid_string_yaml() {
1516            let x = ArrangeRow::ReminderRow(ReminderRow {
1517                // reminder: String::from("HELLO WORLD"),
1518                reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
1519                unused: [0, 0, 0, 0, 0, 0],
1520            });
1521            let r = serde_norway::to_string(&x);
1522            println!("{r:?}");
1523            assert!(r.is_ok());
1524            assert_eq!(r.unwrap(), "!reminder_row\nreminder:\n- 72\n- 69\n- 76\n- 76\n- 79\n- 32\n- 87\n- 79\n- 82\n- 76\n- 68\n- 20\n- 20\n- 20\n- 20\nunused:\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n");
1525        }
1526
1527        #[test]
1528        fn valid_string_json() {
1529            let x = ArrangeRow::ReminderRow(ReminderRow {
1530                // reminder: String::from("HELLO WORLD"),
1531                reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
1532                unused: [0, 0, 0, 0, 0, 0],
1533            });
1534            let r = serde_json::to_string(&x);
1535            println!("{r:?}");
1536            assert!(r.is_ok());
1537            assert_eq!(r.unwrap(), "{\"reminder_row\":{\"reminder\":[72,69,76,76,79,32,87,79,82,76,68,20,20,20,20],\"unused\":[0,0,0,0,0,0]}}");
1538        }
1539
1540        #[test]
1541        fn empty_string() {
1542            let x = ArrangeRow::ReminderRow(ReminderRow {
1543                // reminder: String::from("HELLO WORLD"),
1544                reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
1545                unused: [0, 0, 0, 0, 0, 0],
1546            });
1547            let r = x.parse_to_bytes();
1548            println!("{r:?}");
1549            assert!(r.is_ok());
1550            assert_eq!(r.unwrap().len(), 22);
1551        }
1552
1553        #[test]
1554        fn empty_string_yaml() {
1555            let x = ArrangeRow::ReminderRow(ReminderRow {
1556                // reminder: String::from("HELLO WORLD"),
1557                reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
1558                unused: [0, 0, 0, 0, 0, 0],
1559            });
1560            let r = serde_norway::to_string(&x);
1561            println!("{r:?}");
1562            assert!(r.is_ok());
1563            assert_eq!(r.unwrap(), "!reminder_row\nreminder:\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\n- 20\nunused:\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n");
1564        }
1565
1566        #[test]
1567        fn empty_string_json() {
1568            let x = ArrangeRow::ReminderRow(ReminderRow {
1569                // reminder: String::from("HELLO WORLD"),
1570                reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
1571                unused: [0, 0, 0, 0, 0, 0],
1572            });
1573            let r = serde_json::to_string(&x);
1574            println!("{r:?}");
1575            assert!(r.is_ok());
1576            assert_eq!(r.unwrap(), "{\"reminder_row\":{\"reminder\":[20,20,20,20,20,20,20,20,20,20,20,20,20,20,20],\"unused\":[0,0,0,0,0,0]}}");
1577        }
1578
1579        // #[test]
1580        // fn invalid() {
1581        //     // 16 character string
1582        //     let x = super::ArrangeRow::Reminder ( super::ReminderRow {
1583        //         reminder: String::from("1111111111111111"),
1584        //         unused: [0, 0, 0, 0, 0, 0]
1585        //     };
1586        //     let r = bincode::serialize(&x);
1587        //     assert!(r.is_err());
1588        // }
1589    }
1590
1591    mod empty_row {
1592        use crate::arrangements::ArrangeRow;
1593
1594        #[test]
1595        fn valid_yaml() {
1596            let x = ArrangeRow::default();
1597            let r = serde_norway::to_string(&x);
1598            println!("{r:?}");
1599            assert!(r.is_ok());
1600            assert_eq!(r.unwrap(), "!empty_row\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n");
1601        }
1602
1603        #[test]
1604        fn valid_json() {
1605            let x = ArrangeRow::default();
1606            let r = serde_json::to_string(&x);
1607            println!("{r:?}");
1608            assert!(r.is_ok());
1609            assert_eq!(
1610                r.unwrap(),
1611                "{\"empty_row\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}"
1612            );
1613        }
1614    }
1615}