1use crate::{NoteInfo, Ssr, MsdForAllRates as BindingsMsdForAllRates, CalcHandle, create_calc, calc_version, calc_msd, calc_ssr, destroy_calc};
2use crate::error::{MinaCalcError, MinaCalcResult};
3
4#[derive(Debug, Clone, Copy)]
6pub struct Note {
7 pub notes: u32,
9 pub row_time: f32,
11}
12
13impl Note {
14 pub fn validate(&self) -> MinaCalcResult<()> {
16 if self.notes == 0 {
17 return Err(MinaCalcError::InvalidNoteData("Note must have at least one column".to_string()));
18 }
19 if self.notes > 0b1111 {
20 return Err(MinaCalcError::InvalidNoteData("Note bitflags exceed 4K limit".to_string()));
21 }
22 if self.row_time < 0.0 {
23 return Err(MinaCalcError::InvalidNoteData("Row time cannot be negative".to_string()));
24 }
25 Ok(())
26 }
27}
28
29impl From<Note> for NoteInfo {
30 fn from(note: Note) -> Self {
31 NoteInfo {
32 notes: note.notes,
33 rowTime: note.row_time,
34 }
35 }
36}
37
38impl From<NoteInfo> for Note {
39 fn from(note_info: NoteInfo) -> Self {
40 Note {
41 notes: note_info.notes,
42 row_time: note_info.rowTime,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy)]
49pub struct SkillsetScores {
50 pub overall: f32,
51 pub stream: f32,
52 pub jumpstream: f32,
53 pub handstream: f32,
54 pub stamina: f32,
55 pub jackspeed: f32,
56 pub chordjack: f32,
57 pub technical: f32,
58}
59
60impl SkillsetScores {
61 pub fn validate(&self) -> MinaCalcResult<()> {
63 let scores = [
64 self.overall, self.stream, self.jumpstream, self.handstream,
65 self.stamina, self.jackspeed, self.chordjack, self.technical
66 ];
67
68 for score in scores {
69 if score < 0.0 || score > 1000.0 {
70 return Err(MinaCalcError::InvalidNoteData(format!("Score {} is out of reasonable bounds", score)));
71 }
72 }
73 Ok(())
74 }
75}
76
77impl From<Ssr> for SkillsetScores {
78 fn from(ssr: Ssr) -> Self {
79 SkillsetScores {
80 overall: ssr.overall,
81 stream: ssr.stream,
82 jumpstream: ssr.jumpstream,
83 handstream: ssr.handstream,
84 stamina: ssr.stamina,
85 jackspeed: ssr.jackspeed,
86 chordjack: ssr.chordjack,
87 technical: ssr.technical,
88 }
89 }
90}
91
92impl From<SkillsetScores> for Ssr {
93 fn from(scores: SkillsetScores) -> Self {
94 Ssr {
95 overall: scores.overall,
96 stream: scores.stream,
97 jumpstream: scores.jumpstream,
98 handstream: scores.handstream,
99 stamina: scores.stamina,
100 jackspeed: scores.jackspeed,
101 chordjack: scores.chordjack,
102 technical: scores.technical,
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct AllRates {
110 pub msds: [SkillsetScores; 14],
111}
112
113impl AllRates {
114 pub fn validate(&self) -> MinaCalcResult<()> {
116 for (i, scores) in self.msds.iter().enumerate() {
117 scores.validate()
118 .map_err(|e| MinaCalcError::InvalidNoteData(format!("Rate {}: {}", (i as f32) / 10.0 + 0.7, e)))?;
119 }
120 Ok(())
121 }
122}
123
124impl From<AllRates> for super::MsdForAllRates {
125 fn from(msd: AllRates) -> Self {
126 let mut bindings_msd = super::MsdForAllRates {
127 msds: [Ssr {
128 overall: 0.0,
129 stream: 0.0,
130 jumpstream: 0.0,
131 handstream: 0.0,
132 stamina: 0.0,
133 jackspeed: 0.0,
134 chordjack: 0.0,
135 technical: 0.0,
136 }; 14],
137 };
138
139 for (i, scores) in msd.msds.iter().enumerate() {
140 bindings_msd.msds[i] = (*scores).into();
141 }
142
143 bindings_msd
144 }
145}
146
147impl From<BindingsMsdForAllRates> for AllRates {
148 fn from(bindings_msd: BindingsMsdForAllRates) -> Self {
149 let mut msds = [SkillsetScores {
150 overall: 0.0,
151 stream: 0.0,
152 jumpstream: 0.0,
153 handstream: 0.0,
154 stamina: 0.0,
155 jackspeed: 0.0,
156 chordjack: 0.0,
157 technical: 0.0,
158 }; 14];
159
160 for (i, ssr) in bindings_msd.msds.iter().enumerate() {
161 msds[i] = (*ssr).into();
162 }
163
164 AllRates { msds }
165 }
166}
167
168#[derive(Clone)]
170pub struct Calc {
171 handle: *mut CalcHandle,
172}
173
174impl Calc {
175 pub fn new() -> MinaCalcResult<Self> {
177 let handle = unsafe { create_calc() };
178 if handle.is_null() {
179 return Err(MinaCalcError::CalculatorCreationFailed);
180 }
181 Ok(Calc { handle })
182 }
183
184 pub fn version() -> i32 {
186 unsafe { calc_version() }
187 }
188
189 pub fn calc_msd(&self, notes: &[Note]) -> MinaCalcResult<AllRates> {
191 if notes.is_empty() {
192 return Err(MinaCalcError::NoNotesProvided);
193 }
194
195 for note in notes {
197 note.validate()?;
198 }
199
200 let note_infos: Vec<NoteInfo> = notes.iter().map(|¬e| note.into()).collect();
202
203 let result = unsafe {
204 calc_msd(self.handle, note_infos.as_ptr(), note_infos.len())
205 };
206
207 let msd: AllRates = result.into();
208 msd.validate()?;
209 Ok(msd)
210 }
211
212 pub fn calc_ssr(
214 &self,
215 notes: &[Note],
216 music_rate: f32,
217 score_goal: f32,
218 ) -> MinaCalcResult<SkillsetScores> {
219 if notes.is_empty() {
220 return Err(MinaCalcError::NoNotesProvided);
221 }
222
223 if music_rate <= 0.0 {
224 return Err(MinaCalcError::InvalidMusicRate(music_rate));
225 }
226
227 if score_goal <= 0.0 || score_goal > 100.0 {
228 return Err(MinaCalcError::InvalidScoreGoal(score_goal));
229 }
230
231 for note in notes {
233 note.validate()?;
234 }
235
236 let mut note_infos: Vec<NoteInfo> = notes.iter().map(|¬e| note.into()).collect();
238
239 let result = unsafe {
240 calc_ssr(self.handle, note_infos.as_mut_ptr(), note_infos.len(), music_rate, score_goal)
241 };
242
243 let scores: SkillsetScores = result.into();
244 scores.validate()?;
245 Ok(scores)
246 }
247
248 pub fn is_valid(&self) -> bool {
250 !self.handle.is_null()
251 }
252}
253
254impl Drop for Calc {
255 fn drop(&mut self) {
256 if !self.handle.is_null() {
257 unsafe {
258 destroy_calc(self.handle);
259 }
260 }
261 }
262}
263
264impl Default for Calc {
265 fn default() -> Self {
266 Self::new().expect("Failed to create default calculator")
267 }
268}
269
270#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_calc_version() {
277 let version = Calc::version();
278 assert!(version > 0);
279 }
280
281 #[test]
282 fn test_calc_creation() {
283 let calc = Calc::new();
284 assert!(calc.is_ok());
285 }
286
287 #[test]
288 fn test_note_conversion() {
289 let note = Note {
290 notes: 4,
291 row_time: 1.5,
292 };
293
294 let note_info: NoteInfo = note.into();
295 let converted_note: Note = note_info.into();
296
297 assert_eq!(note.notes, converted_note.notes);
298 assert_eq!(note.row_time, converted_note.row_time);
299 }
300
301 #[test]
302 fn test_skillset_scores_conversion() {
303 let scores = SkillsetScores {
304 overall: 10.5,
305 stream: 8.2,
306 jumpstream: 12.1,
307 handstream: 9.3,
308 stamina: 7.8,
309 jackspeed: 11.4,
310 chordjack: 6.9,
311 technical: 13.2,
312 };
313
314 let ssr: Ssr = scores.into();
315 let converted_scores: SkillsetScores = ssr.into();
316
317 assert_eq!(scores.overall, converted_scores.overall);
318 assert_eq!(scores.stream, converted_scores.stream);
319 assert_eq!(scores.jumpstream, converted_scores.jumpstream);
320 assert_eq!(scores.handstream, converted_scores.handstream);
321 assert_eq!(scores.stamina, converted_scores.stamina);
322 assert_eq!(scores.jackspeed, converted_scores.jackspeed);
323 assert_eq!(scores.chordjack, converted_scores.chordjack);
324 assert_eq!(scores.technical, converted_scores.technical);
325 }
326}