pub(crate) mod lex;
pub(crate) mod parse;
pub(crate) mod token;
pub use token::Channel;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct RawBms {
raw_bms: BmsBlock,
all_wav_files: HashSet<String>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub(crate) struct BmsBlock(Vec<BmsElement>);
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum BmsElement {
Command(token::Command),
Random(BmsRandomBlock),
Switch(BmsSwitchBlock),
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct BmsRandomBlock(RandomValue, Vec<BmsRandomElement>);
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum BmsRandomElement {
Block(BmsBlock),
IfBlock(BmsIfBlock),
}
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct BmsIfBlock {
pub(crate) r#if: Vec<(u128, BmsBlock)>,
pub(crate) r#else: Option<BmsBlock>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct BmsSwitchBlock(
RandomValue,
Vec<BmsCaseBlock>,
std::collections::HashSet<u128>,
);
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct BmsCaseBlock(SwitchLabel, BmsBlock, bool);
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum RandomValue {
Max(u128),
Set(u128),
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum SwitchLabel {
Case(u128),
Default,
}
impl BmsBlock {
pub(crate) fn get_token_vec<'a, Rng: rand::RngCore>(
&'a self,
output: &mut Vec<&'a token::Command>,
rng: &mut Rng,
) {
for e in &self.0 {
e.get_token_vec(output, rng);
}
}
}
impl BmsElement {
fn get_token_vec<'a, Rng: rand::RngCore>(
&'a self,
output: &mut Vec<&'a token::Command>,
rng: &mut Rng,
) {
match self {
BmsElement::Command(c) => {
output.push(c);
}
BmsElement::Random(rb) => {
rb.get_token_vec(output, rng);
}
BmsElement::Switch(sb) => {
sb.get_token_vec(output, rng);
}
}
}
}
impl BmsRandomBlock {
fn get_token_vec<'a, Rng: rand::RngCore>(
&'a self,
output: &mut Vec<&'a token::Command>,
rng: &mut Rng,
) {
use rand::Rng;
let n = match self.0 {
RandomValue::Max(n) => rng.random_range(1..=n),
RandomValue::Set(n) => n,
};
for e in &self.1 {
e.get_token_vec(output, rng, n);
}
}
}
impl BmsRandomElement {
fn get_token_vec<'a, Rng: rand::RngCore>(
&'a self,
output: &mut Vec<&'a token::Command>,
rng: &mut Rng,
n: u128,
) {
match self {
BmsRandomElement::Block(b) => {
b.get_token_vec(output, rng);
}
BmsRandomElement::IfBlock(ib) => {
ib.get_token_vec(output, rng, n);
}
}
}
}
impl BmsIfBlock {
fn get_token_vec<'a, Rng: rand::RngCore>(
&'a self,
output: &mut Vec<&'a token::Command>,
rng: &mut Rng,
n: u128,
) {
for (i, b) in &self.r#if {
if *i == n {
b.get_token_vec(output, rng);
return;
}
}
if let Some(b) = &self.r#else {
b.get_token_vec(output, rng);
}
}
}
impl BmsSwitchBlock {
fn get_token_vec<'a, Rng: rand::RngCore>(
&'a self,
output: &mut Vec<&'a token::Command>,
rng: &mut Rng,
) {
use rand::Rng;
let n = match self.0 {
RandomValue::Max(n) => rng.random_range(1..=n),
RandomValue::Set(n) => n,
};
let mut flag = false;
for e in &self.1 {
if match &e.0 {
SwitchLabel::Case(i) => *i == n,
SwitchLabel::Default => !self.2.contains(&n),
} {
flag = true;
}
if flag {
e.1.get_token_vec(output, rng);
}
if flag && e.2 {
return;
}
}
}
}
use std::collections::{HashMap, HashSet};
impl RawBms {
pub fn parse(source: &str) -> RawBms {
use token::*;
use winnow::prelude::*;
let token_stream = lex::lex(source);
let all_wav_files = token_stream
.iter()
.filter_map(|t| {
if let Token::Command(Command::Wav(_, file)) = t {
Some(file.clone())
}
else {
None
}
})
.collect::<HashSet<_>>();
RawBms {
raw_bms: parse::block
.parse_next(&mut token_stream.as_slice())
.unwrap(),
all_wav_files,
}
}
pub fn all_wav_files(&self) -> &HashSet<String> {
&self.all_wav_files
}
#[allow(deprecated)]
pub fn make_bms(&self, mut rng: impl rand::RngCore) -> Bms<'_> {
use token::Command::*;
let mut commands = vec![];
self.raw_bms.get_token_vec(&mut commands, &mut rng);
let base62 = commands.iter().any(|c| matches!(c, Base62));
let mut bms = Bms {
main_data: Vec::with_capacity(1000),
..Default::default()
};
let convert_channel_vec = |ch_vec: &[Channel]| {
ch_vec
.iter()
.map(|ch| ch.to_base_36_or_62(base62))
.collect::<Vec<_>>()
};
for c in commands {
match c {
MainData(measure, data) => {
if bms.main_data.len() <= *measure {
bms.main_data
.resize_with(measure + 1, Default::default);
}
let measure = &mut bms.main_data[*measure];
use token::MainDataValue::*;
#[rustfmt::skip]
match data {
Bgm(data) =>
measure.bgm.push(convert_channel_vec(data)),
Length(n) =>
measure.length = *n,
Bga(data) =>
measure.bga.push(convert_channel_vec(data)),
Bpm(data) =>
measure.bpm.push(data),
BgaPoor(data) =>
measure.bga_poor.push(convert_channel_vec(data)),
BgaLayer(data) =>
measure.bga_layer.push(convert_channel_vec(data)),
ExBpm(data) =>
measure.ex_bpm.push(convert_channel_vec(data)),
Stop(data) =>
measure.stop.push(convert_channel_vec(data)),
BgaLayer2(data) =>
measure.bga_layer2.push(convert_channel_vec(data)),
ExRank(data) =>
measure.ex_rank.push(convert_channel_vec(data)),
BgaAlpha(data) =>
measure.bga_alpha.push(data),
BgaLayerAlpha(data) =>
measure.bga_layer_alpha.push(data),
BgaLayer2Alpha(data) =>
measure.bga_layer2_alpha.push(data),
BgaPoorAlpha(data) =>
measure.bga_poor_alpha.push(data),
Note(ch, data) =>
measure.notes.entry(*ch).or_default().push(convert_channel_vec(data)),
InvisibleNote(ch, data) =>
measure.invisible_notes.entry(*ch).or_default().push(convert_channel_vec(data)),
LongNote(ch, data) =>
measure.long_notes.entry(*ch).or_default().push(convert_channel_vec(data)),
Text(data) =>
measure.text.push(convert_channel_vec(data)),
BgaArgb(data) =>
measure.bga_argb.push(convert_channel_vec(data)),
BgaLayerArgb(data) =>
measure.bga_layer_argb.push(convert_channel_vec(data)),
BgaLayer2Argb(data) =>
measure.bga_layer2_argb.push(convert_channel_vec(data)),
BgaPoorArgb(data) =>
measure.bga_poor_argb.push(convert_channel_vec(data)),
SwitchBga(data) =>
measure.switch_bga.push(convert_channel_vec(data)),
Option(data) =>
measure.option.push(convert_channel_vec(data)),
Landmine(ch, data) =>
measure.landmine.entry(*ch).or_default().push(data),
Scroll(data) =>
measure.scroll.push(convert_channel_vec(data)),
Speed(data) =>
measure.speed.push(convert_channel_vec(data)),
Other(ch, data) =>
measure.other.push((*ch, data)),
};
}
Player(n) => {
bms.player = Some(match n {
1 => PlayType::SinglePlay,
2 => PlayType::CouplePlay,
3 => PlayType::DoublePlay,
4 => PlayType::BattlePlay,
_ => unreachable!(),
});
}
Rank(n) => bms.rank = Some(*n),
DefExRank(n) => bms.def_ex_rank = Some(*n),
ExRank(ch, n) => {
bms.ex_rank.insert(ch.to_base_36_or_62(base62), *n);
}
Total(n) => bms.total = Some(*n),
VolumeWav(n) => bms.volume_wav = Some(*n),
StageFile(s) => bms.stage_file = Some(s),
Banner(s) => bms.banner = Some(s),
BackBmp(s) => bms.back_bmp = Some(s),
CharacterFile(s) => bms.character_file = Some(s),
PlayLevel(n) => bms.play_level = Some(*n),
Difficulty(n) => bms.difficulty = Some(*n),
Title(s) => bms.title = Some(s),
SubTitle(s) => bms.sub_title.push(s),
Artist(s) => bms.artist = Some(s),
SubArtist(s) => bms.sub_artist.push(s),
Maker(s) => bms.maker = Some(s),
Genre(s) => bms.genre = Some(s),
Comment(s) => bms.comment.push(s),
Text(ch, s) => {
bms.text.insert(ch.to_base_36_or_62(base62), s);
}
PathWav(s) => bms.path_wav = Some(s),
Bpm(n) => bms.bpm = Some(*n),
ExBpm(ch, n) => {
bms.ex_bpm.insert(ch.to_base_36_or_62(base62), *n);
}
BaseBpm(n) => bms.base_bpm = Some(*n),
Stop(ch, n) => {
bms.stop.insert(ch.to_base_36_or_62(base62), *n);
}
Stp(x, y, z) => bms.stp.push((*x, *y, *z)),
LnMode(n) => bms.ln_mode = Some(*n),
LnType(n) => bms.ln_type = Some(*n),
LnObject(ch) => {
bms.ln_object.insert(ch.to_base_36_or_62(base62));
}
OctFp => bms.oct_fp = true,
Option(opt) => bms.option.push(opt),
ChangeOption(ch, opt) => {
bms.change_option.insert(ch.to_base_36_or_62(base62), opt);
}
Wav(ch, s) => {
bms.wav.insert(ch.to_base_36_or_62(base62), s);
}
WavCommand(opt, ch, v) => {
bms.wav_command.push((
*opt,
ch.to_base_36_or_62(base62),
*v,
));
}
ExWav(ch, opt, s) => {
bms.ex_wav.insert(ch.to_base_36_or_62(base62), (opt, s));
}
Cdda(n) => bms.cdda = Some(*n),
MidiFile(s) => bms.midi_file = Some(s),
Bmp(ch, s) => {
bms.bmp.insert(ch.to_base_36_or_62(base62), s);
}
ExBmp(ch, argb, s) => {
bms.ex_bmp.insert(ch.to_base_36_or_62(base62), (argb, s));
}
Bga(ch, bmp, pos) => {
bms.bga.insert(
ch.to_base_36_or_62(base62),
(bmp.to_base_36_or_62(base62), pos),
);
}
AtBga(ch, bmp, pos) => {
bms.at_bga.insert(
ch.to_base_36_or_62(base62),
(bmp.to_base_36_or_62(base62), pos),
);
}
PoorBga(n) => bms.poor_bga = Some(*n),
SwitchBga(ch, fr, time, line, r#loop, argb, data) => {
bms.switch_bga.insert(
ch.to_base_36_or_62(base62),
(*fr, *time, line.to_base_36(), *r#loop, argb, data),
);
}
Argb(ch, argb) => {
bms.argb.insert(ch.to_base_36_or_62(base62), argb);
}
VideoFile(s) => bms.video_file = Some(s),
VideoFps(n) => bms.video_fps = Some(*n),
VideoColors(n) => bms.video_colors = Some(*n),
VideoDelay(n) => bms.video_delay = Some(*n),
Movie(s) => bms.movie = Some(s),
Seek(ch, n) => {
bms.seek.insert(ch.to_base_36_or_62(base62), *n);
}
ExCharacter(sprite_num, bmp, trim_rect, offset, abs_pos) => {
bms.ex_character = Some(super::bms::ExCharacter {
sprite_num: *sprite_num,
bmp: *bmp,
trim_rect,
offset: offset.as_ref(),
abs_pos: abs_pos.as_ref(),
});
}
Url(s) => bms.url = Some(s),
Email(s) => bms.email = Some(s),
Scroll(ch, n) => {
bms.scroll.insert(ch.to_base_36_or_62(base62), *n);
}
Speed(ch, n) => {
bms.speed.insert(ch.to_base_36_or_62(base62), *n);
}
Preview(s) => bms.preview = Some(s),
Other(command, value) => {
bms.other.push((command, value));
}
_ => (),
}
}
bms
}
}
#[derive(Default, Debug, PartialEq)]
pub struct Bms<'a> {
pub main_data: Vec<MainData<'a>>,
pub rank: Option<i32>,
pub def_ex_rank: Option<f64>,
pub total: Option<f64>,
pub volume_wav: Option<f64>,
pub stage_file: Option<&'a str>,
pub banner: Option<&'a str>,
pub back_bmp: Option<&'a str>,
pub play_level: Option<i32>,
pub difficulty: Option<i32>,
pub title: Option<&'a str>,
pub sub_title: Vec<&'a str>,
pub artist: Option<&'a str>,
pub sub_artist: Vec<&'a str>,
pub genre: Option<&'a str>,
pub bpm: Option<f64>,
pub ex_bpm: HashMap<usize, f64>,
pub stop: HashMap<usize, f64>,
pub ln_mode: Option<i32>,
pub ln_type: Option<i32>,
pub ln_object: HashSet<usize>,
pub wav: HashMap<usize, &'a str>,
pub bmp: HashMap<usize, &'a str>,
pub url: Option<&'a str>,
pub email: Option<&'a str>,
pub scroll: HashMap<usize, f64>,
pub speed: HashMap<usize, f64>,
pub preview: Option<&'a str>,
pub ex_rank: HashMap<usize, f64>,
pub character_file: Option<&'a str>,
pub maker: Option<&'a str>,
pub comment: Vec<&'a str>,
pub text: HashMap<usize, &'a str>,
pub path_wav: Option<&'a str>,
pub stp: Vec<(usize, u32, f64)>,
pub oct_fp: bool,
pub option: Vec<&'a str>,
pub change_option: HashMap<usize, &'a str>,
pub wav_command: Vec<(i32, usize, f64)>,
pub ex_wav: HashMap<usize, (&'a [Option<f64>; 3], &'a str)>,
pub cdda: Option<u32>,
pub midi_file: Option<&'a str>,
pub ex_bmp: HashMap<usize, (&'a [u8; 4], &'a str)>,
pub bga: HashMap<usize, (usize, &'a [[f64; 2]; 3])>,
pub at_bga: HashMap<usize, (usize, &'a [[f64; 2]; 3])>,
pub poor_bga: Option<i32>,
pub argb: HashMap<usize, &'a [u8; 4]>,
pub video_file: Option<&'a str>,
pub video_fps: Option<f64>,
pub video_colors: Option<u32>,
pub video_delay: Option<u32>,
pub movie: Option<&'a str>,
pub ex_character: Option<ExCharacter<'a>>,
#[deprecated]
pub player: Option<PlayType>,
#[deprecated]
pub base_bpm: Option<f64>,
#[deprecated]
pub switch_bga:
HashMap<usize, (f64, f64, usize, bool, &'a [u8; 4], &'a [Channel])>,
#[deprecated]
pub seek: HashMap<usize, f64>,
pub other: Vec<(&'a str, &'a str)>,
}
#[derive(Debug, PartialEq)]
pub struct MainData<'a> {
pub bgm: Vec<Vec<usize>>,
pub length: f64,
pub bpm: Vec<&'a [Option<f64>]>,
pub bga: Vec<Vec<usize>>,
pub bga_poor: Vec<Vec<usize>>,
pub bga_layer: Vec<Vec<usize>>,
pub ex_bpm: Vec<Vec<usize>>,
pub stop: Vec<Vec<usize>>,
pub bga_layer2: Vec<Vec<usize>>,
pub bga_alpha: Vec<&'a [u8]>,
pub bga_layer_alpha: Vec<&'a [u8]>,
pub bga_layer2_alpha: Vec<&'a [u8]>,
pub bga_poor_alpha: Vec<&'a [u8]>,
pub notes: HashMap<usize, Vec<Vec<usize>>>,
pub invisible_notes: HashMap<usize, Vec<Vec<usize>>>,
pub long_notes: HashMap<usize, Vec<Vec<usize>>>,
pub text: Vec<Vec<usize>>,
pub ex_rank: Vec<Vec<usize>>,
pub bga_argb: Vec<Vec<usize>>,
pub bga_layer_argb: Vec<Vec<usize>>,
pub bga_layer2_argb: Vec<Vec<usize>>,
pub bga_poor_argb: Vec<Vec<usize>>,
pub switch_bga: Vec<Vec<usize>>,
pub option: Vec<Vec<usize>>,
pub landmine: HashMap<usize, Vec<&'a [f64]>>,
pub scroll: Vec<Vec<usize>>,
pub speed: Vec<Vec<usize>>,
pub other: Vec<(usize, &'a str)>,
}
impl Default for MainData<'_> {
fn default() -> Self {
MainData {
bgm: vec![],
length: 1.0,
bpm: vec![],
bga: vec![],
bga_poor: vec![],
bga_layer: vec![],
ex_bpm: vec![],
stop: vec![],
bga_layer2: vec![],
bga_alpha: vec![],
bga_layer_alpha: vec![],
bga_layer2_alpha: vec![],
bga_poor_alpha: vec![],
notes: HashMap::new(),
invisible_notes: HashMap::new(),
long_notes: HashMap::new(),
text: vec![],
ex_rank: vec![],
bga_argb: vec![],
bga_layer_argb: vec![],
bga_layer2_argb: vec![],
bga_poor_argb: vec![],
switch_bga: vec![],
option: vec![],
landmine: HashMap::new(),
scroll: vec![],
speed: vec![],
other: vec![],
}
}
}
#[derive(Debug, PartialEq)]
pub struct ExCharacter<'a> {
pub sprite_num: u32,
pub bmp: usize,
pub trim_rect: &'a [[f64; 2]; 2],
pub offset: Option<&'a [f64; 2]>,
pub abs_pos: Option<&'a [f64; 2]>,
}
#[derive(Debug, PartialEq)]
pub enum PlayType {
SinglePlay,
CouplePlay,
DoublePlay,
BattlePlay,
}