bms_rs/chart_process/
types.rs

1//! Type definition module
2
3use crate::bms::Decimal;
4use crate::chart_process::ChartEvent;
5use fraction::{BigUint, GenericDecimal};
6use std::str::FromStr;
7
8/// Y coordinate wrapper type, using arbitrary precision decimal numbers.
9///
10/// 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)` to measure units.
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
12pub struct YCoordinate(pub Decimal);
13
14impl YCoordinate {
15    /// Create a new YCoordinate
16    #[must_use]
17    pub const fn new(value: Decimal) -> Self {
18        Self(value)
19    }
20
21    /// Get the internal Decimal value
22    #[must_use]
23    pub const fn value(&self) -> &Decimal {
24        &self.0
25    }
26
27    /// Convert to f64 (for compatibility)
28    #[must_use]
29    pub fn as_f64(&self) -> f64 {
30        self.0.to_string().parse::<f64>().unwrap_or(0.0)
31    }
32}
33
34impl From<Decimal> for YCoordinate {
35    fn from(value: Decimal) -> Self {
36        Self(value)
37    }
38}
39
40impl From<f64> for YCoordinate {
41    fn from(value: f64) -> Self {
42        // Convert f64 to string then parse as Decimal
43        let decimal_str = value.to_string();
44        let decimal = GenericDecimal::from_str(&decimal_str).unwrap_or_else(|_| {
45            // If parsing fails, use 0
46            GenericDecimal::from(BigUint::from(0u32))
47        });
48        Self(decimal)
49    }
50}
51
52impl std::ops::Add for YCoordinate {
53    type Output = Self;
54
55    fn add(self, rhs: Self) -> Self::Output {
56        Self(self.0 + rhs.0)
57    }
58}
59
60impl std::ops::Sub for YCoordinate {
61    type Output = Self;
62
63    fn sub(self, rhs: Self) -> Self::Output {
64        Self(self.0 - rhs.0)
65    }
66}
67
68impl std::ops::Mul for YCoordinate {
69    type Output = Self;
70
71    fn mul(self, rhs: Self) -> Self::Output {
72        Self(self.0 * rhs.0)
73    }
74}
75
76impl std::ops::Div for YCoordinate {
77    type Output = Self;
78
79    fn div(self, rhs: Self) -> Self::Output {
80        Self(self.0 / rhs.0)
81    }
82}
83
84/// Display ratio wrapper type, representing the actual position of a note in the display area.
85///
86/// 0 is the judgment line, 1 is the position where the note generally starts to appear.
87/// The value of this type is only affected by: current Y, Y visible range, and current Speed, Scroll values.
88#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
89pub struct DisplayRatio(pub Decimal);
90
91impl DisplayRatio {
92    /// Create a new DisplayRatio
93    #[must_use]
94    pub const fn new(value: Decimal) -> Self {
95        Self(value)
96    }
97
98    /// Get the internal Decimal value
99    #[must_use]
100    pub const fn value(&self) -> &Decimal {
101        &self.0
102    }
103
104    /// Convert to f64 (for compatibility)
105    #[must_use]
106    pub fn as_f64(&self) -> f64 {
107        self.0.to_string().parse::<f64>().unwrap_or(0.0)
108    }
109
110    /// Create a DisplayRatio representing the judgment line (value 0)
111    #[must_use]
112    pub fn at_judgment_line() -> Self {
113        Self(Decimal::from(0))
114    }
115
116    /// Create a DisplayRatio representing the position where note starts to appear (value 1)
117    #[must_use]
118    pub fn at_appearance() -> Self {
119        Self(Decimal::from(1))
120    }
121}
122
123impl From<Decimal> for DisplayRatio {
124    fn from(value: Decimal) -> Self {
125        Self(value)
126    }
127}
128
129impl From<f64> for DisplayRatio {
130    fn from(value: f64) -> Self {
131        // Convert f64 to string then parse as Decimal
132        let decimal_str = value.to_string();
133        let decimal = GenericDecimal::from_str(&decimal_str).unwrap_or_else(|_| {
134            // If parsing fails, use 0
135            GenericDecimal::from(BigUint::from(0u32))
136        });
137        Self(decimal)
138    }
139}
140
141/// WAV audio file ID wrapper type
142#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
143pub struct WavId(pub usize);
144
145impl WavId {
146    /// Create a new WavId
147    #[must_use]
148    pub const fn new(id: usize) -> Self {
149        Self(id)
150    }
151
152    /// Get the internal usize value
153    #[must_use]
154    pub const fn value(self) -> usize {
155        self.0
156    }
157}
158
159impl From<usize> for WavId {
160    fn from(value: usize) -> Self {
161        Self(value)
162    }
163}
164
165impl From<WavId> for usize {
166    fn from(id: WavId) -> Self {
167        id.0
168    }
169}
170
171/// BMP/BGA image file ID wrapper type
172#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
173pub struct BmpId(pub usize);
174
175impl BmpId {
176    /// Create a new BmpId
177    #[must_use]
178    pub const fn new(id: usize) -> Self {
179        Self(id)
180    }
181
182    /// Get the internal usize value
183    #[must_use]
184    pub const fn value(self) -> usize {
185        self.0
186    }
187}
188
189impl From<usize> for BmpId {
190    fn from(value: usize) -> Self {
191        Self(value)
192    }
193}
194
195impl From<BmpId> for usize {
196    fn from(id: BmpId) -> Self {
197        id.0
198    }
199}
200
201/// Timeline event and position wrapper type.
202///
203/// Represents an event in chart playback and its position on the timeline.
204#[derive(Debug, Clone)]
205pub struct ChartEventWithPosition {
206    /// Event position on timeline (y coordinate)
207    pub position: YCoordinate,
208    /// Chart event
209    pub event: ChartEvent,
210}
211
212impl ChartEventWithPosition {
213    /// Create a new ChartEventWithPosition
214    #[must_use]
215    pub const fn new(position: YCoordinate, event: ChartEvent) -> Self {
216        Self { position, event }
217    }
218
219    /// Get event position
220    #[must_use]
221    pub const fn position(&self) -> &YCoordinate {
222        &self.position
223    }
224
225    /// Get chart event
226    #[must_use]
227    pub const fn event(&self) -> &ChartEvent {
228        &self.event
229    }
230
231    /// Destructure into tuple
232    #[must_use]
233    pub fn into_tuple(self) -> (YCoordinate, ChartEvent) {
234        (self.position, self.event)
235    }
236}
237
238impl From<(YCoordinate, ChartEvent)> for ChartEventWithPosition {
239    fn from((position, event): (YCoordinate, ChartEvent)) -> Self {
240        Self::new(position, event)
241    }
242}
243
244impl From<ChartEventWithPosition> for (YCoordinate, ChartEvent) {
245    fn from(wrapper: ChartEventWithPosition) -> Self {
246        wrapper.into_tuple()
247    }
248}
249
250/// Visible area event and position and display ratio wrapper type.
251///
252/// Represents an event in the visible area, including its position, event content, and display ratio.
253#[derive(Debug, Clone)]
254pub struct VisibleEvent {
255    /// Event position on timeline (y coordinate)
256    pub position: YCoordinate,
257    /// Chart event
258    pub event: ChartEvent,
259    /// Display ratio
260    pub display_ratio: DisplayRatio,
261}
262
263impl VisibleEvent {
264    /// Create a new VisibleEvent
265    #[must_use]
266    pub const fn new(
267        position: YCoordinate,
268        event: ChartEvent,
269        display_ratio: DisplayRatio,
270    ) -> Self {
271        Self {
272            position,
273            event,
274            display_ratio,
275        }
276    }
277
278    /// Get event position
279    #[must_use]
280    pub const fn position(&self) -> &YCoordinate {
281        &self.position
282    }
283
284    /// Get chart event
285    #[must_use]
286    pub const fn event(&self) -> &ChartEvent {
287        &self.event
288    }
289
290    /// Get display ratio
291    #[must_use]
292    pub const fn display_ratio(&self) -> &DisplayRatio {
293        &self.display_ratio
294    }
295
296    /// Destructure into tuple
297    #[must_use]
298    pub fn into_tuple(self) -> (YCoordinate, ChartEvent, DisplayRatio) {
299        (self.position, self.event, self.display_ratio)
300    }
301}
302
303impl From<(YCoordinate, ChartEvent, DisplayRatio)> for VisibleEvent {
304    fn from((position, event, display_ratio): (YCoordinate, ChartEvent, DisplayRatio)) -> Self {
305        Self::new(position, event, display_ratio)
306    }
307}
308
309impl From<VisibleEvent> for (YCoordinate, ChartEvent, DisplayRatio) {
310    fn from(wrapper: VisibleEvent) -> Self {
311        wrapper.into_tuple()
312    }
313}