use super::{is_not_valid_ascii_byte, ArrangementError};
use crate::{Defaults, OtToolsIoError};
use ot_tools_io_derive::IsDefaultCheck;
use ot_tools_io_derive::{ArrayDefaults, AsMutDerive, AsRefDerive, BoxedArrayDefaults};
use serde::{Deserialize, Serialize};
use std::array::from_fn;
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Deserialize,
Serialize,
ArrayDefaults,
BoxedArrayDefaults,
AsRefDerive,
AsMutDerive,
IsDefaultCheck,
)]
#[serde(rename_all = "snake_case")]
pub enum ArrangeRow {
PatternRow(PatternRow),
LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow),
ReminderRow(ReminderRow),
EmptyRow([u8; 22]),
}
impl Default for ArrangeRow {
fn default() -> Self {
Self::EmptyRow(from_fn(|_| 0))
}
}
impl ArrangeRow {
pub fn validate(&self) -> Result<(), OtToolsIoError> {
match self {
Self::EmptyRow(_) => Ok(()),
Self::LoopOrJumpOrHaltRow(x) => x.validate(),
Self::PatternRow(x) => x.validate(),
Self::ReminderRow(x) => x.validate(),
}
}
pub(super) fn parse_to_bytes(&self) -> Result<Vec<u8>, OtToolsIoError> {
let mut v = vec![];
match self {
Self::EmptyRow(x) => v.append(&mut x.to_vec()),
Self::PatternRow(pattern_row) => {
pattern_row.validate()?;
let PatternRow {
pattern_id,
repetitions,
unused_1,
mute_mask,
unused_2,
tempo_1,
tempo_2,
scene_a,
scene_b,
unused_3,
offset,
unused_4,
length,
midi_transpose,
} = *pattern_row;
v.append(&mut vec![0]); v.append(&mut vec![pattern_id]);
v.append(&mut vec![repetitions]);
v.append(&mut vec![unused_1]);
v.append(&mut vec![mute_mask]);
v.append(&mut vec![unused_2]);
v.append(&mut vec![tempo_1]);
v.append(&mut vec![tempo_2]);
v.append(&mut vec![scene_a]);
v.append(&mut vec![scene_b]);
v.append(&mut vec![unused_3]);
v.append(&mut vec![offset]);
v.append(&mut vec![unused_4]);
v.append(&mut vec![length]);
v.append(&mut midi_transpose.to_vec());
}
Self::LoopOrJumpOrHaltRow(loop_row) => {
loop_row.validate()?;
let LoopOrJumpOrHaltRow {
loop_count,
row_target,
unused,
} = *loop_row;
v.append(&mut vec![1]); v.append(&mut vec![loop_count]);
v.append(&mut vec![row_target]);
v.append(&mut unused.to_vec());
}
Self::ReminderRow(reminder_row) => {
reminder_row.validate()?;
let ReminderRow { reminder, unused } = *reminder_row;
v.append(&mut vec![2]); v.append(&mut reminder.to_vec());
v.append(&mut unused.to_vec());
}
}
Ok(v)
}
pub(super) fn parse_from_bytes(bytes: &[u8; 22]) -> Result<Self, OtToolsIoError> {
let (row_type, row_data) = bytes.split_first().unwrap();
match row_type {
0 => {
let pattern_id = row_data[0];
let repetitions = row_data[1];
let unused_1 = row_data[2];
let mute_mask = row_data[3];
let unused_2 = row_data[4];
let tempo_1 = row_data[5];
let tempo_2 = row_data[6];
let scene_a = row_data[7];
let scene_b = row_data[8];
let unused_3 = row_data[9];
let offset = row_data[10];
let unused_4 = row_data[11];
let length = row_data[12];
let midi_transpose: [u8; 8] = from_fn(|x| row_data[x + 13]);
let pattern = PatternRow {
pattern_id,
repetitions,
mute_mask,
tempo_1,
tempo_2,
scene_a,
scene_b,
offset,
length,
midi_transpose,
unused_1,
unused_2,
unused_3,
unused_4,
};
pattern.validate()?;
Ok(ArrangeRow::PatternRow(pattern))
}
1 => {
let loop_count = row_data[0];
let row_target = row_data[1];
let unused: [u8; 19] = from_fn(|x| row_data[x + 2]);
let loop_row = LoopOrJumpOrHaltRow {
loop_count,
row_target,
unused,
};
loop_row.validate()?;
Ok(ArrangeRow::LoopOrJumpOrHaltRow(loop_row))
}
2 => {
let reminder = from_fn(|x| row_data[x]);
let unused: [u8; 6] = from_fn(|x| row_data[x + 15]);
let reminder_row = ReminderRow { reminder, unused };
reminder_row.validate()?;
Ok(ArrangeRow::ReminderRow(reminder_row))
}
_ => Err(ArrangementError::RowType(*row_type).into()),
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsRefDerive,
AsMutDerive,
)]
pub struct PatternRow {
pub pattern_id: u8,
pub repetitions: u8,
pub unused_1: u8,
pub mute_mask: u8,
pub unused_2: u8,
pub tempo_1: u8,
pub tempo_2: u8,
pub scene_a: u8,
pub scene_b: u8,
pub unused_3: u8,
pub offset: u8,
pub unused_4: u8,
pub length: u8,
pub midi_transpose: [u8; 8],
}
impl PatternRow {
pub fn validate(&self) -> Result<(), OtToolsIoError> {
if self.repetitions > 63 {
Err(ArrangementError::RepetitionCount(self.repetitions).into())
} else if self.scene_a > 15 && self.scene_a != 255 {
Err(ArrangementError::Scene(self.scene_a).into())
} else if self.scene_b > 15 && self.scene_b != 255 {
Err(ArrangementError::Scene(self.scene_b).into())
} else if self.midi_transpose.iter().any(|x| (49..208).contains(x)) {
Err(ArrangementError::MidiTranspose(self.midi_transpose).into())
} else {
Ok(())
}
}
}
#[cfg(test)]
mod pattern_row {
mod validate {
use crate::arrangements::{ArrangementError, PatternRow};
use crate::OtToolsIoError;
#[test]
fn ok_maxish_values() {
let x = PatternRow {
pattern_id: 255,
repetitions: 63,
unused_1: 0,
mute_mask: 0, unused_2: 0,
tempo_1: 0, tempo_2: 0, scene_a: 15,
scene_b: 15,
unused_3: 0,
offset: 0, unused_4: 0,
length: 0, midi_transpose: [255, 255, 255, 255, 255, 255, 255, 255],
};
assert!(x.validate().is_ok());
}
#[test]
fn invalid_repetitions() {
let x = PatternRow {
pattern_id: 0,
repetitions: 64,
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],
};
assert!(x.validate().is_err());
assert_eq!(
x.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::RepetitionCount(64)).to_string(),
);
}
#[test]
fn valid_scene_a_off() {
let x = PatternRow {
pattern_id: 0,
repetitions: 0,
unused_1: 0,
mute_mask: 0,
unused_2: 0,
tempo_1: 0,
tempo_2: 0,
scene_a: 255,
scene_b: 0,
unused_3: 0,
offset: 0,
unused_4: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_ok());
}
#[test]
fn valid_scene_b_off() {
let x = PatternRow {
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: 255,
unused_3: 0,
offset: 0,
unused_4: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_ok());
}
#[test]
fn valid_scene_a_max() {
let x = PatternRow {
pattern_id: 0,
repetitions: 0,
unused_1: 0,
mute_mask: 0,
unused_2: 0,
tempo_1: 0,
tempo_2: 0,
scene_a: 15,
scene_b: 0,
unused_3: 0,
offset: 0,
unused_4: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_ok());
}
#[test]
fn valid_scene_b_max() {
let x = PatternRow {
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: 15,
unused_3: 0,
offset: 0,
unused_4: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_ok());
}
#[test]
fn invalid_scene_a() {
let x = PatternRow {
pattern_id: 0,
repetitions: 0,
unused_1: 0,
mute_mask: 0,
unused_2: 0,
tempo_1: 0,
tempo_2: 0,
scene_a: 16,
scene_b: 0,
unused_3: 0,
offset: 0,
unused_4: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_err());
assert_eq!(
x.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::Scene(16)).to_string(),
);
}
#[test]
fn invalid_scene_b() {
let x = PatternRow {
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: 16,
unused_3: 0,
offset: 0,
unused_4: 0,
length: 0,
midi_transpose: [0, 0, 0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_err());
assert_eq!(
x.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::Scene(16)).to_string(),
);
}
#[test]
fn invalid_midi_transpose_lower() {
let x = PatternRow {
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: [49, 49, 49, 49, 49, 49, 49, 49],
};
assert!(x.validate().is_err());
assert_eq!(
x.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::MidiTranspose([
49, 49, 49, 49, 49, 49, 49, 49
]))
.to_string(),
);
}
#[test]
fn invalid_midi_transpose_upper() {
let x = PatternRow {
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: [207, 207, 207, 207, 207, 207, 207, 207],
};
assert!(x.validate().is_err());
assert_eq!(
x.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::MidiTranspose([
207, 207, 207, 207, 207, 207, 207, 207
]))
.to_string(),
);
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsRefDerive,
AsMutDerive,
)]
pub struct LoopOrJumpOrHaltRow {
pub loop_count: u8,
pub row_target: u8,
pub unused: [u8; 19],
}
impl LoopOrJumpOrHaltRow {
pub fn new(loop_count: u8, row_target: u8, unused_bytes: [u8; 19]) -> Self {
Self {
loop_count,
row_target,
unused: unused_bytes,
}
}
pub fn new_without_unused(loop_count: u8, row_target: u8) -> Self {
Self::new(loop_count, row_target, Default::default())
}
pub fn validate(&self) -> Result<(), OtToolsIoError> {
if self.loop_count > 100 {
Err(ArrangementError::LoopCount(self.loop_count).into())
} else {
Ok(())
}
}
}
#[cfg(test)]
mod loop_or_jump_or_halt_row {
mod new {
use crate::arrangements::LoopOrJumpOrHaltRow;
#[test]
fn valid() {
let x = LoopOrJumpOrHaltRow::new(1, 1, Default::default());
assert_eq!(x.loop_count, 1);
assert_eq!(x.row_target, 1);
assert_eq!(x.unused, std::array::from_fn(|_| 0));
}
}
mod new_without_unused {
use crate::arrangements::LoopOrJumpOrHaltRow;
#[test]
fn valid() {
let x = LoopOrJumpOrHaltRow::new_without_unused(1, 1);
assert_eq!(x.loop_count, 1);
assert_eq!(x.row_target, 1);
assert_eq!(x.unused, std::array::from_fn(|_| 0));
}
}
mod validate {
use crate::arrangements::{ArrangementError, LoopOrJumpOrHaltRow};
use crate::OtToolsIoError;
#[test]
fn valid() {
let x = LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
unused: std::array::from_fn(|_| 0),
};
let r = x.validate();
assert!(r.is_ok());
}
#[test]
fn invalid_loop_count() {
let x = LoopOrJumpOrHaltRow {
loop_count: 101,
row_target: 1,
unused: std::array::from_fn(|_| 0),
};
assert!(x.validate().is_err());
assert_eq!(
x.validate().err().unwrap().to_string(),
OtToolsIoError::Arrangement(ArrangementError::LoopCount(101)).to_string(),
);
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
AsRefDerive,
AsMutDerive,
)]
pub struct ReminderRow {
pub reminder: [u8; 15],
pub unused: [u8; 6],
}
impl TryFrom<String> for ReminderRow {
type Error = OtToolsIoError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let string = value.clone();
let bytes = string
.as_bytes()
.as_array::<15>()
.ok_or(ArrangementError::ReminderStrLen(value))?;
if bytes.iter().any(|x| is_not_valid_ascii_byte(*x)) {
Err(ArrangementError::ReminderAscii(*bytes).into())
} else {
Ok(Self {
reminder: *bytes,
unused: Default::default(),
})
}
}
}
impl std::fmt::Display for ReminderRow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", super::bytes_to_ascii_string(&self.reminder))
}
}
impl ReminderRow {
pub fn new(reminder: [u8; 15], unused: [u8; 6]) -> Self {
Self { reminder, unused }
}
pub fn new_without_unused(reminder: [u8; 15]) -> Self {
Self::new(reminder, Default::default())
}
pub fn new_from_string<S: AsRef<str>>(reminder: S) -> Result<Self, OtToolsIoError> {
Self::try_from(reminder.as_ref().to_string())
}
pub fn validate(&self) -> Result<(), OtToolsIoError> {
Ok(())
}
}
#[cfg(test)]
mod reminder_row {
const HELLO_WORLD_BYTES: [u8; 15] =
[72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 32, 32, 32, 32];
const ONLY_SPACES_BYTES: [u8; 15] =
[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32];
const INVALID_BYTES: [u8; 15] = [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9];
mod new_without_unused {
use crate::arrangements::ReminderRow;
#[test]
fn ok_hello_world() {
let rem_row = ReminderRow::new_without_unused(super::HELLO_WORLD_BYTES);
assert_eq!(super::HELLO_WORLD_BYTES, rem_row.reminder);
assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
}
#[test]
fn ok_only_spaces() {
let rem_row = ReminderRow::new_without_unused(super::ONLY_SPACES_BYTES);
assert_eq!(super::ONLY_SPACES_BYTES, rem_row.reminder);
assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
}
#[test]
fn ok_invalid() {
let rem_row = ReminderRow::new_without_unused(super::INVALID_BYTES);
assert_eq!(super::INVALID_BYTES, rem_row.reminder);
assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
}
}
mod new_from_string {
use crate::arrangements::ReminderRow;
use crate::OtToolsIoError;
#[test]
fn ok_hello_world() -> Result<(), OtToolsIoError> {
let rem = String::from("HELLO WORLD ");
let rem_row = ReminderRow::new_from_string(rem)?;
assert_eq!(super::HELLO_WORLD_BYTES, rem_row.reminder);
assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
Ok(())
}
#[test]
fn ok_only_spaces() -> Result<(), OtToolsIoError> {
let rem = String::from(" ");
let rem_row = ReminderRow::new_from_string(rem)?;
assert_eq!(super::ONLY_SPACES_BYTES, rem_row.reminder);
assert_eq!(std::array::from_fn(|_| 0), rem_row.unused);
Ok(())
}
}
mod validate {
use crate::arrangements::ReminderRow;
#[test]
fn valid_string() {
let x = ReminderRow {
reminder: super::HELLO_WORLD_BYTES,
unused: [0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_ok())
}
#[test]
fn empty_string() {
let x = ReminderRow {
reminder: super::ONLY_SPACES_BYTES,
unused: [0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_ok())
}
#[test]
fn invalid_ascii() {
let x = ReminderRow {
reminder: super::INVALID_BYTES,
unused: [0, 0, 0, 0, 0, 0],
};
assert!(x.validate().is_ok())
}
}
mod display {
use crate::arrangements::ReminderRow;
#[test]
fn hello_world() {
let x = ReminderRow {
reminder: super::HELLO_WORLD_BYTES,
unused: [0, 0, 0, 0, 0, 0],
};
assert_eq!(format!["{x}"], "HELLO WORLD ");
}
#[test]
fn empty_string() {
let x = ReminderRow {
reminder: super::ONLY_SPACES_BYTES,
unused: [0, 0, 0, 0, 0, 0],
};
assert_eq!(format!["{x}"], " ");
}
#[test]
fn invalid_ascii() {
let x = ReminderRow {
reminder: super::INVALID_BYTES,
unused: [0, 0, 0, 0, 0, 0],
};
assert_eq!(format!["{x}"], "");
}
}
}
#[cfg(test)]
mod parse_to_bytes {
mod pattern {
use crate::arrangements::{ArrangeRow, PatternRow};
#[test]
fn valid() {
let x = ArrangeRow::PatternRow(PatternRow {
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],
});
let r = x.parse_to_bytes();
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
}
mod loop_or_jump_or_halt {
use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
#[test]
fn valid() {
let x = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
unused: std::array::from_fn(|_| 0),
});
let r = x.parse_to_bytes();
assert!(r.is_ok());
}
}
mod reminder {
use crate::arrangements::{ArrangeRow, ReminderRow};
#[test]
fn valid_string() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = x.parse_to_bytes();
println!("{r:?}");
assert!(r.is_ok());
println!("{r:?}");
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn empty_string() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = x.parse_to_bytes();
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
}
mod empty_row {
use crate::arrangements::ArrangeRow;
#[test]
fn valid() {
let x = ArrangeRow::default();
let r = x.parse_to_bytes();
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
}
}
#[cfg(test)]
mod parse_from_bytes {
use crate::arrangements::ArrangeRow;
#[test]
fn invalid_row_type() {
let b = [
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert!(r.is_err());
}
mod empty_row {
use crate::arrangements::{ArrangeRow, PatternRow};
#[test]
fn row_index_matters() {
let not_expected = ArrangeRow::default();
let expected = ArrangeRow::PatternRow(PatternRow {
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],
});
let b = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
println!("{r:?}");
assert!(r.is_ok());
let x = r.unwrap();
assert_ne!(not_expected, x);
assert_eq!(expected, x);
}
}
mod reminder_row {
use crate::arrangements::{ArrangeRow, ReminderRow};
#[test]
fn valid() {
let expected = ArrangeRow::ReminderRow(ReminderRow {
reminder: [43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43],
unused: [0, 0, 0, 0, 0, 0],
});
let b = [
2, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert_eq!(expected, r.unwrap());
}
#[test]
fn valid_drop_excess_characters() {
let expected = ArrangeRow::ReminderRow(ReminderRow {
reminder: [43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43],
unused: [0, 0, 0, 0, 0, 0],
});
let b = [
2, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert_eq!(expected, r.unwrap());
}
}
mod loop_jump_or_halt_row {
use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
#[test]
fn valid() {
let expected = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
unused: std::array::from_fn(|_| 0),
});
let b = [
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert_eq!(expected, r.unwrap());
}
#[test]
fn invalid_loop_count() {
let b = [
1, 101, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert!(r.is_err());
}
}
mod pattern_row {
use crate::arrangements::{ArrangeRow, PatternRow};
#[test]
fn valid_pattern_id_only() {
let expected = ArrangeRow::PatternRow(PatternRow {
pattern_id: 1,
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],
});
let b = [
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert_eq!(expected, r.unwrap());
}
#[test]
fn valid_last_midi_transpose_only() {
let expected = ArrangeRow::PatternRow(PatternRow {
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, 8],
});
let b = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert_eq!(expected, r.unwrap());
}
#[test]
fn invalid_too_many_repetitions() {
let b = [
0, 1, 64, 1, 1, 1, 1, 1, 15, 15, 1, 1, 1, 64, 3, 3, 3, 3, 3, 3, 3, 3,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert!(r.is_err());
}
#[test]
fn valid_all() {
let expected = ArrangeRow::PatternRow(PatternRow {
pattern_id: 1,
repetitions: 10,
unused_1: 55,
mute_mask: 1,
unused_2: 55,
tempo_1: 1,
tempo_2: 1,
scene_a: 15,
scene_b: 15,
unused_3: 55,
offset: 1,
unused_4: 55,
length: 64,
midi_transpose: [3, 3, 3, 3, 3, 3, 3, 3],
});
let b = [
0, 1, 10, 55, 1, 55, 1, 1, 15, 15, 55, 1, 55, 64, 3, 3, 3, 3, 3, 3, 3, 3,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert_eq!(expected, r.unwrap());
}
#[test]
fn zero_valued_is_still_pattern_row() {
let expected = ArrangeRow::PatternRow(PatternRow {
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],
});
let b = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let r = ArrangeRow::parse_from_bytes(&b);
assert_eq!(expected, r.unwrap());
}
}
}
#[cfg(test)]
mod deserialize {
mod pattern {
}
mod empty {
use crate::arrangements::ArrangeRow;
#[test]
fn valid_yaml() {
let expected = ArrangeRow::default();
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";
let r = serde_norway::from_str::<ArrangeRow>(s);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(expected, r.unwrap());
}
#[test]
fn valid_json() {
let expected = ArrangeRow::default();
let s =
"{\"empty_row\":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}";
let r = serde_json::from_str::<ArrangeRow>(s);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(expected, r.unwrap());
}
}
mod loop_jump_or_halt {
use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
#[test]
fn valid_yaml() {
let expected = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
unused: std::array::from_fn(|_| 0),
});
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";
let r = serde_norway::from_str::<ArrangeRow>(s);
assert_eq!(expected, r.unwrap());
}
#[test]
fn valid_json() {
let expected = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
unused: std::array::from_fn(|_| 0),
});
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]}}";
let r = serde_json::from_str::<ArrangeRow>(s);
assert_eq!(expected, r.unwrap());
}
}
mod reminder {
use crate::arrangements::{ArrangeRow, ReminderRow};
#[test]
fn valid_json() {
let expected = ArrangeRow::ReminderRow(ReminderRow {
reminder: [67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67],
unused: [0, 0, 0, 0, 0, 0],
});
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]}}";
let r = serde_json::from_str::<ArrangeRow>(s);
assert_eq!(expected, r.unwrap());
}
#[test]
fn valid_yaml() {
let expected = ArrangeRow::ReminderRow(ReminderRow {
reminder: [67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67],
unused: [0, 0, 0, 0, 0, 0],
});
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";
let r = serde_norway::from_str::<ArrangeRow>(s);
assert_eq!(expected, r.unwrap());
}
}
}
#[cfg(test)]
mod arrange_row {
mod pattern {
use crate::arrangements::{ArrangeRow, PatternRow};
#[test]
fn valid_yaml() {
let x = ArrangeRow::PatternRow(PatternRow {
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],
});
let r = serde_norway::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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");
}
#[test]
fn valid_json() {
let x = ArrangeRow::PatternRow(PatternRow {
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],
});
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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]}}");
}
}
mod loop_or_jump_or_halt {
use crate::arrangements::{ArrangeRow, LoopOrJumpOrHaltRow};
use std::array::from_fn;
#[test]
fn valid_yaml() {
let x = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
unused: from_fn(|_| 0),
});
let r = serde_norway::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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");
}
#[test]
fn valid_json() {
let x = ArrangeRow::LoopOrJumpOrHaltRow(LoopOrJumpOrHaltRow {
loop_count: 1,
row_target: 1,
unused: from_fn(|_| 0),
});
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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]}}");
}
}
mod reminder_row {
use crate::arrangements::{ArrangeRow, ReminderRow};
#[test]
fn valid_string() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = x.parse_to_bytes();
println!("{r:?}");
assert!(r.is_ok());
println!("{r:?}");
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn valid_string_yaml() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = serde_norway::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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");
}
#[test]
fn valid_string_json() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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]}}");
}
#[test]
fn empty_string() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = x.parse_to_bytes();
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(r.unwrap().len(), 22);
}
#[test]
fn empty_string_yaml() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = serde_norway::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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");
}
#[test]
fn empty_string_json() {
let x = ArrangeRow::ReminderRow(ReminderRow {
reminder: [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
unused: [0, 0, 0, 0, 0, 0],
});
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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]}}");
}
}
mod empty_row {
use crate::arrangements::ArrangeRow;
#[test]
fn valid_yaml() {
let x = ArrangeRow::default();
let r = serde_norway::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
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");
}
#[test]
fn valid_json() {
let x = ArrangeRow::default();
let r = serde_json::to_string(&x);
println!("{r:?}");
assert!(r.is_ok());
assert_eq!(
r.unwrap(),
"{\"empty_row\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}"
);
}
}
}