Slowly defining the BMS header elements

This commit is contained in:
rncwnd 2025-07-06 20:59:31 +01:00
parent dc0300b7da
commit 14092a9c7e
Signed by: rncwnd
GPG key ID: 05EF307E0712FDAA
6 changed files with 282 additions and 18 deletions

12
Cargo.lock generated
View file

@ -2518,6 +2518,17 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "katexit"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccfb0b7ce7938f84a5ecbdca5d0a991e46bc9d6d078934ad5e92c5270fe547db"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "khronos-egl"
version = "6.0.0"
@ -3235,6 +3246,7 @@ dependencies = [
name = "parser"
version = "0.1.0"
dependencies = [
"katexit",
"strum 0.27.1",
"strum_macros 0.27.1",
"winnow",

View file

@ -1,4 +1,4 @@
[workspace]
resolver = "3"
resolver = "2"
members = [ "parser","player"]

View file

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
katexit = "0.1.5"
strum = "0.27.1"
strum_macros = "0.27.1"
winnow = "0.7.11"

View file

@ -1,11 +1,13 @@
struct Header {
use strum_macros::FromRepr;
pub struct Header {
player: Player,
}
/// Defines the play side.
/// `#PLAYER [1-4]`. Defines the play side.
#[derive(FromRepr, Debug, PartialEq, Clone)]
#[repr(u8)]
enum Player {
pub enum Player {
One, // SP
Two, // Couple play
Three, // DP
@ -17,3 +19,256 @@ impl Default for Player {
Self::One
}
}
/// `#RANK [0-3]`. Defines the judge difficulty.
///
/// We follow LR2 convention here, so Rank is 0,1,2,3
pub enum Rank {
VeryHard, // RANK 0, +-8ms
Hard, // RANK 1, +- 15ms
Normal, // RANK 2, +- 18ms
Easy, // RANK 3, +- 21ms
}
// LR2 Convention is to apply Normal when unspecified.
impl Default for Rank {
fn default() -> Self {
Self::Normal
}
}
pub enum JudgeRankType {
/// `#RANK [0-3]` Normal rank system.
///
/// This is what you see 99% of the time.
Rank,
/// `#DEFEXRANK n`. Percentage judge.
/// Defexrank is very strange. It specifies judge difficulty as a percentage of RANK 2.
/// This means that 100 is equal to RANK 2. If a DefExRank of 199.97 is sepcified then
/// the rank is 199.97% of RANK 2.
///
/// TODO: Implement Defexrank
Defexrank(f32),
/// `#EXRANK[01-ZZ] n`. In-chart adjustable rank.
/// Exrank is also weird. It allows the timing window of the chart to be changed
/// during play.
///
/// To steal from hitkey as an example.
/// ```
/// #RANK 2
/// #EXRANKaa 48
/// #EXRANKcc 100
/// #114a0:aa0000cc
/// ```
/// Would result in the timing window for measure 114 changing to DEFEXRANK 48,
/// and then going back to DEFEXRANK 100 at the end of the measure.
///
/// The string represents the identifier (In the example "cc" or "aa")
/// and the f32 represents the rank as a percentage of rank 2.
Exrank(String, f32),
}
#[cfg_attr(doc, katexit::katexit)]
/// `#TOTAL n`. Rate of Gague Recovery ™
///
/// Total is a bit tricky to wrap your head around.
/// As far as the player is concerned, TOTAL is what defines the rate of gauge recovery.
///
/// Let n be the value of #TOTAL n, and y the total amount of objects in the chart
/// the rate of gauge recovery on a PGREAT is
/// $\frac{n}{y}$
///
/// If there are 400 objects to hit, and TOTAL is 200, we get $\frac{200}{400} = 0.5$
///
/// If this field is omitted, the spec does not specify what the default value should be.
/// - LR2 uses 160
/// - jbmsparser for beatoraja uses 100.
/// - We will use 160
pub struct Total(f64);
impl Default for Total {
fn default() -> Total {
Self(160.0)
}
}
/// `#VOLWAV n`. Flat volume multiplier.
///
/// Defaults to 100.
///
/// In general, this gets ignored.
///
/// #VOLWAV 250 would be playing at 250% volume.
///
/// #VOLWAV 25 would be playing at 25% volume.
pub struct Volwav(i32);
/// `#STAGEFILE imagefilename`. Splash screen.
///
/// This command is omissible. When omitted it is expected that the default splashscreen
/// will be used.
pub struct Stagefile(String);
/// `#BANNER imagefilename`. Song select banner image.
pub struct Banner(String);
/// `#BACKBMP imagefilename`. Static "movie" background.
///
/// If we chose to follow the OverActive style, then this is a pre-movie splash
/// like the song title, genre and such in IIDX.
/// https://right-stick.sub.jp/backbmp/index.html
pub struct BackBmp(String);
/// `#PLAYLEVEL n`. Song difficulty.
///
/// Reported difficulty level. This will usually be [1-12] IIDX style.
///
/// #PLAYLEVEL 0 is a strange case. This is usually for gimmick charts which
/// use commands like `#RANDOM` or `#SWITCH`
pub struct PlayLevel(u16);
/// For whatever reason, BM98 used #PLAYLEVEL 3 as it's default if this was
/// omitted. Apparently many followed this, even through it's not spec.
impl Default for PlayLevel {
fn default() -> Self {
PlayLevel(3)
}
}
/// `#DIFFICULTY [1-5]`. Difficulty. Normal/Hyper etc
///
/// We follow an adjusted IIDX naming convention in this enum.
/// Beginner, Normal, Hyper, Another are from IIDX, but we take INSANE from bms.
/// There's a lot of naiming conventions, these are disambiguated in code comments
///
/// This command is omissable, and anything which doesn't have it is expected to be
/// unsortable and unfilterable by this metric.
#[derive(FromRepr, Debug, PartialEq, Clone)]
#[repr(u8)]
enum Difficulty {
Beginner, // Easy/Beginner/Light
Normal, // Normal/Standard
Hyper, // Hard
Another, // EX, Maximum
Insane, // Kusofumen, 糞譜面, INSANE, 発狂, hakkyou, SUPER-CRAZY
}
/// `#TITLE string` Title of the track.
///
/// Unsurprisingly, defines the title of the track.
///
/// When omitted, it's not actually defined what will happen.
///
/// There's no limit on the length of the title.
///
/// LR2 trims whitespace on the left and right of the title.
///
/// Encoding and decoding can be hellish, because of how old BMS as a format is.
/// Don't expect UTF-8 everywhere, expect a good chunk of UTF-16, SHIFT-JIS,
/// IEC 2022 and even EUC.
///
/// Implicit Subtitles exist as a form of in-band signalling.
/// The subtitle is delimited by any of the following
/// - `#TITLE main-sub-`
/// - `#TITLE mainsub`
/// - `#TITLE main(sub)`
/// - `#TITLE main[sub]`
/// - `#TITLE main<sub>`
/// - `#TITLE main␣␣sub` (Two+ halfwidth spaces)
/// - `#TITLE main"sub"` (Straight double quotes)
///
/// Because of how varied these are, we will make a choice as to which we will support.
/// This is fine since there's now a `#SUBTITLE` command.
///
/// We will support full width tilde and quote marks only.
pub struct Title(String);
/// `#SUBTITLE string` Subtitle of the track
///
/// Due to the limitations of the implicit subtitle, an actual SUBTITLE field was
/// defined.
///
/// Omissible. LR2 will only check for a implicit subtitle if `#SUBTITLE` doesn't exist.
pub struct Subtitle(String);
/// `#ARTIST string`
///
/// Definition of the track artist. Interestingly Artist isn't actually defined
/// in the spec.
pub struct Artist(String);
/// `#SUBARTIST string`
///
/// Added by LR2. This is used usually to define things like BGA artists,
/// noters and other such co-artists.
pub struct Subartist(String);
/// `#MAKER string`
///
/// Sometimes supported.
///
/// Used to denote when a composer differs from the chart maker. In this case
/// it is used to store the chart makers name.
pub struct Maker(String);
/// `#GENRE string`
///
/// Denotes the genre of music for the chart.
/// By default it will be empty if not set.
///
/// Supported by basically every client.
pub struct Genre(String);
// TODO: Landmine
// It's in WAV00
/// `#BPM n`
///
/// Defines the BPM of the music. Defines the scroll speed etc.
/// The BMS spec defines 130 as the default value.
///
/// it is expected that fractional BPMs are supported, as such we will repr
/// this as a float.
pub struct ConstantBPM(f32);
// Standard defined default.
// TODO. Implement BPM changes
impl Default for ConstantBPM {
fn default() -> Self {
Self(130.0)
}
}
/// `#BPMxx n` OR `#EXBPM[01-ZZ] n`
///
/// Hitkey refers to this as exBPM or "Extended BPM Change Command".
/// This operates on Channel #xxx08.
///
/// This command allows the chart designer to describe BPM changes.
/// For example,
///
/// ```
/// #BPMAA 256
/// #BPMBB 155.5
/// #00108:AABBAABB
/// ```
///
/// This defines the BPM AA to be 256, the BPM BB to be 155.5 and says where to use it
/// in the chart itself.
///
/// Negative BPMs are allowed, and in general are expected to make the chart scroll _backwards_.
///
/// For more info, see https://hitkey.nekokan.dyndns.info/exbpm-object.htm
///
/// In parsing, we expect to parse the identifier to the string, and the bpm to the float.
pub struct ExBPM(String, f32);
/// Represent the multiple types of BPM as enum variants.
pub enum BPM {
Constant(ConstantBPM),
Extended(ExBPM),
}
/// `#STOP[01-ZZ] n`
pub struct Stop(String, u32);

View file

@ -1,14 +1 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
pub mod header;

View file

@ -5,3 +5,12 @@ edition = "2024"
[dependencies]
bevy = { version = "0.16.1", features = ["dynamic_linking"] }
# Enable a small amount of optimization in the dev profile.
[profile.dev]
opt-level = 1
# Enable a large amount of optimization in the dev profile for dependencies.
[profile.dev.package."*"]
opt-level = 3