#![allow(unused_parens)]
use binrw::io::{Read, Seek, Write};
use binrw::{binrw, BinRead, BinResult, BinWrite, Endian, NullWideString};
use modular_bitfield::prelude::*;
use super::xor::XorStream;
use super::AnlzString;
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub enum AnlzTag {
#[brw(magic = b"PMAI")]
File,
#[brw(magic = b"PQTZ")]
BeatGrid,
#[brw(magic = b"PQT2")]
ExtendedBeatGrid,
#[brw(magic = b"PCOB")]
CueList,
#[brw(magic = b"PCO2")]
ExtendedCueList,
#[brw(magic = b"PCP2")]
ExtendedCue,
#[brw(magic = b"PCPT")]
Cue,
#[brw(magic = b"PPTH")]
Path,
#[brw(magic = b"PVBR")]
VBR,
#[brw(magic = b"PVB2")]
SeekIndex,
#[brw(magic = b"PWAV")]
WaveformPreview,
#[brw(magic = b"PWV2")]
TinyWaveformPreview,
#[brw(magic = b"PWV3")]
WaveformDetail,
#[brw(magic = b"PWV4")]
WaveformColorPreview,
#[brw(magic = b"PWV5")]
WaveformColorDetail,
#[brw(magic = b"PWV6")]
Waveform3BandPreview,
#[brw(magic = b"PWV7")]
Waveform3BandDetail,
#[brw(magic = b"PWVC")]
Waveform3BandCalibration,
#[brw(magic = b"PSSI")]
SongStructure,
#[brw(magic = b"PVDI")]
VocalDetection,
Unknown([u8; 4]),
}
impl AnlzTag {
pub fn to_string(&self) -> String {
match self {
AnlzTag::File => "File".to_string(),
AnlzTag::BeatGrid => "BeatGrid".to_string(),
AnlzTag::ExtendedBeatGrid => "ExtendedBeatGrid".to_string(),
AnlzTag::CueList => "CueList".to_string(),
AnlzTag::ExtendedCueList => "ExtendedCueList".to_string(),
AnlzTag::ExtendedCue => "ExtendedCue".to_string(),
AnlzTag::Cue => "Cue".to_string(),
AnlzTag::Path => "Path".to_string(),
AnlzTag::VBR => "VBR".to_string(),
AnlzTag::SeekIndex => "SeekIndex".to_string(),
AnlzTag::WaveformPreview => "WaveformPreview".to_string(),
AnlzTag::TinyWaveformPreview => "TinyWaveformPreview".to_string(),
AnlzTag::WaveformDetail => "WaveformDetail".to_string(),
AnlzTag::WaveformColorPreview => "WaveformColorPreview".to_string(),
AnlzTag::WaveformColorDetail => "WaveformColorDetail".to_string(),
AnlzTag::Waveform3BandPreview => "Waveform3BandPreview".to_string(),
AnlzTag::Waveform3BandDetail => "Waveform3BandDetail".to_string(),
AnlzTag::Waveform3BandCalibration => "Waveform3BandCalibration".to_string(),
AnlzTag::SongStructure => "SongStructure".to_string(),
AnlzTag::VocalDetection => "VocalDetection".to_string(),
_ => format!("Unknown({:?})", self),
}
}
}
impl From<String> for AnlzTag {
fn from(tag: String) -> Self {
match tag.as_str() {
"File" => AnlzTag::File,
"BeatGrid" => AnlzTag::BeatGrid,
"ExtendedBeatGrid" => AnlzTag::ExtendedBeatGrid,
"CueList" => AnlzTag::CueList,
"ExtendedCueList" => AnlzTag::ExtendedCueList,
"ExtendedCue" => AnlzTag::ExtendedCue,
"Cue" => AnlzTag::Cue,
"Path" => AnlzTag::Path,
"VBR" => AnlzTag::VBR,
"SeekIndex" => AnlzTag::SeekIndex,
"WaveformPreview" => AnlzTag::WaveformPreview,
"TinyWaveformPreview" => AnlzTag::TinyWaveformPreview,
"WaveformDetail" => AnlzTag::WaveformDetail,
"WaveformColorPreview" => AnlzTag::WaveformColorPreview,
"WaveformColorDetail" => AnlzTag::WaveformColorDetail,
"Waveform3BandPreview" => AnlzTag::Waveform3BandPreview,
"Waveform3BandDetail" => AnlzTag::Waveform3BandDetail,
"Waveform3BandCalibration" => AnlzTag::Waveform3BandCalibration,
"SongStructure" => AnlzTag::SongStructure,
"VocalDetection" => AnlzTag::VocalDetection,
_ => panic!("Unknown tag type: {}", tag),
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct Header {
pub tag: AnlzTag,
pub size: u32,
pub total_size: u32,
}
impl Header {
fn remaining_size(&self) -> u32 {
self.size - 12
}
fn content_size(&self) -> u32 {
self.total_size - self.size
}
}
trait SizedSection {
fn size(&self) -> u32;
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct Beat {
pub beat_number: u16,
pub tempo: u16,
pub time: u32,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BeatGrid {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u32,
pub header_flags: u32,
#[br(temp)]
#[bw(calc = beats.len() as u32)]
n_beats: u32,
#[br(count = n_beats)]
pub beats: Vec<Beat>,
}
impl BeatGrid {
pub fn grid_offset(&self) -> i16 {
self.header_flags as u16 as i16
}
pub fn set_grid_offset(&mut self, offset_ms: i16) {
self.header_flags = (self.header_flags & 0xffff0000) | u32::from(offset_ms as u16);
}
}
impl SizedSection for BeatGrid {
fn size(&self) -> u32 {
24 + self.beats.len() as u32 * 8
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct ExtBeat {
pub beat_number: u8,
unknown: u8,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ExtendedBeatGrid {
#[br(temp)]
#[br(assert(header_size == 56))]
#[bw(calc = 56)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(pad1 == 0))]
#[bw(calc = 0)]
pad1: u32,
pub version: u32,
#[br(temp)]
#[br(assert(pad2 == 0))]
#[bw(calc = 0)]
pad2: u32,
#[br(count = 2)]
#[bw(assert(bpm.len() == 2))]
pub bpm: Vec<Beat>,
#[br(temp)]
#[bw(calc = beats.len() as u32)]
n_beats: u32,
u2: u32,
#[br(temp)]
#[br(assert(u3 == 0))]
#[bw(calc = 0)]
u3: u32,
#[br(temp)]
#[br(assert(u4 == 0))]
#[bw(calc = 0)]
u4: u32,
#[br(count = n_beats)]
pub beats: Vec<ExtBeat>,
}
impl SizedSection for ExtendedBeatGrid {
fn size(&self) -> u32 {
56 + self.beats.len() as u32 * 2
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(repr = u8)]
pub enum CueType {
Point = 1,
Loop = 2,
}
impl Into<u16> for CueType {
fn into(self) -> u16 {
match self {
CueType::Point => 1,
CueType::Loop => 2,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big, repr = u32)]
pub enum CueListType {
MemoryCues = 0,
HotCues = 1,
}
impl Into<u16> for CueListType {
fn into(self) -> u16 {
match self {
CueListType::MemoryCues => 0,
CueListType::HotCues => 1,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(repr = u32)]
pub enum CueStatus {
Disabled = 0,
Active = 4,
}
impl Into<u16> for CueStatus {
fn into(self) -> u16 {
match self {
CueStatus::Disabled => 0,
CueStatus::Active => 4,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct Cue {
#[br(assert(header.tag == AnlzTag::Cue && header.total_size == 56))]
header: Header,
pub hot_cue: u32,
pub status: CueStatus,
#[br(temp)]
#[br(assert(u1 == 0x10000))]
#[bw(calc = 0x10000)]
u1: u32,
pub order_first: u16,
pub order_last: u16,
pub cue_type: CueType,
#[br(temp)]
#[br(assert(u2 == 0))]
#[bw(calc = 0)]
u2: u8,
#[br(temp)]
#[br(assert(u3 == 1000))]
#[bw(calc = 1000)]
u3: u16,
pub time: u32,
pub loop_time: u32,
u4: u32,
u5: u32,
u6: u32,
u7: u32,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ColorIndex {
#[brw(magic = 0u8)]
None,
#[brw(magic = 1u8)]
Pink,
#[brw(magic = 2u8)]
Red,
#[brw(magic = 3u8)]
Orange,
#[brw(magic = 4u8)]
Yellow,
#[brw(magic = 5u8)]
Green,
#[brw(magic = 6u8)]
Aqua,
#[brw(magic = 7u8)]
Blue,
#[brw(magic = 8u8)]
Purple,
Other(u8),
}
impl From<u8> for ColorIndex {
fn from(value: u8) -> Self {
match value {
0 => Self::None,
1 => Self::Pink,
2 => Self::Red,
3 => Self::Orange,
4 => Self::Yellow,
5 => Self::Green,
6 => Self::Aqua,
7 => Self::Blue,
8 => Self::Purple,
value => Self::Other(value),
}
}
}
impl From<ColorIndex> for u8 {
fn from(value: ColorIndex) -> Self {
match value {
ColorIndex::None => 0,
ColorIndex::Pink => 1,
ColorIndex::Red => 2,
ColorIndex::Orange => 3,
ColorIndex::Yellow => 4,
ColorIndex::Green => 5,
ColorIndex::Aqua => 6,
ColorIndex::Blue => 7,
ColorIndex::Purple => 8,
ColorIndex::Other(value) => value,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct ExtendedCue {
#[br(assert(header.tag == AnlzTag::ExtendedCue && header.total_size >= 48))]
#[bw(map = |header: &Header| { let mut h = header.clone(); h.total_size = self.size(); h })]
header: Header,
pub hot_cue: u32,
pub cue_type: CueType,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u8,
#[br(temp)]
#[br(assert(u2 == 1000))]
#[bw(calc = 1000)]
u2: u16,
pub time: u32,
pub loop_time: u32,
pub color: ColorIndex,
u3: u8,
u4: u16,
u5: u32,
pub loop_numerator: u16,
pub loop_denominator: u16,
#[br(args(header.total_size - 48), parse_with = AnlzString::read_bounded)]
pub comment: AnlzString,
pub hot_cue_color_index: u8,
pub hot_cue_color_rgb: (u8, u8, u8),
#[br(count = header.total_size - 48 - comment.payload_byte_len())]
pub trailing_data: Vec<u8>,
}
impl ExtendedCue {
fn size(&self) -> u32 {
48 + self.comment.payload_byte_len() + self.trailing_data.len() as u32
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CueList {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
pub list_type: CueListType,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u16,
#[br(temp)]
#[bw(calc = cues.len() as u16)]
len_cues: u16,
pub memory_count: i32,
#[br(count = usize::from(len_cues))]
pub cues: Vec<Cue>,
}
impl SizedSection for CueList {
fn size(&self) -> u32 {
24 + self.cues.len() as u32 * 56
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ExtendedCueList {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
pub list_type: CueListType,
#[br(temp)]
#[bw(calc = cues.len() as u16)]
len_cues: u16,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u16,
#[br(count = usize::from(len_cues))]
pub cues: Vec<ExtendedCue>,
}
impl SizedSection for ExtendedCueList {
fn size(&self) -> u32 {
20 + self.cues.iter().map(ExtendedCue::size).sum::<u32>()
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Path {
#[br(temp)]
#[br(assert(header_size == 16))]
#[bw(calc = 16)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_path == total_size - header_size))]
#[bw(calc = ((path.len() as u32) + 1) * 2)]
len_path: u32,
#[br(assert(len_path == total_size - header_size))]
#[br(assert((path.len() as u32 + 1) * 2 == len_path))]
pub path: NullWideString,
}
impl SizedSection for Path {
fn size(&self) -> u32 {
16 + (self.path.len() as u32 + 1) * 2
}
}
impl Path {
pub fn new<P: AsRef<std::path::Path>>(path: P) -> Self {
let string = NullWideString::from("");
let mut item = Self {
total_size: 0,
path: string,
};
item.set(path);
item
}
pub fn set<P: AsRef<std::path::Path>>(&mut self, path: P) {
let path = path.as_ref();
let string = NullWideString::from(path.to_string_lossy().replace('\\', "/"));
self.path = string.clone();
self.total_size = ((string.len() as u32) + 1) * 2 + 16;
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct VBR {
#[br(temp, assert(header_size == 16))]
#[bw(calc = 16u32)]
header_size: u32,
#[br(temp, assert(total_size == 1620))]
#[bw(calc = 1620u32)]
total_size: u32,
pub u1: u32,
pub index: [u32; 400],
pub u2: u32,
}
impl SizedSection for VBR {
fn size(&self) -> u32 {
1620
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(big)]
pub struct SeekPoint {
pub sample: u64,
pub offset: u64,
pub frame_samples: u32,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct SeekIndex {
#[br(temp, assert(header_size == 32))]
#[bw(calc = 32u32)]
header_size: u32,
#[br(temp, assert(total_size >= header_size))]
#[bw(calc = 32u32 + entries.len() as u32 * 20)]
total_size: u32,
pub u1: u32,
pub total_samples: u64,
#[br(temp, assert(u64::from(entry_count) * 20 == u64::from(total_size - header_size)))]
#[bw(calc = entries.len() as u32)]
entry_count: u32,
#[br(temp, assert(len_entry_bytes == 20))]
#[bw(calc = 20u32)]
len_entry_bytes: u32,
#[br(count = entry_count)]
pub entries: Vec<SeekPoint>,
}
impl SizedSection for SeekIndex {
fn size(&self) -> u32 {
32 + self.entries.len() as u32 * 20
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(big, map = Self::from_bytes)]
#[bw(big, map = |x: &TinyWaveformColumn| x.into_bytes())]
pub struct TinyWaveformColumn {
pub height: B4,
pub unused: B4,
}
impl Default for TinyWaveformColumn {
fn default() -> Self {
Self::new()
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct TinyWaveformPreview {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_preview == total_size - header_size))]
#[bw(calc = data.len() as u32)]
len_preview: u32,
#[br(temp)]
#[br(assert(unknown == 0x00010000))]
#[bw(calc = 0x00010000)]
unknown: u32,
#[br(count = len_preview)]
pub data: Vec<TinyWaveformColumn>,
}
impl SizedSection for TinyWaveformPreview {
fn size(&self) -> u32 {
20 + self.data.len() as u32
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(big, map = Self::from_bytes)]
#[bw(big, map = |x: &WaveformColumn| x.into_bytes())]
pub struct WaveformColumn {
pub height: B5,
pub whiteness: B3,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformPreview {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_preview == total_size - header_size))]
#[bw(calc = data.len() as u32)]
len_preview: u32,
#[br(temp)]
#[br(assert(unknown == 0x00010000))]
#[bw(calc = 0x00010000)]
unknown: u32,
#[br(count = len_preview)]
pub data: Vec<WaveformColumn>,
}
impl SizedSection for WaveformPreview {
fn size(&self) -> u32 {
20 + self.data.len() as u32
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformDetail {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 1))]
#[bw(calc = 1u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(temp)]
#[br(assert(unknown == 0x00960000))]
#[bw(calc = 0x00960000)]
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<WaveformColumn>,
}
impl SizedSection for WaveformDetail {
fn size(&self) -> u32 {
24 + self.data.len() as u32
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(big)]
pub struct WaveformColorPreviewColumn {
u1: u8,
u2: u8,
pub energy_bottom_half_freq: u8,
pub energy_bottom_third_freq: u8,
pub energy_mid_third_freq: u8,
pub energy_top_third_freq: u8,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformColorPreview {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 6))]
#[bw(calc = 6u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(temp)]
#[br(assert(unknown == 0))]
#[bw(calc = 0)]
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<WaveformColorPreviewColumn>,
}
impl SizedSection for WaveformColorPreview {
fn size(&self) -> u32 {
24 + self.data.len() as u32 * 6
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(big, map = |value: u16| Self::from_bytes(value.to_le_bytes()))]
#[bw(big, map = |x: &WaveformColorDetailColumn| u16::from_le_bytes(x.into_bytes()))]
pub struct WaveformColorDetailColumn {
pub low_bits: B2,
pub height: B5,
pub blue: B3,
pub green: B3,
pub red: B3,
}
impl WaveformColorDetailColumn {
pub fn fine_height_substep(&self) -> u8 {
let bits = self.low_bits();
((bits & 1) << 1) | ((bits >> 1) & 1)
}
pub fn full_height(&self) -> u8 {
(self.height() << 2) | self.fine_height_substep()
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformColorDetail {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 2))]
#[bw(calc = 2u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<WaveformColorDetailColumn>,
}
impl SizedSection for WaveformColorDetail {
fn size(&self) -> u32 {
24 + self.data.len() as u32 * 2
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(map = Self::from_bytes)]
#[bw(big, map = |x: &Waveform3BandColumn| x.into_bytes())]
pub struct Waveform3BandColumn {
pub low: B8,
pub mid: B8,
pub high: B8,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Waveform3BandPreview {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 3))]
#[bw(calc = 3u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(count = len_entries)]
pub data: Vec<Waveform3BandColumn>,
}
impl SizedSection for Waveform3BandPreview {
fn size(&self) -> u32 {
20 + self.data.len() as u32 * 3
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Waveform3BandDetail {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 3))]
#[bw(calc = 3u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<Waveform3BandColumn>,
}
impl SizedSection for Waveform3BandDetail {
fn size(&self) -> u32 {
24 + self.data.len() as u32 * 3
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct Waveform3BandCalibration {
#[br(temp, assert(header_size == 14))]
#[bw(calc = 14u32)]
header_size: u32,
#[br(temp, assert(total_size == 20))]
#[bw(calc = 20u32)]
total_size: u32,
#[br(temp)]
#[br(assert(unknown == 0))]
#[bw(calc = 0)]
unknown: u16,
pub low_gain: u16,
pub mid_gain: u16,
pub high_gain: u16,
}
impl SizedSection for Waveform3BandCalibration {
fn size(&self) -> u32 {
20
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(big, repr = u16)]
pub enum Mood {
High = 1,
Mid,
Low,
}
impl Into<u16> for Mood {
fn into(self) -> u16 {
match self {
Mood::High => 1,
Mood::Mid => 2,
Mood::Low => 3,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(repr = u8)]
pub enum Bank {
Default = 0,
Cool,
Natural,
Hot,
Subtle,
Warm,
Vivid,
Club1,
Club2,
}
impl Into<u16> for Bank {
fn into(self) -> u16 {
match self {
Bank::Default => 0,
Bank::Cool => 1,
Bank::Natural => 2,
Bank::Hot => 3,
Bank::Subtle => 4,
Bank::Warm => 5,
Bank::Vivid => 6,
Bank::Club1 => 7,
Bank::Club2 => 8,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(big)]
pub struct Phrase {
pub index: u16,
pub beat: u16,
pub kind: u16,
#[allow(dead_code)]
u1: u8,
pub k1: u8,
#[allow(dead_code)]
u2: u8,
pub k2: u8,
#[allow(dead_code)]
u3: u8,
pub b: u8,
pub beat2: u16,
pub beat3: u16,
pub beat4: u16,
#[allow(dead_code)]
u4: u8,
pub k3: u8,
#[allow(dead_code)]
u5: u8,
pub fill: u8,
pub beat_fill: u16,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[br(import(len_entries: u16))]
pub struct SongStructureData {
pub mood: Mood,
u1: u32,
u2: u16,
pub end_beat: u16,
u3: u16,
pub bank: Bank,
u4: u8,
#[br(count = usize::from(len_entries))]
pub phrases: Vec<Phrase>,
}
impl SongStructureData {
const KEY_DATA: [u8; 19] = [
0xCB, 0xE1, 0xEE, 0xFA, 0xE5, 0xEE, 0xAD, 0xEE, 0xE9, 0xD2, 0xE9, 0xEB, 0xE1, 0xE9, 0xF3,
0xE8, 0xE9, 0xF4, 0xE1,
];
fn get_key(len_entries: u16, version: u16) -> impl Iterator<Item = u8> {
Self::KEY_DATA
.into_iter()
.cycle()
.skip(usize::from(version) % Self::KEY_DATA.len())
.take(Self::KEY_DATA.len())
.map(move |byte| byte.wrapping_add(len_entries as u8))
}
fn check_if_encrypted(raw_mood: [u8; 2]) -> bool {
!(1..=3).contains(&u16::from_be_bytes(raw_mood))
}
fn read_encrypted<R: Read + Seek>(
reader: &mut R,
endian: Endian,
(is_encrypted, len_entries, version): (bool, u16, u16),
) -> BinResult<Self> {
if is_encrypted {
let key: Vec<u8> = Self::get_key(len_entries, version).collect();
let mut xor_reader = XorStream::with_key(reader, key);
Self::read_options(&mut xor_reader, endian, (len_entries,))
} else {
Self::read_options(reader, endian, (len_entries,))
}
}
fn write_encrypted<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
(is_encrypted, len_entries, version): (bool, u16, u16),
) -> BinResult<()> {
if is_encrypted {
let key: Vec<u8> = Self::get_key(len_entries, version).collect();
let mut xor_writer = XorStream::with_key(writer, key);
self.write_options(&mut xor_writer, endian, ())
} else {
self.write_options(writer, endian, ())
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SongStructure {
#[br(temp)]
#[br(assert(header_size == 32))]
#[bw(calc = 32)]
header_size: u32,
#[bw(map = |_: &u32| self.size())]
total_size: u32,
pub version: u16,
#[br(temp)]
#[br(assert(len_entry_bytes == 24))]
#[bw(calc = 24u16)]
len_entry_bytes: u16,
#[br(temp)]
#[br(assert((u32::from(len_entry_bytes) * u32::from(len_entries)) == total_size - header_size))]
#[bw(calc = data.phrases.len() as u16)]
len_entries: u16,
#[br(restore_position, map = |raw_mood: [u8; 2]| SongStructureData::check_if_encrypted(raw_mood))]
#[bw(ignore)]
pub is_encrypted: bool,
#[br(args(is_encrypted, len_entries, version), parse_with = SongStructureData::read_encrypted)]
#[bw(args(*is_encrypted, len_entries, *version), write_with = SongStructureData::write_encrypted)]
pub data: SongStructureData,
}
impl SizedSection for SongStructure {
fn size(&self) -> u32 {
32 + self.data.phrases.len() as u32 * 24
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct VocalDetection {
#[br(temp, assert(header_size == 24))]
#[bw(calc = 24u32)]
header_size: u32,
#[br(temp, assert(total_size >= header_size))]
#[bw(calc = 24u32 + confidence.len() as u32)]
total_size: u32,
pub u1: u32,
pub u2: u32,
#[br(temp, assert(len_confidence == total_size - header_size))]
#[bw(calc = confidence.len() as u32)]
len_confidence: u32,
#[br(count = len_confidence)]
pub confidence: Vec<u8>,
}
impl SizedSection for VocalDetection {
fn size(&self) -> u32 {
24 + self.confidence.len() as u32
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Unknown {
#[br(assert(header_size >= 12))]
header_size: u32,
#[br(assert(total_size >= header_size))]
total_size: u32,
#[br(count = header_size - 12)]
header_data: Vec<u8>,
#[br(count = total_size - header_size)]
content_data: Vec<u8>,
}
impl SizedSection for Unknown {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[br(import(tag: AnlzTag))]
pub enum Content {
#[br(pre_assert(tag == AnlzTag::BeatGrid))]
BeatGrid(BeatGrid),
#[br(pre_assert(tag == AnlzTag::ExtendedBeatGrid))]
ExtendedBeatGrid(ExtendedBeatGrid),
#[br(pre_assert(tag == AnlzTag::CueList))]
CueList(CueList),
#[br(pre_assert(tag == AnlzTag::ExtendedCueList))]
ExtendedCueList(ExtendedCueList),
#[br(pre_assert(tag == AnlzTag::Path))]
Path(Path),
#[br(pre_assert(tag == AnlzTag::VBR))]
VBR(VBR),
#[br(pre_assert(tag == AnlzTag::Waveform3BandCalibration))]
Waveform3BandCalibration(Waveform3BandCalibration),
#[br(pre_assert(tag == AnlzTag::SeekIndex))]
SeekIndex(SeekIndex),
#[br(pre_assert(tag == AnlzTag::VocalDetection))]
VocalDetection(VocalDetection),
#[br(pre_assert(tag == AnlzTag::WaveformPreview))]
WaveformPreview(WaveformPreview),
#[br(pre_assert(tag == AnlzTag::TinyWaveformPreview))]
TinyWaveformPreview(TinyWaveformPreview),
#[br(pre_assert(tag == AnlzTag::WaveformDetail))]
WaveformDetail(WaveformDetail),
#[br(pre_assert(tag == AnlzTag::WaveformColorPreview))]
WaveformColorPreview(WaveformColorPreview),
#[br(pre_assert(tag == AnlzTag::WaveformColorDetail))]
WaveformColorDetail(WaveformColorDetail),
#[br(pre_assert(tag == AnlzTag::Waveform3BandPreview))]
Waveform3BandPreview(Waveform3BandPreview),
#[br(pre_assert(tag == AnlzTag::Waveform3BandDetail))]
Waveform3BandDetail(Waveform3BandDetail),
#[br(pre_assert(tag == AnlzTag::SongStructure))]
SongStructure(SongStructure),
#[br(pre_assert(matches!(tag, AnlzTag::Unknown(_))))]
Unknown(Unknown),
}
impl SizedSection for Content {
fn size(&self) -> u32 {
match *self {
Content::BeatGrid(ref x) => x.size(),
Content::ExtendedBeatGrid(ref x) => x.size(),
Content::CueList(ref x) => x.size(),
Content::ExtendedCueList(ref x) => x.size(),
Content::Path(ref x) => x.size(),
Content::VBR(ref x) => x.size(),
Content::Waveform3BandCalibration(ref x) => x.size(),
Content::SeekIndex(ref x) => x.size(),
Content::VocalDetection(ref x) => x.size(),
Content::WaveformPreview(ref x) => x.size(),
Content::TinyWaveformPreview(ref x) => x.size(),
Content::WaveformDetail(ref x) => x.size(),
Content::WaveformColorPreview(ref x) => x.size(),
Content::WaveformColorDetail(ref x) => x.size(),
Content::Waveform3BandPreview(ref x) => x.size(),
Content::Waveform3BandDetail(ref x) => x.size(),
Content::SongStructure(ref x) => x.size(),
Content::Unknown(ref x) => x.size(),
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Section {
pub tag: AnlzTag,
#[br(args(tag.clone()))]
pub content: Content,
}
impl Section {
pub(crate) fn tag_matches_content(&self) -> bool {
match &self.content {
Content::BeatGrid(..) => self.tag == AnlzTag::BeatGrid,
Content::ExtendedBeatGrid(..) => self.tag == AnlzTag::ExtendedBeatGrid,
Content::VBR(..) => self.tag == AnlzTag::VBR,
Content::SeekIndex(..) => self.tag == AnlzTag::SeekIndex,
Content::TinyWaveformPreview(..) => self.tag == AnlzTag::TinyWaveformPreview,
Content::WaveformPreview(..) => self.tag == AnlzTag::WaveformPreview,
Content::WaveformDetail(..) => self.tag == AnlzTag::WaveformDetail,
Content::WaveformColorPreview(..) => self.tag == AnlzTag::WaveformColorPreview,
Content::WaveformColorDetail(..) => self.tag == AnlzTag::WaveformColorDetail,
Content::Waveform3BandPreview(..) => self.tag == AnlzTag::Waveform3BandPreview,
Content::Waveform3BandDetail(..) => self.tag == AnlzTag::Waveform3BandDetail,
Content::Waveform3BandCalibration(..) => self.tag == AnlzTag::Waveform3BandCalibration,
Content::VocalDetection(..) => self.tag == AnlzTag::VocalDetection,
Content::CueList(..) => self.tag == AnlzTag::CueList,
Content::ExtendedCueList(..) => self.tag == AnlzTag::ExtendedCueList,
Content::Path(..) => self.tag == AnlzTag::Path,
Content::SongStructure(..) => self.tag == AnlzTag::SongStructure,
Content::Unknown(..) => matches!(self.tag, AnlzTag::Unknown(_)),
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct AnlzData {
#[br(assert(header.tag == AnlzTag::File && header.size >= 12 && header.total_size >= header.size))]
#[bw(map = |header: &Header| { let mut h = header.clone(); h.size = 12 + header_data.len() as u32; h.total_size = h.size + sections.iter().map(|s| s.content.size()).sum::<u32>(); h })]
pub header: Header,
#[br(count = header.remaining_size())]
pub header_data: Vec<u8>,
#[br(parse_with = Self::parse_sections, args(header.content_size()))]
pub sections: Vec<Section>,
}
impl AnlzData {
fn parse_sections<R: Read + Seek>(
reader: &mut R,
endian: Endian,
args: (u32,),
) -> BinResult<Vec<Section>> {
let (content_size,) = args;
let final_position = reader.stream_position()? + u64::from(content_size);
let mut sections: Vec<Section> = vec![];
while reader.stream_position()? < final_position {
let section = Section::read_options(reader, endian, ())?;
sections.push(section);
}
Ok(sections)
}
pub fn update_total_size(&mut self) {
self.header.size = 12 + self.header_data.len() as u32;
let mut total = self.header.size;
for section in self.sections.iter_mut() {
total += section.content.size();
}
self.header.total_size = total;
}
}