sonolus-next-rush-engine 0.1.0
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.
- package/LICENSE.txt +21 -0
- package/README.md +38 -0
- package/dist/EngineConfiguration +0 -0
- package/dist/EnginePlayData +0 -0
- package/dist/EnginePreviewData +0 -0
- package/dist/EngineRom +0 -0
- package/dist/EngineTutorialData +0 -0
- package/dist/EngineWatchData +0 -0
- package/dist/LevelData/analyze.d.ts +3 -0
- package/dist/LevelData/analyze.js +7 -0
- package/dist/extended/analyze.d.ts +3 -0
- package/dist/extended/analyze.js +8 -0
- package/dist/extended/convert.d.ts +12 -0
- package/dist/extended/convert.js +453 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +84 -0
- package/dist/lib/binaryseeker/mod.d.ts +14 -0
- package/dist/lib/binaryseeker/mod.js +9 -0
- package/dist/lib/binaryseeker/reader.d.ts +157 -0
- package/dist/lib/binaryseeker/reader.js +305 -0
- package/dist/lib/binaryseeker/writer.d.ts +171 -0
- package/dist/lib/binaryseeker/writer.js +360 -0
- package/dist/mmw/analyze.d.ts +109 -0
- package/dist/mmw/analyze.js +256 -0
- package/dist/mmw/convert.d.ts +14 -0
- package/dist/mmw/convert.js +681 -0
- package/dist/sus/analyze.d.ts +32 -0
- package/dist/sus/analyze.js +287 -0
- package/dist/sus/convert.d.ts +5 -0
- package/dist/sus/convert.js +320 -0
- package/dist/thumbnail.png +0 -0
- package/dist/usc/analyze.d.ts +3 -0
- package/dist/usc/analyze.js +7 -0
- package/dist/usc/convert.d.ts +4 -0
- package/dist/usc/convert.js +496 -0
- package/dist/usc/index.d.ts +112 -0
- package/dist/usc/index.js +21 -0
- package/package.json +47 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type Meta = Map<string, string[]>;
|
|
2
|
+
export interface TimeScaleChangeObject {
|
|
3
|
+
tick: number;
|
|
4
|
+
timeScale: number;
|
|
5
|
+
}
|
|
6
|
+
export interface BpmChangeObject {
|
|
7
|
+
tick: number;
|
|
8
|
+
bpm: number;
|
|
9
|
+
}
|
|
10
|
+
export interface NoteObject {
|
|
11
|
+
tick: number;
|
|
12
|
+
lane: number;
|
|
13
|
+
width: number;
|
|
14
|
+
type: number;
|
|
15
|
+
timeScaleGroup: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SlideObject {
|
|
18
|
+
type: number;
|
|
19
|
+
notes: NoteObject[];
|
|
20
|
+
}
|
|
21
|
+
export interface Score {
|
|
22
|
+
offset: number;
|
|
23
|
+
ticksPerBeat: number;
|
|
24
|
+
timeScaleChanges: TimeScaleChangeObject[][];
|
|
25
|
+
bpmChanges: BpmChangeObject[];
|
|
26
|
+
tapNotes: NoteObject[];
|
|
27
|
+
directionalNotes: NoteObject[];
|
|
28
|
+
slides: SlideObject[];
|
|
29
|
+
meta: Meta;
|
|
30
|
+
}
|
|
31
|
+
export declare const analyze: (sus: string) => Score;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
export const analyze = (sus) => {
|
|
2
|
+
if (!sus || sus.trim().length === 0) {
|
|
3
|
+
throw new Error('The sus file does not exist');
|
|
4
|
+
}
|
|
5
|
+
const { lines, measureChanges, timeScaleGroupChanges, meta } = parse(sus);
|
|
6
|
+
const offset = -+(meta.get('WAVEOFFSET') || '0');
|
|
7
|
+
if (Number.isNaN(offset))
|
|
8
|
+
throw new Error('Unexpected offset');
|
|
9
|
+
const ticksPerBeat = getTicksPerBeat(meta);
|
|
10
|
+
if (!ticksPerBeat)
|
|
11
|
+
throw new Error('Missing or unexpected ticks per beat');
|
|
12
|
+
const barLengths = getBarLengths(lines, measureChanges);
|
|
13
|
+
const toTick = getToTick(barLengths, ticksPerBeat);
|
|
14
|
+
const bpms = new Map();
|
|
15
|
+
const bpmChanges = [];
|
|
16
|
+
const timeScaleGroups = new Map();
|
|
17
|
+
const timeScaleChanges = [];
|
|
18
|
+
const tapNotes = [];
|
|
19
|
+
const directionalNotes = [];
|
|
20
|
+
const streams = new Map();
|
|
21
|
+
for (const [, timeScaleGroup] of timeScaleGroupChanges) {
|
|
22
|
+
if (timeScaleGroups.has(timeScaleGroup))
|
|
23
|
+
continue;
|
|
24
|
+
timeScaleGroups.set(timeScaleGroup, timeScaleGroups.size);
|
|
25
|
+
timeScaleChanges.push([]);
|
|
26
|
+
}
|
|
27
|
+
// Time Scale Changes
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const [header] = line;
|
|
30
|
+
if (header.length === 5 && header.startsWith('TIL')) {
|
|
31
|
+
const timeScaleGroup = header.substring(3, 5);
|
|
32
|
+
const timeScaleIndex = timeScaleGroups.get(timeScaleGroup);
|
|
33
|
+
if (timeScaleIndex === undefined) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
timeScaleChanges[timeScaleIndex].push(...toTimeScaleChanges(line, toTick));
|
|
37
|
+
}
|
|
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
|
+
};
|
|
61
|
+
lines.forEach((line, index) => {
|
|
62
|
+
const [header, data] = line;
|
|
63
|
+
const measureOffset = measureChanges.find(([changeIndex]) => changeIndex <= index)?.[1] ?? 0;
|
|
64
|
+
const timeScaleGroupName = timeScaleGroupChanges.find(([changeIndex]) => changeIndex <= index)?.[1] ?? '00';
|
|
65
|
+
let timeScaleGroup = timeScaleGroups.get(timeScaleGroupName);
|
|
66
|
+
if (timeScaleGroup === undefined) {
|
|
67
|
+
timeScaleGroup = timeScaleGroups.size;
|
|
68
|
+
timeScaleGroups.set(timeScaleGroupName, timeScaleGroups.size);
|
|
69
|
+
timeScaleChanges.push([]);
|
|
70
|
+
}
|
|
71
|
+
// Hispeed definitions
|
|
72
|
+
if (header.length === 5 && header.startsWith('TIL')) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// BPM
|
|
76
|
+
if (header.length === 5 && header.startsWith('BPM')) {
|
|
77
|
+
bpms.set(header.substring(3), +data);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// BPM Changes
|
|
81
|
+
if (header.length === 5 && header.endsWith('08')) {
|
|
82
|
+
bpmChanges.push(...toBpmChanges(line, measureOffset, bpms, toTick));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Tap Notes
|
|
86
|
+
if (header.length === 5 && header[3] === '1') {
|
|
87
|
+
tapNotes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Streams
|
|
91
|
+
if (header.length === 6 && (header[3] === '3' || header[3] === '9')) {
|
|
92
|
+
const key = `${header[5]}-${header[3]}`;
|
|
93
|
+
const stream = streams.get(key);
|
|
94
|
+
if (stream) {
|
|
95
|
+
stream.notes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
streams.set(key, {
|
|
99
|
+
type: +header[3],
|
|
100
|
+
notes: toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Directional Notes
|
|
106
|
+
if (header.length === 5 && header[3] === '5') {
|
|
107
|
+
directionalNotes.push(...toNotes(line, measureOffset, timeScaleGroup, toTick, getCustomGroup));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const slides = [...streams.values()].flatMap(toSlides);
|
|
112
|
+
return {
|
|
113
|
+
offset,
|
|
114
|
+
ticksPerBeat,
|
|
115
|
+
timeScaleChanges,
|
|
116
|
+
bpmChanges,
|
|
117
|
+
tapNotes,
|
|
118
|
+
directionalNotes,
|
|
119
|
+
slides,
|
|
120
|
+
meta,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const parse = (sus) => {
|
|
124
|
+
const lines = [];
|
|
125
|
+
const measureChanges = [];
|
|
126
|
+
const timeScaleGroupChanges = [];
|
|
127
|
+
const meta = new Map();
|
|
128
|
+
sus.split('\n')
|
|
129
|
+
.map((line) => line.trim())
|
|
130
|
+
.filter((line) => line.startsWith('#'))
|
|
131
|
+
.forEach((line) => {
|
|
132
|
+
const isLine = line.includes(':');
|
|
133
|
+
const index = line.indexOf(isLine ? ':' : ' ');
|
|
134
|
+
if (index === -1)
|
|
135
|
+
return;
|
|
136
|
+
const left = line.substring(1, index).trim();
|
|
137
|
+
const right = line.substring(index + 1).trim();
|
|
138
|
+
if (isLine) {
|
|
139
|
+
lines.push([left, right]);
|
|
140
|
+
}
|
|
141
|
+
else if (left === 'MEASUREBS') {
|
|
142
|
+
measureChanges.unshift([lines.length, +right]);
|
|
143
|
+
}
|
|
144
|
+
else if (left === 'HISPEED') {
|
|
145
|
+
timeScaleGroupChanges.unshift([lines.length, right]);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
if (!meta.has(left))
|
|
149
|
+
meta.set(left, []);
|
|
150
|
+
meta.get(left)?.push(right);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
lines,
|
|
155
|
+
measureChanges,
|
|
156
|
+
timeScaleGroupChanges,
|
|
157
|
+
meta,
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
const getTicksPerBeat = (meta) => {
|
|
161
|
+
const request = meta.get('REQUEST');
|
|
162
|
+
if (!request)
|
|
163
|
+
return;
|
|
164
|
+
const tpbRequest = request.find((r) => JSON.parse(r).startsWith('ticks_per_beat'));
|
|
165
|
+
if (!tpbRequest)
|
|
166
|
+
return;
|
|
167
|
+
return +JSON.parse(tpbRequest).split(' ')[1];
|
|
168
|
+
};
|
|
169
|
+
const getBarLengths = (lines, measureChanges) => {
|
|
170
|
+
const barLengths = [];
|
|
171
|
+
lines.forEach((line, index) => {
|
|
172
|
+
const [header, data] = line;
|
|
173
|
+
if (header.length !== 5)
|
|
174
|
+
return;
|
|
175
|
+
if (!header.endsWith('02'))
|
|
176
|
+
return;
|
|
177
|
+
const measure = +header.substring(0, 3) +
|
|
178
|
+
(measureChanges.find(([changeIndex]) => changeIndex <= index)?.[1] ?? 0);
|
|
179
|
+
if (Number.isNaN(measure))
|
|
180
|
+
return;
|
|
181
|
+
barLengths.push({ measure, length: +data });
|
|
182
|
+
});
|
|
183
|
+
return barLengths;
|
|
184
|
+
};
|
|
185
|
+
const getToTick = (barLengths, ticksPerBeat) => {
|
|
186
|
+
let ticks = 0;
|
|
187
|
+
const bars = barLengths
|
|
188
|
+
.sort((a, b) => a.measure - b.measure)
|
|
189
|
+
.map(({ measure, length }, i, values) => {
|
|
190
|
+
if (i) {
|
|
191
|
+
const prev = values[i - 1];
|
|
192
|
+
ticks += (measure - prev.measure) * prev.length * ticksPerBeat;
|
|
193
|
+
}
|
|
194
|
+
return { measure, ticksPerMeasure: length * ticksPerBeat, ticks };
|
|
195
|
+
})
|
|
196
|
+
.reverse();
|
|
197
|
+
return (measure, p, q) => {
|
|
198
|
+
const bar = bars.find((bar) => measure >= bar.measure);
|
|
199
|
+
if (!bar)
|
|
200
|
+
throw new Error('Unexpected missing bar');
|
|
201
|
+
return (bar.ticks +
|
|
202
|
+
(measure - bar.measure) * bar.ticksPerMeasure +
|
|
203
|
+
(p * bar.ticksPerMeasure) / q);
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
const toBpmChanges = (line, measureOffset, bpms, toTick) => toRaws(line, measureOffset, toTick).map(({ tick, value }) => ({
|
|
207
|
+
tick,
|
|
208
|
+
bpm: bpms.get(value) ?? 0,
|
|
209
|
+
}));
|
|
210
|
+
const toTimeScaleChanges = ([, data], toTick) => {
|
|
211
|
+
if (!data.startsWith('"') || !data.endsWith('"'))
|
|
212
|
+
throw new Error('Unexpected time scale changes');
|
|
213
|
+
return data
|
|
214
|
+
.slice(1, -1)
|
|
215
|
+
.split(',')
|
|
216
|
+
.map((segment) => segment.trim())
|
|
217
|
+
.filter((segment) => !!segment)
|
|
218
|
+
.map((segment) => {
|
|
219
|
+
const [l, rest] = segment.split("'");
|
|
220
|
+
const [m, r] = rest.split(':');
|
|
221
|
+
const measure = +l;
|
|
222
|
+
const tick = +m;
|
|
223
|
+
const timeScale = +r;
|
|
224
|
+
if (Number.isNaN(measure) || Number.isNaN(tick) || Number.isNaN(timeScale))
|
|
225
|
+
throw new Error('Unexpected time scale change');
|
|
226
|
+
return {
|
|
227
|
+
tick: toTick(measure, 0, 1) + tick,
|
|
228
|
+
timeScale,
|
|
229
|
+
};
|
|
230
|
+
})
|
|
231
|
+
.sort((a, b) => a.tick - b.tick);
|
|
232
|
+
};
|
|
233
|
+
const toNotes = (line, measureOffset, baseTimeScaleGroup, toTick, getCustomGroup) => {
|
|
234
|
+
const [header] = line;
|
|
235
|
+
const lane = parseInt(header[4], 36);
|
|
236
|
+
return toRaws(line, measureOffset, toTick).map(({ tick, value, speedRatio }) => {
|
|
237
|
+
const width = parseInt(value[1], 36);
|
|
238
|
+
const timeScaleGroup = speedRatio !== undefined
|
|
239
|
+
? getCustomGroup(baseTimeScaleGroup, speedRatio)
|
|
240
|
+
: baseTimeScaleGroup;
|
|
241
|
+
return {
|
|
242
|
+
tick,
|
|
243
|
+
lane,
|
|
244
|
+
width,
|
|
245
|
+
type: parseInt(value[0], 36),
|
|
246
|
+
timeScaleGroup,
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
const toSlides = (stream) => {
|
|
251
|
+
const slides = [];
|
|
252
|
+
let notes;
|
|
253
|
+
for (const note of stream.notes.sort((a, b) => a.tick - b.tick)) {
|
|
254
|
+
if (!notes) {
|
|
255
|
+
notes = [];
|
|
256
|
+
slides.push({
|
|
257
|
+
type: stream.type,
|
|
258
|
+
notes,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
notes.push(note);
|
|
262
|
+
if (note.type === 2) {
|
|
263
|
+
notes = undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return slides;
|
|
267
|
+
};
|
|
268
|
+
const toRaws = ([header, data], measureOffset, toTick) => {
|
|
269
|
+
const measure = +header.substring(0, 3) + measureOffset;
|
|
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;
|
|
285
|
+
})
|
|
286
|
+
.filter((object) => object !== null);
|
|
287
|
+
};
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { analyze } from './analyze.js';
|
|
2
|
+
/** Convert a SUS to a USC */
|
|
3
|
+
export const susToUSC = (sus) => chsLikeToUSC(analyze(sus));
|
|
4
|
+
export const chsLikeToUSC = (score) => {
|
|
5
|
+
const flickMods = new Map();
|
|
6
|
+
const traceMods = new Set();
|
|
7
|
+
const criticalMods = new Set();
|
|
8
|
+
const tickRemoveMods = new Set();
|
|
9
|
+
const slideStartEndRemoveMods = new Set();
|
|
10
|
+
const easeMods = new Map();
|
|
11
|
+
const preventSingles = new Set();
|
|
12
|
+
const dedupeSingles = new Set();
|
|
13
|
+
const dedupeSlides = new Map();
|
|
14
|
+
const requests = {
|
|
15
|
+
sideLane: false,
|
|
16
|
+
laneOffset: 0,
|
|
17
|
+
};
|
|
18
|
+
const requestsRaw = score.meta.get('REQUEST');
|
|
19
|
+
if (requestsRaw) {
|
|
20
|
+
for (const request of requestsRaw) {
|
|
21
|
+
try {
|
|
22
|
+
const [key, value] = JSON.parse(request).split(' ', 2);
|
|
23
|
+
switch (key) {
|
|
24
|
+
case 'side_lane':
|
|
25
|
+
requests.sideLane = value === 'true';
|
|
26
|
+
break;
|
|
27
|
+
case 'lane_offset':
|
|
28
|
+
requests.laneOffset = Number(value);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (_e) {
|
|
33
|
+
// Noop
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const slide of score.slides) {
|
|
38
|
+
if (slide.type !== 3)
|
|
39
|
+
continue;
|
|
40
|
+
for (const note of slide.notes) {
|
|
41
|
+
const key = getKey(note);
|
|
42
|
+
switch (note.type) {
|
|
43
|
+
case 1:
|
|
44
|
+
case 2:
|
|
45
|
+
case 3:
|
|
46
|
+
case 5:
|
|
47
|
+
preventSingles.add(key);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const note of score.directionalNotes) {
|
|
53
|
+
const key = getKey(note);
|
|
54
|
+
switch (note.type) {
|
|
55
|
+
case 1:
|
|
56
|
+
flickMods.set(key, 'up');
|
|
57
|
+
break;
|
|
58
|
+
case 3:
|
|
59
|
+
flickMods.set(key, 'left');
|
|
60
|
+
break;
|
|
61
|
+
case 4:
|
|
62
|
+
flickMods.set(key, 'right');
|
|
63
|
+
break;
|
|
64
|
+
case 2:
|
|
65
|
+
easeMods.set(key, 'in');
|
|
66
|
+
break;
|
|
67
|
+
case 5:
|
|
68
|
+
case 6:
|
|
69
|
+
easeMods.set(key, 'out');
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const note of score.tapNotes) {
|
|
74
|
+
const key = getKey(note);
|
|
75
|
+
switch (note.type) {
|
|
76
|
+
case 2:
|
|
77
|
+
criticalMods.add(key);
|
|
78
|
+
break;
|
|
79
|
+
case 5:
|
|
80
|
+
traceMods.add(key);
|
|
81
|
+
break;
|
|
82
|
+
case 6:
|
|
83
|
+
traceMods.add(key);
|
|
84
|
+
criticalMods.add(key);
|
|
85
|
+
break;
|
|
86
|
+
case 3:
|
|
87
|
+
tickRemoveMods.add(key);
|
|
88
|
+
break;
|
|
89
|
+
case 7:
|
|
90
|
+
slideStartEndRemoveMods.add(key);
|
|
91
|
+
break;
|
|
92
|
+
case 8:
|
|
93
|
+
criticalMods.add(key);
|
|
94
|
+
slideStartEndRemoveMods.add(key);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const objects = [];
|
|
99
|
+
for (const timeScaleChanges of score.timeScaleChanges) {
|
|
100
|
+
objects.push({
|
|
101
|
+
type: 'timeScaleGroup',
|
|
102
|
+
changes: timeScaleChanges.map((timeScaleChange) => ({
|
|
103
|
+
beat: timeScaleChange.tick / score.ticksPerBeat,
|
|
104
|
+
timeScale: timeScaleChange.timeScale,
|
|
105
|
+
})),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
for (const bpmChange of score.bpmChanges) {
|
|
109
|
+
objects.push({
|
|
110
|
+
type: 'bpm',
|
|
111
|
+
beat: bpmChange.tick / score.ticksPerBeat,
|
|
112
|
+
bpm: bpmChange.bpm,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
for (const note of score.tapNotes) {
|
|
116
|
+
if (note.lane === 0 && note.type === 4) {
|
|
117
|
+
objects.push({
|
|
118
|
+
type: 'skill',
|
|
119
|
+
beat: note.tick / score.ticksPerBeat,
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (note.lane === 15 && note.type === 1) {
|
|
124
|
+
objects.push({
|
|
125
|
+
type: 'feverChance',
|
|
126
|
+
beat: note.tick / score.ticksPerBeat,
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (note.lane === 15 && note.type === 2) {
|
|
131
|
+
objects.push({
|
|
132
|
+
type: 'feverStart',
|
|
133
|
+
beat: note.tick / score.ticksPerBeat,
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!requests.sideLane && (note.lane <= 1 || note.lane >= 14))
|
|
138
|
+
continue;
|
|
139
|
+
if (note.type !== 1 &&
|
|
140
|
+
note.type !== 2 &&
|
|
141
|
+
note.type !== 5 &&
|
|
142
|
+
note.type !== 6 &&
|
|
143
|
+
note.type !== 4)
|
|
144
|
+
continue;
|
|
145
|
+
const key = getKey(note);
|
|
146
|
+
if (preventSingles.has(key))
|
|
147
|
+
continue;
|
|
148
|
+
if (dedupeSingles.has(key))
|
|
149
|
+
continue;
|
|
150
|
+
dedupeSingles.add(key);
|
|
151
|
+
let object;
|
|
152
|
+
switch (note.type) {
|
|
153
|
+
case 1:
|
|
154
|
+
case 2:
|
|
155
|
+
case 5:
|
|
156
|
+
case 6: {
|
|
157
|
+
object = {
|
|
158
|
+
type: 'single',
|
|
159
|
+
beat: note.tick / score.ticksPerBeat,
|
|
160
|
+
lane: note.lane - 8 + note.width / 2 + requests.laneOffset,
|
|
161
|
+
size: note.width / 2,
|
|
162
|
+
critical: [2, 6].includes(note.type),
|
|
163
|
+
trace: [5, 6].includes(note.type),
|
|
164
|
+
timeScaleGroup: note.timeScaleGroup,
|
|
165
|
+
};
|
|
166
|
+
const flickMod = flickMods.get(key);
|
|
167
|
+
if (flickMod)
|
|
168
|
+
object.direction = flickMod;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 4:
|
|
172
|
+
object = {
|
|
173
|
+
type: 'damage',
|
|
174
|
+
beat: note.tick / score.ticksPerBeat,
|
|
175
|
+
lane: note.lane - 8 + note.width / 2 + requests.laneOffset,
|
|
176
|
+
size: note.width / 2,
|
|
177
|
+
timeScaleGroup: note.timeScaleGroup,
|
|
178
|
+
};
|
|
179
|
+
break;
|
|
180
|
+
default:
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
objects.push(object);
|
|
184
|
+
}
|
|
185
|
+
for (const slide of score.slides) {
|
|
186
|
+
const startNote = slide.notes.find(({ type }) => type === 1 || type === 2);
|
|
187
|
+
if (!startNote)
|
|
188
|
+
continue;
|
|
189
|
+
const object = {
|
|
190
|
+
type: slide.type === 3 ? 'slide' : 'guide',
|
|
191
|
+
critical: criticalMods.has(getKey(startNote)),
|
|
192
|
+
connections: [],
|
|
193
|
+
};
|
|
194
|
+
for (const note of slide.notes) {
|
|
195
|
+
const key = getKey(note);
|
|
196
|
+
const beat = note.tick / score.ticksPerBeat;
|
|
197
|
+
const lane = note.lane - 8 + note.width / 2 + requests.laneOffset;
|
|
198
|
+
const size = note.width / 2;
|
|
199
|
+
const timeScaleGroup = note.timeScaleGroup;
|
|
200
|
+
const critical = ('critical' in object && object.critical) || criticalMods.has(key);
|
|
201
|
+
const ease = easeMods.get(key) ?? 'linear';
|
|
202
|
+
let judgeType = 'normal';
|
|
203
|
+
if (traceMods.has(key))
|
|
204
|
+
judgeType = 'trace';
|
|
205
|
+
switch (note.type) {
|
|
206
|
+
case 1: {
|
|
207
|
+
if (object.type == 'guide' || slideStartEndRemoveMods.has(key)) {
|
|
208
|
+
const connection = {
|
|
209
|
+
type: 'start',
|
|
210
|
+
beat,
|
|
211
|
+
lane,
|
|
212
|
+
size,
|
|
213
|
+
critical,
|
|
214
|
+
ease,
|
|
215
|
+
judgeType: 'none',
|
|
216
|
+
timeScaleGroup,
|
|
217
|
+
};
|
|
218
|
+
object.connections.push(connection);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const connection = {
|
|
222
|
+
type: 'start',
|
|
223
|
+
beat,
|
|
224
|
+
lane,
|
|
225
|
+
size,
|
|
226
|
+
critical,
|
|
227
|
+
ease: easeMods.get(key) ?? 'linear',
|
|
228
|
+
judgeType,
|
|
229
|
+
timeScaleGroup,
|
|
230
|
+
};
|
|
231
|
+
object.connections.push(connection);
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case 2: {
|
|
236
|
+
if (object.type == 'guide' || slideStartEndRemoveMods.has(key)) {
|
|
237
|
+
const connection = {
|
|
238
|
+
type: 'end',
|
|
239
|
+
beat,
|
|
240
|
+
lane,
|
|
241
|
+
size,
|
|
242
|
+
critical,
|
|
243
|
+
judgeType: 'none',
|
|
244
|
+
timeScaleGroup,
|
|
245
|
+
};
|
|
246
|
+
object.connections.push(connection);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const connection = {
|
|
250
|
+
type: 'end',
|
|
251
|
+
beat,
|
|
252
|
+
lane,
|
|
253
|
+
size,
|
|
254
|
+
critical,
|
|
255
|
+
judgeType,
|
|
256
|
+
timeScaleGroup,
|
|
257
|
+
};
|
|
258
|
+
const flickMod = flickMods.get(key);
|
|
259
|
+
if (flickMod)
|
|
260
|
+
connection.direction = flickMod;
|
|
261
|
+
object.connections.push(connection);
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case 3: {
|
|
266
|
+
if (tickRemoveMods.has(key)) {
|
|
267
|
+
const connection = {
|
|
268
|
+
type: 'attach',
|
|
269
|
+
beat,
|
|
270
|
+
critical,
|
|
271
|
+
timeScaleGroup,
|
|
272
|
+
};
|
|
273
|
+
object.connections.push(connection);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
const connection = {
|
|
277
|
+
type: 'tick',
|
|
278
|
+
beat,
|
|
279
|
+
lane,
|
|
280
|
+
size,
|
|
281
|
+
critical,
|
|
282
|
+
ease,
|
|
283
|
+
judgeType,
|
|
284
|
+
timeScaleGroup,
|
|
285
|
+
};
|
|
286
|
+
object.connections.push(connection);
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 5: {
|
|
291
|
+
if (tickRemoveMods.has(key))
|
|
292
|
+
break;
|
|
293
|
+
const connection = {
|
|
294
|
+
type: 'tick',
|
|
295
|
+
beat,
|
|
296
|
+
lane,
|
|
297
|
+
size,
|
|
298
|
+
ease,
|
|
299
|
+
timeScaleGroup,
|
|
300
|
+
};
|
|
301
|
+
object.connections.push(connection);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
objects.push(object);
|
|
307
|
+
if (object.type == 'guide')
|
|
308
|
+
continue;
|
|
309
|
+
const key = getKey(startNote);
|
|
310
|
+
const dupe = dedupeSlides.get(key);
|
|
311
|
+
if (dupe)
|
|
312
|
+
objects.splice(objects.indexOf(dupe), 1);
|
|
313
|
+
dedupeSlides.set(key, object);
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
offset: score.offset,
|
|
317
|
+
objects,
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
const getKey = (note) => `${note.lane}-${note.tick}`;
|
|
Binary file
|