rhythm_open_exchange/codec/formats/taiko/
decoder.rs1use crate::codec::Decoder;
9use crate::error::RoxResult;
10use crate::model::{Metadata, Note, RoxChart, TimingPoint};
11
12use super::types::{AlternationState, ColumnLayout};
13use crate::codec::formats::taiko::parser;
14
15pub struct TaikoDecoder;
17
18impl TaikoDecoder {
19 pub fn decode_with_layout(data: &[u8], layout: ColumnLayout) -> RoxResult<RoxChart> {
25 let mut state = AlternationState::new(layout);
26 Self::decode_with_state(data, &mut state)
27 }
28
29 pub fn decode_with_state(data: &[u8], state: &mut AlternationState) -> RoxResult<RoxChart> {
35 let beatmap = parser::parse(data)?;
36
37 let mut chart = RoxChart::new(4);
39
40 chart.metadata = Metadata {
42 #[allow(clippy::cast_sign_loss)]
44 chart_id: beatmap.metadata.beatmap_id.map(|id| id as u64),
45 #[allow(clippy::cast_sign_loss)]
46 chartset_id: beatmap.metadata.beatmap_set_id.map(|id| id as u64),
47 key_count: 4,
48 title: beatmap
49 .metadata
50 .title_unicode
51 .clone()
52 .unwrap_or_else(|| beatmap.metadata.title.clone())
53 .into(),
54 artist: beatmap
55 .metadata
56 .artist_unicode
57 .clone()
58 .unwrap_or_else(|| beatmap.metadata.artist.clone())
59 .into(),
60 creator: beatmap.metadata.creator.clone().into(),
61 difficulty_name: beatmap.metadata.version.clone().into(),
62 difficulty_value: Some(beatmap.difficulty.overall_difficulty),
63 audio_file: beatmap.general.audio_filename.clone().into(),
64 background_file: beatmap.background.clone().map(|s| s.into()),
65 audio_offset_us: i64::from(beatmap.general.audio_lead_in) * 1000,
66 preview_time_us: if beatmap.general.preview_time > 0 {
67 i64::from(beatmap.general.preview_time) * 1000
68 } else {
69 0
70 },
71 source: beatmap.metadata.source.clone().map(|s| s.into()),
72 tags: beatmap
73 .metadata
74 .tags
75 .iter()
76 .map(|s| s.clone().into())
77 .collect(),
78 ..Default::default()
79 };
80
81 for tp in &beatmap.timing_points {
83 #[allow(clippy::cast_possible_truncation)]
84 let time_us = (tp.time * 1000.0) as i64;
85
86 if tp.uninherited {
87 if let Some(bpm) = tp.bpm() {
88 let mut timing = TimingPoint::bpm(time_us, bpm);
89 timing.signature = tp.meter;
90 chart.timing_points.push(timing);
91 }
92 } else {
93 }
96 }
97
98 if chart.timing_points.is_empty() {
100 chart.timing_points.push(TimingPoint::bpm(0, 120.0));
101 }
102
103 for ho in &beatmap.hit_objects {
105 if ho.is_spinner() {
107 continue;
108 }
109
110 #[allow(clippy::cast_possible_truncation)]
111 let time_us = (ho.time_ms * 1000.0) as i64;
112 let is_big = ho.hitsound.is_big();
113
114 let columns = if ho.hitsound.is_kat() {
116 state.next_kat_columns(is_big)
117 } else {
118 state.next_don_columns(is_big)
120 };
121
122 for col in columns {
124 chart.notes.push(Note::tap(time_us, col));
125 }
126 }
127
128 chart.notes.sort_by_key(|n| n.time_us);
130
131 Ok(chart)
132 }
133}
134
135impl Decoder for TaikoDecoder {
136 fn decode(data: &[u8]) -> RoxResult<RoxChart> {
137 let mut state = AlternationState::default();
138 Self::decode_with_state(data, &mut state)
139 }
140}