Skip to content

Instantly share code, notes, and snippets.

@dtinth
Last active August 22, 2022 15:10
Show Gist options
  • Save dtinth/53b05f9554ee51ef6ed6 to your computer and use it in GitHub Desktop.
Save dtinth/53b05f9554ee51ef6ed6 to your computer and use it in GitHub Desktop.

!!! This is a temporary documentation for bms parser package v2.0.0 !!!


Module Index


Class and Function Index


Public: The bms package is a library for working with rhythm game data.

Although this library’s name suggests that it is for BMS file format, almost every part can be used standalone.

This package contains:

  • Modules that reads and parses BMS files:

    • {Reader} reads the BMS file from a {Buffer}, detects the character set and decodes the buffer using that character set into a {String}.
    • {Compiler} reads the BMS source from a {String}, and converts into BMSChart, an internal representation of a BMS notechart.
  • Classes for representing a BMS notechart. These module stores the data as close to the BMS file format as possible.

    Almost no musical interpretation is made. For example, a BPM change is simply represented using a BMSObject with channel = 03 or 08.

  • Classes that represent different aspects of a notechart. Instance of these classes may be created from a BMSChart, but they can be used in a standalone manner as well.

    This makes this library very flexible, and able to accomodate different formats of notechart. For example, see the bmson package.

    It’s also possible to use these classes in context other than music gaming, for example, you can use these classes to help building a music player that requires precise synchronization between beats.

    • TimeSignatures represents a collection of time signatures in a musical score, and lets you convert the measure number and fraction into beat number.
    • Timing represents the timing information in a musical score, and provides methods to convert between musical time (beats) and metric time (seconds).
    • SongInfo represents the basic song information, such as title, artist, and genre.
    • Notes represents the sound objects inside your notechart.
    • Keysounds represents a mapping between keysound ID and filename.
    • Positioning represents a mapping between beat and in-game position. Some rhythm game lets chart author control the amount of scrolling per beat. In StepMania 5, this is called the scroll segments.
    • Spacing represents a mapping between beat and note spacing. Some rhythm game lets chart author change the note spacing (HI-SPEED) dynamically. In StepMania 5, this is called the speed segments.
  • Low-level utility classes:


Public: A module that exposes BMSChart.

class BMSChart

Public: A BMSChart holds information about a particular BMS notechart. Note that a BMSChart does not contain any information about #RANDOM, as they are already processed after compilation.

There is not many useful things you can do with a BMSChart other than accessing the header fields and objects inside it.

To extract information from a BMSChart, please look at the documentation of higher-level classes, such as Keysounds, Notes, and Timing.

Public: BMSHeaders representing the BMS-specific headers of this notechart

Public: BMSObjects representing all objects of this notechart

Public: TimeSignatures representing the time signature information in this chart

Public: Converts measure number and fraction into beat. A single beat is equivalent to a quarter note in common time signature.

  • measure {Number} representing the measure number, starting from 0
  • fraction {Number} representing the fraction inside that measure, from 0 to 1

Returns the {Number} representing the beat number, starting from 0


Public: A module that exposes BMSHeaders.

Public: A BMSHeader holds the header information in a BMS file, such as #TITLE, #ARTIST, or #BPM.

You get retrieve a header using the get() method:

chart.headers.get('title')

For some header fields that may contain multiple values, such as #SUBTITLE, you can get them all using getAll():

chart.headers.getAll()

Public: Iterates through each header field using a callback function.

  • callback A {Function} that will be called for each header field
    • key {String} representing the field’s name
    • value {String} representing the field’s value

Public: Retrieves the header field’s latest value.

  • name A {String} representing field’s name

Returns a {String} representing the field’s value

Public: Retrieves the header field’s value. This is useful when a header field is specified multiple times.

  • name A {String} representing field’s name

Returns an {Array} of {String} values

Public: Sets the header field’s value.

  • name A {String} representing field’s name
  • value A {String} representing field’s value

Public: A module that exposes BMSObjects.

Public: A BMSObjects holds a collection of objects inside a BMS notechart.

Public: Adds a new object to this collection.

If an object already exists on the same channel and position, the object is replaced (except for autokeysound tracks).

  • object {BMSObject} to add

Public: A BMSObject data structure represents an object inside a BMSChart.

It is a plain object with the following fields:

  • channel A {String} representing the raw two-character BMS channel of this object
  • measure A {Number} representing the measure number, starting at 0 (corresponds to #000)
  • fraction A {Number} representing the fractional position inside the measure, ranging from 0 (inclusive) to 1 (exclusive). 0 means that the object is at the start of the measure, whereas 1 means that the object is at the end of the measure.
  • value A {String} representing the raw value of the BMS object.

Public: A module that takes a string representing the BMS notechart, parses it, and compiles into a BMSChart.

Public: Reads the string representing the BMS notechart, parses it, and compiles into a {BMSChart}.

  • text {String} representing the BMS notechart
  • options (optional) {Object} representing additional parser options
    • rng (option) {Function} that generates a random number. It is used when processing #RANDOM n directive. This function should return an integer number between 1 and n.
      • max {Number} representing the maximum value.

Returns an {Object} with these keys:

  • chart {BMSChart} representing the resulting chart
  • warnings {Array} of warnings. Each warning is an {Object} with these keys:
    • lineNumber {Number} representing the line number where this warning occurred
    • message {String} representing the warning message

Public: A module that exposes Keysounds

class Keysounds

Public: A Keysounds is a simple mapping between keysounds ID and the file name.

Example

If you have a BMS like this:

#WAVAA cat.wav

Having parsed it using a {Compiler} into a BMSChart, you can create a Keysounds using fromBMSChart():

var keysounds = Keysounds.fromBMSChart(bmsChart)

Then you can retrieve the filename using .get():

keysounds.get('aa') // => 'cat.wav'

Public: Constructs an empty Keysounds object.

Public: Returns the keysound file at the specified ID.

  • id {String} representing the two-character keysound ID

Returns {String} representing the filename Returns undefined if not found

Public: Constructs a new Keysounds object from a BMSChart.

Returns a Keysounds object


Public: A module that exposes Positioning

Public: A Positioning represents the relation between song beats and display position, and provides a way to convert between them.

In some rhythm games, the amount of scrolling per beat may be different. StepMania’s #SCROLL segments is an example.

Public: Constructs a Positioning from the given segments.

  • segments An {Array} of segment objects. Each segment {Object} contains:
    • t {Number} representing the beat number
    • x {Number} representing the total elapsed amount of scrolling at beat t
    • dx {Number} representing the amount of scrolling per beat
    • inclusive {Boolean} representing whether or not to include the starting beat t as part of the segment

Public: Returns the scrolling speed at specified beat.

  • beat {Number} representing the beat number

Returns a {Number} representing the amount of scrolling per beat

Public: Returns the total elapsed scrolling amount at specified beat.

  • beat {Number} representing the beat number

Returns a {Number} representing the total elapsed scrolling amount

Public: Creates a Positioning object from the BMSChart.

Returns a Positioning object


Public: A module that takes a buffer, detects the character set, and returns the decoded string.

The Reader follows ruv-it!’s algorithm for detecting the character set.

exports.read(buffer)

Public: Reads the buffer, detect the character set, and returns the decoded string synchronously.

  • buffer {Buffer} representing the BMS file

Returns a {String} representing the decoded text

Public: Like read(buffer), but this is the asynchronous version.

  • buffer {Buffer} representing the BMS file
  • callback {Function} that will be called when finished
    • error {Error} in case of failure
    • value {String} representing the decoded text

Public: A module that exposes SongInfo

class SongInfo

Public: A SongInfo represents the song info, such as title, artist, genre.

Example

If you have a BMS like this:

#TITLE Exargon [HYPER]

Having parsed it using a {Compiler} into a BMSChart, you can create a SongInfo using fromBMSChart():

var info = SongInfo.fromBMSChart(bmsChart)

Then you can query the song information by accessing its properties.

info.title     // => 'Exargon'
info.subtitles // => ['HYPER']

Public: Constructs a SongInfo with information given by info parameter.

  • info {Object} representing the properties to set on this new instance

Public: {String} representing the song title

Public: {String} representing the song artist

Public: {String} representing the song genre

Public: {Array} representing the song's subtitles, one line per element. The subtitle may be used to represent the difficulty name, such as NORMAL, HYPER, ANOTHER.

Public: {Array} representing the song's other artists, one artist per element.

Public: {Number} representing the difficulty. Meaning of the number is same as BMS's #DIFFICULTY header.

difficulty meaning
1 BEGINNER
2 NORMAL
3 HYPER
4 ANOTHER
5 INSANE

Public: {Number} representing the level of the song. When converted from a BMS chart, this is the value of #PLAYLEVEL header.

Public: Constructs a new SongInfo object from a BMSChart.

Returns a SongInfo object


Public: A module that exposes Spacing

class Spacing

Public: A Spacing represents the relation between song beats and notes spacing.

In some rhythm games, such as Pump It Up!, the speed (note spacing factor) may be adjusted by the notechart. StepMania’s #SPEED segments is an example.

Public: Constructs a Spacing from the given segments.

  • segments An {Array} of segment objects. Each segment {Object} contains:
    • t {Number} representing the beat number
    • x {Number} representing the spacing at beat t
    • dx {Number} representing the amount spacing factor change per beat, in order to create a continuous speed change
    • inclusive {Boolean} representing whether or not to include the starting beat t as part of the segment

Public: Returns a spacing factor at the specified beat.

  • beat {Number} representing the beat

Returns the note spacing factor at the specified beat

Public: Creates a Spacing object from the BMSChart.

#SPEED and #xxxSP

Speed is defined as keyframes. These keyframes will be linearly interpolated.

#SPEED01 1.0
#SPEED02 2.0

#001SP:01010202

In this example, the note spacing factor will gradually change from 1.0x at beat 1 to 2.0x at beat 2.

Returns a Spacing object


Public: A module that exposes Speedcore

class Speedcore

Speedcore is a small internally-used library. A Speedcore represents a single dimensional keyframed linear motion (as in equation x = f(t)), and is useful when working with BPM changes (Timing), note spacing factor (Spacing), or scrolling segments (Positioning). A Speedcore is constructed from an array of Segments.

A {Segment} is defined as { t, x, dx }, such that:

  • speedcore.x(segment.t) = segment.x
  • speedcore.t(segment.x) = segment.t
  • speedcore.x(segment.t + dt) = segment.x + (segment.dx / dt)

Explanation

One way to think of these segments is to think about tempo changes, where:

  • t is the elapsed time (in seconds) since song start.
  • x is the elapsed beat since song start.
  • dx is the amount of x increase per t. In this case, it has the unit of beats per second.

For example, consider a song that starts at 140 BPM. 32 beats later, the tempo changes to 160 BPM. 128 beats later (at beat 160), the tempo reverts to 140 BPM.

We can derive three segments:

  1. At time 0, we are at beat 0, and moving at 2.333 beats per second.
  2. At 13.714s, we are at beat 32, moving at 2.667 beats per second.
  3. At 61.714s, we are at beat 160, moving at 2.333 beats per second.

This maps out to this data structure:

[ /* [0] */ { t:  0.000,  x:   0,  dx: 2.333,  inclusive: true },
  /* [1] */ { t: 13.714,  x:  32,  dx: 2.667,  inclusive: true },
  /* [2] */ { t: 61.714,  x: 160,  dx: 2.333,  inclusive: true } ]

With this data, it is possible to find out the value of x at any given t.

For example, to answer the question, “what is the beat number at 30s?” First, we find the segment with maximum value of t < 30, and we get the segment [1].

We calculate segment.x + (t - segment.t) * segment.dx. The result beat number is (32 + (30 - 13.714) * 2.667) = 75.435.

We can also perform the reverse calculation in a similar way, by reversing the equation.

Interestingly, we can use these segments to represent the effect of both BPM changes and STOP segments in the same array. For example, a 150-BPM song with a 2-beat stop in the 32nd beat can be represented like this:

[ /* [0] */ { t:  0.0,  x:  0,  dx: 2.5,  inclusive: true  },
  /* [1] */ { t: 12.8,  x: 32,  dx: 0,    inclusive: true  },
  /* [2] */ { t: 13.6,  x: 32,  dx: 2.5,  inclusive: false } ]

Public: Constructs a new Speedcore from given segments.

  • segments {Array} of {Segment} objects

Public: Calculates the t, given x.

  • x {Number} representing the value of x

Returns {Number} t

Public: Calculates the x, given t.

  • t {Number} representing the value of t

Returns {Number} x

Public: Finds the dx, given t.

  • t {Number} representing the value of t

Returns {Number} dx


Public: A module that exposes TimeSignatures

Public: A TimeSignatures is a collection of time signature values index by measure number.

The measure number starts from 0. By default, each measure has a measure size of 1 (which represents the common 4/4 time signature)

Example

If you have a BMS like this:

#00102:0.75
#00103:1.25

Having parsed it using a {Compiler} into a BMSChart, you can access the TimeSignatures object:

var timeSignatures = bmsChart.timeSignatures

Note that you can also use the constructor to create a TimeSignatures from scratch.

One of the most useful use case of this class is to convert the measure and fraction into beat number.

timeSignatures.measureToBeat(0, 0.000) // =>  0.0
timeSignatures.measureToBeat(0, 0.500) // =>  2.0
timeSignatures.measureToBeat(1, 0.000) // =>  4.0
timeSignatures.measureToBeat(1, 0.500) // =>  5.5
timeSignatures.measureToBeat(2, 0.000) // =>  7.0
timeSignatures.measureToBeat(2, 0.500) // =>  9.5
timeSignatures.measureToBeat(3, 0.000) // => 12.0

Public: Sets the size of a specified measure.

  • measure {Number} representing the measure number, starting from 0
  • value {Number} representing the measure size. For example, a size of 1.0 represents a common 4/4 time signature, whereas a size of 0.75 represents the 3/4 or 6/8 time signature.

Public: Retrieves the size of a specified measure.

  • measure {Number} representing the measure number.

Returns a {Number} representing the size of the measure. By default, a measure has a size of 1.

Public: Retrieves the number of beats in a specified measure.

Since one beat is equivalent to a quarter note in 4/4 time signature, this is equivalent to (timeSignatures.get(measure) * 4).

  • measure {Number} representing the measure number.

Returns a {Number} representing the size of the measure in beats.

Public: Converts a measure number and a fraction inside that measure into the beat number.

  • measure {Number} representing the measure number.
  • fraction {Number} representing the fraction of a measure, ranging from 0 (inclusive) to 1 (exclusive).

Returns a {Number} representing the number of beats since measure 0.


Public: A module that exposes Timing.

class Timing

Public: A Timing represents the timing information of a musical score. A Timing object provides facilities to synchronize between metric time (seconds) and musical time (beats).

A Timing are created from a series of actions:

  • BPM changes.
  • STOP action.

Public: Constructs a Timing from a specified actions.

Generally, you would use Timing.fromBMSChart to create an instance from a BMSChart, but the constructor may also be used in other situations unrelated to the BMS file format.

  • initialBPM {Number} The initial BPM of this song
  • actions An {Array} of actions objects. Each action object has these properties:
    • type {String} representing action type. bpm for BPM change, and stop for stop
    • beat {Number} representing beat where this action occurs
    • bpm {Number} representing BPM to change to (only for bpm type)
    • stopBeats {Number} of beats to stop (only for stop type)

Public: Convert the given beat into seconds.

Public: Convert the given second into beats.

Public: Returns the BPM at the specified beat.

Public: Returns an array representing the beats where there are events.

Public: Creates a Timing instance from a BMSChart.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment