Skip to content

Commit d4e0b41

Browse files
committed
added BpmList component for time <-> beat convertion
1 parent 456b752 commit d4e0b41

File tree

6 files changed

+116
-20
lines changed

6 files changed

+116
-20
lines changed

src/chart/beat.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
use std::{cmp::Ordering, ops::Sub};
1+
use std::{cmp::Ordering, ops::{Add, Sub}};
22

3-
use num::{Rational32, FromPrimitive};
3+
use num::{FromPrimitive, Rational32};
44

55
#[derive(Clone, Copy, Debug)]
6-
pub struct Beat(i32, pub Rational32);
6+
pub struct Beat(i32, Rational32);
7+
8+
impl Beat {
9+
pub const ZERO: Self = Beat(0, Rational32::ZERO);
10+
}
711

812
impl Into<f32> for Beat {
913
fn into(self) -> f32 {
@@ -59,6 +63,14 @@ impl Sub for Beat {
5963
}
6064
}
6165

66+
impl Add for Beat {
67+
type Output = Self;
68+
69+
fn add(self, rhs: Self) -> Self::Output {
70+
Self(self.0 + rhs.0, self.1 + rhs.1)
71+
}
72+
}
73+
6274
impl PartialEq for Beat {
6375
fn eq(&self, other: &Self) -> bool {
6476
self.0 == other.0 && self.1 == other.1

src/hit_sound.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bevy_kira_audio::prelude::*;
33

44
use crate::{
55
chart::note::{Note, NoteKind},
6-
timing::ChartTime,
6+
timing::{BpmList, ChartTime},
77
};
88

99
pub struct HitSoundPlugin;
@@ -20,9 +20,14 @@ struct PlayHitSound(NoteKind);
2020
#[derive(Component, Debug)]
2121
struct PlayedHitSound;
2222

23-
fn add_marker_system(mut commands: Commands, query: Query<(&Note, Entity), Without<PlayedHitSound>>, time: Res<ChartTime>) {
23+
fn add_marker_system(
24+
mut commands: Commands,
25+
query: Query<(&Note, Entity), Without<PlayedHitSound>>,
26+
time: Res<ChartTime>,
27+
bpm_list: Res<BpmList>,
28+
) {
2429
for (note, entity) in &query {
25-
if note.beat.value() * (60.0 / 174.0) < time.0 {
30+
if bpm_list.time_at(note.beat) < time.0 {
2631
commands.entity(entity).insert(PlayHitSound(note.kind));
2732
}
2833
}

src/loader/official.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
event::{LineEvent, LineEventBundle, LineEventKind},
55
line::LineBundle,
66
note::NoteBundle,
7-
}, constants::{CANVAS_HEIGHT, CANVAS_WIDTH}, selection::SelectedLine
7+
}, constants::{CANVAS_HEIGHT, CANVAS_WIDTH}, selection::SelectedLine, timing::{BpmList, BpmPoint}
88
};
99

1010
use super::Loader;
@@ -71,6 +71,8 @@ struct SpeedEvent {
7171

7272
#[derive(Serialize, Deserialize, Debug)]
7373
struct Line {
74+
bpm: f32,
75+
7476
#[serde(rename(deserialize = "judgeLineMoveEvents"))]
7577
move_events: Vec<PositionLineEvent>,
7678
#[serde(rename(deserialize = "judgeLineRotateEvents"))]
@@ -98,6 +100,10 @@ pub struct OfficialLoader;
98100
impl Loader for OfficialLoader {
99101
fn load(file: std::fs::File, mut commands: Commands) {
100102
let chart: Chart = serde_json::from_reader(file).expect("Failed to load chart");
103+
104+
let first_line = chart.lines.first().expect("The chart should has at least one line");
105+
commands.insert_resource(BpmList::new(vec![BpmPoint::new(Beat::ZERO, first_line.bpm)]));
106+
101107
let mut first_line_id: Option<Entity> = None;
102108
for line in chart.lines.iter() {
103109
let t: fn(f32) -> Beat = |x| Beat::from(x * 1.875 / 60.0);

src/main.rs

+16-8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use bevy_mod_picking::prelude::*;
4040
use constants::{CANVAS_HEIGHT, CANVAS_WIDTH};
4141
use egui_dock::{DockArea, DockState, NodeIndex, Style};
4242
use num::{FromPrimitive, Rational32};
43+
use timing::BpmList;
4344

4445
fn main() {
4546
App::new()
@@ -306,8 +307,9 @@ fn update_note_system(
306307
mut query: Query<(&mut Transform, &mut Sprite, &Note)>,
307308
game_viewport: Res<GameViewport>,
308309
time: Res<ChartTime>,
310+
bpm_list: Res<BpmList>,
309311
) {
310-
let beat = time.0 / (60.0 / 174.0);
312+
let beat = bpm_list.beat_at(time.0);
311313
for (mut transform, mut sprite, note) in &mut query {
312314
transform.translation.x = (note.x / CANVAS_WIDTH) * game_viewport.0.width()
313315
/ (game_viewport.0.width() * 3.0 / 1920.0);
@@ -316,7 +318,7 @@ fn update_note_system(
316318
} else {
317319
0.0
318320
};
319-
sprite.color = Color::WHITE.with_a(if note.beat.value() + hold_beat < beat {
321+
sprite.color = Color::WHITE.with_a(if note.beat.value() + hold_beat < beat.into() {
320322
0.0
321323
} else {
322324
1.0
@@ -337,8 +339,9 @@ fn compute_line_system(
337339
With<Line>,
338340
>,
339341
time: Res<ChartTime>,
342+
bpm_list: Res<BpmList>,
340343
) {
341-
let beat = time.0 / (60.0 / 174.0);
344+
let beat: f32 = bpm_list.beat_at(time.0).into();
342345
for (mut position, mut rotation, mut opacity, entity) in &mut line_query {
343346
let mut events: Vec<_> = event_query.iter().filter(|e| e.line_id == entity).collect();
344347
events.sort_by_key(|e| e.start_beat);
@@ -391,6 +394,7 @@ fn update_note_y_system(
391394
speed_event_query: Query<(&SpeedEvent, &LineEvent)>,
392395
mut note_query: Query<(&mut Transform, &mut Sprite, &Note)>,
393396
time: Res<ChartTime>,
397+
bpm_list: Res<BpmList>,
394398
) {
395399
let all_speed_events: Vec<(&SpeedEvent, &LineEvent)> = speed_event_query.iter().collect();
396400
for (children, entity) in &query {
@@ -410,12 +414,12 @@ fn update_note_y_system(
410414
let current_distance = distance(time.0);
411415
for child in children {
412416
if let Ok((mut transform, mut sprite, note)) = note_query.get_mut(*child) {
413-
let mut y = distance(note.beat.value() * (60.0 / 174.0)) - current_distance;
417+
let mut y = distance(bpm_list.time_at(note.beat)) - current_distance;
414418
match note.kind {
415419
NoteKind::Hold { hold_beat } => {
416420
y = y.max(0.0);
417421
let height = distance(
418-
note.beat.value() * (60.0 / 174.0) + hold_beat.value() * (60.0 / 174.0),
422+
bpm_list.time_at(note.beat + hold_beat),
419423
) - current_distance
420424
- y;
421425
sprite.anchor = Anchor::BottomCenter;
@@ -478,13 +482,17 @@ impl SpeedEvent {
478482
}
479483
}
480484

481-
fn calculate_speed_events_system(mut commands: Commands, query: Query<(&LineEvent, Entity)>) {
485+
fn calculate_speed_events_system(
486+
mut commands: Commands,
487+
query: Query<(&LineEvent, Entity)>,
488+
bpm_list: Res<BpmList>,
489+
) {
482490
for (event, entity) in &query {
483491
match event.kind {
484492
LineEventKind::Speed => {
485493
commands.entity(entity).insert(SpeedEvent::new(
486-
event.start_beat.value() * (60.0 / 174.0),
487-
event.end_beat.value() * (60.0 / 174.0),
494+
bpm_list.time_at(event.start_beat),
495+
bpm_list.time_at(event.end_beat),
488496
event.start,
489497
event.end,
490498
));

src/tab/timeline.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
chart::{
77
event::LineEvent,
88
note::{Note, NoteKind},
9-
}, constants::CANVAS_WIDTH, misc::WorkingDirectory, selection::{SelectNoteEvent, Selected, SelectedLine}, timing::ChartTime
9+
}, constants::CANVAS_WIDTH, misc::WorkingDirectory, selection::{SelectNoteEvent, Selected, SelectedLine}, timing::{BpmList, ChartTime}
1010
};
1111

1212
pub struct TimelineTabPlugin;
@@ -22,6 +22,7 @@ pub fn timeline_ui_system(
2222
selected_line_query: Res<SelectedLine>,
2323
timeline_viewport: Res<TimelineViewport>,
2424
time: Res<ChartTime>,
25+
bpm_list: Res<BpmList>,
2526
event_query: Query<&LineEvent>,
2627
note_query: Query<(&Note, &Parent, Entity, Option<&Selected>)>,
2728
working_dir: Res<WorkingDirectory>,
@@ -61,12 +62,12 @@ pub fn timeline_ui_system(
6162
let x = event_timeline_viewport.width() / 5.0 * track as f32
6263
- event_timeline_viewport.width() / 5.0 / 2.0
6364
+ event_timeline_viewport.min.x;
64-
let y: f32 = (time - event.start_beat.value() * (60.0 / 174.0)) * 400.0 * 2.0
65+
let y: f32 = (time - bpm_list.time_at(event.start_beat)) * 400.0 * 2.0
6566
+ event_timeline_viewport.height() * 0.9;
6667

6768
let size = egui::Vec2::new(
6869
event_timeline_viewport.width() / 8000.0 * 989.0,
69-
event.duration().value() * (60.0 / 174.0) * 400.0 * 2.0,
70+
bpm_list.time_at(event.duration()) * 400.0 * 2.0,
7071
);
7172

7273
let center = egui::Pos2::new(x, y - size.y / 2.0);
@@ -87,7 +88,7 @@ pub fn timeline_ui_system(
8788
}
8889

8990
let x = (note.x / CANVAS_WIDTH + 0.5) * note_timeline_viewport.width();
90-
let y: f32 = (time - note.beat.value() * (60.0 / 174.0)) * 400.0 * 2.0
91+
let y: f32 = (time - bpm_list.time_at(note.beat)) * 400.0 * 2.0
9192
+ note_timeline_viewport.height() * 0.9;
9293

9394
let image = match note.kind {
@@ -109,7 +110,7 @@ pub fn timeline_ui_system(
109110
let size = match note.kind {
110111
NoteKind::Hold { hold_beat } => egui::Vec2::new(
111112
note_timeline_viewport.width() / 8000.0 * image_size.x,
112-
hold_beat.value() * (60.0 / 174.0) * 400.0 * 2.0,
113+
bpm_list.time_at(hold_beat) * 400.0 * 2.0,
113114
),
114115
_ => egui::Vec2::new(
115116
note_timeline_viewport.width() / 8000.0 * image_size.x,

src/timing.rs

+64
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use bevy::prelude::*;
22

3+
use crate::chart::beat::Beat;
4+
35
/// Represents the current time in seconds
46
#[derive(Resource)]
57
pub struct ChartTime(pub f32);
@@ -43,3 +45,65 @@ fn space_pause_resume_control(
4345
}
4446
}
4547
}
48+
49+
#[derive(Debug)]
50+
pub struct BpmPoint {
51+
beat: Beat,
52+
bpm: f32,
53+
54+
time: f32,
55+
}
56+
57+
impl BpmPoint {
58+
pub fn new(beat: Beat, bpm: f32) -> Self {
59+
Self {
60+
beat,
61+
bpm,
62+
time: 0.0,
63+
}
64+
}
65+
}
66+
67+
#[derive(Resource, Debug)]
68+
pub struct BpmList(Vec<BpmPoint>);
69+
70+
impl BpmList {
71+
pub fn new(points: Vec<BpmPoint>) -> Self {
72+
let mut list = Self(points);
73+
list.compute();
74+
list
75+
}
76+
77+
fn compute(&mut self) {
78+
let mut time = 0.0;
79+
let mut last_beat = 0.0;
80+
let mut last_bpm = -1.0;
81+
for point in &mut self.0 {
82+
if last_bpm != -1.0 {
83+
time += (point.beat.value() - last_beat) * (60.0 / last_bpm);
84+
}
85+
last_beat = point.beat.value();
86+
last_bpm = point.bpm;
87+
point.time = time;
88+
}
89+
}
90+
91+
pub fn time_at(&self, beat: Beat) -> f32 {
92+
let point = self.0.iter()
93+
.take_while(|p| p.beat.value() < beat.value())
94+
.last()
95+
.or_else(|| self.0.first())
96+
.expect("No bpm points available");
97+
98+
point.time + (beat.value() - point.beat.value()) * (60.0 / point.bpm)
99+
}
100+
101+
pub fn beat_at(&self, time: f32) -> Beat {
102+
let point = self.0.iter()
103+
.take_while(|p| p.time <= time)
104+
.last()
105+
.expect("No bpm points available");
106+
107+
Beat::from(point.beat.value() + (time - point.time) * point.bpm / 60.0)
108+
}
109+
}

0 commit comments

Comments
 (0)