npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rosu-pp-js

v1.1.0

Published

Difficulty and performance calculation for osu!

Downloads

170

Readme

rosu-pp-js

Library to calculate difficulty and performance attributes for all osu! gamemodes.

This is a js binding to the Rust library rosu-pp through Wasm. As such, its performance is much faster than a native js library. Since Wasm is used as intermediate layer, Rust doesn't even need to be installed.

Usage

The library exposes multiple classes and interfaces:

Beatmap

Class containing a parsed .osu file, ready to be passed to difficulty and performance calculators.

The constructor takes an object of type Uint8Array | string representing the content of a .osu file and throws an error if decoding the beatmap fails.

⚠️WARNING⚠️

Due to current JavaScript oddities, Wasm is not always able to track down objects' lifetime meaning it is possible that memory of unused instances might not get cleared automatically. Hence, to not risk leaking memory, it is recommended to free Beatmap instances manually when they're no longer needed with the free(): void method.

To convert a beatmap use the convert(GameMode): void method.

Beatmap provides various getters:

  • ar: number
  • cs: number
  • hp: number
  • isConvert: boolean
  • mode: GameMode
  • nBreaks: number
  • nCircles: number
  • nHolds: number
  • nObjects: number
  • nSliders: number
  • nSpinners: number
  • od: number
  • sliderMultiplier: number
  • sliderTickRate: number
  • stackLeniency: number
  • version: number

Difficulty

Class to calculate DifficultyAttributes, Strains, or create gradual calculators.

The constructor takes an optional object of the form

{
    // The type must be either
    //   - an integer for bitflags (see https://github.com/ppy/osu-api/wiki#mods)
    //   - a string for acronyms
    //   - a single mod object as described below
    //   - a sequence of types that deserialize into a single mod
    //
    // Types that deserialize into a single mod are
    //   - an integer for bitflags
    //   - a string for an acronym
    //   - a mod object
    //
    // A mod object must have an `acronym: string` property and an optional
    // `settings?: Object` property.
    mods?: Object,
    // Custom clock rate between 0.01 and 100
    clockRate?: number,
    // Custom approach rate between -20 and 20
    ar?: number,
    // Whether given `ar` should be used as is or adjusted based on mods
    // i.e. `true` means "given ar already considers mods".
    arWithMods?: boolean,
    // Custom circle size between -20 and 20
    cs?: number,
    csWithMods?: boolean,
    // Custom drain rate between -20 and 20
    hp?: number,
    hpWithMods?: boolean,
    // Custom overall difficulty between -20 and 20
    od?: number,
    odWithMods?: boolean,
    // Amount of passed objects for partial plays, e.g. a fail
    passedObjects?: number,
    // Adjust patterns as if the HR mod is enabled on osu!catch maps
    hardrockOffsets?: boolean,
}

The following methods are available:

  • calculate(Beatmap): DifficultyAttributes: The difficulty attributes for the given parameters
  • strains(Beatmap): Strains: The strain values for the given parameters, suitable to plot difficulty over time
  • gradualDifficulty(Beatmap): GradualDifficulty: A gradual difficulty calculator
  • gradualPerformance(Beatmap): GradualPerformance: A gradual performance calculator

Performance

Calculator of PerformanceAttributes whose constructor takes an object of the form

{
    // ... same fields as for `Difficulty` but also:

    // Accuracy between 0 and 100
    accuracy?: number,
    // The max combo of a play
    combo?: number,
    // The amount of gekis (n320 for mania)
    nGeki?: number,
    // The amount of katus (tiny droplet misses for catch, n200 for mania)
    nKatu?: number,
    // The amount of n300s
    n300?: number,
    // The amount of n100s
    n100?: number,
    // The amount of n50s
    n50?: number,
    // The amount of misses
    misses?: number,
    // Whether good or bad hitresults should be generated to fit the given accuracy
    hitresultPriority?: HitResultPriority,
}

Its only method calculate(DifficultyAttributes | PerformanceAttributes | Beatmap): PerformanceAttributes produces the performance attributes. The method's argument must be either the attributes of a previous calculation or a beatmap.

Note that if a beatmap is given, difficulty attributes have to be calculated internally which is comparably expensive so passing attributes should be prefered whenever possible.

However, be careful that the passed attributes have been calculated for the same difficulty settings like mods, clock rate, beatmap, custom ar, ... otherwise the final performance attributes will be incorrect.

GradualDifficulty

Class to calculate difficulty attributes after each hitobject.

Its constructor takes a Difficulty and a Beatmap, it has a getter nRemaining: number, and the methods

  • next(): DifficultyAttributes | undefined: Process the next hitobject and return the difficulty attributes (or undefined if the last object has already been processed)
  • nth(number): DifficultyAttributes | undefined: Process the next number - 1 hitobjects, i.e. nth(0) will process one, nth(1) will proces two, ...
  • collect(): DifficultyAttributes[]: Collect all remaining difficulty attributes into a list

GradualPerformance

Class to calculate performance attributes after each hitresult.

Its constructor takes a Difficulty and a Beatmap, it has a getter nRemaining: number, and the methods

  • next(ScoreState): PerformanceAttributes | undefined: Process the next hitobject and return the performance attributes (or undefined if the last object has already been processed)
  • nth(ScoreState, number): PerformanceAttributes | undefined: Process the next number - 1 hitobjects, i.e. nth(0) will process one, nth(1) will proces two, ...

ScoreState is an object like

{
  maxCombo?: number;
  misses?: number;
  n100?: number;
  n300?: number;
  n50?: number;
  nGeki?: number;
  nKatu?: number;
}

BeatmapAttributesBuilder

Class to calculate BeatmapAttributes for various custom parameters.

Its constructor takes an object of the form

{
    // Start off with the given beatmap's attributes, mode, and convert status
    map?: Beatmap,
    // Specify a gamemode
    mode?: GameMode,
    // Whether the map is a convert, only relevant for mania
    isConvert?: boolean,
    mods?: Object,
    clockRate?: number,
    ar?: number,
    arWithMods?: boolean,
    cs?: number,
    csWithMods?: boolean,
    hp?: number,
    hpWithMods?: boolean,
    od?: number,
    odWithMods?: boolean,
}

Its only method is build(): BeatmapAttributes.

Setters

For the Difficulty, Performance, and BeatmapAttributesBuilder classes, each field of their constructor's argument object is also available as a setter afterwards which takes either a value of the field's type or undefined to unset the previously set value.

import * as rosu from "rosu-pp-js";

// Either given in the constructor
let difficulty = new rosu.Difficulty({ clockRate: 1.23 });

// Or adjusted afterwards
difficulty.mods = 8;

// Or even reset entirely
difficulty.clockRate = undefined;

Examples

Calculating performance

import * as rosu from "rosu-pp-js";
import * as fs from "fs";
// or if you're using CommonJS:
const rosu = require("rosu-pp-js");
const fs = require("fs");

const bytes = fs.readFileSync("/path/to/file.osu");

// Parse the map.
let map = new rosu.Beatmap(bytes);

// Optionally convert the beatmap to a specific mode.
map.convert(rosu.GameMode.Taiko);

// Calculating performance attributes for a HDDT SS
const maxAttrs = new rosu.Performance({ mods: 8 + 64 }).calculate(map);

// Calculating performance attributes for a specific score.
const currAttrs = new rosu.Performance({
    mods: "HDDT", // Must be the same as before in order to use the previous attributes!
    misses: 2,
    accuracy: 98.4,
    combo: 567,
    hitresultPriority: rosu.HitResultPriority.WorstCase,
}).calculate(maxAttrs); // Re-using previous attributes to speed up the calculation.

console.log(`PP: ${currAttrs.pp}/${maxAttrs.pp} | Stars: ${maxAttrs.difficulty.stars}`);

// Free the beatmap manually to avoid risking memory leakage.
map.free();

Gradual calculation

import * as rosu from "rosu-pp-js";
import * as fs from "fs";

const content = fs.readFileSync("/path/to/file.osu", "utf-8");
const map = new rosu.Beatmap(content);

// Specifying some difficulty parameters
const difficulty = new rosu.Difficulty({
    mods: 16 + 64, // HRDT
    clockRate: 1.1,
    ar: 10.2,
    arWithMods: true,
    od: 4,
    odWithMods: false,
});

// Gradually calculating *difficulty* attributes
let gradualDiff = difficulty.gradualDifficulty(map);
let i = 1;

while (gradualDiff.nRemaining > 0) {
    console.log(`Stars after ${i} hitobjects: ${gradualDiff.next()?.stars}`);
    i += 1;
}

// Gradually calculating *performance* attributes
let gradualPerf = difficulty.gradualPerformance(map);
let j = 1;

while (gradualPerf.nRemaining > 0) {
    // Need to pass the current score state
    const state = {
        maxCombo: j,
        n300: j,
        n100: 0,
        // ...
    };

    console.log(`PP: ${gradualPerf.next(state)?.pp}`);
    j += 1;
}

map.free();

Installing rosu-pp-js

$ npm install rosu-pp-js

or

$ npm install https://github.com/MaxOhn/rosu-pp-js/releases/download/v1.1.0/rosu_pp_js_nodejs.tar.gz

Note that apart from the *_nodejs version, the release page also includes *_web and *_bundler versions.

Learn More