#![cfg(feature = "bmson")]
#![cfg_attr(docsrs, doc(cfg(feature = "bmson")))]
pub mod bms_to_bmson;
pub mod bmson_to_bms;
pub mod parse;
pub mod prelude;
pub mod pulse;
use std::{
borrow::Cow,
num::{NonZeroU8, NonZeroU64},
};
#[cfg(feature = "diagnostics")]
use ariadne::{Color, Report, ReportKind};
use chumsky::{prelude::*, span::SimpleSpan};
use serde::{Deserialize, Deserializer, Serialize};
use crate::bms::command::LnMode;
#[cfg(feature = "diagnostics")]
use crate::diagnostics::{ToAriadne, build_report};
use self::{
parse::{
Error as JsonError, Recovered as JsonRecovered, Warning as JsonWarning, parser,
split_chumsky_errors,
},
pulse::PulseNumber,
};
use strict_num_extended::{FinF64, PositiveF64};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Bmson<'a> {
pub version: Cow<'a, str>,
pub info: BmsonInfo<'a>,
pub lines: Option<Vec<BarLine>>,
#[serde(default)]
pub bpm_events: Vec<BpmEvent>,
#[serde(default)]
pub stop_events: Vec<StopEvent>,
pub sound_channels: Vec<SoundChannel<'a>>,
#[serde(default)]
pub bga: Bga<'a>,
#[serde(default)]
pub scroll_events: Vec<ScrollEvent>,
#[serde(default)]
pub mine_channels: Vec<MineChannel<'a>>,
#[serde(default)]
pub key_channels: Vec<KeyChannel<'a>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BmsonInfo<'a> {
pub title: Cow<'a, str>,
#[serde(default)]
pub subtitle: Cow<'a, str>,
pub artist: Cow<'a, str>,
#[serde(default)]
pub subartists: Vec<Cow<'a, str>>,
pub genre: Cow<'a, str>,
#[serde(default = "default_mode_hint_cow")]
pub mode_hint: Cow<'a, str>,
#[serde(default)]
pub chart_name: Cow<'a, str>,
pub level: u32,
pub init_bpm: PositiveF64,
#[serde(default = "default_percentage")]
pub judge_rank: FinF64,
#[serde(default = "default_percentage")]
pub total: FinF64,
#[serde(default)]
pub back_image: Option<Cow<'a, str>>,
#[serde(default)]
pub eyecatch_image: Option<Cow<'a, str>>,
#[serde(default)]
pub title_image: Option<Cow<'a, str>>,
#[serde(default)]
pub banner_image: Option<Cow<'a, str>>,
#[serde(default)]
pub preview_music: Option<Cow<'a, str>>,
#[serde(
default = "default_resolution_nonzero",
deserialize_with = "deserialize_resolution"
)]
pub resolution: NonZeroU64,
#[serde(default)]
pub ln_type: LnMode,
}
#[must_use]
pub const fn default_mode_hint() -> &'static str {
"beat-7k"
}
const fn default_mode_hint_cow() -> Cow<'static, str> {
Cow::Borrowed(default_mode_hint())
}
const DEFAULT_PERCENTAGE: FinF64 = FinF64::new_const(100.0);
#[must_use]
pub const fn default_percentage() -> FinF64 {
DEFAULT_PERCENTAGE
}
#[must_use]
pub const fn default_resolution() -> u64 {
240
}
const fn default_resolution_nonzero() -> NonZeroU64 {
let Some(v) = NonZeroU64::new(default_resolution()) else {
return NonZeroU64::MIN;
};
v
}
fn deserialize_resolution<'de, D>(deserializer: D) -> Result<NonZeroU64, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::{Error, Visitor};
use std::fmt;
struct ResolutionVisitor;
impl<'de> Visitor<'de> for ResolutionVisitor {
type Value = NonZeroU64;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a number or null")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: Error,
{
Ok(default_resolution_nonzero())
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(self)
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(NonZeroU64::new(v.unsigned_abs()).unwrap_or_else(default_resolution_nonzero))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(NonZeroU64::new(v).unwrap_or_else(default_resolution_nonzero))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: Error,
{
let av = v.abs();
if !av.is_finite() {
return Err(E::custom("Resolution must be a finite number"));
}
if av.fract() != 0.0 {
return Err(E::custom("Resolution must be an integer (unsigned long)"));
}
if av == 0.0 {
return Ok(default_resolution_nonzero());
}
if av > (u64::MAX as f64) {
return Err(E::custom(format!("Resolution value too large: {v}")));
}
Ok(NonZeroU64::new(av as u64).unwrap_or_else(default_resolution_nonzero))
}
}
deserializer.deserialize_option(ResolutionVisitor)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BarLine {
pub y: PulseNumber,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SoundChannel<'a> {
pub name: Cow<'a, str>,
pub notes: Vec<Note>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Note {
pub y: PulseNumber,
#[serde(deserialize_with = "deserialize_x_none_if_zero")]
pub x: Option<NonZeroU8>,
pub l: u64,
pub c: bool,
#[serde(default)]
pub t: Option<LnMode>,
#[serde(default)]
pub up: Option<bool>,
}
fn deserialize_x_none_if_zero<'de, D>(deserializer: D) -> Result<Option<NonZeroU8>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<u8>::deserialize(deserializer)?;
Ok(opt.and_then(NonZeroU8::new))
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BpmEvent {
pub y: PulseNumber,
pub bpm: PositiveF64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StopEvent {
pub y: PulseNumber,
pub duration: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Bga<'a> {
#[serde(default)]
pub bga_header: Vec<BgaHeader<'a>>,
#[serde(default)]
pub bga_events: Vec<BgaEvent>,
#[serde(default)]
pub layer_events: Vec<BgaEvent>,
#[serde(default)]
pub poor_events: Vec<BgaEvent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BgaHeader<'a> {
pub id: BgaId,
pub name: Cow<'a, str>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BgaEvent {
pub y: PulseNumber,
pub id: BgaId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct BgaId(pub u32);
impl BgaId {
#[must_use]
pub const fn value(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ScrollEvent {
pub y: PulseNumber,
pub rate: FinF64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MineEvent {
#[serde(deserialize_with = "deserialize_x_none_if_zero")]
pub x: Option<NonZeroU8>,
pub y: PulseNumber,
pub damage: FinF64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MineChannel<'a> {
pub name: Cow<'a, str>,
pub notes: Vec<MineEvent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyEvent {
#[serde(deserialize_with = "deserialize_x_none_if_zero")]
pub x: Option<NonZeroU8>,
pub y: PulseNumber,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyChannel<'a> {
pub name: Cow<'a, str>,
pub notes: Vec<KeyEvent>,
}
#[derive(Debug)]
pub enum BmsonParseError<'a> {
JsonWarning {
warning: JsonWarning<'a>,
},
JsonRecovered {
error: JsonRecovered<'a>,
},
JsonError {
error: JsonError<'a>,
},
Deserialize {
error: serde_path_to_error::Error<serde_json::Error>,
},
}
#[cfg(feature = "diagnostics")]
impl ToAriadne for serde_path_to_error::Error<serde_json::Error> {
fn to_report<'b>(
&self,
src: &crate::diagnostics::SimpleSource<'b>,
) -> Report<'b, (String, std::ops::Range<usize>)> {
let message = format!("{self}");
build_report(
src,
ReportKind::Error,
0..0,
"BMSON deserialization error",
&message,
Color::Red,
)
}
}
#[cfg(feature = "diagnostics")]
impl ToAriadne for BmsonParseError<'_> {
fn to_report<'b>(
&self,
src: &crate::diagnostics::SimpleSource<'b>,
) -> Report<'b, (String, std::ops::Range<usize>)> {
match self {
BmsonParseError::JsonWarning { warning } => warning.to_report(src),
BmsonParseError::JsonRecovered { error } => error.to_report(src),
BmsonParseError::JsonError { error } => error.to_report(src),
BmsonParseError::Deserialize { error } => error.to_report(src),
}
}
}
pub struct BmsonParseOutput<'a> {
pub bmson: Option<Bmson<'a>>,
pub errors: Vec<BmsonParseError<'a>>,
}
#[must_use]
pub fn parse_bmson<'a>(json: &'a str) -> BmsonParseOutput<'a> {
let (value, parse_errors) = parser().parse(json.trim()).into_output_errors();
let had_output = value.is_some();
let (warnings, recovered, fatal) = split_chumsky_errors(parse_errors, had_output);
let mut errors: Vec<BmsonParseError<'a>> =
Vec::with_capacity(warnings.len() + recovered.len() + fatal.len());
errors.extend(
warnings
.into_iter()
.map(|warning| BmsonParseError::JsonWarning { warning }),
);
errors.extend(
recovered
.into_iter()
.map(|error| BmsonParseError::JsonRecovered { error }),
);
errors.extend(
fatal
.into_iter()
.map(|error| BmsonParseError::JsonError { error }),
);
let serde_fallback = serde_json::from_str(json);
let json_value = value.or_else(|| serde_fallback.as_ref().ok().cloned());
if json_value.is_none()
&& let Err(e) = serde_fallback
&& !errors
.iter()
.any(|err| matches!(err, BmsonParseError::JsonError { .. }))
{
let span = SimpleSpan::new((), 0..json.len());
errors.push(BmsonParseError::JsonError {
error: JsonError(Rich::custom(span, format!("Invalid JSON: {e}"))),
});
}
let bmson = json_value
.map(|json_value| serde_path_to_error::deserialize(&json_value))
.and_then(|deserialize_result| {
deserialize_result
.map_err(|error| errors.push(BmsonParseError::Deserialize { error }))
.ok()
});
BmsonParseOutput { bmson, errors }
}