1use std::collections::{BTreeMap, HashMap};
4use std::num::NonZeroU64;
5use std::path::Path;
6use std::time::SystemTime;
7
8use crate::bms::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 BmsProcessor {
17 bms: Bms,
18
19 started_at: Option<SystemTime>,
21 last_poll_at: Option<SystemTime>,
22 progressed_y: Decimal,
24
25 inbox: Vec<ControlEvent>,
27
28 all_events: BTreeMap<YCoordinate, Vec<ChartEvent>>,
30
31 preloaded_events: Vec<ChartEventWithPosition>,
33
34 default_visible_y_length: YCoordinate,
36 current_bpm: Decimal,
37 current_speed: Decimal,
38 current_scroll: Decimal,
39}
40
41impl BmsProcessor {
42 #[must_use]
44 pub fn new<T: KeyLayoutMapper>(bms: Bms) -> Self {
45 let init_bpm = bms
47 .bpm
48 .bpm
49 .as_ref()
50 .cloned()
51 .unwrap_or_else(|| Decimal::from(120));
52
53 let reaction_time_seconds = Decimal::from_str("0.6").unwrap(); let base_bpm = Decimal::from(120);
58 let visible_y_length = (init_bpm.clone() / base_bpm) * reaction_time_seconds;
59
60 let all_events = Self::precompute_all_events::<T>(&bms);
61
62 Self {
63 bms,
64 started_at: None,
65 last_poll_at: None,
66 progressed_y: Decimal::from(0),
67 inbox: Vec::new(),
68 all_events,
69 preloaded_events: Vec::new(),
70 default_visible_y_length: YCoordinate::from(visible_y_length),
71 current_bpm: init_bpm,
72 current_speed: Decimal::from(1),
73 current_scroll: Decimal::from(1),
74 }
75 }
76
77 fn precompute_all_events<T: KeyLayoutMapper>(
80 bms: &Bms,
81 ) -> BTreeMap<YCoordinate, Vec<ChartEvent>> {
82 let mut events_map: BTreeMap<YCoordinate, Vec<ChartEvent>> = BTreeMap::new();
83
84 for obj in bms.notes().all_notes() {
86 let y = Self::y_of_time_static(bms, obj.offset, &bms.speed.speed_factor_changes);
87 let event = Self::event_for_note_static::<T>(bms, obj, y.clone());
88
89 events_map
90 .entry(YCoordinate::from(y))
91 .or_default()
92 .push(event);
93 }
94
95 for change in bms.bpm.bpm_changes.values() {
97 let y = Self::y_of_time_static(bms, change.time, &bms.speed.speed_factor_changes);
98 let event = ChartEvent::BpmChange {
99 bpm: change.bpm.clone(),
100 };
101
102 events_map
103 .entry(YCoordinate::from(y))
104 .or_default()
105 .push(event);
106 }
107
108 for change in bms.scroll.scrolling_factor_changes.values() {
110 let y = Self::y_of_time_static(bms, change.time, &bms.speed.speed_factor_changes);
111 let event = ChartEvent::ScrollChange {
112 factor: change.factor.clone(),
113 };
114
115 events_map
116 .entry(YCoordinate::from(y))
117 .or_default()
118 .push(event);
119 }
120
121 for change in bms.speed.speed_factor_changes.values() {
123 let y = Self::y_of_time_static(bms, change.time, &bms.speed.speed_factor_changes);
124 let event = ChartEvent::SpeedChange {
125 factor: change.factor.clone(),
126 };
127
128 events_map
129 .entry(YCoordinate::from(y))
130 .or_default()
131 .push(event);
132 }
133
134 for stop in bms.stop.stops.values() {
136 let y = Self::y_of_time_static(bms, stop.time, &bms.speed.speed_factor_changes);
137 let event = ChartEvent::Stop {
138 duration: stop.duration.clone(),
139 };
140
141 events_map
142 .entry(YCoordinate::from(y))
143 .or_default()
144 .push(event);
145 }
146
147 for bga_obj in bms.bmp.bga_changes.values() {
149 let y = Self::y_of_time_static(bms, bga_obj.time, &bms.speed.speed_factor_changes);
150 let bmp_index = bga_obj.id.as_u16() as usize;
151 let event = ChartEvent::BgaChange {
152 layer: bga_obj.layer,
153 bmp_id: Some(BmpId::from(bmp_index)),
154 };
155
156 events_map
157 .entry(YCoordinate::from(y))
158 .or_default()
159 .push(event);
160 }
161
162 for (layer, opacity_changes) in &bms.bmp.bga_opacity_changes {
165 for opacity_obj in opacity_changes.values() {
166 let y =
167 Self::y_of_time_static(bms, opacity_obj.time, &bms.speed.speed_factor_changes);
168 let event = ChartEvent::BgaOpacityChange {
169 layer: *layer,
170 opacity: opacity_obj.opacity,
171 };
172
173 events_map
174 .entry(YCoordinate::from(y))
175 .or_default()
176 .push(event);
177 }
178 }
179
180 for (layer, argb_changes) in &bms.bmp.bga_argb_changes {
183 for argb_obj in argb_changes.values() {
184 let y = Self::y_of_time_static(bms, argb_obj.time, &bms.speed.speed_factor_changes);
185 let argb = ((argb_obj.argb.alpha as u32) << 24)
186 | ((argb_obj.argb.red as u32) << 16)
187 | ((argb_obj.argb.green as u32) << 8)
188 | (argb_obj.argb.blue as u32);
189 let event = ChartEvent::BgaArgbChange {
190 layer: *layer,
191 argb,
192 };
193
194 events_map
195 .entry(YCoordinate::from(y))
196 .or_default()
197 .push(event);
198 }
199 }
200
201 for bgm_volume_obj in bms.volume.bgm_volume_changes.values() {
203 let y =
204 Self::y_of_time_static(bms, bgm_volume_obj.time, &bms.speed.speed_factor_changes);
205 let event = ChartEvent::BgmVolumeChange {
206 volume: bgm_volume_obj.volume,
207 };
208
209 events_map
210 .entry(YCoordinate::from(y))
211 .or_default()
212 .push(event);
213 }
214
215 for key_volume_obj in bms.volume.key_volume_changes.values() {
217 let y =
218 Self::y_of_time_static(bms, key_volume_obj.time, &bms.speed.speed_factor_changes);
219 let event = ChartEvent::KeyVolumeChange {
220 volume: key_volume_obj.volume,
221 };
222
223 events_map
224 .entry(YCoordinate::from(y))
225 .or_default()
226 .push(event);
227 }
228
229 for text_obj in bms.text.text_events.values() {
231 let y = Self::y_of_time_static(bms, text_obj.time, &bms.speed.speed_factor_changes);
232 let event = ChartEvent::TextDisplay {
233 text: text_obj.text.clone(),
234 };
235
236 events_map
237 .entry(YCoordinate::from(y))
238 .or_default()
239 .push(event);
240 }
241
242 for judge_obj in bms.judge.judge_events.values() {
244 let y = Self::y_of_time_static(bms, judge_obj.time, &bms.speed.speed_factor_changes);
245 let event = ChartEvent::JudgeLevelChange {
246 level: judge_obj.judge_level,
247 };
248
249 events_map
250 .entry(YCoordinate::from(y))
251 .or_default()
252 .push(event);
253 }
254
255 {
258 for seek_obj in bms.video.seek_events.values() {
260 let y = Self::y_of_time_static(bms, seek_obj.time, &bms.speed.speed_factor_changes);
261 let event = ChartEvent::VideoSeek {
262 seek_time: seek_obj.position.to_string().parse::<f64>().unwrap_or(0.0),
263 };
264
265 events_map
266 .entry(YCoordinate::from(y))
267 .or_default()
268 .push(event);
269 }
270
271 for bga_keybound_obj in bms.bmp.bga_keybound_events.values() {
273 let y = Self::y_of_time_static(
274 bms,
275 bga_keybound_obj.time,
276 &bms.speed.speed_factor_changes,
277 );
278 let event = ChartEvent::BgaKeybound {
279 event: bga_keybound_obj.event.clone(),
280 };
281
282 events_map
283 .entry(YCoordinate::from(y))
284 .or_default()
285 .push(event);
286 }
287
288 for option_obj in bms.option.option_events.values() {
290 let y =
291 Self::y_of_time_static(bms, option_obj.time, &bms.speed.speed_factor_changes);
292 let event = ChartEvent::OptionChange {
293 option: option_obj.option.clone(),
294 };
295
296 events_map
297 .entry(YCoordinate::from(y))
298 .or_default()
299 .push(event);
300 }
301 }
302
303 Self::generate_barlines_for_bms(bms, &mut events_map);
305
306 events_map
307 }
308
309 fn generate_barlines_for_bms(
311 bms: &Bms,
312 events_map: &mut BTreeMap<YCoordinate, Vec<ChartEvent>>,
313 ) {
314 let max_y = events_map
316 .keys()
317 .map(|y_coord| y_coord.value())
318 .max()
319 .cloned()
320 .unwrap_or_else(|| Decimal::from(0));
321
322 if max_y <= Decimal::from(0) {
323 return;
324 }
325
326 let last_obj_time = bms.last_obj_time().unwrap_or_else(|| {
328 ObjTime::new(
329 0,
330 0,
331 NonZeroU64::new(4).expect("4 should be a valid NonZeroU64"),
332 )
333 });
334
335 for track in 0..=last_obj_time.track().0 {
337 let track_y = Self::y_of_time_static(
338 bms,
339 ObjTime::new(
340 track,
341 0,
342 NonZeroU64::new(1).expect("1 should be a valid NonZeroU64"),
343 ),
344 &bms.speed.speed_factor_changes,
345 );
346
347 if track_y <= max_y {
348 let y_coord = YCoordinate::from(track_y);
349 let event = ChartEvent::BarLine;
350 events_map.entry(y_coord).or_default().push(event);
351 }
352 }
353 }
354
355 fn y_of_time_static(
358 bms: &Bms,
359 time: ObjTime,
360 speed_changes: &std::collections::BTreeMap<ObjTime, crate::bms::model::obj::SpeedObj>,
361 ) -> Decimal {
362 let mut y = Decimal::from(0);
363 for t in 0..time.track().0 {
365 let section_len = bms
366 .section_len
367 .section_len_changes
368 .get(&Track(t))
369 .map(|s| &s.length)
370 .cloned()
371 .unwrap_or_else(|| Decimal::from(1));
372 y += section_len;
373 }
374 let current_len = bms
376 .section_len
377 .section_len_changes
378 .get(&time.track())
379 .map(|s| &s.length)
380 .cloned()
381 .unwrap_or_else(|| Decimal::from(1));
382 if time.denominator().get() > 0 {
383 let fraction =
384 Decimal::from(time.numerator()) / Decimal::from(time.denominator().get());
385 y += current_len * fraction;
386 }
387
388 let mut current_speed_factor = Decimal::from(1);
390 for (change_time, change) in speed_changes {
391 if *change_time <= time {
392 current_speed_factor = change.factor.clone();
393 }
394 }
395
396 y * current_speed_factor
399 }
400
401 fn event_for_note_static<T: KeyLayoutMapper>(
403 bms: &Bms,
404 obj: &WavObj,
405 y: Decimal,
406 ) -> ChartEvent {
407 let Some((side, key, kind)) = Self::lane_of_channel_id::<T>(obj.channel_id) else {
408 let wav_id = Some(WavId::from(obj.wav_id.as_u16() as usize));
409 return ChartEvent::Bgm { wav_id };
410 };
411 let wav_id = Some(WavId::from(obj.wav_id.as_u16() as usize));
412 let length = (kind == NoteKind::Long)
413 .then(|| {
414 bms.notes()
416 .next_obj_by_key(obj.channel_id, obj.offset)
417 .map(|next_obj| {
418 let next_y = Self::y_of_time_static(
419 bms,
420 next_obj.offset,
421 &bms.speed.speed_factor_changes,
422 );
423 YCoordinate::from(next_y - y.clone())
424 })
425 })
426 .flatten();
427 ChartEvent::Note {
428 side,
429 key,
430 kind,
431 wav_id,
432 length,
433 continue_play: false, }
435 }
436
437 fn section_length_of(&self, track: Track) -> Decimal {
439 self.bms
440 .section_len
441 .section_len_changes
442 .get(&track)
443 .map(|s| s.length.clone())
444 .unwrap_or_else(|| Decimal::from(1))
445 }
446
447 fn y_of_time(&self, time: ObjTime) -> Decimal {
449 let mut y = Decimal::from(0);
450 for t in 0..time.track().0 {
452 y += self.section_length_of(Track(t));
453 }
454 let current_len = self.section_length_of(time.track());
456 let fraction = if time.denominator().get() > 0 {
457 Decimal::from(time.numerator()) / Decimal::from(time.denominator().get())
458 } else {
459 Default::default()
460 };
461 y += current_len * fraction;
462 y
463 }
464
465 fn current_velocity(&self) -> Decimal {
469 let base_bpm = Decimal::from(120);
470 let velocity = if self.current_bpm > Decimal::from(0) {
471 let velocity = self.current_bpm.clone() / base_bpm;
472 let speed_factor = self.current_speed.clone();
473 velocity * speed_factor
474 } else {
475 Default::default()
476 };
477 velocity.max(Decimal::from(f64::EPSILON)) }
479
480 fn next_flow_event_after(&self, y_from_exclusive: Decimal) -> Option<(Decimal, FlowEvent)> {
482 let mut best: Option<(Decimal, FlowEvent)> = None;
484
485 for change in self.bms.bpm.bpm_changes.values() {
486 let y = self.y_of_time(change.time);
487 if y > y_from_exclusive {
488 let bpm = change.bpm.clone();
489 best = min_by_y_decimal(best, (y, FlowEvent::Bpm(bpm)));
490 }
491 }
492 for change in self.bms.scroll.scrolling_factor_changes.values() {
493 let y = self.y_of_time(change.time);
494 if y > y_from_exclusive {
495 let factor = change.factor.clone();
496 best = min_by_y_decimal(best, (y, FlowEvent::Scroll(factor)));
497 }
498 }
499 for change in self.bms.speed.speed_factor_changes.values() {
500 let y = self.y_of_time(change.time);
501 if y > y_from_exclusive {
502 let factor = change.factor.clone();
503 best = min_by_y_decimal(best, (y, FlowEvent::Speed(factor)));
504 }
505 }
506
507 best
508 }
509
510 fn step_to(&mut self, now: SystemTime) {
512 let Some(started) = self.started_at else {
513 return;
514 };
515 let last = self.last_poll_at.unwrap_or(started);
516 if now <= last {
517 return;
518 }
519
520 let mut remaining_secs =
521 Decimal::from(now.duration_since(last).unwrap_or_default().as_secs_f64());
522 let mut cur_vel = self.current_velocity();
523 let mut cur_y = self.progressed_y.clone();
524 loop {
526 let next_event = self.next_flow_event_after(cur_y.clone());
528 if next_event.is_none()
529 || cur_vel <= Decimal::from(0)
530 || remaining_secs <= Decimal::from(0)
531 {
532 cur_y += cur_vel * remaining_secs;
534 break;
535 }
536 let (event_y, evt) = next_event.unwrap();
537 if event_y.clone() <= cur_y.clone() {
538 self.apply_flow_event(evt);
540 cur_vel = self.current_velocity();
541 continue;
542 }
543 let distance = event_y.clone() - cur_y.clone();
545 if cur_vel > Decimal::from(0) {
546 let time_to_event_secs = distance / cur_vel.clone();
547 if time_to_event_secs <= remaining_secs {
548 cur_y = event_y;
550 remaining_secs -= time_to_event_secs;
551 self.apply_flow_event(evt);
552 cur_vel = self.current_velocity();
553 continue;
554 }
555 }
556 cur_y += cur_vel * remaining_secs;
558 break;
559 }
560
561 self.progressed_y = cur_y;
562 self.last_poll_at = Some(now);
563 }
564
565 fn apply_flow_event(&mut self, evt: FlowEvent) {
566 match evt {
567 FlowEvent::Bpm(bpm) => self.current_bpm = bpm,
568 FlowEvent::Speed(s) => self.current_speed = s,
569 FlowEvent::Scroll(s) => self.current_scroll = s,
570 }
571 }
572
573 fn visible_window_y(&self) -> Decimal {
575 let reaction_time_seconds = Decimal::from_str("0.6").unwrap(); let base_bpm = Decimal::from(120);
579 (self.current_bpm.clone() / base_bpm) * reaction_time_seconds
580 }
581
582 fn lane_of_channel_id<T: KeyLayoutMapper>(
583 channel_id: NoteChannelId,
584 ) -> Option<(PlayerSide, Key, NoteKind)> {
585 let map = channel_id.try_into_map::<T>()?;
586 let side = map.side();
587 let key = map.key();
588 let kind = map.kind();
589 Some((side, key, kind))
590 }
591}
592
593impl ChartProcessor for BmsProcessor {
594 fn audio_files(&self) -> HashMap<WavId, &Path> {
595 self.bms
596 .wav
597 .wav_files
598 .iter()
599 .map(|(obj_id, path)| (WavId::from(obj_id.as_u16() as usize), path.as_path()))
600 .collect()
601 }
602
603 fn bmp_files(&self) -> HashMap<BmpId, &Path> {
604 self.bms
605 .bmp
606 .bmp_files
607 .iter()
608 .map(|(obj_id, bmp)| (BmpId::from(obj_id.as_u16() as usize), bmp.file.as_path()))
609 .collect()
610 }
611
612 fn default_visible_y_length(&self) -> YCoordinate {
613 self.default_visible_y_length.clone()
614 }
615
616 fn current_bpm(&self) -> Decimal {
617 self.current_bpm.clone()
618 }
619 fn current_speed(&self) -> Decimal {
620 self.current_speed.clone()
621 }
622 fn current_scroll(&self) -> Decimal {
623 self.current_scroll.clone()
624 }
625
626 fn start_play(&mut self, now: SystemTime) {
627 self.started_at = Some(now);
628 self.last_poll_at = Some(now);
629 self.progressed_y = Decimal::from(0);
630 self.preloaded_events.clear();
631 self.current_bpm = self
633 .bms
634 .bpm
635 .bpm
636 .as_ref()
637 .cloned()
638 .unwrap_or_else(|| Decimal::from(120));
639 }
640
641 fn update(&mut self, now: SystemTime) -> impl Iterator<Item = ChartEventWithPosition> {
642 let incoming = std::mem::take(&mut self.inbox);
644 for evt in &incoming {
645 match evt {
646 ControlEvent::SetDefaultVisibleYLength { length } => {
647 self.default_visible_y_length = length.clone();
648 }
649 }
650 }
651
652 let prev_y = self.progressed_y.clone();
653 self.step_to(now);
654 let cur_y = self.progressed_y.clone();
655
656 let visible_y_length = self.visible_window_y();
658 let preload_end_y = cur_y.clone() + visible_y_length;
659
660 let mut triggered_events: Vec<ChartEventWithPosition> = Vec::new();
662
663 let mut new_preloaded_events: Vec<ChartEventWithPosition> = Vec::new();
665
666 for (y_coord, events) in &self.all_events {
668 let y = y_coord.value();
669
670 if *y > prev_y && *y <= cur_y {
672 for event in events {
673 triggered_events
674 .push(ChartEventWithPosition::new(y_coord.clone(), event.clone()));
675 }
676 }
677
678 if *y > cur_y && *y <= preload_end_y {
680 for event in events {
681 new_preloaded_events
682 .push(ChartEventWithPosition::new(y_coord.clone(), event.clone()));
683 }
684 }
685 }
686
687 triggered_events.sort_by(|a, b| {
689 a.position()
690 .value()
691 .partial_cmp(b.position().value())
692 .unwrap_or(std::cmp::Ordering::Equal)
693 });
694
695 new_preloaded_events.sort_by(|a, b| {
696 a.position()
697 .value()
698 .partial_cmp(b.position().value())
699 .unwrap_or(std::cmp::Ordering::Equal)
700 });
701
702 self.preloaded_events = new_preloaded_events;
704
705 triggered_events.into_iter()
706 }
707
708 fn post_events(&mut self, events: &[ControlEvent]) {
709 self.inbox.extend_from_slice(events);
710 }
711
712 fn visible_events(&mut self, now: SystemTime) -> impl Iterator<Item = VisibleEvent> {
713 self.step_to(now);
714 let current_y = self.progressed_y.clone();
715 let visible_window_y = self.visible_window_y();
716 let scroll_factor = self.current_scroll.clone();
717
718 self.preloaded_events.iter().map(move |event_with_pos| {
719 let event_y = event_with_pos.position().value();
720 let display_ratio_value = if visible_window_y > Decimal::from(0) {
723 ((event_y.clone() - current_y.clone()) / visible_window_y.clone())
724 * scroll_factor.clone()
725 } else {
726 Default::default()
727 };
728 let display_ratio = DisplayRatio::from(display_ratio_value);
729 VisibleEvent::new(
730 event_with_pos.position().clone(),
731 event_with_pos.event().clone(),
732 display_ratio,
733 )
734 })
735 }
736}
737
738#[derive(Debug, Clone)]
739enum FlowEvent {
740 Bpm(Decimal),
741 Speed(Decimal),
742 Scroll(Decimal),
743}
744
745fn min_by_y_decimal(
746 best: Option<(Decimal, FlowEvent)>,
747 candidate: (Decimal, FlowEvent),
748) -> Option<(Decimal, FlowEvent)> {
749 match best {
750 None => Some(candidate),
751 Some((y, _)) if candidate.0 < y => Some(candidate),
752 Some(other) => Some(other),
753 }
754}