Skip to main content

Overview

The BPM system manages tempo changes in charts and provides conversion between beats and real time. Phichain supports multiple BPM changes throughout a chart.

Types

BpmList

A list of BPM change points with automatic time calculation.
0
Vec<BpmPoint>
required
Vector of BPM points, sorted by beat position

BpmPoint

A single BPM change at a specific beat.
beat
Beat
required
The beat position where this BPM takes effect
bpm
f32
required
The tempo in beats per minute
time
f32
Computed timestamp in seconds (automatically calculated, not serialized)

Creating BPM Lists

use phichain_chart::bpm_list::{BpmList, BpmPoint};
use phichain_chart::beat::Beat;

// Simple single-BPM chart at 120 BPM
let bpm_list = BpmList::single(120.0);

// Equivalent to:
let bpm_list = BpmList::new(vec![
    BpmPoint::new(Beat::ZERO, 120.0),
]);

// Or using default (also 120 BPM):
let bpm_list = BpmList::default();

Beat-Time Conversion

use phichain_chart::bpm_list::{BpmList, BpmPoint};
use phichain_chart::beat::Beat;

let bpm_list = BpmList::new(vec![
    BpmPoint::new(Beat::ZERO, 120.0),
    BpmPoint::new(Beat::from(4.0), 240.0),
]);

// Convert beats to seconds
let time_0 = bpm_list.time_at(Beat::ZERO);     // 0.0s
let time_1 = bpm_list.time_at(Beat::ONE);      // 0.5s (60s/120bpm)
let time_2 = bpm_list.time_at(Beat::from(2.0)); // 1.0s
let time_4 = bpm_list.time_at(Beat::from(4.0)); // 2.0s
let time_5 = bpm_list.time_at(Beat::from(5.0)); // 2.25s (at 240 BPM)
let time_8 = bpm_list.time_at(Beat::from(8.0)); // 3.0s

Beat Normalization

Normalization converts beats from a variable-BPM chart to a constant-BPM timeline:
use phichain_chart::bpm_list::{BpmList, BpmPoint};
use phichain_chart::beat::{Beat, beat};

let bpm_list = BpmList::new(vec![
    BpmPoint::new(beat!(0), 120.0),
    BpmPoint::new(beat!(4), 240.0),
]);

// Normalize to 120 BPM timeline
let normalized_4 = bpm_list.normalize_beat(120.0, beat!(4));
assert_eq!(normalized_4, beat!(4)); // same time, same beat at 120 BPM

let normalized_8 = bpm_list.normalize_beat(120.0, beat!(8));
assert_eq!(normalized_8, beat!(6)); // beat 8 at variable BPM = beat 6 at constant 120 BPM

// Normalize to 60 BPM timeline
let normalized = bpm_list.normalize_beat(60.0, beat!(4));
assert_eq!(normalized, beat!(2)); // beat 4 at 120 BPM = beat 2 at 60 BPM

How BPM Calculation Works

The BpmList automatically computes timestamps when created or modified:
// Example: 120 BPM -> 240 BPM at beat 4
BpmList::new(vec![
    BpmPoint { beat: 0, bpm: 120.0 }, // time: 0.0s
    BpmPoint { beat: 4, bpm: 240.0 }, // time: 2.0s (4 beats at 120 BPM)
])

// Calculation for beat 4:
// - Duration from beat 0 to 4: 4 beats
// - BPM during this period: 120
// - Time = (4 beats) * (60 seconds / 120 BPM) = 2.0 seconds

// After beat 4, the chart is at 240 BPM
// Time for beat 5:
// - Start: 2.0s (time at beat 4)
// - Duration: 1 beat at 240 BPM
// - Additional time = (1 beat) * (60s / 240 BPM) = 0.25s
// - Total time = 2.0 + 0.25 = 2.25s

JSON Serialization

[
  {
    "beat": [0, 0, 1],
    "bpm": 120.0
  }
]
Note: The time field is computed automatically and is not included in JSON.

Common Patterns

use phichain_chart::bpm_list::{BpmList, BpmPoint};
use phichain_chart::beat::Beat;

// Gradually speed up
let mut points = vec![];
for i in 0..8 {
    let beat = Beat::from(i as f32 * 4.0);
    let bpm = 120.0 + (i as f32 * 15.0); // 120, 135, 150, ...
    points.push(BpmPoint::new(beat, bpm));
}
let bpm_list = BpmList::new(points);

Performance Notes

  • Use beat_at_f32 instead of beat_at in performance-critical loops to avoid Beat allocation
  • The compute() method is called automatically when inserting points, so BPM lists are always ready to use
  • Time calculations use linear search from the beginning, which is O(n) where n is the number of BPM points
  • For charts with many BPM changes, consider caching conversion results

Notes

  • All BPM values should be positive
  • The first BPM point should typically be at Beat::ZERO
  • Points are automatically sorted by beat when inserted
  • Times are recomputed whenever the BPM list is modified
  • BPM changes are instantaneous at the specified beat