Skip to content

Commit

Permalink
parse more of the osu file, add the ability to play any beatmap
Browse files Browse the repository at this point in the history
  • Loading branch information
konekowo committed Sep 28, 2024
1 parent 9572d61 commit ab227ee
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 94 deletions.
35 changes: 30 additions & 5 deletions src/Audio/Audio.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {BeatmapData} from "../Util/Beatmap/Data/BeatmapData";
import {Main} from "../main";

export class Audio {
public audio!: AudioBuffer;
Expand All @@ -19,11 +20,11 @@ export class Audio {
public LeftChannel: number = 0;
public RightChannel: number = 0;
public FrequencyAmplitudes = new Float32Array(256);
private _connectedToContext = false;
private _useMediaSource = false;
private _onEndCallback?: () => void;
protected _connectedToContext = false;
protected _useMediaSource = false;
protected _onEndCallback?: () => void;
public timeStarted = 0; // only for silent audio when real music is paused
private paused = false; // used to check if music was paused outside of Audio class (i.e. pressing stop media button on the keyboard)
protected paused = false; // used to check if music was paused outside of Audio class (i.e. pressing stop media button on the keyboard)

public GetMaximumAudioLevel() {
return Math.max(this.LeftChannel, this.RightChannel);
Expand All @@ -45,7 +46,6 @@ export class Audio {
}
this.mediaSource = audioContext.createMediaElementSource(this.mediaAudioElement);
this.mediaAudioElement.onpause = () => {
console.log(this.mediaAudioElement.ended);
if (!this.paused && !this.mediaAudioElement.ended) { // check if music was paused outside of Audio class
this.Play();
}
Expand Down Expand Up @@ -198,4 +198,29 @@ export class MapAudio extends Audio {
public fadeOutTimeout!: Timeout
public playingCallback?: () => void;

public FadeOut() {
if (!(!this._useMediaSource ? this.source : this.mediaSource)) {
throw new Error("Source not created yet!");
}
if (!this._connectedToContext) {
throw new Error("Not connected to audio context yet!");
}
if (!this._useMediaSource) {
throw new Error("FadeOut is not supported on AudioSourceBuffer!");
}
if (this.fadingOut) {
return;
}
this.fadingOut = true;
clearTimeout(this.fadeOutTimeout);
let gainNodes = this.GetNode(GainNode);
if (gainNodes == null) {
throw new Error("Gain Node doesn't exist on Audio Object!");
}
let gain = gainNodes[0];
gain.gain.linearRampToValueAtTime(0, Main.AudioEngine.audioContext.currentTime + 1)
setTimeout(() => {
this.Stop();
}, 1000);
}
}
31 changes: 11 additions & 20 deletions src/Audio/AudioEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,14 @@ export class AudioEngine {
}

public PlayMusicImmediately(mapAudio: string, beatMapData: BeatmapData, musicPlayingCallback?: () => void) {
// clear queue
this._musicQueue = [];
let currentPlaying = this.GetCurrentPlayingMusicNoSilent();
if (currentPlaying) {
this._musicQueue = [currentPlaying];
currentPlaying.FadeOut();
}
else {
this._musicQueue = []
}
this.AddToMusicQueue(mapAudio, beatMapData, musicPlayingCallback);
}

Expand Down Expand Up @@ -160,21 +166,6 @@ export class AudioEngine {
// check if audio is type of MapAudio
if ("beatmap" in audio && audio.beatmap) {
audio.Create(this.audioContext, true);
this._playingAudios.audios.forEach((audio) => {
if ("beatmap" in audio && audio.beatmap) {
clearTimeout(audio.fadeOutTimeout);
audio.fadingOut = true;
let gainNodes = audio.GetNode(GainNode);
if (gainNodes == null) {
throw new Error("Gain Node doesn't exist on Audio Object!");
}
let gain = gainNodes[0];
gain.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + 0.4)
setTimeout(() => {
audio.Stop();
}, 400);
}
});
let gain = this.audioContext.createGain();
gain.gain.value = 0;
let analyzer = this.audioContext.createAnalyser();
Expand Down Expand Up @@ -207,10 +198,10 @@ export class AudioEngine {
let timeOffset = Date.now();
audio.mediaAudioElement.onloadedmetadata = () => {
audio.fadeOutTimeout = setTimeout(() => {
gain.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + 0.4);
}, Math.max(0, ((audio.mediaAudioElement.duration - 0.4) * 1000) - (Date.now() - timeOffset)));
gain.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + 1);
}, Math.max(0, ((audio.mediaAudioElement.duration - 1) * 1000) - (Date.now() - timeOffset)));
}
gain.gain.linearRampToValueAtTime(1, this.audioContext.currentTime + 0.4);
gain.gain.linearRampToValueAtTime(1, this.audioContext.currentTime + 0.5);

} else {
audio.Create(this.audioContext, false);
Expand Down
2 changes: 1 addition & 1 deletion src/Screens/IntroScreen/IntroScreen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class IntroScreen extends Screen {
let beatmapData = BeatmapParser.Parse(osuFile);
console.log(beatmapData);
for (const [name, entry] of Object.entries(entries)) {
if (name == beatmapData.General.AudioFileName) {
if (name == beatmapData.General.AudioFilename) {
entry.blob().then(blob => {
let url = URL.createObjectURL(blob);
setTimeout(() => {
Expand Down
6 changes: 3 additions & 3 deletions src/Util/Beatmap/Data/Sections/General/GeneralData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ export class GeneralData {
/**
* Location of the audio file relative to the current folder.
*/
public AudioFileName: string | undefined;
public AudioFilename: string | null = null;
/**
* Milliseconds of silence before the audio starts playing
*/
public AudioLeadIn: number = 0;
/**
* @deprecated The `AudioHash` property is deprecated according to the osu! wiki.
*/
public AudioHash: string | undefined;
public AudioHash: string | null = null;
/**
* Time in milliseconds when the audio preview should start
*/
Expand Down Expand Up @@ -63,7 +63,7 @@ export class GeneralData {
/**
* Preferred skin to use during gameplay
*/
public SkinPreference: string | undefined;
public SkinPreference: string | null = null;
/**
* Whether or not a warning about flashing colours should be shown at the beginning of the map
*/
Expand Down
36 changes: 35 additions & 1 deletion src/Util/Beatmap/Parser/BeatmapParser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import {BeatmapData} from "../Data/BeatmapData";
import {TimingPointsParser} from "./TimingPointsParser";
import {GeneralParser} from "./GeneralParser";
import {EditorParser} from "./EditorParser";
import {MetadataParser} from "./MetadataParser";
import {DifficultyParser} from "./DifficultyParser";

export class BeatmapParser {
public static Parse(osuFileContent: string, storyBoardFileContent?: string): BeatmapData {
const beatMapData = new BeatmapData();
let osuFileContentLines = osuFileContent.split(/\r?\n|\r|\n/g);
GeneralParser.ParseGeneral(beatMapData, BeatmapParser.GetSection("General", osuFileContentLines))
GeneralParser.ParseGeneral(beatMapData, BeatmapParser.GetSection("General", osuFileContentLines));
EditorParser.ParseEditor(beatMapData, BeatmapParser.GetSection("Editor", osuFileContentLines));
MetadataParser.ParseMetadata(beatMapData, BeatmapParser.GetSection("Metadata", osuFileContentLines));
DifficultyParser.ParseDifficulty(beatMapData, BeatmapParser.GetSection("Difficulty", osuFileContentLines));
TimingPointsParser.ParseTimingPoints(beatMapData, BeatmapParser.GetSection("TimingPoints", osuFileContentLines));
return beatMapData
}
Expand All @@ -27,4 +33,32 @@ export class BeatmapParser {
});
return section;
}
public static AutoParse<T>(sectionType: new (...args: any[]) => T, propValue: string[], beatmapDataSection: T) {
let key = propValue[0] as keyof T;
let keyExists = false;
for (let sectionKey in beatmapDataSection) {
if (sectionKey == key){
keyExists = true;
}
}
if (!keyExists) {
console.warn(key.toString() + " does not exist on " + sectionType.name +"!");
return;
}
let isNumber = /^[0-9]+$|^[0-9]+.+$|^-[0-9]+$|^-[0-9]+.+$/.test(propValue[1]);
let isBoolean = typeof beatmapDataSection[key] == "boolean";
let value;
if (isBoolean) {
value = propValue[1] == "1";
}
else if (isNumber) {
value = parseFloat(propValue[1]);
}
else {
value = propValue[1];
}

// @ts-ignore
beatmapDataSection[key] = value;
}
}
17 changes: 17 additions & 0 deletions src/Util/Beatmap/Parser/DifficultyParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {BeatmapData} from "../Data/BeatmapData";
import {BeatmapParser} from "./BeatmapParser";
import {DifficultyData} from "../Data/Sections/Difficulty/DifficultyData";

export class DifficultyParser {
public static ParseDifficulty(beatmapData: BeatmapData, section: string[]) {
section.forEach((str) => {
let propValue = str.split(":");
if (propValue[1].startsWith(" ")){
propValue[1] = propValue[1].substring(1, propValue[1].length);
}

BeatmapParser.AutoParse(DifficultyData, propValue, beatmapData.Difficulty);
});
}

}
22 changes: 22 additions & 0 deletions src/Util/Beatmap/Parser/EditorParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {BeatmapData} from "../Data/BeatmapData";
import {BeatmapParser} from "./BeatmapParser";
import {EditorData} from "../Data/Sections/Editor/EditorData";

export class EditorParser {
public static ParseEditor(beatmapData: BeatmapData, section: string[]) {
section.forEach((str) => {
let propValue = str.split(":");
if (propValue[1].startsWith(" ")){
propValue[1] = propValue[1].substring(1, propValue[1].length);
}
if (propValue[0] != "Bookmarks") {
BeatmapParser.AutoParse(EditorData, propValue, beatmapData.Editor);
}
else {
propValue[1].split(",")
.forEach((num) => {beatmapData.Editor.Bookmarks.push(parseFloat(num));});
}
});
}

}
67 changes: 5 additions & 62 deletions src/Util/Beatmap/Parser/GeneralParser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {BeatmapData} from "../Data/BeatmapData";
import {SampleSet} from "../Data/Sections/General/SampleSet";
import {OverlayPosition} from "../Data/Sections/General/OverlayPosition";
import {GeneralData} from "../Data/Sections/General/GeneralData";
import {BeatmapParser} from "./BeatmapParser";

export class GeneralParser {
public static ParseGeneral(beatmapData: BeatmapData, section: string[]) {
Expand All @@ -9,66 +9,9 @@ export class GeneralParser {
if (propValue[1].startsWith(" ")){
propValue[1] = propValue[1].substring(1, propValue[1].length);
}
switch (propValue[0]) {
case "AudioFilename":
beatmapData.General.AudioFileName = propValue[1];
break;
case "AudioLeadIn":
beatmapData.General.AudioLeadIn = parseInt(propValue[1]);
break;
case "AudioHash":
beatmapData.General.AudioHash = propValue[1];
break;
case "PreviewTime":
beatmapData.General.PreviewTime = parseInt(propValue[1]);
break;
case "Countdown":
beatmapData.General.Countdown = parseInt(propValue[1]);
break;
case "SampleSet":
beatmapData.General.SampleSet = propValue[1] as SampleSet;
break;
case "StackLeniency":
beatmapData.General.StackLeniency = parseFloat(propValue[1]);
break;
case "Mode":
beatmapData.General.Mode = parseInt(propValue[1]);
break;
case "LetterboxInBreaks":
beatmapData.General.LetterboxInBreaks = propValue[1] == "1";
break;
case "StoryFireInFront":
beatmapData.General.StoryFireInFront = propValue[1] == "1";
break;
case "UseSkinSprites":
beatmapData.General.UseSkinSprites = propValue[1] == "1";
break;
case "AlwaysShowPlayfield":
beatmapData.General.AlwaysShowPlayfield = propValue[1] == "1";
break;
case "OverlayPosition":
beatmapData.General.OverlayPosition = propValue[1] as OverlayPosition;
break;
case "SkinPreference":
beatmapData.General.SkinPreference = propValue[1];
break;
case "EpilepsyWarning":
beatmapData.General.EpilepsyWarning = propValue[1] == "1";
break;
case "CountdownOffset":
beatmapData.General.CountdownOffset = parseInt(propValue[1]);
break;
case "SpecialStyle":
beatmapData.General.SpecialStyle = propValue[1] == "1";
break;
case "WidescreenStoryboard":
beatmapData.General.WidescreenStoryboard = propValue[1] == "1";
break;
case "SamplesMatchPlaybackRate":
beatmapData.General.SamplesMatchPlaybackRate = propValue[1] == "1";
break;
}
})

BeatmapParser.AutoParse(GeneralData, propValue, beatmapData.General);
});
}

}
17 changes: 17 additions & 0 deletions src/Util/Beatmap/Parser/MetadataParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {BeatmapData} from "../Data/BeatmapData";
import {BeatmapParser} from "./BeatmapParser";
import {Metadata} from "../Data/Sections/Metadata/Metadata";

export class MetadataParser {
public static ParseMetadata(beatmapData: BeatmapData, section: string[]) {
section.forEach((str) => {
let propValue = str.split(":");
if (propValue[1].startsWith(" ")){
propValue[1] = propValue[1].substring(1, propValue[1].length);
}

BeatmapParser.AutoParse(Metadata, propValue, beatmapData.Metadata);
});
}

}
44 changes: 42 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {MenuCursor} from "./Elements/MenuCursor/MenuCursor";
import {AudioEngine} from "./Audio/AudioEngine";
import * as TWEEN from "@tweenjs/tween.js";
import {SettingsPane} from "./Elements/Settings/SettingsPane";
import {unzip} from "unzipit";
import {BeatmapParser} from "./Util/Beatmap/Parser/BeatmapParser";

export class Main {
public static app: Application;
Expand All @@ -23,9 +25,47 @@ export class Main {

public constructor(app: Application) {
Main.app = app;
// @ts-ignore
document.body.appendChild(Main.app.canvas);
// for testing purposes
Object.defineProperty(window, "onDropEvent", {value: (e: DragEvent) => {
e.preventDefault();
if (e.dataTransfer!.items) {
let item = [...e.dataTransfer!.items][0];
if (item.kind == "file") {
const file = item.getAsFile()!;
unzip(file).then(({entries}) => {
for (const [name, entry] of Object.entries(entries)) {
if (name.endsWith(".osu")) {
entry.text().then((osuFile) => {
let beatmapData = BeatmapParser.Parse(osuFile);
console.log(beatmapData);
for (const [name, entry] of Object.entries(entries)) {
if (name == beatmapData.General.AudioFilename) {
entry.blob().then(blob => {
let url = URL.createObjectURL(blob);
Main.AudioEngine.PlayMusicImmediately(url, beatmapData, () => {
console.log("Now playing " + beatmapData.Metadata.TitleUnicode + " - " +beatmapData.Metadata.ArtistUnicode +
" ("+beatmapData.Metadata.Title + " - " + beatmapData.Metadata.Artist + ")");
});
});
}
}
});
break;
}
}
});
}
}
}});

Object.defineProperty(window, "onDragEvent", {value: (e: DragEvent) => {
e.preventDefault();
}});
Main.app.canvas.setAttribute("ondrop", "window.onDropEvent(event);");
Main.app.canvas.setAttribute("ondragover", "window.onDragEvent(event);");

document.body.appendChild(Main.app.canvas);

Main.settingsPane = new SettingsPane();
Main.settingsPane.zIndex = 999998;
Main.app.stage.addChild(Main.settingsPane);
Expand Down

0 comments on commit ab227ee

Please sign in to comment.