diff --git a/nise-replay-viewer/package-lock.json b/nise-replay-viewer/package-lock.json index b7ef681..40bc142 100644 --- a/nise-replay-viewer/package-lock.json +++ b/nise-replay-viewer/package-lock.json @@ -37,6 +37,7 @@ "sonner": "^1.3.1", "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", + "ts-md5": "^1.3.1", "zustand": "^4.4.1" }, "devDependencies": { @@ -3645,6 +3646,14 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, + "node_modules/ts-md5": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.3.1.tgz", + "integrity": "sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==", + "engines": { + "node": ">=12" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", diff --git a/nise-replay-viewer/package.json b/nise-replay-viewer/package.json index dc6d8e5..86af9af 100644 --- a/nise-replay-viewer/package.json +++ b/nise-replay-viewer/package.json @@ -46,6 +46,7 @@ "sonner": "^1.3.1", "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", + "ts-md5": "^1.3.1", "zustand": "^4.4.1" } } diff --git a/nise-replay-viewer/src/interface/composites/song-slider.tsx b/nise-replay-viewer/src/interface/composites/song-slider.tsx index f0f9c23..b7e42d6 100644 --- a/nise-replay-viewer/src/interface/composites/song-slider.tsx +++ b/nise-replay-viewer/src/interface/composites/song-slider.tsx @@ -6,55 +6,71 @@ import { Button } from "../components/ui/button"; import { ArrowLeft, ArrowRight, PauseIcon, PlayIcon } from "lucide-react"; export function SongSlider() { - const { beatmap, replay, playing, time } = state(); + const { beatmap, replay, playing, time, speed } = state(); if (!beatmap || !replay) { return; } return ( - - - - Current time - {new Date(time).toISOString().slice(11, 19)} + + + + Current time + {new Date(time).toISOString().slice(11, 19)} - - - { - OsuRenderer.setTime(OsuRenderer.time - 1000); - }}> - - - { - OsuRenderer.setPlaying(!playing); - }} - variant="outline" - size="icon" - > - {playing ? : } - - { - OsuRenderer.setTime(OsuRenderer.time + 1000); - }}> - - - - + + + { + OsuRenderer.setTime(OsuRenderer.time - 1000); + }}> + + + { + OsuRenderer.setPlaying(!playing); + }} + variant="outline" + size="icon" + > + {playing ? : } + + { + OsuRenderer.setTime(OsuRenderer.time + 1000); + }}> + + + + - + + + { + OsuRenderer.setTime(value[0]); + }} + /> + + + Current speed + {speed}x + + - { - OsuRenderer.setTime(value[0]); - }} - /> - + { + OsuRenderer.setSpeed(value[0]); + }} + /> + ); } diff --git a/nise-replay-viewer/src/osu/Drawer.ts b/nise-replay-viewer/src/osu/Drawer.ts index 7cd007c..99f8c60 100644 --- a/nise-replay-viewer/src/osu/Drawer.ts +++ b/nise-replay-viewer/src/osu/Drawer.ts @@ -1,9 +1,12 @@ import { Vector2 } from "osu-classes"; import p5 from "p5"; import { loadImageAsync } from "@/utils"; +import { Md5 } from "ts-md5"; export class Drawer { + private static imageCache: Record = {}; + static images = { cursor: undefined as any as p5.Image, cursortrail: undefined as any as p5.Image, @@ -128,51 +131,56 @@ export class Drawer { static drawSliderBody(origin: Vector2, path: Vector2[], radius: number) { Drawer.p.push(); - const g = Drawer.p.createGraphics(512 * 4, 384 * 4); - g.scale(2); - g.translate(512 - 256, 384 - 192); - //@ts-ignore - const ctx = g.drawingContext; - ctx.lineCap = "round"; - ctx.lineJoin = "round"; - g.translate(origin.x, origin.y); - g.noFill(); + const cacheKey = Md5.hashStr(JSON.stringify(origin) + JSON.stringify(path) + JSON.stringify(radius)); + if (!this.imageCache[cacheKey]) { + const g = Drawer.p.createGraphics(512 * 4, 384 * 4); + g.scale(2); + g.translate(512 - 256, 384 - 192); + //@ts-ignore + const ctx = g.drawingContext; + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + g.translate(origin.x, origin.y); + g.noFill(); - g.strokeWeight(radius * 2 - 10); + g.strokeWeight(radius * 2 - 10); - g.stroke(255); - g.beginShape(); - for (const node of path) { - g.vertex(node.x, node.y); - } - g.endShape(); + g.stroke(255); + g.beginShape(); + for (const node of path) { + g.vertex(node.x, node.y); + } + g.endShape(); - g.strokeWeight(radius * 2 - 17); - g.stroke(10); - g.beginShape(); - for (const node of path) { - g.vertex(node.x, node.y); - } - - g.endShape(); - - for (let i = 0; i < radius * 2 - 17; i += 2) { - g.strokeWeight(radius * 2 - 17 - i); - g.stroke(Math.round((i / (radius * 2 - 17)) * 45)); + g.strokeWeight(radius * 2 - 17); + g.stroke(10); g.beginShape(); for (const node of path) { g.vertex(node.x, node.y); } g.endShape(); - } + for (let i = 0; i < radius * 2 - 17; i += 2) { + g.strokeWeight(radius * 2 - 17 - i); + g.stroke(Math.round((i / (radius * 2 - 17)) * 45)); + g.beginShape(); + for (const node of path) { + g.vertex(node.x, node.y); + } + + g.endShape(); + } + + this.imageCache[cacheKey] = g; + } Drawer.p.imageMode(Drawer.p.CORNER); - Drawer.p.image(g, -256, -192, 512 * 2, 384 * 2); + Drawer.p.image(this.imageCache[cacheKey], -256, -192, 512 * 2, 384 * 2); Drawer.p.pop(); } + static drawCursorPath( path: { position: Vector2; diff --git a/nise-replay-viewer/src/osu/OsuRenderer.ts b/nise-replay-viewer/src/osu/OsuRenderer.ts index c7704bf..8790d8c 100644 --- a/nise-replay-viewer/src/osu/OsuRenderer.ts +++ b/nise-replay-viewer/src/osu/OsuRenderer.ts @@ -20,6 +20,7 @@ export enum OsuRendererEvents { LOAD = "LOAD", PLAY = "PLAY", TIME = "TIME", + SPEED = "SPEED", } export class OsuRendererBridge extends EventEmitter { @@ -37,6 +38,7 @@ export class OsuRenderer { static event = new OsuRendererBridge(); + static speedMultiplier = 0.5; static time: number = 0; static beatmap: StandardBeatmap; static og_beatmap: StandardBeatmap; @@ -64,7 +66,7 @@ export class OsuRenderer { } if (this.playing) { - this.setTime(this.time + (Date.now() - this.lastRender)); + this.setTime(this.time + ((Date.now() - this.lastRender) * this.speedMultiplier)); } this.lastRender = Date.now(); @@ -189,6 +191,11 @@ export class OsuRenderer { this.event.emit(OsuRendererEvents.TIME); } + static setSpeed(speed: number) { + this.speedMultiplier = speed; + this.event.emit(OsuRendererEvents.SPEED); + } + private static renderObject(hitObject: StandardHitObject) { if (hitObject instanceof Circle) { this.renderCircle(hitObject); @@ -318,15 +325,6 @@ export class OsuRenderer { Drawer.drawSliderFollowPoint(sliderPos, hitObject.radius); } - // if (GameplayAnalyzer.renderJudgements[hitObject.startTime]) { - // Drawer.setDrawingOpacity(opacity / 2); - - // Drawer.drawCircleJudgement( - // hitObject.stackedStartPosition, - // hitObject.radius, - // GameplayAnalyzer.renderJudgements[hitObject.startTime] - // ); - // } Drawer.endDrawing(); return arScale; } diff --git a/nise-replay-viewer/src/renderer.ts b/nise-replay-viewer/src/renderer.ts index f1ce7fd..c5aae0a 100644 --- a/nise-replay-viewer/src/renderer.ts +++ b/nise-replay-viewer/src/renderer.ts @@ -52,5 +52,10 @@ export class Renderer { time: OsuRenderer.time, }); }); + OsuRenderer.event.on(OsuRendererEvents.SPEED, () => { + state.setState({ + speed: OsuRenderer.speedMultiplier, + }); + }); } } diff --git a/nise-replay-viewer/src/utils.ts b/nise-replay-viewer/src/utils.ts index bb6958b..abf2e37 100644 --- a/nise-replay-viewer/src/utils.ts +++ b/nise-replay-viewer/src/utils.ts @@ -55,6 +55,7 @@ export const state = create<{ mods: IMod[] | null; playing: boolean; time: number; + speed: number; }>(() => ({ metadataEditorDialog: false, openDialog: false, @@ -69,6 +70,7 @@ export const state = create<{ mods: null, playing: false, time: 0, + speed: 1 })); state.subscribe((newState) => { diff --git a/nise-replay-viewer/yarn.lock b/nise-replay-viewer/yarn.lock index f88e4cf..f2aa269 100644 --- a/nise-replay-viewer/yarn.lock +++ b/nise-replay-viewer/yarn.lock @@ -1899,6 +1899,11 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +ts-md5@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/ts-md5/-/ts-md5-1.3.1.tgz" + integrity sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg== + tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
Current time
{new Date(time).toISOString().slice(11, 19)}
Current speed
{speed}x