bms_rs/chart_process.rs
1//! Chart Processor
2//!
3//! Unified Y coordinate definition:
4//! - In the default 4/4 time signature, the length of "one measure" is 1.
5//! - BMS: When the section length is the default value, each `Track` has a length of 1. The section length comes from the `#XXX02:V` message per measure, where `V` represents the multiple of the default length (e.g., `#00302:0.5` means the 3rd measure has half the default length). Cumulative y is linearly converted with this multiple.
6//! - BMSON: `info.resolution` is the number of pulses corresponding to a quarter note (1/4), so one measure length is `4 * resolution` pulses; all position y is normalized to measure units through `pulses / (4 * resolution)`.
7//! - Speed (default 1.0): Only affects display coordinates (e.g., `visible_notes` `distance_to_hit`), that is, scales the y difference proportionally; does not change time progression and BPM values, nor the actual duration of that measure.
8
9use crate::bms::{
10 Decimal,
11 prelude::{BgaLayer, Key, NoteKind, PlayerSide},
12};
13
14use crate::bms::prelude::SwBgaEvent;
15
16pub mod bms_processor;
17#[cfg(feature = "bmson")]
18pub mod bmson_processor;
19#[cfg(not(feature = "bmson"))]
20pub mod bmson_processor {}
21
22use std::{collections::HashMap, path::Path, time::SystemTime};
23
24// Type definition module
25pub mod types;
26
27// Prelude module
28pub mod prelude;
29
30// Use types from prelude
31pub use prelude::{BmpId, DisplayRatio, WavId, YCoordinate};
32
33// Use custom wrapper types
34pub use types::{ChartEventWithPosition, VisibleEvent};
35
36/// Events generated during playback (Elm style).
37///
38/// These events represent actual events during chart playback, such as note triggers, BGM playback,
39/// BPM changes, etc. Setting and control related events have been separated into [`ControlEvent`].
40///
41/// The effects of [`ChartEvent`] members on [`YCoordinate`] and [`DisplayRatio`] are calculated by the corresponding
42/// [`ChartProcessor`] implementation, so there's no need to recalculate them.
43#[derive(Debug, Clone)]
44pub enum ChartEvent {
45 /// Key note reaches judgment line (includes visible, long, mine, invisible notes, distinguished by `kind`)
46 Note {
47 /// Player side
48 side: PlayerSide,
49 /// Key position
50 key: Key,
51 /// Note type (`NoteKind`)
52 kind: NoteKind,
53 /// Corresponding sound resource ID (if any)
54 wav_id: Option<WavId>,
55 /// Note length (end position for long notes, None for regular notes)
56 length: Option<YCoordinate>,
57 /// Note continue play flag (fixed as false for BMS, based on Note.c field for BMSON)
58 continue_play: bool,
59 },
60 /// BGM and other non-key triggers (no valid side/key)
61 Bgm {
62 /// Corresponding sound resource ID (if any)
63 wav_id: Option<WavId>,
64 },
65 /// BPM change
66 BpmChange {
67 /// New BPM value (beats per minute)
68 bpm: Decimal,
69 },
70 /// Scroll factor change
71 ScrollChange {
72 /// Scroll factor (relative value)
73 factor: Decimal,
74 },
75 /// Speed factor change
76 SpeedChange {
77 /// Spacing factor (relative value)
78 factor: Decimal,
79 },
80 /// Stop scroll event
81 Stop {
82 /// Stop duration (BMS: converted from chart-defined time units; BMSON: pulse count)
83 duration: Decimal,
84 },
85 /// BGA (background animation) change event
86 ///
87 /// Triggered when playback position reaches BGA change time point, indicating the need to switch to the specified background image.
88 /// Supports multiple BGA layers: Base (base layer), Overlay (overlay layer), Overlay2 (second overlay layer), and Poor (displayed on failure).
89 BgaChange {
90 /// BGA layer
91 layer: BgaLayer,
92 /// BGA/BMP resource ID, get the corresponding file path through the `bmp_files()` method (if any)
93 bmp_id: Option<BmpId>,
94 },
95 /// BGA opacity change event (requires minor-command feature)
96 ///
97 /// Dynamically adjust the opacity of the specified BGA layer to achieve fade-in/fade-out effects.
98 BgaOpacityChange {
99 /// BGA layer
100 layer: BgaLayer,
101 /// Opacity value (0x01-0xFF, 0x01 means almost transparent, 0xFF means completely opaque)
102 opacity: u8,
103 },
104 /// BGA ARGB color change event (requires minor-command feature)
105 ///
106 /// Dynamically adjust the color of the specified BGA layer through ARGB values to achieve color filter effects.
107 BgaArgbChange {
108 /// BGA layer
109 layer: BgaLayer,
110 /// ARGB color value (format: 0xAARRGGBB)
111 argb: u32,
112 },
113 /// BGM volume change event
114 ///
115 /// Triggered when playback position reaches BGM volume change time point, used to adjust background music volume.
116 BgmVolumeChange {
117 /// Volume value (0x01-0xFF, 0x01 means minimum volume, 0xFF means maximum volume)
118 volume: u8,
119 },
120 /// KEY volume change event
121 ///
122 /// Triggered when playback position reaches KEY volume change time point, used to adjust key sound effect volume.
123 KeyVolumeChange {
124 /// Volume value (0x01-0xFF, 0x01 means minimum volume, 0xFF means maximum volume)
125 volume: u8,
126 },
127 /// Text display event
128 ///
129 /// Triggered when playback position reaches text display time point, used to display text information in the chart.
130 TextDisplay {
131 /// Text content to display
132 text: String,
133 },
134 /// Judge level change event
135 ///
136 /// Triggered when playback position reaches judge level change time point, used to adjust the strictness of the judgment window.
137 JudgeLevelChange {
138 /// Judge level (VeryHard, Hard, Normal, Easy, OtherInt)
139 level: crate::bms::command::JudgeLevel,
140 },
141 /// Video seek event (requires minor-command feature)
142 ///
143 /// Triggered when playback position reaches video seek time point, used for video playback control.
144 VideoSeek {
145 /// Seek time point (seconds)
146 seek_time: f64,
147 },
148 /// BGA key binding event (requires minor-command feature)
149 ///
150 /// Triggered when playback position reaches BGA key binding time point, used for BGA and key binding control.
151 BgaKeybound {
152 /// BGA key binding event type
153 event: SwBgaEvent,
154 },
155 /// Option change event (requires minor-command feature)
156 ///
157 /// Triggered when playback position reaches option change time point, used for dynamic game option adjustment.
158 OptionChange {
159 /// Option content
160 option: String,
161 },
162 /// Measure line event
163 ///
164 /// Triggered when playback position reaches measure line position, used for chart structure display.
165 BarLine,
166}
167
168/// Player control and setting events.
169///
170/// These events are used to control the player's configuration parameters, such as visible Y range.
171/// Separated from chart playback related events (such as notes, BGM, BPM changes, etc.) to provide a clearer API.
172#[derive(Debug, Clone)]
173pub enum ControlEvent {
174 /// Set: default visible Y range length
175 ///
176 /// The visible Y range length is the distance from when a note appears in the visible area to when it reaches the judgment line.
177 /// This length affects the visible window size calculation.
178 SetDefaultVisibleYLength {
179 /// Visible Y range length (y coordinate units, >0)
180 length: YCoordinate,
181 },
182}
183
184/// Unified y unit description: In default 4/4 time, one measure equals 1; BMS uses `#SECLEN` for linear conversion, BMSON normalizes via `pulses / (4*resolution)`.
185pub trait ChartProcessor {
186 /// Read: audio file resources (id to path mapping).
187 fn audio_files(&self) -> HashMap<WavId, &Path>;
188 /// Read: BGA/BMP image resources (id to path mapping).
189 fn bmp_files(&self) -> HashMap<BmpId, &Path>;
190
191 /// Read: default visible Y range length (distance from when note appears in visible area to judgment line, unit: y coordinate).
192 fn default_visible_y_length(&self) -> YCoordinate;
193
194 /// Read: current BPM (changes with events).
195 fn current_bpm(&self) -> Decimal;
196 /// Read: current Speed factor (changes with events).
197 fn current_speed(&self) -> Decimal;
198 /// Read: current Scroll factor (changes with events).
199 fn current_scroll(&self) -> Decimal;
200
201 /// Notify: start playback, record starting absolute time.
202 fn start_play(&mut self, now: SystemTime);
203
204 /// Update: advance internal timeline, return timeline events generated since last call (Elm style).
205 fn update(&mut self, now: SystemTime) -> impl Iterator<Item = ChartEventWithPosition>;
206
207 /// Post external control events (such as setting default reaction time/default BPM), will be consumed before next `update`.
208 ///
209 /// These events are used to dynamically adjust player configuration parameters. Chart playback related events (such as notes, BGM, etc.)
210 /// are returned by the [`update`] method, not posted through this method.
211 fn post_events(&mut self, events: &[ControlEvent]);
212
213 /// Query: all events in current visible area (preload logic).
214 fn visible_events(&mut self, now: SystemTime) -> impl Iterator<Item = VisibleEvent>;
215}