1use std::{
2 collections::{BTreeMap, HashMap},
3 marker::PhantomData,
4 ops::Bound,
5 path::PathBuf,
6};
7
8use itertools::Itertools;
9
10use crate::{
11 bms::prelude::*,
12 parse::{Result, prompt::ChannelDuplication},
13};
14
15#[derive(Debug, Default, Clone, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17struct WavObjArena(Vec<WavObj>);
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct WavObjArenaIndex(usize);
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct Notes<T> {
27 pub wav_path_root: Option<PathBuf>,
30 pub wav_files: HashMap<ObjId, PathBuf>,
32 arena: WavObjArena,
34 idx_by_wav_id: HashMap<ObjId, Vec<WavObjArenaIndex>>,
36 idx_by_channel: HashMap<NoteChannelId, Vec<WavObjArenaIndex>>,
38 idx_by_time: BTreeMap<ObjTime, Vec<WavObjArenaIndex>>,
40 #[cfg(feature = "minor-command")]
42 pub midi_file: Option<PathBuf>,
43 #[cfg(feature = "minor-command")]
45 pub materials_wav: Vec<PathBuf>,
46 pub bgm_volume_changes: BTreeMap<ObjTime, BgmVolumeObj>,
48 pub key_volume_changes: BTreeMap<ObjTime, KeyVolumeObj>,
50 #[cfg(feature = "minor-command")]
52 pub seek_events: BTreeMap<ObjTime, SeekObj>,
53 pub text_events: BTreeMap<ObjTime, TextObj>,
55 pub judge_events: BTreeMap<ObjTime, JudgeObj>,
57 #[cfg(feature = "minor-command")]
59 pub bga_keybound_events: BTreeMap<ObjTime, BgaKeyboundObj>,
60 #[cfg(feature = "minor-command")]
62 pub option_events: BTreeMap<ObjTime, OptionObj>,
63 _marker: PhantomData<fn() -> T>,
64}
65
66impl<T> Default for Notes<T> {
67 fn default() -> Self {
68 Self {
69 wav_path_root: Default::default(),
70 wav_files: Default::default(),
71 arena: Default::default(),
72 idx_by_wav_id: Default::default(),
73 idx_by_channel: Default::default(),
74 idx_by_time: Default::default(),
75 #[cfg(feature = "minor-command")]
76 midi_file: Default::default(),
77 #[cfg(feature = "minor-command")]
78 materials_wav: Default::default(),
79 bgm_volume_changes: Default::default(),
80 key_volume_changes: Default::default(),
81 #[cfg(feature = "minor-command")]
82 seek_events: Default::default(),
83 text_events: Default::default(),
84 judge_events: Default::default(),
85 #[cfg(feature = "minor-command")]
86 bga_keybound_events: Default::default(),
87 #[cfg(feature = "minor-command")]
88 option_events: Default::default(),
89 _marker: PhantomData,
90 }
91 }
92}
93
94impl<T> Notes<T> {
96 #[must_use]
98 pub fn is_empty(&self) -> bool {
99 self.all_notes().all(|obj| obj.wav_id.is_null())
100 }
101
102 #[must_use]
104 pub fn into_all_notes(self) -> Vec<WavObj> {
105 self.arena.0
106 }
107
108 pub fn all_notes(&self) -> impl Iterator<Item = &WavObj> {
110 self.arena.0.iter().sorted()
111 }
112
113 pub fn all_entries(&self) -> impl Iterator<Item = (WavObjArenaIndex, &WavObj)> {
115 self.arena
116 .0
117 .iter()
118 .enumerate()
119 .sorted_by_key(|obj| obj.1)
120 .map(|(idx, obj)| (WavObjArenaIndex(idx), obj))
121 }
122
123 pub fn playables(&self) -> impl Iterator<Item = &WavObj>
125 where
126 T: KeyLayoutMapper,
127 {
128 self.arena.0.iter().sorted().filter(|obj| {
129 obj.channel_id
130 .try_into_map::<T>()
131 .is_some_and(|map| map.kind().is_playable())
132 })
133 }
134
135 pub fn displayables(&self) -> impl Iterator<Item = &WavObj>
137 where
138 T: KeyLayoutMapper,
139 {
140 self.arena.0.iter().sorted().filter(|obj| {
141 obj.channel_id
142 .try_into_map::<T>()
143 .is_some_and(|map| map.kind().is_displayable())
144 })
145 }
146
147 pub fn bgms(&self) -> impl Iterator<Item = &WavObj>
149 where
150 T: KeyLayoutMapper,
151 {
152 self.arena.0.iter().sorted().filter(|obj| {
153 obj.channel_id
154 .try_into_map::<T>()
155 .is_none_or(|map| !map.kind().is_displayable())
156 })
157 }
158
159 pub fn notes_on(
161 &self,
162 channel_id: NoteChannelId,
163 ) -> impl Iterator<Item = (WavObjArenaIndex, &WavObj)>
164 where
165 T: KeyLayoutMapper,
166 {
167 self.idx_by_channel
168 .get(&channel_id)
169 .into_iter()
170 .flatten()
171 .map(|&arena_index| (arena_index, &self.arena.0[arena_index.0]))
172 }
173
174 pub fn notes_in<R: std::ops::RangeBounds<ObjTime>>(
176 &self,
177 time_span: R,
178 ) -> impl DoubleEndedIterator<Item = (WavObjArenaIndex, &WavObj)> {
179 self.idx_by_time
180 .range(time_span)
181 .flat_map(|(_, indexes)| indexes)
182 .map(|&arena_index| (arena_index, &self.arena.0[arena_index.0]))
183 }
184
185 #[must_use]
187 pub fn next_obj_by_key(&self, channel_id: NoteChannelId, time: ObjTime) -> Option<&WavObj> {
188 self.notes_in((Bound::Excluded(time), Bound::Unbounded))
189 .map(|(_, obj)| obj)
190 .find(|obj| obj.channel_id == channel_id)
191 }
192
193 #[must_use]
195 pub fn last_obj_time(&self) -> Option<ObjTime> {
196 let (&time, _) = self.idx_by_time.last_key_value()?;
197 Some(time)
198 }
199
200 #[must_use]
202 pub fn last_playable_time(&self) -> Option<ObjTime>
203 where
204 T: KeyLayoutMapper,
205 {
206 self.notes_in(..)
207 .map(|(_, obj)| obj)
208 .rev()
209 .find(|obj| {
210 obj.channel_id
211 .try_into_map::<T>()
212 .is_some_and(|map| map.kind().is_displayable())
213 })
214 .map(|obj| obj.offset)
215 }
216
217 #[must_use]
221 pub fn last_bgm_time(&self) -> Option<ObjTime>
222 where
223 T: KeyLayoutMapper,
224 {
225 self.notes_in(..)
226 .map(|(_, obj)| obj)
227 .rev()
228 .find(|obj| {
229 obj.channel_id
230 .try_into_map::<T>()
231 .is_none_or(|map| !map.kind().is_displayable())
232 })
233 .map(|obj| obj.offset)
234 }
235}
236
237impl<T> Notes<T> {
239 pub fn push_note(&mut self, note: WavObj) {
241 let new_index = WavObjArenaIndex(self.arena.0.len());
242 self.idx_by_wav_id
243 .entry(note.wav_id)
244 .or_default()
245 .push(new_index);
246 self.idx_by_channel
247 .entry(note.channel_id)
248 .or_default()
249 .push(new_index);
250 self.idx_by_time
251 .entry(note.offset)
252 .or_default()
253 .push(new_index);
254 self.arena.0.push(note);
255 }
256
257 fn remove_index(&mut self, idx: usize, removing: &WavObj) {
258 let channel_id = removing.channel_id;
259 if let Some(ids_by_channel_idx) = self.idx_by_channel[&channel_id]
260 .iter()
261 .position(|id| id.0 == idx)
262 {
263 self.idx_by_channel
264 .get_mut(&channel_id)
265 .unwrap()
266 .swap_remove(ids_by_channel_idx);
267 }
268 if let Some(ids_by_time_idx) = self.idx_by_time[&removing.offset]
269 .iter()
270 .position(|id| id.0 == idx)
271 {
272 self.idx_by_time
273 .get_mut(&removing.offset)
274 .unwrap()
275 .swap_remove(ids_by_time_idx);
276 }
277 }
278
279 pub fn pop_note(&mut self) -> Option<WavObj> {
281 let last_idx = self.arena.0.len();
282 let last = self.arena.0.pop()?;
283 if let Some(ids_by_wav_id_idx) = self.idx_by_wav_id[&last.wav_id]
284 .iter()
285 .position(|id| id.0 == last_idx)
286 {
287 self.idx_by_wav_id
288 .get_mut(&last.wav_id)?
289 .swap_remove(ids_by_wav_id_idx);
290 }
291 let channel_id = last.channel_id;
292 if let Some(ids_by_channel_idx) = self.idx_by_channel[&channel_id]
293 .iter()
294 .position(|id| id.0 == last_idx)
295 {
296 self.idx_by_channel
297 .get_mut(&channel_id)?
298 .swap_remove(ids_by_channel_idx);
299 }
300 if let Some(ids_by_time_idx) = self.idx_by_time[&last.offset]
301 .iter()
302 .position(|id| id.0 == last_idx)
303 {
304 self.idx_by_time
305 .get_mut(&last.offset)?
306 .swap_remove(ids_by_time_idx);
307 }
308 Some(last)
309 }
310
311 pub fn remove_note(&mut self, wav_id: ObjId) -> Vec<WavObj>
313 where
314 T: KeyLayoutMapper,
315 {
316 let Some(indexes) = self.idx_by_wav_id.remove(&wav_id) else {
317 return vec![];
318 };
319 let mut objs = Vec::with_capacity(indexes.len());
320 for WavObjArenaIndex(idx) in indexes {
321 let removing = std::mem::replace(&mut self.arena.0[idx], WavObj::dangling());
322 self.remove_index(idx, &removing);
323 objs.push(removing);
324 }
325 objs
326 }
327
328 pub fn pop_by_idx(&mut self, idx: WavObjArenaIndex) -> Option<WavObj> {
330 let removing = std::mem::replace(self.arena.0.get_mut(idx.0)?, WavObj::dangling());
331 self.remove_index(idx.0, &removing);
332 Some(removing)
333 }
334
335 pub fn pop_latest_of(&mut self, wav_id: ObjId) -> Option<WavObj>
337 where
338 T: KeyLayoutMapper,
339 {
340 let &WavObjArenaIndex(to_pop) = self.idx_by_wav_id.get(&wav_id)?.last()?;
341 let removing = std::mem::replace(&mut self.arena.0[to_pop], WavObj::dangling());
342 self.remove_index(to_pop, &removing);
343 Some(removing)
344 }
345
346 pub fn push_bgm(&mut self, time: ObjTime, wav_id: ObjId)
348 where
349 T: KeyLayoutMapper,
350 {
351 self.push_note(WavObj {
352 offset: time,
353 channel_id: NoteChannelId::bgm(),
354 wav_id,
355 });
356 }
357
358 pub fn push_wav<P: Into<PathBuf>>(&mut self, wav_id: ObjId, path: P) -> Option<PathBuf> {
360 self.wav_files.insert(wav_id, path.into())
361 }
362
363 pub fn remove_wav(&mut self, wav_id: &ObjId) -> Option<PathBuf> {
365 self.wav_files.remove(wav_id)
366 }
367
368 pub fn retain_notes<F: FnMut(&WavObj) -> bool>(&mut self, mut cond: F)
370 where
371 T: KeyLayoutMapper,
372 {
373 let removing_indexes: Vec<_> = self
374 .arena
375 .0
376 .iter()
377 .enumerate()
378 .filter(|&(_, obj)| cond(obj))
379 .map(|(i, _)| i)
380 .collect();
381 for removing_idx in removing_indexes {
382 let removing = std::mem::replace(&mut self.arena.0[removing_idx], WavObj::dangling());
383 self.remove_index(removing_idx, &removing);
384 }
385 }
386
387 pub fn dup_note_into(&mut self, src: ObjId, at: ObjTime, dst: NoteChannelId) {
389 let Some(src_obj) = self
390 .idx_by_wav_id
391 .get(&src)
392 .into_iter()
393 .flatten()
394 .map(|idx| &self.arena.0[idx.0])
395 .find(|obj| obj.offset == at)
396 else {
397 return;
398 };
399 let new = WavObj {
400 channel_id: dst,
401 ..*src_obj
402 };
403 self.push_note(new);
404 }
405
406 pub fn push_bgm_volume_change(
408 &mut self,
409 volume_obj: BgmVolumeObj,
410 prompt_handler: &mut impl PromptHandler,
411 ) -> Result<()> {
412 match self.bgm_volume_changes.entry(volume_obj.time) {
413 std::collections::btree_map::Entry::Vacant(entry) => {
414 entry.insert(volume_obj);
415 Ok(())
416 }
417 std::collections::btree_map::Entry::Occupied(mut entry) => {
418 let existing = entry.get();
419
420 prompt_handler
421 .handle_channel_duplication(ChannelDuplication::BgmVolumeChangeEvent {
422 time: volume_obj.time,
423 older: existing,
424 newer: &volume_obj,
425 })
426 .apply_channel(
427 entry.get_mut(),
428 volume_obj.clone(),
429 volume_obj.time,
430 Channel::BgmVolume,
431 )
432 }
433 }
434 }
435
436 pub fn push_key_volume_change(
438 &mut self,
439 volume_obj: KeyVolumeObj,
440 prompt_handler: &mut impl PromptHandler,
441 ) -> Result<()> {
442 match self.key_volume_changes.entry(volume_obj.time) {
443 std::collections::btree_map::Entry::Vacant(entry) => {
444 entry.insert(volume_obj);
445 Ok(())
446 }
447 std::collections::btree_map::Entry::Occupied(mut entry) => {
448 let existing = entry.get();
449
450 prompt_handler
451 .handle_channel_duplication(ChannelDuplication::KeyVolumeChangeEvent {
452 time: volume_obj.time,
453 older: existing,
454 newer: &volume_obj,
455 })
456 .apply_channel(
457 entry.get_mut(),
458 volume_obj.clone(),
459 volume_obj.time,
460 Channel::KeyVolume,
461 )
462 }
463 }
464 }
465
466 #[cfg(feature = "minor-command")]
468 pub fn push_seek_event(
469 &mut self,
470 seek_obj: SeekObj,
471 prompt_handler: &mut impl PromptHandler,
472 ) -> Result<()> {
473 match self.seek_events.entry(seek_obj.time) {
474 std::collections::btree_map::Entry::Vacant(entry) => {
475 entry.insert(seek_obj);
476 Ok(())
477 }
478 std::collections::btree_map::Entry::Occupied(mut entry) => {
479 let existing = entry.get();
480
481 prompt_handler
482 .handle_channel_duplication(ChannelDuplication::SeekMessageEvent {
483 time: seek_obj.time,
484 older: existing,
485 newer: &seek_obj,
486 })
487 .apply_channel(
488 entry.get_mut(),
489 seek_obj.clone(),
490 seek_obj.time,
491 Channel::Seek,
492 )
493 }
494 }
495 }
496
497 pub fn push_text_event(
499 &mut self,
500 text_obj: TextObj,
501 prompt_handler: &mut impl PromptHandler,
502 ) -> Result<()> {
503 match self.text_events.entry(text_obj.time) {
504 std::collections::btree_map::Entry::Vacant(entry) => {
505 entry.insert(text_obj);
506 Ok(())
507 }
508 std::collections::btree_map::Entry::Occupied(mut entry) => {
509 let existing = entry.get();
510
511 prompt_handler
512 .handle_channel_duplication(ChannelDuplication::TextEvent {
513 time: text_obj.time,
514 older: existing,
515 newer: &text_obj,
516 })
517 .apply_channel(
518 entry.get_mut(),
519 text_obj.clone(),
520 text_obj.time,
521 Channel::Text,
522 )
523 }
524 }
525 }
526
527 pub fn push_judge_event(
529 &mut self,
530 judge_obj: JudgeObj,
531 prompt_handler: &mut impl PromptHandler,
532 ) -> Result<()> {
533 match self.judge_events.entry(judge_obj.time) {
534 std::collections::btree_map::Entry::Vacant(entry) => {
535 entry.insert(judge_obj);
536 Ok(())
537 }
538 std::collections::btree_map::Entry::Occupied(mut entry) => {
539 let existing = entry.get();
540
541 prompt_handler
542 .handle_channel_duplication(ChannelDuplication::JudgeEvent {
543 time: judge_obj.time,
544 older: existing,
545 newer: &judge_obj,
546 })
547 .apply_channel(
548 entry.get_mut(),
549 judge_obj.clone(),
550 judge_obj.time,
551 Channel::Judge,
552 )
553 }
554 }
555 }
556
557 #[cfg(feature = "minor-command")]
559 pub fn push_bga_keybound_event(
560 &mut self,
561 keybound_obj: BgaKeyboundObj,
562 prompt_handler: &mut impl PromptHandler,
563 ) -> Result<()> {
564 match self.bga_keybound_events.entry(keybound_obj.time) {
565 std::collections::btree_map::Entry::Vacant(entry) => {
566 entry.insert(keybound_obj);
567 Ok(())
568 }
569 std::collections::btree_map::Entry::Occupied(mut entry) => {
570 let existing = entry.get();
571
572 prompt_handler
573 .handle_channel_duplication(ChannelDuplication::BgaKeyboundEvent {
574 time: keybound_obj.time,
575 older: existing,
576 newer: &keybound_obj,
577 })
578 .apply_channel(
579 entry.get_mut(),
580 keybound_obj.clone(),
581 keybound_obj.time,
582 Channel::BgaKeybound,
583 )
584 }
585 }
586 }
587
588 #[cfg(feature = "minor-command")]
590 pub fn push_option_event(
591 &mut self,
592 option_obj: OptionObj,
593 prompt_handler: &mut impl PromptHandler,
594 ) -> Result<()> {
595 match self.option_events.entry(option_obj.time) {
596 std::collections::btree_map::Entry::Vacant(entry) => {
597 entry.insert(option_obj);
598 Ok(())
599 }
600 std::collections::btree_map::Entry::Occupied(mut entry) => {
601 let existing = entry.get();
602
603 prompt_handler
604 .handle_channel_duplication(ChannelDuplication::OptionEvent {
605 time: option_obj.time,
606 older: existing,
607 newer: &option_obj,
608 })
609 .apply_channel(
610 entry.get_mut(),
611 option_obj.clone(),
612 option_obj.time,
613 Channel::Option,
614 )
615 }
616 }
617 }
618}
619
620impl<T> Notes<T> {
622 pub fn change_note_channel<I>(&mut self, targets: I, dst: NoteChannelId)
624 where
625 T: KeyLayoutMapper,
626 I: IntoIterator<Item = WavObjArenaIndex>,
627 {
628 for target in targets {
629 let Some(obj) = self.arena.0.get_mut(target.0) else {
630 continue;
631 };
632
633 let src = obj.channel_id;
635 if let Some(idx_by_channel_idx) = self.idx_by_channel[&src]
636 .iter()
637 .position(|&idx| idx == target)
638 {
639 self.idx_by_channel
640 .get_mut(&src)
641 .unwrap()
642 .swap_remove(idx_by_channel_idx);
643 }
644 self.idx_by_channel.entry(dst).or_default().push(target);
645
646 obj.channel_id = dst;
648 }
649 }
650
651 pub fn change_note_time(
653 &mut self,
654 target: WavObjArenaIndex,
655 new_time: ObjTime,
656 ) -> Option<ObjTime> {
657 let to_change = self.arena.0.get_mut(target.0)?;
658 let old_time = to_change.offset;
659 if old_time == new_time {
660 return Some(new_time);
661 }
662
663 let idx_by_time = self.idx_by_time[&old_time]
664 .iter()
665 .position(|&idx| idx == target)?;
666 self.idx_by_time
667 .get_mut(&to_change.offset)?
668 .swap_remove(idx_by_time);
669 self.idx_by_time.entry(new_time).or_default().push(target);
670 to_change.offset = new_time;
671 Some(old_time)
672 }
673}
674
675#[cfg(test)]
676mod tests {
677 use super::Notes;
678 use crate::bms::prelude::*;
679
680 #[test]
681 fn push_and_pop() {
682 let mut notes = Notes::<KeyLayoutBeat>::default();
683 let note = WavObj {
684 offset: ObjTime::new(1, 2, 4),
685 channel_id: NoteChannelId::bgm(),
686 wav_id: "01".try_into().unwrap(),
687 };
688
689 assert!(notes.pop_note().is_none());
690
691 notes.push_note(note.clone());
692 let removed = notes.pop_note();
693 assert_eq!(Some(note), removed);
694
695 assert!(notes.pop_note().is_none());
696 }
697
698 #[test]
699 fn change_note_channel() {
700 let mut notes = Notes::<KeyLayoutBeat>::default();
701 let note = WavObj {
702 offset: ObjTime::new(1, 2, 4),
703 channel_id: NoteChannelId::bgm(),
704 wav_id: "01".try_into().unwrap(),
705 };
706
707 assert!(notes.pop_note().is_none());
708
709 notes.push_note(note.clone());
710 let (idx, _) = notes.all_entries().next().unwrap();
711 notes.change_note_channel(
712 [idx],
713 KeyLayoutBeat::new(PlayerSide::Player1, NoteKind::Visible, Key::Key(1)).to_channel_id(),
714 );
715
716 assert_eq!(
717 notes.all_notes().next(),
718 Some(&WavObj {
719 offset: ObjTime::new(1, 2, 4),
720 channel_id: KeyLayoutBeat::new(PlayerSide::Player1, NoteKind::Visible, Key::Key(1))
721 .to_channel_id(),
722 wav_id: "01".try_into().unwrap(),
723 })
724 );
725 }
726
727 #[test]
728 fn change_note_time() {
729 let mut notes = Notes::<KeyLayoutBeat>::default();
730 let note = WavObj {
731 offset: ObjTime::new(1, 2, 4),
732 channel_id: NoteChannelId::bgm(),
733 wav_id: "01".try_into().unwrap(),
734 };
735
736 assert!(notes.pop_note().is_none());
737
738 notes.push_note(note.clone());
739 let (idx, _) = notes.all_entries().next().unwrap();
740 notes.change_note_time(idx, ObjTime::new(1, 1, 4));
741
742 assert_eq!(
743 notes.all_notes().next(),
744 Some(&WavObj {
745 offset: ObjTime::new(1, 1, 4),
746 channel_id: NoteChannelId::bgm(),
747 wav_id: "01".try_into().unwrap(),
748 })
749 );
750 }
751}