Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

bsmap

KivalEvan8.4kMIT2.2.9TypeScript support: included

General-purpose scripting module for Beat Saber beatmap using TypeScript.

beat, saber, beatsaber, beatmap

readme

Beat Saber JS Map

General-purpose Beat Saber beatmap scripting library with TypeScript, fully-typed schema and flexible tool to ease scripting development surrounding beatmap.

It is designed to be simple and familiar with traditional scripting with barely hidden layer of abstraction. It is optimised for speed with minimal compromise allowing for faster work iteration.

[!WARNING]

API changes, structural changes, or game updates that require restructuring may result in breaking changes in future update. Many work has been placed in order to minimise the breakage on minor version update.

Features

  • Latest Schema: Supports all official schema including modded features.
    • Supported version: v4.1.0, v3.3.0, v2.6.0, v1.5.0.
  • Version-agnostic Wrapper: Readable and cross-version core allows for seamless version transferring.
  • Partial Creation: Define beatmap object partially and let default fill the rest of fields.
  • Mod Compatible: Chroma, Cinema, Noodle Extensions, and Mapping Extensions is supported out of the box.
    • Helpers are available in main for essentials such as getting modded position.
    • Class method may contain function parameter to access any arbitrary data.
  • Tree-shakeable: Modularity approach minimise build size.
  • Built-in Utility: Relevant utilities including math, colour, easings, and more.
  • Validator & Optimiser: Customisable tool ensuring beatmap schema is valid to the game and optimised for storage.

Prerequisite

  • Any TypeScript supported runtime/transpiler
    • Deno
    • Bun
    • Node.js
    • Vite
  • Basic JavaScript or TypeScript knowledge
    • Module is entirely TypeScript, but for common use case you do not need an in-depth knowledge.

Getting Started

Before you start, you may want to understand how Beat Saber stores the beatmap data here.

You may get this package from NPM or JSR using respective package manager.

To get scripting, simply create a .ts file anywhere, preferably inside map folder for simpler setup, import module via module specifier or package manager, and then run the script. That's it. Do check out the the guide for usage detail.

Importing/Runtime

This is for beginner on how to import library and use the script. Importing bsmap into script file can be done in various ways, it is recommended that you run the command in the same folder as the script file.

Deno

// Choose one of the four ways, prioritise top to bottom
import * as bsmap from 'jsr:@kvl/bsmap';
import * as bsmap from '@kvl/bsmap'; // via `deno add bsmap`
import * as bsmap from 'npm:bsmap';
import * as bsmap from 'bsmap'; // if previously used NPM `package.json` exist

// Run command: `deno run script.ts`

Bun

// via `bun add bsmap`
import * as bsmap from 'bsmap';

// Run command: `bun script.ts`

Node.js & Browser NPM (ESM)

// via `npm install bsmap`
import * as bsmap from 'bsmap';

// Run command: `ts-node script.ts`
// Refer below for browser

Node.js NPM (CJS)

// via `npm install bsmap`
const bsmap = require('bsmap');

// Run command: `ts-node script.ts`

Once you've imported the library, you can try the bare minimum example:

const data = bsmap.readDifficultyFileSync('ExpertPlus.beatmap.dat', 4);
// ... arbitrary code
bsmap.writeDifficultyFileSync(data, 4);

You may also clone the library to store and import locally, and make any modification as you wish.

If you are using the script outside of the map directory, you can specify the map directory without the need to explicitly apply directory on IO function. This can be any valid directory as long as it points to directory. If directory is explicitly written in IO function, then that will instead be prioritised.

// you should always use absolute path for this,
// otherwise it will try to resolve path with your CWD
bsmap.globals.directory = '/PATH/TO/YOUR/BEAT_SABER/MAP_FOLDER/';

Runtime

Module uses respective vendor API for filesystem and path functionality to handle read and write module, currently supporting Deno, Bun, and Node.js. This may also work on other runtime given that node: built-in module is available on import, otherwise you may be required to provide the following fs and path functionality in shims module.

Browser

As it is written in TypeScript, you may need transpiler such as tsc or vite that will compile down to single JavaScript file to be able to be used on browser, depending on build option down to ES5 support.

Typical browser do not have filesystem functionality and thus read and write module may not work as expected. You may use load and save which can read from web input.

Development

Deno is used for development, simply install and setup workspace, no other setup is required to get started as it provides necessary toolchain.

If you wish to contribute, do follow the guidelines. Make pull request for feature addition/enhancement/fix or create an issue if you encounter error/problem or want an improvement.

Guidelines

Styling & Documentation

  • Use deno fmt for standard formatting
    • Do not change deno.json configuration
  • File names shall use camel case
  • Exported types, interfaces, fields, and functions should be accompanied by JSDoc comment right above its definition
    • Function/method should provide usage example
  • Interfaces that are exposed to user must use I prefix to indicate interface rather than instantiable object.

Coding

  • Top-level function shall use regular function
  • No dependencies shall be used outside of examples, extensions, and tests
    • Vendor dependency is allowed so long it gracefully handles every platform possible
  • Prefer types over concrete type for parameter/return type
    • Use generic if necessary
  • Use ESM
    • Avoid circular imports
    • Avoid URL imports
    • Avoid default export
    • Prefer node: built-in import if needed

Planned

  • Write JSDoc on every important bit
  • Separate class method to own function
  • Add more helper for Chroma and Noodle Extensions

Known Issue

  • As beatmap module here is version agnostic, certain data such as bomb direction, info set custom data, etc. are either renamed, restructured or unavailable.
    • Certain beatmap version do not contain certain information in schema and are therefore not present in-game.

Credits & References

changelog

Changelog

2.2.9 [2025-08-20]

Fixed

  • [Swing] removed stray log

2.2.8 [2025-08-02]

Fixed

  • [Swing] incorrectly calculates slider speed and next swing detection

2.2.7 [2025-08-02]

Changed

  • [Swing] now only output real-time value
  • [Placement] properly handle modded params

Fixed

  • [Swing] incorrectly calculate SPS progression drop

2.2.6 [2025-07-09]

Removed

  • No longer serialise v4 spawn rotation

2.2.5 [2025-07-07]

Added

  • Regex split mappers field in v2 serialisation (#6)
  • Event renamer for older environment (#6)

Changed

  • Point definition should accurately represent modern format
  • Pre-v3 obstacle preserve ME _type (#6)

Fixed

  • Missing exports (+#6)
  • Correct event name mapping (#6)
  • Fix validation error for ME pre-v3 obstacle
  • Renamed offsetRotation to correct offsetWorldRotation

2.2.4 [2025-04-30]

Fixed

  • Ok, for real this time

2.2.3 [2025-04-30]

Fixed

  • Implicit version not working

2.2.2 [2025-04-24]

Added

  • Additional NJS helper method

Fixed

  • Fixed DNT dependency hopefully again
  • Fixed Vivify CreateCamera interface
  • Fixed several other type issue

2.2.1 [2025-04-09]

Changed

  • Faster deepCopy for array

Fixed

  • Dependency mapping error for standard schema

2.2.0 [2025-03-29]

Added

  • Vivify support

Changed

  • Schema validation overhaul (#3)
  • Decouple wrapper functionality into their own (#5)
  • ⚠️ BREAKING: IO functionality now returns wrapper object instead of concrete class (#5)

Removed

  • Wrapper implementation interface boilerplate
    • Removed attribute suffix to replace the removal

2.1.9 [2024-12-21]

Fixed

  • Metallica variant color scheme fix

2.1.8 [2024-12-21]

Added

  • Metallica environment & color schema
  • Info 4.0.1
    • Color scheme override note and light
  • Beatmap 4.1.0
    • NJS change event

Changed

  • Color scheme use override now separated to two type: Note and Light

Fixed

  • Certain schema type not being exported

2.1.7 [2024-11-14]

Added

  • Monstercat 2.0 environment & color scheme
  • getBpmAtTime and toRealTimeAtOffset TimeProcessor method

Deprecated

  • Beatmap v4 spawnRotation, now uses object lane attribute r
  • adjustTime TimeProcessor method

Removed

  • isLonger in obstacle (BPM dependent can cause issue with change)

2.1.6 [2024-10-13]

Fixed

  • Fixed conversion from v4 always including 0 rotation value event

2.1.5 [2024-10-12]

Fixed

  • Info color scheme should no longer include white color if not present
  • Stats note count did not count

2.1.4 [2024-10-08]

Added

  • Britney Spears environment & color scheme
  • Official editor bookmarks
  • setVersion to versioned beatmap file class

Changed

  • Split GenericFilename type to 4 types: Info, AudioData, Beatmap and Lightshow

2.1.3 [2024-09-09]

Fixed

  • Indexing error when optimising v3

2.1.2 [2024-08-31]

Fixed

  • Fixed shims for real this time

2.1.1 [2024-08-31]

Changed

  • Certain function return generic whenever possible

Fixed

  • Shims should no longer error when bundling for browser

2.1.0 [2024-08-10]

Added

  • Static method createOne for beatmap core
    • As opposed to create, this returns instance instead of array of instance.
  • Add new difficulty name serial
  • Several JSDocs written

Changed

  • Moved source files to src
  • Separate exports for utils and types to entrypoint
  • Renamed function in time utils for clarification
  • Renamed data check to schema declaration
  • Updated color scheme
  • Implicitly use beatmap version for conversion function
  • Allow beatmap conversion on save/write
  • globals reduced to only global beatmap directory

Fixed

  • Loading beatmap with explicit version did not convert version if mismatched
  • v2 to v3 conversion accidentally removed bomb notes
  • Several undocumented fixes
  • Typod from hass to has

Removed

  • web functions from utils
  • easing params from certain function with alpha value

2.0.2 [2024-07-23]

Fixed

  • Default exports not being exported
  • Missing many exports

2.0.1 [2024-07-23]

Fixed

  • NPM entrypoint fix, surely

2.0.0 [2024-07-23]

Added

  • Collider environment & color scheme
  • read and write module
    • This is to separate loading and saving functionality from including fs
  • Beatmap class object
    • Contains both Difficulty and Lightshow, separating respective object to supposed category
    • Loading and saving will now use Beatmap object rather than individual class above
    • customData under Beatmap will be placed onto but not replacing existing customData object in Difficulty and Lightshow upon save
  • Compatibility check on save
  • Several beatmap helpers
  • Gaussian random using Box-Muller transform utils function

Changed

  • Complete overhaul on beatmap structure
    • Now version agnostic, no need to create from version-specific object
    • This follows much closer to how beatmap is processed in-game than what it is in format
    • Customizable and arbitrary version format
      • For simplicity sake, only counts major version, but not limited to single digit
    • Implicit Mapping Extensions conversion
      • Mapping extensions is not recommended to be used as it is highly dependent on map format and the module tries its best to interpret Mapping Extensions as close to plugin beatmap processing
  • BeatPerMinute class renamed to TimeProcessor

Removed

  • Several leftover/dead code that could lead to confusion
  • Default value and deep copy omitted when deserialize
    • This will instead be done inside core beatmap