Purpose
The Primitive format serves as the conversion hub for all chart formats: This architecture:- Simplifies conversion logic (N formats need 2N conversions, not N²)
- Provides a stable internal API
- Enables easy addition of new formats
- Normalizes data structures
When It’s Used
Format Conversion
Every format conversion goes through Primitive as an intermediate step.
Chart Compilation
The compiler converts Phichain → Primitive before advanced processing.
Testing
Unit tests use Primitive for format-agnostic chart validation.
Tool Development
Custom tools can work with Primitive instead of dealing with multiple formats.
File Structure
Field Reference
PrimitiveChart
Primitive format version. Always
1 (stable).Audio offset in milliseconds.
BPM change points. Same structure as Phichain format.
Array of simplified Line objects.
Line
A minimal line representation with only notes and events:Notes attached to this line. Same structure as Phichain.
Line events. Always uses Transition form (no Constant values).
LineEvent
Simplified event structure:Event type:
"x", "y", "rotation", "opacity", or "speed".Event start beat
[bar, numerator, denominator].Event end beat.
Starting value.
Ending value.
Easing function name.
Key Difference from Phichain:Primitive events always use the flat structure with
start, end, and easing fields.Phichain’s LineEventValue::Constant is converted to Transition with start == end.Conversion Examples
Phichain → Primitive
LineEventValue::Constant(v)→start: v, end: v, easing: LinearLineEventValue::Transition{...}→ kept as-is- Child lines are flattened (not preserved)
- Curve note tracks are dropped
- Line names are lost
Official → Primitive
- Time values converted to beats:
beat = time * 60.0 / 1.875 - Position normalized:
x = (positionX / 18.0 - 0.5) * CANVAS_WIDTH - Opacity scaled:
opacity = disappear_value * 255.0 - Speed scaled:
speed = value / 2.0 * 9.0 - Per-line BPM → first line’s BPM becomes global BPM
RPE → Primitive
- Event layers flattened into single layer
- Rotation values negated:
rotation = -rpe_rotation - Easing ID mapped to Easing enum
- Custom bezier →
Easing::Custom(x1, y1, x2, y2)
Rust API
TheFormat trait defines conversion methods:
PrimitiveChart(identity conversion)PhichainChartOfficialChartRpeChart
Usage Example
Core Types
Primitive format reuses core types fromphichain-chart:
phichain-chart/src/primitive/mod.rs- PrimitiveChartphichain-chart/src/primitive/line.rs- Linephichain-chart/src/primitive/event.rs- LineEvent
Complete Example
Design Philosophy
Why an intermediate format?
Why an intermediate format?
Without Primitive, converting between N formats requires N×(N-1) conversion functions:
- 3 formats: 6 conversions
- 4 formats: 12 conversions
- 5 formats: 20 conversions
- 3 formats: 6 conversions (same!)
- 4 formats: 8 conversions (33% fewer)
- 5 formats: 10 conversions (50% fewer)
Why drop advanced features?
Why drop advanced features?
Primitive represents the common subset of all formats:
- Official format has no child lines → Primitive can’t preserve them
- Official has linear easing only → but Primitive keeps full easing (approximated on export)
- RPE has event layers → Primitive flattens them
Why keep easing?
Why keep easing?
Even though Official format only supports linear interpolation, Primitive preserves easing information:
- Allows round-trip Phichain → Official → Phichain without losing easing
- Enables future formats to support easing
- Approximation to linear segments happens only during Official export
Use Cases
Debugging Conversions
Custom Tools
Testing
Limitations
Identity Conversion
PrimitiveChart implements the Format trait with identity conversions:Source Code
Primitive format definition:phichain-chart/src/primitive/mod.rs- Main typesphichain-chart/src/primitive/line.rs- Line structphichain-chart/src/primitive/event.rs- LineEvent struct
Format Overview
Understanding multi-format support
Conversion Tool
Working with format conversions