bms_rs/bms/
command.rs

1//! Definitions of command argument data.
2//!
3//! Structures in this module can be used in [Lex] part, [Parse] part, and the output models.
4
5pub mod channel;
6pub mod graphics;
7pub mod mixin;
8pub mod time;
9
10/// Minor command types and utilities.
11///
12/// This module contains types and utilities for minor BMS commands that are only available
13/// when the `minor-command` feature is enabled.
14pub mod minor_command;
15
16/// A play style of the score.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub enum PlayerMode {
20    /// For single play, a player uses 5 or 7 keys.
21    Single,
22    /// For couple play, two players use each 5 or 7 keys.
23    Two,
24    /// For double play, a player uses 10 or 14 keys.
25    Double,
26}
27
28/// A rank to determine judge level, but treatment differs among the BMS players.
29///
30/// IIDX/LR2/beatoraja judge windows: <https://iidx.org/misc/iidx_lr2_beatoraja_diff>
31///
32/// Note: The difficulty `VeryEasy` is decided to be unimplemented.
33/// See [discussions in the PR](https://github.com/MikuroXina/bms-rs/pull/122).
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum JudgeLevel {
37    /// Rank 0, the most difficult rank.
38    VeryHard,
39    /// Rank 1, the harder rank.
40    Hard,
41    /// Rank 2, the normal rank.
42    Normal,
43    /// Rank 3, the easier rank.
44    Easy,
45    /// Other integer value. Please See `JudgeLevel` for more details.
46    /// If used for `#EXRANK`, representing percentage.
47    OtherInt(i64),
48}
49
50impl From<i64> for JudgeLevel {
51    fn from(value: i64) -> Self {
52        match value {
53            0 => Self::VeryHard,
54            1 => Self::Hard,
55            2 => Self::Normal,
56            3 => Self::Easy,
57            val => Self::OtherInt(val),
58        }
59    }
60}
61
62impl<'a> TryFrom<&'a str> for JudgeLevel {
63    type Error = &'a str;
64    fn try_from(value: &'a str) -> core::result::Result<Self, Self::Error> {
65        value.parse::<i64>().map(Self::from).map_err(|_| value)
66    }
67}
68
69impl std::fmt::Display for JudgeLevel {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        match self {
72            Self::VeryHard => write!(f, "0"),
73            Self::Hard => write!(f, "1"),
74            Self::Normal => write!(f, "2"),
75            Self::Easy => write!(f, "3"),
76            Self::OtherInt(value) => write!(f, "{value}"),
77        }
78    }
79}
80
81pub(crate) const fn char_to_base62(ch: char) -> Option<u8> {
82    match ch {
83        '0'..='9' | 'A'..='Z' | 'a'..='z' => Some(ch as u32 as u8),
84        _ => None,
85    }
86}
87
88pub(crate) fn base62_to_byte(base62: u8) -> u8 {
89    match base62 {
90        b'0'..=b'9' => base62 - b'0',
91        b'A'..=b'Z' => base62 - b'A' + 10,
92        b'a'..=b'z' => base62 - b'a' + 36,
93        _ => unreachable!(),
94    }
95}
96
97#[test]
98fn test_base62() {
99    assert_eq!(char_to_base62('/'), None);
100    assert_eq!(char_to_base62('0'), Some(b'0'));
101    assert_eq!(char_to_base62('9'), Some(b'9'));
102    assert_eq!(char_to_base62(':'), None);
103    assert_eq!(char_to_base62('@'), None);
104    assert_eq!(char_to_base62('A'), Some(b'A'));
105    assert_eq!(char_to_base62('Z'), Some(b'Z'));
106    assert_eq!(char_to_base62('['), None);
107    assert_eq!(char_to_base62('`'), None);
108    assert_eq!(char_to_base62('a'), Some(b'a'));
109    assert_eq!(char_to_base62('z'), Some(b'z'));
110    assert_eq!(char_to_base62('{'), None);
111}
112
113/// An object id. Its meaning is determined by the channel belonged to.
114///
115/// The representation is 2 digits of ASCII characters.
116#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118pub struct ObjId([u8; 2]);
119
120impl std::fmt::Debug for ObjId {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        f.debug_tuple("ObjId")
123            .field(&format!("{}{}", self.0[0] as char, self.0[1] as char))
124            .finish()
125    }
126}
127
128impl std::fmt::Display for ObjId {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        write!(f, "{}{}", self.0[0] as char, self.0[1] as char)
131    }
132}
133
134impl TryFrom<[char; 2]> for ObjId {
135    type Error = [char; 2];
136    fn try_from(value: [char; 2]) -> core::result::Result<Self, Self::Error> {
137        Ok(Self([
138            char_to_base62(value[0]).ok_or(value)?,
139            char_to_base62(value[1]).ok_or(value)?,
140        ]))
141    }
142}
143
144impl TryFrom<[u8; 2]> for ObjId {
145    type Error = [u8; 2];
146    fn try_from(value: [u8; 2]) -> core::result::Result<Self, Self::Error> {
147        <Self as TryFrom<[char; 2]>>::try_from([value[0] as char, value[1] as char])
148            .map_err(|_| value)
149    }
150}
151
152impl<'a> TryFrom<&'a str> for ObjId {
153    type Error = &'a str;
154    fn try_from(value: &'a str) -> core::result::Result<Self, Self::Error> {
155        if value.len() != 2 {
156            return Err(value);
157        }
158        let mut chars = value.bytes();
159        let [Some(ch1), Some(ch2), None] = [chars.next(), chars.next(), chars.next()] else {
160            return Err(value);
161        };
162        Self::try_from([ch1, ch2]).map_err(|_| value)
163    }
164}
165
166impl From<ObjId> for u16 {
167    fn from(value: ObjId) -> Self {
168        base62_to_byte(value.0[0]) as u16 * 62 + base62_to_byte(value.0[1]) as u16
169    }
170}
171
172impl From<ObjId> for u32 {
173    fn from(value: ObjId) -> Self {
174        Into::<u16>::into(value) as u32
175    }
176}
177
178impl From<ObjId> for u64 {
179    fn from(value: ObjId) -> Self {
180        Into::<u16>::into(value) as u64
181    }
182}
183
184impl ObjId {
185    /// Instances a special null id, which means the rest object.
186    #[must_use]
187    pub const fn null() -> Self {
188        Self([0, 0])
189    }
190
191    /// Returns whether the id is `00`.
192    #[must_use]
193    pub fn is_null(self) -> bool {
194        self.0 == [0, 0]
195    }
196
197    /// Converts the object id into an `u16` value.
198    #[must_use]
199    pub fn as_u16(self) -> u16 {
200        self.into()
201    }
202
203    /// Converts the object id into an `u32` value.
204    #[must_use]
205    pub fn as_u32(self) -> u32 {
206        self.into()
207    }
208
209    /// Converts the object id into an `u64` value.
210    #[must_use]
211    pub fn as_u64(self) -> u64 {
212        self.into()
213    }
214
215    /// Makes the object id uppercase.
216    pub const fn make_uppercase(&mut self) {
217        self.0[0] = self.0[0].to_ascii_uppercase();
218        self.0[1] = self.0[1].to_ascii_uppercase();
219    }
220}
221
222/// A play volume of the sound in the score. Defaults to 100.
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225pub struct Volume {
226    /// A play volume percentage of the sound.
227    pub relative_percent: u8,
228}
229
230impl Default for Volume {
231    fn default() -> Self {
232        Self {
233            relative_percent: 100,
234        }
235    }
236}
237
238/// A POOR BGA display mode.
239#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
240#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
241pub enum PoorMode {
242    /// To hide the normal BGA and display the POOR BGA.
243    #[default]
244    Interrupt,
245    /// To overlap the POOR BGA onto the normal BGA.
246    Overlay,
247    /// Not to display the POOR BGA.
248    Hidden,
249}
250
251/// A notation type about LN in the score. But you don't have to take care of how the notes are actually placed in.
252#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
254pub enum LnType {
255    /// The RDM type.
256    #[default]
257    Rdm,
258    /// The MGQ type.
259    Mgq,
260}
261
262/// Long Note Mode Type
263#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
265#[repr(u8)]
266pub enum LnMode {
267    /// Normal Long Note, no tail judge (LN)
268    #[default]
269    Ln = 1,
270    /// IIDX Classic Long Note, with tail judge (CN)
271    Cn = 2,
272    /// IIDX Hell Long Note, with tail judge. holding add gurge, un-holding lose gurge (HCN)
273    Hcn = 3,
274}
275
276impl From<LnMode> for u8 {
277    fn from(mode: LnMode) -> u8 {
278        match mode {
279            LnMode::Ln => 1,
280            LnMode::Cn => 2,
281            LnMode::Hcn => 3,
282        }
283    }
284}
285
286impl TryFrom<u8> for LnMode {
287    type Error = u8;
288    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
289        Ok(match value {
290            1 => Self::Ln,
291            2 => Self::Cn,
292            3 => Self::Hcn,
293            _ => return Err(value),
294        })
295    }
296}