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}