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}