rhythia-api 131.0.0 → 132.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/approveMap.ts +1 -1
- package/api/getBeatmapComments.ts +42 -0
- package/api/postBeatmapComment.ts +56 -0
- package/index.ts +10 -0
- package/package.json +2 -1
- package/scripts/test.ts +46 -15
- package/types/database.ts +39 -0
- package/utils/star-calc/index.ts +30 -1
- package/utils/star-calc/sspmParser.ts +5 -1
- package/utils/star-calc/sspmv1Parser.ts +165 -0
package/api/approveMap.ts
CHANGED
|
@@ -56,7 +56,7 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
|
56
56
|
|
|
57
57
|
if ((mapData.nominations as number[])!.length < 2) {
|
|
58
58
|
return NextResponse.json({
|
|
59
|
-
error: "Maps can get approved only if they have 2
|
|
59
|
+
error: "Maps can get approved only if they have 2 nominations",
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
+
import { supabase } from "../utils/supabase";
|
|
5
|
+
|
|
6
|
+
export const Schema = {
|
|
7
|
+
input: z.strictObject({
|
|
8
|
+
page: z.number(),
|
|
9
|
+
}),
|
|
10
|
+
output: z.strictObject({
|
|
11
|
+
error: z.string().optional(),
|
|
12
|
+
comments: z.array(
|
|
13
|
+
z.object({
|
|
14
|
+
beatmapPage: z.number(),
|
|
15
|
+
content: z.string().nullable(),
|
|
16
|
+
owner: z.number(),
|
|
17
|
+
})
|
|
18
|
+
),
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
23
|
+
return protectedApi({
|
|
24
|
+
request,
|
|
25
|
+
schema: Schema,
|
|
26
|
+
authorization: () => {},
|
|
27
|
+
activity: handler,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function handler({
|
|
32
|
+
page,
|
|
33
|
+
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
34
|
+
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
35
|
+
> {
|
|
36
|
+
let { data: userData, error: userError } = await supabase
|
|
37
|
+
.from("beatmapPageComments")
|
|
38
|
+
.select("*")
|
|
39
|
+
.eq("beatmapPage", page);
|
|
40
|
+
|
|
41
|
+
return NextResponse.json({ comments: userData! });
|
|
42
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
+
import { supabase } from "../utils/supabase";
|
|
5
|
+
|
|
6
|
+
export const Schema = {
|
|
7
|
+
input: z.strictObject({
|
|
8
|
+
session: z.string(),
|
|
9
|
+
page: z.number(),
|
|
10
|
+
content: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
output: z.strictObject({
|
|
13
|
+
error: z.string().optional(),
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
18
|
+
return protectedApi({
|
|
19
|
+
request,
|
|
20
|
+
schema: Schema,
|
|
21
|
+
authorization: validUser,
|
|
22
|
+
activity: handler,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function handler({
|
|
27
|
+
session,
|
|
28
|
+
page,
|
|
29
|
+
content,
|
|
30
|
+
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
31
|
+
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
32
|
+
> {
|
|
33
|
+
const user = (await supabase.auth.getUser(session)).data.user!;
|
|
34
|
+
let { data: userData, error: userError } = await supabase
|
|
35
|
+
.from("profiles")
|
|
36
|
+
.select("*")
|
|
37
|
+
.eq("uid", user.id)
|
|
38
|
+
.single();
|
|
39
|
+
|
|
40
|
+
if (!userData) return NextResponse.json({ error: "No user." });
|
|
41
|
+
|
|
42
|
+
const upserted = await supabase
|
|
43
|
+
.from("beatmapPageComments")
|
|
44
|
+
.upsert({
|
|
45
|
+
beatmapPage: page,
|
|
46
|
+
owner: userData.id,
|
|
47
|
+
content,
|
|
48
|
+
})
|
|
49
|
+
.select("*")
|
|
50
|
+
.single();
|
|
51
|
+
|
|
52
|
+
if (upserted.error?.message.length) {
|
|
53
|
+
return NextResponse.json({ error: upserted.error.message });
|
|
54
|
+
}
|
|
55
|
+
return NextResponse.json({});
|
|
56
|
+
}
|
package/index.ts
CHANGED
|
@@ -35,6 +35,11 @@ import { Schema as GetBadgedUsers } from "./api/getBadgedUsers"
|
|
|
35
35
|
export { Schema as SchemaGetBadgedUsers } from "./api/getBadgedUsers"
|
|
36
36
|
export const getBadgedUsers = handleApi({url:"/api/getBadgedUsers",...GetBadgedUsers})
|
|
37
37
|
|
|
38
|
+
// ./api/getBeatmapComments.ts API
|
|
39
|
+
import { Schema as GetBeatmapComments } from "./api/getBeatmapComments"
|
|
40
|
+
export { Schema as SchemaGetBeatmapComments } from "./api/getBeatmapComments"
|
|
41
|
+
export const getBeatmapComments = handleApi({url:"/api/getBeatmapComments",...GetBeatmapComments})
|
|
42
|
+
|
|
38
43
|
// ./api/getBeatmapPage.ts API
|
|
39
44
|
import { Schema as GetBeatmapPage } from "./api/getBeatmapPage"
|
|
40
45
|
export { Schema as SchemaGetBeatmapPage } from "./api/getBeatmapPage"
|
|
@@ -80,6 +85,11 @@ import { Schema as NominateMap } from "./api/nominateMap"
|
|
|
80
85
|
export { Schema as SchemaNominateMap } from "./api/nominateMap"
|
|
81
86
|
export const nominateMap = handleApi({url:"/api/nominateMap",...NominateMap})
|
|
82
87
|
|
|
88
|
+
// ./api/postBeatmapComment.ts API
|
|
89
|
+
import { Schema as PostBeatmapComment } from "./api/postBeatmapComment"
|
|
90
|
+
export { Schema as SchemaPostBeatmapComment } from "./api/postBeatmapComment"
|
|
91
|
+
export const postBeatmapComment = handleApi({url:"/api/postBeatmapComment",...PostBeatmapComment})
|
|
92
|
+
|
|
83
93
|
// ./api/rankMapsArchive.ts API
|
|
84
94
|
import { Schema as RankMapsArchive } from "./api/rankMapsArchive"
|
|
85
95
|
export { Schema as SchemaRankMapsArchive } from "./api/rankMapsArchive"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "132.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update": "bun ./scripts/update.ts",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"isomorphic-git": "^1.27.1",
|
|
33
33
|
"lodash": "^4.17.21",
|
|
34
34
|
"next": "^14.2.5",
|
|
35
|
+
"osu-classes": "^3.1.0",
|
|
35
36
|
"osu-parsers": "^4.1.7",
|
|
36
37
|
"osu-standard-stable": "^5.0.0",
|
|
37
38
|
"sharp": "^0.33.5",
|
package/scripts/test.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createClient } from "@supabase/supabase-js";
|
|
2
2
|
import { Database } from "../types/database";
|
|
3
|
+
import { SSPMParser } from "../utils/star-calc/sspmParser";
|
|
4
|
+
import { rateMap, rateMapOld } from "../utils/star-calc";
|
|
5
|
+
import { V1SSPMParser } from "../utils/star-calc/sspmv1Parser";
|
|
3
6
|
|
|
4
7
|
export const supabase = createClient<Database>(
|
|
5
8
|
`https://pfkajngbllcbdzoylrvp.supabase.co`,
|
|
@@ -13,24 +16,52 @@ export const supabase = createClient<Database>(
|
|
|
13
16
|
);
|
|
14
17
|
|
|
15
18
|
async function main() {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
const req = await fetch(`https://cdn.rhythia.net/index.json`);
|
|
20
|
+
const json = Object.values(await req.json()) as any[];
|
|
21
|
+
console.log(json[0]);
|
|
22
|
+
|
|
23
|
+
let count = 0;
|
|
24
|
+
for (const map of json) {
|
|
25
|
+
const request = await fetch(map.download);
|
|
26
|
+
const bytes = await request.arrayBuffer();
|
|
27
|
+
const parser = new SSPMParser(Buffer.from(bytes));
|
|
28
|
+
let rate = 0;
|
|
29
|
+
try {
|
|
30
|
+
const data = parser.parse();
|
|
31
|
+
rate = rateMap(data);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const parserOld = new V1SSPMParser(Buffer.from(bytes));
|
|
34
|
+
const data = parserOld.parse();
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
rate = rateMapOld(data);
|
|
37
|
+
}
|
|
38
|
+
await supabase.from("beatmaps").upsert({
|
|
39
|
+
beatmapHash: map.id,
|
|
40
|
+
starRating: rate,
|
|
31
41
|
});
|
|
32
|
-
|
|
42
|
+
count++;
|
|
43
|
+
console.log(count, json.length, map.id, rate);
|
|
33
44
|
}
|
|
45
|
+
// for (const map of json) {
|
|
46
|
+
// const reqMap = await fetch(map.link);
|
|
47
|
+
// const buff = await reqMap.arrayBuffer();
|
|
48
|
+
// writeFileSync("./toSubmit/" + map.id + ".sspm", Buffer.from(buff));
|
|
49
|
+
// }
|
|
50
|
+
|
|
51
|
+
// const { data: mapData, error } = await supabase
|
|
52
|
+
// .from("beatmapPages")
|
|
53
|
+
// .select("id,nominations,owner,status")
|
|
54
|
+
// .eq("owner", 10)
|
|
55
|
+
// .eq("status", "RANKED");
|
|
56
|
+
|
|
57
|
+
// if (!mapData) {
|
|
58
|
+
// throw "lolz";
|
|
59
|
+
// }
|
|
60
|
+
|
|
61
|
+
// for (const element of mapData) {
|
|
62
|
+
|
|
63
|
+
// console.log(data.error);
|
|
64
|
+
// }
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
main();
|
package/types/database.ts
CHANGED
|
@@ -9,6 +9,45 @@ export type Json =
|
|
|
9
9
|
export type Database = {
|
|
10
10
|
public: {
|
|
11
11
|
Tables: {
|
|
12
|
+
beatmapPageComments: {
|
|
13
|
+
Row: {
|
|
14
|
+
beatmapPage: number
|
|
15
|
+
content: string | null
|
|
16
|
+
created_at: string
|
|
17
|
+
id: number
|
|
18
|
+
owner: number
|
|
19
|
+
}
|
|
20
|
+
Insert: {
|
|
21
|
+
beatmapPage: number
|
|
22
|
+
content?: string | null
|
|
23
|
+
created_at?: string
|
|
24
|
+
id?: number
|
|
25
|
+
owner: number
|
|
26
|
+
}
|
|
27
|
+
Update: {
|
|
28
|
+
beatmapPage?: number
|
|
29
|
+
content?: string | null
|
|
30
|
+
created_at?: string
|
|
31
|
+
id?: number
|
|
32
|
+
owner?: number
|
|
33
|
+
}
|
|
34
|
+
Relationships: [
|
|
35
|
+
{
|
|
36
|
+
foreignKeyName: "beatmapPageComments_beatmapPage_fkey"
|
|
37
|
+
columns: ["beatmapPage"]
|
|
38
|
+
isOneToOne: false
|
|
39
|
+
referencedRelation: "beatmapPages"
|
|
40
|
+
referencedColumns: ["id"]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
foreignKeyName: "beatmapPageComments_owner_fkey"
|
|
44
|
+
columns: ["owner"]
|
|
45
|
+
isOneToOne: false
|
|
46
|
+
referencedRelation: "profiles"
|
|
47
|
+
referencedColumns: ["id"]
|
|
48
|
+
},
|
|
49
|
+
]
|
|
50
|
+
}
|
|
12
51
|
beatmapPages: {
|
|
13
52
|
Row: {
|
|
14
53
|
created_at: string
|
package/utils/star-calc/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { BeatmapDecoder } from "osu-parsers";
|
|
|
2
2
|
import { StandardRuleset } from "osu-standard-stable";
|
|
3
3
|
import { sampleMap } from "./osuUtils";
|
|
4
4
|
import { SSPMParsedMap } from "./sspmParser";
|
|
5
|
+
import { SSPMMap, V1SSPMParser } from "./sspmv1Parser";
|
|
5
6
|
|
|
6
7
|
function easeInExpoDeq(x: number) {
|
|
7
8
|
return x === 0 ? 0 : Math.pow(2, 35 * x - 35);
|
|
@@ -20,7 +21,7 @@ export function rateMap(map: SSPMParsedMap) {
|
|
|
20
21
|
const decoder = new BeatmapDecoder();
|
|
21
22
|
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
let notes = map.markers
|
|
24
25
|
.filter((marker) => marker.type === 0)
|
|
25
26
|
.map((marker) => ({
|
|
26
27
|
time: marker.position,
|
|
@@ -28,6 +29,34 @@ export function rateMap(map: SSPMParsedMap) {
|
|
|
28
29
|
y: marker.data["field0"].y,
|
|
29
30
|
}));
|
|
30
31
|
|
|
32
|
+
notes = notes.sort((a, b) => a.time - b.time);
|
|
33
|
+
|
|
34
|
+
for (const note of notes) {
|
|
35
|
+
const hittable = beatmap1.hitObjects[0].clone();
|
|
36
|
+
hittable.startX = Math.round((note.x / 2) * 100);
|
|
37
|
+
hittable.startY = Math.round((note.y / 2) * 100);
|
|
38
|
+
hittable.startTime = note.time;
|
|
39
|
+
beatmap1.hitObjects.push(hittable);
|
|
40
|
+
}
|
|
41
|
+
const ruleset = new StandardRuleset();
|
|
42
|
+
const mods = ruleset.createModCombination("RX");
|
|
43
|
+
const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
|
|
44
|
+
const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
|
|
45
|
+
return difficultyAttributes.starRating;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function rateMapOld(map: SSPMMap) {
|
|
49
|
+
const decoder = new BeatmapDecoder();
|
|
50
|
+
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
51
|
+
|
|
52
|
+
let notes = map.notes.map((marker) => ({
|
|
53
|
+
time: marker.position,
|
|
54
|
+
x: marker.x,
|
|
55
|
+
y: marker.y,
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
notes = notes.sort((a, b) => a.time - b.time);
|
|
59
|
+
|
|
31
60
|
for (const note of notes) {
|
|
32
61
|
const hittable = beatmap1.hitObjects[0].clone();
|
|
33
62
|
hittable.startX = Math.round((note.x / 2) * 100);
|
|
@@ -233,6 +233,9 @@ export class SSPMParser {
|
|
|
233
233
|
version: this.readUInt16(),
|
|
234
234
|
reserved: this.readBytes(4),
|
|
235
235
|
};
|
|
236
|
+
if (header.version == 1) {
|
|
237
|
+
throw "Can't parse";
|
|
238
|
+
}
|
|
236
239
|
|
|
237
240
|
// Static Metadata
|
|
238
241
|
const metadata: StaticMetadata = {
|
|
@@ -278,7 +281,8 @@ export class SSPMParser {
|
|
|
278
281
|
mapID: this.readString(),
|
|
279
282
|
mapName: this.readString(),
|
|
280
283
|
songName: this.readString(),
|
|
281
|
-
mappers:
|
|
284
|
+
mappers: [],
|
|
285
|
+
// mappers: this.readStringList(this.readUInt16()),
|
|
282
286
|
};
|
|
283
287
|
|
|
284
288
|
let customData: CustomData = { fields: [] };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
enum Difficulty {
|
|
2
|
+
NA = 0x00,
|
|
3
|
+
Easy = 0x01,
|
|
4
|
+
Medium = 0x02,
|
|
5
|
+
Hard = 0x03,
|
|
6
|
+
Logic = 0x04,
|
|
7
|
+
Tasukete = 0x05,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
enum CoverStorageType {
|
|
11
|
+
None = 0x00,
|
|
12
|
+
PNG = 0x02,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
enum AudioStorageType {
|
|
16
|
+
None = 0x00,
|
|
17
|
+
StoredAudioFile = 0x01,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
enum NoteStorageType {
|
|
21
|
+
Integer = 0x00,
|
|
22
|
+
Quantum = 0x01,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Note {
|
|
26
|
+
position: number;
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SSPMMap {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
creator: string;
|
|
35
|
+
lastNotePosition: number;
|
|
36
|
+
noteCount: number;
|
|
37
|
+
difficulty: Difficulty;
|
|
38
|
+
cover: Buffer | null;
|
|
39
|
+
audio: Buffer | null;
|
|
40
|
+
notes: Note[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class V1SSPMParser {
|
|
44
|
+
private buffer: Buffer;
|
|
45
|
+
private offset: number = 0;
|
|
46
|
+
|
|
47
|
+
constructor(buffer: Buffer) {
|
|
48
|
+
this.buffer = buffer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
parse(): SSPMMap {
|
|
52
|
+
this.validateHeader();
|
|
53
|
+
const metadata = this.parseMetadata();
|
|
54
|
+
const cover = this.parseCover();
|
|
55
|
+
const audio = this.parseAudio();
|
|
56
|
+
const notes = this.parseNotes(metadata.noteCount);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...metadata,
|
|
60
|
+
cover,
|
|
61
|
+
audio,
|
|
62
|
+
notes,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private validateHeader() {
|
|
67
|
+
const signature = this.buffer.slice(0, 4).toString("hex");
|
|
68
|
+
if (signature !== "53532b6d") {
|
|
69
|
+
throw new Error("Invalid SSPM file signature");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const version = this.buffer.readUInt16LE(4);
|
|
73
|
+
if (version !== 1) {
|
|
74
|
+
throw new Error(`Unsupported SSPM version: ${version}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.offset = 8; // Skip reserved space
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private parseMetadata(): Omit<SSPMMap, "cover" | "audio" | "notes"> {
|
|
81
|
+
const id = this.readString();
|
|
82
|
+
const name = this.readString();
|
|
83
|
+
const creator = this.readString();
|
|
84
|
+
const lastNotePosition = this.buffer.readUInt32LE(this.offset);
|
|
85
|
+
this.offset += 4;
|
|
86
|
+
const noteCount = this.buffer.readUInt32LE(this.offset);
|
|
87
|
+
this.offset += 4;
|
|
88
|
+
const difficulty = this.buffer.readUInt8(this.offset++) as Difficulty;
|
|
89
|
+
|
|
90
|
+
return { id, name, creator, lastNotePosition, noteCount, difficulty };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private parseCover(): Buffer | null {
|
|
94
|
+
const coverType = this.buffer.readUInt8(this.offset++) as CoverStorageType;
|
|
95
|
+
if (coverType === CoverStorageType.None) {
|
|
96
|
+
return null;
|
|
97
|
+
} else if (coverType === CoverStorageType.PNG) {
|
|
98
|
+
const length = this.buffer.readBigUInt64LE(this.offset);
|
|
99
|
+
this.offset += 8;
|
|
100
|
+
const cover = this.buffer.slice(
|
|
101
|
+
this.offset,
|
|
102
|
+
this.offset + Number(length)
|
|
103
|
+
);
|
|
104
|
+
this.offset += Number(length);
|
|
105
|
+
return cover;
|
|
106
|
+
} else {
|
|
107
|
+
throw new Error(`Unsupported cover storage type: ${coverType}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private parseAudio(): Buffer | null {
|
|
112
|
+
const audioType = this.buffer.readUInt8(this.offset++) as AudioStorageType;
|
|
113
|
+
if (audioType === AudioStorageType.None) {
|
|
114
|
+
return null;
|
|
115
|
+
} else if (audioType === AudioStorageType.StoredAudioFile) {
|
|
116
|
+
const length = this.buffer.readBigUInt64LE(this.offset);
|
|
117
|
+
this.offset += 8;
|
|
118
|
+
const audio = this.buffer.slice(
|
|
119
|
+
this.offset,
|
|
120
|
+
this.offset + Number(length)
|
|
121
|
+
);
|
|
122
|
+
this.offset += Number(length);
|
|
123
|
+
return audio;
|
|
124
|
+
} else {
|
|
125
|
+
throw new Error(`Unsupported audio storage type: ${audioType}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private parseNotes(count: number): Note[] {
|
|
130
|
+
const notes: Note[] = [];
|
|
131
|
+
for (let i = 0; i < count; i++) {
|
|
132
|
+
const position = this.buffer.readUInt32LE(this.offset);
|
|
133
|
+
this.offset += 4;
|
|
134
|
+
const storageType = this.buffer.readUInt8(
|
|
135
|
+
this.offset++
|
|
136
|
+
) as NoteStorageType;
|
|
137
|
+
|
|
138
|
+
let x: number, y: number;
|
|
139
|
+
if (storageType === NoteStorageType.Integer) {
|
|
140
|
+
x = this.buffer.readUInt8(this.offset++);
|
|
141
|
+
y = this.buffer.readUInt8(this.offset++);
|
|
142
|
+
} else if (storageType === NoteStorageType.Quantum) {
|
|
143
|
+
x = this.buffer.readFloatLE(this.offset);
|
|
144
|
+
this.offset += 4;
|
|
145
|
+
y = this.buffer.readFloatLE(this.offset);
|
|
146
|
+
this.offset += 4;
|
|
147
|
+
} else {
|
|
148
|
+
throw new Error(`Unsupported note storage type: ${storageType}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
notes.push({ position, x, y });
|
|
152
|
+
}
|
|
153
|
+
return notes;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private readString(): string {
|
|
157
|
+
let end = this.offset;
|
|
158
|
+
while (this.buffer[end] !== 0x0a) {
|
|
159
|
+
end++;
|
|
160
|
+
}
|
|
161
|
+
const str = this.buffer.slice(this.offset, end).toString("utf-8");
|
|
162
|
+
this.offset = end + 1;
|
|
163
|
+
return str;
|
|
164
|
+
}
|
|
165
|
+
}
|