use itertools::Itertools;
use std::{
cmp::Reverse,
collections::{BTreeMap, HashMap},
ops::Bound,
};
use super::{header::Header, obj::Obj, ParseError, Result};
use crate::{
lex::{
command::{self, Channel, Key, NoteKind, ObjId},
token::Token,
},
time::{ObjTime, Track},
};
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BpmChangeObj {
pub time: ObjTime,
pub bpm: f64,
}
impl PartialEq for BpmChangeObj {
fn eq(&self, other: &Self) -> bool {
self.time == other.time
}
}
impl Eq for BpmChangeObj {}
impl PartialOrd for BpmChangeObj {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BpmChangeObj {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.time.cmp(&other.time)
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SectionLenChangeObj {
pub track: Track,
pub length: f64,
}
impl PartialEq for SectionLenChangeObj {
fn eq(&self, other: &Self) -> bool {
self.track == other.track
}
}
impl Eq for SectionLenChangeObj {}
impl PartialOrd for SectionLenChangeObj {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SectionLenChangeObj {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.track.cmp(&other.track)
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StopObj {
pub time: ObjTime,
pub duration: u32,
}
impl PartialEq for StopObj {
fn eq(&self, other: &Self) -> bool {
self.time == other.time
}
}
impl Eq for StopObj {}
impl PartialOrd for StopObj {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StopObj {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.time.cmp(&other.time)
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BgaObj {
pub time: ObjTime,
pub id: ObjId,
pub layer: BgaLayer,
}
impl PartialEq for BgaObj {
fn eq(&self, other: &Self) -> bool {
self.time == other.time
}
}
impl Eq for BgaObj {}
impl PartialOrd for BgaObj {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BgaObj {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.time.cmp(&other.time)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum BgaLayer {
Base,
Poor,
Overlay,
}
#[derive(Debug, Default)]
pub struct Notes {
objs: HashMap<ObjId, Obj>,
bgms: BTreeMap<ObjTime, Vec<ObjId>>,
ids_by_key: HashMap<Key, BTreeMap<ObjTime, ObjId>>,
bpm_changes: BTreeMap<ObjTime, BpmChangeObj>,
section_len_changes: BTreeMap<Track, SectionLenChangeObj>,
stops: BTreeMap<ObjTime, StopObj>,
bga_changes: BTreeMap<ObjTime, BgaObj>,
}
impl Notes {
pub fn new() -> Self {
Default::default()
}
pub fn into_all_notes(self) -> Vec<Obj> {
self.objs.into_values().sorted().collect()
}
pub fn all_notes(&self) -> impl Iterator<Item = &Obj> {
self.objs.values().sorted()
}
pub fn bgms(&self) -> &BTreeMap<ObjTime, Vec<ObjId>> {
&self.bgms
}
pub fn bpm_changes(&self) -> &BTreeMap<ObjTime, BpmChangeObj> {
&self.bpm_changes
}
pub fn section_len_changes(&self) -> &BTreeMap<Track, SectionLenChangeObj> {
&self.section_len_changes
}
pub fn stops(&self) -> &BTreeMap<ObjTime, StopObj> {
&self.stops
}
pub fn bga_changes(&self) -> &BTreeMap<ObjTime, BgaObj> {
&self.bga_changes
}
pub fn next_obj_by_key(&self, key: Key, time: ObjTime) -> Option<&Obj> {
self.ids_by_key
.get(&key)?
.range((Bound::Excluded(time), Bound::Unbounded))
.next()
.and_then(|(_, id)| self.objs.get(id))
}
pub fn push_note(&mut self, note: Obj) {
self.objs.insert(note.obj, note.clone());
self.ids_by_key
.entry(note.key)
.or_insert_with(BTreeMap::new)
.insert(note.offset, note.obj);
}
pub fn remove_note(&mut self, id: ObjId) -> Option<Obj> {
self.objs.remove(&id).map(|removed| {
self.ids_by_key
.get_mut(&removed.key)
.unwrap()
.remove(&removed.offset)
.unwrap();
removed
})
}
pub fn push_bpm_change(&mut self, bpm_change: BpmChangeObj) {
if self
.bpm_changes
.insert(bpm_change.time, bpm_change)
.is_some()
{
eprintln!(
"duplicate bpm change object detected at {:?}",
bpm_change.time
);
}
}
pub fn push_section_len_change(&mut self, section_len_change: SectionLenChangeObj) {
if self
.section_len_changes
.insert(section_len_change.track, section_len_change)
.is_some()
{
eprintln!(
"duplicate section length change object detected at {:?}",
section_len_change.track
);
}
}
pub fn push_stop(&mut self, stop: StopObj) {
self.stops
.entry(stop.time)
.and_modify(|existing| {
existing.duration = existing.duration.saturating_add(stop.duration)
})
.or_insert(stop);
}
pub fn push_bga_change(&mut self, bga: BgaObj) {
if self.bga_changes.insert(bga.time, bga).is_some() {
eprintln!("duplicate bga change object detected at {:?}", bga.time);
}
}
pub(crate) fn parse(&mut self, token: &Token, header: &Header) -> Result<()> {
match token {
Token::Message {
track,
channel: Channel::BpmChange,
message,
} => {
for (time, obj) in ids_from_message(*track, message) {
let &bpm = header
.bpm_changes
.get(&obj)
.ok_or(ParseError::UndefinedObject(obj))?;
self.push_bpm_change(BpmChangeObj { time, bpm });
}
}
Token::Message {
track,
channel: Channel::BpmChangeU8,
message,
} => {
let denominator = message.len() as u32 / 2;
for (i, (c1, c2)) in message.chars().tuples().enumerate() {
let bpm = c1.to_digit(16).unwrap() * 16 + c2.to_digit(16).unwrap();
if bpm == 0 {
continue;
}
let time = ObjTime::new(track.0, i as u32, denominator);
self.push_bpm_change(BpmChangeObj {
time,
bpm: bpm as f64,
});
}
}
Token::Message {
track,
channel: Channel::SectionLen,
message,
} => {
let track = Track(track.0);
let length = message.parse().expect("f64 as section length");
assert!(0.0 < length, "section length must be greater than zero");
self.push_section_len_change(SectionLenChangeObj { track, length });
}
Token::Message {
track,
channel: Channel::Stop,
message,
} => {
for (time, obj) in ids_from_message(*track, message) {
let &duration = header
.stops
.get(&obj)
.ok_or(ParseError::UndefinedObject(obj))?;
self.push_stop(StopObj { time, duration })
}
}
Token::Message {
track,
channel: channel @ (Channel::BgaBase | Channel::BgaPoor | Channel::BgaLayer),
message,
} => {
for (time, obj) in ids_from_message(*track, message) {
if !header.bmp_files.contains_key(&obj) {
return Err(ParseError::UndefinedObject(obj));
}
let layer = match channel {
Channel::BgaBase => BgaLayer::Base,
Channel::BgaPoor => BgaLayer::Poor,
Channel::BgaLayer => BgaLayer::Overlay,
_ => unreachable!(),
};
self.push_bga_change(BgaObj {
time,
id: obj,
layer,
});
}
}
Token::Message {
track,
channel: Channel::Bgm,
message,
} => {
for (time, obj) in ids_from_message(*track, message) {
self.bgms
.entry(time)
.and_modify(|vec| vec.push(obj))
.or_insert_with(Vec::new);
}
}
Token::Message {
track,
channel:
Channel::Note {
kind,
is_player1,
key,
},
message,
} => {
for (offset, obj) in ids_from_message(*track, message) {
self.push_note(Obj {
offset,
kind: *kind,
is_player1: *is_player1,
key: *key,
obj,
});
}
}
&Token::LnObj(end_id) => {
let mut end_note = self
.remove_note(end_id)
.ok_or(ParseError::UndefinedObject(end_id))?;
let Obj { offset, key, .. } = &end_note;
let (_, &begin_id) =
self.ids_by_key[key].range(..offset).last().ok_or_else(|| {
ParseError::SyntaxError(format!(
"expected preceding object for #LNOBJ {:?}",
end_id
))
})?;
let mut begin_note = self.remove_note(begin_id).unwrap();
begin_note.kind = NoteKind::Long;
end_note.kind = NoteKind::Long;
self.push_note(begin_note);
self.push_note(end_note);
}
_ => {}
}
Ok(())
}
pub fn last_visible_time(&self) -> Option<ObjTime> {
self.objs
.values()
.filter(|obj| !matches!(obj.kind, NoteKind::Invisible))
.map(Reverse)
.sorted()
.next()
.map(|Reverse(obj)| obj.offset)
}
pub fn last_bgm_time(&self) -> Option<ObjTime> {
self.bgms.last_key_value().map(|(time, _)| time).cloned()
}
pub fn last_obj_time(&self) -> Option<ObjTime> {
let obj_last = self
.objs
.values()
.map(Reverse)
.sorted()
.next()
.map(|Reverse(obj)| obj.offset);
let bpm_last = self.bpm_changes.last_key_value().map(|(&time, _)| time);
let section_len_last =
self.section_len_changes
.last_key_value()
.map(|(&time, _)| ObjTime {
track: time,
numerator: 0,
denominator: 4,
});
let stop_last = self.stops.last_key_value().map(|(&time, _)| time);
let bga_last = self.bga_changes.last_key_value().map(|(&time, _)| time);
[obj_last, bpm_last, section_len_last, stop_last, bga_last]
.into_iter()
.max()
.flatten()
}
pub fn resolution_for_pulses(&self) -> u32 {
use num::Integer;
let mut hyp_resolution = 1;
for obj in self.objs.values() {
hyp_resolution = hyp_resolution.lcm(&obj.offset.denominator);
}
for bpm_change in self.bpm_changes.values() {
hyp_resolution = hyp_resolution.lcm(&bpm_change.time.denominator);
}
hyp_resolution
}
}
fn ids_from_message(
track: command::Track,
message: &'_ str,
) -> impl Iterator<Item = (ObjTime, ObjId)> + '_ {
let denominator = message.len() as u32 / 2;
let mut chars = message.chars().tuples().enumerate();
std::iter::from_fn(move || {
let (i, id) = loop {
let (i, (c1, c2)) = chars.next()?;
let id = c1.to_digit(36).unwrap() * 36 + c2.to_digit(36).unwrap();
if id != 0 {
break (i, id);
}
};
let obj = (id as u16).try_into().unwrap();
let time = ObjTime::new(track.0, i as u32, denominator);
Some((time, obj))
})
}
#[cfg(feature = "serde")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NotesPack {
pub objs: Vec<Obj>,
pub bpm_changes: Vec<BpmChangeObj>,
pub section_len_changes: Vec<SectionLenChangeObj>,
pub stops: Vec<StopObj>,
pub bga_changes: Vec<BgaObj>,
}
#[cfg(feature = "serde")]
impl serde::Serialize for Notes {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
NotesPack {
objs: self.all_notes().cloned().collect(),
bpm_changes: self.bpm_changes.values().cloned().collect(),
section_len_changes: self.section_len_changes.values().cloned().collect(),
stops: self.stops.values().cloned().collect(),
bga_changes: self.bga_changes.values().cloned().collect(),
}
.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Notes {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let pack = NotesPack::deserialize(deserializer)?;
let mut objs = HashMap::new();
let mut bgms: BTreeMap<ObjTime, Vec<ObjId>> = BTreeMap::new();
let mut ids_by_key: HashMap<Key, BTreeMap<ObjTime, ObjId>> = HashMap::new();
for obj in pack.objs {
if matches!(obj.kind, NoteKind::Invisible) {
bgms.entry(obj.offset)
.and_modify(|ids| ids.push(obj.obj))
.or_default();
}
ids_by_key
.entry(obj.key)
.and_modify(|id_map| {
id_map.insert(obj.offset, obj.obj);
})
.or_default();
objs.insert(obj.obj, obj);
}
let mut bpm_changes = BTreeMap::new();
for bpm_change in pack.bpm_changes {
bpm_changes.insert(bpm_change.time, bpm_change);
}
let mut section_len_changes = BTreeMap::new();
for section_len_change in pack.section_len_changes {
section_len_changes.insert(section_len_change.track, section_len_change);
}
let mut stops = BTreeMap::new();
for stop in pack.stops {
stops.insert(stop.time, stop);
}
let mut bga_changes = BTreeMap::new();
for bga_change in pack.bga_changes {
bga_changes.insert(bga_change.time, bga_change);
}
Ok(Notes {
objs,
bgms,
ids_by_key,
bpm_changes,
section_len_changes,
stops,
bga_changes,
})
}
}