rhythm_open_exchange/codec/formats/fnf/
encoder.rs1use crate::codec::Encoder;
7use crate::error::RoxResult;
8use crate::model::RoxChart;
9
10use super::types::{FnfChart, FnfNote, FnfSection, FnfSong};
11
12pub struct FnfEncoder;
14
15impl Encoder for FnfEncoder {
16 fn encode(chart: &RoxChart) -> RoxResult<Vec<u8>> {
17 let base_bpm = chart
19 .timing_points
20 .iter()
21 .find(|tp| !tp.is_inherited)
22 .map_or(120.0, |tp| tp.bpm);
23
24 let is_8k = chart.key_count() >= 8;
26
27 let mut section_notes: Vec<FnfNote> = Vec::new();
30
31 for note in &chart.notes {
32 #[allow(clippy::cast_precision_loss)]
33 let time_ms = note.time_us as f64 / 1000.0;
34
35 let lane = if is_8k {
37 note.column
39 } else {
40 note.column
42 };
43
44 let fnf_note = match ¬e.note_type {
45 crate::model::NoteType::Hold { duration_us } => {
46 #[allow(clippy::cast_precision_loss)]
47 let duration_ms = *duration_us as f64 / 1000.0;
48 FnfNote::hold(time_ms, lane, duration_ms)
49 }
50 _ => FnfNote::tap(time_ms, lane),
51 };
52
53 section_notes.push(fnf_note);
54 }
55
56 let section = FnfSection {
57 section_notes,
58 length_in_steps: 160_000, must_hit_section: !is_8k, change_bpm: true,
61 bpm: base_bpm,
62 type_of_section: 0,
63 };
64
65 let fnf = FnfChart {
67 song: FnfSong {
68 song: chart.metadata.title.to_string(),
69 bpm: base_bpm,
70 speed: chart.metadata.difficulty_value.unwrap_or(1.5).into(),
71 player1: "bf".to_string(),
72 player2: chart.metadata.creator.to_string(),
73 needs_voices: false,
74 valid_score: true,
75 notes: vec![section], sections: 0, section_lengths: Vec::new(),
78 },
79 };
80
81 let json = serde_json::to_string_pretty(&fnf)
83 .map_err(|e| crate::error::RoxError::InvalidFormat(format!("JSON error: {e}")))?;
84
85 Ok(json.into_bytes())
86 }
87}
88
89#[cfg(test)]
90mod tests {
91
92 #[test]
93 #[cfg(feature = "analysis")]
94 #[ignore = "FNF is currently WIP/Unstable"]
95 fn test_roundtrip_both() {
96 use super::*;
97 use crate::analysis::RoxAnalysis;
98 use crate::codec::Decoder;
99 use crate::codec::formats::fnf::FnfDecoder;
100 let data = crate::test_utils::get_test_asset("fnf/test-song.json");
101 let chart1 = FnfDecoder::decode(&data).unwrap();
103 let encoded = FnfEncoder::encode(&chart1).unwrap();
104 let chart2 = FnfDecoder::decode(&encoded).unwrap();
105
106 assert_eq!(chart1.key_count(), chart2.key_count());
107 assert_eq!(
108 chart1.notes_hash(),
109 chart2.notes_hash(),
110 "Notes hash mismatch"
111 );
112 assert_eq!(
115 chart1.timings_hash(),
116 chart2.timings_hash(),
117 "Timings hash mismatch"
118 );
119 }
120}