diff --git a/nise-frontend/package-lock.json b/nise-frontend/package-lock.json
index c9fb831..05f2eed 100644
--- a/nise-frontend/package-lock.json
+++ b/nise-frontend/package-lock.json
@@ -21,6 +21,7 @@
"chart.js": "^4.4.1",
"date-fns": "^3.3.1",
"lz-string": "^1.5.0",
+ "lzma-web": "^3.0.1",
"ng2-charts": "^5.0.4",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
@@ -8081,6 +8082,11 @@
"lz-string": "bin/bin.js"
}
},
+ "node_modules/lzma-web": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/lzma-web/-/lzma-web-3.0.1.tgz",
+ "integrity": "sha512-sb5cdfd+PLNljK/HUgYzvnz4G7r0GFK8sonyGrqJS0FVyUQjFYcnmU2LqTWFi6r48lH1ZBstnxyLWepKM/t7QA=="
+ },
"node_modules/magic-string": {
"version": "0.30.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
diff --git a/nise-frontend/package.json b/nise-frontend/package.json
index b94142f..18c0f8c 100644
--- a/nise-frontend/package.json
+++ b/nise-frontend/package.json
@@ -24,6 +24,7 @@
"chart.js": "^4.4.1",
"date-fns": "^3.3.1",
"lz-string": "^1.5.0",
+ "lzma-web": "^3.0.1",
"ng2-charts": "^5.0.4",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
diff --git a/nise-frontend/src/app/app.component.html b/nise-frontend/src/app/app.component.html
index c5a7846..7421738 100644
--- a/nise-frontend/src/app/app.component.html
+++ b/nise-frontend/src/app/app.component.html
@@ -1,31 +1,3 @@
-
-
diff --git a/nise-frontend/src/app/app.component.ts b/nise-frontend/src/app/app.component.ts
index 4b773cd..f74c9f4 100644
--- a/nise-frontend/src/app/app.component.ts
+++ b/nise-frontend/src/app/app.component.ts
@@ -3,6 +3,7 @@ import {Router, RouterLink, RouterOutlet} from "@angular/router";
import {UserService} from "../corelib/service/user.service";
import {NgIf} from '@angular/common';
import {FormsModule} from '@angular/forms';
+import {ReplayViewerComponent} from "../corelib/components/replay-viewer/replay-viewer.component";
@Component({
selector: 'app-root',
@@ -10,7 +11,7 @@ import {FormsModule} from '@angular/forms';
imports: [
RouterLink,
FormsModule,
- NgIf, RouterOutlet
+ NgIf, RouterOutlet, ReplayViewerComponent
],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
diff --git a/nise-frontend/src/corelib/components/replay-viewer/decode-beatmap.ts b/nise-frontend/src/corelib/components/replay-viewer/decode-beatmap.ts
new file mode 100644
index 0000000..750d0da
--- /dev/null
+++ b/nise-frontend/src/corelib/components/replay-viewer/decode-beatmap.ts
@@ -0,0 +1,57 @@
+export enum HitObjectType {
+ HitCircle = 1,
+ Slider = 2,
+ Spinner = 8
+}
+
+export type HitObject = {
+ x: number;
+ y: number;
+ time: number;
+ type: number;
+ hitSound: number;
+ objectParams: string;
+ hitSample: string;
+};
+
+export function parseHitObjects(beatmap: string): any[] {
+ const lines = beatmap.split('\n');
+ let recording = false;
+ const hitObjects = [];
+
+ for (const line of lines) {
+ if (line.trim() === '[HitObjects]') {
+ recording = true;
+ continue;
+ }
+
+ if (!recording) continue;
+
+ if (line.startsWith('[')) break;
+
+ const parts = line.split(',');
+ if (parts.length < 5) continue;
+
+ const type = parseInt(parts[3], 10);
+ const hitObject = {
+ x: parseInt(parts[0], 10),
+ y: parseInt(parts[1], 10),
+ time: parseInt(parts[2], 10),
+ type: getTypeFromFlag(type),
+ hitSound: parseInt(parts[4], 10),
+ objectParams: parts[5],
+ hitSample: parts.length > 6 ? parts[6] : '0:0:0:0:'
+ };
+
+ hitObjects.push(hitObject);
+ }
+
+ return hitObjects;
+}
+
+function getTypeFromFlag(flag: number): HitObjectType | null {
+ if (flag & HitObjectType.HitCircle) return HitObjectType.HitCircle;
+ if (flag & HitObjectType.Slider) return HitObjectType.Slider;
+ if (flag & HitObjectType.Spinner) return HitObjectType.Spinner;
+ return null;
+}
diff --git a/nise-frontend/src/corelib/components/replay-viewer/decode-replay.ts b/nise-frontend/src/corelib/components/replay-viewer/decode-replay.ts
new file mode 100644
index 0000000..ce8136f
--- /dev/null
+++ b/nise-frontend/src/corelib/components/replay-viewer/decode-replay.ts
@@ -0,0 +1,70 @@
+import {KeyPress, ReplayEvent} from "./replay-viewer.component";
+import LZMA from 'lzma-web'
+
+export async function getEvents(replayString: string): Promise {
+ const decompressedData = await decompressData(replayString);
+ const replayDataStr = new TextDecoder("utf-8").decode(decompressedData);
+ const trimmedReplayDataStr = replayDataStr.endsWith(',') ? replayDataStr.slice(0, -1) : replayDataStr;
+ return processEvents(trimmedReplayDataStr);
+}
+
+function processEvents(replayDataStr: string): ReplayEvent[] {
+ const eventStrings = replayDataStr.split(",");
+ const playData: ReplayEvent[] = [];
+ eventStrings.forEach((eventStr, index) => {
+ const event = createReplayEvent(eventStr.split('|'), index, eventStrings.length);
+ if (event) playData.push(event);
+ });
+ return playData;
+}
+
+function createReplayEvent(eventParts: string[], index: number, totalEvents: number): ReplayEvent | null {
+
+ const timeDelta = parseInt(eventParts[0], 10);
+ const x = parseFloat(eventParts[1]);
+ const y = parseFloat(eventParts[2]);
+ const rawKey = parseInt(eventParts[3], 10);
+
+ if (timeDelta == -12345 && index == totalEvents - 1) {
+ return null;
+ }
+ if (index < 2 && x == 256.0 && y == -500.0) {
+ return null;
+ }
+
+ // Safely cast the integer value to the KeyPress enum
+ let keyPress = KeyPress[rawKey as unknown as keyof typeof KeyPress];
+
+ if (keyPress === undefined) {
+ // TODO: Fix
+ console.error("Unknown key press:", rawKey);
+ keyPress = KeyPress.Smoke;
+ }
+
+ return {
+ timeDelta,
+ x,
+ y,
+ key: keyPress
+ };
+}
+
+async function decompressData(base64Data: string): Promise {
+ const lzma = new LZMA();
+
+ const binaryString = atob(base64Data);
+ const len = binaryString.length;
+ const bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+
+ const result = await lzma.decompress(bytes);
+
+ if (typeof result === 'string') {
+ return new TextEncoder().encode(result);
+ } else {
+ return result;
+ }
+}
+
diff --git a/nise-frontend/src/corelib/components/replay-viewer/process-replay.ts b/nise-frontend/src/corelib/components/replay-viewer/process-replay.ts
new file mode 100644
index 0000000..d8a75f5
--- /dev/null
+++ b/nise-frontend/src/corelib/components/replay-viewer/process-replay.ts
@@ -0,0 +1,72 @@
+import {KeyPress, ReplayEvent} from "./replay-viewer.component";
+
+export class ReplayEventProcessed {
+ x: number;
+ y: number;
+ t: number; // Time
+ key: KeyPress;
+
+ constructor(x: number, y: number, t: number, key: KeyPress) {
+ this.x = x;
+ this.y = y;
+ this.t = t;
+ this.key = key;
+ }
+}
+
+export function processReplay(events: ReplayEvent[]): ReplayEventProcessed[] {
+ if (events.length === 0) throw new Error("This replay's replay data was empty. It indicates a misbehaved replay.");
+
+ if (events[0].timeDelta === 0 && events.length > 1) events.shift();
+
+ const pEvents: ReplayEventProcessed[] = [];
+
+ let cumulativeTimeDelta = events[0].timeDelta;
+ let highestTimeDelta = Number.NEGATIVE_INFINITY;
+ let lastPositiveFrame: ReplayEvent | null = null;
+
+ let wasInNegativeSection = false;
+ const lastPositiveFrameData: [number, [number, number]][] = [];
+
+ events.slice(1).forEach((currentFrame, index) => {
+ const previousCumulativeTime = cumulativeTimeDelta;
+ cumulativeTimeDelta += currentFrame.timeDelta;
+
+ highestTimeDelta = Math.max(highestTimeDelta, cumulativeTimeDelta);
+
+ const isInNegativeSection = cumulativeTimeDelta < highestTimeDelta;
+ if (isInNegativeSection) {
+ if (!wasInNegativeSection) {
+ lastPositiveFrame = index > 0 ? events[index] : null;
+ }
+ } else {
+ if (wasInNegativeSection && lastPositiveFrame) {
+ const lastPositiveTime = lastPositiveFrameData.length > 0 ? lastPositiveFrameData[lastPositiveFrameData.length - 1][0] : previousCumulativeTime;
+ const ratio = (lastPositiveTime - previousCumulativeTime) / (cumulativeTimeDelta - previousCumulativeTime);
+
+ const interpolatedX = lastPositiveFrame.x + ratio * (currentFrame.x - lastPositiveFrame.x);
+ const interpolatedY = lastPositiveFrame.y + ratio * (currentFrame.y - lastPositiveFrame.y);
+
+ pEvents.push(new ReplayEventProcessed(interpolatedX, interpolatedY, lastPositiveTime, lastPositiveFrame.key));
+ }
+ wasInNegativeSection = false;
+ }
+
+ wasInNegativeSection = isInNegativeSection;
+
+ if (!isInNegativeSection) {
+ pEvents.push(new ReplayEventProcessed(currentFrame.x, currentFrame.y, cumulativeTimeDelta, currentFrame.key));
+ }
+
+ if (!isInNegativeSection) {
+ lastPositiveFrameData.push([cumulativeTimeDelta, [currentFrame.x, currentFrame.y]]);
+ }
+ });
+
+ // Ensuring uniqueness based on time to avoid duplicates
+ return pEvents.filter((event, index, self) =>
+ index === self.findIndex((t) => (
+ t.t === event.t
+ ))
+ );
+}
diff --git a/nise-frontend/src/corelib/components/replay-viewer/replay-service.ts b/nise-frontend/src/corelib/components/replay-viewer/replay-service.ts
new file mode 100644
index 0000000..445b6ec
--- /dev/null
+++ b/nise-frontend/src/corelib/components/replay-viewer/replay-service.ts
@@ -0,0 +1,213 @@
+import { ElementRef, Injectable } from '@angular/core';
+import {HitObject, HitObjectType} from "./decode-beatmap";
+import {ReplayEventProcessed} from "./process-replay";
+
+@Injectable({
+ providedIn: 'root',
+})
+export class ReplayService {
+
+ private hitObjects: HitObject[] = [];
+ private replayEvents: ReplayEventProcessed[] = [];
+
+ currentTime = 0;
+ private totalDuration = 0;
+ private lastRenderTime = 0;
+
+ private isPlaying = false;
+ private requestId: number | null = null;
+
+ private replayCanvas: ElementRef | null = null;
+ private ctx: CanvasRenderingContext2D | null = null;
+
+ constructor() {}
+
+ setCanvasElement(canvas: ElementRef) {
+ this.replayCanvas = canvas;
+ }
+
+ setCanvasContext(ctx: CanvasRenderingContext2D) {
+ this.ctx = ctx;
+ }
+
+ setHitObjects(hitObjects: HitObject[]) {
+ this.hitObjects = hitObjects;
+ console.log('Hit objects:', this.hitObjects);
+ }
+
+ setEvents(events: ReplayEventProcessed[]) {
+ this.replayEvents = events;
+ this.calculateTotalDuration();
+ console.log('Replay events:', this.replayEvents);
+ }
+
+ private calculateTotalDuration() {
+ if (this.replayEvents.length === 0) {
+ this.totalDuration = 0;
+ return;
+ }
+ const lastEvent = this.replayEvents[this.replayEvents.length - 1];
+ this.totalDuration = lastEvent.t;
+ }
+
+ start() {
+ this.isPlaying = true;
+ if(this.currentTime >= this.totalDuration) {
+ this.currentTime = 0;
+ }
+ this.animate();
+ }
+
+ pause() {
+ this.isPlaying = false;
+ if (this.requestId) {
+ cancelAnimationFrame(this.requestId);
+ this.requestId = null;
+ }
+ }
+
+ seek(time: number) {
+ this.currentTime = Math.min(Math.max(time, 0), this.totalDuration);
+ this.lastRenderTime = 0;
+ this.isPlaying = true;
+ this.animate();
+ this.isPlaying = false;
+ console.log('Seeking to:', this.currentTime);
+ }
+
+ private animate(currentTimestamp: number = 0) {
+ if (!this.isPlaying) return;
+
+ if (!this.lastRenderTime) {
+ this.lastRenderTime = currentTimestamp;
+ }
+
+ const elapsedTime = currentTimestamp - this.lastRenderTime;
+
+ // Check if enough time has passed for the next frame (approximately 16.67ms for 60 FPS)
+ if (elapsedTime < 16.67) {
+ // Request the next animation frame and return early
+ this.requestId = requestAnimationFrame(this.animate.bind(this));
+ return;
+ }
+
+ // Assuming elapsedTime is sufficient for 1 frame, update currentTime for real-time playback
+ this.currentTime += elapsedTime;
+
+ if (this.currentTime > this.totalDuration) {
+ this.currentTime = this.totalDuration;
+ this.pause();
+ return;
+ }
+
+ this.drawCurrentEventPosition();
+ this.drawHitCircles();
+ this.drawSliders();
+
+ this.lastRenderTime = currentTimestamp;
+
+ // Request the next frame
+ this.requestId = requestAnimationFrame(this.animate.bind(this));
+ }
+
+ public getCurrentReplayEvent(): ReplayEventProcessed | null {
+ let currentEvent: ReplayEventProcessed | null = null;
+ for (const event of this.replayEvents) {
+ if (event.t <= this.currentTime) {
+ currentEvent = event;
+ } else {
+ break; // Exit the loop once an event exceeds the current time
+ }
+ }
+ return currentEvent;
+ }
+
+ private drawCurrentEventPosition() {
+ const currentEvent = this.getCurrentReplayEvent();
+ if (currentEvent) {
+ this.updateCanvas(currentEvent.x, currentEvent.y);
+ }
+ }
+
+ private drawHitCircles() {
+ if (!this.ctx || !this.replayCanvas) {
+ console.error('Canvas context not initialized');
+ return;
+ }
+
+ const visibleHitCircles = this.hitObjects.filter(obj =>
+ obj.type === HitObjectType.HitCircle &&
+ this.currentTime >= obj.time - 200 && // Start showing 200ms before
+ this.currentTime <= obj.time // Hide after its time
+ );
+
+ visibleHitCircles.forEach(hitCircle => {
+ const opacity = this.calculateOpacity(hitCircle.time);
+ this.drawHitCircle(hitCircle.x, hitCircle.y, opacity);
+ });
+ }
+
+ private drawSliders() {
+ if (!this.ctx || !this.replayCanvas) {
+ console.error('Canvas context not initialized');
+ return;
+ }
+
+ const visibleSliders = this.hitObjects.filter(obj =>
+ obj.type === HitObjectType.Slider &&
+ this.currentTime >= obj.time - 200 && // Start showing 200ms before
+ this.currentTime <= obj.time // Hide after its time
+ );
+
+ visibleSliders.forEach(slider => {
+ const opacity = this.calculateOpacity(slider.time);
+ this.drawSlider(slider, opacity);
+ });
+ }
+
+ private drawSlider(slider: HitObject, opacity: number) {
+ this.ctx!.fillStyle = `rgba(255, 255, 255, ${opacity})`; // Set opacity for fade-in effect
+ this.ctx!.beginPath();
+ this.ctx!.arc(slider.x, slider.y, 25, 0, 2 * Math.PI); // Assuming a radius of 5px
+ this.ctx!.fill();
+ }
+
+ private calculateOpacity(circleTime: number): number {
+ const timeDifference = circleTime - this.currentTime;
+ if (timeDifference < 0) {
+ return 0; // Circle time has passed
+ }
+
+ // Calculate fade-in effect (0 to 1 over 200ms)
+ return Math.min(1, (200 - timeDifference) / 200);
+ }
+
+ private drawHitCircle(x: number, y: number, opacity: number) {
+ this.ctx!.fillStyle = `rgba(255, 255, 255, ${opacity})`; // Set opacity for fade-in effect
+ this.ctx!.beginPath();
+ this.ctx!.arc(x, y, 25, 0, 2 * Math.PI); // Assuming a radius of 50px
+ this.ctx!.fill();
+ }
+
+ private updateCanvas(x: number, y: number) {
+ if (!this.ctx || !this.replayCanvas) {
+ console.error('Canvas context not initialized');
+ return;
+ }
+
+ this.ctx.clearRect(0, 0, this.replayCanvas.nativeElement.width, this.replayCanvas.nativeElement.height);
+ this.ctx.fillStyle = '#FFFFFF';
+ this.ctx.beginPath();
+ this.ctx.arc(x, y, 5, 0, 2 * Math.PI);
+ this.ctx.fill();
+ }
+
+ getTotalDuration() {
+ return this.totalDuration;
+ }
+
+ getIsPlaying() {
+ return this.isPlaying;
+ }
+
+}
diff --git a/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.css b/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.html b/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.html
new file mode 100644
index 0000000..5c3c08e
--- /dev/null
+++ b/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.html
@@ -0,0 +1,5 @@
+
+Current Time: {{ replayService.currentTime | number: '1.0-0' }}
+Total Duration: {{ replayService.getTotalDuration() | number: '1.0-0' }}
+
+
diff --git a/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.ts b/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.ts
new file mode 100644
index 0000000..a6ffa77
--- /dev/null
+++ b/nise-frontend/src/corelib/components/replay-viewer/replay-viewer.component.ts
@@ -0,0 +1,91 @@
+import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
+import {getEvents} from "./decode-replay";
+import {DecimalPipe, JsonPipe} from "@angular/common";
+import {beatmapData, replayData} from "./sample-replay";
+import {ReplayService} from "./replay-service";
+import {FormsModule} from "@angular/forms";
+import {parseHitObjects} from "./decode-beatmap";
+import {processReplay} from "./process-replay";
+
+export enum KeyPress {
+ M1 = 1,
+ M2 = 2,
+ K1 = 5,
+ K2 = 10,
+ Smoke = 16,
+}
+
+export interface ReplayEvent {
+ /**
+ * Time in milliseconds since the previous action
+ */
+ timeDelta: number;
+
+ /**
+ * x-coordinate of the cursor from 0 - 512
+ */
+ x: number;
+
+ /**
+ * y-coordinate of the cursor from 0 - 384
+ */
+ y: number;
+
+ /**
+ * Key being pressed.
+ */
+ key: KeyPress;
+
+}
+
+@Component({
+ selector: 'app-replay-viewer',
+ standalone: true,
+ imports: [
+ JsonPipe,
+ FormsModule,
+ DecimalPipe
+ ],
+ templateUrl: './replay-viewer.component.html',
+ styleUrl: './replay-viewer.component.css'
+})
+export class ReplayViewerComponent implements OnInit, AfterViewInit {
+
+ @ViewChild('replayCanvas') replayCanvas!: ElementRef;
+ private ctx!: CanvasRenderingContext2D;
+
+ constructor(public replayService: ReplayService) { }
+
+ ngOnInit() {
+ // Assume getEvents() method returns a promise of ReplayEvent[]
+ getEvents(replayData).then(events => {
+ this.replayService.setEvents(processReplay(events));
+ this.replayService.setHitObjects(parseHitObjects(beatmapData))
+ });
+ }
+
+ ngAfterViewInit() {
+ this.ctx = this.replayCanvas.nativeElement.getContext('2d')!;
+
+ this.replayService.setCanvasElement(this.replayCanvas);
+ this.replayService.setCanvasContext(this.ctx);
+
+ this.replayService.start(); // Start the animation loop
+ }
+
+ togglePlayPause() {
+ if (this.replayService.getIsPlaying()) {
+ this.replayService.pause();
+ } else {
+ this.replayService.start();
+ }
+ }
+
+ seek(time: number) {
+ this.replayService.seek(time);
+ if (!this.replayService.getIsPlaying()) {
+ // Redraw the canvas for the new current time without resuming playback
+ }
+ }
+
+}
diff --git a/nise-frontend/src/corelib/components/replay-viewer/sample-replay.ts b/nise-frontend/src/corelib/components/replay-viewer/sample-replay.ts
new file mode 100644
index 0000000..f69ae9c
--- /dev/null
+++ b/nise-frontend/src/corelib/components/replay-viewer/sample-replay.ts
@@ -0,0 +1,224 @@
+export let replayData = "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"
+export let beatmapData = "osu file format v14\n" +
+ "\n" +
+ "[General]\n" +
+ "AudioFilename: audio.mp3\n" +
+ "AudioLeadIn: 0\n" +
+ "PreviewTime: 932\n" +
+ "Countdown: 0\n" +
+ "SampleSet: Soft\n" +
+ "StackLeniency: 0.5\n" +
+ "Mode: 0\n" +
+ "LetterboxInBreaks: 0\n" +
+ "WidescreenStoryboard: 1\n" +
+ "\n" +
+ "[Editor]\n" +
+ "Bookmarks: 1327,11432,21537,31642\n" +
+ "DistanceSpacing: 0.5\n" +
+ "BeatDivisor: 4\n" +
+ "GridSize: 32\n" +
+ "TimelineZoom: 3.15\n" +
+ "\n" +
+ "[Metadata]\n" +
+ "Title:PADORU / PADORU\n" +
+ "TitleUnicode:PADORU / PADORU\n" +
+ "Artist:Turbo\n" +
+ "ArtistUnicode:Turbo\n" +
+ "Creator:DeRandom Otaku\n" +
+ "Version:Rolniczy's Hi-Speed Expert\n" +
+ "Source:Fate/EXTRA\n" +
+ "Tags:TurboAutism eurobeats nero claudius ren kowari -_frontier_- anzeigeistraus iljaaz rolniczy fuju smokelind deppyforce xenon- xehn affirmation neoskylove dorsalplum -_light_- xen rolni contagious Serizawa Haruki Jack J_a_c_k electronic japanese jingle bells 丹下桜 Sakura Tange Saber\n" +
+ "BeatmapID:2223135\n" +
+ "BeatmapSetID:1061287\n" +
+ "\n" +
+ "[Difficulty]\n" +
+ "HPDrainRate:6\n" +
+ "CircleSize:3.6\n" +
+ "OverallDifficulty:8.6\n" +
+ "ApproachRate:9.5\n" +
+ "SliderMultiplier:2.1\n" +
+ "SliderTickRate:1\n" +
+ "\n" +
+ "[Events]\n" +
+ "//Background and Video events\n" +
+ "0,0,\"bg.jpg\",0,0\n" +
+ "//Break Periods\n" +
+ "//Storyboard Layer 0 (Background)\n" +
+ "//Storyboard Layer 1 (Fail)\n" +
+ "//Storyboard Layer 2 (Pass)\n" +
+ "//Storyboard Layer 3 (Foreground)\n" +
+ "//Storyboard Layer 4 (Overlay)\n" +
+ "//Storyboard Sound Samples\n" +
+ "\n" +
+ "[TimingPoints]\n" +
+ "64,315.789473684211,4,2,0,60,1,0\n" +
+ "64,-100,4,2,0,60,0,0\n" +
+ "1011,-100,4,2,1,45,0,0\n" +
+ "1327,-100,4,2,1,60,0,0\n" +
+ "5748,-200,4,2,1,60,0,0\n" +
+ "8590,-125,4,2,1,65,0,0\n" +
+ "11432,-66.6666666666667,4,2,1,80,0,0\n" +
+ "12853,-62.5,4,2,1,80,0,0\n" +
+ "13327,-71.4285714285714,4,2,1,80,0,0\n" +
+ "13958,-66.6666666666667,4,2,1,80,0,0\n" +
+ "14274,-62.5,4,2,1,80,0,0\n" +
+ "14590,-66.6666666666667,4,2,1,80,0,0\n" +
+ "15379,-58.8235294117647,4,2,1,80,0,0\n" +
+ "15853,-66.6666666666667,4,2,1,80,0,0\n" +
+ "16485,-62.5,4,2,1,80,0,0\n" +
+ "17906,-55.5555555555556,4,2,1,80,0,0\n" +
+ "18537,-62.5,4,2,1,80,0,0\n" +
+ "19011,-58.8235294117647,4,2,1,80,0,0\n" +
+ "19958,-66.6666666666667,4,2,1,80,0,0\n" +
+ "20274,-66.6666666666667,4,2,1,70,0,0\n" +
+ "20748,-111.111111111111,4,2,1,70,0,0\n" +
+ "21537,-62.5,4,2,1,90,0,0\n" +
+ "22958,-58.8235294117647,4,2,1,90,0,0\n" +
+ "24064,-62.5,4,2,1,90,0,0\n" +
+ "25485,-55.5555555555556,4,2,1,90,0,0\n" +
+ "26590,-62.5,4,2,1,90,0,0\n" +
+ "29116,-45.4545454545455,4,2,1,90,0,0\n" +
+ "29748,-50,4,2,1,90,0,0\n" +
+ "30379,-50,4,2,1,75,0,0\n" +
+ "30853,-76.9230769230769,4,2,1,75,0,0\n" +
+ "31327,-76.9230769230769,4,2,1,70,0,0\n" +
+ "31642,-76.9230769230769,4,2,1,60,0,0\n" +
+ "31958,-76.9230769230769,4,2,0,5,0,0\n" +
+ "\n" +
+ "\n" +
+ "[Colours]\n" +
+ "Combo1 : 72,164,255\n" +
+ "Combo2 : 128,128,192\n" +
+ "\n" +
+ "[HitObjects]\n" +
+ "256,192,1011,5,8,0:2:0:0:\n" +
+ "351,323,1327,5,6,1:2:0:0:\n" +
+ "256,292,1642,1,2,0:2:0:0:\n" +
+ "256,192,1958,1,2,0:2:0:0:\n" +
+ "351,161,2274,1,2,0:2:0:0:\n" +
+ "409,242,2590,1,2,0:2:0:0:\n" +
+ "409,242,3537,5,2,1:2:0:0:\n" +
+ "103,142,3853,5,6,1:2:0:0:\n" +
+ "161,223,4169,1,2,0:2:0:0:\n" +
+ "256,192,4485,1,2,0:2:0:0:\n" +
+ "256,92,4800,1,2,0:2:0:0:\n" +
+ "161,61,5116,1,2,0:2:0:0:\n" +
+ "161,61,5748,6,0,P|172:112|182:171,1,105,0|2,1:2|0:2,0:0:0:0:\n" +
+ "355,87,6379,5,6,1:2:0:0:\n" +
+ "486,143,6695,1,2,0:2:0:0:\n" +
+ "470,288,7011,1,2,0:2:0:0:\n" +
+ "328,318,7327,1,2,0:2:0:0:\n" +
+ "256,192,7642,2,0,L|372:181,2,105,2|0|0,0:2|0:0|0:0,0:0:0:0:\n" +
+ "0,307,8590,6,0,P|32:281|134:282,1,126,2|0,1:2|0:0,0:0:0:0:\n" +
+ "148,192,8906,6,0,P|193:239|211:334,1,126,6|0,1:2|0:0,0:0:0:0:\n" +
+ "148,359,9221,1,2,0:2:0:0:\n" +
+ "148,359,9379,1,0,0:0:0:0:\n" +
+ "376,327,9537,2,0,L|268:307,1,84,2|0,0:2|0:0,0:0:0:0:\n" +
+ "507,196,9853,2,0,L|347:225,1,126,2|0,0:2|0:0,0:0:0:0:\n" +
+ "352,226,10169,6,0,L|369:125,1,84,2|0,0:2|0:0,0:0:0:0:\n" +
+ "147,190,10485,2,0,L|130:89,1,84\n" +
+ "467,28,10800,2,0,P|403:52|369:51,1,84\n" +
+ "101,24,11116,2,0,B|172:11|172:11|228:87,1,126,2|0,0:2|0:0,0:0:0:0:\n" +
+ "258,56,11432,6,0,L|241:233,1,157.500006008148,6|0,1:2|3:0,0:0:0:0:\n" +
+ "157,281,11669,1,0,3:0:0:0:\n" +
+ "157,281,11748,2,0,B|212:293|252:345|252:345|286:274|386:231,1,236.250009012223,2|0,1:2|0:0,0:0:0:0:\n" +
+ "510,290,12064,6,0,P|520:328|501:361,1,78.7500030040742,2|0,1:2|0:0,0:0:0:0:\n" +
+ "424,273,12221,2,0,P|405:306|415:344,1,78.7500030040742\n" +
+ "354,108,12379,6,0,P|363:71|344:38,1,78.7500030040742,2|0,1:2|0:0,0:0:0:0:\n" +
+ "266,125,12537,2,0,P|247:93|256:55,1,78.7500030040742\n" +
+ "75,301,12695,5,2,1:2:0:0:\n" +
+ "75,301,12853,6,0,L|188:272,1,84,2|0,0:2|0:0,0:0:0:0:\n" +
+ "266,125,13011,2,0,L|233:237,1,84,2|0,1:2|0:0,0:0:0:0:\n" +
+ "325,372,13169,2,0,L|244:287,1,84,2|0,0:2|0:0,0:0:0:0:\n" +
+ "13,160,13327,6,0,L|129:178,1,73.4999977569581,2|0,1:2|0:0,0:0:0:0:\n" +
+ "130,177,13485,2,0,L|111:13,2,146.999995513916,2|2|0,0:2|1:2|0:0,0:0:0:0:\n" +
+ "512,200,13958,6,0,B|523:186|511:164|511:164|425:159|396:269,1,157.500006008148,2|0,1:2|3:0,0:0:0:0:\n" +
+ "364,285,14195,1,0,3:0:0:0:\n" +
+ "364,285,14274,2,0,L|402:-8,1,252,2|0,1:2|0:0,0:0:0:0:\n" +
+ "486,13,14590,6,0,P|435:41|386:39,1,78.7500030040742,2|0,1:2|0:0,0:0:0:0:\n" +
+ "274,67,14748,2,0,P|218:49|185:13,1,78.7500030040742\n" +
+ "279,241,14906,6,0,P|226:215|200:173,1,78.7500030040742,2|0,1:2|0:0,0:0:0:0:\n" +
+ "129,176,15064,2,0,P|111:120|122:72,1,78.7500030040742\n" +
+ "19,264,15221,5,2,1:2:0:0:\n" +
+ "19,264,15379,6,0,P|44:265|17:225,1,89.2500017023087,2|0,0:2|0:0,0:0:0:0:\n" +
+ "7,29,15537,2,0,L|25:159,1,89.2500017023087,2|0,1:2|0:0,0:0:0:0:\n" +
+ "147,348,15695,2,0,L|165:218,1,89.2500017023087,2|0,0:2|0:0,0:0:0:0:\n" +
+ "274,67,15853,6,0,B|222:82|222:82|230:126,1,78.7500030040742,2|0,1:2|0:0,0:0:0:0:\n" +
+ "398,275,16011,2,0,B|450:260|450:260|420:142,2,157.500006008148,2|2|0,0:2|1:2|1:0,0:0:0:0:\n" +
+ "116,6,16485,6,0,P|140:101|155:182,1,168,2|0,1:2|3:0,0:0:0:0:\n" +
+ "159,259,16721,1,0,3:0:0:0:\n" +
+ "159,259,16800,2,0,B|221:218|324:240|262:280|365:302|428:262,1,252,2|0,1:2|0:0,0:0:0:0:\n" +
+ "492,230,17116,6,0,L|477:341,1,84,2|0,1:2|0:0,0:0:0:0:\n" +
+ "398,275,17274,2,0,L|386:358,1,84\n" +
+ "141,85,17432,6,0,L|153:169,1,84,2|0,1:2|0:0,0:0:0:0:\n" +
+ "235,131,17590,2,0,L|246:214,1,84\n" +
+ "488,46,17748,5,2,1:2:0:0:\n" +
+ "488,46,17906,6,0,B|457:85|457:85|392:68,1,94.499997116089,2|0,0:2|0:0,0:0:0:0:\n" +
+ "177,12,18064,2,0,B|221:5|221:5|252:44,1,94.499997116089,2|0,1:2|0:0,0:0:0:0:\n" +
+ "326,190,18221,2,0,L|310:323,1,94.499997116089,2|0,0:2|0:0,0:0:0:0:\n" +
+ "46,331,18379,5,2,1:2:0:0:\n" +
+ "46,331,18458,1,0,0:0:0:0:\n" +
+ "46,331,18537,2,0,L|234:309,2,168,2|2|0,0:2|1:2|0:0,0:0:0:0:\n" +
+ "504,224,19011,6,0,P|475:299|397:276,1,178.500003404617,2|0,1:2|3:0,0:0:0:0:\n" +
+ "326,190,19248,1,0,3:0:0:0:\n" +
+ "326,190,19327,2,0,L|517:206,1,178.500003404617,2|0,1:2|0:0,0:0:0:0:\n" +
+ "24,133,19642,2,0,L|316:108,1,267.750005106926,2|0,1:2|0:0,0:0:0:0:\n" +
+ "323,31,19958,6,0,P|302:50|293:121,1,78.7500030040742,2|0,1:2|0:0,0:0:0:0:\n" +
+ "402,155,20116,2,0,P|423:136|432:65,1,78.7500030040742\n" +
+ "189,16,20274,5,2,1:2:0:0:\n" +
+ "158,122,20432,1,2,0:2:0:0:\n" +
+ "189,228,20590,1,2,0:2:0:0:\n" +
+ "158,334,20748,6,0,B|205:303|283:319|236:350|314:367|361:336,1,188.999994232178,2|2,1:2|0:2,0:0:0:0:\n" +
+ "512,304,21221,2,0,P|476:235|438:282,1,141.749995674133,2|0,1:2|0:0,0:0:0:0:\n" +
+ "430,287,21537,6,0,L|405:120,1,168,6|0,1:2|3:0,0:0:0:0:\n" +
+ "393,22,21774,1,0,3:0:0:0:\n" +
+ "417,14,21853,2,0,B|454:30|459:55|459:55|372:22|247:16,1,252,2|0,1:2|0:0,0:0:0:0:\n" +
+ "213,89,22169,6,0,B|187:122|187:122|201:175,1,84,2|0,1:2|0:0,0:0:0:0:\n" +
+ "283,178,22327,2,0,B|308:144|308:144|294:91,1,84\n" +
+ "46,112,22485,2,0,L|257:82,1,168,2|0,1:2|0:0,0:0:0:0:\n" +
+ "498,44,22800,5,0,1:2:0:0:\n" +
+ "498,44,22958,6,0,P|476:67|512:58,1,89.2500017023087,2|0,0:2|0:0,0:0:0:0:\n" +
+ "283,178,23116,2,0,P|306:199|297:163,1,89.2500017023087,2|0,1:2|0:0,0:0:0:0:\n" +
+ "416,384,23274,2,0,P|437:360|401:369,1,89.2500017023087,2|0,0:2|0:0,0:0:0:0:\n" +
+ "462,216,23432,5,2,1:2:0:0:\n" +
+ "462,216,23511,1,0,0:0:0:0:\n" +
+ "462,216,23590,1,2,0:2:0:0:\n" +
+ "197,255,23748,2,0,L|484:212,1,267.750005106926,2|0,1:2|0:0,0:0:0:0:\n" +
+ "512,289,24064,6,0,B|428:308|428:308|367:221,1,168,2|0,1:2|3:0,0:0:0:0:\n" +
+ "282,155,24300,1,0,3:0:0:0:\n" +
+ "282,155,24379,2,0,B|458:118|458:118|443:214,1,252,2|0,1:2|0:0,0:0:0:0:\n" +
+ "307,323,24695,6,0,P|278:294|289:256,1,84,2|0,1:2|0:0,0:0:0:0:\n" +
+ "380,240,24853,2,0,P|408:268|397:306,1,84\n" +
+ "197,255,25011,2,0,L|4:221,1,168,2|0,1:2|0:0,0:0:0:0:\n" +
+ "298,61,25327,5,0,1:2:0:0:\n" +
+ "298,61,25485,6,0,L|183:81,1,94.499997116089,2|0,0:2|0:0,0:0:0:0:\n" +
+ "81,5,25642,2,0,L|101:120,1,94.499997116089,2|0,1:2|0:0,0:0:0:0:\n" +
+ "31,225,25800,2,0,L|146:205,1,94.499997116089,2|0,0:2|0:0,0:0:0:0:\n" +
+ "304,290,25958,6,0,L|397:306,1,94.499997116089,2|0,1:2|0:0,0:0:0:0:\n" +
+ "54,379,26116,2,0,L|241:358,2,188.999994232178,2|2|0,0:2|1:2|1:0,0:0:0:0:\n" +
+ "500,141,26590,6,0,B|419:170|419:170|360:113,1,168,2|0,1:2|3:0,0:0:0:0:\n" +
+ "441,58,26827,1,0,3:0:0:0:\n" +
+ "441,58,26906,2,0,L|390:341,1,252,2|0,1:2|0:0,0:0:0:0:\n" +
+ "352,332,27221,6,0,B|385:353|385:353|452:341,1,84,2|0,1:2|0:0,0:0:0:0:\n" +
+ "447,216,27379,2,0,B|414:195|414:195|347:207,1,84\n" +
+ "123,279,27537,2,0,L|314:261,1,168,2|0,1:2|0:0,0:0:0:0:\n" +
+ "0,291,27853,5,0,1:2:0:0:\n" +
+ "0,291,28011,6,0,L|20:387,1,84,2|0,0:2|0:0,0:0:0:0:\n" +
+ "75,15,28169,2,0,L|48:208,1,168,2|2,1:2|0:2,0:0:0:0:\n" +
+ "344,10,28485,1,2,1:2:0:0:\n" +
+ "344,10,28564,1,0,0:0:0:0:\n" +
+ "344,10,28642,1,2,0:2:0:0:\n" +
+ "491,316,28800,2,0,B|452:378|452:378|412:167,1,252,2|0,1:2|0:0,0:0:0:0:\n" +
+ "463,139,29116,6,0,B|415:168|415:168|351:149,1,115.50000352478,2|0,1:2|0:0,0:0:0:0:\n" +
+ "174,30,29274,1,0,3:0:0:0:\n" +
+ "164,110,29353,1,0,3:0:0:0:\n" +
+ "152,190,29432,2,0,B|227:204|227:204|206:72|206:72|34:107,1,346.500010574341,2|0,1:2|0:0,0:0:0:0:\n" +
+ "37,108,29748,6,0,B|15:164|15:164|44:232,1,105,2|0,1:2|0:0,0:0:0:0:\n" +
+ "163,347,29906,2,0,L|310:307,1,105\n" +
+ "477,6,30064,2,0,L|436:240,1,210,2|0,1:2|0:0,0:0:0:0:\n" +
+ "192,31,30379,5,2,1:2:0:0:\n" +
+ "337,202,30537,1,2,0:2:0:0:\n" +
+ "163,347,30695,1,2,0:2:0:0:\n" +
+ "441,213,30853,6,0,L|127:181,1,272.999987503052,2|2,1:2|0:2,0:0:0:0:\n" +
+ "11,19,31327,2,0,P|114:22|110:134,1,204.749990627289,2|0,1:2|0:0,0:0:0:0:\n" +
+ "85,178,31642,5,0,1:0:0:0:"