Code Monkey home page Code Monkey logo

rosu-pp-js's Introduction

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:

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.

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

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

The constructor takes an optional object of the form

{
    // Mod bitflags, see <https://github.com/ppy/osu-api/wiki#mods>
    mods?: number,
    // 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

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.

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

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;
}

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?: number,
    clockRate?: number,
    ar?: number,
    arWithMods?: boolean,
    cs?: number,
    csWithMods?: boolean,
    hp?: number,
    hpWithMods?: boolean,
    od?: number,
    odWithMods?: boolean,
}

Its only method is build(): BeatmapAttributes.

Example

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: 8 + 64, // 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}`);

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;
}

Installing rosu-pp-js

$ npm install rosu-pp-js

or

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

Learn More

rosu-pp-js's People

Contributors

calemy avatar maxohn avatar minidomo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

rosu-pp-js's Issues

Buffer support

Just wondering if it would be possible to add support for buffers instead of just file paths.

Beatmap leaks memory

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

const file_path = `/mnt/f/osu!/beatmaps/53.osu`;
const bytes = fs.readFileSync(file_path);

while (true) {
    let beatmap = new rosu.Beatmap(bytes);
}

This MRE currently results in RAM usage steadily climbing up. This issue has most likely to do with rustwasm/wasm-bindgen#3917.

I believe the best "workaround" is to denote very clearly in the readme and documentation to call Beatmap.free manually.

Add setters to builders

As mentioned in #11, the classes Performance, Difficulty, and BeatmapAttributesBuilder should have setter methods to adjust their fields.

This might lead users to initialize each field with setters instead of using the more efficient constructor but that's a worthy tradeoff considering that there's currently no way to re-use a builder with just a single field modified for example.

Some mod pp's are wrong

I came here to say that the PP calculations for some of the mods were wrong.

const map = fs.readFileSync('test.osu', 'utf-8'); //https://osu.ppy.sh/beatmapsets/1856862#taiko/3825077
const beatmap = new rosu.Beatmap(map);

//HD: 8
//HR: 16
//EZ: 2
//DT: 64
//HT: 256

let difficultyArgs = {
    mods: Tested with the above values.
};

let difficulty = new rosu
    .Difficulty(difficultyArgs)
    .calculate(beatmap);

const PP = new rosu
    .Performance()
    .calculate(difficulty);

console.log(PP.pp);

Result
Original PP => rosu-pp-js
HDSS: 759pp => 692.3902281341881(same as NM)
HRSS: 779pp => 751.8363331710395
EZSS: 620pp => 643.6490906614597
DTSS: 1275pp => 1275.1074471835482(Correct)
HTSS: 439pp => 439.4615725388199(Correct)

The calculation seems to be correct for mods that change speed.

No compatibility with ESM

Since the switch to wasm the compability with ESM faded, since it's now considered a commonjs only module. There is a workaround but i'd really wish to see native ESM back. Took me by surprise that my beatmap mirror just suddenly broke after upgrading packages lol

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.