1use std::collections::{BTreeMap, HashMap};
4use std::path::Path;
5use std::time::SystemTime;
6
7use crate::bms::prelude::*;
8use crate::bmson::prelude::*;
9use crate::chart_process::{
10 ChartEvent, ChartEventWithPosition, ChartProcessor, ControlEvent, VisibleEvent,
11 types::{BmpId, DisplayRatio, WavId, YCoordinate},
12};
13use std::str::FromStr;
14
15pub struct BmsonProcessor<'a> {
17 bmson: Bmson<'a>,
18
19 audio_name_to_id: HashMap<String, WavId>,
22 bmp_name_to_id: HashMap<String, BmpId>,
24
25 started_at: Option<SystemTime>,
27 last_poll_at: Option<SystemTime>,
28 progressed_y: Decimal,
29
30 default_visible_y_length: YCoordinate,
32 current_bpm: Decimal,
33 current_scroll: Decimal,
34
35 inbox: Vec<ControlEvent>,
37
38 preloaded_events: Vec<ChartEventWithPosition>,
40
41 all_events: BTreeMap<YCoordinate, Vec<ChartEvent>>,
43}
44
45impl<'a> BmsonProcessor<'a> {
46 #[must_use]
48 pub fn new(bmson: Bmson<'a>) -> Self {
49 let init_bpm: Decimal = bmson.info.init_bpm.as_f64().into();
50
51 let mut audio_name_to_id = HashMap::new();
53 let mut bmp_name_to_id = HashMap::new();
54 let mut next_audio_id = 0usize;
55 let mut next_bmp_id = 0usize;
56
57 for sound_channel in &bmson.sound_channels {
59 let std::collections::hash_map::Entry::Vacant(e) =
60 audio_name_to_id.entry(sound_channel.name.to_string())
61 else {
62 continue;
63 };
64 e.insert(WavId::new(next_audio_id));
65 next_audio_id += 1;
66 }
67
68 for mine_channel in &bmson.mine_channels {
70 let std::collections::hash_map::Entry::Vacant(e) =
71 audio_name_to_id.entry(mine_channel.name.to_string())
72 else {
73 continue;
74 };
75 e.insert(WavId::new(next_audio_id));
76 next_audio_id += 1;
77 }
78
79 for key_channel in &bmson.key_channels {
81 let std::collections::hash_map::Entry::Vacant(e) =
82 audio_name_to_id.entry(key_channel.name.to_string())
83 else {
84 continue;
85 };
86 e.insert(WavId::new(next_audio_id));
87 next_audio_id += 1;
88 }
89
90 for BgaHeader { name, .. } in &bmson.bga.bga_header {
92 let std::collections::hash_map::Entry::Vacant(e) =
93 bmp_name_to_id.entry(name.to_string())
94 else {
95 continue;
96 };
97 e.insert(BmpId::new(next_bmp_id));
98 next_bmp_id += 1;
99 }
100
101 let reaction_time_seconds = Decimal::from_str("0.6").unwrap(); let base_bpm = Decimal::from(120);
106 let visible_y_length = (init_bpm.clone() / base_bpm) * reaction_time_seconds;
107
108 let mut processor = Self {
109 bmson,
110 audio_name_to_id,
111 bmp_name_to_id,
112 started_at: None,
113 last_poll_at: None,
114 progressed_y: Decimal::from(0),
115 inbox: Vec::new(),
116 preloaded_events: Vec::new(),
117 all_events: BTreeMap::new(),
118 default_visible_y_length: YCoordinate::from(visible_y_length),
119 current_bpm: init_bpm,
120 current_scroll: Decimal::from(1),
121 };
122
123 processor.preprocess_events();
124 processor
125 }
126
127 fn preprocess_events(&mut self) {
129 let mut events_map: BTreeMap<YCoordinate, Vec<ChartEvent>> = BTreeMap::new();
130
131 for SoundChannel { name, notes } in &self.bmson.sound_channels {
133 for Note { y, x, l, c, .. } in notes {
134 let yy = self.pulses_to_y(y.0);
135 let y_coord = YCoordinate::from(yy.clone());
136
137 let Some((side, key)) = Self::lane_from_x(x.as_ref().copied()) else {
138 let wav_id = self.get_wav_id_for_name(name);
139 let event = ChartEvent::Bgm { wav_id };
140 events_map.entry(y_coord).or_default().push(event);
141 continue;
142 };
143 let wav_id = self.get_wav_id_for_name(name);
144 let length = (*l > 0).then(|| {
145 let end_y = self.pulses_to_y(y.0 + l);
146 YCoordinate::from(end_y - yy.clone())
147 });
148 let kind = if *l > 0 {
149 NoteKind::Long
150 } else {
151 NoteKind::Visible
152 };
153 let event = ChartEvent::Note {
154 side,
155 key,
156 kind,
157 wav_id,
158 length,
159 continue_play: *c,
160 };
161 events_map.entry(y_coord).or_default().push(event);
162 }
163 }
164
165 for ev in &self.bmson.bpm_events {
167 let y = self.pulses_to_y(ev.y.0);
168 let y_coord = YCoordinate::from(y);
169 let event = ChartEvent::BpmChange {
170 bpm: ev.bpm.as_f64().into(),
171 };
172 events_map.entry(y_coord).or_default().push(event);
173 }
174
175 for ScrollEvent { y, rate } in &self.bmson.scroll_events {
177 let y = self.pulses_to_y(y.0);
178 let y_coord = YCoordinate::from(y);
179 let event = ChartEvent::ScrollChange {
180 factor: rate.as_f64().into(),
181 };
182 events_map.entry(y_coord).or_default().push(event);
183 }
184
185 for stop in &self.bmson.stop_events {
187 let y = self.pulses_to_y(stop.y.0);
188 let y_coord = YCoordinate::from(y);
189 let event = ChartEvent::Stop {
190 duration: (stop.duration as f64).into(),
191 };
192 events_map.entry(y_coord).or_default().push(event);
193 }
194
195 for BgaEvent { y, id, .. } in &self.bmson.bga.bga_events {
197 let yy = self.pulses_to_y(y.0);
198 let y_coord = YCoordinate::from(yy);
199 let bmp_name = self
200 .bmson
201 .bga
202 .bga_header
203 .iter()
204 .find(|header| header.id.0 == id.0)
205 .map(|header| &*header.name);
206 let bmp_id = bmp_name.and_then(|name| self.get_bmp_id_for_name(name));
207 let event = ChartEvent::BgaChange {
208 layer: BgaLayer::Base,
209 bmp_id,
210 };
211 events_map.entry(y_coord).or_default().push(event);
212 }
213
214 for BgaEvent { y, id, .. } in &self.bmson.bga.layer_events {
216 let yy = self.pulses_to_y(y.0);
217 let y_coord = YCoordinate::from(yy);
218 let bmp_name = self
219 .bmson
220 .bga
221 .bga_header
222 .iter()
223 .find(|header| header.id.0 == id.0)
224 .map(|header| &*header.name);
225 let bmp_id = bmp_name.and_then(|name| self.get_bmp_id_for_name(name));
226 let event = ChartEvent::BgaChange {
227 layer: BgaLayer::Overlay,
228 bmp_id,
229 };
230 events_map.entry(y_coord).or_default().push(event);
231 }
232
233 for BgaEvent { y, id, .. } in &self.bmson.bga.poor_events {
235 let yy = self.pulses_to_y(y.0);
236 let y_coord = YCoordinate::from(yy);
237 let bmp_name = self
238 .bmson
239 .bga
240 .bga_header
241 .iter()
242 .find(|header| header.id.0 == id.0)
243 .map(|header| &*header.name);
244 let bmp_id = bmp_name.and_then(|name| self.get_bmp_id_for_name(name));
245 let event = ChartEvent::BgaChange {
246 layer: BgaLayer::Poor,
247 bmp_id,
248 };
249 events_map.entry(y_coord).or_default().push(event);
250 }
251
252 if let Some(lines) = &self.bmson.lines {
254 for bar_line in lines {
255 let y = self.pulses_to_y(bar_line.y.0);
256 let y_coord = YCoordinate::from(y);
257 let event = ChartEvent::BarLine;
258 events_map.entry(y_coord).or_default().push(event);
259 }
260 } else {
261 self.generate_auto_barlines(&mut events_map);
263 }
264
265 for MineChannel { name, notes } in &self.bmson.mine_channels {
267 for MineEvent { x, y, .. } in notes {
268 let yy = self.pulses_to_y(y.0);
269 let y_coord = YCoordinate::from(yy);
270 let Some((side, key)) = Self::lane_from_x(*x) else {
271 continue;
272 };
273 let wav_id = self.get_wav_id_for_name(name);
274 let event = ChartEvent::Note {
275 side,
276 key,
277 kind: NoteKind::Landmine,
278 wav_id,
279 length: None,
280 continue_play: false,
281 };
282 events_map.entry(y_coord).or_default().push(event);
283 }
284 }
285
286 for KeyChannel { name, notes } in &self.bmson.key_channels {
288 for KeyEvent { x, y, .. } in notes {
289 let yy = self.pulses_to_y(y.0);
290 let y_coord = YCoordinate::from(yy);
291 let Some((side, key)) = Self::lane_from_x(*x) else {
292 continue;
293 };
294 let wav_id = self.get_wav_id_for_name(name);
295 let event = ChartEvent::Note {
296 side,
297 key,
298 kind: NoteKind::Invisible,
299 wav_id,
300 length: None,
301 continue_play: false,
302 };
303 events_map.entry(y_coord).or_default().push(event);
304 }
305 }
306
307 self.all_events = events_map;
308 }
309
310 fn pulses_to_y(&self, pulses: u64) -> Decimal {
312 let denom = Decimal::from(4 * self.bmson.info.resolution.get());
313 if denom == Decimal::from(0) {
314 Decimal::from(0)
315 } else {
316 Decimal::from(pulses) / denom
317 }
318 }
319
320 fn generate_auto_barlines(&self, events_map: &mut BTreeMap<YCoordinate, Vec<ChartEvent>>) {
322 let max_y = events_map
324 .keys()
325 .map(|y_coord| y_coord.value())
326 .max()
327 .cloned()
328 .unwrap_or_else(|| Decimal::from(0));
329
330 if max_y <= Decimal::from(0) {
331 return;
332 }
333
334 let mut current_y = Decimal::from(0);
336 while current_y <= max_y {
337 let y_coord = YCoordinate::from(current_y.clone());
338 let event = ChartEvent::BarLine;
339 events_map.entry(y_coord).or_default().push(event);
340 current_y += Decimal::from(1);
341 }
342 }
343
344 fn get_wav_id_for_name(&self, name: &str) -> Option<WavId> {
346 self.audio_name_to_id.get(name).copied()
347 }
348
349 fn get_bmp_id_for_name(&self, name: &str) -> Option<BmpId> {
351 self.bmp_name_to_id.get(name).copied()
352 }
353
354 fn current_velocity(&self) -> Decimal {
359 let base_bpm = Decimal::from(120);
360 if self.current_bpm.is_sign_negative() {
361 Decimal::from(0)
362 } else {
363 (self.current_bpm.clone() / base_bpm).max(Decimal::from(0))
364 }
365 }
366
367 fn next_flow_event_after(&self, y_from_exclusive: Decimal) -> Option<(Decimal, FlowEvent)> {
369 let mut best: Option<(Decimal, FlowEvent)> = None;
370
371 for ev in &self.bmson.bpm_events {
372 let y = self.pulses_to_y(ev.y.0);
373 if y > y_from_exclusive {
374 best = min_by_y_decimal(best, (y, FlowEvent::Bpm(ev.bpm.as_f64().into())));
375 }
376 }
377 for ScrollEvent { y, rate } in &self.bmson.scroll_events {
378 let y = self.pulses_to_y(y.0);
379 if y > y_from_exclusive {
380 best = min_by_y_decimal(best, (y, FlowEvent::Scroll(rate.as_f64().into())));
381 }
382 }
383 best
384 }
385
386 fn step_to(&mut self, now: SystemTime) {
387 let Some(started) = self.started_at else {
388 return;
389 };
390 let last = self.last_poll_at.unwrap_or(started);
391 if now.duration_since(last).unwrap_or_default().is_zero() {
392 return;
393 }
394
395 let mut remaining_secs =
396 Decimal::from(now.duration_since(last).unwrap_or_default().as_secs_f64());
397 let mut cur_vel = self.current_velocity();
398 let mut cur_y = self.progressed_y.clone();
399 loop {
400 let next_event = self.next_flow_event_after(cur_y.clone());
401 if next_event.is_none()
402 || cur_vel == Decimal::from(0)
403 || remaining_secs == Decimal::from(0)
404 {
405 cur_y += cur_vel * remaining_secs;
406 break;
407 }
408 let (event_y, evt) = next_event.unwrap();
409 if event_y.clone() <= cur_y.clone() {
410 self.apply_flow_event(evt);
411 cur_vel = self.current_velocity();
412 continue;
413 }
414 let distance = event_y.clone() - cur_y.clone();
415 if cur_vel > Decimal::from(0) {
416 let time_to_event_secs = distance / cur_vel.clone();
417 if time_to_event_secs <= remaining_secs {
418 cur_y = event_y;
419 remaining_secs -= time_to_event_secs;
420 self.apply_flow_event(evt);
421 cur_vel = self.current_velocity();
422 continue;
423 }
424 }
425 cur_y += cur_vel * remaining_secs;
426 break;
427 }
428
429 self.progressed_y = cur_y;
430 self.last_poll_at = Some(now);
431 }
432
433 fn apply_flow_event(&mut self, evt: FlowEvent) {
434 match evt {
435 FlowEvent::Bpm(bpm) => self.current_bpm = Decimal::from(bpm),
436 FlowEvent::Scroll(s) => self.current_scroll = Decimal::from(s),
437 }
438 }
439
440 fn visible_window_y(&self) -> Decimal {
441 let reaction_time_seconds = Decimal::from_str("0.6").unwrap(); let base_bpm = Decimal::from(120);
445 (self.current_bpm.clone() / base_bpm) * reaction_time_seconds
446 }
447
448 fn lane_from_x(x: Option<std::num::NonZeroU8>) -> Option<(PlayerSide, Key)> {
449 let lane_value = x.map_or(0, |l| l.get());
450 let (adjusted_lane, side) = if lane_value > 8 {
451 (lane_value - 8, PlayerSide::Player2)
452 } else {
453 (lane_value, PlayerSide::Player1)
454 };
455 let key = match adjusted_lane {
456 1..=7 => Key::Key(adjusted_lane),
457 8 => Key::Scratch(1),
458 _ => return None,
459 };
460 Some((side, key))
461 }
462}
463
464impl<'a> ChartProcessor for BmsonProcessor<'a> {
465 fn audio_files(&self) -> HashMap<WavId, &Path> {
466 self.audio_name_to_id
469 .iter()
470 .map(|(name, id)| (*id, Path::new(name)))
471 .collect()
472 }
473
474 fn bmp_files(&self) -> HashMap<BmpId, &Path> {
475 self.bmp_name_to_id
478 .iter()
479 .map(|(name, id)| (*id, Path::new(name)))
480 .collect()
481 }
482
483 fn default_visible_y_length(&self) -> YCoordinate {
484 self.default_visible_y_length.clone()
485 }
486
487 fn current_bpm(&self) -> Decimal {
488 self.current_bpm.clone()
489 }
490 fn current_speed(&self) -> Decimal {
491 Decimal::from(1)
492 }
493 fn current_scroll(&self) -> Decimal {
494 self.current_scroll.clone()
495 }
496
497 fn start_play(&mut self, now: SystemTime) {
498 self.started_at = Some(now);
499 self.last_poll_at = Some(now);
500 self.progressed_y = Decimal::from(0);
501 self.preloaded_events.clear();
502 self.current_bpm = self.bmson.info.init_bpm.as_f64().into();
503 }
504
505 fn update(&mut self, now: SystemTime) -> impl Iterator<Item = ChartEventWithPosition> {
506 let incoming = std::mem::take(&mut self.inbox);
507 for evt in &incoming {
508 match evt {
509 ControlEvent::SetDefaultVisibleYLength { length } => {
510 self.default_visible_y_length = length.clone();
511 }
512 }
513 }
514
515 let prev_y = self.progressed_y.clone();
516 self.step_to(now);
517 let cur_y = self.progressed_y.clone();
518
519 let visible_y_length = self.visible_window_y();
521 let preload_end_y = cur_y.clone() + visible_y_length;
522
523 let mut triggered_events: Vec<ChartEventWithPosition> = Vec::new();
525
526 let mut new_preloaded_events: Vec<ChartEventWithPosition> = Vec::new();
528
529 for (y_coord, events) in &self.all_events {
531 let y_value = y_coord.value();
532
533 if *y_value > prev_y && *y_value <= cur_y {
535 for event in events {
536 triggered_events
537 .push(ChartEventWithPosition::new(y_coord.clone(), event.clone()));
538 }
539 }
540
541 if *y_value > cur_y && *y_value <= preload_end_y {
543 for event in events {
544 new_preloaded_events
545 .push(ChartEventWithPosition::new(y_coord.clone(), event.clone()));
546 }
547 }
548 }
549
550 self.preloaded_events = new_preloaded_events;
552
553 triggered_events.into_iter()
554 }
555
556 fn post_events(&mut self, events: &[ControlEvent]) {
557 self.inbox.extend_from_slice(events);
558 }
559
560 fn visible_events(&mut self, now: SystemTime) -> impl Iterator<Item = VisibleEvent> {
561 self.step_to(now);
562 let current_y = self.progressed_y.clone();
563 let visible_window_y = self.visible_window_y();
564 let scroll_factor = self.current_scroll.clone();
565
566 self.preloaded_events.iter().map(move |event_with_pos| {
567 let event_y = event_with_pos.position().value();
568 let display_ratio_value = if visible_window_y > Decimal::from(0) {
571 ((event_y.clone() - current_y.clone()) / visible_window_y.clone())
572 * scroll_factor.clone()
573 } else {
574 Decimal::from(0)
575 };
576 let display_ratio = DisplayRatio::from(display_ratio_value);
577 VisibleEvent::new(
578 event_with_pos.position().clone(),
579 event_with_pos.event().clone(),
580 display_ratio,
581 )
582 })
583 }
584}
585
586#[derive(Debug, Clone)]
587enum FlowEvent {
588 Bpm(Decimal),
589 Scroll(Decimal),
590}
591
592fn min_by_y_decimal(
593 best: Option<(Decimal, FlowEvent)>,
594 candidate: (Decimal, FlowEvent),
595) -> Option<(Decimal, FlowEvent)> {
596 match best {
597 None => Some(candidate),
598 Some((y, _)) if candidate.0 < y => Some(candidate),
599 Some(o) => Some(o),
600 }
601}