use crate::codec::Decoder;
use crate::error::RoxResult;
use crate::model::{Metadata, Note, RoxChart, TimingPoint};
use super::parser;
use super::types::QuaChart;
pub struct QuaDecoder;
impl QuaDecoder {
#[must_use]
pub fn from_qua(qua: &QuaChart) -> RoxChart {
let key_count = qua.mode.key_count();
let mut chart = RoxChart::new(key_count);
chart.metadata = Metadata {
chart_id: if qua.map_id > 0 {
#[allow(clippy::cast_sign_loss)]
Some(qua.map_id as u64)
} else {
None
},
chartset_id: if qua.map_set_id > 0 {
#[allow(clippy::cast_sign_loss)]
Some(qua.map_set_id as u64)
} else {
None
},
key_count,
title: qua.title.clone().into(),
artist: qua.artist.clone().into(),
creator: qua.creator.clone().into(),
difficulty_name: qua.difficulty_name.clone().into(),
audio_file: qua.audio_file.clone().into(),
background_file: qua.background_file.clone().map(|s| s.into()),
preview_time_us: i64::from(qua.preview_time) * 1000,
source: qua.source.clone().map(|s| s.into()),
tags: qua
.tags
.as_deref()
.unwrap_or("")
.split_whitespace()
.map(|s| s.into())
.collect(),
..Default::default()
};
for tp in &qua.timing_points {
#[allow(clippy::cast_possible_truncation)]
let time_us = (tp.start_time * 1000.0) as i64;
let mut timing = TimingPoint::bpm(time_us, tp.bpm);
timing.signature = tp
.signature
.as_ref()
.map_or(4, super::types::TimeSignature::beats);
chart.timing_points.push(timing);
}
for sv in &qua.slider_velocities {
#[allow(clippy::cast_possible_truncation)]
let time_us = (sv.start_time * 1000.0) as i64;
#[allow(clippy::cast_possible_truncation)]
let multiplier = sv.multiplier as f32;
chart
.timing_points
.push(TimingPoint::sv(time_us, multiplier));
}
chart.timing_points.sort_by_key(|tp| tp.time_us);
for ho in &qua.hit_objects {
#[allow(clippy::cast_possible_truncation)]
let time_us = (ho.start_time * 1000.0) as i64;
let column = ho.lane.saturating_sub(1);
let note = if let Some(end_time) = ho.end_time {
#[allow(clippy::cast_possible_truncation)]
let end_us = (end_time * 1000.0) as i64;
let duration_us = end_us - time_us;
Note::hold(time_us, duration_us, column)
} else {
Note::tap(time_us, column)
};
chart.notes.push(note);
}
chart.notes.sort_by_key(|n| n.time_us);
chart
}
}
impl Decoder for QuaDecoder {
fn decode(data: &[u8]) -> RoxResult<RoxChart> {
let qua = parser::parse(data)?;
Ok(Self::from_qua(&qua))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::Decoder;
#[test]
fn test_decode_asset_4k() {
let data = crate::test_utils::get_test_asset("quaver/4K.qua");
let chart = <QuaDecoder as Decoder>::decode(&data).expect("Failed to decode 4K.qua");
assert_eq!(chart.key_count(), 4);
assert!(!chart.notes.is_empty());
assert!(!chart.timing_points.is_empty());
}
}