mod file;
mod row;
mod state;
#[derive(Debug, thiserror::Error)]
pub enum ArrangementError {
#[error("invalid arrangement anme, must be valid ascii characters: {0:?}")]
Name([u8; 15]),
#[error("row type must be 0 (Pattern) / 1 (LoopOrHaltOrJump) / 2 (Reminder): {0:?}")]
RowType(u8),
#[error("scene value must be <= 15 or 255: {0:?}")]
Scene(u8),
#[error("all midi transpose values must be < 48 || > 207: {0:?}")]
MidiTranspose([u8; 8]),
#[error("number of bytes should be 11,336: {0:?}")]
ByteLength(usize),
#[error("number of rows doesn't match: n_rows={n_rows:?} first_empty_idx={first_empty_idx:?}")]
RowCount { n_rows: u8, first_empty_idx: u8 },
#[error("saved states must be boolean values: {0:?}")]
SavedStates([u8; 8]),
#[error("save state must be boolean value: {0:?}")]
SaveState(u8),
#[error("loop count cannot exceed 100 (99x): {0:?}")]
LoopCount(u8),
#[error("repetitions cannot exceed 63 (64x): {0:?}")]
RepetitionCount(u8),
#[error("reminder string must have 15 character length: {0:?}")]
ReminderStrLen(String),
#[error("reminder bytes contains non-ascii values: {0:?}")]
ReminderAscii([u8; 15]),
}
pub use file::ArrangementFile;
use itertools::Itertools;
pub use row::{ArrangeRow, LoopOrJumpOrHaltRow, PatternRow, ReminderRow};
pub use state::ArrangementState;
fn is_not_valid_ascii_byte(x: u8) -> bool {
!(32..=126).contains(&x)
}
fn bytes_to_ascii_string(bytes: &[u8]) -> String {
let mut str_data = bytes.to_vec();
let first_invalid = str_data
.iter()
.find_position(|x| is_not_valid_ascii_byte(**x));
if let Some((x, _)) = first_invalid {
str_data = str_data[..x].to_vec();
};
let s = std::str::from_utf8(&str_data).unwrap().to_ascii_uppercase();
s
}
pub const ARRANGEMENT_FILE_HEADER: [u8; 21] = [
70, 79, 82, 77, 0, 0, 0, 0, 68, 80, 83, 49, 65, 82, 82, 65, 0, 0, 0, 0, 0,
];
pub const ARRANGEMENT_FILE_VERSION: u8 = 6;
const ARRANGEMENT_DEFAULT_NAME: [u8; 15] =
[79, 67, 84, 65, 84, 79, 79, 76, 83, 45, 65, 82, 82, 32, 32];
#[cfg(test)]
mod mocks {
use super::{
ArrangementFile, ArrangementState, ARRANGEMENT_FILE_HEADER, ARRANGEMENT_FILE_VERSION,
};
use crate::generics::ArrangeRows;
use crate::HasChecksumField;
pub(super) fn mock_arrangement_file_empty_rows() -> (ArrangementFile, [u8; 11336]) {
let (block_type, block_bytes) = mock_arrangement_block_empty_rows();
let mut arr = ArrangementFile {
working_state: block_type,
stored_state: block_type,
saved_state: 1,
other_saved_states: [1, 1, 1, 1, 1, 1, 1, 1],
..Default::default()
};
arr.update_checksum().unwrap();
let mut bytes = vec![];
bytes.append(&mut ARRANGEMENT_FILE_HEADER.to_vec());
bytes.append(&mut vec![ARRANGEMENT_FILE_VERSION]);
bytes.append(&mut [0, 0].to_vec());
bytes.append(&mut block_bytes.to_vec());
bytes.append(&mut [0, 1].to_vec());
bytes.append(&mut block_bytes.to_vec());
bytes.append(&mut [1, 1, 1, 1, 1, 1, 1, 1].to_vec());
bytes.append(&mut arr.checksum.to_be_bytes().to_vec());
(arr, *bytes.as_array::<11336>().unwrap())
}
pub(super) fn mock_arrangement_block_empty_rows() -> (ArrangementState, [u8; 5650]) {
let expected = ArrangementState {
name: [48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48],
unknown_1: [10, 9],
n_rows: 0,
rows: ArrangeRows::default(),
};
let bytes: [u8; 5650] = std::array::from_fn(|x| {
if x <= 14 {
48
} else {
match x {
15 => 10,
16 => 9,
17 => 0,
_ => 0,
}
}
});
(expected, bytes)
}
}