sonolus-next-rush-plus-engine 1.8.9 → 1.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
@@ -1,12 +1,12 @@
1
1
  import { type LevelData } from '@sonolus/core';
2
- export type ExtendedEntityDataField = {
2
+ export interface ExtendedEntityDataField {
3
3
  name: string;
4
4
  value?: number;
5
5
  ref?: string;
6
- };
7
- export type ExtendedEntityData = {
6
+ }
7
+ export interface ExtendedEntityData {
8
8
  archetype: string;
9
9
  name?: string;
10
10
  data: ExtendedEntityDataField[];
11
- };
11
+ }
12
12
  export declare const extendedToLevelData: (data: LevelData, offset?: number) => LevelData | undefined;
@@ -97,7 +97,7 @@ class ExtData {
97
97
  const arch = e.archetype;
98
98
  if (!this.byArch.has(arch))
99
99
  this.byArch.set(arch, []);
100
- this.byArch.get(arch).push({ idx, e });
100
+ this.byArch.get(arch)?.push({ idx, e });
101
101
  if (arch in noteTypeMapping)
102
102
  this.notes.push({ idx, e });
103
103
  if (arch in activeConnectorKindMapping)
@@ -274,17 +274,17 @@ export const extendedToLevelData = (data, offset = 0) => {
274
274
  const attachRef = getField(e, 'attach');
275
275
  if (attachRef !== undefined) {
276
276
  const attachConn = getConn(attachRef);
277
- if (attachConn && attachConn.refs['head'] && attachConn.refs['tail']) {
278
- note.set('attachHead', attachConn.refs['head']);
279
- note.set('attachTail', attachConn.refs['tail']);
277
+ if (attachConn?.refs.head && attachConn.refs.tail) {
278
+ note.set('attachHead', attachConn.refs.head);
279
+ note.set('attachTail', attachConn.refs.tail);
280
280
  note.set('isAttached', 1);
281
281
  }
282
282
  }
283
283
  const slideRef = getField(e, 'slide');
284
284
  if (slideRef !== undefined) {
285
285
  const slideConn = getConn(slideRef);
286
- if (slideConn && slideConn.refs['activeHead']) {
287
- note.set('activeHead', slideConn.refs['activeHead']);
286
+ if (slideConn?.refs.activeHead) {
287
+ note.set('activeHead', slideConn.refs.activeHead);
288
288
  }
289
289
  }
290
290
  }
@@ -304,25 +304,25 @@ export const extendedToLevelData = (data, offset = 0) => {
304
304
  const anchors = anchorsByBeat.get(beat) || [];
305
305
  for (const anchor of anchors) {
306
306
  const positions = anchorPositions.get(anchor);
307
- if (positions && positions.has(pos))
307
+ if (positions?.has(pos))
308
308
  continue;
309
- if (anchor.values['lane'] === lane &&
310
- anchor.values['size'] === size &&
309
+ if (anchor.values.lane === lane &&
310
+ anchor.values.size === size &&
311
311
  anchor.refs['#TIMESCALE_GROUP'] === tsg &&
312
312
  (segmentKind === -1 ||
313
- anchor.values['segmentKind'] === segmentKind ||
314
- anchor.values['segmentKind'] === -1) &&
313
+ anchor.values.segmentKind === segmentKind ||
314
+ anchor.values.segmentKind === -1) &&
315
315
  (segmentAlpha === -1 ||
316
- anchor.values['segmentAlpha'] === segmentAlpha ||
317
- anchor.values['segmentAlpha'] === -1) &&
316
+ anchor.values.segmentAlpha === segmentAlpha ||
317
+ anchor.values.segmentAlpha === -1) &&
318
318
  (connectorEase === -1 ||
319
- anchor.values['connectorEase'] === connectorEase ||
320
- anchor.values['connectorEase'] === -1)) {
321
- if (segmentKind !== -1 && anchor.values['segmentKind'] === -1)
319
+ anchor.values.connectorEase === connectorEase ||
320
+ anchor.values.connectorEase === -1)) {
321
+ if (segmentKind !== -1 && anchor.values.segmentKind === -1)
322
322
  anchor.set('segmentKind', segmentKind);
323
- if (segmentAlpha !== -1 && anchor.values['segmentAlpha'] === -1)
323
+ if (segmentAlpha !== -1 && anchor.values.segmentAlpha === -1)
324
324
  anchor.set('segmentAlpha', segmentAlpha);
325
- if (connectorEase !== -1 && anchor.values['connectorEase'] === -1)
325
+ if (connectorEase !== -1 && anchor.values.connectorEase === -1)
326
326
  anchor.set('connectorEase', connectorEase);
327
327
  positions?.add(pos);
328
328
  return anchor;
@@ -342,7 +342,7 @@ export const extendedToLevelData = (data, offset = 0) => {
342
342
  finalEntities.push(newAnchor);
343
343
  if (!anchorsByBeat.has(beat))
344
344
  anchorsByBeat.set(beat, []);
345
- anchorsByBeat.get(beat).push(newAnchor);
345
+ anchorsByBeat.get(beat)?.push(newAnchor);
346
346
  anchorPositions.set(newAnchor, new Set([pos]));
347
347
  return newAnchor;
348
348
  }
@@ -379,11 +379,11 @@ export const extendedToLevelData = (data, offset = 0) => {
379
379
  }
380
380
  for (const list of anchorsByBeat.values()) {
381
381
  for (const anchor of list) {
382
- if (anchor.values['segmentKind'] === -1)
382
+ if (anchor.values.segmentKind === -1)
383
383
  anchor.set('segmentKind', ConnectorKind.GUIDE_NEUTRAL);
384
- if (anchor.values['segmentAlpha'] === -1)
384
+ if (anchor.values.segmentAlpha === -1)
385
385
  anchor.set('segmentAlpha', 1.0);
386
- if (anchor.values['connectorEase'] === -1)
386
+ if (anchor.values.connectorEase === -1)
387
387
  anchor.set('connectorEase', EaseType.LINEAR);
388
388
  }
389
389
  }
@@ -396,8 +396,8 @@ export const extendedToLevelData = (data, offset = 0) => {
396
396
  });
397
397
  for (const e of finalEntities) {
398
398
  if (e.archetype === 'Connector') {
399
- const head = e.refs['head'];
400
- const tail = e.refs['tail'];
399
+ const head = e.refs.head;
400
+ const tail = e.refs.tail;
401
401
  if (head && tail) {
402
402
  head.set('next', tail);
403
403
  }
@@ -413,11 +413,11 @@ export const extendedToLevelData = (data, offset = 0) => {
413
413
  data.push({ name: key, value: val });
414
414
  }
415
415
  for (const [key, refObj] of Object.entries(e.refs)) {
416
- data.push({ name: key, ref: entityToName.get(refObj) });
416
+ data.push({ name: key, ref: entityToName.get(refObj) ?? '' });
417
417
  }
418
418
  return {
419
419
  archetype: e.archetype,
420
- name: entityToName.get(e),
420
+ name: entityToName.get(e) ?? '',
421
421
  data,
422
422
  };
423
423
  });
package/dist/index.d.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { LevelData } from '@sonolus/core';
2
- import { mmwsToUSC } from './mmw/convert.js';
3
- import { ucmmwsToLevelData } from './mmw/convert.js';
2
+ import { type ExtendedEntityData, type ExtendedEntityDataField, extendedToLevelData } from './extended/convert.js';
3
+ import { mmwsToUSC, ucmmwsToLevelData } from './mmw/convert.js';
4
4
  import { susToUSC } from './sus/convert.js';
5
5
  import { uscToLevelData } from './usc/convert.js';
6
6
  import { USC } from './usc/index.js';
7
- import { extendedToLevelData, type ExtendedEntityData, type ExtendedEntityDataField } from './extended/convert.js';
8
- export { susToUSC, mmwsToUSC, uscToLevelData, ucmmwsToLevelData, extendedToLevelData, type ExtendedEntityData, type ExtendedEntityDataField, };
9
7
  export * from './usc/index.js';
8
+ export { type ExtendedEntityData, type ExtendedEntityDataField, extendedToLevelData, mmwsToUSC, susToUSC, ucmmwsToLevelData, uscToLevelData, };
10
9
  export declare const convertToLevelData: (input: string | Uint8Array | USC | LevelData, offset?: number) => LevelData;
11
- export declare const version = "1.8.9";
10
+ export declare const version = "1.8.10";
12
11
  export declare const databaseEngineItem: {
13
12
  readonly name: "next-rush-plus";
14
13
  readonly version: 13;
package/dist/index.js CHANGED
@@ -1,16 +1,15 @@
1
- import { detectMMWSType } from './mmw/analyze.js';
2
- import { mmwsToUSC } from './mmw/convert.js';
3
- import { ucmmwsToLevelData } from './mmw/convert.js';
4
- import { susToUSC } from './sus/convert.js';
5
- import { uscToLevelData } from './usc/convert.js';
6
- import { isUSC } from './usc/analyze.js';
1
+ import { isExtendedLevelData } from './extended/analyze.js';
2
+ import { extendedToLevelData, } from './extended/convert.js';
7
3
  import { isLevelData } from './LevelData/analyze.js';
4
+ import { detectMMWSType } from './mmw/analyze.js';
5
+ import { mmwsToUSC, ucmmwsToLevelData } from './mmw/convert.js';
8
6
  import { isPJSK } from './pjsk/analyze.js';
9
- import { isExtendedLevelData } from './extended/analyze.js';
10
7
  import { pjskToUSC } from './pjsk/convert.js';
11
- import { extendedToLevelData, } from './extended/convert.js';
12
- export { susToUSC, mmwsToUSC, uscToLevelData, ucmmwsToLevelData, extendedToLevelData, };
8
+ import { susToUSC } from './sus/convert.js';
9
+ import { isUSC } from './usc/analyze.js';
10
+ import { uscToLevelData } from './usc/convert.js';
13
11
  export * from './usc/index.js';
12
+ export { extendedToLevelData, mmwsToUSC, susToUSC, ucmmwsToLevelData, uscToLevelData, };
14
13
  export const convertToLevelData = (input, offset = 0) => {
15
14
  if (isExtendedLevelData(input)) {
16
15
  const converted = extendedToLevelData(input, offset);
@@ -71,7 +70,7 @@ export const convertToLevelData = (input, offset = 0) => {
71
70
  }
72
71
  return uscToLevelData(usc, offset, true, true);
73
72
  };
74
- export const version = '1.8.9';
73
+ export const version = '1.8.10';
75
74
  export const databaseEngineItem = {
76
75
  name: 'next-rush-plus',
77
76
  version: 13,
@@ -4,7 +4,7 @@ declare const StepType: readonly ["visible", "hidden", "ignored"];
4
4
  type StepType = (typeof StepType)[number];
5
5
  declare const EaseType: readonly ["linear", "easeIn", "easeOut", "easeInOut", "easeOutIn"];
6
6
  export type EaseType = (typeof EaseType)[number];
7
- export type Score = {
7
+ export interface Score {
8
8
  metadata: {
9
9
  title: string;
10
10
  author: string;
@@ -102,7 +102,7 @@ export type Score = {
102
102
  layer: number;
103
103
  }[];
104
104
  numLayers: number;
105
- };
105
+ }
106
106
  /** Detect MMWS Type */
107
107
  export declare const detectMMWSType: (mmws: Uint8Array) => string;
108
108
  export declare const analyze: (mmws: Uint8Array) => Score;
@@ -1,5 +1,5 @@
1
- import { USC } from '../usc/index.js';
2
1
  import { type LevelData } from '@sonolus/core';
2
+ import { USC } from '../usc/index.js';
3
3
  /**
4
4
  * Convert MMWS or CCMMWS to a USC
5
5
  */
@@ -87,13 +87,13 @@ export const mmwsToUSC = (mmws) => {
87
87
  if (!tsGroups.has(key)) {
88
88
  if (!tsGroups.has(0))
89
89
  tsGroups.set(0, []);
90
- tsGroups.get(0).push({
90
+ tsGroups.get(0)?.push({
91
91
  beat: hispeedChange.tick / TICKS_PER_BEAT,
92
92
  timeScale: hispeedChange.speed,
93
93
  });
94
94
  continue;
95
95
  }
96
- tsGroups.get(key).push({
96
+ tsGroups.get(key)?.push({
97
97
  beat: hispeedChange.tick / TICKS_PER_BEAT,
98
98
  timeScale: hispeedChange.speed,
99
99
  });
@@ -152,7 +152,7 @@ export const mmwsToUSC = (mmws) => {
152
152
  const uscGuide = {
153
153
  type: 'guide',
154
154
  fade: hold.fadeType === 0 ? 'out' : hold.fadeType === 1 ? 'none' : 'in',
155
- color: Object.entries(USCColor).find(([, i]) => i === hold.guideColor)[0],
155
+ color: Object.entries(USCColor).find(([, i]) => i === hold.guideColor)?.[0],
156
156
  midpoints: [hold.start, ...hold.steps, hold.end].map((step) => ({
157
157
  beat: step.tick / TICKS_PER_BEAT,
158
158
  lane: laneToUSCLane(step),
@@ -263,10 +263,10 @@ export const ucmmwsToLevelData = (mmws) => {
263
263
  if (!layerChanges.has(hs.layer)) {
264
264
  if (!layerChanges.has(0))
265
265
  layerChanges.set(0, []);
266
- layerChanges.get(0).push(hs);
266
+ layerChanges.get(0)?.push(hs);
267
267
  continue;
268
268
  }
269
- layerChanges.get(hs.layer).push(hs);
269
+ layerChanges.get(hs.layer)?.push(hs);
270
270
  }
271
271
  for (let i = 0; i < Math.max(1, score.numLayers); i++) {
272
272
  const changes = layerChanges.get(i) || [];
@@ -293,14 +293,14 @@ export const ucmmwsToLevelData = (mmws) => {
293
293
  '#TIMESCALE_GROUP': groupIntermediate,
294
294
  };
295
295
  if (hs.hideNotes) {
296
- data['hideNotes'] = 1;
296
+ data.hideNotes = 1;
297
297
  }
298
298
  const newChangeIntermediate = createIntermediate('#TIMESCALE_CHANGE', data);
299
299
  if (lastChangeIntermediate === null) {
300
- groupIntermediate.data['first'] = newChangeIntermediate;
300
+ groupIntermediate.data.first = newChangeIntermediate;
301
301
  }
302
302
  else {
303
- lastChangeIntermediate.data['next'] = newChangeIntermediate;
303
+ lastChangeIntermediate.data.next = newChangeIntermediate;
304
304
  }
305
305
  lastChangeIntermediate = newChangeIntermediate;
306
306
  }
@@ -329,7 +329,7 @@ export const ucmmwsToLevelData = (mmws) => {
329
329
  '#TIMESCALE_GROUP': timeScaleGroupRef,
330
330
  };
331
331
  if (tap.flickType !== 'none') {
332
- data['direction'] = flickTypeToDirection(tap.flickType);
332
+ data.direction = flickTypeToDirection(tap.flickType);
333
333
  }
334
334
  createIntermediate(archetype, data, true);
335
335
  }
@@ -392,23 +392,23 @@ export const ucmmwsToLevelData = (mmws) => {
392
392
  tail: midpointIntermediate,
393
393
  });
394
394
  guideConnectors.push(connectorIntermediate);
395
- prevMidpointIntermediate.data['next'] = midpointIntermediate;
395
+ prevMidpointIntermediate.data.next = midpointIntermediate;
396
396
  }
397
397
  prevMidpointIntermediate = midpointIntermediate;
398
398
  stepIdx++;
399
399
  }
400
400
  if (headMidpointIntermediate && prevMidpointIntermediate) {
401
401
  for (const conn of guideConnectors) {
402
- conn.data['segmentHead'] = headMidpointIntermediate;
403
- conn.data['segmentTail'] = prevMidpointIntermediate;
404
- conn.data['activeHead'] = headMidpointIntermediate;
405
- conn.data['activeTail'] = prevMidpointIntermediate;
402
+ conn.data.segmentHead = headMidpointIntermediate;
403
+ conn.data.segmentTail = prevMidpointIntermediate;
404
+ conn.data.activeHead = headMidpointIntermediate;
405
+ conn.data.activeTail = prevMidpointIntermediate;
406
406
  }
407
407
  if (hold.fadeType === 2) {
408
- headMidpointIntermediate.data['segmentAlpha'] = 0;
408
+ headMidpointIntermediate.data.segmentAlpha = 0;
409
409
  }
410
410
  else if (hold.fadeType === 0) {
411
- prevMidpointIntermediate.data['segmentAlpha'] = 0;
411
+ prevMidpointIntermediate.data.segmentAlpha = 0;
412
412
  }
413
413
  }
414
414
  }
@@ -546,10 +546,10 @@ export const ucmmwsToLevelData = (mmws) => {
546
546
  '#TIMESCALE_GROUP': timeScaleGroupRef,
547
547
  };
548
548
  if (event.flick && event.flick !== 'none') {
549
- data['direction'] = flickTypeToDirection(event.flick);
549
+ data.direction = flickTypeToDirection(event.flick);
550
550
  }
551
551
  else {
552
- data['direction'] = 0;
552
+ data.direction = 0;
553
553
  }
554
554
  const connectionIntermediate = createIntermediate(archetype, data, isSimLineEligible);
555
555
  if (headNoteIntermediate === null) {
@@ -558,15 +558,15 @@ export const ucmmwsToLevelData = (mmws) => {
558
558
  if (currentSegmentHead === null) {
559
559
  currentSegmentHead = connectionIntermediate;
560
560
  }
561
- connectionIntermediate.data['activeHead'] = headNoteIntermediate;
561
+ connectionIntermediate.data.activeHead = headNoteIntermediate;
562
562
  if (isAttached) {
563
563
  queuedAttachIntermediates.push(connectionIntermediate);
564
564
  }
565
565
  else {
566
566
  if (prevJointIntermediate !== null) {
567
567
  for (const attachIntermediate of queuedAttachIntermediates) {
568
- attachIntermediate.data['attachHead'] = prevJointIntermediate;
569
- attachIntermediate.data['attachTail'] = connectionIntermediate;
568
+ attachIntermediate.data.attachHead = prevJointIntermediate;
569
+ attachIntermediate.data.attachTail = connectionIntermediate;
570
570
  }
571
571
  queuedAttachIntermediates.length = 0;
572
572
  while (nextHiddenTickBeat + EPSILON <
@@ -574,8 +574,8 @@ export const ucmmwsToLevelData = (mmws) => {
574
574
  createIntermediate('TransientHiddenTickNote', {
575
575
  '#BEAT': nextHiddenTickBeat,
576
576
  '#TIMESCALE_GROUP': timeScaleGroupRef,
577
- lane: connectionIntermediate.data['lane'],
578
- size: connectionIntermediate.data['size'],
577
+ lane: connectionIntermediate.data.lane,
578
+ size: connectionIntermediate.data.size,
579
579
  direction: 0,
580
580
  isAttached: 1,
581
581
  connectorEase: SONOLUS_CONNECTOR_EASES.linear,
@@ -599,20 +599,20 @@ export const ucmmwsToLevelData = (mmws) => {
599
599
  prevJointIntermediate = connectionIntermediate;
600
600
  }
601
601
  if (prevNoteIntermediate !== null) {
602
- prevNoteIntermediate.data['next'] = connectionIntermediate;
602
+ prevNoteIntermediate.data.next = connectionIntermediate;
603
603
  }
604
604
  prevNoteIntermediate = connectionIntermediate;
605
605
  }
606
606
  if (headNoteIntermediate && prevJointIntermediate) {
607
607
  if (currentSegmentHead) {
608
608
  for (const conn of pendingSegmentConnectors) {
609
- conn.data['segmentHead'] = currentSegmentHead;
610
- conn.data['segmentTail'] = prevJointIntermediate;
609
+ conn.data.segmentHead = currentSegmentHead;
610
+ conn.data.segmentTail = prevJointIntermediate;
611
611
  }
612
612
  }
613
613
  for (const conn of connectors) {
614
- conn.data['activeHead'] = headNoteIntermediate;
615
- conn.data['activeTail'] = prevJointIntermediate;
614
+ conn.data.activeHead = headNoteIntermediate;
615
+ conn.data.activeTail = prevJointIntermediate;
616
616
  }
617
617
  }
618
618
  }
@@ -622,8 +622,8 @@ export const ucmmwsToLevelData = (mmws) => {
622
622
  const beatB = b.data['#BEAT'];
623
623
  if (Math.abs(beatA - beatB) > EPSILON)
624
624
  return beatA - beatB;
625
- const laneA = a.data['lane'];
626
- const laneB = b.data['lane'];
625
+ const laneA = a.data.lane;
626
+ const laneB = b.data.lane;
627
627
  return laneA - laneB;
628
628
  });
629
629
  const simGroups = [];
@@ -2,7 +2,7 @@
2
2
  export const isPJSK = (data) => {
3
3
  if (typeof data !== 'object' || data === null)
4
4
  return false;
5
- const hasNotes = 'notes' in data && Array.isArray(data.notes);
5
+ const hasNotes = 'notes' in data && Array.isArray(data.objects);
6
6
  const hasVersionOrOffset = 'version' in data || 'offset' in data;
7
7
  return hasNotes && hasVersionOrOffset;
8
8
  };
@@ -1,5 +1,5 @@
1
1
  import { USC } from '../usc/index.js';
2
- type PJSKNote = {
2
+ interface PJSKNote {
3
3
  id: number;
4
4
  type: string;
5
5
  beat: number;
@@ -17,11 +17,11 @@ type PJSKNote = {
17
17
  nextNode?: number;
18
18
  prevNode?: number;
19
19
  isEvent?: boolean;
20
- };
21
- type PJSKData = {
20
+ }
21
+ interface PJSKData {
22
22
  offset: number;
23
23
  notes: PJSKNote[];
24
- };
24
+ }
25
25
  /** Convert a pjsk to a USC */
26
26
  export declare const pjskToUSC: (json: string | PJSKData) => USC;
27
27
  export {};
@@ -1,24 +1,24 @@
1
1
  type Meta = Map<string, string[]>;
2
- export type TimeScaleChangeObject = {
2
+ export interface TimeScaleChangeObject {
3
3
  tick: number;
4
4
  timeScale: number;
5
- };
6
- export type BpmChangeObject = {
5
+ }
6
+ export interface BpmChangeObject {
7
7
  tick: number;
8
8
  bpm: number;
9
- };
10
- export type NoteObject = {
9
+ }
10
+ export interface NoteObject {
11
11
  tick: number;
12
12
  lane: number;
13
13
  width: number;
14
14
  type: number;
15
15
  timeScaleGroup: number;
16
- };
17
- export type SlideObject = {
16
+ }
17
+ export interface SlideObject {
18
18
  type: number;
19
19
  notes: NoteObject[];
20
- };
21
- export type Score = {
20
+ }
21
+ export interface Score {
22
22
  offset: number;
23
23
  ticksPerBeat: number;
24
24
  timeScaleChanges: TimeScaleChangeObject[][];
@@ -27,6 +27,6 @@ export type Score = {
27
27
  directionalNotes: NoteObject[];
28
28
  slides: SlideObject[];
29
29
  meta: Meta;
30
- };
30
+ }
31
31
  export declare const analyze: (sus: string) => Score;
32
32
  export {};
@@ -36,6 +36,28 @@ export const analyze = (sus) => {
36
36
  timeScaleChanges[timeScaleIndex].push(...toTimeScaleChanges(line, toTick));
37
37
  }
38
38
  }
39
+ const customSpeedGroups = new Map();
40
+ const getCustomGroup = (baseGroup, speedRatio) => {
41
+ const key = `${baseGroup}_${speedRatio}`;
42
+ const group = customSpeedGroups.get(key);
43
+ if (group !== undefined)
44
+ return group;
45
+ const newIndex = timeScaleChanges.length;
46
+ customSpeedGroups.set(key, newIndex);
47
+ const baseChanges = timeScaleChanges[baseGroup] || [];
48
+ let scaledChanges = [];
49
+ if (baseChanges.length === 0) {
50
+ scaledChanges = [{ tick: 0, timeScale: speedRatio }];
51
+ }
52
+ else {
53
+ scaledChanges = baseChanges.map((change) => ({
54
+ tick: change.tick,
55
+ timeScale: change.timeScale * speedRatio,
56
+ }));
57
+ }
58
+ timeScaleChanges.push(scaledChanges);
59
+ return newIndex;
60
+ };
39
61
  lines.forEach((line, index) => {
40
62
  const [header, data] = line;
41
63
  const measureOffset = measureChanges.find(([changeIndex]) => changeIndex <= index)?.[1] ?? 0;
@@ -62,7 +84,7 @@ export const analyze = (sus) => {
62
84
  }
63
85
  // Tap Notes
64
86
  if (header.length === 5 && header[3] === '1') {
65
- tapNotes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick));
87
+ tapNotes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup));
66
88
  return;
67
89
  }
68
90
  // Streams
@@ -70,19 +92,19 @@ export const analyze = (sus) => {
70
92
  const key = `${header[5]}-${header[3]}`;
71
93
  const stream = streams.get(key);
72
94
  if (stream) {
73
- stream.notes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick));
95
+ stream.notes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup));
74
96
  }
75
97
  else {
76
98
  streams.set(key, {
77
99
  type: +header[3],
78
- notes: toNotes(line, measureOffset, timeScaleGroup, toTick),
100
+ notes: toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup),
79
101
  });
80
102
  }
81
103
  return;
82
104
  }
83
105
  // Directional Notes
84
106
  if (header.length === 5 && header[3] === '5') {
85
- directionalNotes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick));
107
+ directionalNotes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup));
86
108
  return;
87
109
  }
88
110
  });
@@ -208,11 +230,14 @@ const toTimeScaleChanges = ([, data], toTick) => {
208
230
  })
209
231
  .sort((a, b) => a.tick - b.tick);
210
232
  };
211
- const toNotes = (line, measureOffset, timeScaleGroup, toTick) => {
233
+ const toNotes = (line, measureOffset, baseTimeScaleGroup, toTick, getCustomGroup) => {
212
234
  const [header] = line;
213
235
  const lane = parseInt(header[4], 36);
214
- return toRaws(line, measureOffset, toTick).map(({ tick, value }) => {
236
+ return toRaws(line, measureOffset, toTick).map(({ tick, value, speedRatio }) => {
215
237
  const width = parseInt(value[1], 36);
238
+ const timeScaleGroup = speedRatio !== undefined
239
+ ? getCustomGroup(baseTimeScaleGroup, speedRatio)
240
+ : baseTimeScaleGroup;
216
241
  return {
217
242
  tick,
218
243
  lane,
@@ -242,10 +267,21 @@ const toSlides = (stream) => {
242
267
  };
243
268
  const toRaws = ([header, data], measureOffset, toTick) => {
244
269
  const measure = +header.substring(0, 3) + measureOffset;
245
- return (data.match(/.{2}/g) ?? [])
246
- .map((value, i, values) => value !== '00' && {
247
- tick: toTick(measure, i, values.length),
248
- value,
270
+ const matches = [...data.matchAll(/([0-9a-zA-Z]{2})(?:,([-+]?\d*\.?\d+))?/g)];
271
+ return matches
272
+ .map((match, i, values) => {
273
+ const value = match[1];
274
+ if (value === '00')
275
+ return null;
276
+ const speedRatio = match[2] !== undefined ? parseFloat(match[2]) : undefined;
277
+ const rawObject = {
278
+ tick: toTick(measure, i, values.length),
279
+ value,
280
+ };
281
+ if (speedRatio !== undefined) {
282
+ rawObject.speedRatio = speedRatio;
283
+ }
284
+ return rawObject;
249
285
  })
250
- .filter((object) => !!object);
286
+ .filter((object) => object !== null);
251
287
  };
@@ -99,10 +99,10 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
99
99
  '#TIMESCALE_EASE': 0,
100
100
  });
101
101
  if (lastChangeIntermediate === null) {
102
- groupIntermediateEntity.data['first'] = newChangeIntermediate;
102
+ groupIntermediateEntity.data.first = newChangeIntermediate;
103
103
  }
104
104
  else {
105
- lastChangeIntermediate.data['next'] = newChangeIntermediate;
105
+ lastChangeIntermediate.data.next = newChangeIntermediate;
106
106
  }
107
107
  lastChangeIntermediate = newChangeIntermediate;
108
108
  }
@@ -139,7 +139,7 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
139
139
  };
140
140
  const directionValue = SONOLUS_DIRECTIONS[sonolusDirName];
141
141
  if (directionValue !== undefined) {
142
- data['direction'] = directionValue;
142
+ data.direction = directionValue;
143
143
  }
144
144
  createIntermediate(archetype, data, true);
145
145
  }
@@ -249,7 +249,9 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
249
249
  else {
250
250
  sonolusDirName = direction;
251
251
  }
252
- const sonolusEaseValue = SONOLUS_CONNECTOR_EASES[mapUscEaseToSonolusEase('ease' in connectionNote ? connectionNote.ease : undefined)];
252
+ const sonolusEaseValue = SONOLUS_CONNECTOR_EASES[mapUscEaseToSonolusEase('ease' in connectionNote
253
+ ? connectionNote.ease
254
+ : undefined)];
253
255
  let segmentAlpha = 1;
254
256
  if (slideNote.type === 'guide') {
255
257
  // 공식: 1 - (0.8 * (현재단계 / 전체구간)) -> 1에서 0.2까지 감소
@@ -276,7 +278,7 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
276
278
  };
277
279
  const directionValue = SONOLUS_DIRECTIONS[sonolusDirName];
278
280
  if (directionValue !== undefined) {
279
- data['direction'] = directionValue;
281
+ data.direction = directionValue;
280
282
  }
281
283
  const connectionIntermediate = createIntermediate(archetype, data, isSimLineEligible);
282
284
  if (headNoteIntermediate === null) {
@@ -285,15 +287,15 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
285
287
  if (currentSegmentHead === null) {
286
288
  currentSegmentHead = connectionIntermediate;
287
289
  }
288
- connectionIntermediate.data['activeHead'] = headNoteIntermediate;
290
+ connectionIntermediate.data.activeHead = headNoteIntermediate;
289
291
  if (isAttached) {
290
292
  queuedAttachIntermediates.push(connectionIntermediate);
291
293
  }
292
294
  else {
293
295
  if (prevJointIntermediate !== null) {
294
296
  for (const attachIntermediate of queuedAttachIntermediates) {
295
- attachIntermediate.data['attachHead'] = prevJointIntermediate;
296
- attachIntermediate.data['attachTail'] = connectionIntermediate;
297
+ attachIntermediate.data.attachHead = prevJointIntermediate;
298
+ attachIntermediate.data.attachTail = connectionIntermediate;
297
299
  }
298
300
  queuedAttachIntermediates.length = 0;
299
301
  while (slideNote.type == 'slide' &&
@@ -302,8 +304,8 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
302
304
  createIntermediate('TransientHiddenTickNote', {
303
305
  '#BEAT': nextHiddenTickBeat,
304
306
  '#TIMESCALE_GROUP': timeScaleGroupIntermediates[0],
305
- lane: connectionIntermediate.data['lane'],
306
- size: connectionIntermediate.data['size'],
307
+ lane: connectionIntermediate.data.lane,
308
+ size: connectionIntermediate.data.size,
307
309
  direction: SONOLUS_DIRECTIONS.up,
308
310
  isAttached: 1,
309
311
  connectorEase: SONOLUS_CONNECTOR_EASES.linear,
@@ -327,14 +329,14 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
327
329
  }
328
330
  if (isSeparatorValue === 1) {
329
331
  for (const conn of pendingSegmentConnectors) {
330
- conn.data['segmentHead'] = currentSegmentHead;
331
- conn.data['segmentTail'] = connectionIntermediate;
332
+ conn.data.segmentHead = currentSegmentHead;
333
+ conn.data.segmentTail = connectionIntermediate;
332
334
  }
333
335
  pendingSegmentConnectors.length = 0;
334
336
  currentSegmentHead = connectionIntermediate;
335
337
  }
336
338
  if (prevNoteIntermediate !== null) {
337
- prevNoteIntermediate.data['next'] = connectionIntermediate;
339
+ prevNoteIntermediate.data.next = connectionIntermediate;
338
340
  }
339
341
  prevNoteIntermediate = connectionIntermediate;
340
342
  stepIdx++;
@@ -344,14 +346,14 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
344
346
  }
345
347
  if (currentSegmentHead) {
346
348
  for (const conn of pendingSegmentConnectors) {
347
- conn.data['segmentHead'] = currentSegmentHead;
348
- conn.data['segmentTail'] = prevJointIntermediate;
349
+ conn.data.segmentHead = currentSegmentHead;
350
+ conn.data.segmentTail = prevJointIntermediate;
349
351
  }
350
352
  }
351
353
  if (slideNote.type === 'slide') {
352
354
  for (const connectorIntermediate of connectors) {
353
- connectorIntermediate.data['activeHead'] = headNoteIntermediate;
354
- connectorIntermediate.data['activeTail'] = prevJointIntermediate;
355
+ connectorIntermediate.data.activeHead = headNoteIntermediate;
356
+ connectorIntermediate.data.activeTail = prevJointIntermediate;
355
357
  }
356
358
  }
357
359
  }
@@ -397,7 +399,7 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
397
399
  tail: midpointIntermediate,
398
400
  });
399
401
  guideConnectors.push(connectorIntermediate);
400
- prevMidpointIntermediate.data['next'] = midpointIntermediate;
402
+ prevMidpointIntermediate.data.next = midpointIntermediate;
401
403
  }
402
404
  prevMidpointIntermediate = midpointIntermediate;
403
405
  stepIdx++;
@@ -406,16 +408,20 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
406
408
  continue;
407
409
  }
408
410
  for (const connectorIntermediate of guideConnectors) {
409
- connectorIntermediate.data['segmentHead'] = headMidpointIntermediate;
410
- connectorIntermediate.data['segmentTail'] = prevMidpointIntermediate;
411
+ connectorIntermediate.data.segmentHead = headMidpointIntermediate;
412
+ connectorIntermediate.data.segmentTail = prevMidpointIntermediate;
411
413
  }
412
414
  if (!smoothGuideFade) {
413
415
  switch (guideNote.fade) {
414
416
  case 'in':
415
- headMidpointIntermediate.data['segmentAlpha'] = 0;
417
+ if (headMidpointIntermediate) {
418
+ headMidpointIntermediate.data.segmentAlpha = 0;
419
+ }
416
420
  break;
417
421
  case 'out':
418
- prevMidpointIntermediate.data['segmentAlpha'] = 0;
422
+ if (prevMidpointIntermediate) {
423
+ prevMidpointIntermediate.data.segmentAlpha = 0;
424
+ }
419
425
  break;
420
426
  case 'none':
421
427
  break;
@@ -427,8 +433,8 @@ export const uscToLevelData = (usc, offset = 0, smoothGuideFade = false, useGuid
427
433
  const beatB = noteB.data['#BEAT'];
428
434
  if (beatA !== beatB)
429
435
  return beatA - beatB;
430
- const laneA = noteA.data['lane'];
431
- const laneB = noteB.data['lane'];
436
+ const laneA = noteA.data.lane;
437
+ const laneB = noteB.data.lane;
432
438
  return laneA - laneB;
433
439
  });
434
440
  const simGroups = [];
@@ -1,23 +1,23 @@
1
- export type USC = {
1
+ export interface USC {
2
2
  offset: number;
3
3
  objects: USCObject[];
4
- };
4
+ }
5
5
  export type USCObject = USCBpmChange | USCTimeScaleChange | USCSingleNote | USCSlideNote | USCGuideNote | USCDamageNote | USCSkill | USCFever;
6
- type BaseUSCObject = {
6
+ interface BaseUSCObject {
7
7
  beat: number;
8
8
  timeScaleGroup: number;
9
- };
9
+ }
10
10
  export type USCBpmChange = Omit<BaseUSCObject, 'timeScaleGroup'> & {
11
11
  type: 'bpm';
12
12
  bpm: number;
13
13
  };
14
- export type USCTimeScaleChange = {
14
+ export interface USCTimeScaleChange {
15
15
  type: 'timeScaleGroup';
16
16
  changes: {
17
17
  beat: number;
18
18
  timeScale: number;
19
19
  }[];
20
- };
20
+ }
21
21
  type BaseUSCNote = BaseUSCObject & {
22
22
  lane: number;
23
23
  size: number;
@@ -54,7 +54,7 @@ export type USCConnectionEndNote = BaseUSCNote & {
54
54
  direction?: 'left' | 'up' | 'right';
55
55
  judgeType: 'normal' | 'trace' | 'none';
56
56
  };
57
- export type USCSlideNote = {
57
+ export interface USCSlideNote {
58
58
  type: 'slide' | 'guide';
59
59
  critical: boolean;
60
60
  connections: [
@@ -62,7 +62,7 @@ export type USCSlideNote = {
62
62
  ...(USCConnectionTickNote | USCConnectionAttachNote)[],
63
63
  USCConnectionEndNote
64
64
  ];
65
- };
65
+ }
66
66
  export declare const USCColor: {
67
67
  neutral: number;
68
68
  red: number;
@@ -83,12 +83,12 @@ export declare const USCFade: {
83
83
  none: number;
84
84
  };
85
85
  export type USCFade = keyof typeof USCFade;
86
- export type USCGuideNote = {
86
+ export interface USCGuideNote {
87
87
  type: 'guide';
88
88
  color: USCColor;
89
89
  fade: USCFade;
90
90
  midpoints: USCGuideMidpointNote[];
91
- };
91
+ }
92
92
  export declare const SkillEffects: {
93
93
  readonly none: 0;
94
94
  readonly heal: 1;
@@ -97,9 +97,9 @@ export declare const SkillEffects: {
97
97
  };
98
98
  export type SkillEffects = (typeof SkillEffects)[keyof typeof SkillEffects];
99
99
  type Level = 1 | 2 | 3 | 4;
100
- type USCEvent = {
100
+ interface USCEvent {
101
101
  beat: number;
102
- };
102
+ }
103
103
  export type USCSkill = USCEvent & {
104
104
  type: 'skill';
105
105
  effect?: SkillEffects;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonolus-next-rush-plus-engine",
3
- "version": "1.8.9",
3
+ "version": "1.8.10",
4
4
  "description": "A new Project Sekai inspired engine for Sonolus",
5
5
  "author": "Hyeon2",
6
6
  "repository": {
@@ -27,8 +27,11 @@
27
27
  },
28
28
  "scripts": {
29
29
  "check-type": "tsc -p . --noEmit",
30
+ "check-lint": "biome lint",
31
+ "check-format": "biome format",
30
32
  "biome": "biome check",
31
- "format": "biome format --write --files-max-size=100000000000",
33
+ "format": "biome format --write",
34
+ "fix": "biome check --write",
32
35
  "version": "node ./version.js",
33
36
  "build": "tsc -p . && node ./build.js"
34
37
  },
@@ -36,10 +39,9 @@
36
39
  "@sonolus/core": "~7.15.1"
37
40
  },
38
41
  "devDependencies": {
39
- "@biomejs/biome": "~2.4.10",
42
+ "@biomejs/biome": "~2.4.12",
40
43
  "@sonolus/sonolus.js": "~9.7.1",
41
- "@types/node": "^25.5.0",
42
- "typescript": "~6.0.2",
43
- "typescript-eslint": "^8.58.0"
44
+ "@types/node": "^25.6.0",
45
+ "typescript": "~6.0.3"
44
46
  }
45
- }
47
+ }