Full Code of abstrakt8/rewind for AI

master d9d24182c893 cached
507 files
758.9 KB
223.5k tokens
1378 symbols
1 requests
Download .txt
Showing preview only (893K chars total). Download the full file or copy to clipboard to get everything.
Repository: abstrakt8/rewind
Branch: master
Commit: d9d24182c893
Files: 507
Total size: 758.9 KB

Directory structure:
gitextract_icqr6nvq/

├── .editorconfig
├── .eslintrc.json
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       └── build-release.yml
├── .gitignore
├── .gitmodules
├── .prettierignore
├── .prettierrc
├── .storybook/
│   ├── main.js
│   ├── preview.js
│   ├── tsconfig.json
│   └── webpack.config.js
├── .vscode/
│   └── extensions.json
├── .yarnrc
├── CONTRIBUTION.md
├── LICENSE.md
├── README.md
├── apps/
│   ├── desktop/
│   │   ├── README.md
│   │   ├── frontend/
│   │   │   ├── .babelrc
│   │   │   ├── .browserslistrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── .storybook/
│   │   │   │   ├── main.js
│   │   │   │   ├── preview.js
│   │   │   │   ├── tsconfig.json
│   │   │   │   └── webpack.config.js
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── proxy.conf.json
│   │   │   ├── src/
│   │   │   │   ├── app/
│   │   │   │   │   ├── RewindApp.tsx
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── analyzer/
│   │   │   │   │   │   │   ├── BaseAudioSettingsPanel.tsx
│   │   │   │   │   │   │   ├── BaseCurrentTime.stories.tsx
│   │   │   │   │   │   │   ├── BaseCurrentTime.tsx
│   │   │   │   │   │   │   ├── BaseDialog.tsx
│   │   │   │   │   │   │   ├── BaseGameTimeSlider.stories.tsx
│   │   │   │   │   │   │   ├── BaseGameTimeSlider.tsx
│   │   │   │   │   │   │   ├── BaseSettingsModal.stories.tsx
│   │   │   │   │   │   │   ├── BaseSettingsModal.tsx
│   │   │   │   │   │   │   ├── GameCanvas.tsx
│   │   │   │   │   │   │   ├── HelpModal.stories.tsx
│   │   │   │   │   │   │   ├── HelpModal.tsx
│   │   │   │   │   │   │   ├── PlayBar.tsx
│   │   │   │   │   │   │   └── SettingsModal.tsx
│   │   │   │   │   │   ├── logo/
│   │   │   │   │   │   │   └── RewindLogo.tsx
│   │   │   │   │   │   ├── sidebar/
│   │   │   │   │   │   │   └── LeftMenuSidebar.tsx
│   │   │   │   │   │   └── update/
│   │   │   │   │   │       └── UpdateModal.tsx
│   │   │   │   │   ├── hooks/
│   │   │   │   │   │   ├── app-info.ts
│   │   │   │   │   │   ├── audio.ts
│   │   │   │   │   │   ├── energy-saver.ts
│   │   │   │   │   │   ├── game-clock.ts
│   │   │   │   │   │   ├── interval.ts
│   │   │   │   │   │   ├── mods.ts
│   │   │   │   │   │   ├── redux.ts
│   │   │   │   │   │   └── shortcuts.ts
│   │   │   │   │   ├── model/
│   │   │   │   │   │   ├── BlueprintInfo.ts
│   │   │   │   │   │   ├── OsuReplay.ts
│   │   │   │   │   │   ├── Skin.ts
│   │   │   │   │   │   └── SkinId.ts
│   │   │   │   │   ├── providers/
│   │   │   │   │   │   ├── SettingsProvider.tsx
│   │   │   │   │   │   └── TheaterProvider.tsx
│   │   │   │   │   ├── screens/
│   │   │   │   │   │   ├── analyzer/
│   │   │   │   │   │   │   └── Analyzer.tsx
│   │   │   │   │   │   ├── home/
│   │   │   │   │   │   │   └── HomeScreen.tsx
│   │   │   │   │   │   ├── setup/
│   │   │   │   │   │   │   └── SetupScreen.tsx
│   │   │   │   │   │   └── splash/
│   │   │   │   │   │       └── SplashScreen.tsx
│   │   │   │   │   ├── services/
│   │   │   │   │   │   ├── analysis/
│   │   │   │   │   │   │   ├── AnalysisApp.ts
│   │   │   │   │   │   │   ├── analysis-cursor.ts
│   │   │   │   │   │   │   ├── createRewindAnalysisApp.ts
│   │   │   │   │   │   │   ├── mod-settings.ts
│   │   │   │   │   │   │   ├── scenes/
│   │   │   │   │   │   │   │   ├── AnalysisScene.ts
│   │   │   │   │   │   │   │   ├── IdleScene.ts
│   │   │   │   │   │   │   │   ├── LoadingScene.ts
│   │   │   │   │   │   │   │   └── ResultsScreenScene.ts
│   │   │   │   │   │   │   └── screenshot.ts
│   │   │   │   │   │   ├── common/
│   │   │   │   │   │   │   ├── CommonManagers.ts
│   │   │   │   │   │   │   ├── app-info.ts
│   │   │   │   │   │   │   ├── audio/
│   │   │   │   │   │   │   │   ├── AudioEngine.ts
│   │   │   │   │   │   │   │   ├── AudioService.ts
│   │   │   │   │   │   │   │   └── settings.ts
│   │   │   │   │   │   │   ├── beatmap-background.ts
│   │   │   │   │   │   │   ├── beatmap-render.ts
│   │   │   │   │   │   │   ├── cursor.ts
│   │   │   │   │   │   │   ├── game/
│   │   │   │   │   │   │   │   ├── GameLoop.ts
│   │   │   │   │   │   │   │   ├── GameSimulator.ts
│   │   │   │   │   │   │   │   └── GameplayClock.ts
│   │   │   │   │   │   │   ├── hit-error-bar.ts
│   │   │   │   │   │   │   ├── key-press-overlay.ts
│   │   │   │   │   │   │   ├── local/
│   │   │   │   │   │   │   │   ├── BlueprintLocatorService.ts
│   │   │   │   │   │   │   │   ├── OsuDBDao.ts
│   │   │   │   │   │   │   │   ├── OsuFolderService.ts
│   │   │   │   │   │   │   │   ├── ReplayFileWatcher.ts
│   │   │   │   │   │   │   │   ├── ReplayService.ts
│   │   │   │   │   │   │   │   └── SkinLoader.ts
│   │   │   │   │   │   │   ├── local-storage.ts
│   │   │   │   │   │   │   ├── playbar.ts
│   │   │   │   │   │   │   ├── playfield-border.ts
│   │   │   │   │   │   │   ├── replay-cursor.ts
│   │   │   │   │   │   │   ├── scenes/
│   │   │   │   │   │   │   │   ├── IScene.ts
│   │   │   │   │   │   │   │   └── SceneManager.ts
│   │   │   │   │   │   │   └── skin.ts
│   │   │   │   │   │   ├── core/
│   │   │   │   │   │   │   └── service.ts
│   │   │   │   │   │   ├── manager/
│   │   │   │   │   │   │   ├── AnalysisSceneManager.ts
│   │   │   │   │   │   │   ├── BeatmapManager.ts
│   │   │   │   │   │   │   ├── ClipRecorder.ts
│   │   │   │   │   │   │   ├── ReplayManager.ts
│   │   │   │   │   │   │   └── ScenarioManager.ts
│   │   │   │   │   │   ├── renderers/
│   │   │   │   │   │   │   ├── PixiRendererManager.ts
│   │   │   │   │   │   │   ├── components/
│   │   │   │   │   │   │   │   ├── background/
│   │   │   │   │   │   │   │   │   └── BeatmapBackground.ts
│   │   │   │   │   │   │   │   ├── hud/
│   │   │   │   │   │   │   │   │   └── ForegroundHUDPreparer.ts
│   │   │   │   │   │   │   │   ├── keypresses/
│   │   │   │   │   │   │   │   │   └── KeyPressOverlay.ts
│   │   │   │   │   │   │   │   ├── playfield/
│   │   │   │   │   │   │   │   │   ├── CursorPreparer.ts
│   │   │   │   │   │   │   │   │   ├── HitCircleFactory.ts
│   │   │   │   │   │   │   │   │   ├── HitObjectsContainerFactory.ts
│   │   │   │   │   │   │   │   │   ├── JudgementPreparer.ts
│   │   │   │   │   │   │   │   │   ├── PlayfieldBorderFactory.ts
│   │   │   │   │   │   │   │   │   ├── PlayfieldFactory.ts
│   │   │   │   │   │   │   │   │   ├── SliderFactory.ts
│   │   │   │   │   │   │   │   │   └── SpinnerFactory.ts
│   │   │   │   │   │   │   │   ├── sliders/
│   │   │   │   │   │   │   │   │   └── SliderTextureManager.ts
│   │   │   │   │   │   │   │   └── stage/
│   │   │   │   │   │   │   │       └── AnalysisStage.ts
│   │   │   │   │   │   │   └── constants.ts
│   │   │   │   │   │   ├── textures/
│   │   │   │   │   │   │   └── TextureManager.ts
│   │   │   │   │   │   └── types/
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── store/
│   │   │   │   │   │   ├── index.tsx
│   │   │   │   │   │   ├── settings/
│   │   │   │   │   │   │   └── slice.ts
│   │   │   │   │   │   └── update/
│   │   │   │   │   │       └── slice.ts
│   │   │   │   │   ├── styles/
│   │   │   │   │   │   └── theme.ts
│   │   │   │   │   └── utils/
│   │   │   │   │       ├── constants.ts
│   │   │   │   │       ├── focus.ts
│   │   │   │   │       ├── pooling/
│   │   │   │   │       │   ├── ObjectPool.ts
│   │   │   │   │       │   └── TemporaryObjectPool.ts
│   │   │   │   │       └── replay.ts
│   │   │   │   ├── assets/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── constants.ts
│   │   │   │   ├── environments/
│   │   │   │   │   ├── environment.prod.ts
│   │   │   │   │   └── environment.ts
│   │   │   │   ├── index.html
│   │   │   │   ├── main.tsx
│   │   │   │   ├── polyfills.ts
│   │   │   │   └── styles.css
│   │   │   ├── test/
│   │   │   │   └── ajv.spec.ts
│   │   │   ├── tsconfig.app.json
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.spec.json
│   │   │   └── webpack.config.js
│   │   └── main/
│   │       ├── .eslintrc.json
│   │       ├── README.md
│   │       ├── electron-builder.json
│   │       ├── jest.config.ts
│   │       ├── package.json
│   │       ├── src/
│   │       │   ├── app/
│   │       │   │   ├── .gitkeep
│   │       │   │   ├── config.ts
│   │       │   │   ├── events.ts
│   │       │   │   ├── updater.ts
│   │       │   │   └── windows.ts
│   │       │   ├── assets/
│   │       │   │   └── .gitkeep
│   │       │   ├── environments/
│   │       │   │   ├── environment.prod.ts
│   │       │   │   └── environment.ts
│   │       │   └── index.ts
│   │       ├── tsconfig.app.json
│   │       ├── tsconfig.json
│   │       └── tsconfig.spec.json
│   └── web/
│       └── backend/
│           ├── .eslintrc.json
│           ├── README.md
│           ├── jest.config.ts
│           ├── src/
│           │   ├── DesktopAPI.ts
│           │   ├── api-common.module.ts
│           │   ├── assets/
│           │   │   └── index.html
│           │   ├── blueprints/
│           │   │   ├── BlueprintInfo.ts
│           │   │   ├── LocalBlueprintController.ts
│           │   │   ├── LocalBlueprintService.ts
│           │   │   └── OsuDBDao.ts
│           │   ├── config/
│           │   │   ├── DesktopConfigController.ts
│           │   │   ├── DesktopConfigService.ts
│           │   │   ├── UserConfigService.ts
│           │   │   └── utils.ts
│           │   ├── constants.ts
│           │   ├── environments/
│           │   │   ├── environment.prod.ts
│           │   │   └── environment.ts
│           │   ├── events/
│           │   │   ├── Events.ts
│           │   │   └── EventsGateway.ts
│           │   ├── main.ts
│           │   ├── replays/
│           │   │   ├── LocalReplayController.ts
│           │   │   ├── LocalReplayService.ts
│           │   │   ├── ReplayWatcher.ts
│           │   │   └── ScoresDBDao.ts
│           │   ├── skins/
│           │   │   ├── SkinController.ts
│           │   │   ├── SkinNameResolver.ts
│           │   │   └── SkinService.ts
│           │   ├── status/
│           │   │   └── SetupStatusController.ts
│           │   └── utils/
│           │       ├── names.spec.ts
│           │       └── names.ts
│           ├── tsconfig.app.json
│           ├── tsconfig.json
│           └── tsconfig.spec.json
├── babel.config.json
├── electron-builder.json
├── jest.config.ts
├── jest.preset.js
├── libs/
│   ├── @types/
│   │   └── node-osr/
│   │       └── index.d.ts
│   ├── osu/
│   │   ├── core/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── audio/
│   │   │   │   │   ├── HitSampleInfo.ts
│   │   │   │   │   └── LegacySampleBank.ts
│   │   │   │   ├── beatmap/
│   │   │   │   │   ├── Beatmap.ts
│   │   │   │   │   ├── BeatmapBuilder.ts
│   │   │   │   │   ├── BeatmapDifficulty.ts
│   │   │   │   │   ├── ControlPoints/
│   │   │   │   │   │   ├── ControlPoint.ts
│   │   │   │   │   │   ├── ControlPointGroup.ts
│   │   │   │   │   │   ├── ControlPointInfo.ts
│   │   │   │   │   │   ├── DifficultyControlPoint.ts
│   │   │   │   │   │   ├── EffectControlPoint.ts
│   │   │   │   │   │   ├── SampleControlPoint.ts
│   │   │   │   │   │   └── TimingControlPoint.ts
│   │   │   │   │   ├── LegacyEffectFlag.ts
│   │   │   │   │   └── TimeSignatures.ts
│   │   │   │   ├── blueprint/
│   │   │   │   │   ├── Blueprint.ts
│   │   │   │   │   ├── BlueprintParser.spec.ts
│   │   │   │   │   ├── BlueprintParser.ts
│   │   │   │   │   └── HitObjectSettings.ts
│   │   │   │   ├── gameplay/
│   │   │   │   │   ├── GameState.ts
│   │   │   │   │   ├── GameStateEvaluator.spec.ts
│   │   │   │   │   ├── GameStateEvaluator.ts
│   │   │   │   │   ├── GameStateTimeMachine.ts
│   │   │   │   │   ├── GameplayAnalysisEvent.ts
│   │   │   │   │   ├── GameplayInfo.ts
│   │   │   │   │   └── Verdicts.ts
│   │   │   │   ├── hitobjects/
│   │   │   │   │   ├── HitCircle.ts
│   │   │   │   │   ├── Properties.ts
│   │   │   │   │   ├── Slider.ts
│   │   │   │   │   ├── SliderCheckPoint.ts
│   │   │   │   │   ├── Spinner.ts
│   │   │   │   │   ├── Types.ts
│   │   │   │   │   └── slider/
│   │   │   │   │       ├── PathApproximator.ts
│   │   │   │   │       ├── PathControlPoint.ts
│   │   │   │   │       ├── PathType.ts
│   │   │   │   │       ├── SliderCheckPointDescriptor.ts
│   │   │   │   │       ├── SliderCheckPointGenerator.ts
│   │   │   │   │       └── SliderPath.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── mods/
│   │   │   │   │   ├── EasyMod.ts
│   │   │   │   │   ├── HardRockMod.ts
│   │   │   │   │   ├── HiddenMod.ts
│   │   │   │   │   ├── Mods.spec.ts
│   │   │   │   │   ├── Mods.ts
│   │   │   │   │   └── StackingMod.ts
│   │   │   │   ├── playfield.ts
│   │   │   │   ├── replays/
│   │   │   │   │   ├── RawReplayData.ts
│   │   │   │   │   ├── Replay.ts
│   │   │   │   │   ├── ReplayClicks.ts
│   │   │   │   │   ├── ReplayParser.spec.ts
│   │   │   │   │   └── ReplayParser.ts
│   │   │   │   └── utils/
│   │   │   │       ├── SortedList.spec.ts
│   │   │   │       ├── SortedList.ts
│   │   │   │       ├── index.spec.ts
│   │   │   │       └── index.ts
│   │   │   ├── test/
│   │   │   │   ├── PathApproximator.spec.ts
│   │   │   │   └── utils/
│   │   │   │       └── asserts.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── math/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── Vec2.ts
│   │   │   │   ├── colors.ts
│   │   │   │   ├── difficulty.spec.ts
│   │   │   │   ├── difficulty.ts
│   │   │   │   ├── easing.ts
│   │   │   │   ├── float32.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── sliders.ts
│   │   │   │   ├── time.spec.ts
│   │   │   │   ├── time.ts
│   │   │   │   └── utils.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── pp/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── index.ts
│   │   │   │   └── lib/
│   │   │   │       ├── diff.ts
│   │   │   │       ├── mods.spec.ts
│   │   │   │       ├── mods.ts
│   │   │   │       ├── pp.ts
│   │   │   │       ├── skills/
│   │   │   │       │   ├── aim.ts
│   │   │   │       │   ├── flashlight.ts
│   │   │   │       │   ├── speed.ts
│   │   │   │       │   └── strain.ts
│   │   │   │       ├── utils.spec.ts
│   │   │   │       └── utils.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   └── skin/
│   │       ├── .babelrc
│   │       ├── .eslintrc.json
│   │       ├── README.md
│   │       ├── jest.config.ts
│   │       ├── src/
│   │       │   ├── index.ts
│   │       │   └── lib/
│   │       │       ├── OsuSkinTextureConfig.ts
│   │       │       ├── SkinConfig.ts
│   │       │       ├── SkinConfigParser.spec.ts
│   │       │       ├── SkinConfigParser.ts
│   │       │       └── TextureTypes.ts
│   │       ├── tsconfig.json
│   │       ├── tsconfig.lib.json
│   │       └── tsconfig.spec.json
│   ├── osu-local/
│   │   ├── db-reader/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   ├── DatabaseReader.ts
│   │   │   │   ├── DatabaseTypes.ts
│   │   │   │   ├── OsuBuffer.ts
│   │   │   │   ├── OsuDBReader.ts
│   │   │   │   ├── ScoresDBReader.ts
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── gosumemory/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   ├── gosumemory.ts
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── osr-reader/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── skin-reader/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   ├── SkinFolderReader.ts
│   │   │   │   ├── SkinTextureResolver.ts
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   └── utils/
│   │       ├── .babelrc
│   │       ├── .eslintrc.json
│   │       ├── README.md
│   │       ├── jest.config.ts
│   │       ├── src/
│   │       │   ├── dates.ts
│   │       │   ├── files.ts
│   │       │   ├── index.ts
│   │       │   ├── osuUserConfig.spec.ts
│   │       │   ├── osuUserConfig.ts
│   │       │   └── stable.ts
│   │       ├── tsconfig.json
│   │       ├── tsconfig.lib.json
│   │       └── tsconfig.spec.json
│   └── osu-pixi/
│       ├── classic-components/
│       │   ├── .babelrc
│       │   ├── .eslintrc.json
│       │   ├── README.md
│       │   ├── jest.config.ts
│       │   ├── src/
│       │   │   ├── DrawableSettings.ts
│       │   │   ├── hitobjects/
│       │   │   │   ├── OsuClassicApproachCircle.ts
│       │   │   │   ├── OsuClassicConstants.ts
│       │   │   │   ├── OsuClassicCursor.ts
│       │   │   │   ├── OsuClassicHitCircleArea.ts
│       │   │   │   ├── OsuClassicJudgements.ts
│       │   │   │   ├── OsuClassicSliderBall.ts
│       │   │   │   ├── OsuClassicSliderBody.ts
│       │   │   │   ├── OsuClassicSliderRepeat.ts
│       │   │   │   ├── OsuClassicSliderTick.ts
│       │   │   │   └── OsuClassicSpinner.ts
│       │   │   ├── hud/
│       │   │   │   ├── HitErrorBar.ts
│       │   │   │   ├── OsuClassicAccuracy.ts
│       │   │   │   ├── OsuClassicJudgement.ts
│       │   │   │   └── OsuClassicNumber.ts
│       │   │   ├── index.ts
│       │   │   ├── playfield/
│       │   │   │   └── PlayfieldBorder.ts
│       │   │   ├── renderers/
│       │   │   │   └── BasicSliderTextureRenderer.ts
│       │   │   └── utils/
│       │   │       ├── Animation.ts
│       │   │       ├── Pixi.ts
│       │   │       ├── Preparable.ts
│       │   │       ├── Transformations.ts
│       │   │       ├── constants.ts
│       │   │       ├── numbers.spec.ts
│       │   │       └── numbers.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.lib.json
│       │   └── tsconfig.spec.json
│       └── rewind/
│           ├── .babelrc
│           ├── .eslintrc.json
│           ├── README.md
│           ├── jest.config.ts
│           ├── src/
│           │   ├── index.ts
│           │   └── lib/
│           │       └── AnalysisCursor.ts
│           ├── tsconfig.json
│           ├── tsconfig.lib.json
│           └── tsconfig.spec.json
├── migrations.json
├── nx.json
├── package.json
├── resources/
│   └── Skins/
│       ├── OsuDefaultSkin/
│       │   └── README.md
│       └── RewindDefaultSkin/
│           ├── README.md
│           └── skin.ini
├── testdata/
│   └── osu!/
│       ├── Replays/
│       │   ├── - Perfume - Daijobanai [Short kick slider] (2021-07-16) Perfect.osr
│       │   ├── - Perfume - Daijobanai [Short kick slider] (2021-07-16) TooLateMissed.osr
│       │   ├── - Perfume - Daijobanai [Slider (Repeat = 1)] (2021-07-07) Perfect.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu Perfect.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu SliderHeadMissedButTrackingWtf.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu SliderHeadTooEarly.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu SliderHeadTooLate.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Perfect.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderEndMissed.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderHeadMissedButTrackingWtf.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderHeadTooEarly.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderHeadTooLate.osr
│       │   ├── RyuK - HoneyWorks - Akatsuki Zukuyo [Taeyang_s Extra] (2019-06-08) Osu.osr
│       │   ├── Varvalian - Aether Realm - The Sun, The Moon, The Star [Mourning Those Things I've Long Left Behind] (2019-05-15) Osu.osr
│       │   ├── abstrakt - Gojou Mayumi - DANZEN! Futari wa PreCure Ver. MaxHeart (TV Size) [Insane] (2021-08-21) Osu.osr
│       │   ├── abstrakt - PSYQUI - Hype feat. Such [Dreamer] (2021-08-08) Osu.osr
│       │   ├── abstrakt - SHK - Violet Perfume [Insane] (2021-03-27) Osu.osr
│       │   ├── abstrakt - Smile.dk - Koko Soko (AKIBA KOUBOU Eurobeat Remix) [Couch Mini2a] (2021-04-09) Osu.osr
│       │   ├── abstrakt - sabi - true DJ MAG top ranker_s song Zenpen (katagiri Remix) [Senseabel's Extra] (2021-08-08) Osu.osr
│       │   ├── hallowatcher - DECO27 - HIBANA feat. Hatsune Miku [Lock On] (2020-02-09) Osu.osr
│       │   └── kellad - Asriel - Raison D'etre [EXist] (2025-02-16) Osu.osr
│       ├── Songs/
│       │   ├── 1001507 ZUTOMAYO - Kan Saete Kuyashiiwa/
│       │   │   └── ZUTOMAYO - Kan Saete Kuyashiiwa (Nathan) [geragera].osu
│       │   ├── 1010865 SHK - Violet Perfume [no video]/
│       │   │   └── SHK - Violet Perfume (ktgster) [Insane].osu
│       │   ├── 1236927 Frums - XNOR XNOR XNOR/
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [.-- .-. --- -. --. .-- .- -.--].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Beloved Exclusive].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Earth (atm)].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Earth].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Fire].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Metal].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Moon].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Sun].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Water].osu
│       │   │   └── Frums - XNOR XNOR XNOR (fanzhen0019) [Wood].osu
│       │   ├── 1302792 Smiledk - Koko Soko (AKIBA KOUBOU Eurobeat Remix)/
│       │   │   └── Smile.dk - Koko Soko (AKIBA KOUBOU Eurobeat Remix) ([ Couch ] Mini) [Couch Mini2a].osu
│       │   ├── 1357624 sabi - true DJ MAG top ranker's song Zenpen (katagiri Remix)/
│       │   │   └── sabi - true DJ MAG top ranker's song Zenpen (katagiri Remix) (Nathan) [KEMOMIMI EDM SQUAD].osu
│       │   ├── 1495211 Aether Realm - The Tower/
│       │   │   └── Aether Realm - The Tower (Takane) [Brick and Mortar].osu
│       │   ├── 150945 Knife Party - Centipede/
│       │   │   └── Knife Party - Centipede (Sugoi-_-Desu) [This isn't a map, just a simple visualisation].osu
│       │   ├── 158023 UNDEAD CORPORATION - Everything will freeze/
│       │   │   └── UNDEAD CORPORATION - Everything will freeze (Ekoro) [Time Freeze].osu
│       │   ├── 29157 Within Temptation - The Unforgiving [no video]/
│       │   │   └── Within Temptation - The Unforgiving (Armin) [Marathon].osu
│       │   ├── 351280 HoneyWorks - Akatsuki Zukuyo/
│       │   │   └── HoneyWorks - Akatsuki Zukuyo ([C u r i]) [Taeyang's Extra].osu
│       │   ├── 607089 Xi - Rokujuu Nenme no Shinsoku Saiban _ Rapidity is a justice/
│       │   │   └── Xi - Rokujuu Nenme no Shinsoku Saiban ~ Rapidity is a justice (tokiko) [Extra Stage].osu
│       │   ├── 863227 Brian The Sun - Lonely Go! (TV Size) [no video]/
│       │   │   └── Brian The Sun - Lonely Go! (TV Size) (Nevo) [Fiery's Extreme].osu
│       │   ├── 931596 Apol - Hidamari no Uta/
│       │   │   └── Apol - Hidamari no Uta (-Keitaro) [Expert].osu
│       │   ├── 933630 Aether Realm - The Sun, The Moon, The Star/
│       │   │   └── Aether Realm - The Sun, The Moon, The Star (ItsWinter) [Mourning Those Things I've Long Left Behind].osu
│       │   └── 967347 Perfume - Daijobanai/
│       │       ├── Perfume - Daijobanai (eiri-) [Easy].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Hard].osu
│       │       ├── Perfume - Daijobanai (eiri-) [HitCircle 1].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Normal].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Short kick slider].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Slider (Repeat = 1)].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Slider 1].osu
│       │       └── Perfume - Daijobanai (eiri-) [Smile].osu
│       └── osu!.me.cfg
├── tests/
│   ├── game-simulation/
│   │   ├── .eslintrc.json
│   │   ├── README.md
│   │   ├── jest.config.ts
│   │   ├── src/
│   │   │   ├── core/
│   │   │   │   ├── BeatmapBuilder.spec.ts
│   │   │   │   ├── BlueprintParser.spec.ts
│   │   │   │   ├── OsuStdReplayState.spec.ts
│   │   │   │   ├── ReplayClicks.spec.ts
│   │   │   │   ├── archive/
│   │   │   │   │   ├── reference/
│   │   │   │   │   │   ├── DANZEN.spec.ts
│   │   │   │   │   │   └── KokoSokoMini.spec.ts
│   │   │   │   │   └── replays/
│   │   │   │   │       ├── DaijobanaiSlider1.spec.ts
│   │   │   │   │       ├── sunMoonStar.spec.ts
│   │   │   │   │       └── topranker.spec.ts
│   │   │   │   ├── bpm.test.ts
│   │   │   │   └── hitobjects.test.ts
│   │   │   ├── local/
│   │   │   │   ├── osudb.test.ts
│   │   │   │   └── scoresdb.test.ts
│   │   │   ├── others.ts
│   │   │   ├── pp/
│   │   │   │   ├── diff.test.ts
│   │   │   │   ├── ojsama.test.ts
│   │   │   │   └── pp.test.ts
│   │   │   ├── reference.ts
│   │   │   └── util.ts
│   │   ├── tsconfig.json
│   │   └── tsconfig.spec.json
│   └── osu-stable-test-generator/
│       ├── .eslintrc.json
│       ├── jest.config.ts
│       ├── src/
│       │   ├── app/
│       │   │   └── .gitkeep
│       │   ├── assets/
│       │   │   └── .gitkeep
│       │   ├── environments/
│       │   │   ├── environment.prod.ts
│       │   │   └── environment.ts
│       │   └── main.ts
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       └── tsconfig.spec.json
├── tools/
│   ├── generators/
│   │   └── .gitkeep
│   └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# Editor configuration, see http://editorconfig.org
root = true

[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false


================================================
FILE: .eslintrc.json
================================================
{
  "root": true,
  "ignorePatterns": ["**/*"],
  "plugins": ["@nrwl/nx"],
  "overrides": [
    {
      "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
      "rules": {
        "@nrwl/nx/enforce-module-boundaries": [
          "error",
          {
            "enforceBuildableLibDependency": true,
            "allow": [],
            "depConstraints": [
              {
                "sourceTag": "*",
                "onlyDependOnLibsWithTags": ["*"]
              }
            ]
          }
        ]
      }
    },
    {
      "files": ["*.ts", "*.tsx"],
      "extends": ["plugin:@nrwl/nx/typescript"],
      "rules": {}
    },
    {
      "files": ["*.js", "*.jsx"],
      "extends": ["plugin:@nrwl/nx/javascript"],
      "rules": {}
    }
  ]
}


================================================
FILE: .gitattributes
================================================
# Often used LFS tracks
# https://gist.github.com/ma-al/019f7f76498c55f0061120a5b13c6d88
# ------------------------------------------------
# Usual image types
*.png filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
# ------------------------------------------------

# Audio files
*.ogg filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text

# osu! related files
*.osr filter=lfs diff=lfs merge=lfs -text
*.db filter=lfs diff=lfs merge=lfs -text
*.osu filter=lfs diff=lfs merge=lfs -text


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce** (optional)
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Version [e.g. 22]

**Additional context**

**Please** also provide the following if they are mentioned and related:
* Beatmap
* Replay
* Skin


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''

---

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Additional context** (optional)
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/workflows/build-release.yml
================================================
name: Build/release v2

on:
  push:
  workflow_dispatch:

jobs:
  release:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ ubuntu-latest, windows-latest, macos-latest ]

    steps:
      - name: Checkout code
        uses: nschloe/action-cached-lfs-checkout@v1

      - name: Install Node.js, NPM and Yarn
        uses: actions/setup-node@v3
        with:
          node-version: 20
          cache: 'npm'

      - name: Build/release Electron app
        uses: Yan-Jobs/action-electron-builder@v1.7.0
        with:
          # GitHub token, automatically provided to the action
          # (No need to define this secret in the repo settings)
          github_token: ${{ secrets.github_token }}

          # If the commit is tagged with a version (e.g. "v1.0.0"),
          # release the app after building
          release: ${{ startsWith(github.ref, 'refs/tags/v') }}


================================================
FILE: .gitignore
================================================
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp
/out-tsc

# dependencies
/node_modules

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings

# System Files
.DS_Store
Thumbs.db

# Custom
/logs/


================================================
FILE: .gitmodules
================================================
[submodule "testdata/osu-testdata"]
	path = testdata/osu-testdata
	url = https://github.com/abstrakt8/osu-testdata


================================================
FILE: .prettierignore
================================================
# Add files here to ignore them from prettier formatting

/dist
/coverage


================================================
FILE: .prettierrc
================================================
{
  "printWidth": 120,
  "tabWidth": 2,
  "trailingComma": "all",
  "endOfLine": "lf"
}


================================================
FILE: .storybook/main.js
================================================
module.exports = {
  stories: [],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "storybook-css-modules-preset",
    {
      name: "@storybook/addon-postcss",
      options: {
        postcssLoaderOptions: {
          implementation: require("postcss"),
        },
      },
    },
  ],
};


================================================
FILE: .storybook/preview.js
================================================
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

================================================
FILE: .storybook/tsconfig.json
================================================
{
  "extends": "../tsconfig.base.json",
  "exclude": [
    "../**/*.spec.js",
    "../**/*.spec.ts",
    "../**/*.spec.tsx",
    "../**/*.spec.jsx"
  ],
  "include": ["../**/*"]
}


================================================
FILE: .storybook/webpack.config.js
================================================
/**
 * Export a function. Accept the base config as the only param.
 * @param {Object} options
 * @param {Required<import('webpack').Configuration>} options.config
 * @param {'DEVELOPMENT' | 'PRODUCTION'} options.mode - change the build configuration. 'PRODUCTION' is used when building the static version of storybook.
 */
module.exports = async ({ config, mode }) => {
  // Make whatever fine-grained changes you need

  // Return the altered config
  return config;
};


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "nrwl.angular-console",
    "esbenp.prettier-vscode",
    "firsttris.vscode-jest-runner",
    "dbaeumer.vscode-eslint"
  ]
}


================================================
FILE: .yarnrc
================================================
# https://github.com/yarnpkg/yarn/issues/5540
# because of material-ui icons
network-timeout 600000




================================================
FILE: CONTRIBUTION.md
================================================
Project Structure
===

General
---

This repository is a mono-repo that is currently focused on the development of the Rewind desktop application. However,
as we are working with web technologies (WebGL, JS), there are also plans to implement a Rewind web version.

This mono-repo is powered by the [nx](https://nx.dev/) build system.

In `apps` you will find the applications - every folder corresponds to one "product".

In `libs` you will find the common code that is shared across all the applications.

In `tests` you will find the integration tests that will test the correctness of the libraries. Generally speaking,
tests that have some external dependencies (such as a beatmap or replay file) will belong here. Unit tests should be
written in the corresponding library `src` folder with `*.spec.ts` filename extension.

> Familiarity with [Electron's process model](https://www.electronjs.org/docs/latest/tutorial/process-model) required.
 
Setup
===
Basics
---

Install the following:

* Latest Node.js LTS version (e.g. `nvm install --lts`)
* [`git-lfs`](https://git-lfs.github.com/) for the test data in `testdata/`


When changes have been done to some submodules, you need to merge them as follows:
```
git submodule update --remote --merge
```

Building
---

```bash
yarn install
yarn run build
```

Developing
---

First start the `desktop-frontend` to expose the frontend on port 4200 with "Hot Reloading" enabled.

```bash
yarn run desktop-frontend:dev
```

Then start the Electron application:

```bash
yarn run desktop-main:dev
```

If you make a change in the `desktop-main` package, you will need to rerun the command above again.


Releasing
---

When you want to create a new release, follow these steps:

1. Update the version in your project's `package.json` file (e.g. `1.2.3`)
2. Commit that change (`git commit -am v1.2.3`)
3. Tag your commit (`git tag v1.2.3`). Make sure your tag name's format is `v*.*.*`. Your workflow will use this tag to detect when to create a release
4. Push your changes to GitHub (`git push && git push --tags`)

After building successfully, the action will publish your release artifacts. By default, a new release draft will be created on GitHub with download links for your app. If you want to change this behavior, have a look at the [`electron-builder` docs](https://www.electron.build).


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) 2021 abstrakt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<h1 align="center">Rewind</h1>

![Github releases](https://img.shields.io/github/v/release/abstrakt8/rewind?label=stable)
![Github releases](https://img.shields.io/github/v/release/abstrakt8/rewind?include_prereleases&label=latest)
[![GitHub Releases Downloads](https://img.shields.io/github/downloads/abstrakt8/rewind/total?label=Downloads)](https://github.com/abstrakt8/rewind/releases/latest)
[![Discord](https://img.shields.io/discord/841454370888351784.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/QubdHdnBVg)

Rewind is a beatmap/replay analyzer for [osu!](https://osu.ppy.sh/) and is currently in development phase.

<img src="resources/readme/ed_can_fc_ror.gif" alt="BTMC on Road of Resistance" /><br/>

## Features

This [video](https://www.youtube.com/watch?v=KDatdxvjdmc) shows most of the features listed down below:

* Music playback
* Speed change 0.25x-4.0x
* Scrubbing (jumping to a specific time)
* Toggling the "Hidden" mod
* Analysis cursor
* Slider dev mode
* Hit error bar (slightly improved)
* Statistics such as UR
* Watching frame by frame
* Skin support
* Difficulty graph on the timeline
* Gameplay events (Miss/SB/50/100) in the timeline
* Support for unsubmitted maps
* File watcher
* Customizability of many elements (e.g. cursor size)
* Shortcuts
* ...

## Download

The latest release for Windows/Linux can be found at the [release page](https://github.com/abstrakt8/rewind/releases).

## Questions / Feedback / Discussions

If you have any questions about Rewind or want to contribute by submitting ideas, feel free to join
the [osu! University Discord](https://discord.gg/QubdHdnBVg). It is an improvement-focused osu! hub, osu!
coaching hub, and a platform for experienced players to spread their game knowledge to the public.

## Contribution (development, testing, documentation, ...)

If you want to contribute, please join the [Dev Discord](https://discord.gg/pwCVATunVt).

Currently, a large portion of the code is in a "volatile" / "prototype" / "proof of concept" state. So if you want to contribute by developing, please notify me beforehand.


================================================
FILE: apps/desktop/README.md
================================================
The Rewind desktop application consists of two renderers which are implemented in `backend` and `frontend`.

The entry point of this application is implemented in "main.js".


================================================
FILE: apps/desktop/frontend/.babelrc
================================================
{
  "presets": [
    [
      "@nrwl/react/babel",
      {
        "runtime": "automatic"
      }
    ]
  ],
  "plugins": []
}


================================================
FILE: apps/desktop/frontend/.browserslistrc
================================================
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.

last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

================================================
FILE: apps/desktop/frontend/.eslintrc.json
================================================
{
  "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
  "ignorePatterns": ["!**/*"],
  "overrides": [
    {
      "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
      "rules": {}
    },
    {
      "files": ["*.ts", "*.tsx"],
      "rules": {}
    },
    {
      "files": ["*.js", "*.jsx"],
      "rules": {}
    }
  ]
}


================================================
FILE: apps/desktop/frontend/.storybook/main.js
================================================
const rootMain = require("../../../../.storybook/main");

// Use the following syntax to add addons!
// rootMain.addons.push('');
rootMain.stories.push(...["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"]);

module.exports = rootMain;


================================================
FILE: apps/desktop/frontend/.storybook/preview.js
================================================
import React from "react";

import { addDecorator } from "@storybook/react";

import { ThemeProvider as EmotionThemeProvider } from "emotion-theming";
import { rewindTheme } from "../src/app/styles/theme";
import { CssBaseline, ThemeProvider } from "@mui/material";

const defaultTheme = rewindTheme;

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

const withThemeProvider = (Story, context) => {
  return (
    <EmotionThemeProvider theme={defaultTheme}>
      <ThemeProvider theme={defaultTheme}>
        <CssBaseline />
        <Story {...context} />
      </ThemeProvider>
    </EmotionThemeProvider>
  );
};

addDecorator(withThemeProvider);


================================================
FILE: apps/desktop/frontend/.storybook/tsconfig.json
================================================
{
  "extends": "../../../../libs/feature-replay-viewer/tsconfig.json",
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "outDir": "tsconfig"
  },
  "files": [
    "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
    "../../node_modules/@nrwl/react/typings/image.d.ts"
  ],
  "exclude": [
    "../**/*.spec.ts",
    "../**/*.test.ts",
    "../**/*.spec.js",
    "../**/*.test.js",
    "../**/*.spec.tsx",
    "../**/*.test.tsx",
    "../**/*.spec.jsx",
    "../**/*.test.jsx"
  ],
  "include": [
    "../src/**/*",
    "*.js"
  ]
}


================================================
FILE: apps/desktop/frontend/.storybook/webpack.config.js
================================================
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const rootWebpackConfig = require("../../../../.storybook/webpack.config");
/**
 * Export a function. Accept the base config as the only param.
 *
 * @param {Parameters<typeof rootWebpackConfig>[0]} options
 */
module.exports = async ({ config, mode }) => {
  config = await rootWebpackConfig({ config, mode });

  const tsPaths = new TsconfigPathsPlugin({
    configFile: "./tsconfig.base.json",
  });

  config.resolve.plugins ? config.resolve.plugins.push(tsPaths) : (config.resolve.plugins = [tsPaths]);

  // Found this here: https://github.com/nrwl/nx/issues/2859
  // And copied the part of the solution that made it work

  const svgRuleIndex = config.module.rules.findIndex((rule) => {
    const { test } = rule;

    // @rewind: very important to have test?.
    return test?.toString().startsWith("/\\.(svg|ico");
  });
  config.module.rules[svgRuleIndex].test = /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;

  config.module.rules.push(
    {
      test: /\.(png|jpe?g|gif|webp)$/,
      loader: require.resolve("url-loader"),
      options: {
        limit: 10000, // 10kB
        name: "[name].[hash:7].[ext]",
      },
    },
    {
      test: /\.svg$/,
      oneOf: [
        // If coming from JS/TS file, then transform into React component using SVGR.
        {
          issuer: {
            test: /\.[jt]sx?$/,
          },
          use: [
            {
              loader: require.resolve("@svgr/webpack"),
              options: {
                svgo: false,
                titleProp: true,
                ref: true,
              },
            },
            {
              loader: require.resolve("url-loader"),
              options: {
                limit: 10000, // 10kB
                name: "[name].[hash:7].[ext]",
                esModule: false,
              },
            },
          ],
        },
        // Fallback to plain URL loader.
        {
          use: [
            {
              loader: require.resolve("url-loader"),
              options: {
                limit: 10000, // 10kB
                name: "[name].[hash:7].[ext]",
              },
            },
          ],
        },
      ],
    },
  );

  return config;
};


================================================
FILE: apps/desktop/frontend/README.md
================================================
This is the frontend that gets deployed with the Electron app. That's why there is

Some notes:

- Uses `redux` for state management.
- The `rewind` app is kind of a standalone application, therefore it's not really connected to the redux state
  management.


================================================
FILE: apps/desktop/frontend/jest.config.ts
================================================
/* eslint-disable */
export default {
  displayName: "frontend",
  preset: "../../../jest.preset.js",
  transform: {
    "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "@nrwl/react/plugins/jest",
    "^.+\\.[tj]sx?$": "babel-jest",
  },
  moduleFileExtensions: ["ts", "tsx", "js", "jsx"],
  coverageDirectory: "../../../coverage/apps/frontend",
};


================================================
FILE: apps/desktop/frontend/proxy.conf.json
================================================
{
  "/api": {
    "target": "http://localhost:3333",
    "secure": false
  },
  "/desktop-backend-api": {
    "target": "http://localhost:3333",
    "secure": false
  }
}


================================================
FILE: apps/desktop/frontend/src/app/RewindApp.tsx
================================================
import { useAppDispatch } from "./hooks/redux";
import { Outlet, Routes, Route, useNavigate } from "react-router-dom";
import { LeftMenuSidebar } from "./components/sidebar/LeftMenuSidebar";
import { SplashScreen } from "./screens/splash/SplashScreen";
import { HomeScreen } from "./screens/home/HomeScreen";
import { Box, Divider, Stack } from "@mui/material";
import { UpdateModal } from "./components/update/UpdateModal";
import { useEffect } from "react";
import { downloadFinished, downloadProgressed, newVersionAvailable } from "./store/update/slice";
import { frontendAPI } from "./api";
import { ipcRenderer } from "electron";
import { useTheaterContext } from "./providers/TheaterProvider";
import { ELECTRON_UPDATE_FLAG } from "./utils/constants";
import { Analyzer } from "./screens/analyzer/Analyzer";
import { SetupScreen } from "./screens/setup/SetupScreen";

function NormalView() {
  return (
    <Stack direction={"row"} sx={{ height: "100vh" }}>
      <LeftMenuSidebar />
      <Divider orientation={"vertical"} />
      <Box sx={{ flexGrow: 1, height: "100%" }}>
        <Outlet />
      </Box>
      <UpdateModal />
    </Stack>
  );
}

export function RewindApp() {
  const navigate = useNavigate();
  const dispatch = useAppDispatch();
  const theater = useTheaterContext();

  useEffect(() => {
    void theater.common.initialize();

    // For now, we will just navigate to the analyzer app since we only have one tool
    ipcRenderer.on("onManualReplayOpen", (event, file) => {
      navigate("/app/analyzer");
      void theater.analyzer.loadReplay(file);
    });

    (async function () {
      if (!(await theater.analyzer.osuFolderService.hasValidOsuFolderSet())) {
        console.log("osu! folder was not set, redirecting to the setup screen.");
        navigate("/setup");
      } else {
        console.log(`osu! folder = ${theater.analyzer.osuFolderService.getOsuFolder()}`);
        console.log(`osu!/Songs folder = ${theater.analyzer.osuFolderService.songsFolder$.getValue()}`);
        console.log(`osu!/Replays folder = ${theater.analyzer.osuFolderService.replaysFolder$.getValue()}`);
        navigate("/app/analyzer");
      }
    })();

    if (ELECTRON_UPDATE_FLAG) {
      frontendAPI.onUpdateAvailable((version) => {
        dispatch(newVersionAvailable(version));
      });
      frontendAPI.onDownloadFinished(() => {
        dispatch(downloadFinished());
      });
      frontendAPI.onUpdateDownloadProgress((updateInfo) => {
        const { total, bytesPerSecond, transferred } = updateInfo;
        dispatch(downloadProgressed({ downloadedBytes: transferred, totalBytes: total, bytesPerSecond }));
      });
      // We start checking for update on the front end, otherwise if we start it from the Electron main process, the
      // notification might get lost (probably need a message queue if we want to start from the Electron main process)
      frontendAPI.checkForUpdate();
    }
  }, []);

  return (
    <Routes>
      <Route index element={<SplashScreen />} />
      <Route path={"setup"} element={<SetupScreen />} />
      <Route path={"app"} element={<NormalView />}>
        <Route path={"home"} element={<HomeScreen />} />
        <Route path={"analyzer"} element={<Analyzer />} />
      </Route>
    </Routes>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/api.ts
================================================
import { ipcRenderer } from "electron";

type Listener = (...args: any) => any;

// Maybe use it for onUpdateDownloadProgress
interface UpdateInfo {
  total: number;
  transferred: number;
  bytesPerSecond: number;
}

export const frontendAPI = {
  getPath: (type: string) => ipcRenderer.invoke("getPath", type),
  selectDirectory: (defaultPath: string) => ipcRenderer.invoke("selectDirectory", defaultPath),
  selectFile: (defaultPath: string) => ipcRenderer.invoke("selectFile", defaultPath),
  reboot: () => ipcRenderer.invoke("reboot"),
  getAppVersion: () => ipcRenderer.invoke("getAppVersion"),
  getPlatform: () => ipcRenderer.invoke("getPlatform"),
  onManualReplayOpen: (listener: Listener) => ipcRenderer.on("onManualReplayOpen", (event, file) => listener(file)),
  onUpdateAvailable: (listener: Listener) => ipcRenderer.on("onUpdateAvailable", (event, version) => listener(version)),
  onUpdateDownloadProgress: (listener: Listener) =>
    ipcRenderer.on("onUpdateDownloadProgress", (event, info) => listener(info)),
  startDownloadingUpdate: () => ipcRenderer.invoke("startDownloadingUpdate"),
  onDownloadFinished: (listener: Listener) => ipcRenderer.on("onDownloadFinished", (event) => listener()),
  checkForUpdate: () => ipcRenderer.invoke("checkForUpdate"),
  quitAndInstall: () => ipcRenderer.invoke("quitAndInstall"),
};


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseAudioSettingsPanel.tsx
================================================
import { Box, Slider, Stack, Tooltip, Typography } from "@mui/material";
import { VolumeDown, VolumeUp } from "@mui/icons-material";

type Change = (x: number) => void;

interface BaseAudioSettingsPanelProps {
  master: number;
  effects: number;
  music: number;

  onMasterChange: Change;
  onEffectsChange: Change;
  onMusicChange: Change;
  onMutedChange: (muted: boolean) => unknown;
}

export function VolumeSlider({ disabled, onChange, value }: { disabled?: boolean; onChange: Change; value: number }) {
  return (
    <Stack gap={2} direction={"row"}>
      <VolumeDown />
      <Slider
        size="small"
        valueLabelDisplay="auto"
        disabled={disabled}
        value={Math.floor(value * 100)}
        step={1}
        onChange={(_, x) => onChange((x as number) / 100)}
      />
      <VolumeUp />
    </Stack>
  );
}

export function BaseAudioSettingsPanel(props: BaseAudioSettingsPanelProps) {
  const { master, effects, music, onMasterChange, onEffectsChange, onMusicChange, onMutedChange } = props;
  return (
    <Stack
      sx={{
        p: 2,
      }}
      gap={1}
    >
      <Box>
        <Typography id="input-slider" gutterBottom>
          Master Volume
        </Typography>
        <VolumeSlider onChange={onMasterChange} value={master} />
      </Box>

      <Box>
        <Typography id="input-slider" gutterBottom>
          Music Volume
        </Typography>
        <VolumeSlider onChange={onMusicChange} value={music} />
      </Box>
      <Box>
        <Typography id="input-slider" gutterBottom>
          Effects Volume
        </Typography>
        <Tooltip title={"Hit sounds coming soon!"}>
          {/*https://mui.com/components/tooltips/#disabled-elements*/}
          <span>
            <VolumeSlider disabled={true} onChange={onEffectsChange} value={effects} />
          </span>
        </Tooltip>
      </Box>
    </Stack>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseCurrentTime.stories.tsx
================================================
import { Meta, Story } from "@storybook/react";
import { Paper } from "@mui/material";
import { BaseCurrentTime, GameCurrentTimeProps } from "./BaseCurrentTime";

export default {
  component: BaseCurrentTime,
  title: "BaseCurrentTime",
  argTypes: {
    onClick: { action: "onClick executed!" },
  },
} as Meta;

const Template: Story<GameCurrentTimeProps> = (args) => (
  <Paper elevation={1}>
    <BaseCurrentTime {...args} />
  </Paper>
);

export const Time200ms = Template.bind({});
Time200ms.args = {
  currentTimeInMs: 200,
};

export const Time1727ms = Template.bind({});
Time1727ms.args = {
  currentTimeInMs: 1727,
};

export const TimeHour = Template.bind({});
TimeHour.args = {
  currentTimeInMs: 60 * 60 * 1000 + 727,
};


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseCurrentTime.tsx
================================================
import React, { forwardRef, ForwardRefRenderFunction, useImperativeHandle, useRef } from "react";
import { formatGameTime } from "@osujs/math";
import { darken, Typography } from "@mui/material";

export type GameCurrentTimeProps = Record<string, unknown>;

export interface GameCurrentTimeHandle {
  updateTime: (timeInMs: number) => void;
}

const ForwardCurrentTime: ForwardRefRenderFunction<GameCurrentTimeHandle, GameCurrentTimeProps> = (props, ref) => {
  const refMain = useRef<HTMLSpanElement>(null);
  const refMs = useRef<HTMLSpanElement>(null);

  useImperativeHandle(ref, () => ({
    updateTime(timeInMs) {
      const [timeHMS, timeMS] = formatGameTime(timeInMs, true).split(".");
      if (refMain.current) refMain.current.textContent = timeHMS;
      if (refMs.current) refMs.current.textContent = "." + timeMS;
    },
  }));
  return (
    <Typography component={"span"} sx={{ userSelect: "all" }}>
      <span ref={refMain}>0:00</span>

      <Typography component={"span"} sx={{ color: (theme) => darken(theme.palette.text.primary, 0.6) }} ref={refMs}>
        <span ref={refMs}>.000</span>
      </Typography>
    </Typography>
  );
};

export const BaseCurrentTime = forwardRef(ForwardCurrentTime);


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseDialog.tsx
================================================
import { IconButton, Link, Typography } from "@mui/material";
import { FaDiscord } from "react-icons/fa";
import React from "react";
import { RewindLinks } from "../../utils/constants";
import { useCommonManagers } from "../../providers/TheaterProvider";

export function PromotionFooter() {
  const appVersion = useCommonManagers().appInfoService.version;
  return (
    <Typography fontSize={"caption.fontSize"} color={"text.secondary"}>
      Rewind {appVersion} by{" "}
      <Link href={RewindLinks.OsuPpyShAbstrakt} target={"_blank"} color={"text.secondary"}>
        abstrakt
      </Link>{" "}
      | osu! University
      <IconButton href={RewindLinks.OsuUniDiscord} target={"_blank"} size={"small"}>
        <FaDiscord />
      </IconButton>
    </Typography>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseGameTimeSlider.stories.tsx
================================================
import { Box } from "@mui/material";
import { Meta, Story } from "@storybook/react";
import { BaseGameTimeSlider, BaseGameTimeSliderProps } from "./BaseGameTimeSlider";

export default {
  component: BaseGameTimeSlider,
  title: "BaseGameTimeSlider",
  argTypes: {
    onClick: { action: "onClick executed!" },
  },
} as Meta;

const Template: Story<BaseGameTimeSliderProps> = (args) => (
  <Box width={"420px"}>
    <BaseGameTimeSlider {...args} />
  </Box>
);

export const Primary = Template.bind({});
Primary.args = {
  backgroundEnable: true,
};

export const NotEnabled = Template.bind({});
NotEnabled.args = {
  backgroundEnable: false,
};


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseGameTimeSlider.tsx
================================================
import { Box, Slider, styled } from "@mui/material";
import { formatGameTime, rgbToInt } from "@osujs/math";
import { ignoreFocus } from "../../utils/focus";
import { useEffect, useRef } from "react";
import { Renderer, Sprite, Texture } from "pixi.js";
import { AdjustmentFilter } from "@pixi/filter-adjustment";
import { Container } from "@pixi/display";
import { Chart, registerables } from "chart.js";
import colorString from "color-string";

//
Chart.register(...registerables);

interface EventLineProps {
  color: string;
  tooltip: string;
  positions: number[];
}

type EventType = { timings: number[]; tooltip: string; color: string };

export interface BaseGameTimeSliderProps {
  backgroundEnable?: boolean;
  // Duration in ms
  duration: number;
  // Time in ms
  currentTime: number;
  // When the slider is dragged
  onChange: (value: number) => any;

  events: EventType[];
  difficulties: number[];
}

function drawPlaybarEvents(canvas: HTMLCanvasElement, eventTypes: EventType[], duration: number) {
  const renderer = new Renderer({ view: canvas, backgroundAlpha: 0.0 });
  const stage = new Container();
  const { height, width } = renderer.screen;
  const eventLineHeight = height / eventTypes.length;
  for (let i = 0; i < eventTypes.length; i++) {
    const eventType = eventTypes[i];
    const container = new Container();
    const descriptor = colorString.get(eventType.color);
    if (!descriptor) break;
    const tint = rgbToInt(descriptor.value);
    // console.log(`Event ${i} has color ${tint} and ${descriptor.value}`);
    for (const timing of eventType.timings) {
      const sprite = Sprite.from(Texture.WHITE);
      sprite.tint = tint;
      // TODO: This needs to be centered -> but doesn't really matter since it's kinda negligible
      sprite.width = 1;
      sprite.height = eventLineHeight;
      sprite.position.set((timing / duration) * width, 0);
      container.addChild(sprite);
    }
    stage.addChild(container);
    container.position.set(0, i * eventLineHeight);
  }
  stage.filters = [new AdjustmentFilter({ brightness: 0.7 })];

  stage.interactive = false;
  stage.interactiveChildren = false;
  renderer.render(stage);
  // Doesn't need ticker right?
}

const PlaybarEventsCanvas = styled("canvas")`
  //background: aqua;
  position: absolute;
  height: 40px;
  width: 100%;
  top: 0;
  left: 0;
  transform: translate(0, -50%);
`;

const DifficultyCanvas = styled("canvas")`
  //position: absolute;
  //top: 0;
  //left: 0;
  //transform: translate(0, -100%);
  //background: aqua;
`;

function drawDifficulty(canvas: HTMLCanvasElement, data: number[]) {
  // const labels = [0, 20, 40, 50, 99, 1000];
  const labels = data.map((_) => "");

  const chart = new Chart(canvas, {
    type: "line",
    options: {
      maintainAspectRatio: false,

      // To hide the little "knobs"
      elements: {
        point: {
          radius: 0,
        },
      },
      scales: {
        x: {
          display: false,
        },
        y: {
          display: false,
        },
      },
      plugins: {
        legend: {
          display: false,
        },
        tooltip: {
          enabled: false,
        },
      },
    },
    data: {
      labels,
      datasets: [
        {
          data,
          fill: true,
          // borderColor: "rgba(255, 255, 255, 0.5)",
          // borderColor: "hsla(0,4%,31%,0.77)",
          backgroundColor: "hsla(0, 2%, 44%, 0.3)",
          tension: 0.5,
        },
      ],
    },
  });
  chart.draw();
  return chart;
}

export function BaseGameTimeSlider(props: BaseGameTimeSliderProps) {
  const { backgroundEnable, duration, currentTime, onChange, events, difficulties } = props;
  const valueLabelFormat = (value: number) => formatGameTime(value);

  const eventsCanvas = useRef<HTMLCanvasElement>(null!);
  const difficultyCanvas = useRef<HTMLCanvasElement>(null!);

  useEffect(() => {
    drawPlaybarEvents(eventsCanvas.current, events, duration);
  }, [eventsCanvas, events, duration]);
  useEffect(() => {
    const chart = drawDifficulty(difficultyCanvas.current, difficulties);
    return () => {
      chart.destroy();
    };
  }, [difficultyCanvas, difficulties]);

  return (
    <Box sx={{ width: "100%", position: "relative" }}>
      <PlaybarEventsCanvas ref={eventsCanvas} />
      <Box sx={{ position: "absolute", top: 0, transform: "translate(0, -100%)", width: "100%" }}>
        <Box sx={{ position: "relative", height: "48px", width: "100%" }}>
          <DifficultyCanvas ref={difficultyCanvas} />
        </Box>
      </Box>
      <Slider
        onFocus={ignoreFocus}
        size={"small"}
        // The padding here determines how clickable the slider is
        // This is copied from: https://mui.com/components/slider/#music-player
        sx={{
          position: "absolute",
          top: "50%",
          transform: "translate(0, -50%)",
          padding: "2px 0",

          color: "white",
          height: 6,
          "& .MuiSlider-thumb": {
            width: 8,
            height: 8,
            // transition: "0.3s bezier(.47,1.64,.41,.8)",
            transition: "none",
            "&:before": {
              boxShadow: "0 2px 12px 0 rgba(0,0,0,0.4)",
            },
            "&:hover, &.Mui-focusVisible": {
              boxShadow: `0px 0px 0px 8px ${"rgb(255 255 255 / 16%)"}`,
            },
            "&.Mui-active": {
              width: 12,
              height: 12,
            },
          },
          "& .MuiSlider-rail": {
            opacity: 0.28,
          },
          "& .MuiSlider-track": {
            transition: "none", // Otherwise it lags behind on very short songs
          },
        }}
        value={currentTime}
        onChange={(_, x) => onChange(x as number)}
        getAriaValueText={valueLabelFormat}
        valueLabelFormat={valueLabelFormat}
        valueLabelDisplay={"auto"}
        step={16}
        max={duration}
      />
    </Box>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseSettingsModal.stories.tsx
================================================
import { Meta, Story } from "@storybook/react";
import { BaseSettingsModal, SettingsProps } from "./BaseSettingsModal";
import { Paper } from "@mui/material";

export default {
  component: BaseSettingsModal,
  title: "BaseSettingsModal",
  argTypes: {
    onClose: { action: "onClick executed!" },
  },
} as Meta;

const Template: Story<SettingsProps> = (args) => (
  <Paper elevation={2} sx={{ width: 560 }}>
    <BaseSettingsModal {...args} />
  </Paper>
);

export const Primary = Template.bind({});
Primary.args = {
  tabs: [
    { component: <div>General</div>, label: "General" },
    { component: <div>Skinning</div>, label: "Cool" },
  ],
};


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/BaseSettingsModal.tsx
================================================
import { Box, Divider, IconButton, Paper, Slider, Stack, Tab, Tabs, Typography } from "@mui/material";
import React from "react";
import { Close, Settings as SettingsIcon, Visibility, VisibilityOff } from "@mui/icons-material";
import { PromotionFooter } from "./BaseDialog";

interface SettingTab {
  component: React.ReactNode;
  label: string;
}

export interface SettingsProps {
  onClose?: () => void;
  tabs: Array<SettingTab>;

  opacity: number;
  onOpacityChange: (o: number) => unknown;

  tabIndex: number;
  onTabIndexChange: (i: number) => unknown;
}

const MIN_OPACITY = 25;
const MAX_OPACITY = 100;

export function BaseSettingsModal(props: SettingsProps) {
  const { onClose, tabs, opacity, onOpacityChange, tabIndex, onTabIndexChange } = props;

  const handleTabChange = (event: any, newValue: any) => {
    onTabIndexChange(newValue);
  };

  const displayedTab = tabs[tabIndex].component;

  return (
    <Paper
      sx={{
        filter: `opacity(${opacity}%)`,
        height: "100%",
        display: "flex",
        flexDirection: "column",
        position: "relative",
      }}
      elevation={2}
    >
      <Stack sx={{ py: 1, px: 2, alignItems: "center" }} direction={"row"} gap={1}>
        <SettingsIcon />
        <Typography fontWeight={"bolder"}>Settings</Typography>
        <Box flexGrow={1} />
        <IconButton onClick={onClose}>
          <Close />
        </IconButton>
      </Stack>
      <Divider />
      <Stack direction={"row"} sx={{ flexGrow: 1, overflow: "auto" }}>
        {/*TODO: Holy moly, the CSS here needs to be changed a bit*/}
        <Tabs
          orientation="vertical"
          variant="scrollable"
          value={tabIndex}
          onChange={handleTabChange}
          sx={{ borderRight: 1, borderColor: "divider", position: "absolute" }}
        >
          {tabs.map(({ label }, index) => (
            <Tab label={label} key={index} tabIndex={index} sx={{ textTransform: "none" }} />
          ))}
        </Tabs>
        {/*TODO: For example this should not (?) be hardcoded */}
        <Box sx={{ marginLeft: "90px" }}>{displayedTab}</Box>
      </Stack>
      <Divider />
      <Stack sx={{ px: 2, py: 1, flexDirection: "row", alignItems: "center" }}>
        <PromotionFooter />
        <Box flexGrow={1} />
        <Stack direction={"row"} alignItems={"center"} gap={2}>
          <IconButton onClick={() => onOpacityChange(MIN_OPACITY)}>
            <VisibilityOff />
          </IconButton>
          <Slider
            value={opacity}
            onChange={(_, v) => onOpacityChange(v as number)}
            step={5}
            min={MIN_OPACITY}
            max={MAX_OPACITY}
            valueLabelFormat={(value: number) => `${value}%`}
            sx={{ width: "12em" }}
            valueLabelDisplay={"auto"}
          />
          <IconButton onClick={() => onOpacityChange(MAX_OPACITY)}>
            <Visibility />
          </IconButton>
        </Stack>
      </Stack>
    </Paper>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/GameCanvas.tsx
================================================
import React, { useEffect, useRef } from "react";
import { useAnalysisApp } from "../../providers/TheaterProvider";
import { Box, CircularProgress, IconButton, Stack, Tooltip, Typography } from "@mui/material";
import { useObservable } from "rxjs-hooks";
import { LightningBoltIcon } from "@heroicons/react/solid";
import InfoIcon from "@mui/icons-material/Info";
import { ignoreFocus } from "../../utils/focus";
import CloseIcon from "@mui/icons-material/Close";

function EmptyState() {
  return (
    <Stack gap={2} alignItems={"center"}>
      <Stack gap={1} alignItems={"center"}>
        <InfoIcon sx={{ height: "2em", width: "2em" }} />
        <Typography>No replay loaded</Typography>
      </Stack>
      <Stack gap={1} alignItems={"center"} direction={"row"}>
        <Box component={LightningBoltIcon} sx={{ height: "1em", color: "text.secondary" }} />
        <Typography color={"text.secondary"}>
          In osu! press F2 while being at a score/fail screen to load the replay
        </Typography>
      </Stack>
      <Stack gap={1} alignItems={"center"} direction={"row"}>
        <Box component={LightningBoltIcon} sx={{ height: "1em", color: "text.secondary" }} />
        <Typography color={"text.secondary"}>
          You can also load a replay with the menu action "File &gt; Open Replay (Ctrl+O)"
        </Typography>
      </Stack>
    </Stack>
  );
}

export const GameCanvas = () => {
  const canvas = useRef<HTMLCanvasElement | null>(null);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const analysisApp = useAnalysisApp();
  const { status } = useObservable(() => analysisApp.scenarioManager.scenario$, { status: "DONE" });

  const showOverlay = status !== "DONE";

  useEffect(() => {
    if (containerRef.current) {
      containerRef.current.append(analysisApp.stats());
    }
  }, [analysisApp]);
  useEffect(() => {
    if (status === "INIT") {
      analysisApp.stats().hidden = true;
    } else {
      analysisApp.stats().hidden = false;
    }
  }, [status, analysisApp]);

  useEffect(() => {
    if (canvas.current) {
      console.log("Initializing renderer to the canvas");
      analysisApp.onEnter(canvas.current);
    }
    return () => analysisApp.onHide();
  }, [analysisApp]);

  return (
    <Box ref={containerRef} sx={{ borderRadius: 2, overflow: "hidden", position: "relative", flex: 1 }}>
      {status === "DONE" && (
        <Tooltip title={"Close replay"}>
          <IconButton
            sx={{ position: "absolute", right: "0", top: "0" }}
            onClick={() => analysisApp.scenarioManager.clearReplay()}
            onFocus={ignoreFocus}
          >
            <CloseIcon />
          </IconButton>
        </Tooltip>
      )}
      <canvas
        style={{
          width: "100%",
          height: "100%",
          // , pointerEvents: "none"
        }}
        ref={canvas}
        // This does not work
      />
      {/*Overlay*/}
      {showOverlay && (
        <Box
          sx={{
            position: "absolute",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            top: "0",
            left: "0",
            height: "100%",
            width: "100%",
            backgroundColor: "rgba(0,0,0,0.7)",
          }}
        >
          {status === "INIT" && <EmptyState />}
          {status === "LOADING" && <CircularProgress />}
          {status === "ERROR" && <Typography>Something wrong happened...</Typography>}
        </Box>
      )}
    </Box>
  );
};


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/HelpModal.stories.tsx
================================================
import { Meta, Story } from "@storybook/react";
import { HelpBox } from "./HelpModal";

export default {
  component: HelpBox,
  title: "HelpModal",
  argTypes: {
    onClose: { action: "onClose executed!" },
  },
} as Meta;

const Template: Story<void> = (args) => <HelpBox onClose={() => {}} />;

export const Primary = Template.bind({});


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/HelpModal.tsx
================================================
import React from "react";
import styled from "styled-components";
import { Box, Divider, IconButton, Modal, Paper, Stack, Typography } from "@mui/material";
import { Close, Help } from "@mui/icons-material";
import { PromotionFooter } from "./BaseDialog";

export function Key({ text }: { text: string }) {
  return (
    <Box
      component={"span"}
      sx={{
        backgroundColor: "hsl(0,2%,44%)",
        fontFamily: "monospace",
        borderStyle: "solid",
        borderRadius: 1,
        borderWidth: "0px 4px 4px 0",
        // borderBottomWidth: 2,
        // borderRightWidth: 4,
        borderColor: "hsl(0,2%,20%)",
        paddingLeft: 1,
        paddingRight: 1,
      }}
    >
      {text}
    </Box>
    // <span className={"bg-gray-300 font-mono text-gray-800 rounded border-b-4 border-r-4 border-gray-600 pl-1 pr-1"}>
    //   {text}
    // </span>
  );
}

const ShortcutBox = styled.div`
  display: grid;
  grid-template-columns: max-content 1fr;
  grid-column-gap: 1em;
  grid-row-gap: 0.5em;
  align-items: center;
  justify-content: center;
`;

const leftArrowKey = "←";
const rightArrowKey = "→";
const upArrowKey = "↑";
const downArrowKey = "↓";

const leftKeys = [leftArrowKey, "a"];
const rightKeys = [rightArrowKey, "d"];

function KeyBindings({ separator = "+", keys, inline }: { separator?: string; keys: string[]; inline?: boolean }) {
  return (
    <div className={inline ? "inline" : ""}>
      {keys.map((k, i) => (
        <React.Fragment key={i}>
          <Key text={k} />
          {i + 1 < keys.length && ` ${separator} `}
        </React.Fragment>
      ))}
    </div>
  );
}

function Title({ children }: any) {
  return <Typography variant={"h6"}>{children}</Typography>;
}

// Which one?
const orSeparator = " or ";

// const orSeparator = " , ";

function PlaybarNavigationShortcuts() {
  return (
    <Stack sx={{ py: 2 }} flexDirection={"column"} gap={1}>
      <Title>Shortcuts</Title>
      <ShortcutBox>
        <KeyBindings keys={["Spacebar"]} /> <span>Start / Pause</span>
        <KeyBindings separator={orSeparator} keys={[upArrowKey, "w"]} /> <span>Increase speed</span>
        <KeyBindings separator={orSeparator} keys={[downArrowKey, "s"]} /> <span>Decrease speed</span>
        <KeyBindings separator={orSeparator} keys={leftKeys} /> <span>Small jump back</span>
        <KeyBindings separator={orSeparator} keys={rightKeys} /> <span>Small jump forward</span>
        {/*<div>*/}
        {/*  <KeyBindings keys={["Ctrl"]} inline /> + <KeyBindings separator={orSeparator} keys={leftKeys} inline />*/}
        {/*</div>*/}
        <KeyBindings separator={"+"} keys={["Ctrl", leftArrowKey]} />
        <span>Micro jump back</span>
        {/*<div>*/}
        {/*  <KeyBindings keys={["Ctrl"]} inline /> + <KeyBindings separator={orSeparator} keys={rightKeys} inline />*/}
        {/*</div>*/}
        <KeyBindings separator={"+"} keys={["Ctrl", rightArrowKey]} />
        <span>Micro jump forward</span>
        {/*<div>*/}
        {/*  <KeyBindings keys={["Shift"]} inline /> + <KeyBindings separator={orSeparator} keys={leftKeys} inline />*/}
        {/*</div>*/}
        <KeyBindings separator={"+"} keys={["Shift", leftArrowKey]} />
        <span>Large jump back</span>
        {/*<div>*/}
        {/*  <KeyBindings keys={["Shift"]} inline /> + <KeyBindings separator={orSeparator} keys={rightKeys} inline />*/}
        {/*</div>*/}
        <KeyBindings separator={"+"} keys={["Shift", rightArrowKey]} />
        <span>Large jump forward</span>
        <KeyBindings keys={["f"]} /> <span>Toggle hidden</span>
      </ShortcutBox>
    </Stack>
  );
}

interface HelpModalProps {
  isOpen: boolean;
  onClose: () => void;
}

export function HelpBox(props: Pick<HelpModalProps, "onClose">) {
  const { onClose } = props;
  return (
    <Paper sx={{ px: 2, py: 2, display: "flex", flexDirection: "column" }}>
      {/*MenuBar could be reused*/}
      <Stack sx={{ alignItems: "center" }} direction={"row"} gap={1}>
        <Help />
        <Typography fontWeight={"bolder"}>Help</Typography>
        <Box flexGrow={1} />
        <IconButton onClick={onClose}>
          <Close />
        </IconButton>
      </Stack>
      <Divider />

      <PlaybarNavigationShortcuts />
      {/*<OtherResources />*/}
      {/*Footer*/}
      <Divider />
      <Stack sx={{ paddingTop: 1 }}>
        <PromotionFooter />
      </Stack>
    </Paper>
  );
}

export function HelpModalDialog(props: HelpModalProps) {
  const { isOpen, onClose } = props;

  return (
    <Modal open={isOpen} onClose={onClose}>
      <Box
        sx={{
          position: "absolute",
          top: "50%",
          left: "50%",
          transform: "translate(-50%, -50%)",
          minWidth: 600,
          // maxWidth: 700,
          maxWidth: "100%",
          // maxHeight: 600,
          maxHeight: "100%",
        }}
      >
        <HelpBox onClose={onClose} />
      </Box>
    </Modal>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/PlayBar.tsx
================================================
import {
  Box,
  Button,
  Divider,
  IconButton,
  ListItemIcon,
  ListItemText,
  Menu,
  MenuItem,
  MenuList,
  Popover,
  Stack,
  Tooltip,
  Typography,
} from "@mui/material";
import {
  Help,
  MoreVert,
  PauseCircle,
  PhotoCamera,
  PlayCircle,
  Settings,
  VolumeOff,
  VolumeUp,
} from "@mui/icons-material";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { BaseAudioSettingsPanel } from "./BaseAudioSettingsPanel";
import { BaseGameTimeSlider } from "./BaseGameTimeSlider";
import { useGameClockControls, useGameClockTime } from "../../hooks/game-clock";
import { formatGameTime } from "@osujs/math";
import { useAudioSettings, useAudioSettingsService } from "../../hooks/audio";
import { useModControls } from "../../hooks/mods";
import modHiddenImg from "../../../assets/mod_hidden.png";
import { ALLOWED_SPEEDS, PlaybarColors } from "../../utils/constants";

import { useSettingsModalContext } from "../../providers/SettingsProvider";
import { ReplayAnalysisEvent } from "@osujs/core";
import { useObservable } from "rxjs-hooks";
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
import { HelpModalDialog } from "./HelpModal";
import { BaseCurrentTime, GameCurrentTimeHandle } from "./BaseCurrentTime";
import { ignoreFocus } from "../../utils/focus";
import { useAnalysisApp, useCommonManagers } from "../../providers/TheaterProvider";
import { DEFAULT_PLAY_BAR_SETTINGS } from "../../services/common/playbar";

const centerUp = {
  anchorOrigin: {
    vertical: "top",
    horizontal: "center",
  },
  transformOrigin: {
    vertical: "bottom",
    horizontal: "center",
  },
};

function MoreMenu() {
  const [anchorEl, setAnchorEl] = useState(null);
  const open = Boolean(anchorEl);
  const handleClick = (event: any) => {
    setAnchorEl(event.currentTarget);
  };
  const handleClose = () => {
    setAnchorEl(null);
  };

  const analyzer = useAnalysisApp();
  const handleTakeScreenshot = () => {
    analyzer.screenshotTaker.takeScreenshot();
    handleClose();
  };

  const [helpOpen, setHelpOpen] = useState(false);

  const handleOpenHelp = () => {
    setHelpOpen(true);
    handleClose();
  };

  return (
    <>
      <HelpModalDialog isOpen={helpOpen} onClose={() => setHelpOpen(false)} />
      <IconButton
        aria-label="more"
        id="long-button"
        aria-controls="long-menu"
        // aria-expanded={open ? "true" : undefined}
        aria-haspopup="true"
        onClick={handleClick}
        onFocus={ignoreFocus}
      >
        <MoreVert />
      </IconButton>
      <Menu
        open={open}
        onClose={handleClose}
        anchorEl={anchorEl}
        anchorOrigin={{
          vertical: "top",
          horizontal: "center",
        }}
        transformOrigin={{
          vertical: "bottom",
          horizontal: "center",
        }}
      >
        <MenuItem onClick={handleTakeScreenshot}>
          <ListItemIcon>
            <PhotoCamera />
          </ListItemIcon>
          <ListItemText>Take Screenshot</ListItemText>
        </MenuItem>
        <MenuItem onClick={handleOpenHelp}>
          <ListItemIcon>
            <Help />
          </ListItemIcon>
          <ListItemText>Help</ListItemText>
        </MenuItem>
      </Menu>
    </>
  );
}

function AudioButton() {
  const [anchorEl, setAnchorEl] = useState(null);
  const open = Boolean(anchorEl);
  const handlePopOverOpen = (event: any) => {
    setAnchorEl(event.currentTarget);
  };
  const handleClose = () => {
    setAnchorEl(null);
  };
  const { volume, muted } = useAudioSettings();
  const service = useAudioSettingsService();

  const handleClick = () => {
    service.toggleMuted();
  };
  return (
    <>
      <IconButton onClick={handlePopOverOpen}>{muted ? <VolumeOff /> : <VolumeUp />}</IconButton>
      <Popover
        open={open}
        anchorEl={anchorEl}
        onClose={handleClose}
        anchorOrigin={{
          vertical: "top",
          horizontal: "center",
        }}
        transformOrigin={{
          vertical: "bottom",
          horizontal: "center",
        }}
      >
        <Box width={256}>
          <BaseAudioSettingsPanel
            master={volume.master}
            music={volume.music}
            /* Currently it's disabled */
            effects={0}
            onMutedChange={(x) => service.setMuted(x)}
            onMasterChange={(x) => service.setMasterVolume(x)}
            onMusicChange={(x) => service.setMusicVolume(x)}
            onEffectsChange={(x) => service.setEffectsVolume(x)}
          />
        </Box>
      </Popover>
    </>
  );
}

// Connected
function PlayButton() {
  const { isPlaying, toggleClock } = useGameClockControls();
  const Icon = !isPlaying ? PlayCircle : PauseCircle;

  return (
    <IconButton onClick={toggleClock} onFocus={ignoreFocus}>
      <Icon fontSize={"large"} />
    </IconButton>
  );
}

// https://css-tricks.com/using-requestanimationframe-with-react-hooks/
const timeAnimateFPS = 30;

function CurrentTime() {
  const analyzer = useAnalysisApp();
  const requestRef = useRef<number>(0);
  const timeRef = useRef<GameCurrentTimeHandle>(null);

  // const animate = () => {};

  useEffect(() => {
    // requestRef.current = requestAnimationFrame(animate);
    let last = -1;
    const requiredElapsed = 1000 / timeAnimateFPS;

    function animate(currentTimestamp: number) {
      const elapsed = currentTimestamp - last;
      if (elapsed > requiredElapsed) {
        if (timeRef.current) timeRef.current.updateTime(analyzer.gameClock.timeElapsedInMs);
        last = currentTimestamp;
      }
      requestRef.current = requestAnimationFrame(animate);
    }

    requestRef.current = requestAnimationFrame(animate);
    return () => {
      if (requestRef.current) cancelAnimationFrame(requestRef.current);
    };
  }, [analyzer]);
  return (
    // We MUST fix the width because the font is not monospace e.g. "111" is thinner than "000"
    // Also if the duration is more than an hour there will also be a slight shift
    <Box sx={{ width: "7em" }}>
      <BaseCurrentTime ref={timeRef} />
    </Box>
  );
}

function groupTimings(events: ReplayAnalysisEvent[]) {
  const missTimings: number[] = [];
  const mehTimings: number[] = [];
  const okTimings: number[] = [];
  const sliderBreakTimings: number[] = [];

  events.forEach((e) => {
    switch (e.type) {
      case "HitObjectJudgement":
        // TODO: for lazer style, this needs some rework
        if (e.isSliderHead) {
          if (e.verdict === "MISS") sliderBreakTimings.push(e.time);
          return;
        } else {
          if (e.verdict === "MISS") missTimings.push(e.time);
          if (e.verdict === "MEH") mehTimings.push(e.time);
          if (e.verdict === "OK") okTimings.push(e.time);
        }
        // if(e.verdict === "GREAT" && show300s) events.push(); // Not sure if this will ever be implemented
        break;
      case "CheckpointJudgement":
        if (!e.hit && !e.isLastTick) sliderBreakTimings.push(e.time);
        break;
      case "UnnecessaryClick":
        // TODO
        break;
    }
  });
  return { missTimings, mehTimings, okTimings, sliderBreakTimings };
}

function GameTimeSlider() {
  // TODO: Depending on if replay is loaded and settings
  const backgroundEnable = true;
  const currentTime = useGameClockTime(15);
  const { seekTo, duration } = useGameClockControls();
  const { gameSimulator } = useAnalysisApp();
  const { playbarSettingsStore } = useCommonManagers();
  const replayEvents = useObservable(() => gameSimulator.replayEvents$, []);
  const difficulties = useObservable(() => gameSimulator.difficulties$, []);
  const playbarSettings = useObservable(() => playbarSettingsStore.settings$, DEFAULT_PLAY_BAR_SETTINGS);

  const events = useMemo(() => {
    const { sliderBreakTimings, missTimings, mehTimings, okTimings } = groupTimings(replayEvents);
    return [
      { color: PlaybarColors.MISS, timings: missTimings, tooltip: "Misses" },
      { color: PlaybarColors.SLIDER_BREAK, timings: sliderBreakTimings, tooltip: "Sliderbreaks" },
      { color: PlaybarColors.MEH, timings: mehTimings, tooltip: "50s" },
      { color: PlaybarColors.OK, timings: okTimings, tooltip: "100s" },
    ];
  }, [replayEvents]);

  return (
    <BaseGameTimeSlider
      backgroundEnable={backgroundEnable}
      duration={duration}
      currentTime={currentTime}
      onChange={seekTo}
      events={events}
      difficulties={playbarSettings.difficultyGraphEnabled ? difficulties : []}
    />
  );
}

function Duration() {
  const { duration } = useGameClockControls();
  const f = formatGameTime(duration);

  return <Typography>{f}</Typography>;
}

function HiddenButton() {
  const { setHidden, hidden: hiddenEnabled } = useModControls();
  const handleClick = useCallback(() => setHidden(!hiddenEnabled), [hiddenEnabled, setHidden]);

  return (
    <Button onFocus={ignoreFocus} onClick={handleClick} sx={{ px: 0 }}>
      <img
        src={modHiddenImg}
        alt={"ModHidden"}
        style={{ filter: `grayscale(${hiddenEnabled ? "0%" : "100%"})`, width: "60%" }}
      />
    </Button>
  );
}

function SettingsButton() {
  const { onSettingsModalOpenChange } = useSettingsModalContext();
  return (
    <IconButton onClick={() => onSettingsModalOpenChange(true)} onFocus={ignoreFocus}>
      <Settings />
    </IconButton>
  );
}

interface BaseSpeedButtonProps {
  value: number;
  onChange: (value: number) => any;
}

const speedLabels: Record<number, string> = { 0.75: "HT", 1.5: "DT" } as const;

function BaseSpeedButton(props: BaseSpeedButtonProps) {
  const { value, onChange } = props;

  const [anchorEl, setAnchorEl] = useState(null);
  const open = Boolean(anchorEl);
  const handleClick = (event: any) => {
    setAnchorEl(event.currentTarget);
  };
  const handleClose = () => {
    setAnchorEl(null);
  };
  const formatSpeed = (s: number) => `${s}x`;

  // Floating point issues?

  return (
    <>
      <Button
        sx={{
          color: "text.primary",
          textTransform: "none",
          fontSize: "1em",
          // minWidth: "0",
          // px: 2,
        }}
        size={"small"}
        onClick={handleClick}
        onFocus={ignoreFocus}
      >
        {formatSpeed(value)}
        {/*<Typography>{formatSpeed(value)}</Typography>*/}
      </Button>
      <Menu
        open={open}
        onClose={handleClose}
        anchorEl={anchorEl}
        anchorOrigin={{
          vertical: "top",
          horizontal: "center",
        }}
        transformOrigin={{
          vertical: "bottom",
          horizontal: "center",
        }}
      >
        <MenuList>
          {ALLOWED_SPEEDS.map((s) => (
            <MenuItem
              key={s}
              onClick={() => {
                onChange(s);
                handleClose();
              }}
              sx={{ width: "120px", maxWidth: "100%" }}
            >
              <ListItemText>{formatSpeed(s)}</ListItemText>
              <Typography variant="body2" color="text.secondary">
                {speedLabels[s] ?? ""}
              </Typography>
            </MenuItem>
          ))}
        </MenuList>
      </Menu>
    </>
  );
}

function SpeedButton() {
  const { speed, setSpeed } = useGameClockControls();
  return (
    // <Box sx={{ display: "flex", justifyContent: "center" }}>
    <BaseSpeedButton value={speed} onChange={setSpeed} />
    // </Box>
  );
}

function RecordButton() {
  // TODO: Probably stop at a certain time otherwise the program might crash due to memory issue
  const { clipRecorder } = useAnalysisApp();
  const recordingSince = useObservable(() => clipRecorder.recordingSince$, 0);

  const isRecording = recordingSince > 0;

  const recordingTime = "3:00";

  const handleClick = useCallback(() => {
    if (isRecording) {
      clipRecorder.stopRecording();
    } else {
      clipRecorder.startRecording();
    }
  }, [isRecording, clipRecorder]);

  return (
    <Tooltip title={"Start recording a clip"}>
      <IconButton onClick={handleClick}>
        <FiberManualRecordIcon
          sx={{
            color: isRecording ? "red" : "text.primary",
          }}
        />
      </IconButton>
    </Tooltip>
  );
}

const VerticalDivider = () => <Divider orientation={"vertical"} sx={{ height: "80%" }} />;

export function PlayBar() {
  return (
    <Stack height={64} gap={1} p={2} direction={"row"} alignItems={"center"}>
      <PlayButton />
      <CurrentTime />
      <GameTimeSlider />
      <Duration />
      <VerticalDivider />
      <Stack direction={"row"} alignItems={"center"} justifyContent={"center"}>
        <AudioButton />
        <SpeedButton />
        <HiddenButton />
        {/*<RecordButton />*/}
        <SettingsButton />
        <MoreMenu />
      </Stack>
    </Stack>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/analyzer/SettingsModal.tsx
================================================
import { useSettingsModalContext } from "../../providers/SettingsProvider";
import {
  Autocomplete,
  Box,
  Button,
  FormControlLabel,
  FormGroup,
  Modal,
  Paper,
  Slider,
  Stack,
  Switch,
  TextField,
  Typography,
} from "@mui/material";
import { BaseSettingsModal } from "./BaseSettingsModal";
import { useCommonManagers } from "../../providers/TheaterProvider";
import { useCallback, useEffect, useMemo } from "react";
import { useObservable } from "rxjs-hooks";
import { DEFAULT_HIT_ERROR_BAR_SETTINGS } from "../../services/common/hit-error-bar";
import { DEFAULT_PLAY_BAR_SETTINGS } from "../../services/common/playbar";
import { DEFAULT_OSU_SKIN_ID, DEFAULT_REWIND_SKIN_ID, SkinId, SkinSource, stringToSkinId } from "../../model/SkinId";
import { DEFAULT_BEATMAP_RENDER_SETTINGS } from "../../services/common/beatmap-render";
import { DEFAULT_SKIN_SETTINGS } from "../../services/common/skin";
import { DEFAULT_REPLAY_CURSOR_SETTINGS } from "../../services/common/replay-cursor";
import { DEFAULT_ANALYSIS_CURSOR_SETTINGS } from "../../services/analysis/analysis-cursor";
import { frontendAPI } from "../../api";

const sourceName: Record<SkinSource, string> = {
  osu: "osu!/Skins Folder",
  rewind: "Rewind",
};

const formatToPercent = (value: number) => `${value}%`;

function BeatmapBackgroundSettings() {
  const theater = useCommonManagers();
  const { beatmapBackgroundSettingsStore } = theater;
  const settings = useObservable(() => beatmapBackgroundSettingsStore.settings$, { blur: 0, enabled: false, dim: 0 });
  return (
    <Paper sx={{ boxShadow: "none", p: 2 }}>
      <Stack gap={1}>
        <Typography variant={"h6"}>Beatmap Background</Typography>
        <Stack>
          <Typography>Blur</Typography>
          <Slider
            value={Math.round(settings.blur * 100)}
            onChange={(_, v) => beatmapBackgroundSettingsStore.setBlur((v as number) / 100)}
            valueLabelDisplay={"auto"}
            valueLabelFormat={formatToPercent}
          />
          <Typography>Dim</Typography>
          <Slider
            value={Math.round(settings.dim * 100)}
            onChange={(_, v) => beatmapBackgroundSettingsStore.setDim((v as number) / 100)}
            valueLabelDisplay={"auto"}
            valueLabelFormat={formatToPercent}
          />
        </Stack>
      </Stack>
    </Paper>
  );
}

function BeatmapRenderSettings() {
  const { beatmapRenderSettingsStore } = useCommonManagers();
  const settings = useObservable(() => beatmapRenderSettingsStore.settings$, DEFAULT_BEATMAP_RENDER_SETTINGS);

  return (
    <Stack>
      <FormGroup>
        <FormControlLabel
          control={
            <Switch
              checked={settings.sliderDevMode}
              onChange={(event) => beatmapRenderSettingsStore.setSliderDevMode(event.target.checked)}
            />
          }
          label={"Slider Dev Mode"}
        />
      </FormGroup>
      {/*  draw slider ends*/}
    </Stack>
  );
}

function AnalysisCursorSettingsSection() {
  const { analysisCursorSettingsStore } = useCommonManagers();
  const settings = useObservable(() => analysisCursorSettingsStore.settings$, DEFAULT_ANALYSIS_CURSOR_SETTINGS);

  return (
    <Paper sx={{ boxShadow: "none", p: 2 }}>
      <Stack gap={1}>
        <Typography variant={"h6"}>Analysis Cursor</Typography>
        <FormGroup>
          <FormControlLabel
            control={
              <Switch
                checked={settings.enabled}
                onChange={(event) => analysisCursorSettingsStore.setEnabled(event.target.checked)}
              />
            }
            label={"Enabled"}
          />
        </FormGroup>
      </Stack>
    </Paper>
  );
}

function ReplayCursorSettingsSection() {
  const { replayCursorSettingsStore } = useCommonManagers();
  const settings = useObservable(() => replayCursorSettingsStore.settings$, DEFAULT_REPLAY_CURSOR_SETTINGS);

  return (
    <Paper sx={{ boxShadow: "none", p: 2 }}>
      <Stack gap={1}>
        <Typography variant={"h6"}>Replay Cursor</Typography>
        <FormGroup>
          <FormControlLabel
            control={
              <Switch
                checked={settings.enabled}
                onChange={(event) =>
                  replayCursorSettingsStore.changeSettings((s) => (s.enabled = event.target.checked))
                }
              />
            }
            label={"Enabled"}
          />
        </FormGroup>
        <FormGroup>
          <FormControlLabel
            disabled={!settings.enabled}
            control={
              <Switch
                checked={settings.smoothCursorTrail}
                onChange={(event) =>
                  replayCursorSettingsStore.changeSettings((s) => (s.smoothCursorTrail = event.target.checked))
                }
              />
            }
            label={"Smooth Cursor Trail"}
          />
        </FormGroup>
        <Typography>Scale</Typography>
        <Slider
          value={Math.round(settings.scale * 100)}
          valueLabelFormat={formatToPercent}
          disabled={!settings.enabled}
          min={10}
          max={200}
          onChange={(_, v) => replayCursorSettingsStore.changeSettings((s) => (s.scale = (v as number) / 100))}
          valueLabelDisplay={"auto"}
        />
      </Stack>
    </Paper>
  );
}

function HitErrorBarSettingsSection() {
  const { hitErrorBarSettingsStore } = useCommonManagers();
  const settings = useObservable(() => hitErrorBarSettingsStore.settings$, DEFAULT_HIT_ERROR_BAR_SETTINGS);
  return (
    <Paper elevation={1} sx={{ boxShadow: "none", p: 2 }}>
      <Stack>
        {/*TODO: Enabled*/}
        <Typography>Hit Error Bar Scaling</Typography>
        <Slider
          value={Math.round(settings.scale * 100)}
          valueLabelFormat={formatToPercent}
          max={300}
          onChange={(_, v) => hitErrorBarSettingsStore.changeSettings((s) => (s.scale = (v as number) / 100))}
          valueLabelDisplay={"auto"}
        />
      </Stack>
    </Paper>
  );
}

function PlaybarSettingsSection() {
  const { playbarSettingsStore } = useCommonManagers();
  const settings = useObservable(() => playbarSettingsStore.settings$, DEFAULT_PLAY_BAR_SETTINGS);

  return (
    <Paper elevation={1} sx={{ boxShadow: "none", p: 2 }}>
      <Stack gap={1}>
        <Typography variant={"h6"}>Playbar</Typography>
        <FormGroup>
          <FormControlLabel
            control={
              <Switch
                checked={settings.difficultyGraphEnabled}
                onChange={(event) =>
                  playbarSettingsStore.changeSettings((s) => (s.difficultyGraphEnabled = event.target.checked))
                }
              />
            }
            label={"Show difficulty graph"}
          />
        </FormGroup>
      </Stack>
    </Paper>
  );
}

function ResetAllSettingsSection() {
  const resetSettings = useCallback(() => {
    localStorage.clear();
    frontendAPI.reboot();
  }, []);
  return (
    <Button variant={"contained"} onClick={resetSettings}>
      Reset All Settings and Restart
    </Button>
  );
}

function OtherSettings() {
  return (
    <Stack p={2} gap={1}>
      <ResetAllSettingsSection />
    </Stack>
  );
}

function GameplaySettings() {
  return (
    <Stack p={2} gap={1}>
      <ReplayCursorSettingsSection />
      <AnalysisCursorSettingsSection />
      <HitErrorBarSettingsSection />
      <BeatmapBackgroundSettings />
      <PlaybarSettingsSection />
      <BeatmapRenderSettings />
    </Stack>
  );
}

function SkinsSettings() {
  // TODO: Button for synchronizing skin list again

  const theater = useCommonManagers();

  const { preferredSkinId } = useObservable(() => theater.skinSettingsStore.settings$, DEFAULT_SKIN_SETTINGS);
  const chosenSkinId = stringToSkinId(preferredSkinId);
  const skins = useObservable(() => theater.skinManager.skinList$, []);

  const skinOptions: SkinId[] = useMemo(
    () => [DEFAULT_OSU_SKIN_ID, DEFAULT_REWIND_SKIN_ID].concat(skins.map((name) => ({ source: "osu", name }))),
    [skins],
  );

  useEffect(() => {
    theater.skinManager.loadSkinList();
  }, [theater]);

  // TODO:

  const handleSkinChange = useCallback(
    (skinId: SkinId) => {
      (async function () {
        try {
          await theater.skinManager.loadSkin(skinId);
        } catch (e) {
          // Show some error dialog
          console.error(`Could not load skin ${skinId}`);
        }
      })();
      // TODO: Error handling
    },
    [theater],
  );

  return (
    <Box sx={{ p: 2 }}>
      <Autocomplete
        id="skin-selection-demo"
        // TODO: Make sure skinIds are sorted
        options={skinOptions}
        groupBy={(option: SkinId) => sourceName[option.source]}
        value={chosenSkinId}
        onChange={(event, newValue) => {
          if (newValue) {
            handleSkinChange(newValue as SkinId);
          }
        }}
        getOptionLabel={(option: SkinId) => option.name}
        sx={{ width: 300 }}
        renderInput={(params) => <TextField {...params} label="Skin" />}
        isOptionEqualToValue={(option, value) => option.name === value.name && option.source === value.source}
      />
    </Box>
  );
}

export function SettingsModal() {
  const { onSettingsModalOpenChange, settingsModalOpen, opacity, onTabIndexChange, onOpacityChange, tabIndex } =
    useSettingsModalContext();
  const onClose = () => onSettingsModalOpenChange(false);

  return (
    <Modal
      open={settingsModalOpen}
      onClose={onClose}
      hideBackdrop={false}
      sx={{
        // We reduce the default backdrop from 0.5 alpha to 0.1 in order for the user to better see the background
        // behind the modal
        "& .MuiBackdrop-root": {
          backgroundColor: "rgba(0,0,0,0.1)",
        },
      }}
    >
      <Box
        sx={{
          position: "absolute",
          top: "50%",
          left: "50%",
          transform: "translate(-50%, -50%)",
          width: 800,
          maxWidth: "100%",
          height: 600,
          maxHeight: "100%",
        }}
      >
        <BaseSettingsModal
          opacity={opacity}
          tabIndex={tabIndex}
          onTabIndexChange={onTabIndexChange}
          onOpacityChange={onOpacityChange}
          onClose={onClose}
          tabs={[
            { label: "Game", component: <GameplaySettings /> },
            {
              label: "Skins",
              component: <SkinsSettings />,
            },
            { label: "Other", component: <OtherSettings /> },
          ]}
        />
      </Box>
    </Modal>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/logo/RewindLogo.tsx
================================================
import { Stack, Typography } from "@mui/material";
import { FastRewind } from "@mui/icons-material";

export const RewindLogo = () => (
  <Stack alignItems={"center"}>
    <FastRewind fontSize={"large"} />
    <Typography fontSize={"8px"} sx={{ userSelect: "none" }}>
      REWIND
    </Typography>
  </Stack>
);


================================================
FILE: apps/desktop/frontend/src/app/components/sidebar/LeftMenuSidebar.tsx
================================================
import { RewindLogo } from "../logo/RewindLogo";
import { Badge, Box, Divider, IconButton, Stack, Tooltip } from "@mui/material";
import { Home } from "@mui/icons-material";
import { FaMicroscope } from "react-icons/fa";
import React, { useCallback, useEffect, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../hooks/redux";
import UpdateIcon from "@mui/icons-material/Update";
import { setUpdateModalOpen } from "../../store/update/slice";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { ELECTRON_UPDATE_FLAG } from "../../utils/constants";
import { useAppInfo } from "../../hooks/app-info";

const tooltipPosition = {
  anchorOrigin: {
    vertical: "center",
    horizontal: "right",
  },
  transformOrigin: {
    vertical: "center",
    horizontal: "left",
  },
};

const repoOwner = "abstrakt8";
const repoName = "rewind";

const latestReleaseUrl = `https://github.com/${repoOwner}/${repoName}/releases/latest`;
const latestReleaseApi = `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`;

function useCheckForUpdate() {
  const { appVersion } = useAppInfo();
  const { newVersion } = useAppSelector((state) => state.updater);
  const [state, setState] = useState<{ hasNewUpdate: boolean; latestVersion: string }>({
    hasNewUpdate: false,
    latestVersion: "",
  });
  useEffect(() => {
    (async function () {
      const response = await fetch(latestReleaseApi);
      const json = await response.json();

      // Should be something like "v0.1.0"
      const tagName = json["tag_name"] as string;
      if (!tagName) {
        return;
      }
      // Removes the "v" prefix
      const latestVersion = tagName.substring(1);
      const hasNewUpdate = appVersion !== latestVersion;
      setState({ hasNewUpdate, latestVersion });
      console.log(
        `Current release: ${appVersion} and latest release: ${latestVersion}, therefore hasNewUpdate=${hasNewUpdate}`,
      );
    })();
  }, [appVersion]);

  if (ELECTRON_UPDATE_FLAG) {
    return { hasNewUpdate: newVersion !== appVersion, latestVersion: newVersion };
  } else {
    return state;
  }
}

export function LeftMenuSidebar() {
  const dispatch = useAppDispatch();
  const location = useLocation();
  const navigate = useNavigate();

  const state = useCheckForUpdate();

  const openUpdateModal = () => dispatch(setUpdateModalOpen(true));

  const onNewUpdateAvailableButtonClick = useCallback(() => {
    if (ELECTRON_UPDATE_FLAG) {
      openUpdateModal();
    } else {
      window.open(latestReleaseUrl);
    }
  }, []);
  const handleLinkClick = (to: string) => () => navigate(to);
  const buttonColor = (name: string) => (location.pathname.endsWith(name) ? "primary" : "default");

  return (
    <Stack
      sx={{
        width: (theme) => theme.spacing(10),
        paddingBottom: 2,
      }}
      gap={1}
      p={1}
      alignItems={"center"}
      component={"nav"}
    >
      <Box onClick={handleLinkClick("home")} sx={{ cursor: "pointer" }}>
        <RewindLogo />
      </Box>
      <Divider orientation={"horizontal"} sx={{ borderWidth: 1, width: "80%" }} />
      <Tooltip title={"Overview"} placement={"right"}>
        <Link to={"home"}>
          <IconButton color={buttonColor("/home")}>
            <Home />
          </IconButton>
        </Link>
      </Tooltip>
      <Tooltip title={"Analyzer"} placement={"right"}>
        <Link to={"analyzer"}>
          <IconButton
            // These are not centered
            color={buttonColor("/analyzer")}
          >
            <FaMicroscope height={"0.75em"} />
          </IconButton>
        </Link>
      </Tooltip>
      {/*Nothing*/}
      <Box flexGrow={1} />
      <Tooltip
        title={state.hasNewUpdate ? `New version ${state.latestVersion} available!` : `You are on the latest version`}
        placement={"right"}
      >
        <IconButton onClick={onNewUpdateAvailableButtonClick}>
          <Badge variant={"dot"} color={"error"} invisible={!state.hasNewUpdate}>
            <UpdateIcon />
          </Badge>
        </IconButton>
      </Tooltip>
    </Stack>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/components/update/UpdateModal.tsx
================================================
import { Box, Button, Divider, IconButton, LinearProgress, Link, Modal, Paper, Stack, Typography } from "@mui/material";
import { useAppDispatch, useAppSelector } from "../../hooks/redux";
import { setUpdateModalOpen } from "../../store/update/slice";
import { Close } from "@mui/icons-material";
import React from "react";
import { frontendAPI } from "../../api";
import { useAppInfo } from "../../hooks/app-info";

const units = ["bytes", "KB", "MB", "GB", "TB", "PB"];

function niceBytes(x: any) {
  let l = 0,
    n = parseInt(x, 10) || 0;
  while (n >= 1024 && ++l) {
    n = n / 1024;
  }
  return n.toFixed(n < 10 && l > 0 ? 1 : 0) + " " + units[l];
}

function versionUrl(version: string) {
  const repoOwner = "abstrakt8";
  const repoName = "rewind";
  // The version does not contain "v"
  return `https://github.com/${repoOwner}/${repoName}/releases/v${version}`;
}

export function UpdateModal() {
  const { modalOpen, newVersion, isDownloading, downloadedBytes, bytesPerSecond, downloadFinished, error, totalBytes } =
    useAppSelector((state) => state.updater);
  const dispatch = useAppDispatch();
  const handleClose = () => dispatch(setUpdateModalOpen(false));
  const { appVersion } = useAppInfo();

  const updateAvailable = newVersion !== null;
  const title = updateAvailable ? `Update available` : `Up to date`;

  const downloadProgress = (downloadedBytes / totalBytes) * 100;

  return (
    <Modal open={modalOpen} onClose={handleClose}>
      <Box
        sx={{
          position: "absolute",
          top: "50%",
          left: "50%",
          transform: "translate(-50%, -50%)",
          maxWidth: "100%",
          maxHeight: "100%",
        }}
      >
        <Paper elevation={1}>
          <Stack sx={{ px: 2, py: 1, alignItems: "center" }} direction={"row"} gap={1}>
            <Typography fontWeight={"bolder"}>{title}</Typography>
            <Box flexGrow={1} />
            <IconButton onClick={handleClose}>
              <Close />
            </IconButton>
          </Stack>
          <Divider />
          <Stack sx={{ px: 3, py: 2 }} direction={"row"} alignItems={"center"} gap={4}>
            {!updateAvailable && <Typography>You are on the latest version: {appVersion}</Typography>}
            {updateAvailable && (
              <Stack gap={2}>
                <Typography>
                  New Rewind version available:{" "}
                  <Link href={versionUrl(newVersion)} target={"_blank"} color={"text.secondary"}>
                    {newVersion}
                  </Link>
                </Typography>
                {isDownloading && (
                  <Typography>
                    {downloadFinished ? "Finished downloading!" : "Downloading..."}{" "}
                    <Typography variant={"caption"}>
                      {`${niceBytes(downloadedBytes)} / ${niceBytes(totalBytes)} (${downloadProgress.toFixed(2)} %)`}
                    </Typography>
                  </Typography>
                )}
                {isDownloading && <LinearProgress variant="determinate" value={downloadProgress} />}
                {downloadFinished && (
                  <Typography variant={"caption"}>Installation will happen when you close the application.</Typography>
                )}
                <Stack direction={"row-reverse"}>
                  {!downloadFinished && (
                    <Button
                      variant={"contained"}
                      onClick={() => frontendAPI.startDownloadingUpdate()}
                      disabled={isDownloading}
                    >
                      Download update
                    </Button>
                  )}
                  {downloadFinished && (
                    <Button variant={"contained"} onClick={() => frontendAPI.quitAndInstall()}>
                      Restart and install
                    </Button>
                  )}
                </Stack>
              </Stack>
            )}
          </Stack>
        </Paper>
      </Box>
    </Modal>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/hooks/app-info.ts
================================================
import { useCommonManagers } from "../providers/TheaterProvider";


export function useAppInfo() {
  const { appInfoService } = useCommonManagers();

  return {
    appVersion: appInfoService.version,
    platform: appInfoService.platform,
  };
}


================================================
FILE: apps/desktop/frontend/src/app/hooks/audio.ts
================================================
import { useCommonManagers } from "../providers/TheaterProvider";
import { useObservable } from "rxjs-hooks";
import { AudioSettings } from "../services/common/audio/settings";

export function useAudioSettingsService() {
  const theater = useCommonManagers();
  return theater.audioSettingsService;
}

const defaultSettings: AudioSettings = { volume: { effects: 0, master: 0, music: 0 }, muted: true };

export function useAudioSettings() {
  const audioSettingsService = useAudioSettingsService();
  return useObservable(() => audioSettingsService.settings$, defaultSettings);
}


================================================
FILE: apps/desktop/frontend/src/app/hooks/energy-saver.ts
================================================
// import { useStageContext } from "../components/StageProvider/StageProvider";

// export function useEnergySaver(enabled = true) {
//   const isVisible = usePageVisibility();
//   // const { stage } = useStageContext();
//   const { pauseClock } = useGameClockContext();
//
//   useEffect(() => {
//     if (!enabled) {
//       return;
//     }
//     if (!isVisible) {
//       pauseClock();
//       stage.stopTicker();
//     } else {
//       stage.startTicker();
//     }
//   }, [isVisible, stage, pauseClock, enabled]);
// }


================================================
FILE: apps/desktop/frontend/src/app/hooks/game-clock.ts
================================================
import { useCallback, useState } from "react";
import { useObservable } from "rxjs-hooks";
import { useAnalysisApp } from "../providers/TheaterProvider";
import { ALLOWED_SPEEDS } from "../utils/constants";
import { useInterval } from "./interval";

export function useGameClock() {
  const analyzer = useAnalysisApp();
  return analyzer.gameClock;
}

// TODO: FLOATING POINT EQUALITY ALERT
const speedIndex = (speed: number) => ALLOWED_SPEEDS.indexOf(speed);
const nextSpeed = (speed: number) => ALLOWED_SPEEDS[Math.min(ALLOWED_SPEEDS.length - 1, speedIndex(speed) + 1)];
const prevSpeed = (speed: number) => ALLOWED_SPEEDS[Math.max(0, speedIndex(speed) - 1)];

export function useGameClockControls() {
  const clock = useGameClock();

  const isPlaying = useObservable(() => clock.isPlaying$, false);
  const duration = useObservable(() => clock.durationInMs$, 0);
  const speed = useObservable(() => clock.speed$, 1.0);

  const toggleClock = useCallback(() => clock.toggle(), [clock]);
  const seekTo = useCallback((timeInMs: number) => clock.seekTo(timeInMs), [clock]);

  const setSpeed = useCallback((x: number) => clock.setSpeed(x), [clock]);
  const increaseSpeed = useCallback(() => clock.setSpeed(nextSpeed(clock.speed)), [clock]);
  const decreaseSpeed = useCallback(() => clock.setSpeed(prevSpeed(clock.speed)), [clock]);

  const seekForward = useCallback((timeInMs: number) => clock.seekTo(clock.timeElapsedInMs + timeInMs), [clock]);
  const seekBackward = useCallback((timeInMs: number) => clock.seekTo(clock.timeElapsedInMs - timeInMs), [clock]);

  return {
    isPlaying,
    duration,
    speed,
    // Actions
    setSpeed,
    increaseSpeed,
    decreaseSpeed,
    toggleClock,
    seekTo,
    seekForward,
    seekBackward,
  };
}

// 60FPS by default
export function useGameClockTime(fps = 60) {
  const gameClock = useGameClock();
  const [time, setTime] = useState(0);
  useInterval(() => {
    setTime(gameClock.timeElapsedInMs);
  }, 1000 / fps);
  return time;
}


================================================
FILE: apps/desktop/frontend/src/app/hooks/interval.ts
================================================
import { useEffect, useRef } from "react";

export function useInterval(callback: () => void, delay: number | null) {
  const savedCallback = useRef(callback);

  // Remember the latest callback if it changes.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    // Don't schedule if no delay is specified.
    if (delay === null) {
      // eslint-disable-next-line @typescript-eslint/no-empty-function
      return () => {};
    }

    const id = setInterval(() => savedCallback.current(), delay);

    return () => clearInterval(id);
  }, [delay]);
}


================================================
FILE: apps/desktop/frontend/src/app/hooks/mods.ts
================================================
import { useAnalysisApp } from "../providers/TheaterProvider";
import { useObservable } from "rxjs-hooks";
import { useCallback } from "react";

export function useModControls() {
  const { modSettingsService } = useAnalysisApp();

  const modSettings = useObservable(() => modSettingsService.modSettings$, { flashlight: false, hidden: false });
  const setHidden = useCallback((value: boolean) => modSettingsService.setHidden(value), [modSettingsService]);

  return { ...modSettings, setHidden };
}


================================================
FILE: apps/desktop/frontend/src/app/hooks/redux.ts
================================================
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../store";

export const useAppDispatch = () => useDispatch<AppDispatch>(); // Export a hook that can be reused to resolve types
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;


================================================
FILE: apps/desktop/frontend/src/app/hooks/shortcuts.ts
================================================
import { useHotkeys } from "react-hotkeys-hook";
import { useGameClockControls } from "./game-clock";
import { useModControls } from "./mods";

// TODO: Configurable

// These should stay constant or make them dynamic depending on gameClock speed
const microscopeJump = 1;
const frameJump = 16; // Assuming 16fps
const mediumJump = 1 * 1000;
const largeJump = 15 * 1000;

const leftKeys = ["a", "left"];
const rightKeys = ["d", "right"];

const upKeys = ["w", "up"];
const downKeys = ["s", "down"];

const generateKeyComboSimple = (keys: string[]) => keys.join(", ");
const generateKeyCombo = (modifier = "", keys: string[]) => keys.map((k) => `${modifier}+${k}`).join(", ");

// TODO: Get platform from AppInfo, so that we can also support MacOS (currently they are hardcoded for Windows/Linux)
export function useShortcuts() {
  const { toggleClock, seekBackward, seekForward, increaseSpeed, decreaseSpeed } = useGameClockControls();
  const { setHidden, hidden } = useModControls();

  useHotkeys(generateKeyComboSimple(upKeys), () => increaseSpeed(), [increaseSpeed]);
  useHotkeys(generateKeyComboSimple(downKeys), () => decreaseSpeed(), [decreaseSpeed]);
  useHotkeys("space", () => toggleClock(), [toggleClock]);

  useHotkeys(generateKeyCombo("shift", leftKeys), () => seekBackward(mediumJump), [seekBackward]);
  useHotkeys(generateKeyCombo("shift", rightKeys), () => seekForward(mediumJump), [seekForward]);
  useHotkeys(generateKeyCombo("ctrl", leftKeys), () => seekBackward(microscopeJump), [seekBackward]);
  useHotkeys(generateKeyCombo("ctrl", rightKeys), () => seekForward(microscopeJump), [seekForward]);
  useHotkeys(generateKeyComboSimple(leftKeys), () => seekBackward(frameJump), [seekBackward]);
  useHotkeys(generateKeyComboSimple(rightKeys), () => seekForward(frameJump), [seekForward]);

  // These have really bad collisions
  // useHotkeys(`alt+${leftKey}`, () => seekBackward(frameJump), [seekBackward]);
  // useHotkeys(`alt+${rightKey}`, () => seekForward(frameJump), [seekForward]);
  // useHotkeys(`ctrl+${leftKey}`, () => seekBackward(largeJump), [seekBackward]);
  // useHotkeys(`ctrl+${rightKey}`, () => seekForward(largeJump), [seekForward]);

  useHotkeys("f", () => setHidden(!hidden), [hidden, setHidden]);
}


================================================
FILE: apps/desktop/frontend/src/app/model/BlueprintInfo.ts
================================================
export interface BlueprintInfo {
  md5Hash: string;
  lastPlayed: Date;
  title: string;
  artist: string;
  creator: string;
  // This is assuming that they are in the folderName
  folderName: string;
  audioFileName: string;
  osuFileName: string;

  // [Events]
  bgFileName?: string; // Usually unknown unless .osu file is parsed
}


================================================
FILE: apps/desktop/frontend/src/app/model/OsuReplay.ts
================================================
// TODO: Rename this to replay or something
import { OsuClassicMod, ReplayFrame } from "@osujs/core";

export type OsuReplay = {
  md5hash: string;
  beatmapMd5: string;
  gameVersion: number;
  mods: OsuClassicMod[];
  player: string; // Could be useful to draw
  frames: ReplayFrame[];
};


================================================
FILE: apps/desktop/frontend/src/app/model/Skin.ts
================================================
import { rgbToInt } from "@osujs/math";
import { Texture } from "@pixi/core";
import {
  comboDigitFonts,
  defaultDigitFonts,
  generateDefaultSkinConfig,
  hitCircleDigitFonts,
  OsuSkinTextures,
  SkinConfig,
} from "@rewind/osu/skin";

export type SkinTexturesByKey = Partial<Record<OsuSkinTextures, Texture[]>>;

// Read
// https://github.com/pixijs/pixi.js/blob/dev/packages/loaders/src/TextureLoader.ts
export interface ISkin {
  config: SkinConfig;

  getComboColorForIndex(i: number): number;

  getTexture(key: OsuSkinTextures): Texture;

  getTextures(key: OsuSkinTextures): Texture[];

  getHitCircleNumberTextures(): Texture[];

  getComboNumberTextures(): Texture[];
}

export class EmptySkin implements ISkin {
  config = generateDefaultSkinConfig(false);

  getComboColorForIndex(): number {
    return 0;
  }

  getComboNumberTextures(): [] {
    return [];
  }

  getHitCircleNumberTextures(): Texture[] {
    return [];
  }

  getTexture() {
    return Texture.EMPTY;
  }

  getTextures(): Texture[] {
    return [Texture.EMPTY];
  }
}

/**
 * A simple skin that can provide the basic information a beatmap needs.
 */
export class Skin implements ISkin {
  static EMPTY = new Skin(generateDefaultSkinConfig(false), {});

  constructor(public readonly config: SkinConfig, public readonly textures: SkinTexturesByKey) {}

  getComboColorForIndex(i: number): number {
    const comboColors = this.config.colors.comboColors;
    return rgbToInt(comboColors[i % comboColors.length]);
  }

  getTexture(key: OsuSkinTextures): Texture {
    return this.getTextures(key)[0];
  }

  getTextures(key: OsuSkinTextures): Texture[] {
    if (!(key in this.textures)) {
      return [Texture.EMPTY];
      // throw new Error("Texture key not found");
    }
    const list = this.textures[key] as Texture[];
    if (list.length === 0) {
      return [Texture.EMPTY];
    } else {
      return list;
    }
  }

  getHitCircleNumberTextures(): Texture[] {
    return hitCircleDigitFonts.map((h) => this.getTexture(h));
  }

  getComboNumberTextures(): Texture[] {
    return comboDigitFonts.map((h) => this.getTexture(h));
  }

  // The textures that are used for every other numbers on the interface (except combo)
  getScoreTextures(): Texture[] {
    return defaultDigitFonts.map((h) => this.getTexture(h));
  }
}


================================================
FILE: apps/desktop/frontend/src/app/model/SkinId.ts
================================================
export type SkinSource = "rewind" | "osu";

export interface SkinId {
  source: SkinSource;
  name: string;
}

export function skinIdToString({ source, name }: SkinId) {
  return `${source}:${name}`;
}

const isSkinSource = (s: string): s is SkinSource => s === "rewind" || s === "osu";

export function stringToSkinId(str: string): SkinId {
  const [source, name] = str.split(":");
  if (isSkinSource(source)) {
    return { source, name };
  } else {
    throw Error("Skin source wrong");
  }
}

export const DEFAULT_OSU_SKIN_ID: SkinId = { source: "rewind", name: "OsuDefaultSkin" };
export const DEFAULT_REWIND_SKIN_ID: SkinId = { source: "rewind", name: "RewindDefaultSkin" };


================================================
FILE: apps/desktop/frontend/src/app/providers/SettingsProvider.tsx
================================================
import React, { createContext, useContext, useState } from "react";

type ISettingsContext = {
  settingsModalOpen: boolean;
  onSettingsModalOpenChange: (open: boolean) => unknown;

  opacity: number;
  onOpacityChange: (opacity: number) => unknown;

  tabIndex: number;
  onTabIndexChange: (index: number) => unknown;
};

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
export const SettingsContext = createContext<ISettingsContext>(null!);

interface SettingsModalProps {
  children: React.ReactNode;
  defaultOpen?: boolean;
}

const DEFAULT_OPACITY = 100; // maxOpacity = 100%

export function SettingsModalProvider({ children, defaultOpen = false }: SettingsModalProps) {
  const [settingsModalOpen, setSettingsModalOpen] = useState(defaultOpen);
  const [opacity, onOpacityChange] = useState(DEFAULT_OPACITY);
  const [tabIndex, onTabIndexChange] = useState(0);

  return (
    <SettingsContext.Provider
      value={{
        settingsModalOpen,
        onSettingsModalOpenChange: (b) => setSettingsModalOpen(b),
        opacity,
        onOpacityChange,
        tabIndex,
        onTabIndexChange,
      }}
    >
      {children}
    </SettingsContext.Provider>
  );
}

export function useSettingsModalContext() {
  const context = useContext(SettingsContext);
  if (!context) {
    throw Error("useSettingsModalContext can only be used within a SettingsModalProvider");
  }
  return context;
}


================================================
FILE: apps/desktop/frontend/src/app/providers/TheaterProvider.tsx
================================================
import React, { createContext, useContext } from "react";
import { RewindTheater } from "../services/common/CommonManagers";

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
export const TheaterContext = createContext<RewindTheater>(null!);

interface TheaterProviderProps {
  theater: RewindTheater;
  children: React.ReactNode;
}

export function TheaterProvider({ theater, children }: TheaterProviderProps) {
  return <TheaterContext.Provider value={theater}>{children}</TheaterContext.Provider>;
}

export function useTheaterContext() {
  const context = useContext(TheaterContext);
  if (!context) {
    throw Error("useTheaterContext can only be used within a TheaterProvider");
  }
  return context;
}

export function useCommonManagers() {
  const theater = useTheaterContext();
  return theater.common;
}

export function useAnalysisApp() {
  const theater = useTheaterContext();
  return theater.analyzer;
}


================================================
FILE: apps/desktop/frontend/src/app/screens/analyzer/Analyzer.tsx
================================================
import { Paper, Stack } from "@mui/material";
import { PlayBar } from "../../components/analyzer/PlayBar";
import { useShortcuts } from "../../hooks/shortcuts";
import { GameCanvas } from "../../components/analyzer/GameCanvas";
import { SettingsModal } from "../../components/analyzer/SettingsModal";
import { SettingsModalProvider } from "../../providers/SettingsProvider";
import { useEffect } from "react";
import { environment } from "../../../environments/environment";
import { useAnalysisApp } from "../../providers/TheaterProvider";

export function Analyzer() {
  const analyzer = useAnalysisApp();
  // Shortcuts will then only be available when this page is <Analyzer/> is open
  useShortcuts();

  useEffect(() => {
    void analyzer.initialize();
    if (environment.debugAnalyzer) {
      void analyzer.loadReplay(environment.debugAnalyzer.replayPath);
    }
  }, [analyzer]);

  return (
    <SettingsModalProvider>
      <SettingsModal />
      <Stack
        sx={{
          p: 2,
          flexGrow: 1,
          height: "100%",
        }}
        gap={2}
      >
        <GameCanvas />
        <Paper elevation={1} sx={{ boxShadow: "none" }}>
          <PlayBar />
        </Paper>
      </Stack>
    </SettingsModalProvider>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/screens/home/HomeScreen.tsx
================================================
import React from "react";
import { FaDiscord, FaTwitter, FaYoutube } from "react-icons/fa";
import { IconButton, Link, Stack, Typography } from "@mui/material";
import { FastRewind } from "@mui/icons-material";
import { useAppInfo } from "../../hooks/app-info";
import { discordUrl, RewindLinks, twitterUrl, youtubeUrl } from "../../utils/constants";

// This page is actually just a placeholder for an overview page that can show things such as "Recently Played", etc.

export function HomeScreen() {
  const { appVersion } = useAppInfo();
  return (
    <Stack gap={4} sx={{ justifyContent: "center", alignItems: "center", margin: "auto", height: "100%" }}>
      <Stack alignItems={"center"}>
        <FastRewind sx={{ height: "2em", width: "2em" }} />
        <Typography fontSize={"1em"} sx={{ userSelect: "none", marginBottom: 2 }}>
          REWIND
        </Typography>
        <Typography fontSize={"caption.fontSize"} color={"text.secondary"}>
          Rewind {appVersion} by{" "}
          <Link href={RewindLinks.OsuPpyShAbstrakt} target={"_blank"} color={"text.secondary"}>
            abstrakt
          </Link>
        </Typography>
        <Typography fontSize={"caption.fontSize"} color={"text.secondary"}>
          osu! University
          <IconButton href={discordUrl} target={"_blank"} size={"small"}>
            <FaDiscord />
          </IconButton>
          <IconButton href={twitterUrl} target={"_blank"} size={"small"}>
            <FaTwitter />
          </IconButton>
          <IconButton href={youtubeUrl} target={"_blank"} size={"small"}>
            <FaYoutube />
          </IconButton>
        </Typography>
      </Stack>
    </Stack>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/screens/setup/SetupScreen.tsx
================================================
import * as React from "react";
import { useCallback, useEffect, useState } from "react";
import { Alert, Badge, Box, Button, IconButton, InputBase, Paper, Stack } from "@mui/material";
import { RewindLogo } from "../../components/logo/RewindLogo";
import { Help, RocketLaunch } from "@mui/icons-material";
import FolderIcon from "@mui/icons-material/Folder";
import { frontendAPI } from "../../api";
import { useNavigate } from "react-router-dom";
import { useAnalysisApp } from "../../providers/TheaterProvider";

interface DirectorySelectionProps {
  value: string | null;
  onChange: (value: string | null) => void;
  placeHolder: string;
  badgeOnEmpty?: boolean;
}

function DirectorySelection({ value, onChange, placeHolder, badgeOnEmpty }: DirectorySelectionProps) {
  const handleSelectFolderClick = useCallback(() => {
    frontendAPI.selectDirectory(value ?? "").then((path) => {
      if (path !== null) {
        onChange(path);
      }
    });
  }, [onChange, value]);

  const onInputChange = useCallback(
    (event: any) => {
      onChange(event.target.value);
    },
    [onChange],
  );

  const invisibleBadge = !badgeOnEmpty || !!value;
  return (
    <Paper sx={{ px: 2, py: 1, display: "flex", alignItems: "center", width: 400 }} elevation={2}>
      {/*<span className={"text-gray-400 select-none w-96"}>{value ?? placeHolder}</span>*/}
      <InputBase
        sx={{ flex: 1 }}
        placeholder={placeHolder}
        value={value ?? ""}
        onChange={onInputChange}
        disabled={true}
      />
      <IconButton onClick={handleSelectFolderClick}>
        <Badge invisible={invisibleBadge} color={"primary"} variant={"dot"}>
          <FolderIcon />
        </Badge>
      </IconButton>
    </Paper>
  );
}

const setupWikiUrl = "https://github.com/abstrakt8/rewind/wiki/Setup";

// TODO: Maybe tell which file is actually missing
export function SetupScreen() {
  // TODO: Add a guess for directory path
  const [directoryPath, setDirectoryPath] = useState<string | null>(null);
  const [saveEnabled, setSaveEnabled] = useState(false);
  const navigate = useNavigate();
  const analyzer = useAnalysisApp();
  // const [updateOsuDirectory, updateState] = useUpdateOsuDirectoryMutation();
  const [showErrorMessage, setShowErrorMessage] = useState(false);

  const handleConfirmClick = useCallback(async () => {
    if (!directoryPath) {
      return;
    }
    const isValid = await analyzer.osuFolderService.isValidOsuFolder(directoryPath);
    if (isValid) {
      analyzer.osuFolderService.setOsuFolder(directoryPath);
      navigate("/app/analyzer");
    } else {
      setShowErrorMessage(true);
    }
  }, [navigate, analyzer.osuFolderService, directoryPath]);

  const handleOnDirectoryChange = useCallback(
    (path: string | null) => {
      setDirectoryPath(path);
      // TODO: Just directly validate since it's so fast
      setShowErrorMessage(false);
    },
    [setShowErrorMessage],
  );

  // Makes sure that the button is only clickable when it's allowed.
  useEffect(() => {
    setSaveEnabled(directoryPath !== null);
  }, [directoryPath]);

  return (
    <Box
      sx={{
        height: "100vh",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <Paper elevation={1}>
        <Stack gap={2} sx={{ px: 6, py: 4 }}>
          <RewindLogo />
          {showErrorMessage && (
            <>
              <Alert severity="error" variant="filled">
                <div>Does not look a valid osu! directory!</div>
              </Alert>
            </>
          )}
          <DirectorySelection
            value={directoryPath}
            onChange={handleOnDirectoryChange}
            placeHolder={"Select your osu! directory"}
            badgeOnEmpty={true}
          />
          <Stack direction={"row-reverse"} gap={2}>
            <Button
              variant={"contained"}
              startIcon={<RocketLaunch />}
              disabled={!saveEnabled}
              onClick={handleConfirmClick}
            >
              Save & Continue
            </Button>
            <Button variant={"text"} onClick={() => window.open(setupWikiUrl)} startIcon={<Help />}>
              Help
            </Button>
          </Stack>
        </Stack>
      </Paper>
    </Box>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/screens/splash/SplashScreen.tsx
================================================
import { HashLoader } from "react-spinners";
import { Stack } from "@mui/material";

export function SplashScreen() {
  return (
    <Stack
      sx={{
        height: "100vh",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
      gap={2}
    >
      <HashLoader color={"white"} loading={true} />
      <div>Services are getting ready ...</div>
    </Stack>
  );
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/AnalysisApp.ts
================================================
import { ReplayService } from "../common/local/ReplayService";
import { GameplayClock } from "../common/game/GameplayClock";
import { BeatmapManager } from "../manager/BeatmapManager";
import { GameSimulator } from "../common/game/GameSimulator";
import { injectable } from "inversify";
import { PixiRendererManager } from "../renderers/PixiRendererManager";
import { AnalysisSceneManager } from "../manager/AnalysisSceneManager";
import { GameLoop } from "../common/game/GameLoop";
import { ScenarioManager } from "../manager/ScenarioManager";
import { ReplayFileWatcher } from "../common/local/ReplayFileWatcher";
import { OsuFolderService } from "../common/local/OsuFolderService";
import { ClipRecorder } from "../manager/ClipRecorder";
import { ModSettingsService } from "./mod-settings";
import { ScreenshotTaker } from "./screenshot";

@injectable()
export class AnalysisApp {
  constructor(
    public readonly gameClock: GameplayClock,
    public readonly gameSimulator: GameSimulator,
    public readonly scenarioManager: ScenarioManager,
    public readonly modSettingsService: ModSettingsService,
    public readonly replayWatcher: ReplayFileWatcher,
    public readonly screenshotTaker: ScreenshotTaker,
    public readonly clipRecorder: ClipRecorder,
    public readonly osuFolderService: OsuFolderService,
    private readonly replayService: ReplayService,
    private readonly gameLoop: GameLoop,
    private readonly beatmapManager: BeatmapManager,
    private readonly sceneManager: AnalysisSceneManager,
    private readonly pixiRenderer: PixiRendererManager,
  ) {}

  stats() {
    return this.gameLoop.stats();
  }

  initialize() {
    console.log("AnalysisApp: Initialize");
    this.replayWatcher.startWatching();
    this.scenarioManager.initialize();
  }

  close() {}

  onEnter(canvas: HTMLCanvasElement) {
    this.pixiRenderer.initializeRenderer(canvas);
    // this.gameLoop.startTicker();
  }

  onHide() {
    this.gameClock.pause();
    this.pixiRenderer.destroy();
    // this.gameLoop.stopTicker();
  }

  /**
   * Loads the replay and the corresponding beatmap and makes the application ready to visualize the replay.
   *
   * Note: This procedure can be optimized in the future, but for now it's ok.
   *
   * @param replayId the id of the replay to load
   */
  async loadReplay(replayId: string) {
    return this.scenarioManager.loadReplay(replayId);
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/analysis-cursor.ts
================================================
import { PersistentService } from "../core/service";
import { injectable } from "inversify";
import { JSONSchemaType } from "ajv";
import { CursorSettings } from "../common/cursor";

export interface AnalysisCursorSettings extends CursorSettings {
  colorKey1: number;
  colorKey2: number;
  colorNoKeys: number;
  colorBothKeys: number;
}

export const DEFAULT_ANALYSIS_CURSOR_SETTINGS: AnalysisCursorSettings = Object.freeze({
  scale: 0.8,
  enabled: true,
  scaleWithCS: true,
  colorNoKeys: 0x5d6463, // gray
  colorKey1: 0xffa500, // orange)
  colorKey2: 0x00ff00, // green)
  colorBothKeys: 0x3cbdc1, // cyan)
});

export const AnalysisCursorSettingsSchema: JSONSchemaType<AnalysisCursorSettings> = {
  type: "object",
  properties: {
    scale: { type: "number", default: DEFAULT_ANALYSIS_CURSOR_SETTINGS.scale },
    enabled: { type: "boolean", default: DEFAULT_ANALYSIS_CURSOR_SETTINGS.enabled },
    scaleWithCS: { type: "boolean", default: DEFAULT_ANALYSIS_CURSOR_SETTINGS.scaleWithCS },
    colorNoKeys: { type: "number", default: DEFAULT_ANALYSIS_CURSOR_SETTINGS.colorNoKeys },
    colorKey1: { type: "number", default: DEFAULT_ANALYSIS_CURSOR_SETTINGS.colorKey1 },
    colorKey2: { type: "number", default: DEFAULT_ANALYSIS_CURSOR_SETTINGS.colorKey2 },
    colorBothKeys: { type: "number", default: DEFAULT_ANALYSIS_CURSOR_SETTINGS.colorBothKeys },
  },
  required: [],
};

@injectable()
export class AnalysisCursorSettingsStore extends PersistentService<AnalysisCursorSettings> {
  key = "analysis-cursor";
  schema: JSONSchemaType<AnalysisCursorSettings> = AnalysisCursorSettingsSchema;
  getDefaultValue(): AnalysisCursorSettings {
    return DEFAULT_ANALYSIS_CURSOR_SETTINGS;
  }

  setEnabled(enabled: boolean) {
    this.changeSettings((s) => (s.enabled = enabled));
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/createRewindAnalysisApp.ts
================================================
import { Container } from "inversify";
import { AnalysisApp } from "./AnalysisApp";
import { PixiRendererManager } from "../renderers/PixiRendererManager";
import { GameplayClock } from "../common/game/GameplayClock";
import { EventEmitter2 } from "eventemitter2";
import { BeatmapManager } from "../manager/BeatmapManager";
import { ReplayManager } from "../manager/ReplayManager";
import { GameSimulator } from "../common/game/GameSimulator";
import { AnalysisSceneManager } from "../manager/AnalysisSceneManager";
import { SceneManager } from "../common/scenes/SceneManager";
import { GameLoop } from "../common/game/GameLoop";
import { AnalysisScene } from "./scenes/AnalysisScene";
import { BeatmapBackgroundFactory } from "../renderers/components/background/BeatmapBackground";
import { TextureManager } from "../textures/TextureManager";
import { AnalysisStage } from "../renderers/components/stage/AnalysisStage";
import { ForegroundHUDPreparer } from "../renderers/components/hud/ForegroundHUDPreparer";
import { PlayfieldFactory } from "../renderers/components/playfield/PlayfieldFactory";
import { PlayfieldBorderFactory } from "../renderers/components/playfield/PlayfieldBorderFactory";
import { HitObjectsContainerFactory } from "../renderers/components/playfield/HitObjectsContainerFactory";
import { HitCircleFactory } from "../renderers/components/playfield/HitCircleFactory";
import { SliderFactory } from "../renderers/components/playfield/SliderFactory";
import { SpinnerFactory } from "../renderers/components/playfield/SpinnerFactory";
import { SliderTextureManager } from "../renderers/components/sliders/SliderTextureManager";
import { CursorPreparer } from "../renderers/components/playfield/CursorPreparer";
import { JudgementPreparer } from "../renderers/components/playfield/JudgementPreparer";
import { AudioEngine } from "../common/audio/AudioEngine";
import { ScenarioManager } from "../manager/ScenarioManager";
import { ReplayFileWatcher } from "../common/local/ReplayFileWatcher";
import { ClipRecorder } from "../manager/ClipRecorder";
import { IdleScene } from "./scenes/IdleScene";
import { KeyPressWithNoteSheetPreparer } from "../renderers/components/keypresses/KeyPressOverlay";
import { ModSettingsService } from "./mod-settings";
import { ScreenshotTaker } from "./screenshot";
import { STAGE_TYPES } from "../types";

/**
 * This is a Rewind specific constructor of the "Analysis" tool (not to be used outside of Rewind).
 *
 * Reason is that many "Rewind" tools share the same services in order to provide smoother experiences.
 *
 * Example: If I use the "Cutter" tool then I want to use the same preferred skin that is used across Rewind.
 *
 * The analysis tool can be used as a standalone app though.
 */
export function createRewindAnalysisApp(commonContainer: Container) {
  const container = new Container({ defaultScope: "Singleton" });
  container.parent = commonContainer;
  container.bind(STAGE_TYPES.EVENT_EMITTER).toConstantValue(new EventEmitter2());

  container.bind(ReplayManager).toSelf();
  container.bind(BeatmapManager).toSelf();
  container.bind(GameplayClock).toSelf();
  container.bind(ScenarioManager).toSelf();
  container.bind(ModSettingsService).toSelf();
  container.bind(GameSimulator).toSelf();
  container.bind(PixiRendererManager).toSelf();

  // Plugins ?
  container.bind(ScreenshotTaker).toSelf();
  container.bind(ClipRecorder).toSelf();
  container.bind(ReplayFileWatcher).toSelf();

  // Assets
  container.bind(TextureManager).toSelf();
  container.bind(AudioEngine).toSelf();

  // Scenes
  container.bind(AnalysisSceneManager).toSelf();
  container.bind(SceneManager).toSelf();

  // Skin is given by above
  // container.bind(SkinManager).toSelf();

  // AnalysisScenes
  container.bind(AnalysisScene).toSelf();
  container.bind(IdleScene).toSelf();

  // Sliders
  container.bind(SliderTextureManager).toSelf();

  container.bind(AnalysisStage).toSelf();
  {
    container.bind(BeatmapBackgroundFactory).toSelf();
    container.bind(ForegroundHUDPreparer).toSelf();
    container.bind(KeyPressWithNoteSheetPreparer).toSelf();
    container.bind(PlayfieldFactory).toSelf();
    {
      container.bind(PlayfieldBorderFactory).toSelf();
      container.bind(HitObjectsContainerFactory).toSelf();
      container.bind(HitCircleFactory).toSelf();
      container.bind(SliderFactory).toSelf();
      container.bind(SpinnerFactory).toSelf();

      container.bind(CursorPreparer).toSelf();
      container.bind(JudgementPreparer).toSelf();
    }
  }

  container.bind(GameLoop).toSelf();

  container.bind(AnalysisApp).toSelf();
  return container.get(AnalysisApp);
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/mod-settings.ts
================================================
import { injectable } from "inversify";
import { BehaviorSubject } from "rxjs";

interface AnalysisModSettings {
  hidden: boolean;
  flashlight: boolean;
}

@injectable()
export class ModSettingsService {
  modSettings$: BehaviorSubject<AnalysisModSettings>;

  constructor() {
    this.modSettings$ = new BehaviorSubject<AnalysisModSettings>({
      flashlight: false,
      hidden: false,
    });
  }

  get modSettings() {
    return this.modSettings$.getValue();
  }

  setHidden(hidden: boolean) {
    this.modSettings$.next({ ...this.modSettings, hidden });
  }

  setFlashlight(flashlight: boolean) {
    this.modSettings$.next({ ...this.modSettings, flashlight });
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/scenes/AnalysisScene.ts
================================================
import { UserScene } from "../../common/scenes/IScene";
import { injectable } from "inversify";
import { AnalysisStage } from "../../renderers/components/stage/AnalysisStage";
import { GameplayClock } from "../../common/game/GameplayClock";
import { GameSimulator } from "../../common/game/GameSimulator";

// Just the normal analysis scene that updates according to a virtual game clock.
@injectable()
export class AnalysisScene implements UserScene {
  constructor(
    private readonly gameClock: GameplayClock,
    private readonly gameSimulator: GameSimulator,
    private readonly analysisStage: AnalysisStage,
  ) {}

  update() {
    this.gameClock.tick();
    this.gameSimulator.simulate(this.gameClock.timeElapsedInMs);
    this.analysisStage.updateAnalysisStage();
  }

  get stage() {
    return this.analysisStage.stage;
  }

  destroy(): void {
    this.analysisStage.destroy();
  }

  init(data: unknown): void {
    // Do nothing
  }

  preload(): Promise<void> {
    return Promise.resolve(undefined);
  }

  create(): void {
    // Do nothing
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/scenes/IdleScene.ts
================================================
// Usually if there is no replay loaded
// Just show a text
// In the future -> show a cool looking logo

import { UserScene } from "../../common/scenes/IScene";
import { Container } from "pixi.js";
import { injectable } from "inversify";

@injectable()
export class IdleScene implements UserScene {
  stage = new Container();

  destroy(): void {
    this.stage.destroy();
  }

  init(data: string): void {}

  async preload(): Promise<void> {
    // Do nothing
  }

  update(): void {
    // Do nothing
  }

  create(): void {
    this.stage = new Container();
    // const text = new Text("Load a beatmap/replay to get started!", {
    //   fontSize: 16,
    //   fill: 0xeeeeee,
    //   fontFamily: "Arial",
    //   align: "left",
    // });
    // // Maybe center it somewhere
    // this.stage.addChild(text);
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/scenes/LoadingScene.ts
================================================
// Very simple loading scene that shows the progress of the loading
import { UserScene } from "../../common/scenes/IScene";
import { Container, Text } from "pixi.js";

export class LoadingScene implements UserScene {
  stage: Container = new Container();

  destroy(): void {}

  init(): void {
    //
  }

  async preload(): Promise<void> {
    //
  }

  update(): void {
    //
  }

  create(): void {
    this.stage = new Container();

    const text = new Text("LoadingScene...", {
      fontSize: 16,
      fill: 0xeeeeee,
      fontFamily: "Arial",
      align: "left",
    });
    // Maybe center it somewhere
    this.stage.addChild(text);
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/scenes/ResultsScreenScene.ts
================================================
// Should be shown after the replay or can be manually triggered


================================================
FILE: apps/desktop/frontend/src/app/services/analysis/screenshot.ts
================================================
import { injectable } from "inversify";
import { AnalysisScene } from "./scenes/AnalysisScene";
import { PixiRendererManager } from "../renderers/PixiRendererManager";

@injectable()
export class ScreenshotTaker {
  constructor(private readonly analysisScene: AnalysisScene, private readonly pixiRenderer: PixiRendererManager) {
  }

  takeScreenshot() {
    const renderer = this.pixiRenderer.getRenderer();
    if (!renderer) return;

    const canvas: HTMLCanvasElement = renderer.plugins.extract.canvas(this.analysisScene.stage);
    canvas.toBlob(
      (blob) => {
        const a = document.createElement("a");
        a.download = `Rewind Screenshot ${new Date().toISOString()}.jpg`;
        a.href = URL.createObjectURL(blob!);
        a.click();
        a.remove();
      },
      "image/jpeg",
      0.9,
    );
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/CommonManagers.ts
================================================
import { Container, injectable } from "inversify";
import { ReplayService } from "./local/ReplayService";
import { SkinLoader } from "./local/SkinLoader";
import { AudioService } from "./audio/AudioService";
import { createRewindAnalysisApp } from "../analysis/createRewindAnalysisApp";
import { AudioSettingsStore } from "./audio/settings";
import { BeatmapBackgroundSettingsStore } from "./beatmap-background";
import { PlayfieldBorderSettingsStore } from "./playfield-border";
import { AnalysisCursorSettingsStore } from "../analysis/analysis-cursor";
import { ReplayCursorSettingsStore } from "./replay-cursor";
import { SkinHolder, SkinManager, SkinSettingsStore } from "./skin";
import { HitErrorBarSettingsStore } from "./hit-error-bar";
import { PlaybarSettingsStore } from "./playbar";
import { OsuFolderService } from "./local/OsuFolderService";
import { OsuDBDao } from "./local/OsuDBDao";
import { BlueprintLocatorService } from "./local/BlueprintLocatorService";
import { BeatmapRenderService } from "./beatmap-render";
import { STAGE_TYPES } from "../types";
import { AppInfoService } from "./app-info";

/**
 * Creates the services that support all the osu! tools such as the Analyzer.
 * Common settings are set here so that they can be shared with other tools.
 * Example: Preferred skin can be set at only one place and is shared among all tools.
 */
@injectable()
export class CommonManagers {
  constructor(
    public readonly skinManager: SkinManager,
    public readonly skinSettingsStore: SkinSettingsStore,
    public readonly audioSettingsService: AudioSettingsStore,
    public readonly beatmapBackgroundSettingsStore: BeatmapBackgroundSettingsStore,
    public readonly beatmapRenderSettingsStore: BeatmapRenderService,
    public readonly hitErrorBarSettingsStore: HitErrorBarSettingsStore,
    public readonly analysisCursorSettingsStore: AnalysisCursorSettingsStore,
    public readonly replayCursorSettingsStore: ReplayCursorSettingsStore,
    public readonly playbarSettingsStore: PlaybarSettingsStore,
    public readonly appInfoService: AppInfoService,
  ) {}

  async initialize() {
    await this.skinManager.loadPreferredSkin();
  }
}

interface Settings {
  rewindSkinsFolder: string;
  appVersion: string;
  appPlatform: string;
}

export function createRewindTheater({ rewindSkinsFolder, appPlatform, appVersion }: Settings) {
  // Regarding `skipBaseClassChecks`: https://github.com/inversify/InversifyJS/issues/522#issuecomment-682246076
  const container = new Container({ defaultScope: "Singleton" });
  container.bind(STAGE_TYPES.AUDIO_CONTEXT).toConstantValue(new AudioContext());
  container.bind(STAGE_TYPES.REWIND_SKINS_FOLDER).toConstantValue(rewindSkinsFolder);
  container.bind(STAGE_TYPES.APP_PLATFORM).toConstantValue(appPlatform);
  container.bind(STAGE_TYPES.APP_VERSION).toConstantValue(appVersion);
  container.bind(OsuFolderService).toSelf();
  container.bind(OsuDBDao).toSelf();
  container.bind(BlueprintLocatorService).toSelf();
  container.bind(ReplayService).toSelf();
  container.bind(SkinLoader).toSelf();
  container.bind(SkinHolder).toSelf();
  container.bind(AudioService).toSelf();
  container.bind(SkinManager).toSelf();
  container.bind(AppInfoService).toSelf();

  // General settings stores
  container.bind(AudioSettingsStore).toSelf();
  container.bind(AnalysisCursorSettingsStore).toSelf();
  container.bind(BeatmapBackgroundSettingsStore).toSelf();
  container.bind(BeatmapRenderService).toSelf();
  container.bind(HitErrorBarSettingsStore).toSelf();
  container.bind(PlayfieldBorderSettingsStore).toSelf();
  container.bind(ReplayCursorSettingsStore).toSelf();
  container.bind(SkinSettingsStore).toSelf();
  container.bind(PlaybarSettingsStore).toSelf();

  // Theater facade
  container.bind(CommonManagers).toSelf();

  return {
    common: container.get(CommonManagers),
    analyzer: createRewindAnalysisApp(container),
  };
}

export type RewindTheater = ReturnType<typeof createRewindTheater>;


================================================
FILE: apps/desktop/frontend/src/app/services/common/app-info.ts
================================================
import { inject, injectable } from "inversify";
import { STAGE_TYPES } from "../types";

@injectable()
export class AppInfoService {
  constructor(
    @inject(STAGE_TYPES.APP_PLATFORM) public readonly platform: string,
    @inject(STAGE_TYPES.APP_VERSION) public readonly version: string,
  ) {
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/audio/AudioEngine.ts
================================================
import { inject, injectable, postConstruct } from "inversify";
import { AudioSettings, AudioSettingsStore } from "./settings";
import { STAGE_TYPES } from "../../types";
import { GameplayClock } from "../game/GameplayClock";

// HTML5 Audio supports time stretching without pitch changing (otherwise sounds like night core)
// Chromium's implementation of <audio> is the best.
// HTML5 Audio currentTime sucks though in terms of accuracy

// "AudioEngine"
@injectable()
export class AudioEngine {
  // Just like in osu!, we can change the volume of music and samples separately
  musicGain: GainNode;
  samplesGain: GainNode;
  masterGain: GainNode;

  musicUrl?: string;
  musicBuffer?: AudioBuffer;

  song?: MediaElementAudioSourceNode;

  // In seconds
  sampleWindow = 0.1;
  schedulePointer = 0;

  constructor(
    private readonly audioSettingService: AudioSettingsStore,
    @inject(STAGE_TYPES.AUDIO_CONTEXT) private readonly audioContext: AudioContext,
    private readonly gameClock: GameplayClock,
  ) {
    this.masterGain = this.audioContext.createGain();
    this.musicGain = this.audioContext.createGain();
    this.musicGain.connect(this.masterGain);
    this.samplesGain = this.audioContext.createGain();
    this.samplesGain.connect(this.masterGain);
    this.masterGain.connect(this.audioContext.destination);

    // this.handleAudioSettingsChanged(this.audioSettingService.getSettings());
  }

  @postConstruct()
  postConstruct() {
    console.log("Initializing AudioEngine");
    this.setupListeners();
  }

  // async loadSong(songUrl: string) {
  //   // Disconnect other one?
  //   const audio = new HTMLAudioElement();
  //   audio.crossOrigin = "anonymous";
  //   audio.src = songUrl;
  //   this.song = this.audioContext.createMediaElementSource(audio);
  //   this.song.connect(this.musicGain);
  // }
  //
  setSong(audio: HTMLAudioElement) {
    this.song = this.audioContext.createMediaElementSource(audio);
    this.song.connect(this.musicGain);
  }

  setupListeners() {
    this.gameClock.seeked$.subscribe(this.seekTo.bind(this));
    this.gameClock.isPlaying$.subscribe((isPlaying: boolean) => {
      if (isPlaying) this.start();
      else this.pause();
    });
    this.gameClock.speed$.subscribe(this.changePlaybackRate.bind(this));

    this.audioSettingService.settings$.subscribe((value) => this.handleAudioSettingsChanged(value));
  }

  start() {
    // TODO: ???
    this.audioContext.resume();
    this.song?.mediaElement.play();
  }

  pause() {
    if (this.song) {
      this.song.mediaElement.pause();
      this.schedulePointer = 0;
    }
  }

  changePlaybackRate(newPlaybackRate: number) {
    if (this.song) {
      this.song.mediaElement.playbackRate = newPlaybackRate;
    }
  }

  // Don't know if this is accurate *enough*
  currentTime() {
    return (this.song?.mediaElement.currentTime ?? 0) * 1000;
  }

  seekTo(toInMs: number) {
    if (this.song) {
      this.song.mediaElement.currentTime = toInMs / 1000;
    }
  }

  get isPlaying() {
    return !this.song?.mediaElement.paused;
  }

  togglePlaying(): boolean {
    if (this.isPlaying) this.pause();
    else this.start();
    return this.isPlaying;
  }

  get durationInMs() {
    return (this.song?.mediaElement.duration ?? 1) * 1000;
  }

  destroy() {
    if (this.song) {
      this.song.disconnect();
      this.song = undefined;
    }
    // this.pause();
    // this.audioContext.close().then(() => {
    //   console.log("Audio context closed.");
    // });
  }

  private handleAudioSettingsChanged(settings: AudioSettings) {
    const { muted, volume } = settings;
    this.masterGain.gain.value = muted ? 0 : volume.master;
    this.musicGain.gain.value = volume.music;
    this.samplesGain.gain.value = volume.effects;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/audio/AudioService.ts
================================================
import { injectable } from "inversify";

/**
 * Only one AudioService?
 * Only one AudioContext.
 */
@injectable()
export class AudioService {
  private audioContext: AudioContext;

  audios: Record<string, HTMLAudioElement> = {};

  constructor() {
    this.audioContext = new AudioContext();
  }

  async loadAudio(filePath: string) {
    const songUrl = filePath;
    const audio = new Audio();
    audio.crossOrigin = "anonymous";
    audio.src = songUrl;
    return audio;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/audio/settings.ts
================================================
import { injectable } from "inversify";
import { PersistentService } from "../../core/service";
import { JSONSchemaType } from "ajv";

export interface AudioSettings {
  muted: boolean;
  volume: {
    master: number;
    music: number;
    effects: number;
  };
}

export const DEFAULT_AUDIO_SETTINGS: AudioSettings = Object.freeze({
  muted: false,
  volume: {
    // Make it intentionally low, but not muted.
    master: 0.1,
    music: 1.0,
    effects: 0.25,
  },
});
export const AudioSettingsSchema: JSONSchemaType<AudioSettings> = {
  type: "object",
  properties: {
    muted: { type: "boolean", default: DEFAULT_AUDIO_SETTINGS.muted },
    volume: {
      type: "object",
      properties: {
        master: { type: "number", default: DEFAULT_AUDIO_SETTINGS.volume.master },
        music: { type: "number", default: DEFAULT_AUDIO_SETTINGS.volume.music },
        effects: { type: "number", default: DEFAULT_AUDIO_SETTINGS.volume.effects },
      },
      required: [],
    },
  },
  required: [],
};

@injectable()
export class AudioSettingsStore extends PersistentService<AudioSettings> {
  key = "audio-settings";
  schema = AudioSettingsSchema;

  getDefaultValue(): AudioSettings {
    return DEFAULT_AUDIO_SETTINGS;
  }

  toggleMuted() {
    this.changeSettings((d) => (d.muted = !d.muted));
  }

  setMasterVolume(volume: number) {
    this.changeSettings((d) => (d.volume.master = volume));
  }

  setMusicVolume(volume: number) {
    this.changeSettings((d) => (d.volume.music = volume));
  }

  setEffectsVolume(volume: number) {
    this.changeSettings((d) => (d.volume.effects = volume));
  }

  setMuted(muted: boolean) {
    this.changeSettings((d) => (d.muted = muted));
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/beatmap-background.ts
================================================
import { injectable } from "inversify";
import { BehaviorSubject } from "rxjs";
import { Texture } from "pixi.js";
import { PersistentService } from "../core/service";
import { JSONSchemaType } from "ajv";

export interface BeatmapBackgroundSettings {
  // Whether it should be rendered or not. Temporarily disabling with this flag gives a better UX than setting the dim
  // to 100% since the user can retain the old value.
  enabled: boolean;
  // A number between 0 and 1. The resulting blur strength equals `blur * MAX_BLUR_STRENGTH`.
  blur: number;
  // dim = 1 - alpha
  dim: number;
}

export const DEFAULT_BEATMAP_BACKGROUND_SETTINGS: BeatmapBackgroundSettings = Object.freeze({
  dim: 0.8,
  blur: 0.4,
  enabled: true,
});
export const BeatmapBackgroundSettingsSchema: JSONSchemaType<BeatmapBackgroundSettings> = {
  type: "object",
  properties: {
    dim: { type: "number", default: DEFAULT_BEATMAP_BACKGROUND_SETTINGS.dim },
    blur: { type: "number", default: DEFAULT_BEATMAP_BACKGROUND_SETTINGS.blur },
    enabled: { type: "boolean", default: DEFAULT_BEATMAP_BACKGROUND_SETTINGS.enabled },
  },
  required: [],
};

@injectable()
export class BeatmapBackgroundSettingsStore extends PersistentService<BeatmapBackgroundSettings> {
  key = "beatmap-background";
  schema = BeatmapBackgroundSettingsSchema;

  // TODO: Move?
  texture$ = new BehaviorSubject<Texture>(Texture.EMPTY);

  getDefaultValue(): BeatmapBackgroundSettings {
    return DEFAULT_BEATMAP_BACKGROUND_SETTINGS;
  }

  get texture() {
    return this.texture$.getValue();
  }

  setBlur(blur: number) {
    this.settings$.next({ ...this.settings, blur });
  }

  setDim(dim: number) {
    this.settings$.next({ ...this.settings, dim });
  }

  setEnabled(enabled: boolean) {
    this.settings$.next({ ...this.settings, enabled });
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/beatmap-render.ts
================================================
import { JSONSchemaType } from "ajv";
import { injectable } from "inversify";
import { PersistentService } from "../core/service";

export interface BeatmapRenderSettings {
  sliderDevMode: boolean;
  // By default, osu! will ALWAYS draw slider ends, but most skins have an invisible slider end texture.
  drawSliderEnds: boolean;
}

export const DEFAULT_BEATMAP_RENDER_SETTINGS: BeatmapRenderSettings = Object.freeze({
  sliderDevMode: false,
  drawSliderEnds: false,
});

export const BeatmapRenderSettingsSchema: JSONSchemaType<BeatmapRenderSettings> = {
  type: "object",
  properties: {
    sliderDevMode: { type: "boolean", default: DEFAULT_BEATMAP_RENDER_SETTINGS.sliderDevMode },
    drawSliderEnds: { type: "boolean", default: DEFAULT_BEATMAP_RENDER_SETTINGS.drawSliderEnds },
  },
  required: [],
};

@injectable()
export class BeatmapRenderService extends PersistentService<BeatmapRenderSettings> {
  key = "beatmap-render";
  schema = BeatmapRenderSettingsSchema;

  getDefaultValue(): BeatmapRenderSettings {
    return DEFAULT_BEATMAP_RENDER_SETTINGS;
  }
  setSliderDevMode(sliderDevMode: boolean) {
    this.changeSettings((draft) => (draft.sliderDevMode = sliderDevMode));
  }

  setDrawSliderEnds(drawSliderEnds: boolean) {
    this.changeSettings((draft) => (draft.drawSliderEnds = drawSliderEnds));
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/cursor.ts
================================================
export interface CursorSettings {
  // A number between 0.1 and 2.0
  scale: number;
  // If it should be shown or not
  enabled: boolean;
  // If it should scale with the circle size of the beatmap
  scaleWithCS: boolean;
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/game/GameLoop.ts
================================================
import * as PIXI from "pixi.js";
import { GameplayClock } from "./GameplayClock";
import { PixiRendererManager } from "../../renderers/PixiRendererManager";
import { injectable, postConstruct } from "inversify";
import { SceneManager } from "../scenes/SceneManager";
import MrDoobStats from "stats.js";

function defaultMonitor() {
  const s = new MrDoobStats();
  s.dom.style.position = "absolute";
  s.dom.style.left = "0px";
  s.dom.style.top = "0px";
  s.dom.style.zIndex = "9000";
  // if (props.initialPanel !== undefined) s.showPanel(props.initialPanel);
  return s;
}

// Game loop does not have to stop if the game clock is paused.
// For example, we could still toggle hidden on/off and need to see the changes on the canvas.
// However, it should be paused, if the view is destroyed or if the window was blurred ...
// Default behavior is to stop the game loop in case of window.blur because usually the player does not want to
// watch the replay in Rewind while playing osu!.
// End game behavior would be to automatically stop the game loop if the user is playing osu!, which we
// could detect through some memory reader.
@injectable()
export class GameLoop {
  private ticker: PIXI.Ticker;
  private readonly performanceMonitor: MrDoobStats;

  constructor(
    private gameClock: GameplayClock, // Maybe also inject?
    private sceneManager: SceneManager,
    private pixiRendererService: PixiRendererManager,
  ) {
    this.ticker = new PIXI.Ticker();
    this.performanceMonitor = defaultMonitor();
  }

  stats() {
    return this.performanceMonitor.dom;
  }

  @postConstruct()
  initializeTicker() {
    this.ticker.add(this.tickHandler.bind(this));
  }

  startTicker() {
    this.ticker.start();
  }

  stopTicker() {
    this.ticker.stop();
  }

  private update(deltaTimeMs: number) {
    this.sceneManager.update(deltaTimeMs);
  }

  private render() {
    const renderer = this.pixiRendererService.getRenderer();
    if (renderer) {
      this.pixiRendererService.resizeCanvasToDisplaySize();
      renderer.render(this.sceneManager.createStage());
    }
  }

  // deltaTimeMs will be given by PixiTicker
  tickHandler(deltaTimeMs: number) {
    // Maybe we can measure the `.update()` and the `.render()` independently
    // console.debug(`Updating with timeDelta=${deltaTimeMs}ms`);
    this.performanceMonitor.begin();
    this.update(deltaTimeMs);
    this.render();
    this.performanceMonitor.end();
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/game/GameSimulator.ts
================================================
import {
  Beatmap,
  BucketedGameStateTimeMachine,
  defaultGameplayInfo,
  GameplayInfo,
  GameplayInfoEvaluator,
  GameState,
  HitObjectJudgement,
  isHitObjectJudgement,
  ReplayAnalysisEvent,
  retrieveEvents,
} from "@osujs/core";
import { injectable } from "inversify";
import type { OsuReplay } from "../../../model/OsuReplay";
import { BehaviorSubject } from "rxjs";
import { parser, std_diff } from "ojsama";
import { Queue } from "typescript-collections";
import { max } from "simple-statistics";

@injectable()
export class GameSimulator {
  private gameplayTimeMachine?: BucketedGameStateTimeMachine;
  private gameplayEvaluator?: GameplayInfoEvaluator;
  private currentState?: GameState;
  private lastState?: GameState;
  private currentInfo: GameplayInfo = defaultGameplayInfo;
  public replayEvents$: BehaviorSubject<ReplayAnalysisEvent[]>;
  public difficulties$: BehaviorSubject<number[]>;
  public judgements: HitObjectJudgement[] = [];
  public hits: [number, number, boolean][] = [];

  constructor() {
    this.replayEvents$ = new BehaviorSubject<ReplayAnalysisEvent[]>([]);
    this.difficulties$ = new BehaviorSubject<number[]>([]);
  }

  calculateDifficulties(rawBeatmap: string, durationInMs: number, mods: number) {
    console.log(`Calculating difficulty for beatmap with duration=${durationInMs}ms and mods=${mods}`);
    const p = new parser();
    p.feed(rawBeatmap);
    const map = p.map;
    const d = new std_diff().calc({ map, mods });

    const TIME_STEP = 500;
    const q = new Queue<[number, number]>();
    let i = 0;
    let sum = 0;
    const res: number[] = [];

    // O(n + m)
    for (let t = 0; t < durationInMs; t += TIME_STEP) {
      while (i < map.objects.length) {
        const o = map.objects[i];
        if (t + TIME_STEP < o.time) {
          break;
        }
        const strainTotal = d.objects[i].strains[0] + d.objects[i].strains[1];
        q.enqueue([o.time, strainTotal]);
        sum += strainTotal;
        i++;
      }
      while (!q.isEmpty()) {
        const [time, totalStrain] = q.peek() as [number, number];
        if (time > t - TIME_STEP) {
          break;
        }
        sum -= totalStrain;
        q.dequeue();
      }
      res.push(q.isEmpty() ? 0 : sum / q.size());
    }
    if (res.length > 0) {
      // normalize
      const m = max(res);
      if (m > 0) {
        const normalizedRes = res.map((r) => r / m);
        this.difficulties$.next(normalizedRes);
      }
    }
  }

  calculateHitErrorArray() {}

  simulateReplay(beatmap: Beatmap, replay: OsuReplay) {
    this.gameplayTimeMachine = new BucketedGameStateTimeMachine(replay.frames, beatmap, {
      hitWindowStyle: "OSU_STABLE",
      noteLockStyle: "STABLE",
    });
    this.gameplayEvaluator = new GameplayInfoEvaluator(beatmap, {});
    // TODO: Move this to async ...
    this.lastState = this.gameplayTimeMachine.gameStateAt(1e9);
    this.currentInfo = defaultGameplayInfo;
    // this.currentState = finalState...
    this.replayEvents$.next(retrieveEvents(this.lastState, beatmap.hitObjects));
    this.judgements = this.replayEvents$.getValue().filter(isHitObjectJudgement);

    this.hits = [];
    if (!this.lastState) return;

    // In order
    for (const id of this.lastState.judgedObjects) {
      const h = beatmap.getHitObject(id);
      if (h.type === "HIT_CIRCLE") {
        const s = this.lastState.hitCircleVerdict[id];
        const hitCircle = beatmap.getHitCircle(id);
        const offset = s.judgementTime - hitCircle.hitTime;
        const hit = s.type !== "MISS";
        this.hits.push([s.judgementTime, offset, hit]);
      }
    }
    // not sure if this is needed
    this.hits.sort((a, b) => a[0] - b[0]);
  }

  // Simulates the game to be at the given time
  // If a whole game simulation has happened, then this should be really fast
  simulate(gameTimeInMs: number) {
    if (this.gameplayTimeMachine && this.gameplayEvaluator) {
      this.currentState = this.gameplayTimeMachine.gameStateAt(gameTimeInMs);
      this.currentInfo = this.gameplayEvaluator.evaluateReplayState(this.currentState!);
    }
  }

  getCurrentState() {
    return this.currentState;
  }

  getCurrentInfo() {
    return this.currentInfo;
  }

  // Very likely to be a request from the UI since it wants to render the playbar events
  async calculateEvents() {
    // In case it takes unbearably long -> we might need a web worker
  }

  clear() {
    this.replayEvents$.next([]);
    this.difficulties$.next([]);
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/game/GameplayClock.ts
================================================
import { injectable } from "inversify";
import { BehaviorSubject, Subject } from "rxjs";

const getNowInMs = () => performance.now();

@injectable()
export class GameplayClock {
  public isPlaying$: BehaviorSubject<boolean>;
  public durationInMs$: BehaviorSubject<number>;
  public speed$: BehaviorSubject<number>;

  public seeked$: Subject<number>;

  // Things that get updated very often should not be Subjects due to performance issues
  public timeElapsedInMs = 0;
  private lastUpdateTimeInMs = 0;

  // private eventEmitter: EventEmitter;

  constructor() {
    this.isPlaying$ = new BehaviorSubject<boolean>(false);
    this.durationInMs$ = new BehaviorSubject<number>(0);
    this.speed$ = new BehaviorSubject<number>(1);
    this.seeked$ = new Subject<number>();
  }

  /**
   * Tick should only be used once in each frame and for the rest of the frame one should refer to `timeElapsedInMs`.
   * This is to ensure that every related game object is referring to the same time at the same time.
   */
  tick() {
    this.updateTimeElapsed();
    if (this.timeElapsedInMs > this.durationInMs) {
      this.pause();
      this.timeElapsedInMs = this.durationInMs;
    }
    return this.timeElapsedInMs;
  }

  get speed() {
    return this.speed$.getValue();
  }

  set speed(value: number) {
    this.speed$.next(value);
  }

  get durationInMs() {
    return this.durationInMs$.getValue();
  }

  set durationInMs(value: number) {
    this.durationInMs$.next(Number.isNaN(value) ? 0 : value);
  }

  get isPlaying() {
    return this.isPlaying$.getValue();
  }

  set isPlaying(value: boolean) {
    this.isPlaying$.next(value);
  }

  updateTimeElapsed() {
    if (!this.isPlaying) return;
    const nowInMs = getNowInMs();
    const deltaInMs = this.speed * (nowInMs - this.lastUpdateTimeInMs);
    this.timeElapsedInMs += deltaInMs;
    this.lastUpdateTimeInMs = nowInMs;
  }

  toggle() {
    if (this.isPlaying) this.pause();
    else this.start();
  }

  start() {
    if (this.isPlaying) return;

    // TODO: Maybe
    // Resets it back to 0 in case the user wants to start the clock again when it already ended.
    if (this.timeElapsedInMs >= this.durationInMs) {
      // Will also emit an event
      this.seekTo(0);
    }
    this.isPlaying = true;
    this.lastUpdateTimeInMs = getNowInMs();
  }

  pause() {
    if (!this.isPlaying) return;
    this.updateTimeElapsed();
    this.isPlaying = false;
  }

  setSpeed(speed: number) {
    this.updateTimeElapsed();
    this.speed = speed;
  }

  setDuration(durationInMs: number) {
    console.debug(`GameClock duration has been set to ${durationInMs}ms`);
    this.durationInMs = durationInMs;
  }

  seekTo(timeInMs: number) {
    timeInMs = Math.min(this.durationInMs, Math.max(0, timeInMs));
    this.timeElapsedInMs = timeInMs;
    this.lastUpdateTimeInMs = getNowInMs();
    this.seeked$.next(timeInMs);
  }

  clear() {
    this.pause();
    this.speed$.next(1.0);
    // This is just ahot fix
    this.durationInMs$.next(1);
    this.timeElapsedInMs = 0;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/hit-error-bar.ts
================================================
import { injectable } from "inversify";
import { PersistentService } from "../core/service";
import { JSONSchemaType } from "ajv";

export interface HitErrorBarSettings {
  enabled: boolean;
  scale: number;
}

export const DEFAULT_HIT_ERROR_BAR_SETTINGS: HitErrorBarSettings = Object.freeze({
  enabled: true,
  scale: 2.0,
});

export const HitErrorBarSettingsSchema: JSONSchemaType<HitErrorBarSettings> = {
  type: "object",
  properties: {
    enabled: { type: "boolean", default: DEFAULT_HIT_ERROR_BAR_SETTINGS.enabled },
    scale: { type: "number", default: DEFAULT_HIT_ERROR_BAR_SETTINGS.scale },
  },
  required: [],
};

@injectable()
export class HitErrorBarSettingsStore extends PersistentService<HitErrorBarSettings> {
  key = "hit-error";
  schema = HitErrorBarSettingsSchema;

  getDefaultValue() {
    return DEFAULT_HIT_ERROR_BAR_SETTINGS;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/key-press-overlay.ts
================================================
export interface KeyPressOverlaySettings {
  // The key presses that are relative to [-timeWindow+currentTime, currentTime+timeWindow] will be shown
  timeWindow: number;

  // Number between 0 and 1 determining the opacity of the right hand side
  futureOpacity: number;
}

// export class KeyPressOverlaySettingsStore extends AbstractSettingsStore<KeyPressOverlaySettings> {}


================================================
FILE: apps/desktop/frontend/src/app/services/common/local/BlueprintLocatorService.ts
================================================
import { promises as fsPromises } from "fs";
import { Blueprint, BlueprintSection, parseBlueprint } from "@osujs/core";
import { join } from "path";
import { createHash } from "crypto";
import { filterFilenamesInDirectory } from "@rewind/osu-local/utils";
import { injectable } from "inversify";
import { OsuFolderService } from "./OsuFolderService";
import { BlueprintInfo } from "../../../model/BlueprintInfo";
import { OsuDBDao } from "./OsuDBDao";

const { stat, readFile } = fsPromises;

const METADATA_SECTIONS_TO_READ: BlueprintSection[] = ["General", "Difficulty", "Events", "Metadata"];

function mapToLocalBlueprint(
  blueprint: Blueprint,
  osuFileName: string,
  folderName: string,
  md5Hash: string,
): BlueprintInfo {
  const { metadata } = blueprint.blueprintInfo;
  return {
    creator: "", // TODO: ?
    title: metadata.title,
    osuFileName,
    folderName,
    bgFileName: metadata.backgroundFile,
    md5Hash,
    audioFileName: metadata.audioFile,
    artist: metadata.artist,
    lastPlayed: new Date(),
  };
}

// Basically just the identifier of the blueprint
type BlueprintMD5 = string;

/**
 * Service responsible for locating the blueprints.
 *
 * It mainly uses the `osu!.db` to get the majority of the blueprints. However, once the user imports new beatmaps,
 * the `osu!.db` doesn't get immediately updated (no flushing), thus we have to find out which ones were added by
 * manually looking for new files.
 */
@injectable()
export class BlueprintLocatorService {
  private blueprints: Record<BlueprintMD5, BlueprintInfo> = {};

  constructor(private readonly osuDbDao: OsuDBDao, private readonly osuFolderService: OsuFolderService) {
  }

  private get songsFolder() {
    return this.osuFolderService.songsFolder$.getValue();
  }

  private async completeRead() {
    const freshBlueprints = await this.osuDbDao.getAllBlueprints();
    const lastModifiedTime = await this.osuDbDao.getOsuDbLastModifiedTime();

    console.log(`Reading the osu!/Songs folder: ${this.songsFolder}`);
    const candidates = await getNewFolderCandidates(this.songsFolder, new Date(lastModifiedTime));
    if (candidates.length > 0) {
      console.log(`New blueprint candidates (${candidates.length}) : ${candidates.join(",")}`);
    }

    for (const songFolder of candidates) {
      const osuFiles = await listOsuFiles(join(this.songsFolder, songFolder));
      for (const osuFile of osuFiles) {
        const fileName = join(this.songsFolder, songFolder, osuFile);
        console.log(`Reading file ${fileName}`);
        const data = await readFile(fileName);
        const hash = createHash("md5");
        hash.update(data);
        const md5Hash = hash.digest("hex");
        const blueprint = await parseBlueprint(data.toString("utf-8"), { sectionsToRead: METADATA_SECTIONS_TO_READ });
        freshBlueprints.push(mapToLocalBlueprint(blueprint, osuFile, songFolder, md5Hash));
      }
    }

    this.blueprints = {};
    freshBlueprints.forEach(this.addNewBlueprint.bind(this));
    return this.blueprints;
  }

  // Usually for watchers
  private addNewBlueprint(blueprint: BlueprintInfo) {
    this.blueprints[blueprint.md5Hash] = blueprint;
  }

  async blueprintHasBeenAdded() {
    // It's better to rely on the "osu!.db" than the ones we received from watching.
    // But ofc, we if we read from osu!.db again there might still be some folders that have not been properly flushed.
    if (await this.osuDbDao.hasChanged()) {
      return true;
    }
    const s = await stat(this.songsFolder);
    const t = await this.osuDbDao.getOsuDbLastModifiedTime();
    return s.mtime.getTime() > t;
  }

  async getAllBlueprints(): Promise<Record<string, BlueprintInfo>> {
    const needToCheckAgain = await this.blueprintHasBeenAdded();
    if (needToCheckAgain) {
      await this.completeRead();
    }
    return this.blueprints;
  }

  async getBlueprintByMD5(md5: string): Promise<BlueprintInfo | undefined> {
    const maps = await this.getAllBlueprints();
    return maps[md5];
  }

  public async blueprintBg(md5: string): Promise<string | undefined> {
    const blueprintMetaData = await this.getBlueprintByMD5(md5);
    if (!blueprintMetaData) return undefined;
    const { osuFileName, folderName } = blueprintMetaData;
    const data = await readFile(join(this.songsFolder, folderName, osuFileName), { encoding: "utf-8" });
    const parsedBlueprint = parseBlueprint(data, { sectionsToRead: ["Events"] });
    // There is also an offset but let's ignore for now
    return parsedBlueprint.blueprintInfo.metadata.backgroundFile;
  }
}

// This might be a very expensive operation and should be done only once at startup. The new ones should be watched
// ~250ms at my Songs folder
export async function getNewFolderCandidates(songsFolder: string, importedLaterThan: Date): Promise<string[]> {
  return filterFilenamesInDirectory(songsFolder, async (fileName) => {
    const s = await stat(join(songsFolder, fileName));
    return s.mtime.getTime() > importedLaterThan.getTime() && s.isDirectory();
  });
}

export async function listOsuFiles(songFolder: string): Promise<string[]> {
  return filterFilenamesInDirectory(songFolder, async (fileName) => fileName.endsWith(".osu"));
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/local/OsuDBDao.ts
================================================
import { join } from "path";
import { Beatmap as DBBeatmap, OsuDBReader } from "@rewind/osu-local/db-reader";
import { fileLastModifiedTime, ticksToDate } from "@rewind/osu-local/utils";
import { readFile } from "fs/promises";
import { BlueprintInfo } from "../../../model/BlueprintInfo";
import { OsuFolderService } from "./OsuFolderService";
import { injectable } from "inversify";

const mapToBlueprint = (b: DBBeatmap): BlueprintInfo => {
  return {
    md5Hash: b.md5Hash,
    audioFileName: b.audioFileName,
    folderName: b.folderName,
    lastPlayed: ticksToDate(b.lastPlayed)[0],
    osuFileName: b.fileName,
    artist: b.artist, // unicode?
    title: b.title,
    creator: b.creator,
  };
};

@injectable()
export class OsuDBDao {
  private lastMtime = -1;
  private blueprints: BlueprintInfo[] = [];

  constructor(private readonly osuFolderService: OsuFolderService) {
    // We just assume that we need to read it again
    this.osuFolderService.settings$.subscribe(() => {
      this.lastMtime = -1;
      this.blueprints = [];
    });
  }

  private get osuDbPath() {
    return join(this.osuFolderService.getOsuFolder(), "osu!.db");
  }

  private async createReader() {
    const buffer = await readFile(this.osuDbPath);
    return new OsuDBReader(buffer);
  }

  async getOsuDbLastModifiedTime() {
    return await fileLastModifiedTime(this.osuDbPath);
  }

  async hasChanged(): Promise<boolean> {
    return this.cachedTime !== (await this.getOsuDbLastModifiedTime());
  }

  get cachedTime() {
    return this.lastMtime;
  }

  async getAllBlueprints(): Promise<BlueprintInfo[]> {
    const lastModified = await this.getOsuDbLastModifiedTime();
    if (lastModified === this.lastMtime) {
      return this.blueprints;
    }
    console.log(`Reading the osu!.db with lastModifiedTime=${lastModified}`);

    this.lastMtime = lastModified;
    const reader = await this.createReader();
    const osuDB = await reader.readOsuDB();
    return (this.blueprints = osuDB.beatmaps.map(mapToBlueprint));
  }

  async getBlueprintByMD5(md5: string): Promise<BlueprintInfo | undefined> {
    const maps = await this.getAllBlueprints();
    return maps.find((m) => m.md5Hash === md5);
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/local/OsuFolderService.ts
================================================
import { injectable } from "inversify";
import { access } from "fs/promises";
import { join } from "path";
import { constants } from "fs";
import { BehaviorSubject } from "rxjs";
import { determineSongsFolder } from "@rewind/osu-local/utils";
import username from "username";
import { ipcRenderer } from "electron";
import { PersistentService } from "../../core/service";
import { JSONSchemaType } from "ajv";

const filesToCheck = ["osu!.db", "scores.db", "Skins"];

/**
 * Checks certain files to see if Rewind can be booted without any problems with the given `osuFolderPath`.
 * @param osuFolderPath the folder path to check the files in
 */
export async function osuFolderSanityCheck(osuFolderPath: string) {
  try {
    await Promise.all(filesToCheck.map((f) => access(join(osuFolderPath, f), constants.R_OK)));
  } catch (err) {
    console.log(err);
    return false;
  }
  return true;
}

interface OsuSettings {
  osuStablePath: string;
}

export const DEFAULT_OSU_SETTINGS: OsuSettings = Object.freeze({
  osuStablePath: "",
});
export const OsuSettingsSchema: JSONSchemaType<OsuSettings> = {
  type: "object",
  properties: {
    osuStablePath: { type: "string", default: DEFAULT_OSU_SETTINGS.osuStablePath },
  },
  required: [],
};

@injectable()
export class OsuFolderService extends PersistentService<OsuSettings> {
  public replaysFolder$ = new BehaviorSubject<string>("");
  public songsFolder$ = new BehaviorSubject<string>("");

  key = "osu-settings";
  schema = OsuSettingsSchema;

  constructor() {
    super();
    this.settings$.subscribe(this.onFolderChange.bind(this));
  }

  getDefaultValue(): OsuSettings {
    return DEFAULT_OSU_SETTINGS;
  }

  async onFolderChange(osuSettings: OsuSettings) {
    const { osuStablePath } = osuSettings;
    ipcRenderer.send("osuFolderChanged", osuStablePath);
    this.replaysFolder$.next(join(osuStablePath, "Replays"));
    const userId = await username();
    this.songsFolder$.next((await determineSongsFolder(osuStablePath, userId as string)) as string);
  }

  getOsuFolder(): string {
    return this.settings.osuStablePath;
  }

  setOsuFolder(path: string) {
    console.log(`osu! folder was set to '${path}'`);
    this.changeSettings((draft) => (draft.osuStablePath = path));
  }

  async isValidOsuFolder(directoryPath: string) {
    return osuFolderSanityCheck(directoryPath);
  }

  async hasValidOsuFolderSet(): Promise<boolean> {
    return this.isValidOsuFolder(this.getOsuFolder());
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/local/ReplayFileWatcher.ts
================================================
import { injectable } from "inversify";
import { Subject } from "rxjs";
import * as chokidar from "chokidar";
import { OsuFolderService } from "./OsuFolderService";

@injectable()
export class ReplayFileWatcher {
  public readonly newReplays$: Subject<string>;
  private watcher?: chokidar.FSWatcher;

  constructor(private readonly osuFolderService: OsuFolderService) {
    this.newReplays$ = new Subject<string>();
  }

  public startWatching() {
    this.osuFolderService.replaysFolder$.subscribe(this.onNewReplayFolder.bind(this));
  }

  // Unsubscribes from the old replay folder and starts listening on the new folder that was given
  // TODO: In the future we might want to watch on a list of folders and not just the osu! folder
  private onNewReplayFolder(folder: string) {
    // For now, we just use .close()
    // We could make it cleaner by using .unwatch() and adding new files to watch
    if (this.watcher) {
      void this.watcher.close();
    }

    const globPattern = folder;
    console.log(`Watching for replays (.osr) in folder: ${folder} with pattern: ${globPattern}`);
    this.watcher = chokidar.watch(globPattern, {
      // ignoreInitial must be true otherwise addDir will be triggered for every folder initially.
      ignoreInitial: true,
      persistent: true,
      depth: 0, // if somehow osu! is trolling, this will prevent it
    });
    this.watcher.on("ready", () => {
      console.log("");
    });
    this.watcher.on("add", (path) => {
      if (!path.endsWith(".osr")) {
        return;
      }
      console.log(`Detected new file at: ${path}`);
      this.newReplays$.next(path);
    });
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/local/ReplayService.ts
================================================
import { modsFromBitmask, parseReplayFramesFromRaw } from "@osujs/core";
import { injectable } from "inversify";
import { OsuReplay } from "../../../model/OsuReplay";
import { ipcRenderer } from "electron";

export type REPLAY_SOURCES = "OSU_API" | "FILE";

@injectable()
export class ReplayService {
  async retrieveReplay(replayId: string, source: REPLAY_SOURCES = "FILE"): Promise<OsuReplay> {
    const filePath = replayId;
    // const res = await readOsr(filePath);
    // Currently using readOsr is bugged, so we need to use it from the main process
    const res = await ipcRenderer.invoke("readOsr", filePath);
    return {
      gameVersion: res.gameVersion,
      frames: parseReplayFramesFromRaw(res.replay_data),
      mods: modsFromBitmask(res.mods),
      md5hash: res.replayMD5,
      beatmapMd5: res.beatmapMD5,
      player: res.playerName,
    };
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/local/SkinLoader.ts
================================================
import { Loader } from "@pixi/loaders";
import { Texture } from "pixi.js";
import { DEFAULT_SKIN_TEXTURE_CONFIG, OsuSkinTextures } from "@rewind/osu/skin";
import { inject, injectable } from "inversify";
import { Skin, SkinTexturesByKey } from "../../../model/Skin";
import { SkinId } from "../../../model/SkinId";
import { OsuFolderService } from "./OsuFolderService";
import { GetTextureFileOption, OsuSkinTextureResolver, SkinFolderReader } from "@rewind/osu-local/skin-reader";
import { join } from "path";
import { STAGE_TYPES } from "../../types";

export type SkinTextureLocation = { key: OsuSkinTextures; paths: string[] };

async function startLoading(loader: Loader, skinName: string): Promise<boolean> {
  return new Promise<boolean>((resolve, reject) => {
    loader.onStart.once(() => {
      console.log(`Skin loading started for ${skinName}`);
    });

    loader.onComplete.once(() => {
      console.debug(`Skin loading completed for ${skinName}`);
      resolve(true);
    });

    loader.onError.once((resource) => {
      console.error(`Could not load resource ${resource.name}`);
    });
    loader.load();
  });
}

const OSU_DEFAULT_SKIN_ID: SkinId = { source: "rewind", name: "OsuDefaultSkin" };

@injectable()
export class SkinLoader {
  skins: { [key: string]: Skin };
  skinElementCounter = 0;

  // Maybe generalize it to skinSource or something
  // TODO: Maybe just load into TextureManager
  constructor(
    private osuFolderService: OsuFolderService,
    @inject(STAGE_TYPES.REWIND_SKINS_FOLDER) private rewindSkinsFolder: string,
  ) {
    this.skins = {};
  }

  // TODO: AppResourcesPath and get the included Rewind Skins

  async loadSkinList() {
    return SkinFolderReader.listSkinsInFolder(join(this.osuFolderService.getOsuFolder(), "Skins"), {
      skinIniRequired: false,
    });
  }

  sourcePath(source: string) {
    switch (source) {
      case "rewind":
        return this.rewindSkinsFolder;
      case "osu":
        return join(this.osuFolderService.getOsuFolder(), "Skins");
    }
    return "";
  }

  resolveToPath({ source, name }: SkinId) {
    return join(this.sourcePath(source), name);
  }

  /**
   * In the future we also want to get the skin info with the beatmap as the parameters in order to retrieve
   * beatmap related skin files as well.
   */
  async resolve(
    osuSkinTexture: OsuSkinTextures,
    options: GetTextureFileOption,
    list: { prefix: string; resolver: OsuSkinTextureResolver }[],
  ) {
    for (const { prefix, resolver } of list) {
      const filePaths = await resolver.resolve(osuSkinTexture, options);
      if (filePaths.length === 0) {
        continue;
      }
      return filePaths.map((path) => `${prefix}/${path}`);
    }
    console.debug(`No skin has the skin texture ${osuSkinTexture}`);
    return [];
  }

  // force such like reloading
  async loadSkin(skinId: SkinId, forceReload?: boolean): Promise<Skin> {
    const id = `${skinId.source}/${skinId.name}`;
    if (this.skins[id] && !forceReload) {
      console.info(`Skin ${id} is already loaded, using the one in cache`);
      return this.skins[id];
    }
    console.log(`Loading skin with name: ${skinId.name} with source ${skinId.source}`);
    const loader = new Loader();

    const osuDefaultSkinResolver = await SkinFolderReader.getSkinResolver(this.resolveToPath(OSU_DEFAULT_SKIN_ID));
    const skinResolver = await SkinFolderReader.getSkinResolver(this.resolveToPath(skinId));

    const { config } = skinResolver;

    const skinTextureKeys = Object.keys(DEFAULT_SKIN_TEXTURE_CONFIG);
    const files = await Promise.all(
      skinTextureKeys.map(async (key) => ({
        key,
        paths: await this.resolve(key as OsuSkinTextures, { animatedIfExists: true, hdIfExists: true }, [
          // In the future beatmap stuff can be listed here as well
          {
            prefix: join("file://", this.resolveToPath(skinId)),
            resolver: skinResolver,
          },
          {
            prefix: join("file://", this.resolveToPath(OSU_DEFAULT_SKIN_ID)),
            resolver: osuDefaultSkinResolver,
          },
        ]),
      })),
    );

    const textures: SkinTexturesByKey = {};
    const skinName = config.general.name;

    // Every file, even non-animatable files have the index in their name
    // For example "WhiteCat/hitcircle-0" would be one key name

    const queueFiles: { key: OsuSkinTextures; name: string }[] = [];
    files.forEach((stl) => {
      stl.paths.forEach((path, index) => {
        // `Loader` will die if the same `name` gets used twice therefore the unique skinElementCounter
        // Maybe even use a timestamp
        const name = `${this.skinElementCounter++}/${skinName}/${stl.key}-${index}`;
        loader.add(name, path);
        queueFiles.push({ key: stl.key as OsuSkinTextures, name });
      });
    });

    const loaded = await startLoading(loader, skinName);

    if (!loaded) {
      throw new Error(`${skinName} not loaded correctly`);
    }

    queueFiles.forEach((file) => {
      if (!(file.key in textures)) textures[file.key] = [];
      textures[file.key]?.push(loader.resources[file.name].texture as Texture);
    });

    return (this.skins[id] = new Skin(config, textures));
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/local-storage.ts
================================================
// Will store the settings after a couple seconds
import Ajv, { JSONSchemaType, ValidateFunction } from "ajv";

const ajv = new Ajv({ useDefaults: true });

export class LocalStorageLoader<T> {
  validate: ValidateFunction<T>;

  constructor(private readonly localStorageKey: string, schema: JSONSchemaType<T>, private readonly defaultValue: T) {
    this.validate = ajv.compile<T>(schema);
  }

  loadFromLocalStorage(): T {
    const { validate, localStorageKey, defaultValue } = this;
    const str = window.localStorage.getItem(localStorageKey);
    if (str === null) {
      // This is very expected on first time start up
      return defaultValue;
    }
    let json;
    try {
      json = JSON.parse(str);
    } catch (e) {
      console.warn(`Could not parse '${localStorageKey}' as a JSON properly: ${str}`);
      return defaultValue;
    }

    if (!validate(json)) {
      console.warn(`JSON '${localStorageKey}' was not validated properly: ${JSON.stringify(validate.errors)}`);
      return defaultValue;
    } else {
      return json;
    }
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/playbar.ts
================================================
import { PersistentService } from "../core/service";
import { injectable } from "inversify";
import { JSONSchemaType } from "ajv";

export interface PlaybarSettings {
  difficultyGraphEnabled: boolean;
}

export const DEFAULT_PLAY_BAR_SETTINGS: PlaybarSettings = Object.freeze({
  difficultyGraphEnabled: true,
});

export const PlaybarSettingsSchema: JSONSchemaType<PlaybarSettings> = {
  type: "object",
  properties: {
    difficultyGraphEnabled: { type: "boolean", default: DEFAULT_PLAY_BAR_SETTINGS.difficultyGraphEnabled },
  },
  required: [],
};

@injectable()
export class PlaybarSettingsStore extends PersistentService<PlaybarSettings> {
  key = "playbar";
  schema = PlaybarSettingsSchema;

  getDefaultValue(): PlaybarSettings {
    return DEFAULT_PLAY_BAR_SETTINGS;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/playfield-border.ts
================================================
import { injectable } from "inversify";
import { BehaviorSubject } from "rxjs";
import { PlayfieldBorderSettings } from "@rewind/osu-pixi/classic-components";

const DEFAULT_SETTINGS: PlayfieldBorderSettings = {
  enabled: true,
  thickness: 2,
};

// TODO: Make this configurable, currently it's just hardcoded

@injectable()
export class PlayfieldBorderSettingsStore {
  settings$: BehaviorSubject<PlayfieldBorderSettings>;

  constructor() {
    this.settings$ = new BehaviorSubject<PlayfieldBorderSettings>(DEFAULT_SETTINGS);
  }

  get settings() {
    return this.settings$.getValue();
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/replay-cursor.ts
================================================
import { PersistentService } from "../core/service";
import { JSONSchemaType } from "ajv";
import { CursorSettings } from "./cursor";
import { injectable } from "inversify";

export interface ReplayCursorSettings extends CursorSettings {
  showTrail: boolean;
  smoothCursorTrail: boolean;
}

export const DEFAULT_REPLAY_CURSOR_SETTINGS: ReplayCursorSettings = Object.freeze({
  showTrail: true,
  scale: 0.8,
  enabled: true,
  scaleWithCS: true,
  smoothCursorTrail: true,
});
export const ReplayCursorSettingsSchema: JSONSchemaType<ReplayCursorSettings> = {
  type: "object",
  properties: {
    showTrail: { type: "boolean", default: DEFAULT_REPLAY_CURSOR_SETTINGS.showTrail },
    scale: { type: "number", default: DEFAULT_REPLAY_CURSOR_SETTINGS.scale },
    enabled: { type: "boolean", default: DEFAULT_REPLAY_CURSOR_SETTINGS.enabled },
    scaleWithCS: { type: "boolean", default: DEFAULT_REPLAY_CURSOR_SETTINGS.scaleWithCS },
    smoothCursorTrail: { type: "boolean", default: DEFAULT_REPLAY_CURSOR_SETTINGS.smoothCursorTrail },
  },
  required: [],
};

@injectable()
export class ReplayCursorSettingsStore extends PersistentService<ReplayCursorSettings> {
  key = "replay-cursor";
  schema = ReplayCursorSettingsSchema;

  getDefaultValue(): ReplayCursorSettings {
    return DEFAULT_REPLAY_CURSOR_SETTINGS;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/scenes/IScene.ts
================================================
// Scene that needs to be implemented by the user
// Ideas taken from Phaser3 https://rexrainbow.github.io/phaser3-rex-notes/docs/site/scene/
import { Container } from "pixi.js";

export interface UserScene {
  // Initialize
  init(data: unknown): void;

  // Loading assets
  preload(): Promise<void>;

  // Creates stage objects
  create(): void;

  // This function is updated every tick.
  update(deltaTimeInMs: number): void;

  // Free resources, unsubscribe from events, etc.
  destroy(): void;

  stage: Container;
}

// Not to be implemented by the user

type ManagedSceneState = "INITIALIZING" | "PLAYING" | "PAUSED" | "SLEEPING";

export class ManagedScene implements UserScene {
  state: ManagedSceneState = "INITIALIZING";

  constructor(private readonly scene: UserScene, public readonly key: string) {}

  get stage() {
    return this.scene.stage;
  }

  pause() {
    this.state = "PAUSED";
  }

  resume() {
    this.state = "PLAYING";
  }

  sleep() {
    this.state = "SLEEPING";
  }

  destroy(): void {
    return this.scene.destroy();
  }

  init(data: unknown): void {
    return this.scene.init(data);
  }

  async preload(): Promise<void> {
    return this.scene.preload();
  }

  update(deltaTimeInMs: number): void {
    if (this.state === "PLAYING") {
      // console.log(`Scene ${this.key} is getting updated with dt=${deltaTimeInMs}ms`);
      return this.scene.update(deltaTimeInMs);
    }
  }

  create(): void {
    return this.scene.create();
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/scenes/SceneManager.ts
================================================
// Manages the scene
import { Container } from "pixi.js";
import { ManagedScene, UserScene } from "./IScene";
import { injectable } from "inversify";

@injectable()
export class SceneManager {
  managedScene: ManagedScene[] = [];
  container: Container;

  constructor() {
    this.container = new Container();
  }

  add(scene: UserScene, key: string) {
    const managedScene = new ManagedScene(scene, key);
    this.managedScene.push(managedScene);
  }

  async start(scene: ManagedScene, data: unknown) {
    console.log(`Starting the scene with the key='${scene.key}'`);
    scene.state = "INITIALIZING";
    scene.init(data);
    await scene.preload();
    scene.create();
    scene.state = "PLAYING";
  }

  stop(scene: ManagedScene) {
    scene.destroy();
    scene.state = "SLEEPING";
  }

  update(deltaTimeMs: number) {
    this.managedScene.forEach((scene) => scene.update(deltaTimeMs));
  }

  sceneByKey(key: string) {
    return this.managedScene.find((s) => s.key === key);
  }

  createStage() {
    this.container.removeChildren();
    this.managedScene.forEach((scene) => {
      if (scene.state === "PLAYING" || scene.state === "PAUSED") {
        this.container.addChild(scene.stage);
      }
    });
    return this.container;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/common/skin.ts
================================================
import { PersistentService } from "../core/service";
import { injectable } from "inversify";
import { JSONSchemaType } from "ajv";
import { BehaviorSubject } from "rxjs";
import { SkinLoader } from "./local/SkinLoader";
import { SkinId, skinIdToString, stringToSkinId } from "../../model/SkinId";
import { Skin } from "../../model/Skin";

export interface SkinSettings {
  // fallbackSkinId is rewind:OsuDefaultSkin
  preferredSkinId: string;
}

export const DEFAULT_SKIN_SETTINGS: SkinSettings = Object.freeze({
  preferredSkinId: "rewind:RewindDefaultSkin",
});
export const SkinSettingsSchema: JSONSchemaType<SkinSettings> = {
  type: "object",
  properties: {
    preferredSkinId: { type: "string", default: DEFAULT_SKIN_SETTINGS.preferredSkinId },
  },
  required: ["preferredSkinId"],
};

@injectable()
export class SkinSettingsStore extends PersistentService<SkinSettings> {
  key = "Skin";
  schema = SkinSettingsSchema;

  getDefaultValue(): SkinSettings {
    return DEFAULT_SKIN_SETTINGS;
  }

  setPreferredSkinId(preferredSkinId: string) {
    this.changeSettings((s) => (s.preferredSkinId = preferredSkinId));
  }
}

@injectable()
export class SkinHolder {
  private skin: Skin;

  constructor() {
    this.skin = Skin.EMPTY;
  }

  getSkin(): Skin {
    return this.skin;
  }

  setSkin(skin: Skin) {
    this.skin = skin;
  }
}

@injectable()
export class SkinManager {
  skinList$: BehaviorSubject<string[]>;

  constructor(
    private readonly skinLoader: SkinLoader,
    private readonly skinSettingsStore: SkinSettingsStore,
    private readonly skinHolder: SkinHolder,
  ) {
    this.skinList$ = new BehaviorSubject<string[]>([]);
  }

  async loadSkin(skinId: SkinId) {
    const skin = await this.skinLoader.loadSkin(skinId);
    this.skinSettingsStore.setPreferredSkinId(skinIdToString(skinId));
    this.skinHolder.setSkin(skin);
  }

  async loadPreferredSkin() {
    const { preferredSkinId } = this.skinSettingsStore.settings;

    try {
      console.log(`Loading preferred skin from preferences: ${preferredSkinId}`);
      const skinId = stringToSkinId(preferredSkinId);
      await this.loadSkin(skinId);
    } catch (e) {
      console.error(`Could not load preferred skin '${preferredSkinId}' so falling back to default`);
      this.skinSettingsStore.setPreferredSkinId(DEFAULT_SKIN_SETTINGS.preferredSkinId);
      await this.loadSkin(stringToSkinId(DEFAULT_SKIN_SETTINGS.preferredSkinId));
    }
  }

  async loadSkinList() {
    this.skinList$.next(await this.skinLoader.loadSkinList());
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/core/service.ts
================================================
import { BehaviorSubject } from "rxjs";
import { Draft } from "immer/dist/types/types-external";
import produce from "immer";
import { injectable, postConstruct } from "inversify";
import { LocalStorageLoader } from "../common/local-storage";
import { JSONSchemaType } from "ajv";
import { debounceTime } from "rxjs/operators";

@injectable()
export abstract class StatefulService<T> {
  settings$: BehaviorSubject<T>;

  constructor() {
    this.settings$ = new BehaviorSubject<T>(this.getDefaultValue());
  }

  abstract getDefaultValue(): T;

  getSettings() {
    return this.settings;
  }

  changeSettings(fn: (draft: Draft<T>) => unknown) {
    this.settings = produce(this.settings, (draft) => {
      fn(draft);
      // We explicitly return here so that `fn` can return anything
      return;
    });
  }

  getSubject() {
    return this.settings$;
  }

  get settings() {
    return this.settings$.getValue();
  }

  set settings(s: T) {
    this.settings$.next(s);
  }
}

const DEBOUNCE_TIME_IN_MS = 500;

@injectable()
export abstract class PersistentService<T> extends StatefulService<T> {
  abstract key: string;
  abstract schema: JSONSchemaType<T>;

  @postConstruct()
  init() {
    const localStorageLoader = new LocalStorageLoader<T>(this.key, this.schema, this.getDefaultValue());
    this.settings$.next(localStorageLoader.loadFromLocalStorage());
    this.settings$.pipe(debounceTime(DEBOUNCE_TIME_IN_MS)).subscribe((s) => {
      // Store the serialized version into the LocalStorage
      window.localStorage.setItem(this.key, JSON.stringify(s));
    });
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/manager/AnalysisSceneManager.ts
================================================
import { SceneManager } from "../common/scenes/SceneManager";
import { injectable } from "inversify";
import { AnalysisScene } from "../analysis/scenes/AnalysisScene";
import { IdleScene } from "../analysis/scenes/IdleScene";
import { ManagedScene } from "../common/scenes/IScene";

export enum AnalysisSceneKeys {
  IDLE = "idle",
  LOADING = "loading",
  ANALYSIS = "analysis",
}

@injectable()
export class AnalysisSceneManager {
  currentScene?: ManagedScene;

  constructor(
    private readonly sceneManager: SceneManager,
    private readonly analysisScene: AnalysisScene,
    private readonly idleScene: IdleScene,
  ) {
    sceneManager.add(analysisScene, AnalysisSceneKeys.ANALYSIS);
    sceneManager.add(idleScene, AnalysisSceneKeys.IDLE);
  }

  async changeToScene(sceneKey: AnalysisSceneKeys, data?: any) {
    if (this.currentScene) {
      this.sceneManager.stop(this.currentScene);
    }
    const scene = this.sceneManager.sceneByKey(sceneKey);
    if (scene) {
      this.currentScene = scene;
      return this.sceneManager.start(scene, data);
    }
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/manager/BeatmapManager.ts
================================================
// Holds the beatmap
import { Beatmap } from "@osujs/core";
import { injectable } from "inversify";
import { BehaviorSubject } from "rxjs";

@injectable()
export class BeatmapManager {
  beatmap$: BehaviorSubject<Beatmap> = new BehaviorSubject<Beatmap>(Beatmap.EMPTY_BEATMAP);

  setBeatmap(beatmap: Beatmap) {
    this.beatmap$.next(beatmap);
  }

  getBeatmap() {
    return this.beatmap$.value;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/manager/ClipRecorder.ts
================================================
import { injectable } from "inversify";
import { PixiRendererManager } from "../renderers/PixiRendererManager";
import { BehaviorSubject } from "rxjs";

// This is too slow
@injectable()
export class ClipRecorder {
  chunks: Blob[] = [];
  recordingSince$: BehaviorSubject<number>;
  recorder?: MediaRecorder;

  constructor(private readonly renderManager: PixiRendererManager) {
    this.recordingSince$ = new BehaviorSubject<number>(0);
  }

  startRecording() {
    const renderer = this.renderManager.getRenderer();
    if (!renderer) {
      console.error("Can not start recording without a renderer");
      return;
    }
    this.recordingSince$.next(performance.now());
    this.chunks = [];

    const canvas = renderer.view;
    const context = canvas.getContext("webgl2");
    // context.getImageData()
    const frameRate = 30;

    const stream = canvas.captureStream(frameRate);
    this.recorder = new MediaRecorder(stream);
    // Every time the recorder has new data, we will store it in our array
    this.recorder.ondataavailable = (e) => this.chunks.push(e.data);
    this.recorder.onstop = (e) => {
      this.exportVideo();
    };
    this.recorder.start();
  }

  exportVideo() {
    console.log(`Recording chunks.size = ${this.chunks.length}`);
    this.recordingSince$.next(0);

    const blob = new Blob(this.chunks, { type: "video/webm" });
    const vid = document.createElement("video");
    vid.src = URL.createObjectURL(blob);
    vid.controls = true;

    const a = document.createElement("a");
    a.href = vid.src;
    a.download = `Rewind Clip ${new Date().toISOString()}.webm`;
    a.click();
    a.remove();
  }

  stopRecording() {
    this.recorder?.stop();
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/manager/ReplayManager.ts
================================================
// Holds the replays
import { injectable } from "inversify";
import { OsuReplay } from "../../model/OsuReplay";
import { BehaviorSubject } from "rxjs";

@injectable()
export class ReplayManager {
  // mainReplay: OsuReplay | null = null;
  mainReplay$: BehaviorSubject<OsuReplay | null>;

  constructor() {
    this.mainReplay$ = new BehaviorSubject<OsuReplay | null>(null);
  }

  getMainReplay() {
    return this.mainReplay$.value;
  }

  setMainReplay(replay: OsuReplay | null) {
    this.mainReplay$.next(replay);
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/manager/ScenarioManager.ts
================================================
import { injectable } from "inversify";
import { BehaviorSubject } from "rxjs";
import { Beatmap, buildBeatmap, modsToBitmask, parseBlueprint } from "@osujs/core";
import { GameSimulator } from "../common/game/GameSimulator";
import { AudioService } from "../common/audio/AudioService";
import { ReplayService } from "../common/local/ReplayService";
import { BeatmapManager } from "./BeatmapManager";
import { ReplayManager } from "./ReplayManager";
import { AnalysisSceneKeys, AnalysisSceneManager } from "./AnalysisSceneManager";
import { AudioEngine } from "../common/audio/AudioEngine";
import { GameplayClock } from "../common/game/GameplayClock";
import { GameLoop } from "../common/game/GameLoop";
import { PixiRendererManager } from "../renderers/PixiRendererManager";
import { join } from "path";
import { readFile } from "fs/promises";
import { BlueprintLocatorService } from "../common/local/BlueprintLocatorService";
import { OsuFolderService } from "../common/local/OsuFolderService";
import { BeatmapBackgroundSettingsStore } from "../common/beatmap-background";
import { TextureManager } from "../textures/TextureManager";
import { ReplayFileWatcher } from "../common/local/ReplayFileWatcher";
import { ModSettingsService } from "../analysis/mod-settings";

interface Scenario {
  status: "LOADING" | "ERROR" | "DONE" | "INIT";
}

function localFile(path: string) {
  return `file://${path}`;
}

@injectable()
export class ScenarioManager {
  public scenario$: BehaviorSubject<Scenario>;

  constructor(
    private readonly gameClock: GameplayClock,
    private readonly renderer: PixiRendererManager,
    private readonly gameLoop: GameLoop,
    private readonly gameSimulator: GameSimulator,
    private readonly modSettingsService: ModSettingsService,
    private readonly blueprintLocatorService: BlueprintLocatorService,
    private readonly osuFolderService: OsuFolderService,
    private readonly audioService: AudioService,
    private readonly textureManager: TextureManager,
    private readonly replayService: ReplayService,
    private readonly beatmapBackgroundSettingsStore: BeatmapBackgroundSettingsStore,
    private readonly beatmapManager: BeatmapManager,
    private readonly replayManager: ReplayManager,
    private readonly sceneManager: AnalysisSceneManager,
    private readonly replayWatcher: ReplayFileWatcher,
    private readonly audioEngine: AudioEngine,
  ) {
    this.scenario$ = new BehaviorSubject<Scenario>({ status: "INIT" });
  }

  public initialize() {
    this.replayWatcher.newReplays$.subscribe((replayId) => {
      void this.loadReplay(replayId);
    });
  }

  // This is a temporary solution to
  async clearReplay() {
    this.gameClock.clear();
    this.replayManager.setMainReplay(null);
    this.audioEngine.destroy();
    this.renderer.getRenderer()?.clear();
    this.beatmapManager.setBeatmap(Beatmap.EMPTY_BEATMAP);
    this.gameSimulator.clear();
    this.gameLoop.stopTicker();
    // await this.sceneManager.changeToScene(AnalysisSceneKeys.IDLE);
    this.scenario$.next({ status: "INIT" });
  }

  async loadReplay(replayId: string) {
    console.log(`ScenarioManager loading replay with id = ${replayId}`);
    // TODO: Clean this up
    this.audioEngine.destroy();

    this.scenario$.next({ status: "LOADING" });

    const replay = await this.replayService.retrieveReplay(replayId);
    const blueprintInfo = await this.blueprintLocatorService.getBlueprintByMD5(replay.beatmapMd5);
    if (!blueprintInfo) throw Error(`Could not find the blueprint with MD5=${replay.beatmapMd5}`);

    const absoluteFolderPath = join(this.osuFolderService.songsFolder$.getValue(), blueprintInfo.folderName);

    const rawBlueprint = await readFile(join(absoluteFolderPath, blueprintInfo.osuFileName), "utf-8");
    const blueprint = parseBlueprint(rawBlueprint);

    const { metadata } = blueprint.blueprintInfo;

    // Load background
    this.beatmapBackgroundSettingsStore.texture$.next(
      await this.textureManager.loadTexture(localFile(join(absoluteFolderPath, metadata.backgroundFile))),
    );

    // Load audio
    this.audioEngine.setSong(
      await this.audioService.loadAudio(localFile(join(absoluteFolderPath, metadata.audioFile))),
    );
    this.audioEngine.song?.mediaElement.addEventListener("loadedmetadata", () => {
      const duration = (this.audioEngine.song?.mediaElement.duration ?? 0) * 1000;
      this.gameClock.setDuration(duration);
      this.gameSimulator.calculateDifficulties(rawBlueprint, duration, modsToBitmask(replay.mods));
    });

    // If the building is too slow or unbearable, we should push the building to a WebWorker, but right now it's ok
    // even on long maps.
    const beatmap = buildBeatmap(blueprint, { addStacking: true, mods: replay.mods });

    console.log(`Beatmap built with ${beatmap.hitObjects.length} hitobjects`);
    console.log(`Replay loaded with ${replay.frames.length} frames`);
    const modHidden = replay.mods.includes("HIDDEN");
    const initialSpeed = beatmap.gameClockRate;

    this.modSettingsService.setHidden(modHidden);
    // Not supported yet
    this.modSettingsService.setFlashlight(false);

    this.gameClock.pause();
    this.gameClock.setSpeed(initialSpeed);
    this.gameClock.seekTo(0);
    this.beatmapManager.setBeatmap(beatmap);
    this.replayManager.setMainReplay(replay);

    await this.gameSimulator.simulateReplay(beatmap, replay);
    await this.sceneManager.changeToScene(AnalysisSceneKeys.ANALYSIS);

    this.gameLoop.startTicker();
    this.scenario$.next({ status: "DONE" });
  }

  // This is just the NM view of a beatmap
  async loadBeatmap(blueprintId: string) {
    // Set speed to 1.0
    this.gameClock.setSpeed(1.0);
    this.gameClock.seekTo(0);
    this.modSettingsService.setHidden(false);
    this.replayManager.setMainReplay(null);
  }

  async addSubReplay() {
    // Only possible if `mainReplay` is loaded
    // Adjust y-flip according to `mainReplay`
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/renderers/PixiRendererManager.ts
================================================
import * as PIXI from "pixi.js";
import { injectable } from "inversify";

@injectable()
export class PixiRendererManager {
  // RendererPreferenceSettingsService to get preferences
  private renderer?: PIXI.Renderer;
  private canvas?: HTMLCanvasElement;

  initializeRenderer(canvas: HTMLCanvasElement) {
    // Destroy old renderer
    this.canvas = canvas;
    this.renderer = new PIXI.Renderer({ view: canvas, antialias: true });
    this.resizeCanvasToDisplaySize();
  }

  // https://webgl2fundamentals.org/webgl/lessons/webgl-anti-patterns.html
  // https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth
  resizeCanvasToDisplaySize() {
    const canvas = this.canvas;
    if (!canvas || !this.renderer) {
      return false;
    }
    // If it's resolution does not match change it
    if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
      // canvas.width/height will be set by renderer.resize()
      this.renderer.resize(canvas.clientWidth, canvas.clientHeight);
      console.log(`Canvas dimensions have been set to ${canvas.width} x ${canvas.height}`);
      return true;
    }
    return false;
  }

  destroy() {
    this.renderer?.clear();
    this.renderer?.destroy();
    this.renderer = undefined;
  }

  getRenderer(): PIXI.Renderer | undefined {
    return this.renderer;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/renderers/components/background/BeatmapBackground.ts
================================================
import { Sprite, Texture } from "pixi.js";
import { BlurFilter } from "@pixi/filter-blur";

import { injectable } from "inversify";
import { BeatmapBackgroundSettings, BeatmapBackgroundSettingsStore } from "../../../common/beatmap-background";

const MAX_BLUR_STRENGTH = 15;

interface Dimensions {
  width: number;
  height: number;
}

export class BeatmapBackground {
  public sprite: Sprite;

  constructor(private readonly stageDimensions: Dimensions) {
    this.sprite = new Sprite(); // No pooling needed
  }

  onSettingsChange(beatmapBackgroundSettings: BeatmapBackgroundSettings) {
    const { enabled, dim, blur } = beatmapBackgroundSettings;
    this.sprite.alpha = 1.0 - dim;
    this.sprite.filters = [new BlurFilter(blur * MAX_BLUR_STRENGTH)];
    this.sprite.renderable = enabled;
  }

  onTextureChange(texture: Texture) {
    this.sprite.texture = texture;
    // TODO: STAGE_WIDTH is kinda hardcoded
    const scaling = this.stageDimensions.width / texture.width;
    this.sprite.scale.set(scaling, scaling);
  }
}

@injectable()
export class BeatmapBackgroundFactory {
  constructor(private readonly settingsStore: BeatmapBackgroundSettingsStore) {}

  createBeatmapBackground(stageDimensions: Dimensions) {
    const beatmapBackground = new BeatmapBackground(stageDimensions);
    this.settingsStore.settings$.subscribe((s) => beatmapBackground.onSettingsChange(s));
    this.settingsStore.texture$.subscribe((t) => beatmapBackground.onTextureChange(t));
    return beatmapBackground;
  }
}


================================================
FILE: apps/desktop/frontend/src/app/services/renderers/components/hud/ForegroundHUDPreparer.ts
================================================
import { injectable } from "inversify";
import { GameSimulator } from "../../../common/game/GameSimulator";
import { Container, Text } from "pixi.js";
import {
  calculateDigits,
  OsuClassicAccuracy,
  OsuClassicHitErrorBar,
  OsuClassicNumber,
} from "@rewind/osu-pixi/classic-components";
import { STAGE_HEIGHT, STAGE_WIDTH } from "../../constants";
import { formatGameTime, hitWindowsForOD } from "@osujs/math";
import { GameplayClock } from "../../../common/game/GameplayClock";
import { BeatmapManager } from "../../../manager/BeatmapManager";
import { mean, standardDeviation } from "simple-statistics";
import { HitErrorBarSettingsStore } from "../../../common/hit-error-bar";
import { SkinHolder } from "../../../common/skin";

function calculateUnstableRate(x: number[]) {
  return x.length === 0 ? 0 : standardDeviation(x) * 10;
}

function calculateMean(x: number[]) {
  return x.length === 0 ? 0 : mean(x);
}

@injectable()
export class ForegroundHUDPreparer {
  container: Container;
  stats: Text;
  hitErrorBar: OsuClassicHitErrorBar;

  hitMinIndex = 0;
  hitMaxIndex = 0;

  // these are game clock rate adjusted
  allHits: number[] = [];
  recentHits: number[] = [];

  constructor(
    private readonly beatmapManager: BeatmapManager,
    private readonly skinManager: SkinHolder,
    private readonly gameSimulator: GameSimulator,
    private readonly gameplayClock: GameplayClock,
    private readonly hitErrorBarSettingsStore: HitErrorBarSettingsStore,
  ) {
    this.container = new Container();
    this.stats = new Text("", { fontSize: 16, fill: 0xeeeeee, fontFamily: "Arial", align: "left" });
    this.hitErrorBar = new OsuClassicHitErrorBar();
  }

  updateHitErrorBar() {
    const time = this.gameplayClock.timeElapsedInMs;

    const beatmap = this.beatmapManager.getBeatmap();
    const gameClockRate = beatmap.gameClockRate;
    const maxHitErrorTime = 10000;
    const hits: any[] = [];
    this.allHits = [];
    this.recentHits = [];
    for (const [judgementTime, offset, hit] of this.gameSimulator.hits) {
      const timeAgo = time - judgementTime;
      if (timeAgo < 0) break;
      if (timeAgo <= maxHitErrorTime) hits.push({ timeAgo: time - judgementTime, offset, miss: !hit });
      if (hit) {
        this.allHits.push(offset / gameClockRate);
        if (timeAgo < 1000) this.recentHits.push(offset / gameClockRate);
      }
    }

    // if (h.length === 0) {
    //   return;
    // }
    // if (this.lastHits !== h) {
    //   this.hitMinIndex = this.hitMaxIndex = 0;
    //   this.lastHits = h;
    // }

    // TODO: This needs to be reset in case hits gets changed

    // while (this.hitMaxIndex + 1 < h.length && h[this.hitMaxIndex + 1][0] <= time) this.hitMaxIndex++;
    // while (this.hitMaxIndex >= 0 && h[this.hitMaxIndex][0] > time) this.hitMaxIndex--;
    //
    // while (this.hitMinIndex + 1 < h.length && h[this.hitMinIndex + 1][0] < time - maxTime) this.hitMinIndex++;
    // while (this.hitMinIndex >= 0 && h[this.hitMinIndex][0] >= time - maxTime) this.hitMinIndex--;
    //
    // const hits: any[] = [];
    // for (let i = this.hitMinIndex + 1; i < this.hitMaxIndex; i++) {
    //   hits.push({ timeAgo: time - h[i][0], offset: h[i][1], miss: !h[i][2] });
    // }
    // console.log(hits.length);

    const [hitWindow300, hitWindow100, hitWindow50] = hitWindowsForOD(beatmap.difficulty.overallDifficulty);
    this.hitErrorBar.prepare({
      hitWindow50,
      hitWindow100,
      hitWindow300,
      hits,
      // hits: [
      //   { timeAgo: 100, offset: -2 },
      //   { timeAgo: 2, offset: +10 },
      // ],
    });
    this.hitErrorBar.container.position.set(STAGE_WIDTH / 2, STAGE_HEIGHT - 20);
    this.hitErrorBar.container.scale.set(this.hitErrorBarSettingsStore.settings.scale);
    this.container.addChild(this.hitErrorBar.container);
  }

  updateComboNumber() {
    const skin = this.skinManager.getSkin();
    const gameplayInfo = this.gameSimulator.getCurrentInfo();

    if (gameplayInfo) {
      const comboNumber = new OsuClassicNumber();
      const textures = skin.getComboNumberTextures();
      const overlap = skin.config.fonts.comboOverlap;
      comboNumber.prepare({ digits: calculateDigits(gameplayInfo.currentCombo), textures, overlap });
      comboNumber.position.set(0, STAGE_HEIGHT - 50);
      this.container.addChild(comboNumber);
    }
  }

  updateHUD() {
    this.container.removeChildren();
    this.updateComboNumber();
    this.updateAccuracy();
    this.updateHitErrorBar();
    this.updateStats();
  }

  private updateAccuracy() {
    const skin = this.skinManager.getSkin();
    const gameplayInfo = this.gameSimulator.getCurrentInfo();
    if (gameplayInfo) {
      // const text
      const accNumber = new OsuClassicAccuracy();
      const digitTextures = skin.getScoreTextures();
      const dotTexture = skin.getTexture("SCORE_DOT");
      const percentageTexture = skin.getTexture("SCORE_PERCENT");
      const overlap = skin.config.fonts.scoreOverlap;
      accNumber.prepare({ accuracy: gameplayInfo.accuracy, digitTextures, dotTexture, percentageTexture, overlap });
      accNumber.container.position.set(STAGE_WIDTH - 15, 25);
      this.container.addChild(accNumber.container);
    }
  }

  private updateStats() {
    const gameplayInfo = this.gameSimulator.getCurrentInfo();
    const gameplayState = this.gameSimulator.getCurrentState();
    const time = this.gameplayClock.timeElapsedInMs;

    if (gameplayInfo && gameplayState) {
      const count = gameplayInfo.verdictCounts;
      const maxCombo = gameplayInfo.maxComboSoFar;

      const digits = 2;
      const globalMean = calculateMean(this.allHits);
      const globalDeviation = calculateUnstableRate(this.allHits);

      const localMean = calculateMean(this.recentHits);
      const localDeviation = calculateUnstab
Download .txt
gitextract_icqr6nvq/

├── .editorconfig
├── .eslintrc.json
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       └── build-release.yml
├── .gitignore
├── .gitmodules
├── .prettierignore
├── .prettierrc
├── .storybook/
│   ├── main.js
│   ├── preview.js
│   ├── tsconfig.json
│   └── webpack.config.js
├── .vscode/
│   └── extensions.json
├── .yarnrc
├── CONTRIBUTION.md
├── LICENSE.md
├── README.md
├── apps/
│   ├── desktop/
│   │   ├── README.md
│   │   ├── frontend/
│   │   │   ├── .babelrc
│   │   │   ├── .browserslistrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── .storybook/
│   │   │   │   ├── main.js
│   │   │   │   ├── preview.js
│   │   │   │   ├── tsconfig.json
│   │   │   │   └── webpack.config.js
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── proxy.conf.json
│   │   │   ├── src/
│   │   │   │   ├── app/
│   │   │   │   │   ├── RewindApp.tsx
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── analyzer/
│   │   │   │   │   │   │   ├── BaseAudioSettingsPanel.tsx
│   │   │   │   │   │   │   ├── BaseCurrentTime.stories.tsx
│   │   │   │   │   │   │   ├── BaseCurrentTime.tsx
│   │   │   │   │   │   │   ├── BaseDialog.tsx
│   │   │   │   │   │   │   ├── BaseGameTimeSlider.stories.tsx
│   │   │   │   │   │   │   ├── BaseGameTimeSlider.tsx
│   │   │   │   │   │   │   ├── BaseSettingsModal.stories.tsx
│   │   │   │   │   │   │   ├── BaseSettingsModal.tsx
│   │   │   │   │   │   │   ├── GameCanvas.tsx
│   │   │   │   │   │   │   ├── HelpModal.stories.tsx
│   │   │   │   │   │   │   ├── HelpModal.tsx
│   │   │   │   │   │   │   ├── PlayBar.tsx
│   │   │   │   │   │   │   └── SettingsModal.tsx
│   │   │   │   │   │   ├── logo/
│   │   │   │   │   │   │   └── RewindLogo.tsx
│   │   │   │   │   │   ├── sidebar/
│   │   │   │   │   │   │   └── LeftMenuSidebar.tsx
│   │   │   │   │   │   └── update/
│   │   │   │   │   │       └── UpdateModal.tsx
│   │   │   │   │   ├── hooks/
│   │   │   │   │   │   ├── app-info.ts
│   │   │   │   │   │   ├── audio.ts
│   │   │   │   │   │   ├── energy-saver.ts
│   │   │   │   │   │   ├── game-clock.ts
│   │   │   │   │   │   ├── interval.ts
│   │   │   │   │   │   ├── mods.ts
│   │   │   │   │   │   ├── redux.ts
│   │   │   │   │   │   └── shortcuts.ts
│   │   │   │   │   ├── model/
│   │   │   │   │   │   ├── BlueprintInfo.ts
│   │   │   │   │   │   ├── OsuReplay.ts
│   │   │   │   │   │   ├── Skin.ts
│   │   │   │   │   │   └── SkinId.ts
│   │   │   │   │   ├── providers/
│   │   │   │   │   │   ├── SettingsProvider.tsx
│   │   │   │   │   │   └── TheaterProvider.tsx
│   │   │   │   │   ├── screens/
│   │   │   │   │   │   ├── analyzer/
│   │   │   │   │   │   │   └── Analyzer.tsx
│   │   │   │   │   │   ├── home/
│   │   │   │   │   │   │   └── HomeScreen.tsx
│   │   │   │   │   │   ├── setup/
│   │   │   │   │   │   │   └── SetupScreen.tsx
│   │   │   │   │   │   └── splash/
│   │   │   │   │   │       └── SplashScreen.tsx
│   │   │   │   │   ├── services/
│   │   │   │   │   │   ├── analysis/
│   │   │   │   │   │   │   ├── AnalysisApp.ts
│   │   │   │   │   │   │   ├── analysis-cursor.ts
│   │   │   │   │   │   │   ├── createRewindAnalysisApp.ts
│   │   │   │   │   │   │   ├── mod-settings.ts
│   │   │   │   │   │   │   ├── scenes/
│   │   │   │   │   │   │   │   ├── AnalysisScene.ts
│   │   │   │   │   │   │   │   ├── IdleScene.ts
│   │   │   │   │   │   │   │   ├── LoadingScene.ts
│   │   │   │   │   │   │   │   └── ResultsScreenScene.ts
│   │   │   │   │   │   │   └── screenshot.ts
│   │   │   │   │   │   ├── common/
│   │   │   │   │   │   │   ├── CommonManagers.ts
│   │   │   │   │   │   │   ├── app-info.ts
│   │   │   │   │   │   │   ├── audio/
│   │   │   │   │   │   │   │   ├── AudioEngine.ts
│   │   │   │   │   │   │   │   ├── AudioService.ts
│   │   │   │   │   │   │   │   └── settings.ts
│   │   │   │   │   │   │   ├── beatmap-background.ts
│   │   │   │   │   │   │   ├── beatmap-render.ts
│   │   │   │   │   │   │   ├── cursor.ts
│   │   │   │   │   │   │   ├── game/
│   │   │   │   │   │   │   │   ├── GameLoop.ts
│   │   │   │   │   │   │   │   ├── GameSimulator.ts
│   │   │   │   │   │   │   │   └── GameplayClock.ts
│   │   │   │   │   │   │   ├── hit-error-bar.ts
│   │   │   │   │   │   │   ├── key-press-overlay.ts
│   │   │   │   │   │   │   ├── local/
│   │   │   │   │   │   │   │   ├── BlueprintLocatorService.ts
│   │   │   │   │   │   │   │   ├── OsuDBDao.ts
│   │   │   │   │   │   │   │   ├── OsuFolderService.ts
│   │   │   │   │   │   │   │   ├── ReplayFileWatcher.ts
│   │   │   │   │   │   │   │   ├── ReplayService.ts
│   │   │   │   │   │   │   │   └── SkinLoader.ts
│   │   │   │   │   │   │   ├── local-storage.ts
│   │   │   │   │   │   │   ├── playbar.ts
│   │   │   │   │   │   │   ├── playfield-border.ts
│   │   │   │   │   │   │   ├── replay-cursor.ts
│   │   │   │   │   │   │   ├── scenes/
│   │   │   │   │   │   │   │   ├── IScene.ts
│   │   │   │   │   │   │   │   └── SceneManager.ts
│   │   │   │   │   │   │   └── skin.ts
│   │   │   │   │   │   ├── core/
│   │   │   │   │   │   │   └── service.ts
│   │   │   │   │   │   ├── manager/
│   │   │   │   │   │   │   ├── AnalysisSceneManager.ts
│   │   │   │   │   │   │   ├── BeatmapManager.ts
│   │   │   │   │   │   │   ├── ClipRecorder.ts
│   │   │   │   │   │   │   ├── ReplayManager.ts
│   │   │   │   │   │   │   └── ScenarioManager.ts
│   │   │   │   │   │   ├── renderers/
│   │   │   │   │   │   │   ├── PixiRendererManager.ts
│   │   │   │   │   │   │   ├── components/
│   │   │   │   │   │   │   │   ├── background/
│   │   │   │   │   │   │   │   │   └── BeatmapBackground.ts
│   │   │   │   │   │   │   │   ├── hud/
│   │   │   │   │   │   │   │   │   └── ForegroundHUDPreparer.ts
│   │   │   │   │   │   │   │   ├── keypresses/
│   │   │   │   │   │   │   │   │   └── KeyPressOverlay.ts
│   │   │   │   │   │   │   │   ├── playfield/
│   │   │   │   │   │   │   │   │   ├── CursorPreparer.ts
│   │   │   │   │   │   │   │   │   ├── HitCircleFactory.ts
│   │   │   │   │   │   │   │   │   ├── HitObjectsContainerFactory.ts
│   │   │   │   │   │   │   │   │   ├── JudgementPreparer.ts
│   │   │   │   │   │   │   │   │   ├── PlayfieldBorderFactory.ts
│   │   │   │   │   │   │   │   │   ├── PlayfieldFactory.ts
│   │   │   │   │   │   │   │   │   ├── SliderFactory.ts
│   │   │   │   │   │   │   │   │   └── SpinnerFactory.ts
│   │   │   │   │   │   │   │   ├── sliders/
│   │   │   │   │   │   │   │   │   └── SliderTextureManager.ts
│   │   │   │   │   │   │   │   └── stage/
│   │   │   │   │   │   │   │       └── AnalysisStage.ts
│   │   │   │   │   │   │   └── constants.ts
│   │   │   │   │   │   ├── textures/
│   │   │   │   │   │   │   └── TextureManager.ts
│   │   │   │   │   │   └── types/
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── store/
│   │   │   │   │   │   ├── index.tsx
│   │   │   │   │   │   ├── settings/
│   │   │   │   │   │   │   └── slice.ts
│   │   │   │   │   │   └── update/
│   │   │   │   │   │       └── slice.ts
│   │   │   │   │   ├── styles/
│   │   │   │   │   │   └── theme.ts
│   │   │   │   │   └── utils/
│   │   │   │   │       ├── constants.ts
│   │   │   │   │       ├── focus.ts
│   │   │   │   │       ├── pooling/
│   │   │   │   │       │   ├── ObjectPool.ts
│   │   │   │   │       │   └── TemporaryObjectPool.ts
│   │   │   │   │       └── replay.ts
│   │   │   │   ├── assets/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── constants.ts
│   │   │   │   ├── environments/
│   │   │   │   │   ├── environment.prod.ts
│   │   │   │   │   └── environment.ts
│   │   │   │   ├── index.html
│   │   │   │   ├── main.tsx
│   │   │   │   ├── polyfills.ts
│   │   │   │   └── styles.css
│   │   │   ├── test/
│   │   │   │   └── ajv.spec.ts
│   │   │   ├── tsconfig.app.json
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.spec.json
│   │   │   └── webpack.config.js
│   │   └── main/
│   │       ├── .eslintrc.json
│   │       ├── README.md
│   │       ├── electron-builder.json
│   │       ├── jest.config.ts
│   │       ├── package.json
│   │       ├── src/
│   │       │   ├── app/
│   │       │   │   ├── .gitkeep
│   │       │   │   ├── config.ts
│   │       │   │   ├── events.ts
│   │       │   │   ├── updater.ts
│   │       │   │   └── windows.ts
│   │       │   ├── assets/
│   │       │   │   └── .gitkeep
│   │       │   ├── environments/
│   │       │   │   ├── environment.prod.ts
│   │       │   │   └── environment.ts
│   │       │   └── index.ts
│   │       ├── tsconfig.app.json
│   │       ├── tsconfig.json
│   │       └── tsconfig.spec.json
│   └── web/
│       └── backend/
│           ├── .eslintrc.json
│           ├── README.md
│           ├── jest.config.ts
│           ├── src/
│           │   ├── DesktopAPI.ts
│           │   ├── api-common.module.ts
│           │   ├── assets/
│           │   │   └── index.html
│           │   ├── blueprints/
│           │   │   ├── BlueprintInfo.ts
│           │   │   ├── LocalBlueprintController.ts
│           │   │   ├── LocalBlueprintService.ts
│           │   │   └── OsuDBDao.ts
│           │   ├── config/
│           │   │   ├── DesktopConfigController.ts
│           │   │   ├── DesktopConfigService.ts
│           │   │   ├── UserConfigService.ts
│           │   │   └── utils.ts
│           │   ├── constants.ts
│           │   ├── environments/
│           │   │   ├── environment.prod.ts
│           │   │   └── environment.ts
│           │   ├── events/
│           │   │   ├── Events.ts
│           │   │   └── EventsGateway.ts
│           │   ├── main.ts
│           │   ├── replays/
│           │   │   ├── LocalReplayController.ts
│           │   │   ├── LocalReplayService.ts
│           │   │   ├── ReplayWatcher.ts
│           │   │   └── ScoresDBDao.ts
│           │   ├── skins/
│           │   │   ├── SkinController.ts
│           │   │   ├── SkinNameResolver.ts
│           │   │   └── SkinService.ts
│           │   ├── status/
│           │   │   └── SetupStatusController.ts
│           │   └── utils/
│           │       ├── names.spec.ts
│           │       └── names.ts
│           ├── tsconfig.app.json
│           ├── tsconfig.json
│           └── tsconfig.spec.json
├── babel.config.json
├── electron-builder.json
├── jest.config.ts
├── jest.preset.js
├── libs/
│   ├── @types/
│   │   └── node-osr/
│   │       └── index.d.ts
│   ├── osu/
│   │   ├── core/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── audio/
│   │   │   │   │   ├── HitSampleInfo.ts
│   │   │   │   │   └── LegacySampleBank.ts
│   │   │   │   ├── beatmap/
│   │   │   │   │   ├── Beatmap.ts
│   │   │   │   │   ├── BeatmapBuilder.ts
│   │   │   │   │   ├── BeatmapDifficulty.ts
│   │   │   │   │   ├── ControlPoints/
│   │   │   │   │   │   ├── ControlPoint.ts
│   │   │   │   │   │   ├── ControlPointGroup.ts
│   │   │   │   │   │   ├── ControlPointInfo.ts
│   │   │   │   │   │   ├── DifficultyControlPoint.ts
│   │   │   │   │   │   ├── EffectControlPoint.ts
│   │   │   │   │   │   ├── SampleControlPoint.ts
│   │   │   │   │   │   └── TimingControlPoint.ts
│   │   │   │   │   ├── LegacyEffectFlag.ts
│   │   │   │   │   └── TimeSignatures.ts
│   │   │   │   ├── blueprint/
│   │   │   │   │   ├── Blueprint.ts
│   │   │   │   │   ├── BlueprintParser.spec.ts
│   │   │   │   │   ├── BlueprintParser.ts
│   │   │   │   │   └── HitObjectSettings.ts
│   │   │   │   ├── gameplay/
│   │   │   │   │   ├── GameState.ts
│   │   │   │   │   ├── GameStateEvaluator.spec.ts
│   │   │   │   │   ├── GameStateEvaluator.ts
│   │   │   │   │   ├── GameStateTimeMachine.ts
│   │   │   │   │   ├── GameplayAnalysisEvent.ts
│   │   │   │   │   ├── GameplayInfo.ts
│   │   │   │   │   └── Verdicts.ts
│   │   │   │   ├── hitobjects/
│   │   │   │   │   ├── HitCircle.ts
│   │   │   │   │   ├── Properties.ts
│   │   │   │   │   ├── Slider.ts
│   │   │   │   │   ├── SliderCheckPoint.ts
│   │   │   │   │   ├── Spinner.ts
│   │   │   │   │   ├── Types.ts
│   │   │   │   │   └── slider/
│   │   │   │   │       ├── PathApproximator.ts
│   │   │   │   │       ├── PathControlPoint.ts
│   │   │   │   │       ├── PathType.ts
│   │   │   │   │       ├── SliderCheckPointDescriptor.ts
│   │   │   │   │       ├── SliderCheckPointGenerator.ts
│   │   │   │   │       └── SliderPath.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── mods/
│   │   │   │   │   ├── EasyMod.ts
│   │   │   │   │   ├── HardRockMod.ts
│   │   │   │   │   ├── HiddenMod.ts
│   │   │   │   │   ├── Mods.spec.ts
│   │   │   │   │   ├── Mods.ts
│   │   │   │   │   └── StackingMod.ts
│   │   │   │   ├── playfield.ts
│   │   │   │   ├── replays/
│   │   │   │   │   ├── RawReplayData.ts
│   │   │   │   │   ├── Replay.ts
│   │   │   │   │   ├── ReplayClicks.ts
│   │   │   │   │   ├── ReplayParser.spec.ts
│   │   │   │   │   └── ReplayParser.ts
│   │   │   │   └── utils/
│   │   │   │       ├── SortedList.spec.ts
│   │   │   │       ├── SortedList.ts
│   │   │   │       ├── index.spec.ts
│   │   │   │       └── index.ts
│   │   │   ├── test/
│   │   │   │   ├── PathApproximator.spec.ts
│   │   │   │   └── utils/
│   │   │   │       └── asserts.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── math/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── Vec2.ts
│   │   │   │   ├── colors.ts
│   │   │   │   ├── difficulty.spec.ts
│   │   │   │   ├── difficulty.ts
│   │   │   │   ├── easing.ts
│   │   │   │   ├── float32.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── sliders.ts
│   │   │   │   ├── time.spec.ts
│   │   │   │   ├── time.ts
│   │   │   │   └── utils.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── pp/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── index.ts
│   │   │   │   └── lib/
│   │   │   │       ├── diff.ts
│   │   │   │       ├── mods.spec.ts
│   │   │   │       ├── mods.ts
│   │   │   │       ├── pp.ts
│   │   │   │       ├── skills/
│   │   │   │       │   ├── aim.ts
│   │   │   │       │   ├── flashlight.ts
│   │   │   │       │   ├── speed.ts
│   │   │   │       │   └── strain.ts
│   │   │   │       ├── utils.spec.ts
│   │   │   │       └── utils.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   └── skin/
│   │       ├── .babelrc
│   │       ├── .eslintrc.json
│   │       ├── README.md
│   │       ├── jest.config.ts
│   │       ├── src/
│   │       │   ├── index.ts
│   │       │   └── lib/
│   │       │       ├── OsuSkinTextureConfig.ts
│   │       │       ├── SkinConfig.ts
│   │       │       ├── SkinConfigParser.spec.ts
│   │       │       ├── SkinConfigParser.ts
│   │       │       └── TextureTypes.ts
│   │       ├── tsconfig.json
│   │       ├── tsconfig.lib.json
│   │       └── tsconfig.spec.json
│   ├── osu-local/
│   │   ├── db-reader/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   ├── DatabaseReader.ts
│   │   │   │   ├── DatabaseTypes.ts
│   │   │   │   ├── OsuBuffer.ts
│   │   │   │   ├── OsuDBReader.ts
│   │   │   │   ├── ScoresDBReader.ts
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── gosumemory/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   ├── gosumemory.ts
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── osr-reader/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   ├── skin-reader/
│   │   │   ├── .babelrc
│   │   │   ├── .eslintrc.json
│   │   │   ├── README.md
│   │   │   ├── jest.config.ts
│   │   │   ├── src/
│   │   │   │   ├── SkinFolderReader.ts
│   │   │   │   ├── SkinTextureResolver.ts
│   │   │   │   └── index.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.lib.json
│   │   │   └── tsconfig.spec.json
│   │   └── utils/
│   │       ├── .babelrc
│   │       ├── .eslintrc.json
│   │       ├── README.md
│   │       ├── jest.config.ts
│   │       ├── src/
│   │       │   ├── dates.ts
│   │       │   ├── files.ts
│   │       │   ├── index.ts
│   │       │   ├── osuUserConfig.spec.ts
│   │       │   ├── osuUserConfig.ts
│   │       │   └── stable.ts
│   │       ├── tsconfig.json
│   │       ├── tsconfig.lib.json
│   │       └── tsconfig.spec.json
│   └── osu-pixi/
│       ├── classic-components/
│       │   ├── .babelrc
│       │   ├── .eslintrc.json
│       │   ├── README.md
│       │   ├── jest.config.ts
│       │   ├── src/
│       │   │   ├── DrawableSettings.ts
│       │   │   ├── hitobjects/
│       │   │   │   ├── OsuClassicApproachCircle.ts
│       │   │   │   ├── OsuClassicConstants.ts
│       │   │   │   ├── OsuClassicCursor.ts
│       │   │   │   ├── OsuClassicHitCircleArea.ts
│       │   │   │   ├── OsuClassicJudgements.ts
│       │   │   │   ├── OsuClassicSliderBall.ts
│       │   │   │   ├── OsuClassicSliderBody.ts
│       │   │   │   ├── OsuClassicSliderRepeat.ts
│       │   │   │   ├── OsuClassicSliderTick.ts
│       │   │   │   └── OsuClassicSpinner.ts
│       │   │   ├── hud/
│       │   │   │   ├── HitErrorBar.ts
│       │   │   │   ├── OsuClassicAccuracy.ts
│       │   │   │   ├── OsuClassicJudgement.ts
│       │   │   │   └── OsuClassicNumber.ts
│       │   │   ├── index.ts
│       │   │   ├── playfield/
│       │   │   │   └── PlayfieldBorder.ts
│       │   │   ├── renderers/
│       │   │   │   └── BasicSliderTextureRenderer.ts
│       │   │   └── utils/
│       │   │       ├── Animation.ts
│       │   │       ├── Pixi.ts
│       │   │       ├── Preparable.ts
│       │   │       ├── Transformations.ts
│       │   │       ├── constants.ts
│       │   │       ├── numbers.spec.ts
│       │   │       └── numbers.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.lib.json
│       │   └── tsconfig.spec.json
│       └── rewind/
│           ├── .babelrc
│           ├── .eslintrc.json
│           ├── README.md
│           ├── jest.config.ts
│           ├── src/
│           │   ├── index.ts
│           │   └── lib/
│           │       └── AnalysisCursor.ts
│           ├── tsconfig.json
│           ├── tsconfig.lib.json
│           └── tsconfig.spec.json
├── migrations.json
├── nx.json
├── package.json
├── resources/
│   └── Skins/
│       ├── OsuDefaultSkin/
│       │   └── README.md
│       └── RewindDefaultSkin/
│           ├── README.md
│           └── skin.ini
├── testdata/
│   └── osu!/
│       ├── Replays/
│       │   ├── - Perfume - Daijobanai [Short kick slider] (2021-07-16) Perfect.osr
│       │   ├── - Perfume - Daijobanai [Short kick slider] (2021-07-16) TooLateMissed.osr
│       │   ├── - Perfume - Daijobanai [Slider (Repeat = 1)] (2021-07-07) Perfect.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu Perfect.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu SliderHeadMissedButTrackingWtf.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu SliderHeadTooEarly.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Osu SliderHeadTooLate.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) Perfect.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderEndMissed.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderHeadMissedButTrackingWtf.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderHeadTooEarly.osr
│       │   ├── - Perfume - Daijobanai [Slider 1] (2021-07-07) SliderHeadTooLate.osr
│       │   ├── RyuK - HoneyWorks - Akatsuki Zukuyo [Taeyang_s Extra] (2019-06-08) Osu.osr
│       │   ├── Varvalian - Aether Realm - The Sun, The Moon, The Star [Mourning Those Things I've Long Left Behind] (2019-05-15) Osu.osr
│       │   ├── abstrakt - Gojou Mayumi - DANZEN! Futari wa PreCure Ver. MaxHeart (TV Size) [Insane] (2021-08-21) Osu.osr
│       │   ├── abstrakt - PSYQUI - Hype feat. Such [Dreamer] (2021-08-08) Osu.osr
│       │   ├── abstrakt - SHK - Violet Perfume [Insane] (2021-03-27) Osu.osr
│       │   ├── abstrakt - Smile.dk - Koko Soko (AKIBA KOUBOU Eurobeat Remix) [Couch Mini2a] (2021-04-09) Osu.osr
│       │   ├── abstrakt - sabi - true DJ MAG top ranker_s song Zenpen (katagiri Remix) [Senseabel's Extra] (2021-08-08) Osu.osr
│       │   ├── hallowatcher - DECO27 - HIBANA feat. Hatsune Miku [Lock On] (2020-02-09) Osu.osr
│       │   └── kellad - Asriel - Raison D'etre [EXist] (2025-02-16) Osu.osr
│       ├── Songs/
│       │   ├── 1001507 ZUTOMAYO - Kan Saete Kuyashiiwa/
│       │   │   └── ZUTOMAYO - Kan Saete Kuyashiiwa (Nathan) [geragera].osu
│       │   ├── 1010865 SHK - Violet Perfume [no video]/
│       │   │   └── SHK - Violet Perfume (ktgster) [Insane].osu
│       │   ├── 1236927 Frums - XNOR XNOR XNOR/
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [.-- .-. --- -. --. .-- .- -.--].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Beloved Exclusive].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Earth (atm)].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Earth].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Fire].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Metal].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Moon].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Sun].osu
│       │   │   ├── Frums - XNOR XNOR XNOR (fanzhen0019) [Water].osu
│       │   │   └── Frums - XNOR XNOR XNOR (fanzhen0019) [Wood].osu
│       │   ├── 1302792 Smiledk - Koko Soko (AKIBA KOUBOU Eurobeat Remix)/
│       │   │   └── Smile.dk - Koko Soko (AKIBA KOUBOU Eurobeat Remix) ([ Couch ] Mini) [Couch Mini2a].osu
│       │   ├── 1357624 sabi - true DJ MAG top ranker's song Zenpen (katagiri Remix)/
│       │   │   └── sabi - true DJ MAG top ranker's song Zenpen (katagiri Remix) (Nathan) [KEMOMIMI EDM SQUAD].osu
│       │   ├── 1495211 Aether Realm - The Tower/
│       │   │   └── Aether Realm - The Tower (Takane) [Brick and Mortar].osu
│       │   ├── 150945 Knife Party - Centipede/
│       │   │   └── Knife Party - Centipede (Sugoi-_-Desu) [This isn't a map, just a simple visualisation].osu
│       │   ├── 158023 UNDEAD CORPORATION - Everything will freeze/
│       │   │   └── UNDEAD CORPORATION - Everything will freeze (Ekoro) [Time Freeze].osu
│       │   ├── 29157 Within Temptation - The Unforgiving [no video]/
│       │   │   └── Within Temptation - The Unforgiving (Armin) [Marathon].osu
│       │   ├── 351280 HoneyWorks - Akatsuki Zukuyo/
│       │   │   └── HoneyWorks - Akatsuki Zukuyo ([C u r i]) [Taeyang's Extra].osu
│       │   ├── 607089 Xi - Rokujuu Nenme no Shinsoku Saiban _ Rapidity is a justice/
│       │   │   └── Xi - Rokujuu Nenme no Shinsoku Saiban ~ Rapidity is a justice (tokiko) [Extra Stage].osu
│       │   ├── 863227 Brian The Sun - Lonely Go! (TV Size) [no video]/
│       │   │   └── Brian The Sun - Lonely Go! (TV Size) (Nevo) [Fiery's Extreme].osu
│       │   ├── 931596 Apol - Hidamari no Uta/
│       │   │   └── Apol - Hidamari no Uta (-Keitaro) [Expert].osu
│       │   ├── 933630 Aether Realm - The Sun, The Moon, The Star/
│       │   │   └── Aether Realm - The Sun, The Moon, The Star (ItsWinter) [Mourning Those Things I've Long Left Behind].osu
│       │   └── 967347 Perfume - Daijobanai/
│       │       ├── Perfume - Daijobanai (eiri-) [Easy].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Hard].osu
│       │       ├── Perfume - Daijobanai (eiri-) [HitCircle 1].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Normal].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Short kick slider].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Slider (Repeat = 1)].osu
│       │       ├── Perfume - Daijobanai (eiri-) [Slider 1].osu
│       │       └── Perfume - Daijobanai (eiri-) [Smile].osu
│       └── osu!.me.cfg
├── tests/
│   ├── game-simulation/
│   │   ├── .eslintrc.json
│   │   ├── README.md
│   │   ├── jest.config.ts
│   │   ├── src/
│   │   │   ├── core/
│   │   │   │   ├── BeatmapBuilder.spec.ts
│   │   │   │   ├── BlueprintParser.spec.ts
│   │   │   │   ├── OsuStdReplayState.spec.ts
│   │   │   │   ├── ReplayClicks.spec.ts
│   │   │   │   ├── archive/
│   │   │   │   │   ├── reference/
│   │   │   │   │   │   ├── DANZEN.spec.ts
│   │   │   │   │   │   └── KokoSokoMini.spec.ts
│   │   │   │   │   └── replays/
│   │   │   │   │       ├── DaijobanaiSlider1.spec.ts
│   │   │   │   │       ├── sunMoonStar.spec.ts
│   │   │   │   │       └── topranker.spec.ts
│   │   │   │   ├── bpm.test.ts
│   │   │   │   └── hitobjects.test.ts
│   │   │   ├── local/
│   │   │   │   ├── osudb.test.ts
│   │   │   │   └── scoresdb.test.ts
│   │   │   ├── others.ts
│   │   │   ├── pp/
│   │   │   │   ├── diff.test.ts
│   │   │   │   ├── ojsama.test.ts
│   │   │   │   └── pp.test.ts
│   │   │   ├── reference.ts
│   │   │   └── util.ts
│   │   ├── tsconfig.json
│   │   └── tsconfig.spec.json
│   └── osu-stable-test-generator/
│       ├── .eslintrc.json
│       ├── jest.config.ts
│       ├── src/
│       │   ├── app/
│       │   │   └── .gitkeep
│       │   ├── assets/
│       │   │   └── .gitkeep
│       │   ├── environments/
│       │   │   ├── environment.prod.ts
│       │   │   └── environment.ts
│       │   └── main.ts
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       └── tsconfig.spec.json
├── tools/
│   ├── generators/
│   │   └── .gitkeep
│   └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json
Download .txt
SYMBOL INDEX (1378 symbols across 232 files)

FILE: apps/desktop/frontend/src/app/RewindApp.tsx
  function NormalView (line 17) | function NormalView() {
  function RewindApp (line 30) | function RewindApp() {

FILE: apps/desktop/frontend/src/app/api.ts
  type Listener (line 3) | type Listener = (...args: any) => any;
  type UpdateInfo (line 6) | interface UpdateInfo {

FILE: apps/desktop/frontend/src/app/components/analyzer/BaseAudioSettingsPanel.tsx
  type Change (line 4) | type Change = (x: number) => void;
  type BaseAudioSettingsPanelProps (line 6) | interface BaseAudioSettingsPanelProps {
  function VolumeSlider (line 17) | function VolumeSlider({ disabled, onChange, value }: { disabled?: boolea...
  function BaseAudioSettingsPanel (line 34) | function BaseAudioSettingsPanel(props: BaseAudioSettingsPanelProps) {

FILE: apps/desktop/frontend/src/app/components/analyzer/BaseCurrentTime.tsx
  type GameCurrentTimeProps (line 5) | type GameCurrentTimeProps = Record<string, unknown>;
  type GameCurrentTimeHandle (line 7) | interface GameCurrentTimeHandle {
  method updateTime (line 16) | updateTime(timeInMs) {

FILE: apps/desktop/frontend/src/app/components/analyzer/BaseDialog.tsx
  function PromotionFooter (line 7) | function PromotionFooter() {

FILE: apps/desktop/frontend/src/app/components/analyzer/BaseGameTimeSlider.tsx
  type EventLineProps (line 14) | interface EventLineProps {
  type EventType (line 20) | type EventType = { timings: number[]; tooltip: string; color: string };
  type BaseGameTimeSliderProps (line 22) | interface BaseGameTimeSliderProps {
  function drawPlaybarEvents (line 35) | function drawPlaybarEvents(canvas: HTMLCanvasElement, eventTypes: EventT...
  function drawDifficulty (line 85) | function drawDifficulty(canvas: HTMLCanvasElement, data: number[]) {
  function BaseGameTimeSlider (line 135) | function BaseGameTimeSlider(props: BaseGameTimeSliderProps) {

FILE: apps/desktop/frontend/src/app/components/analyzer/BaseSettingsModal.tsx
  type SettingTab (line 6) | interface SettingTab {
  type SettingsProps (line 11) | interface SettingsProps {
  constant MIN_OPACITY (line 22) | const MIN_OPACITY = 25;
  constant MAX_OPACITY (line 23) | const MAX_OPACITY = 100;
  function BaseSettingsModal (line 25) | function BaseSettingsModal(props: SettingsProps) {

FILE: apps/desktop/frontend/src/app/components/analyzer/GameCanvas.tsx
  function EmptyState (line 10) | function EmptyState() {

FILE: apps/desktop/frontend/src/app/components/analyzer/HelpModal.tsx
  function Key (line 7) | function Key({ text }: { text: string }) {
  function KeyBindings (line 49) | function KeyBindings({ separator = "+", keys, inline }: { separator?: st...
  function Title (line 62) | function Title({ children }: any) {
  function PlaybarNavigationShortcuts (line 71) | function PlaybarNavigationShortcuts() {
  type HelpModalProps (line 107) | interface HelpModalProps {
  function HelpBox (line 112) | function HelpBox(props: Pick<HelpModalProps, "onClose">) {
  function HelpModalDialog (line 138) | function HelpModalDialog(props: HelpModalProps) {

FILE: apps/desktop/frontend/src/app/components/analyzer/PlayBar.tsx
  function MoreMenu (line 57) | function MoreMenu() {
  function AudioButton (line 124) | function AudioButton() {
  function PlayButton (line 173) | function PlayButton() {
  function CurrentTime (line 187) | function CurrentTime() {
  function groupTimings (line 222) | function groupTimings(events: ReplayAnalysisEvent[]) {
  function GameTimeSlider (line 253) | function GameTimeSlider() {
  function Duration (line 286) | function Duration() {
  function HiddenButton (line 293) | function HiddenButton() {
  function SettingsButton (line 308) | function SettingsButton() {
  type BaseSpeedButtonProps (line 317) | interface BaseSpeedButtonProps {
  function BaseSpeedButton (line 324) | function BaseSpeedButton(props: BaseSpeedButtonProps) {
  function SpeedButton (line 391) | function SpeedButton() {
  function RecordButton (line 400) | function RecordButton() {
  function PlayBar (line 432) | function PlayBar() {

FILE: apps/desktop/frontend/src/app/components/analyzer/SettingsModal.tsx
  function BeatmapBackgroundSettings (line 36) | function BeatmapBackgroundSettings() {
  function BeatmapRenderSettings (line 65) | function BeatmapRenderSettings() {
  function AnalysisCursorSettingsSection (line 87) | function AnalysisCursorSettingsSection() {
  function ReplayCursorSettingsSection (line 111) | function ReplayCursorSettingsSection() {
  function HitErrorBarSettingsSection (line 161) | function HitErrorBarSettingsSection() {
  function PlaybarSettingsSection (line 181) | function PlaybarSettingsSection() {
  function ResetAllSettingsSection (line 207) | function ResetAllSettingsSection() {
  function OtherSettings (line 219) | function OtherSettings() {
  function GameplaySettings (line 227) | function GameplaySettings() {
  function SkinsSettings (line 240) | function SkinsSettings() {
  function SettingsModal (line 297) | function SettingsModal() {

FILE: apps/desktop/frontend/src/app/components/sidebar/LeftMenuSidebar.tsx
  function useCheckForUpdate (line 30) | function useCheckForUpdate() {
  function LeftMenuSidebar (line 64) | function LeftMenuSidebar() {

FILE: apps/desktop/frontend/src/app/components/update/UpdateModal.tsx
  function niceBytes (line 11) | function niceBytes(x: any) {
  function versionUrl (line 20) | function versionUrl(version: string) {
  function UpdateModal (line 27) | function UpdateModal() {

FILE: apps/desktop/frontend/src/app/hooks/app-info.ts
  function useAppInfo (line 4) | function useAppInfo() {

FILE: apps/desktop/frontend/src/app/hooks/audio.ts
  function useAudioSettingsService (line 5) | function useAudioSettingsService() {
  function useAudioSettings (line 12) | function useAudioSettings() {

FILE: apps/desktop/frontend/src/app/hooks/game-clock.ts
  function useGameClock (line 7) | function useGameClock() {
  function useGameClockControls (line 17) | function useGameClockControls() {
  function useGameClockTime (line 50) | function useGameClockTime(fps = 60) {

FILE: apps/desktop/frontend/src/app/hooks/interval.ts
  function useInterval (line 3) | function useInterval(callback: () => void, delay: number | null) {

FILE: apps/desktop/frontend/src/app/hooks/mods.ts
  function useModControls (line 5) | function useModControls() {

FILE: apps/desktop/frontend/src/app/hooks/shortcuts.ts
  function useShortcuts (line 23) | function useShortcuts() {

FILE: apps/desktop/frontend/src/app/model/BlueprintInfo.ts
  type BlueprintInfo (line 1) | interface BlueprintInfo {

FILE: apps/desktop/frontend/src/app/model/OsuReplay.ts
  type OsuReplay (line 4) | type OsuReplay = {

FILE: apps/desktop/frontend/src/app/model/Skin.ts
  type SkinTexturesByKey (line 12) | type SkinTexturesByKey = Partial<Record<OsuSkinTextures, Texture[]>>;
  type ISkin (line 16) | interface ISkin {
  class EmptySkin (line 30) | class EmptySkin implements ISkin {
    method getComboColorForIndex (line 33) | getComboColorForIndex(): number {
    method getComboNumberTextures (line 37) | getComboNumberTextures(): [] {
    method getHitCircleNumberTextures (line 41) | getHitCircleNumberTextures(): Texture[] {
    method getTexture (line 45) | getTexture() {
    method getTextures (line 49) | getTextures(): Texture[] {
  class Skin (line 57) | class Skin implements ISkin {
    method constructor (line 60) | constructor(public readonly config: SkinConfig, public readonly textur...
    method getComboColorForIndex (line 62) | getComboColorForIndex(i: number): number {
    method getTexture (line 67) | getTexture(key: OsuSkinTextures): Texture {
    method getTextures (line 71) | getTextures(key: OsuSkinTextures): Texture[] {
    method getHitCircleNumberTextures (line 84) | getHitCircleNumberTextures(): Texture[] {
    method getComboNumberTextures (line 88) | getComboNumberTextures(): Texture[] {
    method getScoreTextures (line 93) | getScoreTextures(): Texture[] {

FILE: apps/desktop/frontend/src/app/model/SkinId.ts
  type SkinSource (line 1) | type SkinSource = "rewind" | "osu";
  type SkinId (line 3) | interface SkinId {
  function skinIdToString (line 8) | function skinIdToString({ source, name }: SkinId) {
  function stringToSkinId (line 14) | function stringToSkinId(str: string): SkinId {
  constant DEFAULT_OSU_SKIN_ID (line 23) | const DEFAULT_OSU_SKIN_ID: SkinId = { source: "rewind", name: "OsuDefaul...
  constant DEFAULT_REWIND_SKIN_ID (line 24) | const DEFAULT_REWIND_SKIN_ID: SkinId = { source: "rewind", name: "Rewind...

FILE: apps/desktop/frontend/src/app/providers/SettingsProvider.tsx
  type ISettingsContext (line 3) | type ISettingsContext = {
  type SettingsModalProps (line 17) | interface SettingsModalProps {
  constant DEFAULT_OPACITY (line 22) | const DEFAULT_OPACITY = 100;
  function SettingsModalProvider (line 24) | function SettingsModalProvider({ children, defaultOpen = false }: Settin...
  function useSettingsModalContext (line 45) | function useSettingsModalContext() {

FILE: apps/desktop/frontend/src/app/providers/TheaterProvider.tsx
  type TheaterProviderProps (line 7) | interface TheaterProviderProps {
  function TheaterProvider (line 12) | function TheaterProvider({ theater, children }: TheaterProviderProps) {
  function useTheaterContext (line 16) | function useTheaterContext() {
  function useCommonManagers (line 24) | function useCommonManagers() {
  function useAnalysisApp (line 29) | function useAnalysisApp() {

FILE: apps/desktop/frontend/src/app/screens/analyzer/Analyzer.tsx
  function Analyzer (line 11) | function Analyzer() {

FILE: apps/desktop/frontend/src/app/screens/home/HomeScreen.tsx
  function HomeScreen (line 10) | function HomeScreen() {

FILE: apps/desktop/frontend/src/app/screens/setup/SetupScreen.tsx
  type DirectorySelectionProps (line 11) | interface DirectorySelectionProps {
  function DirectorySelection (line 18) | function DirectorySelection({ value, onChange, placeHolder, badgeOnEmpty...
  function SetupScreen (line 57) | function SetupScreen() {

FILE: apps/desktop/frontend/src/app/screens/splash/SplashScreen.tsx
  function SplashScreen (line 4) | function SplashScreen() {

FILE: apps/desktop/frontend/src/app/services/analysis/AnalysisApp.ts
  class AnalysisApp (line 17) | class AnalysisApp {
    method constructor (line 18) | constructor(
    method stats (line 34) | stats() {
    method initialize (line 38) | initialize() {
    method close (line 44) | close() {}
    method onEnter (line 46) | onEnter(canvas: HTMLCanvasElement) {
    method onHide (line 51) | onHide() {
    method loadReplay (line 64) | async loadReplay(replayId: string) {

FILE: apps/desktop/frontend/src/app/services/analysis/analysis-cursor.ts
  type AnalysisCursorSettings (line 6) | interface AnalysisCursorSettings extends CursorSettings {
  constant DEFAULT_ANALYSIS_CURSOR_SETTINGS (line 13) | const DEFAULT_ANALYSIS_CURSOR_SETTINGS: AnalysisCursorSettings = Object....
  class AnalysisCursorSettingsStore (line 38) | class AnalysisCursorSettingsStore extends PersistentService<AnalysisCurs...
    method getDefaultValue (line 41) | getDefaultValue(): AnalysisCursorSettings {
    method setEnabled (line 45) | setEnabled(enabled: boolean) {

FILE: apps/desktop/frontend/src/app/services/analysis/createRewindAnalysisApp.ts
  function createRewindAnalysisApp (line 45) | function createRewindAnalysisApp(commonContainer: Container) {

FILE: apps/desktop/frontend/src/app/services/analysis/mod-settings.ts
  type AnalysisModSettings (line 4) | interface AnalysisModSettings {
  class ModSettingsService (line 10) | class ModSettingsService {
    method constructor (line 13) | constructor() {
    method modSettings (line 20) | get modSettings() {
    method setHidden (line 24) | setHidden(hidden: boolean) {
    method setFlashlight (line 28) | setFlashlight(flashlight: boolean) {

FILE: apps/desktop/frontend/src/app/services/analysis/scenes/AnalysisScene.ts
  class AnalysisScene (line 9) | class AnalysisScene implements UserScene {
    method constructor (line 10) | constructor(
    method update (line 16) | update() {
    method stage (line 22) | get stage() {
    method destroy (line 26) | destroy(): void {
    method init (line 30) | init(data: unknown): void {
    method preload (line 34) | preload(): Promise<void> {
    method create (line 38) | create(): void {

FILE: apps/desktop/frontend/src/app/services/analysis/scenes/IdleScene.ts
  class IdleScene (line 10) | class IdleScene implements UserScene {
    method destroy (line 13) | destroy(): void {
    method init (line 17) | init(data: string): void {}
    method preload (line 19) | async preload(): Promise<void> {
    method update (line 23) | update(): void {
    method create (line 27) | create(): void {

FILE: apps/desktop/frontend/src/app/services/analysis/scenes/LoadingScene.ts
  class LoadingScene (line 5) | class LoadingScene implements UserScene {
    method destroy (line 8) | destroy(): void {}
    method init (line 10) | init(): void {
    method preload (line 14) | async preload(): Promise<void> {
    method update (line 18) | update(): void {
    method create (line 22) | create(): void {

FILE: apps/desktop/frontend/src/app/services/analysis/screenshot.ts
  class ScreenshotTaker (line 6) | class ScreenshotTaker {
    method constructor (line 7) | constructor(private readonly analysisScene: AnalysisScene, private rea...
    method takeScreenshot (line 10) | takeScreenshot() {

FILE: apps/desktop/frontend/src/app/services/common/CommonManagers.ts
  class CommonManagers (line 27) | class CommonManagers {
    method constructor (line 28) | constructor(
    method initialize (line 41) | async initialize() {
  type Settings (line 46) | interface Settings {
  function createRewindTheater (line 52) | function createRewindTheater({ rewindSkinsFolder, appPlatform, appVersio...
  type RewindTheater (line 89) | type RewindTheater = ReturnType<typeof createRewindTheater>;

FILE: apps/desktop/frontend/src/app/services/common/app-info.ts
  class AppInfoService (line 5) | class AppInfoService {
    method constructor (line 6) | constructor(

FILE: apps/desktop/frontend/src/app/services/common/audio/AudioEngine.ts
  class AudioEngine (line 12) | class AudioEngine {
    method constructor (line 27) | constructor(
    method postConstruct (line 43) | postConstruct() {
    method setSong (line 57) | setSong(audio: HTMLAudioElement) {
    method setupListeners (line 62) | setupListeners() {
    method start (line 73) | start() {
    method pause (line 79) | pause() {
    method changePlaybackRate (line 86) | changePlaybackRate(newPlaybackRate: number) {
    method currentTime (line 93) | currentTime() {
    method seekTo (line 97) | seekTo(toInMs: number) {
    method isPlaying (line 103) | get isPlaying() {
    method togglePlaying (line 107) | togglePlaying(): boolean {
    method durationInMs (line 113) | get durationInMs() {
    method destroy (line 117) | destroy() {
    method handleAudioSettingsChanged (line 128) | private handleAudioSettingsChanged(settings: AudioSettings) {

FILE: apps/desktop/frontend/src/app/services/common/audio/AudioService.ts
  class AudioService (line 8) | class AudioService {
    method constructor (line 13) | constructor() {
    method loadAudio (line 17) | async loadAudio(filePath: string) {

FILE: apps/desktop/frontend/src/app/services/common/audio/settings.ts
  type AudioSettings (line 5) | interface AudioSettings {
  constant DEFAULT_AUDIO_SETTINGS (line 14) | const DEFAULT_AUDIO_SETTINGS: AudioSettings = Object.freeze({
  class AudioSettingsStore (line 41) | class AudioSettingsStore extends PersistentService<AudioSettings> {
    method getDefaultValue (line 45) | getDefaultValue(): AudioSettings {
    method toggleMuted (line 49) | toggleMuted() {
    method setMasterVolume (line 53) | setMasterVolume(volume: number) {
    method setMusicVolume (line 57) | setMusicVolume(volume: number) {
    method setEffectsVolume (line 61) | setEffectsVolume(volume: number) {
    method setMuted (line 65) | setMuted(muted: boolean) {

FILE: apps/desktop/frontend/src/app/services/common/beatmap-background.ts
  type BeatmapBackgroundSettings (line 7) | interface BeatmapBackgroundSettings {
  constant DEFAULT_BEATMAP_BACKGROUND_SETTINGS (line 17) | const DEFAULT_BEATMAP_BACKGROUND_SETTINGS: BeatmapBackgroundSettings = O...
  class BeatmapBackgroundSettingsStore (line 33) | class BeatmapBackgroundSettingsStore extends PersistentService<BeatmapBa...
    method getDefaultValue (line 40) | getDefaultValue(): BeatmapBackgroundSettings {
    method texture (line 44) | get texture() {
    method setBlur (line 48) | setBlur(blur: number) {
    method setDim (line 52) | setDim(dim: number) {
    method setEnabled (line 56) | setEnabled(enabled: boolean) {

FILE: apps/desktop/frontend/src/app/services/common/beatmap-render.ts
  type BeatmapRenderSettings (line 5) | interface BeatmapRenderSettings {
  constant DEFAULT_BEATMAP_RENDER_SETTINGS (line 11) | const DEFAULT_BEATMAP_RENDER_SETTINGS: BeatmapRenderSettings = Object.fr...
  class BeatmapRenderService (line 26) | class BeatmapRenderService extends PersistentService<BeatmapRenderSettin...
    method getDefaultValue (line 30) | getDefaultValue(): BeatmapRenderSettings {
    method setSliderDevMode (line 33) | setSliderDevMode(sliderDevMode: boolean) {
    method setDrawSliderEnds (line 37) | setDrawSliderEnds(drawSliderEnds: boolean) {

FILE: apps/desktop/frontend/src/app/services/common/cursor.ts
  type CursorSettings (line 1) | interface CursorSettings {

FILE: apps/desktop/frontend/src/app/services/common/game/GameLoop.ts
  function defaultMonitor (line 8) | function defaultMonitor() {
  class GameLoop (line 26) | class GameLoop {
    method constructor (line 30) | constructor(
    method stats (line 39) | stats() {
    method initializeTicker (line 44) | initializeTicker() {
    method startTicker (line 48) | startTicker() {
    method stopTicker (line 52) | stopTicker() {
    method update (line 56) | private update(deltaTimeMs: number) {
    method render (line 60) | private render() {
    method tickHandler (line 69) | tickHandler(deltaTimeMs: number) {

FILE: apps/desktop/frontend/src/app/services/common/game/GameSimulator.ts
  class GameSimulator (line 21) | class GameSimulator {
    method constructor (line 32) | constructor() {
    method calculateDifficulties (line 37) | calculateDifficulties(rawBeatmap: string, durationInMs: number, mods: ...
    method calculateHitErrorArray (line 82) | calculateHitErrorArray() {}
    method simulateReplay (line 84) | simulateReplay(beatmap: Beatmap, replay: OsuReplay) {
    method simulate (line 117) | simulate(gameTimeInMs: number) {
    method getCurrentState (line 124) | getCurrentState() {
    method getCurrentInfo (line 128) | getCurrentInfo() {
    method calculateEvents (line 133) | async calculateEvents() {
    method clear (line 137) | clear() {

FILE: apps/desktop/frontend/src/app/services/common/game/GameplayClock.ts
  class GameplayClock (line 7) | class GameplayClock {
    method constructor (line 20) | constructor() {
    method tick (line 31) | tick() {
    method speed (line 40) | get speed() {
    method speed (line 44) | set speed(value: number) {
    method durationInMs (line 48) | get durationInMs() {
    method durationInMs (line 52) | set durationInMs(value: number) {
    method isPlaying (line 56) | get isPlaying() {
    method isPlaying (line 60) | set isPlaying(value: boolean) {
    method updateTimeElapsed (line 64) | updateTimeElapsed() {
    method toggle (line 72) | toggle() {
    method start (line 77) | start() {
    method pause (line 90) | pause() {
    method setSpeed (line 96) | setSpeed(speed: number) {
    method setDuration (line 101) | setDuration(durationInMs: number) {
    method seekTo (line 106) | seekTo(timeInMs: number) {
    method clear (line 113) | clear() {

FILE: apps/desktop/frontend/src/app/services/common/hit-error-bar.ts
  type HitErrorBarSettings (line 5) | interface HitErrorBarSettings {
  constant DEFAULT_HIT_ERROR_BAR_SETTINGS (line 10) | const DEFAULT_HIT_ERROR_BAR_SETTINGS: HitErrorBarSettings = Object.freeze({
  class HitErrorBarSettingsStore (line 25) | class HitErrorBarSettingsStore extends PersistentService<HitErrorBarSett...
    method getDefaultValue (line 29) | getDefaultValue() {

FILE: apps/desktop/frontend/src/app/services/common/key-press-overlay.ts
  type KeyPressOverlaySettings (line 1) | interface KeyPressOverlaySettings {

FILE: apps/desktop/frontend/src/app/services/common/local-storage.ts
  class LocalStorageLoader (line 6) | class LocalStorageLoader<T> {
    method constructor (line 9) | constructor(private readonly localStorageKey: string, schema: JSONSche...
    method loadFromLocalStorage (line 13) | loadFromLocalStorage(): T {

FILE: apps/desktop/frontend/src/app/services/common/local/BlueprintLocatorService.ts
  constant METADATA_SECTIONS_TO_READ (line 13) | const METADATA_SECTIONS_TO_READ: BlueprintSection[] = ["General", "Diffi...
  function mapToLocalBlueprint (line 15) | function mapToLocalBlueprint(
  type BlueprintMD5 (line 36) | type BlueprintMD5 = string;
  class BlueprintLocatorService (line 46) | class BlueprintLocatorService {
    method constructor (line 49) | constructor(private readonly osuDbDao: OsuDBDao, private readonly osuF...
    method songsFolder (line 52) | private get songsFolder() {
    method completeRead (line 56) | private async completeRead() {
    method addNewBlueprint (line 86) | private addNewBlueprint(blueprint: BlueprintInfo) {
    method blueprintHasBeenAdded (line 90) | async blueprintHasBeenAdded() {
    method getAllBlueprints (line 101) | async getAllBlueprints(): Promise<Record<string, BlueprintInfo>> {
    method getBlueprintByMD5 (line 109) | async getBlueprintByMD5(md5: string): Promise<BlueprintInfo | undefine...
    method blueprintBg (line 114) | public async blueprintBg(md5: string): Promise<string | undefined> {
  function getNewFolderCandidates (line 127) | async function getNewFolderCandidates(songsFolder: string, importedLater...
  function listOsuFiles (line 134) | async function listOsuFiles(songFolder: string): Promise<string[]> {

FILE: apps/desktop/frontend/src/app/services/common/local/OsuDBDao.ts
  class OsuDBDao (line 23) | class OsuDBDao {
    method constructor (line 27) | constructor(private readonly osuFolderService: OsuFolderService) {
    method osuDbPath (line 35) | private get osuDbPath() {
    method createReader (line 39) | private async createReader() {
    method getOsuDbLastModifiedTime (line 44) | async getOsuDbLastModifiedTime() {
    method hasChanged (line 48) | async hasChanged(): Promise<boolean> {
    method cachedTime (line 52) | get cachedTime() {
    method getAllBlueprints (line 56) | async getAllBlueprints(): Promise<BlueprintInfo[]> {
    method getBlueprintByMD5 (line 69) | async getBlueprintByMD5(md5: string): Promise<BlueprintInfo | undefine...

FILE: apps/desktop/frontend/src/app/services/common/local/OsuFolderService.ts
  function osuFolderSanityCheck (line 18) | async function osuFolderSanityCheck(osuFolderPath: string) {
  type OsuSettings (line 28) | interface OsuSettings {
  constant DEFAULT_OSU_SETTINGS (line 32) | const DEFAULT_OSU_SETTINGS: OsuSettings = Object.freeze({
  class OsuFolderService (line 44) | class OsuFolderService extends PersistentService<OsuSettings> {
    method constructor (line 51) | constructor() {
    method getDefaultValue (line 56) | getDefaultValue(): OsuSettings {
    method onFolderChange (line 60) | async onFolderChange(osuSettings: OsuSettings) {
    method getOsuFolder (line 68) | getOsuFolder(): string {
    method setOsuFolder (line 72) | setOsuFolder(path: string) {
    method isValidOsuFolder (line 77) | async isValidOsuFolder(directoryPath: string) {
    method hasValidOsuFolderSet (line 81) | async hasValidOsuFolderSet(): Promise<boolean> {

FILE: apps/desktop/frontend/src/app/services/common/local/ReplayFileWatcher.ts
  class ReplayFileWatcher (line 7) | class ReplayFileWatcher {
    method constructor (line 11) | constructor(private readonly osuFolderService: OsuFolderService) {
    method startWatching (line 15) | public startWatching() {
    method onNewReplayFolder (line 21) | private onNewReplayFolder(folder: string) {

FILE: apps/desktop/frontend/src/app/services/common/local/ReplayService.ts
  type REPLAY_SOURCES (line 6) | type REPLAY_SOURCES = "OSU_API" | "FILE";
  class ReplayService (line 9) | class ReplayService {
    method retrieveReplay (line 10) | async retrieveReplay(replayId: string, source: REPLAY_SOURCES = "FILE"...

FILE: apps/desktop/frontend/src/app/services/common/local/SkinLoader.ts
  type SkinTextureLocation (line 12) | type SkinTextureLocation = { key: OsuSkinTextures; paths: string[] };
  function startLoading (line 14) | async function startLoading(loader: Loader, skinName: string): Promise<b...
  constant OSU_DEFAULT_SKIN_ID (line 32) | const OSU_DEFAULT_SKIN_ID: SkinId = { source: "rewind", name: "OsuDefaul...
  class SkinLoader (line 35) | class SkinLoader {
    method constructor (line 41) | constructor(
    method loadSkinList (line 50) | async loadSkinList() {
    method sourcePath (line 56) | sourcePath(source: string) {
    method resolveToPath (line 66) | resolveToPath({ source, name }: SkinId) {
    method resolve (line 74) | async resolve(
    method loadSkin (line 91) | async loadSkin(skinId: SkinId, forceReload?: boolean): Promise<Skin> {

FILE: apps/desktop/frontend/src/app/services/common/playbar.ts
  type PlaybarSettings (line 5) | interface PlaybarSettings {
  constant DEFAULT_PLAY_BAR_SETTINGS (line 9) | const DEFAULT_PLAY_BAR_SETTINGS: PlaybarSettings = Object.freeze({
  class PlaybarSettingsStore (line 22) | class PlaybarSettingsStore extends PersistentService<PlaybarSettings> {
    method getDefaultValue (line 26) | getDefaultValue(): PlaybarSettings {

FILE: apps/desktop/frontend/src/app/services/common/playfield-border.ts
  constant DEFAULT_SETTINGS (line 5) | const DEFAULT_SETTINGS: PlayfieldBorderSettings = {
  class PlayfieldBorderSettingsStore (line 13) | class PlayfieldBorderSettingsStore {
    method constructor (line 16) | constructor() {
    method settings (line 20) | get settings() {

FILE: apps/desktop/frontend/src/app/services/common/replay-cursor.ts
  type ReplayCursorSettings (line 6) | interface ReplayCursorSettings extends CursorSettings {
  constant DEFAULT_REPLAY_CURSOR_SETTINGS (line 11) | const DEFAULT_REPLAY_CURSOR_SETTINGS: ReplayCursorSettings = Object.free...
  class ReplayCursorSettingsStore (line 31) | class ReplayCursorSettingsStore extends PersistentService<ReplayCursorSe...
    method getDefaultValue (line 35) | getDefaultValue(): ReplayCursorSettings {

FILE: apps/desktop/frontend/src/app/services/common/scenes/IScene.ts
  type UserScene (line 5) | interface UserScene {
  type ManagedSceneState (line 26) | type ManagedSceneState = "INITIALIZING" | "PLAYING" | "PAUSED" | "SLEEPI...
  class ManagedScene (line 28) | class ManagedScene implements UserScene {
    method constructor (line 31) | constructor(private readonly scene: UserScene, public readonly key: st...
    method stage (line 33) | get stage() {
    method pause (line 37) | pause() {
    method resume (line 41) | resume() {
    method sleep (line 45) | sleep() {
    method destroy (line 49) | destroy(): void {
    method init (line 53) | init(data: unknown): void {
    method preload (line 57) | async preload(): Promise<void> {
    method update (line 61) | update(deltaTimeInMs: number): void {
    method create (line 68) | create(): void {

FILE: apps/desktop/frontend/src/app/services/common/scenes/SceneManager.ts
  class SceneManager (line 7) | class SceneManager {
    method constructor (line 11) | constructor() {
    method add (line 15) | add(scene: UserScene, key: string) {
    method start (line 20) | async start(scene: ManagedScene, data: unknown) {
    method stop (line 29) | stop(scene: ManagedScene) {
    method update (line 34) | update(deltaTimeMs: number) {
    method sceneByKey (line 38) | sceneByKey(key: string) {
    method createStage (line 42) | createStage() {

FILE: apps/desktop/frontend/src/app/services/common/skin.ts
  type SkinSettings (line 9) | interface SkinSettings {
  constant DEFAULT_SKIN_SETTINGS (line 14) | const DEFAULT_SKIN_SETTINGS: SkinSettings = Object.freeze({
  class SkinSettingsStore (line 26) | class SkinSettingsStore extends PersistentService<SkinSettings> {
    method getDefaultValue (line 30) | getDefaultValue(): SkinSettings {
    method setPreferredSkinId (line 34) | setPreferredSkinId(preferredSkinId: string) {
  class SkinHolder (line 40) | class SkinHolder {
    method constructor (line 43) | constructor() {
    method getSkin (line 47) | getSkin(): Skin {
    method setSkin (line 51) | setSkin(skin: Skin) {
  class SkinManager (line 57) | class SkinManager {
    method constructor (line 60) | constructor(
    method loadSkin (line 68) | async loadSkin(skinId: SkinId) {
    method loadPreferredSkin (line 74) | async loadPreferredSkin() {
    method loadSkinList (line 88) | async loadSkinList() {

FILE: apps/desktop/frontend/src/app/services/core/service.ts
  method constructor (line 13) | constructor() {
  method getSettings (line 19) | getSettings() {
  method changeSettings (line 23) | changeSettings(fn: (draft: Draft<T>) => unknown) {
  method getSubject (line 31) | getSubject() {
  method settings (line 35) | get settings() {
  method settings (line 39) | set settings(s: T) {
  constant DEBOUNCE_TIME_IN_MS (line 44) | const DEBOUNCE_TIME_IN_MS = 500;
  method init (line 52) | init() {

FILE: apps/desktop/frontend/src/app/services/manager/AnalysisSceneManager.ts
  type AnalysisSceneKeys (line 7) | enum AnalysisSceneKeys {
  class AnalysisSceneManager (line 14) | class AnalysisSceneManager {
    method constructor (line 17) | constructor(
    method changeToScene (line 26) | async changeToScene(sceneKey: AnalysisSceneKeys, data?: any) {

FILE: apps/desktop/frontend/src/app/services/manager/BeatmapManager.ts
  class BeatmapManager (line 7) | class BeatmapManager {
    method setBeatmap (line 10) | setBeatmap(beatmap: Beatmap) {
    method getBeatmap (line 14) | getBeatmap() {

FILE: apps/desktop/frontend/src/app/services/manager/ClipRecorder.ts
  class ClipRecorder (line 7) | class ClipRecorder {
    method constructor (line 12) | constructor(private readonly renderManager: PixiRendererManager) {
    method startRecording (line 16) | startRecording() {
    method exportVideo (line 40) | exportVideo() {
    method stopRecording (line 56) | stopRecording() {

FILE: apps/desktop/frontend/src/app/services/manager/ReplayManager.ts
  class ReplayManager (line 7) | class ReplayManager {
    method constructor (line 11) | constructor() {
    method getMainReplay (line 15) | getMainReplay() {
    method setMainReplay (line 19) | setMainReplay(replay: OsuReplay | null) {

FILE: apps/desktop/frontend/src/app/services/manager/ScenarioManager.ts
  type Scenario (line 23) | interface Scenario {
  function localFile (line 27) | function localFile(path: string) {
  class ScenarioManager (line 32) | class ScenarioManager {
    method constructor (line 35) | constructor(
    method initialize (line 56) | public initialize() {
    method clearReplay (line 63) | async clearReplay() {
    method loadReplay (line 75) | async loadReplay(replayId: string) {
    method loadBeatmap (line 135) | async loadBeatmap(blueprintId: string) {
    method addSubReplay (line 143) | async addSubReplay() {

FILE: apps/desktop/frontend/src/app/services/renderers/PixiRendererManager.ts
  class PixiRendererManager (line 5) | class PixiRendererManager {
    method initializeRenderer (line 10) | initializeRenderer(canvas: HTMLCanvasElement) {
    method resizeCanvasToDisplaySize (line 19) | resizeCanvasToDisplaySize() {
    method destroy (line 34) | destroy() {
    method getRenderer (line 40) | getRenderer(): PIXI.Renderer | undefined {

FILE: apps/desktop/frontend/src/app/services/renderers/components/background/BeatmapBackground.ts
  constant MAX_BLUR_STRENGTH (line 7) | const MAX_BLUR_STRENGTH = 15;
  type Dimensions (line 9) | interface Dimensions {
  class BeatmapBackground (line 14) | class BeatmapBackground {
    method constructor (line 17) | constructor(private readonly stageDimensions: Dimensions) {
    method onSettingsChange (line 21) | onSettingsChange(beatmapBackgroundSettings: BeatmapBackgroundSettings) {
    method onTextureChange (line 28) | onTextureChange(texture: Texture) {
  class BeatmapBackgroundFactory (line 37) | class BeatmapBackgroundFactory {
    method constructor (line 38) | constructor(private readonly settingsStore: BeatmapBackgroundSettingsS...
    method createBeatmapBackground (line 40) | createBeatmapBackground(stageDimensions: Dimensions) {

FILE: apps/desktop/frontend/src/app/services/renderers/components/hud/ForegroundHUDPreparer.ts
  function calculateUnstableRate (line 18) | function calculateUnstableRate(x: number[]) {
  function calculateMean (line 22) | function calculateMean(x: number[]) {
  class ForegroundHUDPreparer (line 27) | class ForegroundHUDPreparer {
    method constructor (line 39) | constructor(
    method updateHitErrorBar (line 51) | updateHitErrorBar() {
    method updateComboNumber (line 108) | updateComboNumber() {
    method updateHUD (line 122) | updateHUD() {
    method updateAccuracy (line 130) | private updateAccuracy() {
    method updateStats (line 146) | private updateStats() {

FILE: apps/desktop/frontend/src/app/services/renderers/components/keypresses/KeyPressOverlay.ts
  function timeIntervalIntersection (line 30) | function timeIntervalIntersection(a: TimeInterval, b: TimeInterval) {
  class NonIntersectingTimeIntervalsTracker (line 34) | class NonIntersectingTimeIntervalsTracker {
    method constructor (line 35) | constructor(public intervals: TimeIntervals, public timeWindow: number...
    method findVisibleIndices (line 41) | findVisibleIndices(time: number) {
  constant WIDTH (line 55) | const WIDTH = 800;
  constant HEIGHT (line 56) | const HEIGHT = 100;
  constant KEY_HEIGHT (line 57) | const KEY_HEIGHT = 35;
  function positionInTimeline (line 59) | function positionInTimeline(currentTime: number, windowDuration: number,...
  class KeyPressOverlayRow (line 66) | class KeyPressOverlayRow {
    method constructor (line 72) | constructor(timeIntervals: TimeIntervals, hitWindowTime: number) {
    method onTintChange (line 84) | onTintChange(tint: number) {
    method update (line 88) | update(time: number) {
  constant MIN_WINDOW_DURATION (line 134) | const MIN_WINDOW_DURATION = 100;
  constant MAX_WINDOW_DURATION (line 135) | const MAX_WINDOW_DURATION = 2000;
  constant DEFAULT_WINDOW_DURATION (line 136) | const DEFAULT_WINDOW_DURATION = 500;
  constant DEFAULT_DEBUG_RECTANGLE_ALPHA (line 137) | const DEFAULT_DEBUG_RECTANGLE_ALPHA = 0.5;
  function createTimeWindow (line 140) | function createTimeWindow(time: number, duration: number): TimeInterval {
  function hitObjectWindow (line 144) | function hitObjectWindow(hitObject: MainHitObject): TimeInterval {
  function preventBrowserShortcuts (line 149) | function preventBrowserShortcuts(e: KeyboardEvent) {
  class KeyPressOverlay (line 156) | class KeyPressOverlay {
    method constructor (line 177) | constructor(private readonly gameplayClock: GameplayClock) {
    method onWheelEvent (line 236) | onWheelEvent(ev: WheelEvent) {
    method onMouseOver (line 266) | onMouseOver() {
    method onMouseOut (line 274) | onMouseOut() {
    method onWindowDurationChange (line 281) | onWindowDurationChange(windowDuration: number) {
    method onKeyPressesChange (line 289) | onKeyPressesChange(timeIntervals: TimeIntervals[]) {
    method onHitObjectsChange (line 294) | onHitObjectsChange(hitObjects: OsuHitObject[]) {
    method setupRulerTicks (line 300) | setupRulerTicks() {
    method update (line 329) | update(currentTime: number) {
  class KeyPressWithNoteSheetPreparer (line 373) | class KeyPressWithNoteSheetPreparer {
    method constructor (line 376) | constructor(
    method container (line 398) | get container() {
    method update (line 402) | update() {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/CursorPreparer.ts
  class CursorPreparer (line 15) | class CursorPreparer {
    method constructor (line 20) | constructor(
    method time (line 32) | get time() {
    method analysisCursorSettings (line 36) | get analysisCursorSettings() {
    method updateOsuCursor (line 40) | updateOsuCursor() {
    method replay (line 98) | get replay() {
    method updateAnalysisCursor (line 102) | updateAnalysisCursor() {
    method updateCursors (line 158) | updateCursors() {
    method getContainer (line 164) | getContainer() {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/HitCircleFactory.ts
  constant HIT_CIRCLE_FADE_OUT_DURATION (line 18) | const HIT_CIRCLE_FADE_OUT_DURATION = 300;
  class HitCircleFactory (line 25) | class HitCircleFactory {
    method constructor (line 28) | constructor(
    method getOsuClassicHitCircleArea (line 42) | private getOsuClassicHitCircleArea(id: string) {
    method getOsuClassicApproachCircle (line 46) | private getOsuClassicApproachCircle(id: string) {
    method freeResources (line 50) | freeResources() {
    method createHitCircle (line 54) | createHitCircle(hitCircle: HitCircle) {
  function createApproachCircleSettings (line 90) | function createApproachCircleSettings(s: {
  function createHitCircleAreaSettings (line 109) | function createHitCircleAreaSettings(s: {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/HitObjectsContainerFactory.ts
  class HitObjectsContainer (line 9) | class HitObjectsContainer {
    method constructor (line 14) | constructor(
    method createHitCircle (line 25) | private createHitCircle(hitCircle: HitCircle) {
    method createSlider (line 31) | private createSlider(slider: Slider) {
    method createSpinner (line 37) | private createSpinner(spinner: Spinner) {
    method updateHitObjects (line 42) | updateHitObjects() {
  class HitObjectsContainerFactory (line 67) | class HitObjectsContainerFactory {
    method constructor (line 68) | constructor(
    method createHitObjectsContainer (line 75) | createHitObjectsContainer() {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/JudgementPreparer.ts
  function texturesForJudgement (line 11) | function texturesForJudgement(t: MainHitObjectVerdict, lastInComboSet?: ...
  class JudgementPreparer (line 25) | class JudgementPreparer {
    method constructor (line 28) | constructor(
    method getContainer (line 37) | getContainer() {
    method updateJudgements (line 41) | updateJudgements() {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/PlayfieldBorderFactory.ts
  class PlayfieldBorderFactory (line 10) | class PlayfieldBorderFactory {
    method constructor (line 11) | constructor(private settingsStore: PlayfieldBorderSettingsStore) {}
    method createPlayfieldBorder (line 13) | createPlayfieldBorder() {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/PlayfieldFactory.ts
  class Playfield (line 9) | class Playfield {
    method constructor (line 12) | constructor(
    method updatePlayfield (line 29) | updatePlayfield() {
  class PlayfieldFactory (line 38) | class PlayfieldFactory {
    method constructor (line 39) | constructor(
    method createPlayfield (line 46) | createPlayfield() {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/SliderFactory.ts
  constant DEBUG_FOLLOW_CIRCLE_COLOR (line 21) | const DEBUG_FOLLOW_CIRCLE_COLOR = 0xff0000;
  constant DEBUG_PIXEL_BALL_COLOR (line 22) | const DEBUG_PIXEL_BALL_COLOR = 0x00ff00;
  constant SLIDER_FADE_OUT_TIME (line 24) | const SLIDER_FADE_OUT_TIME = 300;
  class SliderFactory (line 27) | class SliderFactory {
    method constructor (line 30) | constructor(
    method getSliderBody (line 46) | private getSliderBody(id: string) {
    method modHidden (line 50) | private get modHidden() {
    method prepareSliderBody (line 54) | private prepareSliderBody(slider: Slider) {
    method prepareSliderTail (line 69) | private prepareSliderTail(slider: Slider) {
    method prepareSliderLastLegacyTick (line 74) | private prepareSliderLastLegacyTick(checkpoint: SliderCheckPoint) {
    method prepareSliderTicks (line 85) | private prepareSliderTicks(ticks: SliderCheckPoint[]) {
    method prepareSliderRepeats (line 103) | private prepareSliderRepeats(repeats: SliderCheckPoint[], slider: Slid...
    method sliderDevMode (line 143) | private get sliderDevMode() {
    method prepareSliderBall (line 147) | private prepareSliderBall(slider: Slider) {
    method time (line 195) | get time() {
    method skin (line 199) | get skin() {
    method create (line 203) | create(slider: Slider) {
    method postUpdate (line 243) | postUpdate() {
  function sliderBodySetting (line 248) | function sliderBodySetting(s: {

FILE: apps/desktop/frontend/src/app/services/renderers/components/playfield/SpinnerFactory.ts
  constant SPINNER_FADE_OUT_DURATION (line 10) | const SPINNER_FADE_OUT_DURATION = 300;
  class SpinnerFactory (line 13) | class SpinnerFactory {
    method constructor (line 14) | constructor(
    method create (line 21) | create(spinner: Spinner) {

FILE: apps/desktop/frontend/src/app/services/renderers/components/sliders/SliderTextureManager.ts
  type SliderTextureJob (line 13) | type SliderTextureJob = {
  class SliderTextureManager (line 22) | class SliderTextureManager {
    method constructor (line 25) | constructor(private readonly pixiRendererService: PixiRendererManager) {
    method getTexture (line 29) | getTexture(settings: SliderTextureJob): Texture {
    method removeFromCache (line 57) | removeFromCache(id: string) {

FILE: apps/desktop/frontend/src/app/services/renderers/components/stage/AnalysisStage.ts
  class AnalysisStage (line 18) | class AnalysisStage {
    method constructor (line 25) | constructor(
    method resizeTo (line 74) | resizeTo() {
    method updateAnalysisStage (line 99) | updateAnalysisStage() {
    method destroy (line 107) | destroy(): void {

FILE: apps/desktop/frontend/src/app/services/renderers/constants.ts
  constant STAGE_WIDTH (line 2) | const STAGE_WIDTH = 1600;
  constant STAGE_HEIGHT (line 3) | const STAGE_HEIGHT = 900;

FILE: apps/desktop/frontend/src/app/services/textures/TextureManager.ts
  type RewindTextureId (line 6) | type RewindTextureId = typeof RewindTextures[number];
  class TextureManager (line 9) | class TextureManager {
    method getTexture (line 17) | getTexture(textureId: RewindTextureId): Texture {
    method loadTexture (line 22) | async loadTexture(url: string) {

FILE: apps/desktop/frontend/src/app/services/types/index.ts
  constant STAGE_TYPES (line 1) | const STAGE_TYPES = {

FILE: apps/desktop/frontend/src/app/store/index.tsx
  type RootState (line 26) | type RootState = ReturnType<typeof store.getState>;
  type AppDispatch (line 27) | type AppDispatch = typeof store.dispatch;

FILE: apps/desktop/frontend/src/app/store/settings/slice.ts
  type RewindSettingsState (line 3) | interface RewindSettingsState {
  method settingsModalClosed (line 15) | settingsModalClosed(state) {
  method settingsModalOpened (line 18) | settingsModalOpened(state) {

FILE: apps/desktop/frontend/src/app/store/update/slice.ts
  type UpdateStatus (line 3) | interface UpdateStatus {
  method newVersionAvailable (line 44) | newVersionAvailable(draft, action: PayloadAction<string>) {
  method setUpdateModalOpen (line 48) | setUpdateModalOpen(draft, action: PayloadAction<boolean>) {
  method downloadProgressed (line 51) | downloadProgressed(
  method downloadFinished (line 61) | downloadFinished(draft) {
  method downloadErrorHappened (line 64) | downloadErrorHappened(draft) {

FILE: apps/desktop/frontend/src/app/utils/constants.ts
  constant ALLOWED_SPEEDS (line 10) | const ALLOWED_SPEEDS = [0.25, 0.75, 1.0, 1.5, 2.0, 4.0];
  constant ELECTRON_UPDATE_FLAG (line 12) | const ELECTRON_UPDATE_FLAG = false;

FILE: apps/desktop/frontend/src/app/utils/focus.ts
  function ignoreFocus (line 3) | function ignoreFocus(event: React.FocusEvent<HTMLButtonElement>) {

FILE: apps/desktop/frontend/src/app/utils/pooling/ObjectPool.ts
  type Creator (line 1) | type Creator<T> = () => T;
  type CleanUp (line 2) | type CleanUp<T> = (t: T) => unknown;
  type Options (line 4) | interface Options {
  class ObjectPool (line 12) | class ObjectPool<T> {
    method constructor (line 16) | constructor(
    method allocate (line 29) | allocate(id: string): [T, boolean] {
    method release (line 42) | release(id: string) {

FILE: apps/desktop/frontend/src/app/utils/pooling/TemporaryObjectPool.ts
  class TemporaryObjectPool (line 6) | class TemporaryObjectPool<T> extends ObjectPool<T> {
    method allocate (line 9) | allocate(id: string) {
    method releaseUntouched (line 14) | releaseUntouched() {

FILE: apps/desktop/frontend/src/app/utils/replay.ts
  function findIndexInReplayAtTime (line 7) | function findIndexInReplayAtTime(replay: ReplayFrame[], time: number): n...
  function interpolateReplayPosition (line 25) | function interpolateReplayPosition(fA: ReplayFrame, fB: ReplayFrame, tim...

FILE: apps/desktop/main/src/app/config.ts
  constant REWIND_CFG_NAME (line 6) | const REWIND_CFG_NAME = "rewind.cfg";
  type RewindElectronSettings (line 8) | interface RewindElectronSettings {
  constant DEFAULT_REWIND_ELECTRON_SETTINGS (line 12) | const DEFAULT_REWIND_ELECTRON_SETTINGS: RewindElectronSettings = Object....
  function readRewindElectronSettings (line 24) | function readRewindElectronSettings(configPath: string) {

FILE: apps/desktop/main/src/app/events.ts
  function userSelectDirectory (line 4) | async function userSelectDirectory(defaultPath: string) {
  function userSelectFile (line 13) | async function userSelectFile(defaultPath: string) {
  function getPath (line 22) | function getPath(type: string) {
  function setupEventListeners (line 36) | function setupEventListeners() {

FILE: apps/desktop/main/src/app/updater.ts
  function channelToUse (line 11) | function channelToUse(version: string) {
  function niceBytes (line 21) | function niceBytes(x: any) {
  function checkForUpdates (line 30) | function checkForUpdates() {
  function pollForUpdates (line 36) | function pollForUpdates() {
  function attachListeners (line 44) | function attachListeners() {
  function initializeAutoUpdater (line 83) | function initializeAutoUpdater() {

FILE: apps/desktop/main/src/app/windows.ts
  type Windows (line 3) | interface Windows {

FILE: apps/desktop/main/src/index.ts
  function isDevelopmentMode (line 12) | function isDevelopmentMode() {
  constant DEFAULT_WIDTH (line 17) | const DEFAULT_WIDTH = 1600;
  constant DEFAULT_HEIGHT (line 18) | const DEFAULT_HEIGHT = 900;
  constant MIN_WIDTH (line 19) | const MIN_WIDTH = 1024;
  constant MIN_HEIGHT (line 20) | const MIN_HEIGHT = 600;
  function createFrontendWindow (line 32) | function createFrontendWindow() {
  function handleAllWindowClosed (line 92) | function handleAllWindowClosed() {
  function handleReady (line 99) | function handleReady() {
  function handleActivate (line 115) | function handleActivate() {
  function createMenu (line 152) | function createMenu(osuFolder: string | null) {

FILE: apps/web/backend/src/DesktopAPI.ts
  constant REWIND_CFG_NAME (line 28) | const REWIND_CFG_NAME = "rewind.cfg";
  type RewindBootstrapSettings (line 30) | interface RewindBootstrapSettings {
  type NormalBootstrapSettings (line 42) | interface NormalBootstrapSettings {
  function listenCallback (line 48) | function listenCallback() {
  function createLogger (line 52) | function createLogger(logDirectory: string) {
  function normalBootstrap (line 75) | async function normalBootstrap(settings: {
  type SetupBootstrapSettings (line 152) | interface SetupBootstrapSettings {
  function getRewindCfgPath (line 157) | function getRewindCfgPath(applicationDataPath: string) {
  function setupBootstrap (line 161) | async function setupBootstrap({ userDataPath, logDirectory }: SetupBoots...
  function readOsuFolder (line 182) | async function readOsuFolder(applicationDataPath: string): Promise<strin...
  function bootstrapRewindDesktopBackend (line 192) | async function bootstrapRewindDesktopBackend(settings: RewindBootstrapSe...

FILE: apps/web/backend/src/api-common.module.ts
  class ApiCommonModule (line 19) | class ApiCommonModule {}

FILE: apps/web/backend/src/blueprints/BlueprintInfo.ts
  type BlueprintInfo (line 3) | interface BlueprintInfo {

FILE: apps/web/backend/src/blueprints/LocalBlueprintController.ts
  class LocalBlueprintController (line 9) | class LocalBlueprintController {
    method constructor (line 10) | constructor(private readonly blueprintService: LocalBlueprintService) {}
    method blueprint (line 12) | async blueprint(md5: string) {
    method getBlueprintByMD5 (line 21) | async getBlueprintByMD5(@Res() res: Response, @Param("md5hash") md5has...
    method getBlueprintOsu (line 28) | async getBlueprintOsu(@Res() res: Response, @Param("md5hash") md5hash:...
    method getBlueprintAudio (line 34) | async getBlueprintAudio(@Res() res: Response, @Param("md5hash") md5has...
    method getBlueprintBackground (line 40) | async getBlueprintBackground(@Res() res: Response, @Param("md5hash") m...
    method redirectToFolder (line 47) | async redirectToFolder(@Res() res: Response, @Param("md5hash") md5hash...

FILE: apps/web/backend/src/blueprints/LocalBlueprintService.ts
  constant METADATA_SECTIONS_TO_READ (line 14) | const METADATA_SECTIONS_TO_READ: BlueprintSection[] = ["General", "Diffi...
  function mapToLocalBlueprint (line 16) | function mapToLocalBlueprint(
  class LocalBlueprintService (line 37) | class LocalBlueprintService {
    method constructor (line 41) | constructor(private readonly osuDbDao: OsuDBDao, @Inject(OSU_SONGS_FOL...
    method completeRead (line 43) | async completeRead() {
    method addNewBlueprint (line 72) | addNewBlueprint(blueprint: BlueprintInfo) {
    method blueprintHasBeenAdded (line 76) | async blueprintHasBeenAdded() {
    method getAllBlueprints (line 87) | async getAllBlueprints(): Promise<Record<string, BlueprintInfo>> {
    method getBlueprintByMD5 (line 95) | async getBlueprintByMD5(md5: string): Promise<BlueprintInfo | undefine...
    method blueprintBg (line 101) | async blueprintBg(md5: string): Promise<string | undefined> {
  function getNewFolderCandidates (line 114) | async function getNewFolderCandidates(songsFolder: string, importedLater...
  function listOsuFiles (line 121) | async function listOsuFiles(songFolder: string): Promise<string[]> {

FILE: apps/web/backend/src/blueprints/OsuDBDao.ts
  class OsuDBDao (line 24) | class OsuDBDao {
    method constructor (line 28) | constructor(@Inject(OSU_FOLDER) private readonly osuFolder: string) {}
    method osuDbPath (line 30) | private get osuDbPath() {
    method createReader (line 34) | private async createReader() {
    method getOsuDbLastModifiedTime (line 39) | async getOsuDbLastModifiedTime() {
    method hasChanged (line 43) | async hasChanged(): Promise<boolean> {
    method cachedTime (line 47) | get cachedTime() {
    method getAllBlueprints (line 51) | async getAllBlueprints(): Promise<BlueprintInfo[]> {
    method getBlueprintByMD5 (line 63) | async getBlueprintByMD5(md5: string): Promise<BlueprintInfo | undefine...

FILE: apps/web/backend/src/config/DesktopConfigController.ts
  type UpdateOsuStablePathDto (line 6) | interface UpdateOsuStablePathDto {
  class DesktopConfigController (line 11) | class DesktopConfigController {
    method constructor (line 14) | constructor(private readonly desktopConfigService: DesktopConfigServic...
    method saveOsuStablePath (line 17) | async saveOsuStablePath(@Res() res: Response, @Body() { osuStablePath ...
    method readConfig (line 30) | async readConfig(@Res() res: Response) {

FILE: apps/web/backend/src/config/DesktopConfigService.ts
  type RewindDesktopConfig (line 7) | interface RewindDesktopConfig {
  constant REWIND_CFG_PATH (line 25) | const REWIND_CFG_PATH = Symbol("REWIND_CONFIG_PATH");
  class DesktopConfigService (line 30) | class DesktopConfigService {
    method constructor (line 31) | constructor(@Inject(REWIND_CFG_PATH) private readonly userConfigPath: ...
    method loadConfig (line 33) | async loadConfig(): Promise<RewindDesktopConfig> {
    method saveOsuStablePath (line 42) | async saveOsuStablePath(osuStableFolderPath: string) {

FILE: apps/web/backend/src/config/UserConfigService.ts
  type Config (line 3) | interface Config {
  constant NO_SKIN_ID (line 8) | const NO_SKIN_ID = "";
  class UserConfigService (line 16) | class UserConfigService {
    method constructor (line 19) | constructor() {
    method getConfig (line 23) | getConfig() {

FILE: apps/web/backend/src/config/utils.ts
  function osuFolderSanityCheck (line 16) | async function osuFolderSanityCheck(osuFolderPath: string) {

FILE: apps/web/backend/src/constants.ts
  constant OSU_FOLDER (line 2) | const OSU_FOLDER = "OSU_FOLDER";
  constant OSU_SONGS_FOLDER (line 3) | const OSU_SONGS_FOLDER = Symbol("OSU_SONGS_FOLDER");
  constant DEFAULT_SKIN_ID (line 5) | const DEFAULT_SKIN_ID = "RewindDefault";

FILE: apps/web/backend/src/events/Events.ts
  type ReplayReadEvent (line 8) | interface ReplayReadEvent {

FILE: apps/web/backend/src/events/EventsGateway.ts
  class EventsGateway (line 11) | class EventsGateway implements OnGatewayConnection, OnGatewayDisconnect {
    method constructor (line 14) | constructor(private readonly blueprintService: LocalBlueprintService) {}
    method afterInit (line 18) | afterInit(server: Server) {
    method handleReplayAdded (line 23) | async handleReplayAdded(event: ReplayReadEvent) {
    method handleDisconnect (line 31) | handleDisconnect(client: Socket) {
    method handleConnection (line 35) | handleConnection(client: Socket, ...args: any[]) {

FILE: apps/web/backend/src/replays/LocalReplayController.ts
  class LocalReplayController (line 16) | class LocalReplayController {
    method constructor (line 19) | constructor(private localReplayService: LocalReplayService) {}
    method decodeReplay (line 22) | async decodeReplay(@Res() res: Response, @Param("name") encodedName: s...

FILE: apps/web/backend/src/replays/LocalReplayService.ts
  constant REPLAY_NAME_SEPARATOR (line 7) | const REPLAY_NAME_SEPARATOR = ":";
  class LocalReplayService (line 10) | class LocalReplayService {
    method constructor (line 13) | constructor(@Inject(OSU_FOLDER) private osuDirectory: string) {}
    method exportedPath (line 15) | exportedPath(fileName: string) {
    method internalPath (line 19) | internalPath(fileName: string) {
    method osuExportedReplay (line 26) | async osuExportedReplay(fileName: string) {
    method osuInternalReplay (line 33) | async osuInternalReplay(fileName: string) {
    method localReplay (line 40) | async localReplay(absoluteFilePath: string) {
    method decodeReplay (line 44) | async decodeReplay(name: string) {

FILE: apps/web/backend/src/replays/ReplayWatcher.ts
  class ReplayWatcher (line 9) | class ReplayWatcher {
    method constructor (line 13) | constructor(private readonly eventEmitter: EventEmitter2) {}
    method watchForReplays (line 17) | watchForReplays(replaysFolder: string): ReplayWatcher {

FILE: apps/web/backend/src/skins/SkinController.ts
  class SkinController (line 6) | class SkinController {
    method constructor (line 9) | constructor(private readonly skinService: SkinService) {}
    method getAllSkins (line 12) | async getAllSkins(@Res() res: Response) {
    method getSkinInfo (line 18) | async getSkinInfo(@Res() res: Response, @Query() query: { hd: number; ...

FILE: apps/web/backend/src/skins/SkinNameResolver.ts
  type SkinsFolderConfig (line 4) | interface SkinsFolderConfig {
  type SkinNameResolverConfig (line 9) | type SkinNameResolverConfig = SkinsFolderConfig[];
  constant SKIN_NAME_RESOLVER_CONFIG (line 11) | const SKIN_NAME_RESOLVER_CONFIG = "SkinsFolderConfig";
  class SkinNameResolver (line 14) | class SkinNameResolver {
    method constructor (line 15) | constructor(@Inject(SKIN_NAME_RESOLVER_CONFIG) private readonly skinNa...
    method resolveNameToPath (line 17) | resolveNameToPath(name: string) {

FILE: apps/web/backend/src/skins/SkinService.ts
  constant OSU_DEFAULT_SKIN_ID (line 8) | const OSU_DEFAULT_SKIN_ID = "rewind/OsuDefaultSkin";
  class SkinService (line 11) | class SkinService {
    method constructor (line 14) | constructor(@Inject(OSU_FOLDER) private osuDirectory: string, private ...
    method listAllSkins (line 16) | async listAllSkins() {
    method resolve (line 25) | async resolve(
    method getSkinInfo (line 44) | async getSkinInfo(skinName: string) {

FILE: apps/web/backend/src/status/SetupStatusController.ts
  class SetupStatusController (line 5) | class SetupStatusController {
    method getBackendStatus (line 7) | getBackendStatus(@Res() res: Response) {
  class NormalStatusController (line 13) | class NormalStatusController {
    method getBackendStatus (line 16) | getBackendStatus(@Res() res: Response) {

FILE: apps/web/backend/src/utils/names.ts
  function splitByFirstOccurrence (line 8) | function splitByFirstOccurrence(str: string, delimiter: string) {

FILE: libs/@types/node-osr/index.d.ts
  class Replay (line 5) | class Replay {

FILE: libs/osu-local/db-reader/src/DatabaseReader.ts
  class Reader (line 4) | class Reader {
    method constructor (line 7) | constructor(buffer: Buffer) {
    method readByte (line 11) | readByte() {
    method readShort (line 15) | readShort() {
    method readInt (line 19) | readInt() {
    method readLong (line 23) | readLong() {
    method readString (line 27) | readString() {
    method readSingle (line 31) | readSingle() {
    method readDouble (line 35) | readDouble() {
    method readBoolean (line 39) | readBoolean() {
    method readDateTime (line 43) | readDateTime() {

FILE: libs/osu-local/db-reader/src/DatabaseTypes.ts
  type Byte (line 8) | type Byte = number;
  type Short (line 9) | type Short = number;
  type Int (line 10) | type Int = number;
  type Long (line 11) | type Long = bigint;
  type ULEB128 (line 12) | type ULEB128 = bigint;
  type Single (line 13) | type Single = number;
  type Double (line 14) | type Double = number;
  type IntSinglePair (line 17) | type IntSinglePair = [Int, Single];
  type TimingPoint (line 18) | type TimingPoint = {
  type DateTime (line 24) | type DateTime = bigint;
  type OsuDB (line 26) | type OsuDB = {
  type RankedStatus (line 37) | enum RankedStatus {
  type GameplayMode (line 48) | enum GameplayMode {
  type StarRatings (line 56) | type StarRatings = IntSinglePair[];
  type Beatmap (line 58) | type Beatmap = {
  type Score (line 114) | type Score = {
  type ScoresDBBeatmap (line 135) | type ScoresDBBeatmap = {
  type ScoresDB (line 141) | type ScoresDB = {

FILE: libs/osu-local/db-reader/src/OsuBuffer.ts
  class OsuBuffer (line 26) | class OsuBuffer {
    method constructor (line 33) | constructor(input?: Buffer) {
    method length (line 42) | get length() {
    method toString (line 50) | toString(type: "binary" | "ascii" = "binary") {
    method from (line 58) | static from(args: any) {
    method canRead (line 70) | canRead(length: number) {
    method isEOF (line 78) | isEOF() {
    method slice (line 88) | slice(length: number, asOsuBuffer = true) {
    method peek (line 101) | peek() {
    method readByte (line 110) | readByte() {
    method readInt (line 119) | readInt(byteLength: number) {
    method readUInt (line 129) | readUInt(byteLength: number) {
    method readInt8 (line 138) | readInt8() {
    method readUInt8 (line 146) | readUInt8() {
    method readInt16 (line 154) | readInt16() {
    method readUInt16 (line 162) | readUInt16() {
    method readInt32 (line 170) | readInt32() {
    method readUInt32 (line 178) | readUInt32() {
    method readInt64 (line 186) | readInt64(): BigInt {
    method readUInt64 (line 195) | readUInt64() {
    method readFloat (line 204) | readFloat() {
    method readDouble (line 213) | readDouble() {
    method readString (line 223) | readString(length: number) {
    method readVarInt (line 231) | readVarInt() {
    method readULeb128 (line 257) | readULeb128() {
    method readBoolean (line 265) | readBoolean() {
    method readOsuString (line 273) | readOsuString() {
    method writeBuffer (line 290) | writeBuffer(value: Buffer): OsuBuffer {
    method writeUInt (line 301) | writeUInt(value: number, byteLength: number): OsuBuffer {
    method writeInt (line 314) | writeInt(value: number, byteLength: number) {
    method writeByte (line 326) | writeByte(value: number) {
    method writeBytes (line 335) | writeBytes(value: Array<any>) {
    method writeUInt8 (line 344) | writeUInt8(value: number) {
    method writeInt8 (line 353) | writeInt8(value: number) {
    method writeUInt16 (line 362) | writeUInt16(value: number) {
    method writeInt16 (line 371) | writeInt16(value: number) {
    method writeUInt32 (line 380) | writeUInt32(value: number) {
    method writeInt32 (line 389) | writeInt32(value: number) {
    method writeUInt64 (line 399) | writeUInt64(value: number) {
    method writeInt64 (line 414) | writeInt64(value: number) {
    method writeFloat (line 429) | writeFloat(value: number) {
    method writeDouble (line 441) | writeDouble(value: number) {
    method writeString (line 453) | writeString(value: string) {
    method writeBoolean (line 465) | writeBoolean(value: number) {
    method writeOsuString (line 475) | writeOsuString(value: string, nullable = false) {
    method writeVarInt (line 494) | writeVarInt(value: number) {
    method writeULeb128 (line 513) | writeULeb128(value: number) {

FILE: libs/osu-local/db-reader/src/OsuDBReader.ts
  class OsuDBReader (line 7) | class OsuDBReader extends Reader {
    method readStarRatings (line 8) | readStarRatings(): StarRatings {
    method readTimingPoints (line 23) | readTimingPoints(): TimingPoint[] {
    method readBeatmap (line 35) | readBeatmap(version: number): Beatmap {

FILE: libs/osu-local/db-reader/src/ScoresDBReader.ts
  class ScoresDBReader (line 4) | class ScoresDBReader extends Reader {
    method readScore (line 5) | private readScore(): Score {
    method readScores (line 51) | private readScores(numberOfScores: number): Score[] {
    method readBeatmap (line 59) | private readBeatmap() {
    method readBeatmaps (line 70) | private readBeatmaps(numberOfBeatmaps: number) {
    method readScoresDB (line 78) | readScoresDB(): ScoresDB {

FILE: libs/osu-local/gosumemory/src/gosumemory.ts
  type GosuMemoryAPI (line 3) | type GosuMemoryAPI = {
  type OsuMemoryStatus (line 111) | enum OsuMemoryStatus {

FILE: libs/osu-local/skin-reader/src/SkinFolderReader.ts
  constant SKIN_CONFIG_FILENAME (line 8) | const SKIN_CONFIG_FILENAME = "skin.ini";
  type ListSkinsInFolderOptions (line 10) | interface ListSkinsInFolderOptions {
  class SkinFolderReader (line 21) | class SkinFolderReader {
    method listSkinsInFolder (line 27) | static async listSkinsInFolder(
    method getSkinResolver (line 61) | static async getSkinResolver(skinFolderPath: string): Promise<OsuLegac...

FILE: libs/osu-local/skin-reader/src/SkinTextureResolver.ts
  type GetTextureFileOption (line 12) | type GetTextureFileOption = {
  type OsuFileNameOptions (line 20) | type OsuFileNameOptions = {
  type TextureFileLocation (line 26) | type TextureFileLocation = {
  type OsuSkinTextureResolver (line 31) | interface OsuSkinTextureResolver {
  class OsuLegacySkinTextureResolver (line 44) | class OsuLegacySkinTextureResolver implements OsuSkinTextureResolver {
    method constructor (line 45) | constructor(readonly folderPath: string, readonly config: SkinConfig) {}
    method getFontPrefix (line 50) | getFontPrefix(skinTexture: OsuSkinTextures): string | undefined {
    method getPrefix (line 88) | getPrefix(skinTexture: OsuSkinTextures) {
    method getFilename (line 103) | getFilename(skinTexture: OsuSkinTextures, opt: OsuFileNameOptions) {
    method checkForFileExistenceInFolder (line 126) | async checkForFileExistenceInFolder(fileName: string) {
    method checkForFirstAppearanceInFolder (line 136) | async checkForFirstAppearanceInFolder(fileNames: string[]) {
    method checkForTextureWithFallback (line 147) | async checkForTextureWithFallback(skinTexture: OsuSkinTextures, option...
    method resolve (line 154) | async resolve(skinTexture: OsuSkinTextures, option: GetTextureFileOpti...

FILE: libs/osu-local/utils/src/dates.ts
  type WindowsDate (line 8) | type WindowsDate = [Date, number];

FILE: libs/osu-local/utils/src/files.ts
  function filterFilenamesInDirectory (line 22) | async function filterFilenamesInDirectory(
  function createFolderIfNotExisting (line 41) | async function createFolderIfNotExisting(folderPath: string) {
  function osuUserConfigFileName (line 52) | function osuUserConfigFileName(userName: string) {
  function determineSongsFolder (line 61) | async function determineSongsFolder(osuFolderPath: string, userName: str...

FILE: libs/osu-local/utils/src/osuUserConfig.ts
  function osuUserConfigParse (line 1) | function osuUserConfigParse(data: string) {

FILE: libs/osu-pixi/classic-components/src/DrawableSettings.ts
  type AnimationTimeSetting (line 3) | interface AnimationTimeSetting {
  type ModHiddenSetting (line 7) | interface ModHiddenSetting {
  type PositionSetting (line 10) | interface PositionSetting {
  type ScaleSetting (line 14) | interface ScaleSetting {
  type TintSetting (line 18) | interface TintSetting {
  type HitResult (line 25) | type HitResult = {
  type HitResultSetting (line 29) | type HitResultSetting = {

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicApproachCircle.ts
  type OsuClassicApproachCircleSettings (line 15) | interface OsuClassicApproachCircleSettings extends AnimationTimeSetting,...
  constant DEFAULT_SETTINGS (line 26) | const DEFAULT_SETTINGS: OsuClassicApproachCircleSettings = {
  class OsuClassicApproachCircle (line 37) | class OsuClassicApproachCircle implements PrepareSetting<OsuClassicAppro...
    method constructor (line 41) | constructor(settings: Partial<OsuClassicApproachCircleSettings>) {
    method prepare (line 46) | prepare(settings: Partial<OsuClassicApproachCircleSettings>): void {
    method normalTransformation (line 54) | normalTransformation(): DisplayObjectTransformationProcess {
    method hiddenTransformation (line 82) | hiddenTransformation(): DisplayObjectTransformationProcess {
    method animate (line 90) | animate(): void {
    method dispose (line 97) | dispose(): void {

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicCursor.ts
  constant MAX_CURSOR_TRAILS (line 8) | const MAX_CURSOR_TRAILS = 8;
  type OsuClassicCursorSetting (line 10) | interface OsuClassicCursorSetting {
  class OsuClassicCursor (line 44) | class OsuClassicCursor implements PrepareSetting<OsuClassicCursorSetting> {
    method constructor (line 50) | constructor() {
    method prepare (line 65) | prepare(setting: Partial<OsuClassicCursorSetting>): void {

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicHitCircleArea.ts
  type OsuClassicHitCircleAreaSettings (line 27) | interface OsuClassicHitCircleAreaSettings
  constant DEFAULT_NUMBER_SCALING_IN_HIT_CIRCLE (line 61) | const DEFAULT_NUMBER_SCALING_IN_HIT_CIRCLE = 0.8;
  constant DEFAULT_NUMBER_TEXTURES (line 62) | const DEFAULT_NUMBER_TEXTURES: Texture[] = Array(10).fill(Texture.EMPTY);
  class OsuClassicHitCircleArea (line 93) | class OsuClassicHitCircleArea implements PrepareSetting<OsuClassicHitCir...
    method constructor (line 103) | constructor(settings?: Partial<OsuClassicHitCircleAreaSettings>) {
    method prepareHitCircleSprites (line 112) | private prepareHitCircleSprites(): void {
    method prepareNumber (line 119) | private prepareNumber(): void {
    method prepareSpriteOrder (line 128) | private prepareSpriteOrder(): void {
    method animateHitCircleSprites (line 142) | private animateHitCircleSprites(): void {
    method animateNumber (line 151) | private animateNumber(): void {
    method animateSelf (line 160) | private animateSelf(): void {
    method animate (line 169) | private animate(): void {
    method prepare (line 175) | prepare(settings: Partial<OsuClassicHitCircleAreaSettings>): void {
  function numberTransform (line 185) | function numberTransform(hitResult: HitResult | null): DisplayObjectTran...
  function normalHitCircleTransforms (line 204) | function normalHitCircleTransforms(
  function hiddenHitCircleTransforms (line 226) | function hiddenHitCircleTransforms(approachDuration: number): DisplayObj...
  function hitCircleSpritesTransform (line 246) | function hitCircleSpritesTransform(hitResult: HitResult | null): Display...

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicSliderBall.ts
  type OsuClassicSliderBallSettings (line 12) | interface OsuClassicSliderBallSettings extends PositionSetting, ScaleSet...
  class OsuClassicSliderBall (line 33) | class OsuClassicSliderBall implements PrepareSetting<OsuClassicSliderBal...
    method constructor (line 40) | constructor() {
    method prepare (line 51) | prepare(setting: Partial<OsuClassicSliderBallSettings>): void {

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicSliderBody.ts
  type SliderBodySettings (line 14) | interface SliderBodySettings extends AnimationTimeSetting, ModHiddenSett...
  class OsuClassicSliderBody (line 37) | class OsuClassicSliderBody implements PrepareSetting<SliderBodySettings> {
    method constructor (line 42) | constructor() {
    method transformation (line 48) | static transformation(settings: {
    method prepare (line 93) | prepare(settings: Partial<SliderBodySettings>): void {

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicSliderRepeat.ts
  type OsuClassicSliderRepeatSettings (line 12) | interface OsuClassicSliderRepeatSettings extends PositionSetting, ScaleS...
  class OsuClassicSliderRepeat (line 45) | class OsuClassicSliderRepeat implements PrepareSetting<OsuClassicSliderR...
    method constructor (line 49) | constructor() {
    method normalTransformation (line 54) | static normalTransformation(settings: {
    method prepare (line 107) | prepare(setting: Partial<OsuClassicSliderRepeatSettings>): void {

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicSliderTick.ts
  type SliderTickSettings (line 13) | interface SliderTickSettings {
  constant ANIM_DURATION (line 22) | const ANIM_DURATION = 150;
  class OsuClassicSliderTick (line 24) | class OsuClassicSliderTick {
    method constructor (line 27) | constructor() {
    method normalTransformation (line 31) | static normalTransformation(settings: {
    method prepare (line 51) | prepare(settings: SliderTickSettings): void {

FILE: libs/osu-pixi/classic-components/src/hitobjects/OsuClassicSpinner.ts
  type OsuClassicSpinnerSettings (line 14) | interface OsuClassicSpinnerSettings {
  function approachCircleTransformation (line 21) | function approachCircleTransformation(settings: {
  class OsuClassicSpinner (line 44) | class OsuClassicSpinner {
    method constructor (line 48) | constructor() {
    method prepare (line 55) | prepare(settings: OsuClassicSpinnerSettings) {

FILE: libs/osu-pixi/classic-components/src/hud/HitErrorBar.ts
  type HitEvent (line 4) | type HitEvent = {
  type AnalysisHitErrorBarSettings (line 10) | interface AnalysisHitErrorBarSettings {
  function coloredSprite (line 25) | function coloredSprite(color: number) {
  class OsuClassicHitErrorBar (line 33) | class OsuClassicHitErrorBar {
    method constructor (line 42) | constructor() {
    method prepare (line 58) | prepare(setting: AnalysisHitErrorBarSettings) {

FILE: libs/osu-pixi/classic-components/src/hud/OsuClassicAccuracy.ts
  type OsuClassicAccuracySettings (line 5) | interface OsuClassicAccuracySettings {
  class OsuClassicAccuracy (line 14) | class OsuClassicAccuracy {
    method constructor (line 21) | constructor() {
    method prepare (line 30) | prepare(settings: OsuClassicAccuracySettings) {

FILE: libs/osu-pixi/classic-components/src/hud/OsuClassicJudgement.ts
  type OsuClassicJudgementsSettings (line 13) | interface OsuClassicJudgementsSettings extends AnimationTimeSetting, Pos...
  class OsuClassicJudgement (line 44) | class OsuClassicJudgement implements PrepareSetting<OsuClassicJudgements...
    method constructor (line 48) | constructor() {
    method normalTransformation (line 55) | static normalTransformation(scale: number, position: Position): Displa...
    method prepare (line 71) | prepare(setting: Partial<OsuClassicJudgementsSettings>) {

FILE: libs/osu-pixi/classic-components/src/hud/OsuClassicNumber.ts
  type OsuClassicNumberSettings (line 3) | type OsuClassicNumberSettings = {
  constant DEFAULT_NUMBER_TEXTURES (line 9) | const DEFAULT_NUMBER_TEXTURES: Texture[] = Array(10).fill(Texture.EMPTY);
  constant DEFAULT_NUMBER_SETTINGS (line 11) | const DEFAULT_NUMBER_SETTINGS: OsuClassicNumberSettings = {
  class OsuClassicNumber (line 21) | class OsuClassicNumber extends Container {
    method constructor (line 25) | constructor() {
    method anchorX (line 35) | set anchorX(value) {
    method anchorX (line 40) | get anchorX() {
    method anchorY (line 44) | set anchorY(value) {
    method anchorY (line 49) | get anchorY() {
    method prepare (line 53) | prepare(settings: OsuClassicNumberSettings): void {

FILE: libs/osu-pixi/classic-components/src/playfield/PlayfieldBorder.ts
  type PlayfieldBorderSettings (line 5) | interface PlayfieldBorderSettings {
  constant DEFAULT_COLOR (line 19) | const DEFAULT_COLOR = 0xffffff;
  constant DEFAULT_ALPHA (line 20) | const DEFAULT_ALPHA = 0.7;
  class PlayfieldBorder (line 22) | class PlayfieldBorder {
    method constructor (line 25) | constructor() {
    method onSettingsChange (line 29) | onSettingsChange(settings: PlayfieldBorderSettings) {

FILE: libs/osu-pixi/classic-components/src/renderers/BasicSliderTextureRenderer.ts
  constant MESH_CENTER_HEIGHT (line 8) | const MESH_CENTER_HEIGHT = -0.1;
  constant SLIDER_BODY_UNIT_CIRCLE_SUBDIVISIONS (line 9) | const SLIDER_BODY_UNIT_CIRCLE_SUBDIVISIONS = 42;
  function getUnitCircle (line 118) | function getUnitCircle(numberOfDivisions: number): Position[] {
  function getUnitCircleScaled (line 128) | function getUnitCircleScaled(numberOfDivisions: number, radius: number) {
  constant MAX_NUMBER_OF_VERTICES (line 135) | const MAX_NUMBER_OF_VERTICES = 60000;
  function numberOfVertices (line 137) | function numberOfVertices(numberOfPoints: number) {
  function getSliderGeometry (line 142) | function getSliderGeometry(points: Position[], radius: number): PIXI.Geo...
  type Color (line 295) | type Color = [number, number, number];
  type SliderTextureParams (line 296) | type SliderTextureParams = {
  class BasicSliderTextureRenderer (line 331) | class BasicSliderTextureRenderer {
    method constructor (line 335) | constructor(renderer: PIXI.Renderer) {
    method render (line 340) | render(params: SliderTextureParams): RenderTexture {

FILE: libs/osu-pixi/classic-components/src/utils/Animation.ts
  function animationIndex (line 1) | function animationIndex(time: number, animationFrameRate = 60) {

FILE: libs/osu-pixi/classic-components/src/utils/Pixi.ts
  function setAlphaScaling (line 13) | function setAlphaScaling(o: DisplayObject, alpha: number, scaling: numbe...
  function fading (line 22) | function fading(
  function fadeInFromTo (line 43) | function fadeInFromTo(o: DisplayObject, t: number, from: number, to: num...
  function fadeInWithDuration (line 47) | function fadeInWithDuration(
  function fadeOutFromTo (line 57) | function fadeOutFromTo(o: DisplayObject, t: number, from: number, to: nu...
  function fadeOutWithDuration (line 61) | function fadeOutWithDuration(
  function scaling (line 71) | function scaling(
  function scaleTo (line 92) | function scaleTo(
  function scaleToWithDuration (line 103) | function scaleToWithDuration(
  type DisplayObjectTransformationProcess (line 115) | type DisplayObjectTransformationProcess = {
  type AssignableDisplayObjectProperties (line 121) | type AssignableDisplayObjectProperties = {
  type DisplayObjectProperties (line 127) | type DisplayObjectProperties = {
  type AssignableSpriteProperties (line 133) | type AssignableSpriteProperties = AssignableDisplayObjectProperties & {
  function applyPropertiesToDisplayObject (line 141) | function applyPropertiesToDisplayObject(

FILE: libs/osu-pixi/classic-components/src/utils/Preparable.ts
  type PrepareSetting (line 1) | interface PrepareSetting<T> {

FILE: libs/osu-pixi/classic-components/src/utils/Transformations.ts
  type FunctionOverTime (line 30) | type FunctionOverTime<T> = (time: number) => T;
  type Transformation (line 43) | type Transformation<T> = {
  type TransformationProcess (line 49) | type TransformationProcess<T> = {

FILE: libs/osu-pixi/classic-components/src/utils/constants.ts
  constant OSU_PLAYFIELD_WIDTH (line 2) | const OSU_PLAYFIELD_WIDTH = 512;
  constant OSU_PLAYFIELD_HEIGHT (line 3) | const OSU_PLAYFIELD_HEIGHT = 384;

FILE: libs/osu-pixi/classic-components/src/utils/numbers.ts
  function calculateDigits (line 7) | function calculateDigits(x: number) {
  function calculateAccuracyDigits (line 27) | function calculateAccuracyDigits(accuracy: number): [number[], number[]] {

FILE: libs/osu-pixi/rewind/src/lib/AnalysisCursor.ts
  class AnalysisCross (line 5) | class AnalysisCross extends Graphics {
    method prepare (line 6) | prepare(color: number, interesting?: boolean) {
  type AnalysisColorStyle (line 25) | enum AnalysisColorStyle {
  type AnalysisPoint (line 30) | interface AnalysisPoint {
  type Settings (line 44) | interface Settings {
  class AnalysisCursor (line 54) | class AnalysisCursor {
    method constructor (line 60) | constructor() {
    method prepare (line 73) | prepare(settings: Partial<Settings>): void {

FILE: libs/osu/core/src/audio/HitSampleInfo.ts
  class HitSampleInfo (line 1) | class HitSampleInfo {
    method constructor (line 18) | constructor(name: string, bank: string | null = null, suffix: string |...

FILE: libs/osu/core/src/audio/LegacySampleBank.ts
  type LegacySampleBank (line 1) | enum LegacySampleBank {

FILE: libs/osu/core/src/beatmap/Beatmap.ts
  class Beatmap (line 15) | class Beatmap {
    method constructor (line 21) | constructor(
    method getHitObject (line 31) | getHitObject(id: string): AllHitObjects {
    method getSliderCheckPoint (line 37) | getSliderCheckPoint(id: string): SliderCheckPoint {
    method getSlider (line 41) | getSlider(id: string): Slider {
    method getHitCircle (line 45) | getHitCircle(id: string): HitCircle {
    method getSpinner (line 49) | getSpinner(id: string): Spinner {
  function mostCommonBeatLength (line 57) | function mostCommonBeatLength({

FILE: libs/osu/core/src/beatmap/BeatmapBuilder.ts
  function copyPosition (line 21) | function copyPosition({ x, y }: Position): Position {
  function createHitCircle (line 25) | function createHitCircle(
  function createSliderCheckPoint (line 41) | function createSliderCheckPoint(slider: Slider, id: string, descriptor: ...
  function createSliderCheckPoints (line 54) | function createSliderCheckPoints(slider: Slider): SliderCheckPoint[] {
  function copyPathPoints (line 72) | function copyPathPoints(pathPoints: PathControlPoint[]) {
  function createSlider (line 79) | function createSlider(
  function createSpinner (line 113) | function createSpinner(
  function createStaticHitObject (line 126) | function createStaticHitObject(
  function assignComboIndex (line 149) | function assignComboIndex(bluePrintSettings: HitObjectSettings[], hitObj...
  function findDifficultyApplier (line 173) | function findDifficultyApplier(mods: OsuClassicMod[]): BeatmapDifficulty...
  type BeatmapBuilderOptions (line 183) | interface BeatmapBuilderOptions {
  function buildBeatmap (line 203) | function buildBeatmap(bluePrint: Blueprint, options?: Partial<BeatmapBui...

FILE: libs/osu/core/src/beatmap/BeatmapDifficulty.ts
  type BeatmapDifficulty (line 1) | type BeatmapDifficulty = {
  constant DEFAULT_BEATMAP_DIFFICULTY (line 10) | const DEFAULT_BEATMAP_DIFFICULTY: BeatmapDifficulty = Object.freeze({

FILE: libs/osu/core/src/beatmap/ControlPoints/ControlPoint.ts
  method time (line 7) | get time(): number {
  method compareTo (line 15) | compareTo(other: ControlPoint): number {
  method attachGroup (line 19) | attachGroup(pointGroup: ControlPointGroup): void {

FILE: libs/osu/core/src/beatmap/ControlPoints/ControlPointGroup.ts
  class ControlPointGroup (line 4) | class ControlPointGroup {
    method constructor (line 12) | constructor(time: number) {
    method add (line 16) | add(point: ControlPoint): void {

FILE: libs/osu/core/src/beatmap/ControlPoints/ControlPointInfo.ts
  class ControlPointInfo (line 10) | class ControlPointInfo {
    method difficultyPointAt (line 19) | difficultyPointAt(time: number): DifficultyControlPoint {
    method samplePointAt (line 23) | samplePointAt(time: number): SampleControlPoint {
    method add (line 31) | add(time: number, controlPoint: ControlPoint): boolean {
    method groupAt (line 39) | groupAt(time: number, addIfNotExisting = false): ControlPointGroup | n...
    method groupItemAdded (line 61) | groupItemAdded(controlPoint: ControlPoint): void {
    method groupItemRemoved (line 78) | groupItemRemoved(controlPoint: ControlPoint): void {
    method timingPointAt (line 95) | timingPointAt(time: number): TimingControlPoint {
    method effectPointAt (line 103) | effectPointAt(time: number): EffectControlPoint {
    method binarySearchWithFallback (line 107) | binarySearchWithFallback<T extends ControlPoint>(list: T[], time: numb...
    method binarySearch (line 113) | binarySearch<T extends ControlPoint>(list: T[], time: number): T | null {
    method checkAlreadyExisting (line 129) | private checkAlreadyExisting(time: number, newPoint: ControlPoint): bo...

FILE: libs/osu/core/src/beatmap/ControlPoints/DifficultyControlPoint.ts
  class DifficultyControlPoint (line 3) | class DifficultyControlPoint extends ControlPoint {
    method type (line 8) | get type(): string {
    method isRedundant (line 12) | isRedundant(existing: ControlPoint): boolean {

FILE: libs/osu/core/src/beatmap/ControlPoints/EffectControlPoint.ts
  class EffectControlPoint (line 3) | class EffectControlPoint extends ControlPoint {
    method isRedundant (line 10) | isRedundant(existing: ControlPoint): boolean {
    method type (line 16) | get type(): string {

FILE: libs/osu/core/src/beatmap/ControlPoints/SampleControlPoint.ts
  class SampleControlPoint (line 3) | class SampleControlPoint extends ControlPoint {
    method isRedundant (line 11) | isRedundant(existing: ControlPoint): boolean {
    method type (line 17) | get type(): string {

FILE: libs/osu/core/src/beatmap/ControlPoints/TimingControlPoint.ts
  class TimingControlPoint (line 4) | class TimingControlPoint extends ControlPoint {
    method BPM (line 13) | get BPM(): number {
    method type (line 17) | get type(): string {
    method isRedundant (line 21) | isRedundant(existing: ControlPoint): boolean {

FILE: libs/osu/core/src/beatmap/LegacyEffectFlag.ts
  type LegacyEffectFlags (line 1) | enum LegacyEffectFlags {

FILE: libs/osu/core/src/beatmap/TimeSignatures.ts
  type TimeSignatures (line 1) | enum TimeSignatures {

FILE: libs/osu/core/src/blueprint/Blueprint.ts
  type Blueprint (line 18) | interface Blueprint {
  type BlueprintInfo (line 25) | interface BlueprintInfo {
  type BlueprintMetadata (line 33) | interface BlueprintMetadata {

FILE: libs/osu/core/src/blueprint/BlueprintParser.spec.ts
  function mapToOffsets (line 8) | function mapToOffsets(controlPoints: PathControlPoint[]): Position[] {

FILE: libs/osu/core/src/blueprint/BlueprintParser.ts
  constant SECTION_REGEX (line 19) | const SECTION_REGEX = /^\s*\[(.+?)]\s*$/;
  constant DEFAULT_LEGACY_TICK_OFFSET (line 20) | const DEFAULT_LEGACY_TICK_OFFSET = 36;
  function stripComments (line 27) | function stripComments(line: string): string {
  type LegacyHitObjectType (line 36) | enum LegacyHitObjectType {
  type LegacyHitSoundType (line 45) | enum LegacyHitSoundType {
  class LegacyHitSampleInfo (line 53) | class LegacyHitSampleInfo extends HitSampleInfo {
    method constructor (line 57) | constructor(name: string, bank: string | null = null, volume = 0, cust...
  function hasFlag (line 64) | function hasFlag(bitmask: number, flag: number): boolean {
  function splitKeyVal (line 68) | function splitKeyVal(line: string, separator = ":"): [string, string] {
  function convertPathType (line 73) | function convertPathType(value: string): PathType {
  class LegacyDifficultyControlPoint (line 89) | class LegacyDifficultyControlPoint extends DifficultyControlPoint {
    method constructor (line 92) | constructor(beatLength: number) {
  type SampleBankInfo (line 101) | type SampleBankInfo = {
  function convertPathString (line 109) | function convertPathString(pointString: string, offset: Position): PathC...
  function readPoint (line 136) | function readPoint(value: string, startPos: Position, points: PathContro...
  function isLinear (line 142) | function isLinear(p: Position[]): boolean {
  function convertPoints (line 146) | function convertPoints(
  function parseOsuHitObjectSetting (line 198) | function parseOsuHitObjectSetting(line: string): HitCircleSettings | Sli...
  type BlueprintDifficulty (line 275) | type BlueprintDifficulty = Optional<BeatmapDifficulty, "approachRate">;
  class BlueprintParser (line 305) | class BlueprintParser {
    method constructor (line 329) | constructor(data: string, options: BlueprintParseOptions = defaultOpti...
    method isFinishedReading (line 344) | isFinishedReading() {
    method parseLine (line 348) | parseLine(line: string): void {
    method handleEvents (line 400) | handleEvents(line: string) {
    method handleGeneral (line 417) | handleGeneral(line: string): void {
    method handleEditor (line 453) | handleEditor(line: string): void {
    method handleMetadata (line 469) | handleMetadata(line: string): void {
    method handleDifficulty (line 504) | handleDifficulty(line: string): void {
    method handleHitObjects (line 529) | handleHitObjects(line: string): void {
    method handleTimingPoints (line 536) | handleTimingPoints(line: string): void {
    method createTimingControlPoint (line 605) | createTimingControlPoint(): TimingControlPoint {
    method addControlPoint (line 609) | addControlPoint(time: number, point: ControlPoint, timingChange: boole...
    method flushPendingPoints (line 621) | flushPendingPoints(): void {
    method getOffsetTime (line 634) | getOffsetTime(time: number): number {
    method parse (line 638) | parse(): Blueprint {
  constant MIN_BEAT_LENGTH (line 666) | const MIN_BEAT_LENGTH = 6;
  constant MAX_BEAT_LENGTH (line 667) | const MAX_BEAT_LENGTH = 60000;
  constant DEFAULT_BEAT_LENGTH (line 668) | const DEFAULT_BEAT_LENGTH = 1000;
  function bindableNumberNew (line 672) | function bindableNumberNew(val: number, { min, max, precision }: { min: ...
  type BlueprintSection (line 687) | type BlueprintSection = typeof BlueprintSections[number];
  type BlueprintParseOptions (line 689) | interface BlueprintParseOptions {
  function parseBlueprint (line 706) | function parseBlueprint(data: string, options?: Partial<BlueprintParseOp...

FILE: libs/osu/core/src/blueprint/HitObjectSettings.ts
  type HitObjectSettingsType (line 5) | type HitObjectSettingsType = "HIT_CIRCLE" | "SLIDER" | "SPINNER" | "MANI...
  type HitObjectSettings (line 8) | interface HitObjectSettings {
  type HitCircleSettings (line 20) | interface HitCircleSettings extends HitObjectSettings {
  type SliderSettings (line 24) | interface SliderSettings extends HitObjectSettings {
  type SpinnerSettings (line 41) | interface SpinnerSettings extends HitObjectSettings {

FILE: libs/osu/core/src/gameplay/GameState.ts
  constant NOT_PRESSING (line 4) | const NOT_PRESSING = +727727727;
  type PressingSinceTimings (line 6) | type PressingSinceTimings = number[];
  type HitCircleMissReason (line 20) | type HitCircleMissReason = typeof HitCircleMissReasons[number];
  type HitCircleVerdict (line 22) | type HitCircleVerdict = {
  type CheckPointState (line 27) | type CheckPointState = {
  type SliderBodyState (line 31) | type SliderBodyState = {
  type SpinnerState (line 35) | type SpinnerState = {
  type GameState (line 48) | interface GameState {
  function cloneGameState (line 102) | function cloneGameState(replayState: GameState): GameState {

FILE: libs/osu/core/src/gameplay/GameStateEvaluator.ts
  type Event (line 36) | type Event = {
  function generateEvents (line 49) | function generateEvents(beatmap: Beatmap, hitWindows: number[]): Event[] {
  type NoteLockStyle (line 81) | type NoteLockStyle = "NONE" | "STABLE" | "LAZER";
  type HitWindowStyle (line 82) | type HitWindowStyle = "OSU_STABLE" | "OSU_LAZER";
  type GameStateEvaluatorOptions (line 84) | type GameStateEvaluatorOptions = {
  function isWithinHitWindow (line 101) | function isWithinHitWindow(hitWindow: number[], delta: number, verdict: ...
  class GameStateEvaluator (line 105) | class GameStateEvaluator {
    method constructor (line 112) | constructor(private readonly beatmap: Beatmap, options?: GameStateEval...
    method judgeHitCircle (line 121) | judgeHitCircle(id: string, verdict: HitCircleVerdict) {
    method handleHitCircleSpawn (line 127) | handleHitCircleSpawn(time: number, hitCircleId: string) {
    method handleHitCircleForceKill (line 131) | handleHitCircleForceKill(time: number, hitCircleId: string) {
    method handleSliderStart (line 141) | handleSliderStart(time: number, sliderId: string) {
    method handleSliderEnding (line 145) | handleSliderEnding(time: number, sliderId: string) {
    method predictedCursorPositionAt (line 173) | predictedCursorPositionAt(time: number): Position {
    method handleSliderCheckPoint (line 185) | handleSliderCheckPoint(time: number, id: string) {
    method handleSpinnerStart (line 198) | handleSpinnerStart(id: string) {
    method handleSpinnerEnd (line 202) | handleSpinnerEnd(id: string) {
    method handleEvent (line 207) | handleEvent(event: Event) {
    method handleAliveHitCircles (line 234) | handleAliveHitCircles() {
    method hasFreshClickThisFrame (line 307) | private get hasFreshClickThisFrame(): boolean {
    method headHitTime (line 311) | private headHitTime(headId: string): number | undefined {
    method updateSliderBodyTracking (line 317) | private updateSliderBodyTracking(time: number, cursorPosition: Positio...
    method handleEventsUntilTime (line 338) | handleEventsUntilTime(maxTimeInclusive: number) {
    method evaluate (line 350) | evaluate(gameState: GameState, frame: ReplayFrame) {
  function determineTracking (line 399) | function determineTracking(
  function sliderVerdictBasedOnCheckpoints (line 430) | function sliderVerdictBasedOnCheckpoints(totalCheckpoints: number, hitCh...

FILE: libs/osu/core/src/gameplay/GameStateTimeMachine.ts
  type GameStateTimeMachine (line 7) | interface GameStateTimeMachine {
  class BucketedGameStateTimeMachine (line 19) | class BucketedGameStateTimeMachine implements GameStateTimeMachine {
    method constructor (line 30) | constructor(
    method getHighestCachedIndex (line 45) | private getHighestCachedIndex(time: number): number {
    method gameStateAt (line 55) | gameStateAt(time: number): GameState {

FILE: libs/osu/core/src/gameplay/GameplayAnalysisEvent.ts
  type GameplayAnalysisEventType (line 14) | type GameplayAnalysisEventType = "HitObjectJudgement" | "CheckpointJudge...
  type DisplayBase (line 17) | interface DisplayBase {
  type HitObjectVerdict (line 24) | type HitObjectVerdict = MainHitObjectVerdict;
  type HitObjectJudgement (line 28) | interface HitObjectJudgement extends DisplayBase {
  type CheckpointJudgement (line 37) | interface CheckpointJudgement extends DisplayBase {
  type UnnecessaryClick (line 45) | interface UnnecessaryClick extends DisplayBase {
  type ReplayAnalysisEvent (line 49) | type ReplayAnalysisEvent = HitObjectJudgement | CheckpointJudgement | Un...
  function retrieveEvents (line 57) | function retrieveEvents(gameState: GameState, hitObjects: OsuHitObject[]) {

FILE: libs/osu/core/src/gameplay/GameplayInfo.ts
  type GameplayInfo (line 13) | interface GameplayInfo {
  type ReplayComboInformation (line 25) | interface ReplayComboInformation {
  type ReplayJudgementCounts (line 30) | type ReplayJudgementCounts = number[];
  function updateComboInfo (line 32) | function updateComboInfo(combo: ReplayComboInformation, type: HitObjectT...
  function osuStableAccuracy (line 63) | function osuStableAccuracy(count: number[]): number | undefined {
  type EvaluationOption (line 85) | interface EvaluationOption {
  type StableVerdictCount (line 96) | type StableVerdictCount = Record<MainHitObjectVerdict, number>;
  class GameplayInfoEvaluator (line 111) | class GameplayInfoEvaluator {
    method constructor (line 117) | constructor(private beatmap: Beatmap, options?: Partial<EvaluationOpti...
    method evaluateHitObject (line 125) | evaluateHitObject(hitObjectType: HitObjectType, verdict: MainHitObject...
    method evaluateSliderCheckpoint (line 132) | evaluateSliderCheckpoint(hitObjectType: SliderCheckPointType, hit: boo...
    method countAsArray (line 136) | countAsArray() {
    method evaluateReplayState (line 140) | evaluateReplayState(replayState: GameState): GameplayInfo {

FILE: libs/osu/core/src/gameplay/Verdicts.ts
  type MainHitObjectVerdict (line 1) | type MainHitObjectVerdict = "GREAT" | "OK" | "MEH" | "MISS";
  type SliderCheckpointVerdict (line 2) | type SliderCheckpointVerdict = "HIT" | "MISS";

FILE: libs/osu/core/src/hitobjects/HitCircle.ts
  class HitCircle (line 5) | class HitCircle implements HasId, HasPosition, HasHitTime, HasSpawnTime {
    method type (line 23) | get type(): HitObjectType {
    method radius (line 27) | get radius(): number {
    method spawnTime (line 31) | get spawnTime(): number {

FILE: libs/osu/core/src/hitobjects/Properties.ts
  type HasPosition (line 3) | interface HasPosition {
  type HasHitTime (line 7) | interface HasHitTime {
  type HasSpawnTime (line 11) | interface HasSpawnTime {
  type HasId (line 15) | interface HasId {

FILE: libs/osu/core/src/hitobjects/Slider.ts
  class Slider (line 8) | class Slider implements HasId {
    method constructor (line 25) | constructor(hitCircle: HitCircle) {
    method type (line 29) | get type(): HitObjectType {
    method spawnTime (line 33) | get spawnTime(): number {
    method spanCount (line 38) | get spanCount(): number {
    method scale (line 42) | get scale(): number {
    method radius (line 46) | get radius(): number {
    method spanDuration (line 50) | get spanDuration(): number {
    method duration (line 54) | get duration(): number {
    method startTime (line 58) | get startTime(): number {
    method endTime (line 62) | get endTime(): number {
    method startPosition (line 66) | get startPosition(): Position {
    method endPosition (line 70) | get endPosition(): Position {
    method unstackedEndPosition (line 75) | get unstackedEndPosition(): Position {
    method ballPositionAt (line 84) | ballPositionAt(progress: number): Position {
    method ballOffsetAt (line 94) | ballOffsetAt(progress: number): Position {

FILE: libs/osu/core/src/hitobjects/SliderCheckPoint.ts
  class SliderCheckPoint (line 8) | class SliderCheckPoint implements HasId, HasPosition, HasHitTime {
    method constructor (line 9) | constructor(public readonly slider: Slider) {}
    method position (line 21) | get position(): Position {
    method scale (line 25) | get scale(): number {

FILE: libs/osu/core/src/hitobjects/Spinner.ts
  function diffRange (line 36) | function diffRange(difficulty: number, min: number, mid: number, max: nu...
  class Spinner (line 43) | class Spinner implements HasId, HasSpawnTime {
    method spawnTime (line 51) | get spawnTime(): number {
    method endTime (line 55) | get endTime(): number {
    method type (line 59) | get type() {

FILE: libs/osu/core/src/hitobjects/Types.ts
  type HitObjectType (line 6) | type HitObjectType = "HIT_CIRCLE" | "SLIDER" | "SPINNER";
  type SliderCheckPointType (line 7) | type SliderCheckPointType = "TICK" | "REPEAT" | "LAST_LEGACY_TICK";
  type MainHitObject (line 10) | type MainHitObject = Spinner | HitCircle | Slider;
  type OsuHitObject (line 11) | type OsuHitObject = MainHitObject;
  type AllHitObjects (line 12) | type AllHitObjects = MainHitObject | SliderCheckPoint;

FILE: libs/osu/core/src/hitobjects/slider/PathApproximator.ts
  type CircularArcProperties (line 11) | interface CircularArcProperties {
  class PathApproximator (line 24) | class PathApproximator {
    method approximateBezier (line 30) | static approximateBezier(controlPoints: Vec2[], p = 0): Vec2[] {
    method approximateCatmull (line 117) | static approximateCatmull(controlPoints: Vec2[]): Vec2[] {
    method circularArcProperties (line 137) | static circularArcProperties(controlPoints: Vec2[]): CircularArcProper...
    method approximateCircularArc (line 181) | static approximateCircularArc(controlPoints: Vec2[]): Vec2[] {
    method approximateLinear (line 218) | static approximateLinear(controlPoints: Vec2[]): Vec2[] {
    method approximateLagrangePolynomial (line 226) | static approximateLagrangePolynomial(controlPoints: Vec2[]): Vec2[] {
    method _barycentricWeights (line 259) | static _barycentricWeights(points: Vec2[]): number[] {
    method _barycentricLagrange (line 286) | static _barycentricLagrange(points: Vec2[], weights: number[], time: n...
    method _bezierIsFlatEnough (line 321) | static _bezierIsFlatEnough(controlPoints: Vec2[]): boolean {
    method _bezierSubdivide (line 343) | static _bezierSubdivide(controlPoints: Vec2[], l: Vec2[], r: Vec2[], s...
    method _bezierApproximate (line 371) | static _bezierApproximate(
    method _catmullFindPoint (line 407) | static _catmullFindPoint(vec1: Vec2, vec2: Vec2, vec3: Vec2, vec4: Vec...

FILE: libs/osu/core/src/hitobjects/slider/PathControlPoint.ts
  type PathControlPoint (line 4) | type PathControlPoint = {

FILE: libs/osu/core/src/hitobjects/slider/PathType.ts
  type PathType (line 1) | enum PathType {

FILE: libs/osu/core/src/hitobjects/slider/SliderCheckPointDescriptor.ts
  type SliderCheckPointDescriptor (line 3) | type SliderCheckPointDescriptor = {

FILE: libs/osu/core/src/hitobjects/slider/SliderPath.ts
  function mapToVector2 (line 6) | function mapToVector2(p: Position[]) {
  class SliderPath (line 10) | class SliderPath {
    method constructor (line 21) | constructor(controlPoints: PathControlPoint[], length?: number) {
    method cumulativeLengths (line 29) | get cumulativeLengths(): number[] {
    method calculatedPath (line 34) | get calculatedPath(): Position[] {
    method makeInvalid (line 39) | makeInvalid(): void {
    method ensureValid (line 44) | ensureValid(): void {
    method calculateSubPath (line 53) | calculateSubPath(subControlPoints: Position[], type: PathType): Positi...
    method boundaryBox (line 72) | get boundaryBox(): [Position, Position] {
    method calculateBoundaryBox (line 77) | calculateBoundaryBox(): [Position, Position] {
    method calculatePath (line 94) | calculatePath(): void {
    method calculateLength (line 120) | calculateLength(): void {
    method distance (line 164) | get distance(): number {
    method indexOfDistance (line 170) | private indexOfDistance(distance: number): number {
    method positionAt (line 185) | positionAt(progress: number): Position {
    method interpolateVertices (line 191) | private interpolateVertices(i: number, d: number): Position {

FILE: libs/osu/core/src/mods/EasyMod.ts
  class EasyMod (line 5) | class EasyMod {

FILE: libs/osu/core/src/mods/HardRockMod.ts
  function flipY (line 7) | function flipY(position: Position) {
  class HardRockMod (line 12) | class HardRockMod {

FILE: libs/osu/core/src/mods/Mods.ts
  type BeatmapDifficultyAdjuster (line 5) | type BeatmapDifficultyAdjuster = (d: BeatmapDifficulty) => BeatmapDiffic...
  type OsuClassicMod (line 27) | type OsuClassicMod = typeof OsuClassicMods[number];
  type ModSetting (line 29) | interface ModSetting {
  constant RELAX_LENIENCY (line 62) | const RELAX_LENIENCY = 12;

FILE: libs/osu/core/src/mods/StackingMod.ts
  function stackOffset (line 6) | function stackOffset(stackHeight: number, scale: number): Position {
  function stackedPosition (line 11) | function stackedPosition(initialPosition: Position, stackHeight: number,...
  type StackableHitObject (line 16) | type StackableHitObject = Slider | HitCircle;
  constant STACK_DISTANCE (line 17) | const STACK_DISTANCE = 3;
  function createStackingHeights (line 30) | function createStackingHeights(hitObjects: OsuHitObject[]) {
  function newStackingHeights (line 50) | function newStackingHeights(hitObjects: OsuHitObject[], stackLeniency: n...
  function oldStackingHeights (line 110) | function oldStackingHeights(hitObjects: OsuHitObject[], stackLeniency: n...
  function modifyStackingPosition (line 146) | function modifyStackingPosition(hitObjects: OsuHitObject[], stackLenienc...

FILE: libs/osu/core/src/playfield.ts
  constant OSU_PLAYFIELD_HEIGHT (line 1) | const OSU_PLAYFIELD_HEIGHT = 384;
  constant OSU_PLAYFIELD_WIDTH (line 2) | const OSU_PLAYFIELD_WIDTH = 512;

FILE: libs/osu/core/src/replays/RawReplayData.ts
  class RawReplayData (line 7) | class RawReplayData {
  function modsFromBitmask (line 50) | function modsFromBitmask(modMask: number): OsuClassicMod[] {
  function modsToBitmask (line 61) | function modsToBitmask(mods: OsuClassicMod[]): number {

FILE: libs/osu/core/src/replays/Replay.ts
  type OsuAction (line 3) | enum OsuAction {
  type ReplayFrame (line 8) | type ReplayFrame = {
  type ReplayButtonState (line 14) | enum ReplayButtonState {

FILE: libs/osu/core/src/replays/ReplayClicks.ts
  type TimeInterval (line 3) | type TimeInterval = [number, number];
  type TimeIntervals (line 4) | type TimeIntervals = TimeInterval[];
  function calculateReplayClicks (line 6) | function calculateReplayClicks(frames: ReplayFrame[]) {

FILE: libs/osu/core/src/replays/ReplayParser.ts
  constant MAX_COORDINATE_VALUE (line 3) | const MAX_COORDINATE_VALUE = 131072;

FILE: libs/osu/core/src/utils/SortedList.spec.ts
  class A (line 4) | class A implements Comparable<A> {
    method constructor (line 7) | constructor(value: number) {
    method compareTo (line 11) | compareTo(t: A): number {

FILE: libs/osu/core/src/utils/SortedList.ts
  type Comparable (line 3) | interface Comparable<T> {
  class SortedList (line 7) | class SortedList<T extends Comparable<T>> {
    method constructor (line 10) | constructor() {
    method indexOf (line 15) | indexOf(t: T): number {
    method add (line 20) | add(t: T): void {
    method remove (line 30) | remove(t: T): void {
    method get (line 37) | get(i: number): T {
    method length (line 41) | get length(): number {

FILE: libs/osu/core/src/utils/index.ts
  function normalizeHitObjects (line 5) | function normalizeHitObjects(hitObjects: OsuHitObject[]): Record<string,...
  function determineDefaultPlaybackSpeed (line 19) | function determineDefaultPlaybackSpeed(mods: OsuClassicMod[]) {

FILE: libs/osu/core/test/utils/asserts.ts
  function assertPositionEqual (line 4) | function assertPositionEqual(actual: Position, expected: Position, numDi...
  function arrayEqual (line 9) | function arrayEqual<T>(a: T[], b: T[]) {

FILE: libs/osu/math/src/Vec2.ts
  type Position (line 5) | type Position = { x: number; y: number };
  class Vec2 (line 9) | class Vec2 {
    method constructor (line 15) | constructor(x: number, y: number) {
    method withinDistance (line 22) | static withinDistance(a: Position, b: Position, d: number): boolean {
    method distance (line 27) | static distance(a: Position, b: Position): number {
    method distanceSquared (line 31) | static distanceSquared(a: Position, b: Position): number {
    method equal (line 37) | static equal(a: Position, b: Position): boolean {
    method add (line 43) | static add(a: Position, b: Position): Vec2 {
    method dot (line 47) | static dot(a: Position, b: Position): number {
    method sub (line 51) | static sub(a: Position, b: Position): Vec2 {
    method scale (line 56) | static scale(a: Position, c: number): Vec2 {
    method divide (line 61) | static divide(a: Position, c: number): Vec2 {
    method interpolate (line 66) | static interpolate(a: Position, b: Position, p: number): Vec2 {
    method add (line 70) | add(b: Position): Vec2 {
    method sub (line 74) | sub(b: Position): Vec2 {
    method divide (line 78) | divide(c: number): Vec2 {
    method scale (line 82) | scale(c: number): Vec2 {
    method lengthSquared (line 86) | lengthSquared(): number {
    method length (line 90) | length(): number {
    method equals (line 94) | equals(b: Position): boolean {
    method normalized (line 98) | normalized(): Vec2 {

FILE: libs/osu/math/src/colors.ts
  type RGB (line 1) | type RGB = [number, number, number];
  function rgbToInt (line 3) | function rgbToInt(rgb: number[]): number {

FILE: libs/osu/math/src/difficulty.ts
  function circleSizeToScale (line 7) | function circleSizeToScale(CS: number) {
  function difficultyRange (line 12) | function difficultyRange(difficulty: number, min: number, mid: number, m...
  constant PREEMPT_MIN (line 18) | const PREEMPT_MIN = 450;
  function approachRateToApproachDuration (line 24) | function approachRateToApproachDuration(AR: number) {
  function approachDurationToApproachRate (line 28) | function approachDurationToApproachRate(approachDurationInMs: number) {
  function difficultyRangeForOd (line 32) | function difficultyRangeForOd(difficulty: number, range: { od0: number; ...
  function hitWindowGreatToOD (line 36) | function hitWindowGreatToOD(hitWindowGreat: number) {
  type HitWindows (line 43) | type HitWindows = number[];
  constant OSU_STD_HIT_WINDOW_RANGES (line 45) | const OSU_STD_HIT_WINDOW_RANGES: [number, number, number][] = [
  function hitWindowsForOD (line 58) | function hitWindowsForOD(overallDifficulty: number, lazerStyle?: boolean...
  function overallDifficultyToHitWindowGreat (line 73) | function overallDifficultyToHitWindowGreat(od: number) {

FILE: libs/osu/math/src/easing.ts
  type Easing (line 2) | enum Easing {
  function applyEasing (line 22) | function applyEasing(t: number, easing: Easing): number {
  function applyInterpolation (line 38) | function applyInterpolation(
  function lerp (line 61) | function lerp(start: number, final: number, amount: number) {

FILE: libs/osu/math/src/float32.ts
  function float32 (line 1) | function float32(a: number) {
  function float32_add (line 5) | function float32_add(a: number, b: number) {
  function float32_mul (line 9) | function float32_mul(a: number, b: number) {
  function float32_div (line 13) | function float32_div(a: number, b: number) {
  function float32_sqrt (line 17) | function float32_sqrt(a: number) {

FILE: libs/osu/math/src/sliders.ts
  function sliderRepeatAngle (line 6) | function sliderRepeatAngle(curve: Position[], isRepeatAtEnd: boolean): n...

FILE: libs/osu/math/src/time.ts
  function addZero (line 1) | function addZero(value: number, digits = 2) {
  function parseMs (line 19) | function parseMs(milliseconds: number) {
  function formatGameTime (line 33) | function formatGameTime(timeInMs: number, withMs?: boolean) {
  function beatLengthToBPM (line 42) | function beatLengthToBPM(beatLength: number) {

FILE: libs/osu/math/src/utils.ts
  function approximatelyEqual (line 1) | function approximatelyEqual(x: number, y: number, delta: number): boolean {
  constant FLOAT_EPS (line 6) | const FLOAT_EPS = 1e-3;
  function floatEqual (line 8) | function floatEqual(value1: number, value2: number): boolean {
  constant DOUBLE_EPS (line 12) | const DOUBLE_EPS = 1e-7;
  function doubleEqual (line 15) | function doubleEqual(x: number, y: number): boolean {
  function clamp (line 19) | function clamp(value: number, min: number, max: number): number {

FILE: libs/osu/pp/src/lib/diff.ts
  type OsuDifficultyHitObject (line 31) | interface OsuDifficultyHitObject {
  constant NORMALISED_RADIUS (line 57) | const NORMALISED_RADIUS = 50.0;
  function computeSliderCursorPosition (line 61) | function computeSliderCursorPosition(slider: Slider) {
  function preprocessDifficultyHitObject (line 145) | function preprocessDifficultyHitObject(
  type DifficultyAttributes (line 245) | interface DifficultyAttributes {
  function determineMaxCombo (line 269) | function determineMaxCombo(hitObjects: OsuHitObject[]) {
  constant PERFORMANCE_BASE_MULTIPLIER (line 288) | const PERFORMANCE_BASE_MULTIPLIER = 1.14;
  constant DIFFICULTY_MULTIPLIER (line 289) | const DIFFICULTY_MULTIPLIER = 0.0675;
  function calculateDifficultyAttributes (line 297) | function calculateDifficultyAttributes(

FILE: libs/osu/pp/src/lib/mods.ts
  function normalizeModsForSR (line 31) | function normalizeModsForSR(mods: OsuClassicMod[]): OsuClassicMod[] {

FILE: libs/osu/pp/src/lib/pp.ts
  type OsuPerformanceAttributes (line 5) | interface OsuPerformanceAttributes {
  type ScoreParams (line 17) | interface ScoreParams {
  function calculatePerformanceAttributes (line 26) | function calculatePerformanceAttributes(

FILE: libs/osu/pp/src/lib/skills/aim.ts
  function calculateAimStrains (line 23) | function calculateAimStrains(
  function calculateAim (line 156) | function calculateAim(

FILE: libs/osu/pp/src/lib/skills/flashlight.ts
  function calculateFlashlightStrains (line 22) | function calculateFlashlightStrains(
  function calcFadeInDuration (line 97) | function calcFadeInDuration(approachDuration: number, hidden: boolean) {
  function opacityAt (line 105) | function opacityAt(
  function calculateFlashlight (line 132) | function calculateFlashlight(

FILE: libs/osu/pp/src/lib/skills/speed.ts
  type SpeedInfo (line 17) | interface SpeedInfo {
  function calculateSpeedInfo (line 28) | function calculateSpeedInfo(
  function calculateSpeed (line 215) | function calculateSpeed(hitObjects: OsuHitObject[], diffs: OsuDifficulty...

FILE: libs/osu/pp/src/lib/skills/strain.ts
  constant REDUCED_STRAIN_BASELINE (line 6) | const REDUCED_STRAIN_BASELINE = 0.75;
  type StrainSkillParams (line 10) | interface StrainSkillParams {
  type OsuStrainSkillParams (line 15) | interface OsuStrainSkillParams extends StrainSkillParams {
  function calculateDifficultyValues (line 25) | function calculateDifficultyValues(
  function calculateFlashlightDifficultyValues (line 72) | function calculateFlashlightDifficultyValues(
  function calculateOsuStrainDifficultyValues (line 113) | function calculateOsuStrainDifficultyValues(

FILE: libs/osu/pp/src/lib/utils.ts
  function insertDecreasing (line 10) | function insertDecreasing(queue: number[], element: number, maxSize: num...

FILE: libs/osu/skin/src/lib/OsuSkinTextureConfig.ts
  type OsuSkinTextureConfig (line 3) | type OsuSkinTextureConfig = {
  constant TO_BE_DECIDED_BY_SKIN_INI (line 9) | const TO_BE_DECIDED_BY_SKIN_INI = -1;
  constant DEFAULT_SKIN_TEXTURE_CONFIG (line 21) | const DEFAULT_SKIN_TEXTURE_CONFIG: Record<OsuSkinTextures, OsuSkinTextur...

FILE: libs/osu/skin/src/lib/SkinConfig.ts
  type RGB (line 5) | type RGB = [number, number, number];
  type RGBA (line 6) | type RGBA = [number, number, number, number];
  type Color (line 7) | type Color = RGB | RGBA;
  type SkinConfig (line 9) | type SkinConfig = {
  type SkinIniSection (line 55) | enum SkinIniSection {

FILE: libs/osu/skin/src/lib/SkinConfigParser.ts
  function parseToBool (line 3) | function parseToBool(val: string): boolean {
  class SkinConfigParser (line 9) | class SkinConfigParser {
    method constructor (line 16) | constructor(data: string) {
    method shouldSkipLine (line 22) | shouldSkipLine(line: string): boolean {
    method stripComments (line 27) | stripComments(line: string): string {
    method splitKeyVal (line 35) | splitKeyVal(line: string, separator = ":"): [string, string] {
    method handleGeneral (line 42) | handleGeneral(key: string, val: string) {
    method handleFonts (line 105) | handleFonts(key: string, val: string) {
    method handleColors (line 129) | handleColors(key: string, val: string) {
    method getSection (line 188) | getSection(s: string) {
    method parseLine (line 200) | parseLine(line: string): void {
    method parse (line 224) | parse(): SkinConfig {
  function parseSkinIni (line 234) | function parseSkinIni(data: string): SkinConfig {

FILE: libs/osu/skin/src/lib/TextureTypes.ts
  type DefaultDigitFont (line 22) | type DefaultDigitFont = typeof defaultDigitFonts[number];
  type ComboDigitFont (line 35) | type ComboDigitFont = typeof comboDigitFonts[number];
  type HitCircleDigitFont (line 48) | type HitCircleDigitFont = typeof hitCircleDigitFonts[number];
  type HitCircleTextures (line 50) | type HitCircleTextures = "APPROACH_CIRCLE" | "HIT_CIRCLE" | "HIT_CIRCLE_...
  type SliderTextures (line 51) | type SliderTextures = "SLIDER_BALL" | "SLIDER_FOLLOW_CIRCLE" | "SLIDER_T...
  type SpinnerTextures (line 52) | type SpinnerTextures =
  type JudgementTextures (line 65) | type JudgementTextures = "HIT_0" | "HIT_50" | "HIT_100" | "HIT_100K" | "...
  type CursorTextures (line 66) | type CursorTextures = "CURSOR" | "CURSOR_TRAIL";
  type ScoreTextures (line 67) | type ScoreTextures = "SCORE_X" | "SCORE_DOT" | "SCORE_PERCENT";
  type OsuSkinTextures (line 68) | type OsuSkinTextures =

FILE: tests/game-simulation/src/core/OsuStdReplayState.spec.ts
  function count (line 94) | function count(state: GameState) {

FILE: tests/game-simulation/src/core/archive/replays/DaijobanaiSlider1.spec.ts
  function expectHitCircleToBeNotAMiss (line 14) | function expectHitCircleToBeNotAMiss(hitCircleState?: HitCircleVerdict) {

FILE: tests/game-simulation/src/core/bpm.test.ts
  constant EXPECTED_PRECISION (line 5) | const EXPECTED_PRECISION = 3;

FILE: tests/game-simulation/src/core/hitobjects.test.ts
  type Testsuite (line 15) | interface Testsuite {
  function runTestSuite (line 31) | function runTestSuite({ filename, sliders }: Testsuite) {

FILE: tests/game-simulation/src/others.ts
  function assertPositionEqual (line 25) | function assertPositionEqual(actual: Position, expected: Position, numDi...
  function parseBlueprintFromFS (line 30) | function parseBlueprintFromFS(name: string): Blueprint {
  function parseReplayFramesFromFS (line 35) | function parseReplayFramesFromFS(replayFile: string) {
  type TestReplay (line 40) | interface TestReplay {
  function parseReplayFromFS (line 45) | function parseReplayFromFS(replayFile: string): TestReplay {
  function osuClassicScoreScreenJudgementCount (line 55) | function osuClassicScoreScreenJudgementCount(state: GameState, hitObject...
  function evaluateWholeReplay (line 82) | function evaluateWholeReplay(evaluator: GameStateEvaluator, replay: Repl...
  function defaultStableSettings (line 90) | function defaultStableSettings(mapFile: string) {
  function createTestTimeMachine (line 112) | function createTestTimeMachine(mapFile: string, replayFile: string) {
  function timeDeltas (line 130) | function timeDeltas(frames: { time: number }[]) {
  function commonStats (line 138) | function commonStats(frames: ReplayFrame[], outlierMs = 16 * 2) {

FILE: tests/game-simulation/src/pp/diff.test.ts
  constant SR_EXPECTED_PRECISION (line 15) | const SR_EXPECTED_PRECISION = 3;
  type TestCase (line 17) | type TestCase = {
  type TestSuite (line 26) | interface TestSuite {
  function calculateStarRating (line 31) | function calculateStarRating(blueprint: Blueprint, mods: OsuClassicMod[]...
  function runTestSuite (line 37) | function runTestSuite({ filename, cases }: TestSuite) {

FILE: tests/game-simulation/src/pp/pp.test.ts
  constant PP_EXPECTED_PRECISION (line 7) | const PP_EXPECTED_PRECISION = 2;
  type TestCase (line 9) | type TestCase = {
  type TestSuite (line 19) | interface TestSuite {
  function calculateStarRating (line 24) | function calculateStarRating(blueprint: Blueprint, mods: OsuClassicMod[]...
  function testItMan (line 30) | function testItMan(
  function runTestSuite (line 50) | function runTestSuite({ filename, cases }: TestSuite) {

FILE: tests/game-simulation/src/reference.ts
  type ReferenceStructure (line 8) | interface ReferenceStructure {
  function readStableReferenceJson (line 14) | async function readStableReferenceJson(path: string) {
  function arrayDelta (line 23) | function arrayDelta(a: number[], b: number[]) {
  function debugGameTime (line 30) | function debugGameTime(timeInMs: number) {
  function countsEqual (line 34) | function countsEqual(expected: number[], actual: number[], check: number...
  type Options (line 43) | interface Options {
  function compareTimeMachineWithReference (line 51) | async function compareTimeMachineWithReference(

FILE: tests/game-simulation/src/util.ts
  function getRewindTestDir (line 6) | function getRewindTestDir() {
  function getOsuGameDir (line 10) | function getOsuGameDir() {
  function blueprintPath (line 14) | function blueprintPath(file: string) {
  function osuTestData (line 18) | function osuTestData(file: string) {
  function translateModAcronym (line 23) | function translateModAcronym(acronym: string): OsuClassicMod {
  function getBlueprintFromTestDir (line 47) | function getBlueprintFromTestDir(file: string) {
  function testBlueprintPath (line 52) | function testBlueprintPath(fileName: string): string {
  function testReplayPath (line 56) | function testReplayPath(fileName: string): string {
  function testReferencePath (line 60) | function testReferencePath(fileName: string): string {
  constant TEST_MAPS (line 67) | const TEST_MAPS = {
  constant TEST_REPLAYS (line 89) | const TEST_REPLAYS = {

FILE: tests/osu-stable-test-generator/src/main.ts
  type OsuStableGameState (line 9) | interface OsuStableGameState {
  function arrayEqual (line 15) | function arrayEqual<T>(a: T[], b: T[]) {
  type MyData (line 33) | interface MyData extends OsuStableGameState {
  type DataToStore (line 46) | interface DataToStore {
  function persist (line 52) | function persist(file: string, data: DataToStore) {
  function filterInteresting (line 57) | function filterInteresting(gameStates: OsuStableGameState[]) {
  class TestGenerator (line 71) | class TestGenerator {
    method processPlaying (line 75) | processPlaying(data: MyData) {
    method processResultScreen (line 87) | processResultScreen(data: MyData) {
    method processData (line 111) | processData(data: MyData) {
  type Verdict (line 125) | type Verdict = typeof Verdicts[number];
  function extractData (line 127) | function extractData(data: GosuMemoryAPI): MyData {
Condensed preview — 507 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (855K chars).
[
  {
    "path": ".editorconfig",
    "chars": 262,
    "preview": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\nindent_style = spa"
  },
  {
    "path": ".eslintrc.json",
    "chars": 753,
    "preview": "{\n  \"root\": true,\n  \"ignorePatterns\": [\"**/*\"],\n  \"plugins\": [\"@nrwl/nx\"],\n  \"overrides\": [\n    {\n      \"files\": [\"*.ts\""
  },
  {
    "path": ".gitattributes",
    "chars": 797,
    "preview": "# Often used LFS tracks\n# https://gist.github.com/ma-al/019f7f76498c55f0061120a5b13c6d88\n# -----------------------------"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 698,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 317,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**De"
  },
  {
    "path": ".github/workflows/build-release.yml",
    "chars": 897,
    "preview": "name: Build/release v2\n\non:\n  push:\n  workflow_dispatch:\n\njobs:\n  release:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n"
  },
  {
    "path": ".gitignore",
    "chars": 520,
    "preview": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/tmp\n/out-tsc\n\n# depe"
  },
  {
    "path": ".gitmodules",
    "chars": 115,
    "preview": "[submodule \"testdata/osu-testdata\"]\n\tpath = testdata/osu-testdata\n\turl = https://github.com/abstrakt8/osu-testdata\n"
  },
  {
    "path": ".prettierignore",
    "chars": 74,
    "preview": "# Add files here to ignore them from prettier formatting\n\n/dist\n/coverage\n"
  },
  {
    "path": ".prettierrc",
    "chars": 88,
    "preview": "{\n  \"printWidth\": 120,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"all\",\n  \"endOfLine\": \"lf\"\n}\n"
  },
  {
    "path": ".storybook/main.js",
    "chars": 323,
    "preview": "module.exports = {\n  stories: [],\n  addons: [\n    \"@storybook/addon-links\",\n    \"@storybook/addon-essentials\",\n    \"stor"
  },
  {
    "path": ".storybook/preview.js",
    "chars": 173,
    "preview": "export const parameters = {\n  actions: { argTypesRegex: \"^on[A-Z].*\" },\n  controls: {\n    matchers: {\n      color: /(bac"
  },
  {
    "path": ".storybook/tsconfig.json",
    "chars": 180,
    "preview": "{\n  \"extends\": \"../tsconfig.base.json\",\n  \"exclude\": [\n    \"../**/*.spec.js\",\n    \"../**/*.spec.ts\",\n    \"../**/*.spec.t"
  },
  {
    "path": ".storybook/webpack.config.js",
    "chars": 472,
    "preview": "/**\n * Export a function. Accept the base config as the only param.\n * @param {Object} options\n * @param {Required<impor"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 154,
    "preview": "{\n  \"recommendations\": [\n    \"nrwl.angular-console\",\n    \"esbenp.prettier-vscode\",\n    \"firsttris.vscode-jest-runner\",\n "
  },
  {
    "path": ".yarnrc",
    "chars": 102,
    "preview": "# https://github.com/yarnpkg/yarn/issues/5540\n# because of material-ui icons\nnetwork-timeout 600000\n\n\n"
  },
  {
    "path": "CONTRIBUTION.md",
    "chars": 2349,
    "preview": "Project Structure\n===\n\nGeneral\n---\n\nThis repository is a mono-repo that is currently focused on the development of the R"
  },
  {
    "path": "LICENSE.md",
    "chars": 1065,
    "preview": "MIT License\n\nCopyright (c) 2021 abstrakt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
  },
  {
    "path": "README.md",
    "chars": 2133,
    "preview": "<h1 align=\"center\">Rewind</h1>\n\n![Github releases](https://img.shields.io/github/v/release/abstrakt8/rewind?label=stable"
  },
  {
    "path": "apps/desktop/README.md",
    "chars": 174,
    "preview": "The Rewind desktop application consists of two renderers which are implemented in `backend` and `frontend`.\n\nThe entry p"
  },
  {
    "path": "apps/desktop/frontend/.babelrc",
    "chars": 126,
    "preview": "{\n  \"presets\": [\n    [\n      \"@nrwl/react/babel\",\n      {\n        \"runtime\": \"automatic\"\n      }\n    ]\n  ],\n  \"plugins\":"
  },
  {
    "path": "apps/desktop/frontend/.browserslistrc",
    "chars": 562,
    "preview": "# This file is used by:\n# 1. autoprefixer to adjust CSS to support the below specified browsers\n# 2. babel preset-env to"
  },
  {
    "path": "apps/desktop/frontend/.eslintrc.json",
    "chars": 334,
    "preview": "{\n  \"extends\": [\"plugin:@nrwl/nx/react\", \"../../../.eslintrc.json\"],\n  \"ignorePatterns\": [\"!**/*\"],\n  \"overrides\": [\n   "
  },
  {
    "path": "apps/desktop/frontend/.storybook/main.js",
    "chars": 253,
    "preview": "const rootMain = require(\"../../../../.storybook/main\");\n\n// Use the following syntax to add addons!\n// rootMain.addons."
  },
  {
    "path": "apps/desktop/frontend/.storybook/preview.js",
    "chars": 776,
    "preview": "import React from \"react\";\n\nimport { addDecorator } from \"@storybook/react\";\n\nimport { ThemeProvider as EmotionThemeProv"
  },
  {
    "path": "apps/desktop/frontend/.storybook/tsconfig.json",
    "chars": 552,
    "preview": "{\n  \"extends\": \"../../../../libs/feature-replay-viewer/tsconfig.json\",\n  \"compilerOptions\": {\n    \"emitDecoratorMetadata"
  },
  {
    "path": "apps/desktop/frontend/.storybook/webpack.config.js",
    "chars": 2287,
    "preview": "const TsconfigPathsPlugin = require(\"tsconfig-paths-webpack-plugin\");\nconst rootWebpackConfig = require(\"../../../../.st"
  },
  {
    "path": "apps/desktop/frontend/README.md",
    "chars": 259,
    "preview": "This is the frontend that gets deployed with the Electron app. That's why there is\n\nSome notes:\n\n- Uses `redux` for stat"
  },
  {
    "path": "apps/desktop/frontend/jest.config.ts",
    "chars": 340,
    "preview": "/* eslint-disable */\nexport default {\n  displayName: \"frontend\",\n  preset: \"../../../jest.preset.js\",\n  transform: {\n   "
  },
  {
    "path": "apps/desktop/frontend/proxy.conf.json",
    "chars": 171,
    "preview": "{\n  \"/api\": {\n    \"target\": \"http://localhost:3333\",\n    \"secure\": false\n  },\n  \"/desktop-backend-api\": {\n    \"target\": "
  },
  {
    "path": "apps/desktop/frontend/src/app/RewindApp.tsx",
    "chars": 3281,
    "preview": "import { useAppDispatch } from \"./hooks/redux\";\nimport { Outlet, Routes, Route, useNavigate } from \"react-router-dom\";\ni"
  },
  {
    "path": "apps/desktop/frontend/src/app/api.ts",
    "chars": 1340,
    "preview": "import { ipcRenderer } from \"electron\";\n\ntype Listener = (...args: any) => any;\n\n// Maybe use it for onUpdateDownloadPro"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseAudioSettingsPanel.tsx",
    "chars": 1888,
    "preview": "import { Box, Slider, Stack, Tooltip, Typography } from \"@mui/material\";\nimport { VolumeDown, VolumeUp } from \"@mui/icon"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseCurrentTime.stories.tsx",
    "chars": 736,
    "preview": "import { Meta, Story } from \"@storybook/react\";\nimport { Paper } from \"@mui/material\";\nimport { BaseCurrentTime, GameCur"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseCurrentTime.tsx",
    "chars": 1220,
    "preview": "import React, { forwardRef, ForwardRefRenderFunction, useImperativeHandle, useRef } from \"react\";\nimport { formatGameTim"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseDialog.tsx",
    "chars": 778,
    "preview": "import { IconButton, Link, Typography } from \"@mui/material\";\nimport { FaDiscord } from \"react-icons/fa\";\nimport React f"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseGameTimeSlider.stories.tsx",
    "chars": 647,
    "preview": "import { Box } from \"@mui/material\";\nimport { Meta, Story } from \"@storybook/react\";\nimport { BaseGameTimeSlider, BaseGa"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseGameTimeSlider.tsx",
    "chars": 5945,
    "preview": "import { Box, Slider, styled } from \"@mui/material\";\nimport { formatGameTime, rgbToInt } from \"@osujs/math\";\nimport { ig"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseSettingsModal.stories.tsx",
    "chars": 651,
    "preview": "import { Meta, Story } from \"@storybook/react\";\nimport { BaseSettingsModal, SettingsProps } from \"./BaseSettingsModal\";\n"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/BaseSettingsModal.tsx",
    "chars": 2981,
    "preview": "import { Box, Divider, IconButton, Paper, Slider, Stack, Tab, Tabs, Typography } from \"@mui/material\";\nimport React from"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/GameCanvas.tsx",
    "chars": 3516,
    "preview": "import React, { useEffect, useRef } from \"react\";\nimport { useAnalysisApp } from \"../../providers/TheaterProvider\";\nimpo"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/HelpModal.stories.tsx",
    "chars": 341,
    "preview": "import { Meta, Story } from \"@storybook/react\";\nimport { HelpBox } from \"./HelpModal\";\n\nexport default {\n  component: He"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/HelpModal.tsx",
    "chars": 4932,
    "preview": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { Box, Divider, IconButton, Modal, Paper, Stac"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/PlayBar.tsx",
    "chars": 12796,
    "preview": "import {\n  Box,\n  Button,\n  Divider,\n  IconButton,\n  ListItemIcon,\n  ListItemText,\n  Menu,\n  MenuItem,\n  MenuList,\n  Pop"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/analyzer/SettingsModal.tsx",
    "chars": 10565,
    "preview": "import { useSettingsModalContext } from \"../../providers/SettingsProvider\";\nimport {\n  Autocomplete,\n  Box,\n  Button,\n  "
  },
  {
    "path": "apps/desktop/frontend/src/app/components/logo/RewindLogo.tsx",
    "chars": 313,
    "preview": "import { Stack, Typography } from \"@mui/material\";\nimport { FastRewind } from \"@mui/icons-material\";\n\nexport const Rewin"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/sidebar/LeftMenuSidebar.tsx",
    "chars": 4122,
    "preview": "import { RewindLogo } from \"../logo/RewindLogo\";\nimport { Badge, Box, Divider, IconButton, Stack, Tooltip } from \"@mui/m"
  },
  {
    "path": "apps/desktop/frontend/src/app/components/update/UpdateModal.tsx",
    "chars": 4020,
    "preview": "import { Box, Button, Divider, IconButton, LinearProgress, Link, Modal, Paper, Stack, Typography } from \"@mui/material\";"
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/app-info.ts",
    "chars": 247,
    "preview": "import { useCommonManagers } from \"../providers/TheaterProvider\";\n\n\nexport function useAppInfo() {\n  const { appInfoServ"
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/audio.ts",
    "chars": 581,
    "preview": "import { useCommonManagers } from \"../providers/TheaterProvider\";\nimport { useObservable } from \"rxjs-hooks\";\nimport { A"
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/energy-saver.ts",
    "chars": 535,
    "preview": "// import { useStageContext } from \"../components/StageProvider/StageProvider\";\n\n// export function useEnergySaver(enabl"
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/game-clock.ts",
    "chars": 1993,
    "preview": "import { useCallback, useState } from \"react\";\nimport { useObservable } from \"rxjs-hooks\";\nimport { useAnalysisApp } fro"
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/interval.ts",
    "chars": 628,
    "preview": "import { useEffect, useRef } from \"react\";\n\nexport function useInterval(callback: () => void, delay: number | null) {\n  "
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/mods.ts",
    "chars": 501,
    "preview": "import { useAnalysisApp } from \"../providers/TheaterProvider\";\nimport { useObservable } from \"rxjs-hooks\";\nimport { useC"
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/redux.ts",
    "chars": 323,
    "preview": "import { TypedUseSelectorHook, useDispatch, useSelector } from \"react-redux\";\nimport { AppDispatch, RootState } from \".."
  },
  {
    "path": "apps/desktop/frontend/src/app/hooks/shortcuts.ts",
    "chars": 2246,
    "preview": "import { useHotkeys } from \"react-hotkeys-hook\";\nimport { useGameClockControls } from \"./game-clock\";\nimport { useModCon"
  },
  {
    "path": "apps/desktop/frontend/src/app/model/BlueprintInfo.ts",
    "chars": 336,
    "preview": "export interface BlueprintInfo {\n  md5Hash: string;\n  lastPlayed: Date;\n  title: string;\n  artist: string;\n  creator: st"
  },
  {
    "path": "apps/desktop/frontend/src/app/model/OsuReplay.ts",
    "chars": 291,
    "preview": "// TODO: Rename this to replay or something\nimport { OsuClassicMod, ReplayFrame } from \"@osujs/core\";\n\nexport type OsuRe"
  },
  {
    "path": "apps/desktop/frontend/src/app/model/Skin.ts",
    "chars": 2319,
    "preview": "import { rgbToInt } from \"@osujs/math\";\nimport { Texture } from \"@pixi/core\";\nimport {\n  comboDigitFonts,\n  defaultDigit"
  },
  {
    "path": "apps/desktop/frontend/src/app/model/SkinId.ts",
    "chars": 682,
    "preview": "export type SkinSource = \"rewind\" | \"osu\";\n\nexport interface SkinId {\n  source: SkinSource;\n  name: string;\n}\n\nexport fu"
  },
  {
    "path": "apps/desktop/frontend/src/app/providers/SettingsProvider.tsx",
    "chars": 1424,
    "preview": "import React, { createContext, useContext, useState } from \"react\";\n\ntype ISettingsContext = {\n  settingsModalOpen: bool"
  },
  {
    "path": "apps/desktop/frontend/src/app/providers/TheaterProvider.tsx",
    "chars": 939,
    "preview": "import React, { createContext, useContext } from \"react\";\nimport { RewindTheater } from \"../services/common/CommonManage"
  },
  {
    "path": "apps/desktop/frontend/src/app/screens/analyzer/Analyzer.tsx",
    "chars": 1252,
    "preview": "import { Paper, Stack } from \"@mui/material\";\nimport { PlayBar } from \"../../components/analyzer/PlayBar\";\nimport { useS"
  },
  {
    "path": "apps/desktop/frontend/src/app/screens/home/HomeScreen.tsx",
    "chars": 1681,
    "preview": "import React from \"react\";\nimport { FaDiscord, FaTwitter, FaYoutube } from \"react-icons/fa\";\nimport { IconButton, Link, "
  },
  {
    "path": "apps/desktop/frontend/src/app/screens/setup/SetupScreen.tsx",
    "chars": 4302,
    "preview": "import * as React from \"react\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Alert, Badge, Box, Bu"
  },
  {
    "path": "apps/desktop/frontend/src/app/screens/splash/SplashScreen.tsx",
    "chars": 415,
    "preview": "import { HashLoader } from \"react-spinners\";\nimport { Stack } from \"@mui/material\";\n\nexport function SplashScreen() {\n  "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/AnalysisApp.ts",
    "chars": 2400,
    "preview": "import { ReplayService } from \"../common/local/ReplayService\";\nimport { GameplayClock } from \"../common/game/GameplayClo"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/analysis-cursor.ts",
    "chars": 1794,
    "preview": "import { PersistentService } from \"../core/service\";\nimport { injectable } from \"inversify\";\nimport { JSONSchemaType } f"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/createRewindAnalysisApp.ts",
    "chars": 4648,
    "preview": "import { Container } from \"inversify\";\nimport { AnalysisApp } from \"./AnalysisApp\";\nimport { PixiRendererManager } from "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/mod-settings.ts",
    "chars": 680,
    "preview": "import { injectable } from \"inversify\";\nimport { BehaviorSubject } from \"rxjs\";\n\ninterface AnalysisModSettings {\n  hidde"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/scenes/AnalysisScene.ts",
    "chars": 1067,
    "preview": "import { UserScene } from \"../../common/scenes/IScene\";\nimport { injectable } from \"inversify\";\nimport { AnalysisStage }"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/scenes/IdleScene.ts",
    "chars": 824,
    "preview": "// Usually if there is no replay loaded\n// Just show a text\n// In the future -> show a cool looking logo\n\nimport { UserS"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/scenes/LoadingScene.ts",
    "chars": 654,
    "preview": "// Very simple loading scene that shows the progress of the loading\nimport { UserScene } from \"../../common/scenes/IScen"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/scenes/ResultsScreenScene.ts",
    "chars": 65,
    "preview": "// Should be shown after the replay or can be manually triggered\n"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/analysis/screenshot.ts",
    "chars": 829,
    "preview": "import { injectable } from \"inversify\";\nimport { AnalysisScene } from \"./scenes/AnalysisScene\";\nimport { PixiRendererMan"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/CommonManagers.ts",
    "chars": 3981,
    "preview": "import { Container, injectable } from \"inversify\";\nimport { ReplayService } from \"./local/ReplayService\";\nimport { SkinL"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/app-info.ts",
    "chars": 302,
    "preview": "import { inject, injectable } from \"inversify\";\nimport { STAGE_TYPES } from \"../types\";\n\n@injectable()\nexport class AppI"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/audio/AudioEngine.ts",
    "chars": 3764,
    "preview": "import { inject, injectable, postConstruct } from \"inversify\";\nimport { AudioSettings, AudioSettingsStore } from \"./sett"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/audio/AudioService.ts",
    "chars": 484,
    "preview": "import { injectable } from \"inversify\";\n\n/**\n * Only one AudioService?\n * Only one AudioContext.\n */\n@injectable()\nexpor"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/audio/settings.ts",
    "chars": 1703,
    "preview": "import { injectable } from \"inversify\";\nimport { PersistentService } from \"../../core/service\";\nimport { JSONSchemaType "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/beatmap-background.ts",
    "chars": 1819,
    "preview": "import { injectable } from \"inversify\";\nimport { BehaviorSubject } from \"rxjs\";\nimport { Texture } from \"pixi.js\";\nimpor"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/beatmap-render.ts",
    "chars": 1325,
    "preview": "import { JSONSchemaType } from \"ajv\";\nimport { injectable } from \"inversify\";\nimport { PersistentService } from \"../core"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/cursor.ts",
    "chars": 225,
    "preview": "export interface CursorSettings {\n  // A number between 0.1 and 2.0\n  scale: number;\n  // If it should be shown or not\n "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/game/GameLoop.ts",
    "chars": 2442,
    "preview": "import * as PIXI from \"pixi.js\";\nimport { GameplayClock } from \"./GameplayClock\";\nimport { PixiRendererManager } from \"."
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/game/GameSimulator.ts",
    "chars": 4492,
    "preview": "import {\n  Beatmap,\n  BucketedGameStateTimeMachine,\n  defaultGameplayInfo,\n  GameplayInfo,\n  GameplayInfoEvaluator,\n  Ga"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/game/GameplayClock.ts",
    "chars": 3042,
    "preview": "import { injectable } from \"inversify\";\nimport { BehaviorSubject, Subject } from \"rxjs\";\n\nconst getNowInMs = () => perfo"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/hit-error-bar.ts",
    "chars": 862,
    "preview": "import { injectable } from \"inversify\";\nimport { PersistentService } from \"../core/service\";\nimport { JSONSchemaType } f"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/key-press-overlay.ts",
    "chars": 378,
    "preview": "export interface KeyPressOverlaySettings {\n  // The key presses that are relative to [-timeWindow+currentTime, currentTi"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/local/BlueprintLocatorService.ts",
    "chars": 5217,
    "preview": "import { promises as fsPromises } from \"fs\";\nimport { Blueprint, BlueprintSection, parseBlueprint } from \"@osujs/core\";\n"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/local/OsuDBDao.ts",
    "chars": 2203,
    "preview": "import { join } from \"path\";\nimport { Beatmap as DBBeatmap, OsuDBReader } from \"@rewind/osu-local/db-reader\";\nimport { f"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/local/OsuFolderService.ts",
    "chars": 2473,
    "preview": "import { injectable } from \"inversify\";\nimport { access } from \"fs/promises\";\nimport { join } from \"path\";\nimport { cons"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/local/ReplayFileWatcher.ts",
    "chars": 1641,
    "preview": "import { injectable } from \"inversify\";\nimport { Subject } from \"rxjs\";\nimport * as chokidar from \"chokidar\";\nimport { O"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/local/ReplayService.ts",
    "chars": 872,
    "preview": "import { modsFromBitmask, parseReplayFramesFromRaw } from \"@osujs/core\";\nimport { injectable } from \"inversify\";\nimport "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/local/SkinLoader.ts",
    "chars": 5233,
    "preview": "import { Loader } from \"@pixi/loaders\";\nimport { Texture } from \"pixi.js\";\nimport { DEFAULT_SKIN_TEXTURE_CONFIG, OsuSkin"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/local-storage.ts",
    "chars": 1064,
    "preview": "// Will store the settings after a couple seconds\nimport Ajv, { JSONSchemaType, ValidateFunction } from \"ajv\";\n\nconst aj"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/playbar.ts",
    "chars": 785,
    "preview": "import { PersistentService } from \"../core/service\";\nimport { injectable } from \"inversify\";\nimport { JSONSchemaType } f"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/playfield-border.ts",
    "chars": 598,
    "preview": "import { injectable } from \"inversify\";\nimport { BehaviorSubject } from \"rxjs\";\nimport { PlayfieldBorderSettings } from "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/replay-cursor.ts",
    "chars": 1323,
    "preview": "import { PersistentService } from \"../core/service\";\nimport { JSONSchemaType } from \"ajv\";\nimport { CursorSettings } fro"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/scenes/IScene.ts",
    "chars": 1485,
    "preview": "// Scene that needs to be implemented by the user\n// Ideas taken from Phaser3 https://rexrainbow.github.io/phaser3-rex-n"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/scenes/SceneManager.ts",
    "chars": 1255,
    "preview": "// Manages the scene\nimport { Container } from \"pixi.js\";\nimport { ManagedScene, UserScene } from \"./IScene\";\nimport { i"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/common/skin.ts",
    "chars": 2533,
    "preview": "import { PersistentService } from \"../core/service\";\nimport { injectable } from \"inversify\";\nimport { JSONSchemaType } f"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/core/service.ts",
    "chars": 1587,
    "preview": "import { BehaviorSubject } from \"rxjs\";\nimport { Draft } from \"immer/dist/types/types-external\";\nimport produce from \"im"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/manager/AnalysisSceneManager.ts",
    "chars": 1076,
    "preview": "import { SceneManager } from \"../common/scenes/SceneManager\";\nimport { injectable } from \"inversify\";\nimport { AnalysisS"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/manager/BeatmapManager.ts",
    "chars": 404,
    "preview": "// Holds the beatmap\nimport { Beatmap } from \"@osujs/core\";\nimport { injectable } from \"inversify\";\nimport { BehaviorSub"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/manager/ClipRecorder.ts",
    "chars": 1703,
    "preview": "import { injectable } from \"inversify\";\nimport { PixiRendererManager } from \"../renderers/PixiRendererManager\";\nimport {"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/manager/ReplayManager.ts",
    "chars": 525,
    "preview": "// Holds the replays\nimport { injectable } from \"inversify\";\nimport { OsuReplay } from \"../../model/OsuReplay\";\nimport {"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/manager/ScenarioManager.ts",
    "chars": 5954,
    "preview": "import { injectable } from \"inversify\";\nimport { BehaviorSubject } from \"rxjs\";\nimport { Beatmap, buildBeatmap, modsToBi"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/PixiRendererManager.ts",
    "chars": 1348,
    "preview": "import * as PIXI from \"pixi.js\";\nimport { injectable } from \"inversify\";\n\n@injectable()\nexport class PixiRendererManager"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/background/BeatmapBackground.ts",
    "chars": 1511,
    "preview": "import { Sprite, Texture } from \"pixi.js\";\nimport { BlurFilter } from \"@pixi/filter-blur\";\n\nimport { injectable } from \""
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/hud/ForegroundHUDPreparer.ts",
    "chars": 6263,
    "preview": "import { injectable } from \"inversify\";\nimport { GameSimulator } from \"../../../common/game/GameSimulator\";\nimport { Con"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/keypresses/KeyPressOverlay.ts",
    "chars": 13915,
    "preview": "import { TemporaryObjectPool } from \"../../../../utils/pooling/TemporaryObjectPool\";\nimport { Container, Graphics, Recta"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/CursorPreparer.ts",
    "chars": 5472,
    "preview": "import { Container } from \"pixi.js\";\nimport { OsuClassicCursor } from \"@rewind/osu-pixi/classic-components\";\nimport { fi"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/HitCircleFactory.ts",
    "chars": 4535,
    "preview": "import { injectable } from \"inversify\";\nimport { HitCircle } from \"@osujs/core\";\nimport { GameplayClock } from \"../../.."
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/HitObjectsContainerFactory.ts",
    "chars": 2770,
    "preview": "import { Container } from \"pixi.js\";\nimport { injectable } from \"inversify\";\nimport { HitCircle, isHitCircle, isSlider, "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/JudgementPreparer.ts",
    "chars": 2586,
    "preview": "import { injectable } from \"inversify\";\nimport { Container } from \"pixi.js\";\nimport { OsuClassicJudgement } from \"@rewin"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/PlayfieldBorderFactory.ts",
    "chars": 699,
    "preview": "import { injectable } from \"inversify\";\nimport { PlayfieldBorder } from \"@rewind/osu-pixi/classic-components\";\nimport { "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/PlayfieldFactory.ts",
    "chars": 1844,
    "preview": "import * as PIXI from \"pixi.js\";\nimport { PlayfieldBorderFactory } from \"./PlayfieldBorderFactory\";\nimport { injectable "
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/SliderFactory.ts",
    "chars": 9147,
    "preview": "import { Skin } from \"../../../../model/Skin\";\nimport { Slider, SliderCheckPoint } from \"@osujs/core\";\nimport { Containe"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/playfield/SpinnerFactory.ts",
    "chars": 1357,
    "preview": "import { injectable } from \"inversify\";\nimport { Spinner } from \"@osujs/core\";\nimport { GameplayClock } from \"../../../c"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/sliders/SliderTextureManager.ts",
    "chars": 2312,
    "preview": "import { injectable } from \"inversify\";\nimport { PixiRendererManager } from \"../../PixiRendererManager\";\nimport { Positi"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/components/stage/AnalysisStage.ts",
    "chars": 4156,
    "preview": "import * as PIXI from \"pixi.js\";\nimport { Container } from \"pixi.js\";\nimport { injectable } from \"inversify\";\nimport { P"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/renderers/constants.ts",
    "chars": 141,
    "preview": "// The whole game stage will be in the virtual size 1600x900 (16:9) format\nexport const STAGE_WIDTH = 1600;\nexport const"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/textures/TextureManager.ts",
    "chars": 717,
    "preview": "import { injectable } from \"inversify\";\nimport { Texture } from \"pixi.js\";\n\nconst RewindTextures = [\"BACKGROUND\"] as con"
  },
  {
    "path": "apps/desktop/frontend/src/app/services/types/index.ts",
    "chars": 316,
    "preview": "export const STAGE_TYPES = {\n  AUDIO_CONTEXT: Symbol.for(\"AUDIO_CONTEXT\"),\n  EVENT_EMITTER: Symbol.for(\"EVENT_EMITTER\"),"
  },
  {
    "path": "apps/desktop/frontend/src/app/store/index.tsx",
    "chars": 731,
    "preview": "import { configureStore } from \"@reduxjs/toolkit\";\nimport settingsReducer from \"./settings/slice\";\nimport updaterReducer"
  },
  {
    "path": "apps/desktop/frontend/src/app/store/settings/slice.ts",
    "chars": 530,
    "preview": "import { createSlice } from \"@reduxjs/toolkit\";\n\ninterface RewindSettingsState {\n  open: boolean;\n}\n\nconst initialState:"
  },
  {
    "path": "apps/desktop/frontend/src/app/store/update/slice.ts",
    "chars": 1779,
    "preview": "import { createSlice, PayloadAction } from \"@reduxjs/toolkit\";\n\ninterface UpdateStatus {\n  newVersion: string | null;\n  "
  },
  {
    "path": "apps/desktop/frontend/src/app/styles/theme.ts",
    "chars": 256,
    "preview": "import { createTheme } from \"@mui/material\";\n\nexport const rewindTheme = createTheme({\n  palette: {\n    mode: \"dark\",\n  "
  },
  {
    "path": "apps/desktop/frontend/src/app/utils/constants.ts",
    "chars": 724,
    "preview": "const osuUniDiscord = \"https://discord.gg/QubdHdnBVg\";\nexport const discordUrl = osuUniDiscord;\nexport const twitterUrl "
  },
  {
    "path": "apps/desktop/frontend/src/app/utils/focus.ts",
    "chars": 127,
    "preview": "import React from \"react\";\n\nexport function ignoreFocus(event: React.FocusEvent<HTMLButtonElement>) {\n  event.target.blu"
  },
  {
    "path": "apps/desktop/frontend/src/app/utils/pooling/ObjectPool.ts",
    "chars": 1112,
    "preview": "type Creator<T> = () => T;\ntype CleanUp<T> = (t: T) => unknown;\n\ninterface Options {\n  initialSize: number;\n}\n\nconst def"
  },
  {
    "path": "apps/desktop/frontend/src/app/utils/pooling/TemporaryObjectPool.ts",
    "chars": 671,
    "preview": "// For each round it will record those that have been accessed and after the .end() it will release all that have\n\nimpor"
  },
  {
    "path": "apps/desktop/frontend/src/app/utils/replay.ts",
    "chars": 1220,
    "preview": "import { OsuAction, ReplayFrame } from \"@osujs/core\";\nimport { Position, Vec2 } from \"@osujs/math\";\n\n// TODO: Could be u"
  },
  {
    "path": "apps/desktop/frontend/src/assets/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "apps/desktop/frontend/src/constants.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "apps/desktop/frontend/src/environments/environment.prod.ts",
    "chars": 52,
    "preview": "export const environment = {\n  production: true,\n};\n"
  },
  {
    "path": "apps/desktop/frontend/src/environments/environment.ts",
    "chars": 398,
    "preview": "// This file can be replaced during build by using the `fileReplacements` array.\n// When building for production, this f"
  },
  {
    "path": "apps/desktop/frontend/src/index.html",
    "chars": 321,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Rewind</title>\n    <base href=\"/\" />\n\n"
  },
  {
    "path": "apps/desktop/frontend/src/main.tsx",
    "chars": 1815,
    "preview": "import { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nimport { Provider } from \"react-redu"
  },
  {
    "path": "apps/desktop/frontend/src/polyfills.ts",
    "chars": 245,
    "preview": "/**\n * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.\n *\n * See: https://git"
  },
  {
    "path": "apps/desktop/frontend/src/styles.css",
    "chars": 80,
    "preview": "/* You can add global styles to this file, and also import other style files */\n"
  },
  {
    "path": "apps/desktop/frontend/test/ajv.spec.ts",
    "chars": 1264,
    "preview": "import Ajv from \"ajv\";\nimport { SkinSettings, SkinSettingsSchema } from \"../src/app/services/common/skin\";\n\ndescribe(\"va"
  },
  {
    "path": "apps/desktop/frontend/tsconfig.app.json",
    "chars": 481,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../../dist/out-tsc\",\n    \"types\": [\n      \"no"
  },
  {
    "path": "apps/desktop/frontend/tsconfig.json",
    "chars": 480,
    "preview": "{\n  \"extends\": \"../../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"allowJs\": true,\n    \"e"
  },
  {
    "path": "apps/desktop/frontend/tsconfig.spec.json",
    "chars": 536,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../../dist/out-tsc\",\n    \"module\": \"commonjs\""
  },
  {
    "path": "apps/desktop/frontend/webpack.config.js",
    "chars": 462,
    "preview": "const { merge } = require(\"webpack-merge\");\n\n// A bit of a hack since nx/web executors use target: 'web'\n//https://githu"
  },
  {
    "path": "apps/desktop/main/.eslintrc.json",
    "chars": 309,
    "preview": "{\n  \"extends\": [\"../../../.eslintrc.json\"],\n  \"ignorePatterns\": [\"!**/*\"],\n  \"overrides\": [\n    {\n      \"files\": [\"*.ts\""
  },
  {
    "path": "apps/desktop/main/README.md",
    "chars": 217,
    "preview": "In order to build the whole application we need to first build the projects respectively and then include\nthem while pac"
  },
  {
    "path": "apps/desktop/main/electron-builder.json",
    "chars": 1012,
    "preview": "{\n  \"appId\": \"sh.abstrakt.rewind\",\n  \"productName\": \"Rewind\",\n  \"nsis\": {\n    \"oneClick\": false,\n    \"allowToChangeInsta"
  },
  {
    "path": "apps/desktop/main/jest.config.ts",
    "chars": 367,
    "preview": "/* eslint-disable */\nexport default {\n  displayName: \"main\",\n  preset: \"../../../jest.preset.js\",\n  globals: {\n    \"ts-j"
  },
  {
    "path": "apps/desktop/main/package.json",
    "chars": 1242,
    "preview": "{\n  \"private\": true,\n  \"name\": \"rewind\",\n  \"author\": \"Rewind\",\n  \"description\": \"Will this popup in installation?\",\n  \"v"
  },
  {
    "path": "apps/desktop/main/src/app/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "apps/desktop/main/src/app/config.ts",
    "chars": 1239,
    "preview": "import Ajv, { JSONSchemaType } from \"ajv\";\nimport { join } from \"path\";\nimport { readFileSync } from \"fs\";\n\nconst ajv = "
  },
  {
    "path": "apps/desktop/main/src/app/events.ts",
    "chars": 1491,
    "preview": "import { app, dialog, ipcMain } from \"electron\";\nimport { read } from \"node-osr\";\n\nasync function userSelectDirectory(de"
  },
  {
    "path": "apps/desktop/main/src/app/updater.ts",
    "chars": 3225,
    "preview": "import { prerelease } from \"semver\";\nimport { app, ipcMain } from \"electron\";\nimport { autoUpdater, ProgressInfo, Update"
  },
  {
    "path": "apps/desktop/main/src/app/windows.ts",
    "chars": 162,
    "preview": "import { BrowserWindow } from \"electron\";\n\nexport interface Windows {\n  frontend: null | BrowserWindow;\n}\n\nexport const "
  },
  {
    "path": "apps/desktop/main/src/assets/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "apps/desktop/main/src/environments/environment.prod.ts",
    "chars": 52,
    "preview": "export const environment = {\n  production: true,\n};\n"
  },
  {
    "path": "apps/desktop/main/src/environments/environment.ts",
    "chars": 53,
    "preview": "export const environment = {\n  production: false,\n};\n"
  },
  {
    "path": "apps/desktop/main/src/index.ts",
    "chars": 8224,
    "preview": "import { app, BrowserWindow, dialog, ipcMain, Menu, screen, shell } from \"electron\";\nimport { setupEventListeners } from"
  },
  {
    "path": "apps/desktop/main/tsconfig.app.json",
    "chars": 294,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"strict\": true,\n    \"mo"
  },
  {
    "path": "apps/desktop/main/tsconfig.json",
    "chars": 200,
    "preview": "{\n  \"extends\": \"../../../tsconfig.base.json\",\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./t"
  },
  {
    "path": "apps/desktop/main/tsconfig.spec.json",
    "chars": 253,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"commonjs\",\n "
  },
  {
    "path": "apps/web/backend/.eslintrc.json",
    "chars": 309,
    "preview": "{\n  \"extends\": [\"../../../.eslintrc.json\"],\n  \"ignorePatterns\": [\"!**/*\"],\n  \"overrides\": [\n    {\n      \"files\": [\"*.ts\""
  },
  {
    "path": "apps/web/backend/README.md",
    "chars": 99,
    "preview": "Legacy project that was used as a renderer in the Electron app. Might be deleted or used later on.\n"
  },
  {
    "path": "apps/web/backend/jest.config.ts",
    "chars": 381,
    "preview": "/* eslint-disable */\nexport default {\n  displayName: \"web-backend\",\n  preset: \"../../../jest.preset.js\",\n  globals: {\n  "
  },
  {
    "path": "apps/web/backend/src/DesktopAPI.ts",
    "chars": 7597,
    "preview": "import { ModuleRef, NestFactory } from \"@nestjs/core\";\nimport { NestExpressApplication } from \"@nestjs/platform-express\""
  },
  {
    "path": "apps/web/backend/src/api-common.module.ts",
    "chars": 988,
    "preview": "import { Module } from \"@nestjs/common\";\nimport { EventEmitterModule } from \"@nestjs/event-emitter\";\nimport { LocalRepla"
  },
  {
    "path": "apps/web/backend/src/assets/index.html",
    "chars": 444,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Rewind Desktop API</title>\n</head>\n<body>\n\nThi"
  },
  {
    "path": "apps/web/backend/src/blueprints/BlueprintInfo.ts",
    "chars": 438,
    "preview": "// TODO: Maybe a better name would be BlueprintMetadata ...\n// We DO parse the blue print (.osu), but that's only becaus"
  },
  {
    "path": "apps/web/backend/src/blueprints/LocalBlueprintController.ts",
    "chars": 2325,
    "preview": "import { Controller, Get, Param, Res } from \"@nestjs/common\";\nimport { LocalBlueprintService } from \"./LocalBlueprintSer"
  },
  {
    "path": "apps/web/backend/src/blueprints/LocalBlueprintService.ts",
    "chars": 4731,
    "preview": "import { Inject, Injectable, Logger } from \"@nestjs/common\";\nimport { BlueprintInfo } from \"./BlueprintInfo\";\nimport { O"
  },
  {
    "path": "apps/web/backend/src/blueprints/OsuDBDao.ts",
    "chars": 1919,
    "preview": "import { promises } from \"fs\";\nimport { BlueprintInfo } from \"./BlueprintInfo\";\nimport { fileLastModifiedTime, ticksToDa"
  },
  {
    "path": "apps/web/backend/src/config/DesktopConfigController.ts",
    "chars": 1196,
    "preview": "import { Body, Controller, Get, Logger, Post, Res } from \"@nestjs/common\";\nimport { DesktopConfigService } from \"./Deskt"
  },
  {
    "path": "apps/web/backend/src/config/DesktopConfigService.ts",
    "chars": 1368,
    "preview": "import { Inject, Injectable } from \"@nestjs/common\";\nimport { writeFile, readFile } from \"fs/promises\";\n\n/**\n * Contains"
  },
  {
    "path": "apps/web/backend/src/config/UserConfigService.ts",
    "chars": 391,
    "preview": "import { Injectable } from \"@nestjs/common\";\n\ninterface Config {\n  skinId: string;\n  // apiKey: string;\n}\n\nconst NO_SKIN"
  },
  {
    "path": "apps/web/backend/src/config/utils.ts",
    "chars": 821,
    "preview": "/**\n * Checks if booting with the given osu! folder path would cause major errors.\n * Usually, if osu! stable does not h"
  },
  {
    "path": "apps/web/backend/src/constants.ts",
    "chars": 179,
    "preview": "// Dependency injection types\nexport const OSU_FOLDER = \"OSU_FOLDER\";\nexport const OSU_SONGS_FOLDER = Symbol(\"OSU_SONGS_"
  },
  {
    "path": "apps/web/backend/src/environments/environment.prod.ts",
    "chars": 52,
    "preview": "export const environment = {\n  production: true,\n};\n"
  },
  {
    "path": "apps/web/backend/src/environments/environment.ts",
    "chars": 246,
    "preview": "export const environment = {\n  production: false,\n  // Where the .cfg file is stored as well\n  userDataPath: \"./tmp\",\n  "
  },
  {
    "path": "apps/web/backend/src/events/Events.ts",
    "chars": 271,
    "preview": "import { Replay } from \"node-osr\";\n\nexport const ReplayWatchEvents = {\n  ReplayRead: Symbol(\"ReplayDetected\"),\n  Ready: "
  },
  {
    "path": "apps/web/backend/src/events/EventsGateway.ts",
    "chars": 1414,
    "preview": "import { Logger } from \"@nestjs/common\";\nimport { OnGatewayConnection, OnGatewayDisconnect, WebSocketGateway, WebSocketS"
  },
  {
    "path": "apps/web/backend/src/main.ts",
    "chars": 414,
    "preview": "import { environment } from \"./environments/environment\";\nimport { bootstrapRewindDesktopBackend, RewindBootstrapSetting"
  },
  {
    "path": "apps/web/backend/src/replays/LocalReplayController.ts",
    "chars": 970,
    "preview": "import { Controller, Get, Logger, Param, Res } from \"@nestjs/common\";\nimport { Response } from \"express\";\nimport { Local"
  },
  {
    "path": "apps/web/backend/src/replays/LocalReplayService.ts",
    "chars": 1473,
    "preview": "import { Inject, Injectable, Logger } from \"@nestjs/common\";\nimport { join } from \"path\";\nimport { read as readOsr } fro"
  },
  {
    "path": "apps/web/backend/src/replays/ReplayWatcher.ts",
    "chars": 1576,
    "preview": "import * as chokidar from \"chokidar\";\nimport { basename, join } from \"path\";\nimport { EventEmitter2 } from \"@nestjs/even"
  },
  {
    "path": "apps/web/backend/src/replays/ScoresDBDao.ts",
    "chars": 2454,
    "preview": "//\n// import { Score as DBScore, ScoresDBReader } from \"osu-db-parser\";\n// import { readFile } from \"fs/promises\";\n//\n//"
  },
  {
    "path": "apps/web/backend/src/skins/SkinController.ts",
    "chars": 1045,
    "preview": "import { Controller, Get, Logger, Query, Res } from \"@nestjs/common\";\nimport { Response } from \"express\";\nimport { SkinS"
  },
  {
    "path": "apps/web/backend/src/skins/SkinNameResolver.ts",
    "chars": 786,
    "preview": "import { Inject, Injectable } from \"@nestjs/common\";\nimport { join } from \"path\";\n\ninterface SkinsFolderConfig {\n  prefi"
  },
  {
    "path": "apps/web/backend/src/skins/SkinService.ts",
    "chars": 3347,
    "preview": "import { Inject, Injectable, Logger } from \"@nestjs/common\";\nimport { GetTextureFileOption, OsuSkinTextureResolver, Skin"
  },
  {
    "path": "apps/web/backend/src/status/SetupStatusController.ts",
    "chars": 423,
    "preview": "import { Controller, Get, Res } from \"@nestjs/common\";\nimport { Response } from \"express\";\n\n@Controller(\"/status\")\nexpor"
  },
  {
    "path": "apps/web/backend/src/utils/names.spec.ts",
    "chars": 289,
    "preview": "import { splitByFirstOccurrence } from \"./names\";\n\ntest(\"splitByFirstOccurrence\", () => {\n  expect(splitByFirstOccurrenc"
  },
  {
    "path": "apps/web/backend/src/utils/names.ts",
    "chars": 502,
    "preview": "/**\n * Utility function to split the given string `str` into *at most two* parts, but only at the first occurrence of th"
  },
  {
    "path": "apps/web/backend/tsconfig.app.json",
    "chars": 336,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../../dist/out-tsc\",\n    \"module\": \"commonjs\""
  },
  {
    "path": "apps/web/backend/tsconfig.json",
    "chars": 200,
    "preview": "{\n  \"extends\": \"../../../tsconfig.base.json\",\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./t"
  },
  {
    "path": "apps/web/backend/tsconfig.spec.json",
    "chars": 256,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../../dist/out-tsc\",\n    \"module\": \"commonjs\""
  },
  {
    "path": "babel.config.json",
    "chars": 28,
    "preview": "{\n  \"babelrcRoots\": [\"*\"]\n}\n"
  },
  {
    "path": "electron-builder.json",
    "chars": 772,
    "preview": "{\n  \"appId\": \"sh.abstrakt.rewind\",\n  \"productName\": \"Rewind\",\n  \"nsis\": {\n    \"oneClick\": false,\n    \"allowToChangeInsta"
  },
  {
    "path": "jest.config.ts",
    "chars": 354,
    "preview": "const { getJestProjects } = require(\"@nrwl/jest\");\n\nexport default {\n  projects: getJestProjects(),\n  // Make the enviro"
  },
  {
    "path": "jest.preset.js",
    "chars": 90,
    "preview": "const nxPreset = require(\"@nrwl/jest/preset\").default;\n\nmodule.exports = { ...nxPreset };\n"
  }
]

// ... and 307 more files (download for full content)

About this extraction

This page contains the full source code of the abstrakt8/rewind GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 507 files (758.9 KB), approximately 223.5k tokens, and a symbol index with 1378 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!