From 82934ab22e531fa2afbd48404db340850aa3645e Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 21 Sep 2025 17:08:55 +0800 Subject: [PATCH 1/4] Add Opus audio playback support --- building/libs.xml | 1 + project.xml | 7 +++++++ source/funkin/backend/chart/ChartData.hx | 2 ++ source/funkin/backend/scripting/Script.hx | 2 +- source/funkin/game/PlayState.hx | 6 +++--- source/openfl/utils/Assets.hx | 21 +++++++++++++++++++++ 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/building/libs.xml b/building/libs.xml index 531a07713..897cf843e 100644 --- a/building/libs.xml +++ b/building/libs.xml @@ -18,6 +18,7 @@ + diff --git a/project.xml b/project.xml index 3d6399a14..76a1d0b58 100644 --- a/project.xml +++ b/project.xml @@ -115,6 +115,11 @@ + +
+ +
+ @@ -127,6 +132,8 @@ + + diff --git a/source/funkin/backend/chart/ChartData.hx b/source/funkin/backend/chart/ChartData.hx index dfb514bde..c8ca39f43 100644 --- a/source/funkin/backend/chart/ChartData.hx +++ b/source/funkin/backend/chart/ChartData.hx @@ -39,6 +39,8 @@ typedef ChartMetaData = { public var ?instSuffix:String; public var ?vocalsSuffix:String; public var ?needsVoices:Bool; + + public var ?musicExt:String; } typedef ChartStrumLine = { diff --git a/source/funkin/backend/scripting/Script.hx b/source/funkin/backend/scripting/Script.hx index 7fae347d3..4f7070537 100644 --- a/source/funkin/backend/scripting/Script.hx +++ b/source/funkin/backend/scripting/Script.hx @@ -106,7 +106,7 @@ class Script extends FlxBasic implements IFlxDestroyable { #if TRANSLATIONS_SUPPORT "TranslationUtil" => funkin.backend.utils.TranslationUtil, - "translate" => funkin.backend.utils.TranslationUtil.get, + "translate" => funkin.backend.utils.TranslationUtil.get, #end ]; } diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index baad9d40d..ebd3e2638 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1121,11 +1121,11 @@ class PlayState extends MusicBeatState curSong = songData.meta.name.toLowerCase(); curSongID = curSong.replace(" ", "-"); - FlxG.sound.setMusic(inst = FlxG.sound.load(Assets.getMusic(Paths.inst(SONG.meta.name, difficulty, SONG.meta.instSuffix)))); + FlxG.sound.setMusic(inst = FlxG.sound.load(Assets.getMusic(Paths.inst(SONG.meta.name, difficulty, SONG.meta.instSuffix, SONG.meta.musicExt)))); - var vocalsPath = Paths.voices(SONG.meta.name, difficulty, SONG.meta.vocalsSuffix); + var vocalsPath = Paths.voices(SONG.meta.name, difficulty, SONG.meta.vocalsSuffix, SONG.meta.musicExt); if (SONG.meta.needsVoices && Assets.exists(vocalsPath)) - vocals = FlxG.sound.load(Options.streamedVocals ? Assets.getMusic(vocalsPath) : vocalsPath); + vocals = FlxG.sound.load(Options.streamedVocals ? Assets.getMusic(vocalsPath) : vocalsPath, musicExt); else vocals = new FlxSound(); diff --git a/source/openfl/utils/Assets.hx b/source/openfl/utils/Assets.hx index c14b9f2c9..92994153c 100644 --- a/source/openfl/utils/Assets.hx +++ b/source/openfl/utils/Assets.hx @@ -22,6 +22,9 @@ import lime.utils.Assets as LimeAssets; import lime.media.AudioBuffer; import lime.media.vorbis.VorbisFile; #end +#if OPUS_AUDIO_PLAYBACK +import hxopus.Opus; +#end /** The Assets class provides a cross-platform interface to access @@ -310,6 +313,24 @@ class Assets return sound; } + + #if OPUS_AUDIO_PLAYBACK + // Try to load as Opus if normal loading failed + var bytes = getBytes(id); + + if (bytes != null) + { + var sound = Opus.toOpenFL(bytes); + + if (useCache && cache.enabled) + { + cache.setSound(id, sound); + } + + return sound; + } + #end + #end return null; From 98856f0261fe423024db472cd323d22e083e47ca Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:46:39 +0800 Subject: [PATCH 2/4] Alright, now the audio loading will attempt to read the Opus file if it doesn't detect the OGG version. --- source/funkin/backend/assets/Paths.hx | 53 ++++++++++++++++++++++-- source/funkin/backend/system/Flags.hx | 5 +++ source/funkin/editors/charter/Charter.hx | 6 +-- source/funkin/game/PlayState.hx | 2 +- source/funkin/menus/FreeplayState.hx | 2 +- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index 5b8d8395f..4114584cf 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -81,25 +81,72 @@ class Paths return getPath('data/$key.ps1', library); static public function sound(key:String, ?library:String, ?ext:String) + { + for (path in Flags.SOUND_EXTENSIONS) { + var path = 'sounds/$key.$path'; + if (OpenFlAssets.exists(path)) + return getPath(path, library); + } + return getPath('sounds/$key.${ext != null ? ext : Flags.SOUND_EXT}', library); + } public static inline function soundRandom(key:String, min:Int, max:Int, ?library:String) return sound(key + FlxG.random.int(min, max), library); - inline static public function music(key:String, ?library:String, ?ext:String) + static public function music(key:String, ?library:String, ?ext:String) + { + for (path in Flags.SOUND_EXTENSIONS) + { + var path = 'music/$key.$path'; + if (OpenFlAssets.exists(getPath(path, library))) + return getPath(path, library); + } + return getPath('music/$key.${ext != null ? ext : Flags.SOUND_EXT}', library); + } - inline static public function voices(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) { + static public function voices(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; if (ext == null) ext = Flags.SOUND_EXT; + + for (e in Flags.SOUND_EXTENSIONS) + { + var path = 'songs/$song/song/Voices$suffix-${difficulty}.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + + path = 'songs/$song/song/Voices$suffix.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + } + var diff = getPath('songs/$song/song/Voices$suffix-${difficulty}.${ext}', null); + trace(diff); return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Voices$suffix.${ext}', null); } - inline static public function inst(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) { + static public function inst(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; if (ext == null) ext = Flags.SOUND_EXT; + + for (e in Flags.SOUND_EXTENSIONS) + { + var path = 'songs/$song/song/Inst$suffix-${difficulty}.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + + path = 'songs/$song/song/Inst$suffix.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + } + var diff = getPath('songs/$song/song/Inst$suffix-${difficulty}.${ext}', null); + trace(diff); return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Inst$suffix.${ext}', null); } diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx index f57eb7aef..333f53875 100644 --- a/source/funkin/backend/system/Flags.hx +++ b/source/funkin/backend/system/Flags.hx @@ -56,6 +56,11 @@ class Flags { public static var REPO_OWNER:String = "CodenameCrew"; public static var REPO_URL:String = 'https://github.com/$REPO_OWNER/$REPO_NAME'; + public static var SOUND_EXTENSIONS:Array = [ + #if web "mp3" #else "ogg" #end, + "opus" + ]; + /** * Preferred sound extension for the game's audio files. * Currently is set to `mp3` for web targets, and `ogg` for other targets. diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 952fdacac..1baac0a08 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -608,9 +608,9 @@ class Charter extends UIState { Conductor.setupSong(PlayState.SONG); noteTypes = PlayState.SONG.noteTypes; - FlxG.sound.setMusic(FlxG.sound.load(Paths.inst(__song, __diff, PlayState.SONG.meta.instSuffix))); - if (Assets.exists(Paths.voices(__song, __diff, PlayState.SONG.meta.vocalsSuffix))) - vocals = FlxG.sound.load(Paths.voices(__song, __diff, PlayState.SONG.meta.vocalsSuffix)); + FlxG.sound.setMusic(FlxG.sound.load(Paths.inst(__song, __diff, PlayState.SONG.meta.instSuffix, PlayState.SONG.meta.musicExt))); + if (Assets.exists(Paths.voices(__song, __diff, PlayState.SONG.meta.vocalsSuffix, PlayState.SONG.meta.musicExt))) + vocals = FlxG.sound.load(Paths.voices(__song, __diff, PlayState.SONG.meta.vocalsSuffix, PlayState.SONG.meta.musicExt)); else vocals = new FlxSound(); diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index ebd3e2638..53423639d 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1125,7 +1125,7 @@ class PlayState extends MusicBeatState var vocalsPath = Paths.voices(SONG.meta.name, difficulty, SONG.meta.vocalsSuffix, SONG.meta.musicExt); if (SONG.meta.needsVoices && Assets.exists(vocalsPath)) - vocals = FlxG.sound.load(Options.streamedVocals ? Assets.getMusic(vocalsPath) : vocalsPath, musicExt); + vocals = FlxG.sound.load(Options.streamedVocals ? Assets.getMusic(vocalsPath) : vocalsPath, SONG.meta.musicExt); else vocals = new FlxSound(); diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index 69fc3d8de..d82615356 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -259,7 +259,7 @@ class FreeplayState extends MusicBeatState var dontPlaySongThisFrame = false; autoplayElapsed += elapsed; if (!disableAutoPlay && !songInstPlaying && (autoplayElapsed > timeUntilAutoplay)) { - if (curPlayingInst != (curPlayingInst = Paths.inst(curSong.name, curDifficulties[curDifficulty], curSong.instSuffix))) { + if (curPlayingInst != (curPlayingInst = Paths.inst(curSong.name, curDifficulties[curDifficulty], curSong.instSuffix, curSong.musicExt))) { var streamed = false; if (Options.streamedMusic) { var sound = Assets.getMusic(curPlayingInst, true, false); From 6af14cdd3052ed419b305de172a8d3fdfd671934 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:18:30 +0800 Subject: [PATCH 3/4] If a file extension is already specified, it will directly skip the multi-extension detection process --- source/funkin/backend/assets/Paths.hx | 78 ++++++++++++++------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index 4114584cf..5b343cac6 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -82,11 +82,12 @@ class Paths static public function sound(key:String, ?library:String, ?ext:String) { - for (path in Flags.SOUND_EXTENSIONS) { - var path = 'sounds/$key.$path'; - if (OpenFlAssets.exists(path)) - return getPath(path, library); - } + if (ext == null) + for (path in Flags.SOUND_EXTENSIONS) { + var path = 'sounds/$key.$path'; + if (OpenFlAssets.exists(path)) + return getPath(path, library); + } return getPath('sounds/$key.${ext != null ? ext : Flags.SOUND_EXT}', library); } @@ -96,33 +97,35 @@ class Paths static public function music(key:String, ?library:String, ?ext:String) { - for (path in Flags.SOUND_EXTENSIONS) - { - var path = 'music/$key.$path'; - if (OpenFlAssets.exists(getPath(path, library))) - return getPath(path, library); - } + if (ext == null) + for (path in Flags.SOUND_EXTENSIONS) + { + var path = 'music/$key.$path'; + if (OpenFlAssets.exists(getPath(path, library))) + return getPath(path, library); + } return getPath('music/$key.${ext != null ? ext : Flags.SOUND_EXT}', library); } static public function voices(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; - if (ext == null) ext = Flags.SOUND_EXT; - for (e in Flags.SOUND_EXTENSIONS) - { - var path = 'songs/$song/song/Voices$suffix-${difficulty}.$e'; - trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); - if (OpenFlAssets.exists(getPath(path, null))) - return getPath(path, null); - - path = 'songs/$song/song/Voices$suffix.$e'; - trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); - if (OpenFlAssets.exists(getPath(path, null))) - return getPath(path, null); - } + if (ext == null) + for (e in Flags.SOUND_EXTENSIONS) + { + var path = 'songs/$song/song/Voices$suffix-${difficulty}.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + + path = 'songs/$song/song/Voices$suffix.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + } + if (ext == null) ext = Flags.SOUND_EXT; var diff = getPath('songs/$song/song/Voices$suffix-${difficulty}.${ext}', null); trace(diff); return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Voices$suffix.${ext}', null); @@ -130,21 +133,22 @@ class Paths static public function inst(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) { if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY; - if (ext == null) ext = Flags.SOUND_EXT; - for (e in Flags.SOUND_EXTENSIONS) - { - var path = 'songs/$song/song/Inst$suffix-${difficulty}.$e'; - trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); - if (OpenFlAssets.exists(getPath(path, null))) - return getPath(path, null); - - path = 'songs/$song/song/Inst$suffix.$e'; - trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); - if (OpenFlAssets.exists(getPath(path, null))) - return getPath(path, null); - } + if (ext == null) + for (e in Flags.SOUND_EXTENSIONS) + { + var path = 'songs/$song/song/Inst$suffix-${difficulty}.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + + path = 'songs/$song/song/Inst$suffix.$e'; + trace(path + " | " + OpenFlAssets.exists(getPath(path, null))); + if (OpenFlAssets.exists(getPath(path, null))) + return getPath(path, null); + } + if (ext == null) ext = Flags.SOUND_EXT; var diff = getPath('songs/$song/song/Inst$suffix-${difficulty}.${ext}', null); trace(diff); return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Inst$suffix.${ext}', null); From 57f7747e3daceab3886bbce21db45843c7550546 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:20:49 +0800 Subject: [PATCH 4/4] Update Assets.hx --- source/openfl/utils/Assets.hx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/source/openfl/utils/Assets.hx b/source/openfl/utils/Assets.hx index 92994153c..5ac054dcf 100644 --- a/source/openfl/utils/Assets.hx +++ b/source/openfl/utils/Assets.hx @@ -291,13 +291,10 @@ class Assets var sound = cache.getSound(id); if (isValidSound(sound)) - { return sound; - } } var buffer = LimeAssets.getAudioBuffer(id, false); - if (buffer != null) { #if flash @@ -307,9 +304,7 @@ class Assets #end if (useCache && cache.enabled) - { cache.setSound(id, sound); - } return sound; } @@ -317,15 +312,12 @@ class Assets #if OPUS_AUDIO_PLAYBACK // Try to load as Opus if normal loading failed var bytes = getBytes(id); - if (bytes != null) { var sound = Opus.toOpenFL(bytes); if (useCache && cache.enabled) - { cache.setSound(id, sound); - } return sound; }