Skip to main content

ParseCore

Python library for osu! beatmap parsing, mod handling, and performance point calculation.

PyPI Version Python License Typing pre-commit Code Style Discord


Features

  • Beatmap Parsing - Full .osu file parsing including all sections: General, Metadata, Difficulty, Events, Timing Points, Hit Objects, Colors, and Editor
  • Mod Handling - Complete mod system supporting all osu! game modes (osu!, Taiko, Catch, Mania) with legacy and modern mod representations
  • Star Rating & PP Calculation - Complete difficulty and performance calculation for all four rulesets (osu!, osu!taiko, osu!catch, osu!mania)
  • Up to date - Implements the 2026 Q2 pp/star rating rework (osu! deploy 2026.702.1, July 2026): the new osu! Reading skill, Snap/Flow aim, deviation-based speed pp, reworked taiko rhythm, catch linear-spacing nerf
  • Bit-exact - Verified bit-for-bit identical to the official osu! C# implementation across a 4,400+ case test matrix (all rulesets, all mod combinations, converts, lazer & stable scores)
  • Converts - Faithful osu! → taiko / catch / mania conversion, including the legacy mania pattern generator with osu!stable RNG and key mods (1K-9K)
  • Lazer & Stable scores - Both scoring systems supported: lazer slider statistics (slider tail hits, large ticks) as well as classic scores incl. score-based miss estimation from legacy_total_score
  • Type-safe - Fully typed codebase, mypy-checked

Installation

pip install parsecore

Or with uv:

uv add parsecore

Requires Python 3.10+


Quick Start

Beatmap Parsing

from parsecore.Beatmap import Beatmap

beatmap = Beatmap.from_path("path/to/map.osu")

print(beatmap.metadata.title)
print(beatmap.metadata.version)
print(beatmap.difficulty.approach_rate)
print(beatmap.difficulty.circle_size)

for obj in beatmap.hit_objects.hit_objects:
    print(obj)

for tp in beatmap.timing_points.control_points.timing_points:
    print(tp.time, 60000 / tp.beat_len)  # start time, BPM

Star Rating (Difficulty)

from parsecore.Performance import Beatmap, Difficulty

bm = Beatmap.from_path("path/to/map.osu")

# NoMod
attrs = Difficulty().calculate(bm)
print(attrs.stars, attrs.max_combo)

# Mods via legacy bitflags (HD = 8, DT = 64, ...)
attrs = Difficulty().mods(8 | 64).calculate(bm)
print(attrs.stars)

# osu! attributes include the per-skill breakdown of the 2026 rework
print(attrs.aim, attrs.speed, attrs.reading, attrs.flashlight)

# Custom clock rate, difficulty overrides, partial plays
attrs = (
    Difficulty()
    .mods(64)
    .clock_rate(1.3)
    .ar(10, fixed=True)   # fixed=True: use as-is; fixed=False: mods still apply
    .passed_objects(500)  # difficulty of the first 500 objects only
    .calculate(bm)
)

Performance Points (PP)

from parsecore.Performance import Beatmap, Performance

bm = Beatmap.from_path("path/to/map.osu")

# From accuracy (the score state is generated automatically)
result = Performance(bm).mods(64).accuracy(98.5).misses(2).calculate()
print(result.pp)

# From an explicit score state (osu!lazer score)
result = (
    Performance(bm)
    .lazer(True)
    .n300(194).n100(0).n50(0).misses(0)
    .combo(277)
    .slider_end_hits(68)
    .large_tick_hits(15)
    .calculate()
)
print(result.pp, result.pp_aim, result.pp_speed, result.pp_acc, result.pp_reading)

# osu!(stable) / classic score, incl. score-based miss estimation
result = (
    Performance(bm)
    .lazer(False)
    .n300(2020).n100(27).misses(4)
    .combo(1699)
    .legacy_total_score(31_546_804)
    .calculate()
)
print(result.pp, result.effective_miss_count)

Converts (osu! → taiko / catch / mania)

from parsecore.Beatmap.beatmap import Beatmap as UserBeatmap
from parsecore.Performance import Beatmap, Difficulty, GameMode

user_map = UserBeatmap.from_path("path/to/osu_map.osu")

# Play an osu! map in another ruleset
bm = Beatmap.from_user_beatmap(user_map, override_mode=GameMode.MANIA)
attrs = Difficulty().calculate(bm)
print(attrs.stars, attrs.n_objects, attrs.n_hold_notes)

# mania key mods change the column count of converts (7K = 1 << 18)
attrs = Difficulty().mods(1 << 18).calculate(bm)

Mod Handling

from parsecore.Mods import GameMods, GameMode

# From an acronym string
mods = GameMods.from_acronyms("HDDT", GameMode.Osu)
print(mods)               # DTHD
print(mods.clock_rate())  # 1.5

# Legacy bitfield conversion
legacy = mods.as_legacy()
print(legacy.bits())      # 72

# Intermode mods (not bound to a specific ruleset)
from parsecore.Mods import GameModsIntermode
intermode = GameModsIntermode.from_acronyms(["HD", "NC"])
print(intermode)          # HDNC

How the pp calculation works

parsecore.Performance is a pure-Python port of the official osu! difficulty and performance algorithms (ppy/osu, deploy 2026.702.1) not of a third-party reimplementation. The pipeline:

  1. Parse - the .osu file is decoded into hit objects, timing/difficulty/effect points with osu!-faithful float semantics (positions and curve math are computed in 32-bit floats exactly like the game client).
  2. Convert - if the target ruleset differs from the map's native mode, the map is converted first (taiko drum-roll splitting with scroll-speed effect points, catch mode flag, mania legacy pattern generator with osu!stable RNG).
  3. Preprocess - per-ruleset difficulty objects are built (distances, angles, rhythm groupings, effective BPM, ...).
  4. Skills - each ruleset evaluates its skills (osu!: Aim, Speed, Reading, Flashlight; taiko: Rhythm, Reading, Colour, Stamina; catch: Movement; mania: Strain) and aggregates them into the star rating.
  5. Performance - the score state (either given explicitly or generated from accuracy/miss count) is combined with the difficulty attributes into pp, including miss penalties, slider-break estimation and the classic/lazer scoring differences.

Every step reproduces the C# reference including its floating-point quirks (f32 intermediates, IEEE division semantics, integer-exponent powers as explicit multiplication, C# sorting algorithms, legacy RNG), which is what makes the results bit-identical rather than merely close.

Verification

Correctness is enforced by a parity test suite that compares parsecore against an oracle built from the official ppy.osu.Game packages:

  • 4,400+ cases: star ratings and pp across all four rulesets
  • extended mod matrix (EZ/HR/DT/NC/HT/HD/FL/TD/RX/AP/SO, key mods 1K-9K, combinations)
  • all convert directions, native maps, edge-case and pathological maps
  • randomized full score states, partial states, fails, lazer & stable scores
  • result: 0 differences every value bit-identical to the official implementation

Note: After a pp rework deploys, public calculators and bots that rely on outdated libraries can disagree with parsecore. When in doubt: new scores set in-game receive exactly the values parsecore computes.


Project Structure

parsecore/
├── Beatmap/                # .osu file parsing and encoding
│   ├── beatmap.py
│   ├── reader.py
│   ├── encode.py
│   └── section/            # Individual section parsers
│       ├── general.py
│       ├── metadata.py
│       ├── difficulty.py
│       ├── timing_points.py
│       ├── hit_objects/
│       └── ...
├── Mods/                   # Mod system
│   ├── game_mod.py
│   ├── game_mods.py
│   ├── game_mode.py
│   ├── generated_mods.py
│   └── ...
└── Performance/            # Star rating & pp calculation
    ├── api.py              # Public API: Beatmap, Difficulty, Performance
    ├── utils.py            # Rust/C#-faithful float & RNG helpers
    ├── data/               # Beatmap model, mods, score state, attributes
    └── rulesets/
        ├── osu/            # Aim, Speed, Reading, Flashlight + pp
        ├── taiko/          # Rhythm, Reading, Colour, Stamina + pp
        ├── catch/          # Movement + pp, gradual calculation
        └── mania/          # Strain + pp, legacy convert pattern generator

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.

Security

To report a security vulnerability, see SECURITY.md.

© 2026-Present O!Lib Team

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parsecore-1.0.0.tar.gz (128.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

parsecore-1.0.0-py3-none-any.whl (203.1 kB view details)

Uploaded Python 3

File details

Details for the file parsecore-1.0.0.tar.gz.

File metadata

  • Download URL: parsecore-1.0.0.tar.gz
  • Upload date:
  • Size: 128.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for parsecore-1.0.0.tar.gz
Algorithm Hash digest
SHA256 121c72506b12134ae4ab844b3fafa5d5c000ddb8d06f9031a04859711a01fd58
MD5 3fdaef41e3add9345a92f49a591c85cd
BLAKE2b-256 5e617f0b003ea1500e7dc6d09edb78f3f61eff5ac9e8532da5380583b8e58a0a

See more details on using hashes here.

Provenance

The following attestation bundles were made for parsecore-1.0.0.tar.gz:

Publisher: release.yml on O-Lib/parsecore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file parsecore-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: parsecore-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 203.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for parsecore-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 608cd0ce5290a32a95a23d309b9603f56726e5b4d743cf5fe893eb53ea55247d
MD5 5d30d2595f6e0cf8bd35976264cd8265
BLAKE2b-256 72872ae2d0faee0c8c57f8250aa004b029a78d69cea96f131fd9d9bf29f57aac

See more details on using hashes here.

Provenance

The following attestation bundles were made for parsecore-1.0.0-py3-none-any.whl:

Publisher: release.yml on O-Lib/parsecore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page