use crate::arrangements::{ArrangeRow, ArrangementBlock};
use itertools::Itertools;
use serde::ser::{Error as SerializeErr, SerializeMap, SerializeStruct, Serializer};
use serde::Serialize;
impl Serialize for ArrangementBlock {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let first_empty_row = self
.rows
.iter()
.find_position(|x| **x == ArrangeRow::EmptyRow());
if first_empty_row.is_none() && self.n_rows < 255_u8 {
return Err(S::Error::custom(format![
"No Empty Rows, but n_rows is less than 255: nRows={:?}",
self.n_rows,
]));
}
let first_empty_row = first_empty_row.unwrap_or((0, &ArrangeRow::EmptyRow())).0;
if first_empty_row != self.n_rows as usize {
return Err(S::Error::custom(format![
"Index of first Empty Row does not match value for n_rows: idx={:?} nRows={:?}",
first_empty_row, self.n_rows,
]));
}
let mut state = serializer.serialize_struct("ArrangementBlock", 4)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("unknown_1", &self.unknown_1)?;
state.serialize_field("n_rows", &self.n_rows)?;
state.serialize_field("rows", &self.rows)?;
state.end()
}
}
impl Serialize for ArrangeRow {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ArrangeRow::PatternRow {
pattern_id,
repetitions,
mute_mask,
tempo_1,
tempo_2,
scene_a,
scene_b,
offset,
length,
midi_transpose,
} => {
if repetitions > &63_u8 {
return Err(S::Error::custom(
"ArrangeRow::PatternRow: Repetitions cannot exceed 63 (64x)",
));
}
if *scene_a != 255_u8 && scene_a > &15_u8 {
return Err(S::Error::custom("ArrangeRow::PatternRow: Scene A index cannot be greater than 15 (zero index; 16 length)"));
}
if *scene_b != 255_u8 && scene_b > &15_u8 {
return Err(S::Error::custom("ArrangeRow::PatternRow: Scene B index cannot be greater than 15 (zero index; 16 length)"));
}
if serializer.is_human_readable() {
let mut state = serializer.serialize_struct("PatternRow", 10)?;
state.serialize_field("pattern_id", pattern_id)?;
state.serialize_field("repetitions", repetitions)?;
state.serialize_field("mute_mask", mute_mask)?;
state.serialize_field("tempo_1", tempo_1)?;
state.serialize_field("tempo_2", tempo_2)?;
state.serialize_field("scene_a", scene_a)?;
state.serialize_field("scene_b", scene_b)?;
state.serialize_field("offset", offset)?;
state.serialize_field("length", length)?;
state.serialize_field("midi_transpose", midi_transpose)?;
state.end()
} else {
let mut state = serializer.serialize_struct("PatternRow", 22)?;
state.serialize_field("row_type", &0_u8)?;
state.serialize_field("pattern_id", pattern_id)?;
state.serialize_field("repetitions", repetitions)?;
state.serialize_field("unused_1", &0_u8)?;
state.serialize_field("mute_mask", mute_mask)?;
state.serialize_field("unused_2", &0_u8)?;
state.serialize_field("tempo_1", tempo_1)?;
state.serialize_field("tempo_2", tempo_2)?;
state.serialize_field("scene_a", scene_a)?;
state.serialize_field("scene_b", scene_b)?;
state.serialize_field("unused_3", &0_u8)?;
state.serialize_field("offset", offset)?;
state.serialize_field("unused_4", &0_u8)?;
state.serialize_field("length", length)?;
state.serialize_field("midi_transpose", midi_transpose)?;
state.end()
}
}
ArrangeRow::LoopOrJumpOrHaltRow {
loop_count,
row_target,
} => {
if loop_count > &100_u8 {
return Err(S::Error::custom(
"ArrangeRow::LoopOrJumpOrHaltRow: Loop count cannot exceed 100 (99x)",
));
}
if serializer.is_human_readable() {
let mut state = serializer.serialize_struct("LoopOrJumpOrHaltRow", 2)?;
state.serialize_field("loop_count", loop_count)?;
state.serialize_field("row_target", row_target)?;
state.end()
} else {
let mut state = serializer.serialize_struct("LoopOrJumpOrHaltRow", 22)?;
state.serialize_field("row_type", &1_u8)?;
state.serialize_field("loop_count", loop_count)?;
state.serialize_field("row_target", row_target)?;
state.serialize_field("unused_1", &0_u8)?;
state.serialize_field("unused_2", &0_u8)?;
state.serialize_field("unused_3", &0_u8)?;
state.serialize_field("unused_4", &0_u8)?;
state.serialize_field("unused_5", &0_u8)?;
state.serialize_field("unused_6", &0_u8)?;
state.serialize_field("unused_7", &0_u8)?;
state.serialize_field("unused_8", &0_u8)?;
state.serialize_field("unused_9", &0_u8)?;
state.serialize_field("unused_10", &0_u8)?;
state.serialize_field("unused_11", &0_u8)?;
state.serialize_field("unused_12", &0_u8)?;
state.serialize_field("unused_13", &0_u8)?;
state.serialize_field("unused_14", &0_u8)?;
state.serialize_field("unused_15", &0_u8)?;
state.serialize_field("unused_16", &0_u8)?;
state.serialize_field("unused_17", &0_u8)?;
state.serialize_field("unused_18", &0_u8)?;
state.serialize_field("unused_19", &0_u8)?;
state.end()
}
}
ArrangeRow::ReminderRow(x) => {
if x.len() > 15 {
return Err(S::Error::custom(format![
"ArrangeRow::ReminderRow: string length exceeds 15: str={x:?}",
]));
};
if serializer.is_human_readable() {
let mut state = serializer.serialize_map(Some(1))?;
state.serialize_entry("reminder", x)?;
state.end()
} else {
let mut state = serializer.serialize_struct("ReminderRow", 22)?;
state.serialize_field("row_type", &2_u8)?;
for c in x.as_bytes() {
state.serialize_field("char", &c)?;
}
for _ in x.len()..15 {
state.serialize_field("char", &0_u8)?;
}
state.serialize_field("unused_1", &0_u8)?;
state.serialize_field("unused_2", &0_u8)?;
state.serialize_field("unused_3", &0_u8)?;
state.serialize_field("unused_4", &0_u8)?;
state.serialize_field("unused_5", &0_u8)?;
state.serialize_field("unused_6", &0_u8)?;
state.end()
}
}
ArrangeRow::EmptyRow() => {
if serializer.is_human_readable() {
let mut state = serializer.serialize_map(Some(1))?;
state.serialize_entry("empty", "")?;
state.end()
} else {
let mut state = serializer.serialize_struct("EmptyRow", 0)?;
state.serialize_field("unused_1", &0_u8)?;
state.serialize_field("unused_2", &0_u8)?;
state.serialize_field("unused_3", &0_u8)?;
state.serialize_field("unused_4", &0_u8)?;
state.serialize_field("unused_5", &0_u8)?;
state.serialize_field("unused_6", &0_u8)?;
state.serialize_field("unused_7", &0_u8)?;
state.serialize_field("unused_8", &0_u8)?;
state.serialize_field("unused_9", &0_u8)?;
state.serialize_field("unused_10", &0_u8)?;
state.serialize_field("unused_11", &0_u8)?;
state.serialize_field("unused_12", &0_u8)?;
state.serialize_field("unused_13", &0_u8)?;
state.serialize_field("unused_14", &0_u8)?;
state.serialize_field("unused_15", &0_u8)?;
state.serialize_field("unused_16", &0_u8)?;
state.serialize_field("unused_17", &0_u8)?;
state.serialize_field("unused_18", &0_u8)?;
state.serialize_field("unused_19", &0_u8)?;
state.serialize_field("unused_20", &0_u8)?;
state.serialize_field("unused_21", &0_u8)?;
state.serialize_field("unused_22", &0_u8)?;
state.end()
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod arrangement_file {
use crate::arrangements::ArrangementFile;
use crate::test_utils::get_arrange_dirpath;
use crate::{OctatrackFileIO, OtToolsIoError};
#[test]
fn test_serialize_to_json() -> Result<(), OtToolsIoError> {
let path = get_arrange_dirpath().join("blank.work");
assert!(ArrangementFile::from_data_file(&path)?
.to_json_string()
.is_ok());
Ok(())
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_serialize_to_yaml() -> Result<(), OtToolsIoError> {
let valid_yaml_path = get_arrange_dirpath().join("blank.yaml");
let valid_yaml = crate::read_str_file(&valid_yaml_path);
let bin_file_path = get_arrange_dirpath().join("blank.work");
let yaml = ArrangementFile::from_data_file(&bin_file_path)?.to_yaml_string();
assert!(yaml.is_ok());
assert_eq!(valid_yaml?, yaml?);
Ok(())
}
}
mod arrangement_block {
use crate::generics::ArrangeRows;
#[test]
fn test_ok() {
let expected_rows: [super::ArrangeRow; 256] = std::array::from_fn(|i| {
if i < 10 {
super::ArrangeRow::PatternRow {
pattern_id: 1,
repetitions: 1,
mute_mask: 1,
tempo_1: 1,
tempo_2: 1,
scene_a: 1,
scene_b: 1,
offset: 1,
length: 1,
midi_transpose: [8, 1, 1, 1, 1, 1, 1, 8],
}
} else {
super::ArrangeRow::EmptyRow()
}
});
let rows = ArrangeRows::new(expected_rows);
let expected = super::ArrangementBlock {
name: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10],
unknown_1: [10, 9],
n_rows: 10,
rows,
};
let _: [u8; 5652] = std::array::from_fn(|x| {
match x {
0 => 10,
13 => 10,
14 => 10,
15 => 9,
16 => 10,
5650 => 10,
5651 => 9,
_ => 0,
}
});
let r = bincode::serialize(&expected);
println!("{r:?}");
assert!(r.is_ok());
let v = r.unwrap();
assert_eq!(5650, v.len());
}
}
mod arrangement_row {
use super::*;
mod pattern_row {
#[test]
fn valid() {
let x = super::ArrangeRow::PatternRow {
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],
};
let r = bincode::serialize(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn valid_yaml() {
let x = super::ArrangeRow::PatternRow {
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],
};
let r = serde_yml::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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");
}
#[test]
fn valid_json() {
let x = super::ArrangeRow::PatternRow {
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],
};
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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]}");
}
#[test]
fn invalid_repetitions() {
let x = super::ArrangeRow::PatternRow {
pattern_id: 0,
repetitions: 64,
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],
};
let r = bincode::serialize(&x);
assert!(r.is_err());
}
#[test]
fn valid_scene_a_off() {
let x = super::ArrangeRow::PatternRow {
pattern_id: 0,
repetitions: 0,
mute_mask: 0,
tempo_1: 0,
tempo_2: 0,
scene_a: 255,
scene_b: 0,
offset: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
let r = bincode::serialize(&x);
println!("{r:?}");
assert!(r.is_ok());
}
#[test]
fn valid_scene_b_off() {
let x = super::ArrangeRow::PatternRow {
pattern_id: 0,
repetitions: 0,
mute_mask: 0,
tempo_1: 0,
tempo_2: 0,
scene_a: 0,
scene_b: 255,
offset: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
let r = bincode::serialize(&x);
println!("{r:?}");
assert!(r.is_ok());
}
#[test]
fn invalid_scene_a() {
let x = super::ArrangeRow::PatternRow {
pattern_id: 0,
repetitions: 0,
mute_mask: 0,
tempo_1: 0,
tempo_2: 0,
scene_a: 16,
scene_b: 0,
offset: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
let r = bincode::serialize(&x);
println!("{r:#?}");
assert!(r.is_err());
}
#[test]
fn invalid_scene_b() {
let x = super::ArrangeRow::PatternRow {
pattern_id: 0,
repetitions: 0,
mute_mask: 0,
tempo_1: 0,
tempo_2: 0,
scene_a: 16,
scene_b: 16,
offset: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
let r = bincode::serialize(&x);
assert!(r.is_err());
}
}
mod loop_or_jump_or_halt {
#[test]
fn valid() {
let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
};
let r = bincode::serialize(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn valid_yaml() {
let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
};
let r = serde_yml::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "loop_count: 1\nrow_target: 1\n");
}
#[test]
fn valid_json() {
let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
};
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "{\"loop_count\":1,\"row_target\":1}");
}
#[test]
fn invalid_loop_count() {
let x = super::ArrangeRow::LoopOrJumpOrHaltRow {
loop_count: 101,
row_target: 1,
};
let r = bincode::serialize(&x);
assert!(r.is_err());
}
}
mod reminder_row {
#[test]
fn valid_string() {
let x = super::ArrangeRow::ReminderRow(String::from("HELLO WORLD"));
let r = bincode::serialize(&x);
println!("{r:?}");
assert!(r.is_ok());
println!("{r:?}");
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn valid_string_yaml() {
let x = super::ArrangeRow::ReminderRow(String::from("HELLO WORLD"));
let r = serde_yml::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "reminder: HELLO WORLD\n");
}
#[test]
fn valid_string_json() {
let x = super::ArrangeRow::ReminderRow(String::from("HELLO WORLD"));
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "{\"reminder\":\"HELLO WORLD\"}");
}
#[test]
fn empty_string() {
let x = super::ArrangeRow::ReminderRow(String::new());
let r = bincode::serialize(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn empty_string_yaml() {
let x = super::ArrangeRow::ReminderRow(String::new());
let r = serde_yml::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "reminder: ''\n");
}
#[test]
fn empty_string_json() {
let x = super::ArrangeRow::ReminderRow(String::new());
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "{\"reminder\":\"\"}");
}
#[test]
fn invalid() {
let x = super::ArrangeRow::ReminderRow(String::from("1111111111111111"));
let r = bincode::serialize(&x);
assert!(r.is_err());
}
}
mod empty_row {
#[test]
fn valid() {
let x = super::ArrangeRow::EmptyRow();
let r = bincode::serialize(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn valid_yaml() {
let x = super::ArrangeRow::EmptyRow();
let r = serde_yml::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "empty: ''\n");
}
#[test]
fn valid_json() {
let x = super::ArrangeRow::EmptyRow();
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap(), "{\"empty\":\"\"}");
}
}
}
}