Skip to main content

phichain-chart

The phichain-chart crate is the core library for representing and manipulating Phigros charts in the Phichain toolchain. It provides data structures for beats, notes, lines, events, and complete chart serialization.

Installation

Add this to your Cargo.toml:
[dependencies]
phichain-chart = { version = "1.0.0-beta.5" }

# Optional: Enable Bevy integration
phichain-chart = { version = "1.0.0-beta.5", features = ["bevy"] }

Dependencies

The crate depends on:
  • bevy 0.16.1 (optional, for game engine integration)
  • serde 1.0 (for serialization)
  • num 0.4.3 (for rational number support)
  • simple-easing 1.0.1 (for easing functions)
  • anyhow 1.0 (for error handling)

Core Modules

beat

Represents musical beats using rational numbers for precise timing.
Beat
struct
A beat in the chart, represented with a whole part and a rational part.
use phichain_chart::beat::Beat;
use phichain_chart::beat;

// Create beats using the beat! macro
let beat1 = beat!(4);           // 4th beat (whole number)
let beat2 = beat!(1, 2);        // 1/2 beat
let beat3 = beat!(2, 1, 4);     // 2 + 1/4 beat

// Arithmetic operations
let sum = beat1 + beat2;
let diff = beat3 - beat2;

// Convert to float
let value: f32 = beat2.value();  // 0.5
  • new(whole: i32, ratio: Rational32) -> Beat - Create a new beat
  • value() -> f32 - Get the float value of the beat
  • beat() -> i32 - Get the whole part
  • numer() -> i32 - Get the numerator of the fractional part
  • denom() -> i32 - Get the denominator of the fractional part
  • reduce() -> &mut Self - Reduce the beat to canonical form
  • reduced() -> Self - Get a reduced copy of the beat
  • abs() -> Self - Get the absolute value

bpm_list

Manages tempo changes throughout the chart.
BpmList
struct
A list of BPM (beats per minute) change points.
use phichain_chart::bpm_list::{BpmList, BpmPoint};
use phichain_chart::beat::Beat;
use phichain_chart::beat;

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

// Convert between beats and time
let time = bpm_list.time_at(beat!(2));  // Get time in seconds
let beat = bpm_list.beat_at(1.5);       // Get beat at time

note

Defines note types and properties.
NoteKind
enum
The type of note.
pub enum NoteKind {
    Tap,
    Drag,
    Hold { hold_beat: Beat },
    Flick,
}
Note
struct
A note in the chart.
use phichain_chart::note::{Note, NoteKind};
use phichain_chart::beat;

let note = Note::new(
    NoteKind::Tap,
    true,           // above line
    beat!(2),       // at beat 2
    0.5,            // x position
    1.0,            // speed multiplier
);

// For hold notes
let hold = Note::new(
    NoteKind::Hold { hold_beat: beat!(1) },
    true,
    beat!(3),
    0.0,
    1.0,
);

let end = hold.end_beat();  // beat 4 (3 + 1)

line

Represents judgment lines in the chart.
Line
struct
A judgment line that contains notes.
use phichain_chart::line::Line;

let line = Line {
    name: "Main Line".to_string(),
};
When the bevy feature is enabled, additional components are available:
  • LinePosition(Vec2) - Position of the line
  • LineRotation(f32) - Rotation angle in degrees
  • LineOpacity(f32) - Opacity (0.0 to 1.0)
  • LineSpeed(f32) - Current speed multiplier

event

Defines line animation events.
LineEventKind
enum
The type of line event.
pub enum LineEventKind {
    X = 1,        // Horizontal position
    Y,            // Vertical position
    Rotation,     // Rotation angle
    Opacity,      // Opacity/alpha
    Speed,        // Speed multiplier
}
LineEventValue
enum
The value of a line event, either constant or transitioning.
use phichain_chart::event::LineEventValue;
use phichain_chart::easing::Easing;

// Constant value
let constant = LineEventValue::constant(10.0);

// Transition from 0 to 100 with easing
let transition = LineEventValue::transition(
    0.0,
    100.0,
    Easing::EaseInOutCubic,
);
LineEvent
struct
A timed event that affects a line’s properties.
use phichain_chart::event::{LineEvent, LineEventKind, LineEventValue};
use phichain_chart::easing::Easing;
use phichain_chart::beat;

let event = LineEvent {
    kind: LineEventKind::X,
    start_beat: beat!(0),
    end_beat: beat!(4),
    value: LineEventValue::transition(0.0, 1.0, Easing::Linear),
};

// Evaluate event at a specific beat
let result = event.evaluate(2.0);

easing

Provides easing functions for smooth animations.
Easing
enum
Easing functions for interpolation. See easings.net for visual examples.
use phichain_chart::easing::{Easing, Tween};

// Standard easing functions
let linear = Easing::Linear;
let ease_in = Easing::EaseInCubic;
let ease_out = Easing::EaseOutCubic;
let ease_in_out = Easing::EaseInOutCubic;

// Custom bezier curve
let custom = Easing::Custom(0.42, 0.0, 0.58, 1.0);

// Step function
let steps = Easing::Steps(5);

// Use easing to interpolate values
let value = 0.0.ease_to(100.0, 0.5, Easing::EaseInOutCubic);

serialization

Provides chart serialization and deserialization.
PhichainChart
struct
The main chart structure.
use phichain_chart::serialization::{PhichainChart, SerializedLine};
use phichain_chart::bpm_list::BpmList;
use std::fs::File;

// Create a new chart
let chart = PhichainChart::new(
    0.0,                    // offset in seconds
    BpmList::default(),     // BPM list
    vec![                   // lines
        SerializedLine::default()
    ],
);

// Save to file
let file = File::create("chart.json")?;
serde_json::to_writer_pretty(file, &chart)?;

// Load from file
let file = File::open("chart.json")?;
let chart: PhichainChart = serde_json::from_reader(file)?;
SerializedLine
struct
A line with its notes, events, and children for serialization.
pub struct SerializedLine {
    pub line: Line,
    pub notes: Vec<Note>,
    pub events: Vec<LineEvent>,
    pub children: Vec<SerializedLine>,
    pub curve_note_tracks: Vec<CurveNoteTrack>,
}

project

Manages chart project directories.
Project
struct
Represents a Phichain project directory.
use phichain_chart::project::{Project, ProjectMeta};
use std::path::PathBuf;

// Open a project
let project = Project::open(PathBuf::from("./my_chart"))?;

// Access project files
let chart_path = project.path.chart_path();
let music_path = project.path.music_path();
let illustration_path = project.path.illustration_path();
let meta_path = project.path.meta_path();

// Access metadata
println!("Song: {}", project.meta.name);
println!("Composer: {}", project.meta.composer);
println!("Charter: {}", project.meta.charter);
ProjectMeta
struct
Project metadata from meta.json.
pub struct ProjectMeta {
    pub composer: String,
    pub charter: String,
    pub illustrator: String,
    pub name: String,
    pub level: String,
}

Complete Example

use phichain_chart::{
    beat,
    beat::Beat,
    bpm_list::{BpmList, BpmPoint},
    event::{LineEvent, LineEventKind, LineEventValue},
    easing::Easing,
    line::Line,
    note::{Note, NoteKind},
    serialization::{PhichainChart, SerializedLine},
};
use std::fs::File;

fn main() -> anyhow::Result<()> {
    // Create BPM list
    let bpm_list = BpmList::new(vec![
        BpmPoint::new(beat!(0), 120.0),
    ]);

    // Create some notes
    let notes = vec![
        Note::new(NoteKind::Tap, true, beat!(0), 0.0, 1.0),
        Note::new(NoteKind::Tap, true, beat!(1), 0.5, 1.0),
        Note::new(
            NoteKind::Hold { hold_beat: beat!(2) },
            true,
            beat!(2),
            -0.5,
            1.0,
        ),
        Note::new(NoteKind::Flick, true, beat!(4), 0.0, 1.0),
    ];

    // Create line events
    let events = vec![
        LineEvent {
            kind: LineEventKind::X,
            start_beat: beat!(0),
            end_beat: beat!(4),
            value: LineEventValue::transition(-1.0, 1.0, Easing::EaseInOutCubic),
        },
        LineEvent {
            kind: LineEventKind::Rotation,
            start_beat: beat!(0),
            end_beat: beat!(4),
            value: LineEventValue::transition(0.0, 360.0, Easing::Linear),
        },
    ];

    // Create a line
    let line = SerializedLine::new(
        Line { name: "Main".to_string() },
        notes,
        events,
        vec![],  // no children
        vec![],  // no curve note tracks
    );

    // Create the chart
    let chart = PhichainChart::new(0.0, bpm_list, vec![line]);

    // Save to file
    let file = File::create("chart.json")?;
    serde_json::to_writer_pretty(file, &chart)?;

    Ok(())
}

Bevy Integration

When using the bevy feature, chart components integrate with Bevy’s ECS:
use bevy::prelude::*;
use phichain_chart::{
    line::{Line, LinePosition, LineRotation},
    note::Note,
};

fn spawn_chart_entities(mut commands: Commands) {
    // Spawn a line with Bevy components
    commands.spawn((
        Line { name: "Line 1".to_string() },
        LinePosition(Vec2::new(0.0, 0.0)),
        LineRotation(0.0),
    )).with_children(|parent| {
        // Spawn notes as children
        parent.spawn(Note::new(
            NoteKind::Tap,
            true,
            beat!(0),
            0.0,
            1.0,
        ));
    });
}

Format Migration

The library includes migration support for older chart formats:
use phichain_chart::migration::migrate;
use serde_json::Value;

let old_chart: Value = serde_json::from_reader(file)?;
let migrated = migrate(&old_chart)?;
let chart: PhichainChart = serde_json::from_value(migrated)?;

See Also