This repository has been archived on 2024-01-28. You can view files and clone it, but cannot push or open issues/pull-requests.
brd/src/osu/beatmap.rs

596 lines
14 KiB
Rust

use num_derive::ToPrimitive;
use num_traits::ToPrimitive;
use std::fmt;
// Generic Type Aliases
pub type OsuPixel = i16;
pub type DecimalOsuPixel = f32;
pub type SampleIndex = u16;
pub type Time = i32;
// Helper functions
fn bitflags(flags: [bool; 8]) -> u8 {
let mut value = 0u8;
for (i, flag) in flags.iter().enumerate() {
value += ((0b1 as u8) << i) * (*flag as u8) as u8;
}
value
}
fn join_display_values<T: fmt::Display>(iterable: Vec<T>, separator: &'_ str) -> String {
iterable
.iter()
.map(|val| val.to_string())
.collect::<Vec<_>>()
.join(&separator)
}
fn assemble_hit_object_type(hit_object_type: u8, new_combo: bool, skip_combo_colours: U3) -> u8 {
let hit_object_type = 1u8 << hit_object_type;
let new_combo = if new_combo { 0b0000_0010_u8 } else { 0u8 };
let skip_combo_colours = (skip_combo_colours & 0b_0000_0111u8) << 1;
hit_object_type + new_combo + skip_combo_colours
}
pub fn column_to_x(column: u8, columns: u8) -> OsuPixel {
((512 * column as OsuPixel + 256) / columns as OsuPixel) as OsuPixel
}
#[derive(ToPrimitive, Clone)]
pub enum Countdown {
No = 0,
Normal = 1,
Half = 2,
Double = 3,
}
#[derive(ToPrimitive, Clone)]
pub enum Mode {
Normal = 0,
Taiko = 1,
Catch = 2,
Mania = 3,
}
#[derive(ToPrimitive, Debug, Clone)]
pub enum SampleSet {
BeatmapDefault = 0,
Normal = 1,
Soft = 2,
Drum = 3,
}
#[derive(Clone)]
pub struct General {
pub audio_filename: String,
pub audio_lead_in: Time,
pub preview_time: Time,
pub countdown: Countdown,
pub sample_set: SampleSet,
pub mode: Mode,
}
impl fmt::Display for General {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
[General]\n\
AudioFilename: {}\n\
AudioLeadIn: {}\n\
PreviewTime: {}\n\
Countdown: {}\n\
SampleSet: {:?}\n\
Mode: {}\n\
",
self.audio_filename,
self.audio_lead_in,
self.preview_time,
ToPrimitive::to_u8(&self.countdown).unwrap(),
self.sample_set,
ToPrimitive::to_u8(&self.mode).unwrap()
)
}
}
#[derive(Clone)]
pub struct Editor {/* stub */}
impl fmt::Display for Editor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "[Editor]")
}
}
#[derive(Clone)]
pub struct Metadata {
pub title: String,
pub artist: String,
pub creator: String,
pub version: String,
pub source: String,
pub tags: Vec<String>,
}
impl fmt::Display for Metadata {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
[Metadata]\n\
Title:{}\n\
Artist:{}\n\
Creator:{}\n\
Version:{}\n\
Source:{}\n\
Tags:{}\n\
",
self.title,
self.artist,
self.creator,
self.version,
self.source,
self.tags.join(" ")
)
}
}
#[derive(Clone)]
pub struct Difficulty {
pub hp_drain_rate: f32,
/// Also is the number of keys in mania
pub circle_size: f32,
pub overall_difficulty: f32,
pub approach_rate: f32,
pub slider_multiplier: f32,
pub slider_tick_rate: f32,
}
impl fmt::Display for Difficulty {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
[Difficulty]\n\
HPDrainRate:{}\n\
CircleSize:{}\n\
OverallDifficulty:{}\n\
ApproachRate:{}\n\
SliderMultiplier:{}\n\
SliderTickRate:{}\n\
",
self.hp_drain_rate,
self.circle_size,
self.overall_difficulty,
self.approach_rate,
self.slider_multiplier,
self.slider_tick_rate
)
}
}
#[derive(Clone)]
pub struct Events(pub Vec<Event>);
impl fmt::Display for Events {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
[Events]\n\
{}\n\
",
join_display_values(self.0.clone(), "\n")
)
}
}
#[derive(Clone)]
pub enum Event {
Background {
filename: String,
x_offset: OsuPixel,
y_offset: OsuPixel,
},
Video {
start_time: Time,
filename: String,
x_offset: OsuPixel,
y_offset: OsuPixel,
},
Break {
start_time: Time,
end_time: Time,
},
}
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Event::Background {
filename,
x_offset,
y_offset,
} => write!(f, "0,0,{},{},{}", filename, x_offset, y_offset),
Event::Video {
start_time,
filename,
x_offset,
y_offset,
} => write!(
f,
"Video,{},{},{},{}",
start_time, filename, x_offset, y_offset
),
Event::Break {
start_time,
end_time,
} => write!(f, "Break,{},{}", start_time, end_time),
}
}
}
#[derive(Clone)]
pub struct TimingPoints(pub Vec<TimingPoint>);
impl fmt::Display for TimingPoints {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
[TimingPoints]\n\
{}\n\
",
join_display_values(self.0.clone(), "\n")
)
}
}
#[derive(Clone)]
pub struct TimingPointEffects {
pub kiai_time: bool,
pub omit_first_barline: bool,
}
impl fmt::Display for TimingPointEffects {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
bitflags([
self.kiai_time,
false,
false,
self.omit_first_barline,
false,
false,
false,
false
])
)
}
}
#[derive(Clone)]
pub struct TimingPoint {
pub time: Time,
pub beat_length: f32,
pub meter: u8,
pub sample_set: SampleSet,
pub sample_index: SampleIndex,
pub volume: u8,
pub uninherited: bool,
pub effects: TimingPointEffects,
}
impl fmt::Display for TimingPoint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{},{},{},{},{},{},{},{}",
self.time,
self.beat_length,
self.meter,
ToPrimitive::to_u8(&self.sample_set).unwrap(),
self.sample_index,
self.volume,
self.uninherited as u8,
self.effects
)
}
}
#[derive(Clone)]
pub struct Colours(pub Vec<Colour>);
impl fmt::Display for Colours {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
[Colours]\n\
{}\n\
",
join_display_values(self.0.clone(), "\n")
)
}
}
#[derive(Clone, Debug)]
pub enum ColourScope {
Combo(u16),
SliderTrackOverride,
SliderBorder,
}
impl fmt::Display for ColourScope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ColourScope::Combo(i) => write!(f, "Combo{}", i),
_ => write!(f, "{:?}", self),
}
}
}
#[derive(Clone)]
pub struct Colour {
pub scope: ColourScope,
pub colour: [u8; 3],
}
impl fmt::Display for Colour {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} : {}",
self.scope,
join_display_values(self.colour.to_vec(), ",")
)
}
}
#[derive(Clone)]
pub struct HitSound {
pub normal: bool,
pub whistle: bool,
pub finish: bool,
pub clap: bool,
}
impl fmt::Display for HitSound {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
bitflags([
self.normal,
self.whistle,
self.finish,
self.clap,
false,
false,
false,
false
])
)
}
}
#[derive(Clone)]
pub struct HitSample {
pub normal_set: SampleIndex,
pub addition_set: SampleIndex,
pub index: SampleIndex,
pub volume: u8,
pub filename: String,
}
impl fmt::Display for HitSample {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}:{}:{}:{}:{}",
self.normal_set, self.addition_set, self.index, self.volume, self.filename
)
}
}
// Three bit integer
pub type U3 = u8;
#[derive(Clone)]
pub enum HitObject {
HitCircle {
x: OsuPixel,
y: OsuPixel,
time: Time,
hit_sound: HitSound,
new_combo: bool,
skip_combo_colours: U3,
hit_sample: HitSample,
},
Slider {
x: OsuPixel,
y: OsuPixel,
time: Time,
hit_sound: HitSound,
new_combo: bool,
skip_combo_colours: U3,
curve_type: char,
curve_points: Vec<(DecimalOsuPixel, DecimalOsuPixel)>,
slides: u8,
length: DecimalOsuPixel,
edge_sounds: Vec<SampleIndex>,
edge_sets: Vec<(SampleSet, SampleSet)>,
hit_sample: HitSample,
},
Spinner {
time: Time,
hit_sound: HitSound,
new_combo: bool,
skip_combo_colours: U3,
end_time: Time,
hit_sample: HitSample,
},
Hold {
column: u8,
columns: u8,
time: Time,
hit_sound: HitSound,
new_combo: bool,
skip_combo_colours: U3,
end_time: Time,
hit_sample: HitSample,
},
}
impl fmt::Display for HitObject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
HitObject::HitCircle {
x,
y,
time,
hit_sound,
new_combo,
skip_combo_colours,
hit_sample,
} => write!(
f,
"{},{},{},{},{},{}",
x,
y,
time,
assemble_hit_object_type(0, *new_combo, *skip_combo_colours),
hit_sound,
hit_sample
),
HitObject::Slider {
x,
y,
time,
hit_sound,
new_combo,
skip_combo_colours,
curve_type,
curve_points,
slides,
length,
edge_sounds,
edge_sets,
hit_sample,
} => write!(
f,
"{},{},{},{},{},{}|{},{},{},{},{},{}",
x,
y,
time,
assemble_hit_object_type(1, *new_combo, *skip_combo_colours),
hit_sound,
curve_type,
curve_points
.iter()
.map(|point| format!("{}:{}", point.0, point.1))
.collect::<Vec<_>>()
.join("|"),
slides,
length,
join_display_values(edge_sounds.clone(), "|"),
edge_sets
.iter()
.map(|set| format!(
"{}:{}",
ToPrimitive::to_u16(&set.0).unwrap(),
ToPrimitive::to_u16(&set.1).unwrap()
))
.collect::<Vec<_>>()
.join("|"),
hit_sample
),
HitObject::Spinner {
time,
hit_sound,
new_combo,
skip_combo_colours,
end_time,
hit_sample,
} => write!(
f,
"256,192,{},{},{},{},{}",
time,
assemble_hit_object_type(3, *new_combo, *skip_combo_colours),
hit_sound,
end_time,
hit_sample
),
HitObject::Hold {
column,
columns,
time,
hit_sound,
new_combo,
skip_combo_colours,
end_time,
hit_sample,
} => write!(
f,
"{},192,{},{},{},{}:{}",
column_to_x(*column, *columns),
time,
assemble_hit_object_type(7, *new_combo, *skip_combo_colours),
hit_sound,
end_time,
hit_sample
),
}
}
}
#[derive(Clone)]
pub struct HitObjects(pub Vec<HitObject>);
impl fmt::Display for HitObjects {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
[HitObjects]\n\
{}\n\
",
join_display_values(self.0.clone(), "\n")
)
}
}
pub struct Beatmap {
pub version: u8,
pub general: General,
pub editor: Editor,
pub metadata: Metadata,
pub difficulty: Difficulty,
pub events: Events,
pub timing_points: TimingPoints,
pub colours: Colours,
pub hit_objects: HitObjects,
}
impl fmt::Display for Beatmap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"osu file format v{}\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
self.version,
self.general.clone(),
self.editor.clone(),
self.metadata.clone(),
self.difficulty.clone(),
self.events.clone(),
self.timing_points.clone(),
self.colours.clone(),
self.hit_objects.clone()
)
}
}