Calculate osu! difficulty and performance attributes for all four gamemodes in ISO C++98.
- All four gamemodes, with the mods each one supports.
- Ported from osu!lazer and verified against its
2026.730.0release; the parser is from fosude01f188f2be7831f619ca09882cf2f55b0c9720. - Python bindings use the C ABI through generated
ctypescode. One wheel per platform supports CPython 3.10 and newer. - Node bindings use Node-API. One build works across supported Node.js versions.
- C# bindings use the same C ABI. One assembly works on .NET Framework 2.0 and modern .NET.
The library is built and installed with CMake:
cmake -S . -B build
cmake --build build
cmake --install buildA consumer finds the package with find_package and links the target it needs:
find_package(pppp CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE pppp::fosu)
pppp_place_engine(your_target) # puts the SIMD engine next to your_target#include <pppp/backend.h>
#include <pppp/beatmap.h>
#include <pppp/pppp.h>
pppp::Beatmap beatmap;
pppp::Status status = beatmap.load_file("map.osu"); // or beatmap.load_buffer(data, size)
if (!status.ok()) { puts(status.message()); return 1; }
// The parser engine the loader chose, read-only: pppp::EngineKind::SCALAR, AVX2 or NEON.
// e.g. `pppp::backend() == pppp::EngineKind::AVX2` on an x86-64 host.
pppp::Mods mods;
if (!mods.parse("HDDT").ok()) return 1; // "DT:speed_change=1.4" for settings
// pppp::Mods::from_legacy(pppp::mods::LEGACY_MOD_HIDDEN, beatmap.mode) for osu!stable's bitmask
// mods.push_back(pppp::mods::MOD_HD) for a single mod
pppp::DifficultyAttributes diff_attrs;
if (!pppp::Difficulty().mods(mods).calculate(beatmap, diff_attrs).ok()) return 1;
if (const pppp::OsuDifficultyAttributes* osu = diff_attrs.osu()) printf("aim %.2f\n", osu->aim_difficulty);
pppp::ScoreInfo score;
score.mods = mods;
score.statistics[pppp::HIT_RESULT_GREAT] = 600;
score.statistics[pppp::HIT_RESULT_OK] = 1;
score.max_combo = diff_attrs.max_combo(); score.accuracy = 0.9994;
pppp::PerformanceAttributes pp_attrs;
if (!pppp::Performance(beatmap, diff_attrs).score(score).calculate(pp_attrs).ok()) return 1;
printf("%.2f* %.2fpp\n", diff_attrs.star_rating(), pp_attrs.total());import pppp
beatmap = pppp.Beatmap.from_file("map.osu")
difficulty = pppp.Difficulty(mods="HD,DT").calculate(beatmap)
performance = pppp.Performance(mods="HD,DT", max_combo=789, accuracy=0.992, misses=2).calculate(beatmap, difficulty)
print(difficulty.star_rating, performance.total)using Pppp;
using (Beatmap beatmap = Beatmap.FromFile("map.osu")) {
DifficultyAttributes difficulty = new Difficulty { Mods = "HD,DT" }.Calculate(beatmap);
PerformanceAttributes performance =
new Performance { Mods = "HD,DT", MaxCombo = 789, Accuracy = 0.992, Misses = 2 }.Calculate(beatmap, difficulty);
Console.WriteLine(difficulty.StarRating + " " + performance.Total);
}import { Beatmap, Difficulty, Performance } from "@7mochi/pppp";
const beatmap = await Beatmap.fromFile("map.osu");
const difficulty = new Difficulty({ mods: "HD,DT" }).calculate(beatmap);
const performance = new Performance({ mods: "HD,DT", maxCombo: 789, accuracy: 0.992, misses: 2 }).calculate(beatmap, difficulty);
console.log(difficulty.starRating, performance.total);| CMake option | Default | What it does |
|---|---|---|
PPPP_WITH_FOSU |
ON | the .osu parser and its adapter |
PPPP_ISA |
auto | parser engine: auto, scalar, avx2 or neon |
PPPP_BUILD_TESTS |
top-level only | the test suites (fetches doctest) |
PPPP_BUILD_PYTHON |
OFF | the native library the Python package loads |
PPPP_BUILD_NODE |
OFF | the Node-API extension module |
PPPP_BUILD_CAPI |
OFF | the C ABI shared library the C# bindings use |
PPPP_WIN2K |
OFF | the shim that keeps binaries loadable on Windows 2000 |
PPPP_INSTALL |
top-level only | the find_package(pppp CONFIG) package |
PPPP_WARNINGS_AS_ERRORS |
OFF | -Werror on the library's warnings |
PPPP_PGO |
OFF | profile-guided optimization: GENERATE instruments, USE consumes the profile (make build-pgo runs both, training on the parity suites) |
PPPP_PGO_DIR |
<build>/../profile-data |
where the profile is written and read (GCC keeps it beside each object) |
The toolchain comes from the flake:
nix develop # cmake, ninja, gcc, clang, python, node, .NET and pre-commit
make test-auto # build and run the suites
make check-format # the C++ formatting, checked
make build-pgo # a PGO Release build in build_pgo (GCC or Clang)Formatting, the Python types and the file hygiene are also enforced by pre-commit. The dev shell installs the hooks when you enter it; to run them by hand:
pre-commit run --all-filesclang-format and nixfmt come from the dev shell; outside it the hooks fall
back to nix develop.
The port was done with AI assistance (DeepSeek V4.1 Flash and Opus 5), working file by file against ppy/osu. I have gone through most of the code to check that it's correct and to fix what isn't. Not all of it though, some files have not been checked yet and I'm still working through the rest. If you find something, an issue is welcome.
If you would rather not use something written with AI assistance, there are other options for the same problem:
- osu-native with my bindings: osu-native-py, osu-native-go and osu-native-jar.
- rosu-pp with rosu-pp-py, rosu-pp-ffi and rosu-pp-js.