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