bemusic/bemuse

View on GitHub
packages/bms/src/song-info/index.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { match } from '../util/match'
import { assign } from '../util/lodash'
import { BMSChart } from '../bms/chart'

export interface ISongInfoData {
  title: string
  artist: string
  genre: string
  subtitles: string[]
  subartists: string[]
  difficulty: number
  level: number
}

/**
 * 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()`:
 *
 * ```js
 * var info = SongInfo.fromBMSChart(bmsChart)
 * ```
 *
 * Then you can query the song information by accessing its properties.
 *
 * ```js
 * info.title     // => 'Exargon'
 * info.subtitles // => ['HYPER']
 * ```
 */
export class SongInfo implements ISongInfoData {
  title: string
  artist: string
  genre: string
  subtitles: string[]
  subartists: string[]
  difficulty: number
  level: number
  /**
   * Constructs a SongInfo with given data
   * @param info the properties to set on this new instance
   */
  constructor(info: { [propertyName: string]: any }) {
    /** the song title */
    this.title = 'NO TITLE'
    /** the song artist */
    this.artist = 'NO ARTIST'
    /** the song genre */
    this.genre = 'NO GENRE'
    /**
     * the song's subtitles, one line per element.
     * The subtitle may be used to represent the difficulty name,
     * such as NORMAL, HYPER, ANOTHER.
     * @type {string[]}
     */
    this.subtitles = []
    /**
     * the song's other artists, one artist per element.
     * @type {string[]}
     */
    this.subartists = []
    /**
     * the difficulty.
     * Meaning of the number is same as BMS's [`#DIFFICULTY`](http:*hitkey.nekokan.dyndns.info/cmds.htm#DIFFICULTY) header.
     *
     * | difficulty | meaning  |
     * | ----------:|:--------:|
     * |          1 | BEGINNER |
     * |          2 | NORMAL   |
     * |          3 | HYPER    |
     * |          4 | ANOTHER  |
     * |          5 | INSANE   |
     */
    this.difficulty = 0
    /**
     * the level of the song.
     * When converted from a BMS chart, this is the value of `#PLAYLEVEL` header.
     */
    this.level = 0
    if (info) assign(this, info)
  }

  /**
   * Constructs a new {SongInfo} object from a {BMSChart}.
   * @param chart A {BMSChart} to construct a {SongInfo} from
   */
  static fromBMSChart(chart: BMSChart) {
    void BMSChart
    const info: Partial<ISongInfoData> = {}
    let title = chart.headers.get('title')
    const artist = chart.headers.get('artist')
    const genre = chart.headers.get('genre')
    const difficulty = +chart.headers.get('difficulty')! || 0
    const level = +chart.headers.get('playlevel')! || 0
    let subtitles = chart.headers.getAll('subtitle')
    const subartists = chart.headers.getAll('subartist')
    if (typeof title === 'string' && !subtitles) {
      const extractSubtitle = function (m: string[]) {
        title = m[1]
        subtitles = [m[2]]
      }
      match(title)
        .when(/^(.*\S)\s*-(.+?)-$/, extractSubtitle)
        .when(/^(.*\S)\s*~(.+?)~$/, extractSubtitle)
        .when(/^(.*\S)\s*\((.+?)\)$/, extractSubtitle)
        .when(/^(.*\S)\s*\[(.+?)\]$/, extractSubtitle)
        .when(/^(.*\S)\s*<(.+?)>$/, extractSubtitle)
    }
    if (title) info.title = title
    if (artist) info.artist = artist
    if (genre) info.genre = genre
    if (subtitles) info.subtitles = subtitles
    if (subartists) info.subartists = subartists
    if (difficulty) info.difficulty = difficulty
    if (level) info.level = level
    return new SongInfo(info)
  }
}