bemusic/bemuse

View on GitHub
packages/bms/src/spacing/index.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import { Speedcore } from '../speedcore'
import { BMSChart } from '../bms/chart'
import { SpeedSegment } from '../speedcore/segment'

/**
 * 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.
 */
export class Spacing {
  private _speedcore?: Speedcore
  /**
   * Constructs a Spacing from the given `segments`.
   */
  constructor(segments: SpacingSegment[]) {
    if (segments.length > 0) {
      this._speedcore = new Speedcore(segments)
    }
  }

  /**
   * Returns the note spacing factor at the specified beat.
   * @param beat the beat
   */
  factor(beat: number) {
    if (this._speedcore) {
      return this._speedcore.x(beat)
    } else {
      return 1
    }
  }

  /**
   * 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
   *
   * @param {BMSChart} chart the chart
   */
  static fromBMSChart(chart: BMSChart) {
    void BMSChart
    const segments: SpacingSegment[] = []
    chart.objects.allSorted().forEach(function (object) {
      if (object.channel === 'SP') {
        const beat = chart.measureToBeat(object.measure, object.fraction)
        const factor = +chart.headers.get('speed' + object.value)!
        if (isNaN(factor)) return
        if (segments.length > 0) {
          const previous = segments[segments.length - 1]
          previous.dx = (factor - previous.x) / (beat - previous.t)
        }
        segments.push({
          t: beat,
          x: factor,
          dx: 0,
          inclusive: true,
        })
      }
    })
    if (segments.length > 0) {
      segments.unshift({
        t: 0,
        x: segments[0].x,
        dx: 0,
        inclusive: true,
      })
    }
    return new Spacing(segments)
  }
}

export interface SpacingSegment extends SpeedSegment {
  /** the beat number */
  t: number
  /** the spacing at beat `t` */
  x: number
  /**
   * the amount spacing factor change per beat,
   * in order to create a continuous speed change
   */
  dx: number
  /**
   * whether or not to include the
   * starting beat `t` as part of the segment
   */
  inclusive: boolean
}