bms_rs/
bmson.rs

1//! The [bmson format](https://bmson-spec.readthedocs.io/en/master/doc/index.html) definition.
2//!
3//! # Order of Processing
4//!
5//! When there are coincident events in the same pulse, they are processed in the order below:
6//!
7//! - [`Note`] and [`BgaEvent`] (are independent each other),
8//! - [`BpmEvent`],
9//! - [`StopEvent`].
10//!
11//! If a [`BpmEvent`] and a [`StopEvent`] appear on the same pulse, the current BPM will be changed at first, then scrolling the chart will be stopped for a while depending the changed BPM.
12//!
13//! If a [`Note`] and a [`StopEvent`] appear on the same pulse, the sound will be played (or should be hit by a player), then scrolling the chart will be stopped.
14//!
15//! # Layered Notes
16//!
17//! In case that notes (not BGM) from different sound channels exist on the same (key and pulse) position:
18//!
19//! - When its length is not equal to each other, yo should treat as an error and warn to a player.
20//! - Otherwise your player may fusion the notes. That means when a player hit the key, two sounds will be played.
21//!
22//! # Differences from BMS
23//!
24//! - BMS can play different sound on the start and end of long note. But bmson does not allow this.
25//! - Transparent color on BGA is not supported. But you can use PNG files having RGBA channels.
26#![cfg(feature = "bmson")]
27#![cfg_attr(docsrs, doc(cfg(feature = "bmson")))]
28
29pub mod bms_to_bmson;
30pub mod bmson_to_bms;
31pub mod fin_f64;
32pub mod pulse;
33
34use std::num::NonZeroU8;
35
36use serde::{Deserialize, Deserializer, Serialize};
37
38use crate::bms::command::LnMode;
39
40use self::{fin_f64::FinF64, pulse::PulseNumber};
41
42/// Top-level object for bmson format.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct Bmson {
45    /// Version of bmson format, which should be compared using [Semantic Version 2.0.0](http://semver.org/spec/v2.0.0.html). Older bmson file may not have this field, but lacking this must be an error.
46    pub version: String,
47    /// Score metadata.
48    pub info: BmsonInfo,
49    /// Location of bar lines in pulses. If `None`, then a 4/4 beat is assumed and bar lines will be generates every 4 quarter notes. If `Some(vec![])`, this chart will not have any bar line.
50    ///
51    /// This format represents an irregular meter by bar lines.
52    pub lines: Option<Vec<BarLine>>,
53    /// Events of bpm change. If there are coincident events, the successor is only applied.
54    #[serde(default)]
55    pub bpm_events: Vec<BpmEvent>,
56    /// Events of scroll stop. If there are coincident events, they are happened in succession.
57    #[serde(default)]
58    pub stop_events: Vec<StopEvent>,
59    /// Note data.
60    pub sound_channels: Vec<SoundChannel>,
61    /// BGA data.
62    #[serde(default)]
63    pub bga: Bga,
64    /// Beatoraja implementation of scroll events.
65    #[serde(default)]
66    pub scroll_events: Vec<ScrollEvent>,
67    /// Beatoraja implementation of mine channel.
68    #[serde(default)]
69    pub mine_channels: Vec<MineChannel>,
70    /// Beatoraja implementation of invisible key channel.
71    #[serde(default)]
72    pub key_channels: Vec<KeyChannel>,
73}
74
75/// Header metadata of chart.
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct BmsonInfo {
78    /// Self explanatory title.
79    pub title: String,
80    /// Self explanatory subtitle. Usually this is shown as a smaller text than `title`.
81    #[serde(default)]
82    pub subtitle: String,
83    /// Author of the chart. It may multiple names such as `Alice vs Bob`, `Alice feat. Bob` and so on. But you should respect the value because it usually have special meaning.
84    pub artist: String,
85    /// Other authors of the chart. This is useful for indexing and searching.
86    ///
87    /// Value of the array has form of `key:value`. The `key` can be `music`, `vocal`, `chart`, `image`, `movie` or `other`. If it has no `key`, you should treat as that `key` equals to `other`. The value may contains the spaces before and after `key` and `value`, so you should trim them.
88    ///
89    /// # Example
90    ///
91    /// ```json
92    /// "subartists": ["music:5argon", "music:encX", "chart:flicknote", "movie:5argon", "image:5argon"]
93    /// ```
94    #[serde(default)]
95    pub subartists: Vec<String>,
96    /// Self explanatory genre.
97    pub genre: String,
98    /// Hint for layout lanes, e.g. "beat-7k", "popn-5k", "generic-nkeys". Defaults to `"beat-7k"`.
99    ///
100    /// If you want to support many lane modes of BMS, you should check this to determine the layout for lanes. Also you can check all lane information in `sound_channels` for strict implementation.
101    #[serde(default = "default_mode_hint")]
102    pub mode_hint: String,
103    /// Special chart name, e.g. "BEGINNER", "NORMAL", "HYPER", "FOUR DIMENSIONS".
104    #[serde(default)]
105    pub chart_name: String,
106    /// Self explanatory level number. It is usually set with subjective rating by the author.
107    pub level: u32,
108    /// Initial BPM.
109    pub init_bpm: FinF64,
110    /// Relative judge width in percentage. The variation amount may different by BMS player. Larger is easier.
111    #[serde(default = "default_percentage")]
112    pub judge_rank: FinF64,
113    /// Relative life bar gain in percentage. The variation amount may different by BMS player. Larger is easier.
114    #[serde(default = "default_percentage")]
115    pub total: FinF64,
116    /// Background image file name. This should be displayed during the game play.
117    #[serde(default)]
118    pub back_image: Option<String>,
119    /// Eyecatch image file name. This should be displayed during the chart is loading.
120    #[serde(default)]
121    pub eyecatch_image: Option<String>,
122    /// Title image file name. This should be displayed before the game starts instead of title of the music.
123    #[serde(default)]
124    pub title_image: Option<String>,
125    /// Banner image file name. This should be displayed in music select or result scene. The aspect ratio of image is usually 15:4.
126    #[serde(default)]
127    pub banner_image: Option<String>,
128    /// Preview music file name. This should be played when this chart is selected in a music select scene.
129    #[serde(default)]
130    pub preview_music: Option<String>,
131    /// Numbers of pulse per quarter note in 4/4 measure. You must check this because it affects the actual seconds of `PulseNumber`.
132    #[serde(default = "default_resolution")]
133    pub resolution: u64,
134    /// Beatoraja implementation of long note type.
135    #[serde(default)]
136    pub ln_type: LnMode,
137}
138
139/// Default mode hint, beatmania 7 keys.
140#[must_use]
141pub fn default_mode_hint() -> String {
142    "beat-7k".into()
143}
144
145/// Default relative percentage, 100%.
146#[must_use]
147pub fn default_percentage() -> FinF64 {
148    FinF64::new(100.0).unwrap_or_else(|| {
149        // This should never happen as 100.0 is a valid FinF64 value
150        panic!("Internal error: 100.0 is not a valid FinF64")
151    })
152}
153
154/// Default resolution pulses per quarter note in 4/4 measure, 240 pulses.
155#[must_use]
156pub const fn default_resolution() -> u64 {
157    240
158}
159
160/// Event of bar line of the chart.
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162pub struct BarLine {
163    /// Pulse number to place the line.
164    pub y: PulseNumber,
165}
166
167/// Note sound file and positions to be placed in the chart.
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169pub struct SoundChannel {
170    /// Sound file path. If the extension is not specified or not supported, you can try search files about other extensions for fallback.
171    ///
172    /// BMS players are expected to support the audio containers below:
173    ///
174    /// - WAV (`.wav`),
175    /// - OGG (`.ogg`),
176    /// - Audio-only MPEG-4 (`.m4a`).
177    ///
178    /// BMS players are expected to support the audio codec below:
179    ///
180    /// - LPCM (Linear Pulse-Code Modulation),
181    /// - Ogg Vorbis,
182    /// - AAC (Advanced Audio Coding).
183    pub name: String,
184    /// Data of note to be placed.
185    pub notes: Vec<Note>,
186}
187
188/// Sound note to ring a sound file.
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190pub struct Note {
191    /// Position to be placed.
192    pub y: PulseNumber,
193    /// Lane information. The `Some` number represents the key to play, otherwise it is not playable (BGM) note.
194    #[serde(deserialize_with = "deserialize_x_none_if_zero")]
195    pub x: Option<NonZeroU8>,
196    /// Length of pulses of the note. It will be a normal note if zero, otherwise a long note.
197    pub l: u64,
198    /// Continuation flag. It will continue to ring rest of the file when play if `true`, otherwise it will play from start.
199    pub c: bool,
200    /// Beatoraja implementation of long note type.
201    #[serde(default)]
202    pub t: Option<LnMode>,
203    /// Beatoraja implementation of long note up flag.
204    /// If it is true and configured at the end position of a long note, then this position will become the ending note of the long note.
205    #[serde(default)]
206    pub up: Option<bool>,
207}
208
209fn deserialize_x_none_if_zero<'de, D>(deserializer: D) -> Result<Option<NonZeroU8>, D::Error>
210where
211    D: Deserializer<'de>,
212{
213    let opt = Option::<u8>::deserialize(deserializer)?;
214    Ok(match opt {
215        Some(0) | None => None,
216        Some(v) => NonZeroU8::new(v),
217    })
218}
219
220/// BPM change note.
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub struct BpmEvent {
223    /// Position to change BPM of the chart.
224    pub y: PulseNumber,
225    /// New BPM to be.
226    pub bpm: FinF64,
227}
228
229/// Scroll stop note.
230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub struct StopEvent {
232    /// Start position to scroll stop.
233    pub y: PulseNumber,
234    /// Stopping duration in pulses.
235    pub duration: u64,
236}
237
238/// BGA data.
239#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
240pub struct Bga {
241    /// Pictures data for playing BGA.
242    #[serde(default)]
243    pub bga_header: Vec<BgaHeader>,
244    /// Base picture sequence.
245    #[serde(default)]
246    pub bga_events: Vec<BgaEvent>,
247    /// Layered picture sequence.
248    #[serde(default)]
249    pub layer_events: Vec<BgaEvent>,
250    /// Picture sequence displayed when missed.
251    #[serde(default)]
252    pub poor_events: Vec<BgaEvent>,
253}
254
255/// Picture file information.
256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
257pub struct BgaHeader {
258    /// Self explanatory ID of picture.
259    pub id: BgaId,
260    /// Picture file name.
261    pub name: String,
262}
263
264/// BGA note to display the picture.
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
266pub struct BgaEvent {
267    /// Position to display the picture in pulses.
268    pub y: PulseNumber,
269    /// ID of picture to display.
270    pub id: BgaId,
271}
272
273/// Picture id for [`Bga`].
274#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
275pub struct BgaId(pub u32);
276
277/// Beatoraja implementation of scroll event.
278#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
279pub struct ScrollEvent {
280    /// Position to scroll.
281    pub y: PulseNumber,
282    /// Scroll rate.
283    pub rate: FinF64,
284}
285
286/// Beatoraja implementation of mine channel.
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288pub struct MineEvent {
289    /// Lane information. The `Some` number represents the key to play, otherwise it is not playable (BGM) note.
290    #[serde(deserialize_with = "deserialize_x_none_if_zero")]
291    pub x: Option<NonZeroU8>,
292    /// Position to be placed.
293    pub y: PulseNumber,
294    /// Damage of the mine.
295    pub damage: FinF64,
296}
297
298/// Beatoraja implementation of mine channel.
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
300pub struct MineChannel {
301    /// Name of the mine sound file.
302    pub name: String,
303    /// Mine notes.
304    pub notes: Vec<MineEvent>,
305}
306
307/// Beatoraja implementation of invisible key event.
308#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
309pub struct KeyEvent {
310    /// Lane information. The `Some` number represents the key to play, otherwise it is not playable (BGM) note.
311    #[serde(deserialize_with = "deserialize_x_none_if_zero")]
312    pub x: Option<NonZeroU8>,
313    /// Position to be placed.
314    pub y: PulseNumber,
315}
316
317/// Beatoraja implementation of invisible key channel.
318#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
319pub struct KeyChannel {
320    /// Name of the key sound file.
321    pub name: String,
322    /// Invisible key notes.
323    pub notes: Vec<KeyEvent>,
324}