bemusic / bemuse
Showing 12 of 30 files from the diff.

@@ -12,6 +12,7 @@
Loading
12 12
  completed,
13 13
  operation川FromPromise,
14 14
} from './operations'
15 +
import Immutable, { Seq } from 'immutable'
15 16
import {
16 17
  Observable,
17 18
  ObservableInput,
@@ -33,13 +34,12 @@
Loading
33 34
} from 'rxjs'
34 35
import { RecordLevel, fromObject } from './level'
35 36
36 -
import Immutable from 'immutable'
37 +
import { BatchedFetcher } from './BatchedFetcher'
37 38
import { ScoreCount } from 'bemuse/rules/accuracy'
38 39
import _ from 'lodash'
39 40
import id from './id'
40 41
import { queryClient } from 'bemuse/react-query'
41 42
import { rootQueryKey } from './queryKeys'
42 -
import { BatchedFetcher } from './BatchedFetcher'
43 43
44 44
export interface SignUpInfo {
45 45
  username: string
@@ -221,8 +221,8 @@
Loading
221 221
        )
222 222
      )
223 223
      .pipe(map((map) => map.valueSeq()))
224 -
      .pipe(distinctUntilChanged(Immutable.is))
225 -
      .pipe(map((seq) => seq.toArray()))
224 +
      .pipe(distinctUntilChanged<Seq.Indexed<RecordLevel>>(Immutable.is))
225 +
      .pipe(map((seq: Seq.Indexed<RecordLevel>) => seq.toArray()))
226 226
  }
227 227
228 228
  private fetchRecords = async (

@@ -0,0 +1,35 @@
Loading
1 +
import { MusicServerIndex, SongMetadataInCollection } from 'bemuse-types'
2 +
import produce, { Draft } from 'immer'
3 +
4 +
export interface Preprocessed extends MusicServerIndex {
5 +
  songOfTheDayEnabled?: boolean
6 +
}
7 +
8 +
export const preprocessCollection = produce(
9 +
  (draft: Draft<Preprocessed>, songs?: SongMetadataInCollection[]) => {
10 +
    if (songs) {
11 +
      draft.songs = songs.map((song) => preprocessSong(song))
12 +
    }
13 +
  }
14 +
)
15 +
16 +
function preprocessSong(
17 +
  song: SongMetadataInCollection
18 +
): SongMetadataInCollection {
19 +
  if (!song.chart_names) {
20 +
    return song
21 +
  }
22 +
  return produce(song, (draft) => {
23 +
    if (draft.charts) {
24 +
      draft.charts = draft.charts.map((chart) => {
25 +
        const name = song.chart_names![chart.file]
26 +
        if (!name) return chart
27 +
        return produce(chart, (draft) => {
28 +
          draft.info.subtitles = [...chart.info.subtitles, name]
29 +
        })
30 +
      })
31 +
    }
32 +
  })
33 +
}
34 +
35 +
export default preprocessCollection
0 36
imilarity index 80%
1 37
ename from bemuse/src/music-collection/sortSongs.js
2 38
ename to bemuse/src/music-collection/sortSongs.ts

@@ -5,7 +5,7 @@
Loading
5 5
 *
6 6
 * @see https://bemuse.ninja/project/docs/music-server.html
7 7
 */
8 -
export function getMusicServer() {
8 +
export function getMusicServer(): string | undefined {
9 9
  return query.BEMUSE_MUSIC_SERVER || query.server
10 10
}
11 11
@@ -14,7 +14,7 @@
Loading
14 14
 *
15 15
 * @see https://bemuse.ninja/project/docs/music-server.html
16 16
 */
17 -
export function getSoundVolume() {
17 +
export function getSoundVolume(): number {
18 18
  return +query.volume || 1
19 19
}
20 20
@@ -24,24 +24,24 @@
Loading
24 24
 * @see https://github.com/bemusic/bemuse/pull/568
25 25
 * @see https://twitter.com/Nekokan_Server/status/1173186650865713153
26 26
 */
27 -
export function getPreloadArchiveFlag() {
27 +
export function getPreloadArchiveFlag(): string | undefined {
28 28
  return query.archive
29 29
}
30 30
31 31
/**
32 32
 * The `?grep` flag specifies the initials search text to be pre-filled when the player enters the music selection screen.
33 33
 */
34 -
export function getInitialGrepString() {
34 +
export function getInitialGrepString(): string | undefined {
35 35
  return query.grep
36 36
}
37 37
38 38
/**
39 39
 * The `?song` flag specifies the title of the song to be pre-selected when the player enters the music selection screen.
40 40
 */
41 -
export function getInitiallySelectedSong() {
41 +
export function getInitiallySelectedSong(): string | undefined {
42 42
  return query.song
43 43
}
44 44
45 -
export function getTimeSynchroServer() {
45 +
export function getTimeSynchroServer(): string | undefined {
46 46
  return query.BEMUSE_TIMESYNCHRO_SERVER
47 47
}

@@ -1,15 +1,23 @@
Loading
1 -
import _ from 'lodash'
1 +
import { Song } from 'bemuse/collection-model/types'
2 2
import { SongOfTheDay } from './SongOfTheDay'
3 +
import _ from 'lodash'
4 +
5 +
interface Grouping {
6 +
  title: string
7 +
  criteria: (song: Song, context: { songOfTheDay: SongOfTheDay }) => boolean
8 +
  sort?: (song: Song) => string
9 +
  reverse?: boolean
10 +
}
3 11
4 -
const grouping = [
5 -
  { title: 'Custom Song', criteria: (song) => song.custom },
6 -
  { title: 'Tutorial', criteria: (song) => song.tutorial },
7 -
  { title: 'Unreleased', criteria: (song) => song.unreleased },
12 +
const grouping: readonly Grouping[] = [
13 +
  { title: 'Custom Song', criteria: (song) => !!song.custom },
14 +
  { title: 'Tutorial', criteria: (song) => !!song.tutorial },
15 +
  { title: 'Unreleased', criteria: (song) => !!song.unreleased },
8 16
  {
9 17
    title: 'Recently Added Songs',
10 18
    criteria: (song) =>
11 -
      song.added && Date.now() - Date.parse(song.added) < 60 * 86400000,
12 -
    sort: (song) => song.added,
19 +
      !!song.added && Date.now() - Date.parse(song.added) < 60 * 86400000,
20 +
    sort: (song) => song.added ?? '',
13 21
    reverse: true,
14 22
  },
15 23
  {
@@ -20,7 +28,7 @@
Loading
20 28
]
21 29
22 30
export function groupSongsIntoCategories(
23 -
  songs,
31 +
  songs: readonly Song[],
24 32
  { songOfTheDayEnabled = false } = {}
25 33
) {
26 34
  const context = {
@@ -28,7 +36,7 @@
Loading
28 36
  }
29 37
  const groups = grouping.map((group) => ({
30 38
    input: group,
31 -
    output: { title: group.title, songs: [] },
39 +
    output: { title: group.title, songs: [] as Song[] },
32 40
  }))
33 41
  for (const song of songs) {
34 42
    for (const { input, output } of groups) {

@@ -1,6 +1,6 @@
Loading
1 +
import { Song } from 'bemuse/collection-model/types'
1 2
import _ from 'lodash'
2 3
import { createHash } from 'crypto'
3 -
import { Song } from 'bemuse/collection-model/types'
4 4
5 5
const getHashFunction = _.once(() => {
6 6
  const today = new Date(Date.now() + 9 * 3600e3).toISOString().split('T')[0]
@@ -14,7 +14,7 @@
Loading
14 14
15 15
export class SongOfTheDay {
16 16
  private ids: Set<string>
17 -
  constructor(songs: Song[], { enabled = true } = {}) {
17 +
  constructor(songs: readonly Song[], { enabled = true } = {}) {
18 18
    if (!enabled) {
19 19
      this.ids = new Set()
20 20
      return
21 21
imilarity index 74%
22 22
ename from bemuse/src/music-collection/getPlayableCharts.js
23 23
ename to bemuse/src/music-collection/getPlayableCharts.ts

@@ -1,8 +1,8 @@
Loading
1 +
import { Chart } from 'bemuse-types'
1 2
import _ from 'lodash'
2 -
3 3
import isChartPlayable from './isChartPlayable'
4 4
5 -
export function getPlayableCharts(charts) {
5 +
export function getPlayableCharts(charts: readonly Chart[]): Chart[] {
6 6
  return _(charts)
7 7
    .filter(isChartPlayable)
8 8
    .orderBy([
9 9
imilarity index 64%
10 10
ename from bemuse/src/music-collection/groupSongsIntoCategories.js
11 11
ename to bemuse/src/music-collection/groupSongsIntoCategories.ts

@@ -1,8 +1,9 @@
Loading
1 -
import Progress from 'bemuse/progress'
2 1
import * as ProgressUtils from 'bemuse/progress/utils'
3 -
import readBlob from 'bemuse/utils/read-blob'
4 2
import _ from 'lodash'
3 +
import Progress from 'bemuse/progress'
4 +
import readBlob from 'bemuse/utils/read-blob'
5 5
import throat from 'throat'
6 +
6 7
import { IResource, IResources } from './types'
7 8
import { URLResources } from './url'
8 9
@@ -29,7 +30,7 @@
Loading
29 30
  }
30 31
31 32
  constructor(
32 -
    base: string | IResources,
33 +
    base: string | URL | IResources,
33 34
    options: {
34 35
      metadataFilename?: string
35 36
      fallback?: string | IResources
@@ -37,7 +38,10 @@
Loading
37 38
    } = {}
38 39
  ) {
39 40
    if (typeof base === 'string') {
40 -
      base = new URLResources(new URL(base, location.href))
41 +
      base = new URL(base, location.href)
42 +
    }
43 +
    if (base instanceof URL) {
44 +
      base = new URLResources(base)
41 45
    }
42 46
43 47
    const fallback =

@@ -0,0 +1,21 @@
Loading
1 +
// Finds a song matching the title
2 +
3 +
import type { SongMetadataInCollection } from 'bemuse-types'
4 +
5 +
function findMatchingSong({
6 +
  songs,
7 +
  title,
8 +
  getTitle,
9 +
}: {
10 +
  songs: readonly SongMetadataInCollection[]
11 +
  title: string
12 +
  getTitle: (song: SongMetadataInCollection) => string
13 +
}) {
14 +
  return songs.find((song) => titleFullyMatches(getTitle(song), title))
15 +
}
16 +
17 +
function titleFullyMatches(haystack: string, needle: string) {
18 +
  return haystack.toLowerCase().trim() === needle.toLowerCase().trim()
19 +
}
20 +
21 +
export default findMatchingSong
0 22
imilarity index 82%
1 23
ename from bemuse/src/app/interactors/getNonMissedDeltas.js
2 24
ename to bemuse/src/app/interactors/getNonMissedDeltas.ts

@@ -1,7 +1,8 @@
Loading
1 +
import { Song } from 'bemuse/collection-model/types'
1 2
import _ from 'lodash'
2 3
import isChartPlayable from './isChartPlayable'
3 4
4 -
export function sortSongs(songs) {
5 +
export function sortSongs(songs: readonly Song[]) {
5 6
  return _.orderBy(songs, [
6 7
    (song) => {
7 8
      return _(song.charts)

@@ -19,9 +19,9 @@
Loading
19 19
export type PlayerOptionsGauge = 'off' | 'hope'
20 20
21 21
type PlayerOptionsInputMapping = {
22 -
  keyboard: { [control in PlayerControlKeys]: string }
23 -
  continuous: boolean
24 -
  sensitivity: number
22 +
  keyboard: { [control in PlayerControlKeys]: number }
23 +
  continuous?: boolean
24 +
  sensitivity?: number
25 25
}
26 26
27 27
type PlayerOptionsInternal = {
@@ -42,7 +42,7 @@
Loading
42 42
  speed: number
43 43
  placement?: PlayerOptionsPlacement
44 44
  laneCover?: number
45 -
  gauge: PlayerOptionsGauge
45 +
  gauge?: PlayerOptionsGauge
46 46
  input: PlayerOptionsInputMapping
47 47
  tutorial?: boolean
48 48
}
@@ -63,7 +63,7 @@
Loading
63 63
      scratch: options.scratch || 'left',
64 64
      input: options.input,
65 65
      laneCover: +(options.laneCover || 0),
66 -
      gauge: options.gauge,
66 +
      gauge: options.gauge || 'off',
67 67
      tutorial: !!options.tutorial,
68 68
    }
69 69
  }

@@ -1,12 +1,16 @@
Loading
1 1
import * as Judgments from '../judgments'
2 2
3 -
import _ from 'lodash'
4 3
import Notechart from 'bemuse-notechart'
4 +
import _ from 'lodash'
5 5
6 6
const getAccuracyScore = (accuracy: number) => Math.floor(accuracy * 500000)
7 7
const getComboScore = (sum: number, total: number) =>
8 8
  Math.floor((sum * 55555) / total)
9 9
10 +
export type Counts = {
11 +
  [k in Judgments.JudgedJudgment]: number
12 +
}
13 +
10 14
export class PlayerStats {
11 15
  public totalCombo: number
12 16
  public totalNotes: number
@@ -15,7 +19,7 @@
Loading
15 19
  public rawSumJudgmentWeight: number
16 20
  public rawTotalComboScore: number
17 21
  public rawSumComboScore: number
18 -
  public counts: { [k in Judgments.JudgedJudgment]: number }
22 +
  public counts: Counts
19 23
  public numJudgments: number
20 24
  public poor: boolean
21 25
  public deltas: number[]

@@ -0,0 +1,32 @@
Loading
1 +
import type { MusicServerIndex } from 'bemuse-types'
2 +
3 +
export const OFFICIAL_SERVER_URL = 'https://music4.bemuse.ninja/server'
4 +
5 +
export async function load(
6 +
  serverUrl: string,
7 +
  { fetch = global.fetch } = {}
8 +
): Promise<MusicServerIndex> {
9 +
  const indexUrl = getServerIndexFileUrl(serverUrl)
10 +
  const data = await fetch(indexUrl).then((response) => response.json())
11 +
12 +
  if (Array.isArray(data.songs)) {
13 +
    return data
14 +
  }
15 +
  if (Array.isArray(data.charts)) {
16 +
    // Single-song server
17 +
    const lastSlash = indexUrl.lastIndexOf('/')
18 +
    const dir =
19 +
      lastSlash === -1 ? indexUrl : indexUrl.substring(0, lastSlash + 1)
20 +
    return { songs: [{ ...data, id: 'song', path: dir }] }
21 +
  }
22 +
  throw new Error(
23 +
    `Invalid server file at ${indexUrl}: Does not contain "songs" array.`
24 +
  )
25 +
}
26 +
27 +
export function getServerIndexFileUrl(serverUrl: string) {
28 +
  if (serverUrl.endsWith('/bemuse-song.json')) {
29 +
    return serverUrl
30 +
  }
31 +
  return serverUrl.replace(/\/(?:index\.json)?$/, '') + '/index.json'
32 +
}
Files Coverage
bemuse/src 85.08%
packages/bms 95.54%
Project Totals (178 files) 86.40%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading