rhythia-api 109.0.0 → 110.0.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/api/createBeatmap.ts +2 -5
- package/package.json +3 -2
- package/utils/star-calc/index.ts +43 -0
- package/utils/star-calc/osuUtils.ts +53 -0
- package/utils/star-calc/sspmParser.ts +394 -0
package/api/createBeatmap.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
SSPMParsedMap,
|
|
7
|
-
SSPMParser,
|
|
8
|
-
} from "rhythia-star-calculator/src/sspmParser";
|
|
4
|
+
import { SSPMParser } from "../utils/star-calc/sspmParser";
|
|
9
5
|
|
|
10
6
|
export const Schema = {
|
|
11
7
|
input: z.strictObject({
|
|
12
8
|
url: z.string(),
|
|
9
|
+
session: z.string(),
|
|
13
10
|
}),
|
|
14
11
|
output: z.strictObject({
|
|
15
12
|
hash: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "110.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update": "bun ./scripts/update.ts",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"isomorphic-git": "^1.27.1",
|
|
31
31
|
"lodash": "^4.17.21",
|
|
32
32
|
"next": "^14.2.5",
|
|
33
|
-
"
|
|
33
|
+
"osu-parsers": "^4.1.7",
|
|
34
|
+
"osu-standard-stable": "^5.0.0",
|
|
34
35
|
"simple-git": "^3.25.0",
|
|
35
36
|
"supabase": "^1.192.5",
|
|
36
37
|
"tsx": "^4.17.0",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BeatmapDecoder } from "osu-parsers";
|
|
2
|
+
import { StandardRuleset } from "osu-standard-stable";
|
|
3
|
+
import { sampleMap } from "./osuUtils";
|
|
4
|
+
import { SSPMParsedMap } from "./sspmParser";
|
|
5
|
+
|
|
6
|
+
function easeInExpoDeq(x: number) {
|
|
7
|
+
return x === 0 ? 0 : Math.pow(2, 35 * x - 35);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function calculatePerformancePoints(
|
|
11
|
+
starRating: number,
|
|
12
|
+
accuracy: number
|
|
13
|
+
) {
|
|
14
|
+
return Math.round(
|
|
15
|
+
Math.pow((starRating * easeInExpoDeq(accuracy) * 100) / 2, 2) / 1000
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function rateMap(map: SSPMParsedMap) {
|
|
20
|
+
const decoder = new BeatmapDecoder();
|
|
21
|
+
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
22
|
+
|
|
23
|
+
const notes = map.markers
|
|
24
|
+
.filter((marker) => marker.type === 0)
|
|
25
|
+
.map((marker) => ({
|
|
26
|
+
time: marker.position,
|
|
27
|
+
x: marker.data["field0"].x,
|
|
28
|
+
y: marker.data["field0"].y,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
for (const note of notes) {
|
|
32
|
+
const hittable = beatmap1.hitObjects[0].clone();
|
|
33
|
+
hittable.startX = Math.round((note.x / 2) * 100);
|
|
34
|
+
hittable.startY = Math.round((note.y / 2) * 100);
|
|
35
|
+
hittable.startTime = note.time;
|
|
36
|
+
beatmap1.hitObjects.push(hittable);
|
|
37
|
+
}
|
|
38
|
+
const ruleset = new StandardRuleset();
|
|
39
|
+
const mods = ruleset.createModCombination("RX");
|
|
40
|
+
const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
|
|
41
|
+
const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
|
|
42
|
+
return difficultyAttributes.starRating;
|
|
43
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const sampleMap = `osu file format v14
|
|
2
|
+
|
|
3
|
+
[General]
|
|
4
|
+
AudioFilename: audio.mp3
|
|
5
|
+
AudioLeadIn: 0
|
|
6
|
+
PreviewTime: 99664
|
|
7
|
+
Countdown: 0
|
|
8
|
+
SampleSet: Normal
|
|
9
|
+
StackLeniency: 0
|
|
10
|
+
Mode: 0
|
|
11
|
+
LetterboxInBreaks: 0
|
|
12
|
+
UseSkinSprites: 1
|
|
13
|
+
SkinPreference:Default
|
|
14
|
+
WidescreenStoryboard: 1
|
|
15
|
+
SamplesMatchPlaybackRate: 1
|
|
16
|
+
|
|
17
|
+
[Editor]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
[Metadata]
|
|
21
|
+
Title:new beginnings
|
|
22
|
+
TitleUnicode:new beginnings
|
|
23
|
+
Artist:nekodex
|
|
24
|
+
ArtistUnicode:nekodex
|
|
25
|
+
Creator:pishifat
|
|
26
|
+
Version:tutorial
|
|
27
|
+
Source:
|
|
28
|
+
Tags:
|
|
29
|
+
BeatmapID:2116202
|
|
30
|
+
BeatmapSetID:1011011
|
|
31
|
+
|
|
32
|
+
[Difficulty]
|
|
33
|
+
HPDrainRate:5
|
|
34
|
+
CircleSize:2
|
|
35
|
+
OverallDifficulty:8
|
|
36
|
+
ApproachRate:8
|
|
37
|
+
SliderMultiplier:1
|
|
38
|
+
SliderTickRate:1
|
|
39
|
+
|
|
40
|
+
[Events]
|
|
41
|
+
//Background and Video events
|
|
42
|
+
0,0,"new-beginnings.jpg",0,0
|
|
43
|
+
|
|
44
|
+
[TimingPoints]
|
|
45
|
+
-28,461.538461538462,4,1,0,100,1,0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
[Colours]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
[HitObjects]
|
|
52
|
+
256,192,24202,1,0,0:0:0:0:
|
|
53
|
+
`;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
type DataTypeID =
|
|
2
|
+
| 0x00
|
|
3
|
+
| 0x01
|
|
4
|
+
| 0x02
|
|
5
|
+
| 0x03
|
|
6
|
+
| 0x04
|
|
7
|
+
| 0x05
|
|
8
|
+
| 0x06
|
|
9
|
+
| 0x07
|
|
10
|
+
| 0x08
|
|
11
|
+
| 0x09
|
|
12
|
+
| 0x0a
|
|
13
|
+
| 0x0b
|
|
14
|
+
| 0x0c;
|
|
15
|
+
|
|
16
|
+
interface Header {
|
|
17
|
+
signature: Buffer;
|
|
18
|
+
version: number;
|
|
19
|
+
reserved: Buffer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface StaticMetadata {
|
|
23
|
+
sha1: Buffer;
|
|
24
|
+
lastMarkerPos: number;
|
|
25
|
+
noteCount: number;
|
|
26
|
+
markerCount: number;
|
|
27
|
+
difficulty: number;
|
|
28
|
+
rating: number;
|
|
29
|
+
hasAudio: boolean;
|
|
30
|
+
hasCover: boolean;
|
|
31
|
+
requiresMod: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Pointers {
|
|
35
|
+
customDataOffset: number;
|
|
36
|
+
customDataLength: number;
|
|
37
|
+
audioOffset: number;
|
|
38
|
+
audioLength: number;
|
|
39
|
+
coverOffset: number;
|
|
40
|
+
coverLength: number;
|
|
41
|
+
markerDefinitionsOffset: number;
|
|
42
|
+
markerDefinitionsLength: number;
|
|
43
|
+
markerOffset: number;
|
|
44
|
+
markerLength: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface Strings {
|
|
48
|
+
mapID: string;
|
|
49
|
+
mapName: string;
|
|
50
|
+
songName: string;
|
|
51
|
+
mappers: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface CustomField {
|
|
55
|
+
id: string;
|
|
56
|
+
type: DataTypeID;
|
|
57
|
+
arrayType?: DataTypeID;
|
|
58
|
+
value: any;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface CustomData {
|
|
62
|
+
fields: CustomField[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface MarkerDefinition {
|
|
66
|
+
id: string;
|
|
67
|
+
values: DataTypeID[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Marker {
|
|
71
|
+
position: number;
|
|
72
|
+
type: number;
|
|
73
|
+
data: Buffer;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class SSPMParser {
|
|
77
|
+
private buffer: Buffer;
|
|
78
|
+
private offset: number = 0;
|
|
79
|
+
|
|
80
|
+
constructor(buffer: Buffer) {
|
|
81
|
+
this.buffer = buffer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private checkBounds(length: number): void {
|
|
85
|
+
if (this.offset + length > this.buffer.length) {
|
|
86
|
+
throw new RangeError(
|
|
87
|
+
`Attempt to read beyond buffer length: Offset=${this.offset}, Length=${length}, Buffer Length=${this.buffer.length}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private log(message: string): void {
|
|
93
|
+
// console.log(`[Offset: ${this.offset}] ${message}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private readUInt16(): number {
|
|
97
|
+
this.checkBounds(2);
|
|
98
|
+
const value = this.buffer.readUInt16LE(this.offset);
|
|
99
|
+
this.log(`Read UInt16: ${value}`);
|
|
100
|
+
this.offset += 2;
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private readUInt32(): number {
|
|
105
|
+
this.checkBounds(4);
|
|
106
|
+
const value = this.buffer.readUInt32LE(this.offset);
|
|
107
|
+
this.log(`Read UInt32: ${value}`);
|
|
108
|
+
this.offset += 4;
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private readUInt64(): number {
|
|
113
|
+
this.checkBounds(8);
|
|
114
|
+
const low = this.buffer.readUInt32LE(this.offset);
|
|
115
|
+
const high = this.buffer.readUInt32LE(this.offset + 4);
|
|
116
|
+
this.offset += 8;
|
|
117
|
+
const value = low + high * 2 ** 32;
|
|
118
|
+
this.log(`Read UInt64: ${value} (Low: ${low}, High: ${high})`);
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private readBytes(length: number): Buffer {
|
|
123
|
+
this.checkBounds(length);
|
|
124
|
+
const value = this.buffer.slice(this.offset, this.offset + length);
|
|
125
|
+
this.log(`Read ${length} bytes`);
|
|
126
|
+
this.offset += length;
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private readString(): string {
|
|
131
|
+
const length = this.readUInt16();
|
|
132
|
+
this.checkBounds(length);
|
|
133
|
+
const value = this.readBytes(length).toString("utf-8");
|
|
134
|
+
this.log(`Read String of length ${length}: ${value}`);
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private readStringList(count: number): string[] {
|
|
139
|
+
const list: string[] = [];
|
|
140
|
+
for (let i = 0; i < count; i++) {
|
|
141
|
+
list.push(this.readString());
|
|
142
|
+
}
|
|
143
|
+
return list;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private readMarkerField(typeID: DataTypeID): any {
|
|
147
|
+
switch (typeID) {
|
|
148
|
+
case 0x01: // 1 byte integer
|
|
149
|
+
this.checkBounds(1);
|
|
150
|
+
const int8 = this.buffer.readInt8(this.offset++);
|
|
151
|
+
this.log(`Read Int8: ${int8}`);
|
|
152
|
+
return int8;
|
|
153
|
+
case 0x02: // 2 byte uint
|
|
154
|
+
return this.readUInt16();
|
|
155
|
+
case 0x03: // 4 byte uint
|
|
156
|
+
return this.readUInt32();
|
|
157
|
+
case 0x04: // 8 byte uint
|
|
158
|
+
return this.readUInt64();
|
|
159
|
+
case 0x05: // 4 byte float
|
|
160
|
+
this.checkBounds(4);
|
|
161
|
+
const floatVal32 = this.buffer.readFloatLE(this.offset);
|
|
162
|
+
this.log(`Read Float32: ${floatVal32}`);
|
|
163
|
+
this.offset += 4;
|
|
164
|
+
return floatVal32;
|
|
165
|
+
case 0x06: // 8 byte float
|
|
166
|
+
this.checkBounds(8);
|
|
167
|
+
const floatVal64 = this.buffer.readDoubleLE(this.offset);
|
|
168
|
+
this.log(`Read Float64: ${floatVal64}`);
|
|
169
|
+
this.offset += 8;
|
|
170
|
+
return floatVal64;
|
|
171
|
+
case 0x07: // position type
|
|
172
|
+
const isQuantum = this.buffer.readUInt8(this.offset++);
|
|
173
|
+
let posData;
|
|
174
|
+
if (isQuantum === 0x00) {
|
|
175
|
+
this.checkBounds(2);
|
|
176
|
+
const posX = this.buffer.readUInt8(this.offset++);
|
|
177
|
+
const posY = this.buffer.readUInt8(this.offset++);
|
|
178
|
+
this.log(`Read Position Int: x=${posX}, y=${posY}`);
|
|
179
|
+
posData = { x: posX, y: posY, type: "int" };
|
|
180
|
+
} else {
|
|
181
|
+
this.checkBounds(8);
|
|
182
|
+
const posX = this.buffer.readFloatLE(this.offset);
|
|
183
|
+
this.offset += 4;
|
|
184
|
+
const posY = this.buffer.readFloatLE(this.offset);
|
|
185
|
+
this.offset += 4;
|
|
186
|
+
this.log(`Read Position Quantum: x=${posX}, y=${posY}`);
|
|
187
|
+
posData = { x: posX, y: posY, type: "quantum" };
|
|
188
|
+
}
|
|
189
|
+
return posData;
|
|
190
|
+
case 0x08: // buffer
|
|
191
|
+
case 0x09: // string
|
|
192
|
+
const length16 = this.readUInt16();
|
|
193
|
+
this.checkBounds(length16);
|
|
194
|
+
const value = this.readBytes(length16).toString("utf-8");
|
|
195
|
+
this.log(`Read Buffer/String of length ${length16}`);
|
|
196
|
+
return value;
|
|
197
|
+
case 0x0a: // long buffer
|
|
198
|
+
case 0x0b: // long string
|
|
199
|
+
const length32 = this.readUInt32();
|
|
200
|
+
this.log(`Reading Buffer/String of length ${length32}`);
|
|
201
|
+
this.checkBounds(length32);
|
|
202
|
+
const longValue = this.readBytes(length32).toString("utf-8");
|
|
203
|
+
this.log(`Read Long Buffer/String of length ${length32}`);
|
|
204
|
+
return longValue;
|
|
205
|
+
case 0x00: // end type, should not appear here
|
|
206
|
+
default:
|
|
207
|
+
throw new Error("Unexpected DataTypeID in marker definition.");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private readMarkerData(typeIDs: DataTypeID[]): any {
|
|
212
|
+
const dataObject: any = {};
|
|
213
|
+
for (const [index, typeID] of typeIDs.entries()) {
|
|
214
|
+
dataObject[`field${index}`] = this.readMarkerField(typeID);
|
|
215
|
+
}
|
|
216
|
+
return dataObject;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
parse(): {
|
|
220
|
+
header: Header;
|
|
221
|
+
metadata: StaticMetadata;
|
|
222
|
+
pointers: Pointers;
|
|
223
|
+
strings: Strings;
|
|
224
|
+
customData: CustomData;
|
|
225
|
+
audio?: Buffer;
|
|
226
|
+
cover?: Buffer;
|
|
227
|
+
markerDefinitions: MarkerDefinition[];
|
|
228
|
+
markers: Marker[];
|
|
229
|
+
} {
|
|
230
|
+
// Header
|
|
231
|
+
const header: Header = {
|
|
232
|
+
signature: this.readBytes(4),
|
|
233
|
+
version: this.readUInt16(),
|
|
234
|
+
reserved: this.readBytes(4),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Static Metadata
|
|
238
|
+
const metadata: StaticMetadata = {
|
|
239
|
+
sha1: this.readBytes(20),
|
|
240
|
+
lastMarkerPos: this.readUInt32(),
|
|
241
|
+
noteCount: this.readUInt32(),
|
|
242
|
+
markerCount: this.readUInt32(),
|
|
243
|
+
difficulty: this.buffer.readUInt8(this.offset++),
|
|
244
|
+
rating: this.readUInt16(),
|
|
245
|
+
hasAudio: this.buffer.readUInt8(this.offset++) === 1,
|
|
246
|
+
hasCover: this.buffer.readUInt8(this.offset++) === 1,
|
|
247
|
+
requiresMod: this.buffer.readUInt8(this.offset++) === 1,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Pointers
|
|
251
|
+
const pointers: Pointers = {
|
|
252
|
+
customDataOffset: this.readUInt64(),
|
|
253
|
+
customDataLength: this.readUInt64(),
|
|
254
|
+
audioOffset: this.readUInt64(),
|
|
255
|
+
audioLength: this.readUInt64(),
|
|
256
|
+
coverOffset: this.readUInt64(),
|
|
257
|
+
coverLength: this.readUInt64(),
|
|
258
|
+
markerDefinitionsOffset: this.readUInt64(),
|
|
259
|
+
markerDefinitionsLength: this.readUInt64(),
|
|
260
|
+
markerOffset: this.readUInt64(),
|
|
261
|
+
markerLength: this.readUInt64(),
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Log derived pointer values
|
|
265
|
+
this.log(`customDataOffset: ${pointers.customDataOffset}`);
|
|
266
|
+
this.log(`customDataLength: ${pointers.customDataLength}`);
|
|
267
|
+
this.log(`audioOffset: ${pointers.audioOffset}`);
|
|
268
|
+
this.log(`audioLength: ${pointers.audioLength}`);
|
|
269
|
+
this.log(`coverOffset: ${pointers.coverOffset}`);
|
|
270
|
+
this.log(`coverLength: ${pointers.coverLength}`);
|
|
271
|
+
this.log(`markerDefinitionsOffset: ${pointers.markerDefinitionsOffset}`);
|
|
272
|
+
this.log(`markerDefinitionsLength: ${pointers.markerDefinitionsLength}`);
|
|
273
|
+
this.log(`markerOffset: ${pointers.markerOffset}`);
|
|
274
|
+
this.log(`markerLength: ${pointers.markerLength}`);
|
|
275
|
+
|
|
276
|
+
// Strings
|
|
277
|
+
const strings: Strings = {
|
|
278
|
+
mapID: this.readString(),
|
|
279
|
+
mapName: this.readString(),
|
|
280
|
+
songName: this.readString(),
|
|
281
|
+
mappers: this.readStringList(this.readUInt16()),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
let customData: CustomData = { fields: [] };
|
|
285
|
+
try {
|
|
286
|
+
if (pointers.customDataOffset && pointers.customDataLength) {
|
|
287
|
+
this.log(
|
|
288
|
+
`Reading Custom Data, Offset: ${pointers.customDataOffset}, Length: ${pointers.customDataLength}`
|
|
289
|
+
);
|
|
290
|
+
this.offset = Number(pointers.customDataOffset);
|
|
291
|
+
const fieldCount = this.readUInt16();
|
|
292
|
+
this.log(`Fields: ${fieldCount.toString()}`);
|
|
293
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
294
|
+
const id = this.readString();
|
|
295
|
+
const type = this.buffer.readUInt8(this.offset++) as DataTypeID;
|
|
296
|
+
let arrayType: DataTypeID | undefined;
|
|
297
|
+
if (type === 0x0c) {
|
|
298
|
+
arrayType = this.buffer.readUInt8(this.offset++) as DataTypeID;
|
|
299
|
+
}
|
|
300
|
+
const length = this.readUInt32();
|
|
301
|
+
// this.checkBounds(length);
|
|
302
|
+
const value = this.readBytes(length);
|
|
303
|
+
customData.fields.push({ id, type, arrayType, value });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {}
|
|
307
|
+
|
|
308
|
+
let audio: Buffer | undefined;
|
|
309
|
+
if (
|
|
310
|
+
metadata.hasAudio &&
|
|
311
|
+
pointers.audioOffset != 0 &&
|
|
312
|
+
pointers.audioLength != 0
|
|
313
|
+
) {
|
|
314
|
+
this.log(
|
|
315
|
+
`Reading Audio Data, Offset: ${pointers.audioOffset}, Length: ${pointers.audioLength}`
|
|
316
|
+
);
|
|
317
|
+
this.offset = Number(pointers.audioOffset);
|
|
318
|
+
this.checkBounds(Number(pointers.audioLength));
|
|
319
|
+
audio = this.readBytes(Number(pointers.audioLength));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let cover: Buffer | undefined;
|
|
323
|
+
if (
|
|
324
|
+
metadata.hasCover &&
|
|
325
|
+
pointers.coverOffset != 0 &&
|
|
326
|
+
pointers.coverLength != 0
|
|
327
|
+
) {
|
|
328
|
+
this.log(
|
|
329
|
+
`Reading Cover Data, Offset: ${pointers.coverOffset}, Length: ${pointers.coverLength}`
|
|
330
|
+
);
|
|
331
|
+
this.offset = Number(pointers.coverOffset);
|
|
332
|
+
this.checkBounds(Number(pointers.coverLength));
|
|
333
|
+
cover = this.readBytes(Number(pointers.coverLength));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Marker Definitions
|
|
337
|
+
this.log(
|
|
338
|
+
`Reading Marker Definitions, Offset: ${pointers.markerDefinitionsOffset}, Length: ${pointers.markerDefinitionsLength}`
|
|
339
|
+
);
|
|
340
|
+
this.offset = Number(pointers.markerDefinitionsOffset);
|
|
341
|
+
const markerDefCount = this.buffer.readUInt8(this.offset++);
|
|
342
|
+
const markerDefinitions: MarkerDefinition[] = [];
|
|
343
|
+
for (let i = 0; i < markerDefCount; i++) {
|
|
344
|
+
const id = this.readString();
|
|
345
|
+
const valueCount = this.buffer.readUInt8(this.offset++);
|
|
346
|
+
const values: DataTypeID[] = [];
|
|
347
|
+
for (let j = 0; j < valueCount; j++) {
|
|
348
|
+
values.push(this.buffer.readUInt8(this.offset++) as DataTypeID);
|
|
349
|
+
}
|
|
350
|
+
markerDefinitions.push({ id, values });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Markers
|
|
354
|
+
this.log(
|
|
355
|
+
`Reading Markers, Offset: ${pointers.markerOffset}, Length: ${pointers.markerLength}`
|
|
356
|
+
);
|
|
357
|
+
this.offset = Number(pointers.markerOffset);
|
|
358
|
+
const endOffset = this.offset + Number(pointers.markerLength);
|
|
359
|
+
|
|
360
|
+
const markers: Marker[] = [];
|
|
361
|
+
|
|
362
|
+
while (this.offset < endOffset) {
|
|
363
|
+
const position = this.readUInt32();
|
|
364
|
+
const type = this.buffer.readUInt8(this.offset++);
|
|
365
|
+
const def = markerDefinitions.find((d) => d.id === "ssp_note"); // Adjust as needed for other marker types
|
|
366
|
+
const data = def ? this.readMarkerData(def.values) : {};
|
|
367
|
+
markers.push({ position, type, data });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
header,
|
|
372
|
+
metadata,
|
|
373
|
+
pointers,
|
|
374
|
+
strings,
|
|
375
|
+
customData,
|
|
376
|
+
audio,
|
|
377
|
+
cover,
|
|
378
|
+
markerDefinitions,
|
|
379
|
+
markers,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export type SSPMParsedMap = {
|
|
385
|
+
header: Header;
|
|
386
|
+
metadata: StaticMetadata;
|
|
387
|
+
pointers: Pointers;
|
|
388
|
+
strings: Strings;
|
|
389
|
+
customData: CustomData;
|
|
390
|
+
audio?: Buffer;
|
|
391
|
+
cover?: Buffer;
|
|
392
|
+
markerDefinitions: MarkerDefinition[];
|
|
393
|
+
markers: Marker[];
|
|
394
|
+
};
|