ot_tools_io/arrangements/
serialize.rs

1/*
2SPDX-License-Identifier: GPL-3.0-or-later
3Copyright © 2024 Mike Robeson [dijksterhuis]
4*/
5
6//! Custom serialization of arrangement data.
7//!
8//! The variant of an `ArrangeRow` is determined by
9//! - the row's index in the `ArrangementBlock.rows` array versus number of rows in the arrangement `ArrangementBlock.n_rows`
10//! - The value of the first byte for an `ArrangeRow`. See the table below
11//!
12//! | `ArrangeRow` Variant   | First Byte |
13//! | ---------------------- | ---------- |
14//! | `PatternRow`           | 0          |
15//! | `ReminderRow`          | 0          |
16//! | `LoopOrJumpOrHaltRow`  | 0          |
17//! | `PatternRow`           | 0          |
18//! | `EmptyRow`             | n/a        |
19
20use crate::arrangements::{ArrangeRow, ArrangementBlock};
21use itertools::Itertools;
22
23use serde::ser::{Error as SerializeErr, SerializeMap, SerializeStruct, Serializer};
24use serde::Serialize;
25
26/// Custom serialization to ensure we can validate that the correct number of `ArrangeRow::EmptyRow`
27/// variants will be present in the resulting data.
28impl Serialize for ArrangementBlock {
29    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
30    where
31        S: Serializer,
32    {
33        // Validation for number of arrangement rows versus the first appearance of a EmptyRow
34        // variant (position of first EmptyRow variant should match n_rows).
35        //
36        // NOTE: This is pure serialization. I'm not messing around with easier YAML config
37        // interfaces for users just yet.
38        //
39        // That might come along later in octatrack-bin and would look something like:
40        // - what do you want your arrangement rows to look like
41        // - translate that to an ArrangementFile struct
42        // - dump to work file (maybe copy existing state to previous state in work file if it
43        // exists)
44
45        let first_empty_row = self
46            .rows
47            .iter()
48            .find_position(|x| **x == ArrangeRow::EmptyRow());
49        if first_empty_row.is_none() && self.n_rows < 255_u8 {
50            return Err(S::Error::custom(format![
51                "No Empty Rows, but n_rows is less than 255: firstEmptyIdx={:?} nRows={:?}",
52                first_empty_row.unwrap().0,
53                self.n_rows,
54            ]));
55        }
56
57        let first_empty_row = first_empty_row.unwrap_or((0, &ArrangeRow::EmptyRow())).0;
58        if first_empty_row != self.n_rows as usize {
59            return Err(S::Error::custom(format![
60                "Index of first Empty Row does not match value for n_rows: idx={:?} nRows={:?}",
61                first_empty_row, self.n_rows,
62            ]));
63        }
64
65        let mut state = serializer.serialize_struct("ArrangementBlock", 4)?;
66        state.serialize_field("name", &self.name)?;
67        state.serialize_field("unknown_1", &self.unknown_1)?;
68        state.serialize_field("n_rows", &self.n_rows)?;
69        state.serialize_field("rows", &self.rows)?;
70        state.end()
71    }
72}
73
74/// Custom serialization to ensure we can serialize both bytes and human-readable data formats
75/// correctly.
76/// Please note this currently abuses the `serialize_struct` pattern for writing binary/bytes to
77/// ensure we get the correct number of bytes in the correct positions.
78impl Serialize for ArrangeRow {
79    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
80    where
81        S: Serializer,
82    {
83        // YAML / JSON
84        match self {
85            ArrangeRow::PatternRow {
86                pattern_id,
87                repetitions,
88                mute_mask,
89                tempo_1,
90                tempo_2,
91                scene_a,
92                scene_b,
93                offset,
94                length,
95                midi_transpose,
96            } => {
97                // TODO: Is max 64 on device
98                if repetitions > &63_u8 {
99                    return Err(S::Error::custom(
100                        "ArrangeRow::PatternRow: Repetitions cannot exceed 63 (64x)",
101                    ));
102                }
103
104                if *scene_a != 255_u8 && scene_a > &15_u8 {
105                    return Err(S::Error::custom("ArrangeRow::PatternRow: Scene A index cannot be greater than 15 (zero index; 16 length)"));
106                }
107                if *scene_b != 255_u8 && scene_b > &15_u8 {
108                    return Err(S::Error::custom("ArrangeRow::PatternRow: Scene B index cannot be greater than 15 (zero index; 16 length)"));
109                }
110
111                if serializer.is_human_readable() {
112                    let mut state = serializer.serialize_struct("PatternRow", 10)?;
113                    state.serialize_field("pattern_id", pattern_id)?;
114                    state.serialize_field("repetitions", repetitions)?;
115                    state.serialize_field("mute_mask", mute_mask)?;
116                    state.serialize_field("tempo_1", tempo_1)?;
117                    state.serialize_field("tempo_2", tempo_2)?;
118                    state.serialize_field("scene_a", scene_a)?;
119                    state.serialize_field("scene_b", scene_b)?;
120                    state.serialize_field("offset", offset)?;
121                    state.serialize_field("length", length)?;
122                    state.serialize_field("midi_transpose", midi_transpose)?;
123                    state.end()
124                } else {
125                    let mut state = serializer.serialize_struct("PatternRow", 22)?;
126                    state.serialize_field("row_type", &0_u8)?;
127                    state.serialize_field("pattern_id", pattern_id)?;
128                    state.serialize_field("repetitions", repetitions)?;
129                    state.serialize_field("unused_1", &0_u8)?;
130                    state.serialize_field("mute_mask", mute_mask)?;
131                    state.serialize_field("unused_2", &0_u8)?;
132                    state.serialize_field("tempo_1", tempo_1)?;
133                    state.serialize_field("tempo_2", tempo_2)?;
134                    state.serialize_field("scene_a", scene_a)?;
135                    state.serialize_field("scene_b", scene_b)?;
136                    state.serialize_field("unused_3", &0_u8)?;
137                    state.serialize_field("offset", offset)?;
138                    state.serialize_field("unused_4", &0_u8)?;
139                    state.serialize_field("length", length)?;
140                    state.serialize_field("midi_transpose", midi_transpose)?;
141                    state.end()
142                }
143            }
144            ArrangeRow::LoopOrJumpOrHaltRow {
145                loop_count,
146                row_target,
147            } => {
148                if loop_count > &100_u8 {
149                    return Err(S::Error::custom(
150                        "ArrangeRow::LoopOrJumpOrHaltRow: Loop count cannot exceed 100 (99x)",
151                    ));
152                }
153
154                if serializer.is_human_readable() {
155                    let mut state = serializer.serialize_struct("LoopOrJumpOrHaltRow", 2)?;
156                    state.serialize_field("loop_count", loop_count)?;
157                    state.serialize_field("row_target", row_target)?;
158                    state.end()
159                } else {
160                    let mut state = serializer.serialize_struct("LoopOrJumpOrHaltRow", 22)?;
161                    state.serialize_field("row_type", &1_u8)?;
162                    state.serialize_field("loop_count", loop_count)?;
163                    state.serialize_field("row_target", row_target)?;
164                    state.serialize_field("unused_1", &0_u8)?;
165                    state.serialize_field("unused_2", &0_u8)?;
166                    state.serialize_field("unused_3", &0_u8)?;
167                    state.serialize_field("unused_4", &0_u8)?;
168                    state.serialize_field("unused_5", &0_u8)?;
169                    state.serialize_field("unused_6", &0_u8)?;
170                    state.serialize_field("unused_7", &0_u8)?;
171                    state.serialize_field("unused_8", &0_u8)?;
172                    state.serialize_field("unused_9", &0_u8)?;
173                    state.serialize_field("unused_10", &0_u8)?;
174                    state.serialize_field("unused_11", &0_u8)?;
175                    state.serialize_field("unused_12", &0_u8)?;
176                    state.serialize_field("unused_13", &0_u8)?;
177                    state.serialize_field("unused_14", &0_u8)?;
178                    state.serialize_field("unused_15", &0_u8)?;
179                    state.serialize_field("unused_16", &0_u8)?;
180                    state.serialize_field("unused_17", &0_u8)?;
181                    state.serialize_field("unused_18", &0_u8)?;
182                    state.serialize_field("unused_19", &0_u8)?;
183                    state.end()
184                }
185            }
186            ArrangeRow::ReminderRow(x) => {
187                if x.len() > 15 {
188                    return Err(S::Error::custom(format![
189                        "ArrangeRow::ReminderRow: string length exceeds 15: str={x:?}",
190                    ]));
191                };
192                if serializer.is_human_readable() {
193                    let mut state = serializer.serialize_map(Some(1))?;
194                    state.serialize_entry("reminder", x)?;
195                    state.end()
196                } else {
197                    let mut state = serializer.serialize_struct("ReminderRow", 22)?;
198                    state.serialize_field("row_type", &2_u8)?;
199                    for c in x.as_bytes() {
200                        state.serialize_field("char", &c)?;
201                    }
202                    for _ in x.len()..15 {
203                        state.serialize_field("char", &0_u8)?;
204                    }
205                    state.serialize_field("unused_1", &0_u8)?;
206                    state.serialize_field("unused_2", &0_u8)?;
207                    state.serialize_field("unused_3", &0_u8)?;
208                    state.serialize_field("unused_4", &0_u8)?;
209                    state.serialize_field("unused_5", &0_u8)?;
210                    state.serialize_field("unused_6", &0_u8)?;
211                    state.end()
212                }
213            }
214            ArrangeRow::EmptyRow() => {
215                if serializer.is_human_readable() {
216                    let mut state = serializer.serialize_map(Some(1))?;
217                    state.serialize_entry("empty", "")?;
218                    state.end()
219                } else {
220                    let mut state = serializer.serialize_struct("EmptyRow", 0)?;
221                    state.serialize_field("unused_1", &0_u8)?;
222                    state.serialize_field("unused_2", &0_u8)?;
223                    state.serialize_field("unused_3", &0_u8)?;
224                    state.serialize_field("unused_4", &0_u8)?;
225                    state.serialize_field("unused_5", &0_u8)?;
226                    state.serialize_field("unused_6", &0_u8)?;
227                    state.serialize_field("unused_7", &0_u8)?;
228                    state.serialize_field("unused_8", &0_u8)?;
229                    state.serialize_field("unused_9", &0_u8)?;
230                    state.serialize_field("unused_10", &0_u8)?;
231                    state.serialize_field("unused_11", &0_u8)?;
232                    state.serialize_field("unused_12", &0_u8)?;
233                    state.serialize_field("unused_13", &0_u8)?;
234                    state.serialize_field("unused_14", &0_u8)?;
235                    state.serialize_field("unused_15", &0_u8)?;
236                    state.serialize_field("unused_16", &0_u8)?;
237                    state.serialize_field("unused_17", &0_u8)?;
238                    state.serialize_field("unused_18", &0_u8)?;
239                    state.serialize_field("unused_19", &0_u8)?;
240                    state.serialize_field("unused_20", &0_u8)?;
241                    state.serialize_field("unused_21", &0_u8)?;
242                    state.serialize_field("unused_22", &0_u8)?;
243                    state.end()
244                }
245            }
246        }
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    mod arrangement_file {
255        use crate::arrangements::ArrangementFile;
256        use crate::test_utils::get_arrange_dirpath;
257        use crate::{OctatrackFileIO, OtToolsIoError};
258
259        #[test]
260        // WARN: Currently depends on deserialization being functional.
261        fn test_serialize_to_json() -> Result<(), OtToolsIoError> {
262            let path = get_arrange_dirpath().join("blank.work");
263            assert!(ArrangementFile::from_data_file(&path)?
264                .to_json_string()
265                .is_ok());
266            Ok(())
267        }
268
269        #[test]
270        // Windows will add on carriage returns...
271        #[cfg(not(target_os = "windows"))]
272        // WARN: Depends on deserialization being functional.
273        fn test_serialize_to_yaml() -> Result<(), OtToolsIoError> {
274            let valid_yaml_path = get_arrange_dirpath().join("blank.yaml");
275            let valid_yaml = crate::read_str_file(&valid_yaml_path);
276
277            let bin_file_path = get_arrange_dirpath().join("blank.work");
278            let yaml = ArrangementFile::from_data_file(&bin_file_path)?.to_yaml_string();
279
280            assert!(yaml.is_ok());
281            assert_eq!(valid_yaml?, yaml?);
282            Ok(())
283        }
284    }
285
286    mod arrangement_block {
287        #[test]
288        fn test_ok() {
289            let expected_rows: [super::ArrangeRow; 256] = std::array::from_fn(|i| {
290                if i < 10 {
291                    super::ArrangeRow::PatternRow {
292                        pattern_id: 1,
293                        repetitions: 1,
294                        mute_mask: 1,
295                        tempo_1: 1,
296                        tempo_2: 1,
297                        scene_a: 1,
298                        scene_b: 1,
299                        offset: 1,
300                        length: 1,
301                        midi_transpose: [8, 1, 1, 1, 1, 1, 1, 8],
302                    }
303                } else {
304                    super::ArrangeRow::EmptyRow()
305                }
306            });
307
308            let rows = Box::new(serde_big_array::Array(expected_rows));
309
310            let expected = super::ArrangementBlock {
311                name: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10],
312                unknown_1: [10, 9],
313                n_rows: 10,
314                rows,
315            };
316
317            // TODO: Need to do modulo on index to create pattern row data
318            let _: [u8; 5652] = std::array::from_fn(|x| {
319                match x {
320                    // start name
321                    0 => 10,
322                    // end name
323                    13 => 10,
324                    // unk1 start
325                    14 => 10,
326                    // unk2 end
327                    15 => 9,
328                    // n rows
329                    16 => 10,
330                    // unk2 start
331                    5650 => 10,
332                    // unk2 end
333                    5651 => 9,
334                    _ => 0,
335                }
336            });
337            let r = bincode::serialize(&expected);
338            println!("{r:?}");
339            assert!(r.is_ok());
340            let v = r.unwrap();
341            assert_eq!(5650, v.len());
342        }
343    }
344
345    mod arrangement_row {
346        use super::*;
347
348        mod pattern_row {
349            #[test]
350            fn valid() {
351                let x = super::ArrangeRow::PatternRow {
352                    pattern_id: 0,
353                    repetitions: 0,
354                    mute_mask: 0,
355                    tempo_1: 0,
356                    tempo_2: 0,
357                    scene_a: 0,
358                    scene_b: 0,
359                    offset: 0,
360                    length: 0,
361                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
362                };
363                let r = bincode::serialize(&x);
364                println!("{r:?}");
365                assert!(r.is_ok());
366                assert_eq!(r.unwrap().len(), 22);
367            }
368
369            #[test]
370            fn valid_yaml() {
371                let x = super::ArrangeRow::PatternRow {
372                    pattern_id: 0,
373                    repetitions: 0,
374                    mute_mask: 0,
375                    tempo_1: 0,
376                    tempo_2: 0,
377                    scene_a: 0,
378                    scene_b: 0,
379                    offset: 0,
380                    length: 0,
381                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
382                };
383                let r = serde_yml::to_string(&x);
384                println!("{r:?}");
385                assert!(r.is_ok());
386                assert_eq!(r.unwrap(), "pattern_id: 0\nrepetitions: 0\nmute_mask: 0\ntempo_1: 0\ntempo_2: 0\nscene_a: 0\nscene_b: 0\noffset: 0\nlength: 0\nmidi_transpose:\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n- 0\n");
387            }
388
389            #[test]
390            fn valid_json() {
391                let x = super::ArrangeRow::PatternRow {
392                    pattern_id: 0,
393                    repetitions: 0,
394                    mute_mask: 0,
395                    tempo_1: 0,
396                    tempo_2: 0,
397                    scene_a: 0,
398                    scene_b: 0,
399                    offset: 0,
400                    length: 0,
401                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
402                };
403                let r = serde_json::to_string(&x);
404                println!("{r:?}");
405                assert!(r.is_ok());
406                assert_eq!(r.unwrap(), "{\"pattern_id\":0,\"repetitions\":0,\"mute_mask\":0,\"tempo_1\":0,\"tempo_2\":0,\"scene_a\":0,\"scene_b\":0,\"offset\":0,\"length\":0,\"midi_transpose\":[0,0,0,0,0,0,0,0]}");
407            }
408
409            #[test]
410            fn invalid_repetitions() {
411                let x = super::ArrangeRow::PatternRow {
412                    pattern_id: 0,
413                    repetitions: 64,
414                    mute_mask: 0,
415                    tempo_1: 0,
416                    tempo_2: 0,
417                    scene_a: 0,
418                    scene_b: 0,
419                    offset: 0,
420                    length: 0,
421                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
422                };
423                let r = bincode::serialize(&x);
424                assert!(r.is_err());
425            }
426
427            #[test]
428            fn valid_scene_a_off() {
429                let x = super::ArrangeRow::PatternRow {
430                    pattern_id: 0,
431                    repetitions: 0,
432                    mute_mask: 0,
433                    tempo_1: 0,
434                    tempo_2: 0,
435                    scene_a: 255,
436                    scene_b: 0,
437                    offset: 0,
438                    length: 0,
439                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
440                };
441                let r = bincode::serialize(&x);
442                println!("{r:?}");
443                assert!(r.is_ok());
444            }
445
446            #[test]
447            fn valid_scene_b_off() {
448                let x = super::ArrangeRow::PatternRow {
449                    pattern_id: 0,
450                    repetitions: 0,
451                    mute_mask: 0,
452                    tempo_1: 0,
453                    tempo_2: 0,
454                    scene_a: 0,
455                    scene_b: 255,
456                    offset: 0,
457                    length: 0,
458                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
459                };
460                let r = bincode::serialize(&x);
461                println!("{r:?}");
462                assert!(r.is_ok());
463            }
464
465            #[test]
466            fn invalid_scene_a() {
467                let x = super::ArrangeRow::PatternRow {
468                    pattern_id: 0,
469                    repetitions: 0,
470                    mute_mask: 0,
471                    tempo_1: 0,
472                    tempo_2: 0,
473                    scene_a: 16,
474                    scene_b: 0,
475                    offset: 0,
476                    length: 0,
477                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
478                };
479                let r = bincode::serialize(&x);
480                println!("{r:#?}");
481                assert!(r.is_err());
482            }
483
484            #[test]
485            fn invalid_scene_b() {
486                let x = super::ArrangeRow::PatternRow {
487                    pattern_id: 0,
488                    repetitions: 0,
489                    mute_mask: 0,
490                    tempo_1: 0,
491                    tempo_2: 0,
492                    scene_a: 16,
493                    scene_b: 16,
494                    offset: 0,
495                    length: 0,
496                    midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
497                };
498                let r = bincode::serialize(&x);
499                assert!(r.is_err());
500            }
501        }
502
503        mod loop_or_jump_or_halt {
504
505            #[test]
506            fn valid() {
507                let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
508                    loop_count: 1,
509                    row_target: 1,
510                };
511                let r = bincode::serialize(&x);
512                println!("{r:?}");
513                assert!(r.is_ok());
514                assert_eq!(r.unwrap().len(), 22);
515            }
516
517            #[test]
518            fn valid_yaml() {
519                let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
520                    loop_count: 1,
521                    row_target: 1,
522                };
523                let r = serde_yml::to_string(&x);
524                println!("{r:?}");
525                assert!(r.is_ok());
526                assert_eq!(r.unwrap(), "loop_count: 1\nrow_target: 1\n");
527            }
528
529            #[test]
530            fn valid_json() {
531                let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
532                    loop_count: 1,
533                    row_target: 1,
534                };
535                let r = serde_json::to_string(&x);
536                println!("{r:?}");
537                assert!(r.is_ok());
538                assert_eq!(r.unwrap(), "{\"loop_count\":1,\"row_target\":1}");
539            }
540
541            #[test]
542            fn invalid_loop_count() {
543                let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
544                    loop_count: 101,
545                    row_target: 1,
546                };
547                let r = bincode::serialize(&x);
548                assert!(r.is_err());
549            }
550        }
551
552        mod reminder_row {
553            #[test]
554            fn valid_string() {
555                let x = super::ArrangeRow::ReminderRow(String::from("HELLO WORLD"));
556                let r = bincode::serialize(&x);
557                println!("{r:?}");
558                assert!(r.is_ok());
559                println!("{r:?}");
560                assert_eq!(r.unwrap().len(), 22);
561            }
562
563            #[test]
564            fn valid_string_yaml() {
565                let x = super::ArrangeRow::ReminderRow(String::from("HELLO WORLD"));
566                let r = serde_yml::to_string(&x);
567                println!("{r:?}");
568                assert!(r.is_ok());
569                assert_eq!(r.unwrap(), "reminder: HELLO WORLD\n");
570            }
571
572            #[test]
573            fn valid_string_json() {
574                let x = super::ArrangeRow::ReminderRow(String::from("HELLO WORLD"));
575                let r = serde_json::to_string(&x);
576                println!("{r:?}");
577                assert!(r.is_ok());
578                assert_eq!(r.unwrap(), "{\"reminder\":\"HELLO WORLD\"}");
579            }
580
581            #[test]
582            fn empty_string() {
583                let x = super::ArrangeRow::ReminderRow(String::new());
584                let r = bincode::serialize(&x);
585                println!("{r:?}");
586                assert!(r.is_ok());
587                assert_eq!(r.unwrap().len(), 22);
588            }
589
590            #[test]
591            fn empty_string_yaml() {
592                let x = super::ArrangeRow::ReminderRow(String::new());
593                let r = serde_yml::to_string(&x);
594                println!("{r:?}");
595                assert!(r.is_ok());
596                assert_eq!(r.unwrap(), "reminder: ''\n");
597            }
598
599            #[test]
600            fn empty_string_json() {
601                let x = super::ArrangeRow::ReminderRow(String::new());
602                let r = serde_json::to_string(&x);
603                println!("{r:?}");
604                assert!(r.is_ok());
605                assert_eq!(r.unwrap(), "{\"reminder\":\"\"}");
606            }
607
608            #[test]
609            fn invalid() {
610                // 16 character string
611                let x = super::ArrangeRow::ReminderRow(String::from("1111111111111111"));
612                let r = bincode::serialize(&x);
613                assert!(r.is_err());
614            }
615        }
616
617        mod empty_row {
618            #[test]
619            fn valid() {
620                let x = super::ArrangeRow::EmptyRow();
621                let r = bincode::serialize(&x);
622                println!("{r:?}");
623                assert!(r.is_ok());
624                assert_eq!(r.unwrap().len(), 22);
625            }
626
627            #[test]
628            fn valid_yaml() {
629                let x = super::ArrangeRow::EmptyRow();
630                let r = serde_yml::to_string(&x);
631                println!("{r:?}");
632                assert!(r.is_ok());
633                assert_eq!(r.unwrap(), "empty: ''\n");
634            }
635
636            #[test]
637            fn valid_json() {
638                let x = super::ArrangeRow::EmptyRow();
639                let r = serde_json::to_string(&x);
640                println!("{r:?}");
641                assert!(r.is_ok());
642                assert_eq!(r.unwrap(), "{\"empty\":\"\"}");
643            }
644        }
645    }
646}