diff --git a/README.rst b/README.rst index c5b1ffdf..cd6d900b 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,6 @@ Dependencies - OpenGL >= 3.3 or OpenGL ES >= 3.0 or OpenGL ES >= 2.0 (with some extensions) - SDL2 >= 2.0.6 -- SDL2_mixer >= 2.0.4 - freetype2 - libpng >= 1.5.0 - libwebpdecoder >= 0.5 or libwebp >= 0.5 diff --git a/meson.build b/meson.build index ba17f4da..0691f48e 100644 --- a/meson.build +++ b/meson.build @@ -138,10 +138,8 @@ endif static = get_option('static') or ['emscripten', 'nx'].contains(host_machine.system()) dep_freetype = dependency('freetype2', required : true, static : static, fallback : ['freetype', 'freetype_dep']) -dep_opusfile = dependency('opusfile', required : false, static : static, fallback : ['opusfile', 'opusfile_dep']) dep_png = dependency('libpng', version : '>=1.5', required : true, static : static, fallback : ['libpng', 'png_dep']) dep_sdl2 = dependency('sdl2', version : '>=2.0.6', required : true, static : static, fallback : ['sdl2', 'sdl2_dep']) -dep_sdl2_mixer = dependency('SDL2_mixer', version : '>=2.0.4', required : false, static : static, fallback : ['sdl2_mixer', 'sdl2_mixer_dep']) dep_webp = dependency('libwebp', version : '>=0.5', required : true, static : static, fallback : ['libwebp', 'webpdecoder_dep']) dep_webpdecoder = dependency('libwebpdecoder', version : '>=0.5', required : false, static : static) dep_zip = dependency('libzip', version : '>=1.2', required : false, static : static, fallback : ['libzip', 'libzip_dep']) diff --git a/meson_options.txt b/meson_options.txt index 0adb06d6..db3d41ba 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -145,15 +145,15 @@ option( option( 'a_default', type : 'combo', - choices : ['sdl2mixer', 'null'], + choices : ['sdl', 'null'], description : 'Which audio backend to use by default' ) option( - 'a_sdl2mixer', + 'a_sdl', type : 'boolean', value : true, - description : 'Build the SDL2_Mixer audio backend' + description : 'Build the SDL audio backend' ) option( diff --git a/resources/00-taisei.pkgdir/bgm/bonus0.bgm b/resources/00-taisei.pkgdir/bgm/bonus0.bgm deleted file mode 100644 index 2cfe5433..00000000 --- a/resources/00-taisei.pkgdir/bgm/bonus0.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Divine Correction for Outliers -artist = Tuck V -loop = res/bgm/bonus0.opus -loop_point = 2.909093 -comment = The decision to arrange a character’s existing theme or to come up with something entirely new is difficult and likely one ZUN himself struggles with.\n\nI know my strengths lie in the latter, which ultimately resulted in the stormy composition usually heard at the top of the tower, but Iku has such an interesting original theme I had to at least accept the challenge.\n\nUltimately, I can’t decide if I did the original justice with this one or not, but I know it holds a special place in my heart. diff --git a/resources/00-taisei.pkgdir/bgm/bonus0.opus b/resources/00-taisei.pkgdir/bgm/bonus0.opus index c19b9b80..06eecad0 100644 Binary files a/resources/00-taisei.pkgdir/bgm/bonus0.opus and b/resources/00-taisei.pkgdir/bgm/bonus0.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/credits.bgm b/resources/00-taisei.pkgdir/bgm/credits.bgm deleted file mode 100644 index 827e3ab1..00000000 --- a/resources/00-taisei.pkgdir/bgm/credits.bgm +++ /dev/null @@ -1,4 +0,0 @@ -title = Existential Field -artist = Tuck V -loop = res/bgm/credits.opus -comment = The staff roll theme.\n\nA sense of inspiration and the future is maintained from Excitation Field.\n\nOne that’s a lot gentler than a thunderstorm… perhaps a bit more like a rainbow. diff --git a/resources/00-taisei.pkgdir/bgm/credits.opus b/resources/00-taisei.pkgdir/bgm/credits.opus index 095be126..c339fd3d 100644 Binary files a/resources/00-taisei.pkgdir/bgm/credits.opus and b/resources/00-taisei.pkgdir/bgm/credits.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/ending.bgm b/resources/00-taisei.pkgdir/bgm/ending.bgm deleted file mode 100644 index 7bb93868..00000000 --- a/resources/00-taisei.pkgdir/bgm/ending.bgm +++ /dev/null @@ -1,4 +0,0 @@ -title = Dream Cycle -artist = Tuck V -loop = res/bgm/ending.opus -comment = The ending’s theme.\n\nIn summation, class, what have we learned today?\n\nKnowledge is the forbidden fruit; from it stem the powers of light and darkness. But it’s about time the protagonists have had a snack. diff --git a/resources/00-taisei.pkgdir/bgm/ending.opus b/resources/00-taisei.pkgdir/bgm/ending.opus index 09460c16..741dcf9f 100644 Binary files a/resources/00-taisei.pkgdir/bgm/ending.opus and b/resources/00-taisei.pkgdir/bgm/ending.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/menu.bgm b/resources/00-taisei.pkgdir/bgm/menu.bgm deleted file mode 100644 index 758ba9cb..00000000 --- a/resources/00-taisei.pkgdir/bgm/menu.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Delta Concision -artist = Tuck V -loop = res/bgm/menu.opus -loop_point = 13.71 -comment = An introduction to the occident.\n\nMany paths branch from one. Echoes from across the sea yield a new adventure. diff --git a/resources/00-taisei.pkgdir/bgm/menu.opus b/resources/00-taisei.pkgdir/bgm/menu.opus index fa4780e4..9786155a 100644 Binary files a/resources/00-taisei.pkgdir/bgm/menu.opus and b/resources/00-taisei.pkgdir/bgm/menu.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/scuttle.bgm b/resources/00-taisei.pkgdir/bgm/scuttle.bgm deleted file mode 100644 index 85be185f..00000000 --- a/resources/00-taisei.pkgdir/bgm/scuttle.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Logic Bombardier -artist = Tuck V -loop = res/bgm/scuttle.opus -loop_point = 5.64 -comment = A secret theme for a most beloved character…\n\nI wanted to play with the concept of Scuttle’s motif as heard in the tunnel of light and try to apply it to a full-length song.\n\nThe results were interesting! Her theme is as much an experiment as her existence.\n\nSupposing she were to appear as a boss, I think it might sound different given a different role in the incident. diff --git a/resources/00-taisei.pkgdir/bgm/scuttle.opus b/resources/00-taisei.pkgdir/bgm/scuttle.opus index d77170f6..ea329cf2 100644 Binary files a/resources/00-taisei.pkgdir/bgm/scuttle.opus and b/resources/00-taisei.pkgdir/bgm/scuttle.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage1.bgm b/resources/00-taisei.pkgdir/bgm/stage1.bgm deleted file mode 100644 index 35fb893d..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage1.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = The Fog Invites Unseen Mischief -artist = Tuck V -loop = res/bgm/stage1/stage.opus -loop_point = 6.857143 -comment = The first stage’s theme.\n\nA relaxing mist illustrated with subdued instruments. Familiar shadows stir trouble just out of view. Ripples of times past dance across Misty Lake. diff --git a/resources/00-taisei.pkgdir/bgm/stage1/stage.opus b/resources/00-taisei.pkgdir/bgm/stage1.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage1/stage.opus rename to resources/00-taisei.pkgdir/bgm/stage1.opus index 21faf9a7..070fad7d 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage1/stage.opus and b/resources/00-taisei.pkgdir/bgm/stage1.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage1boss.bgm b/resources/00-taisei.pkgdir/bgm/stage1boss.bgm deleted file mode 100644 index 7e7ba0ac..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage1boss.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = An Impish Reflection on the Water -artist = Tuck V -loop = res/bgm/stage1/boss.opus -loop_point = 3.200000 -comment = Cirno’s theme.\n\nShe’s feeling just as brave this time around, and acting a fair bit stronger.\n\nHowever, she’s really just a minor annoyance, cold and brittle. The protagonists brush off her pranks and move on. diff --git a/resources/00-taisei.pkgdir/bgm/stage1/boss.opus b/resources/00-taisei.pkgdir/bgm/stage1boss.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage1/boss.opus rename to resources/00-taisei.pkgdir/bgm/stage1boss.opus index d1399cfd..02a0a20c 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage1/boss.opus and b/resources/00-taisei.pkgdir/bgm/stage1boss.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage2.bgm b/resources/00-taisei.pkgdir/bgm/stage2.bgm deleted file mode 100644 index abdd5bb3..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage2.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Treasure the Wager, Treasure the Odds -artist = Tuck V -loop = res/bgm/stage2/stage.opus -loop_point = 10.124989 -comment = The second stage’s theme.\n\nFor such a calm and serene path, there sure are a lot of troublemakers — a deceptively fast-paced theme, to be sure. One excitable example makes a brief appearance. The rhythm of this theme is meant to make you feel lucky. diff --git a/resources/00-taisei.pkgdir/bgm/stage2/stage.opus b/resources/00-taisei.pkgdir/bgm/stage2.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage2/stage.opus rename to resources/00-taisei.pkgdir/bgm/stage2.opus index 3fa8c826..633973c2 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage2/stage.opus and b/resources/00-taisei.pkgdir/bgm/stage2.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage2boss.bgm b/resources/00-taisei.pkgdir/bgm/stage2boss.bgm deleted file mode 100644 index 12876339..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage2boss.bgm +++ /dev/null @@ -1,4 +0,0 @@ -title = The Cheerful Presence of a Dark God -artist = Tuck V -loop = res/bgm/stage2/boss.opus -comment = Hina Kagiyama’s theme.\n\nThis time, it’s all up to chance.\n\nIt’s a little merciful sometimes, but you still have to do your best, no matter what. Gambling might be a poor stress-reliever… diff --git a/resources/00-taisei.pkgdir/bgm/stage2/boss.opus b/resources/00-taisei.pkgdir/bgm/stage2boss.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage2/boss.opus rename to resources/00-taisei.pkgdir/bgm/stage2boss.opus index bfb61460..1333b31e 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage2/boss.opus and b/resources/00-taisei.pkgdir/bgm/stage2boss.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage3.bgm b/resources/00-taisei.pkgdir/bgm/stage3.bgm deleted file mode 100644 index c9e72992..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage3.bgm +++ /dev/null @@ -1,4 +0,0 @@ -title = Fizeau’s Finding ~ Lightray Vector -artist = Tuck V -loop = res/bgm/stage3/stage.opus -comment = The third stage’s theme.\n\nIt’s usually tricky to see in the dark. It pulls you in, engulfs you, like a sense-smothering flame. But this time, it might be best to protect your eyes. A fluttering little bug feels most at home in the dead of night, and most alive in a sea of light. diff --git a/resources/00-taisei.pkgdir/bgm/stage3/stage.opus b/resources/00-taisei.pkgdir/bgm/stage3.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage3/stage.opus rename to resources/00-taisei.pkgdir/bgm/stage3.opus index 424a6852..a98d788f 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage3/stage.opus and b/resources/00-taisei.pkgdir/bgm/stage3.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage3boss.bgm b/resources/00-taisei.pkgdir/bgm/stage3boss.bgm deleted file mode 100644 index fcaaf800..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage3boss.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Lightningbug ~ Lightning Heart -artist = Tuck V -loop = res/bgm/stage3/boss.opus -loop_point = 1.500000 -comment = Wriggle Nightbug’s theme.\n\nA very motivated bug. She’s at the top of the world! — No, someone else is, she’s in the middle of a tunnel. Regardless, she knows she can do anything if she puts her mind to it, right?\n\nSounds a little “beach”-like in the introduction… diff --git a/resources/00-taisei.pkgdir/bgm/stage3/boss.opus b/resources/00-taisei.pkgdir/bgm/stage3boss.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage3/boss.opus rename to resources/00-taisei.pkgdir/bgm/stage3boss.opus index d01a3bf9..d8810747 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage3/boss.opus and b/resources/00-taisei.pkgdir/bgm/stage3boss.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage4.bgm b/resources/00-taisei.pkgdir/bgm/stage4.bgm deleted file mode 100644 index fdaf698b..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage4.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Evidence of Ki Domestication -artist = Tuck V -loop = res/bgm/stage4/stage.opus -loop_point = 32.470590 -comment = The fourth stage’s theme.\n\nAnother trip back into the orient… like a pendulum swinging back and forth.\n\nThis theme draws inspiration from a particularly elusive musician living on the other side of the sea known for the Chinese influences in his music.\n\nSmells like rust. diff --git a/resources/00-taisei.pkgdir/bgm/stage4/stage.opus b/resources/00-taisei.pkgdir/bgm/stage4.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage4/stage.opus rename to resources/00-taisei.pkgdir/bgm/stage4.opus index 0a3a5eca..3e9de6b8 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage4/stage.opus and b/resources/00-taisei.pkgdir/bgm/stage4.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage4boss.bgm b/resources/00-taisei.pkgdir/bgm/stage4boss.bgm deleted file mode 100644 index 394764c2..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage4boss.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Marginal Red ~ The Red Word -artist = Tuck V -loop = res/bgm/stage4/boss.opus -loop_point = 24.000000 -comment = Kurumi’s theme.\n\nOld faces and new dreams. She’s taken a new residence, something a little more comfortable than a Lake of Blood, but this confrontation is anything but. A mix of fears make the walls feel like they’re closing in. diff --git a/resources/00-taisei.pkgdir/bgm/stage4/boss.opus b/resources/00-taisei.pkgdir/bgm/stage4boss.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage4/boss.opus rename to resources/00-taisei.pkgdir/bgm/stage4boss.opus index 9865b639..5eed3339 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage4/boss.opus and b/resources/00-taisei.pkgdir/bgm/stage4boss.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage5.bgm b/resources/00-taisei.pkgdir/bgm/stage5.bgm deleted file mode 100644 index ed5cb08f..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage5.bgm +++ /dev/null @@ -1,4 +0,0 @@ -title = Speak Not Falsely ~ Excitation Field -artist = Tuck V -loop = res/bgm/stage5/stage.opus -comment = The fifth stage’s theme.\n\nAnd back again… how did that song go? “No reason to get excited…”\n\nBut in honesty, this is a combination of Eastern and Western influences now. If it were like a pendulum up to this point, then this theme is like an oscillating electric current.\n\nThose who climb risk a long fall, so look to the top and don’t make a single dishonest step. diff --git a/resources/00-taisei.pkgdir/bgm/stage5/stage.opus b/resources/00-taisei.pkgdir/bgm/stage5.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage5/stage.opus rename to resources/00-taisei.pkgdir/bgm/stage5.opus index a1cefcd4..eb3e8948 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage5/stage.opus and b/resources/00-taisei.pkgdir/bgm/stage5.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage5boss.bgm b/resources/00-taisei.pkgdir/bgm/stage5boss.bgm deleted file mode 100644 index cded0846..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage5boss.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = A Prayer Worth Three Hundred Coulombs -artist = Tuck V -loop = res/bgm/stage5/boss.opus -loop_point = 12.80 -comment = Iku Nagae’s theme.\n\nThis song went through the wringer. Perhaps that’s appropriate for what the protagonists have to do. Imagine climbing a tower in the middle of a vicious thunderstorm and try not to feel exhausted.\n\nIku provides the first tribulation. diff --git a/resources/00-taisei.pkgdir/bgm/stage5/boss.opus b/resources/00-taisei.pkgdir/bgm/stage5boss.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage5/boss.opus rename to resources/00-taisei.pkgdir/bgm/stage5boss.opus index 4b1cb8e5..a4cb1529 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage5/boss.opus and b/resources/00-taisei.pkgdir/bgm/stage5boss.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage6.bgm b/resources/00-taisei.pkgdir/bgm/stage6.bgm deleted file mode 100644 index 4dc8177e..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage6.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Summit of Revelations -artist = Tuck V -loop = res/bgm/stage6/stage.opus -loop_point = 13.71 -comment = The sixth stage’s theme.\n\nAt the top of the tower, you can see the world, and at what risk?\n\nA grand architectural marvel may take thousands of years to complete… this song was no different! But the end result feels like a great discovery. diff --git a/resources/00-taisei.pkgdir/bgm/stage6/stage.opus b/resources/00-taisei.pkgdir/bgm/stage6.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage6/stage.opus rename to resources/00-taisei.pkgdir/bgm/stage6.opus index d3cd17ad..046137e7 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage6/stage.opus and b/resources/00-taisei.pkgdir/bgm/stage6.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm b/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm deleted file mode 100644 index a059336e..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm +++ /dev/null @@ -1,4 +0,0 @@ -title = Cosmological Battle ~ The Vacuum Catastrophe -artist = Tuck V -loop = res/bgm/stage6/boss_phase1.opus -comment = Elly’s first theme.\n\nAn old friend. I always found myself inspired by the idea of a completely inconsequential character from forgotten times climbing to great heights after fading into obscurity.\n\nShe may have been a lowly gate guard the last time you saw her, but the apple’s fallen on her head. She’s a guard of a different kind of gate now. diff --git a/resources/00-taisei.pkgdir/bgm/stage6/boss_phase1.opus b/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage6/boss_phase1.opus rename to resources/00-taisei.pkgdir/bgm/stage6boss_phase1.opus index 3a4f9ed7..e9197bf0 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage6/boss_phase1.opus and b/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm b/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm deleted file mode 100644 index ffeae6b2..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Deified Emergent Property ~ Ambivalent Soul -artist = Tuck V -loop = res/bgm/stage6/boss_phase2.opus -loop_point = 1.6 -comment = Elly’s second theme.\n\nThe most frightening and wondrous powers of the world remain just out of reach to our mortal minds. But a couple of truly brilliant people just may have found the bridge between our world and theirs…\n\nElly is a new person now, so an arrange wouldn’t speak the truth. diff --git a/resources/00-taisei.pkgdir/bgm/stage6/boss_phase2.opus b/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage6/boss_phase2.opus rename to resources/00-taisei.pkgdir/bgm/stage6boss_phase2.opus index 2c283a9d..5b697b3d 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage6/boss_phase2.opus and b/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.opus differ diff --git a/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm b/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm deleted file mode 100644 index b7a7ffd3..00000000 --- a/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm +++ /dev/null @@ -1,5 +0,0 @@ -title = Immutable Truth -artist = Tuck V -loop = res/bgm/stage6/boss_phase3.opus -loop_point = 35.64 -comment = Elly’s last spell.\n\nThe most ambitious and impossibly necessary tasks must encapsulate absolutely everything. Many think of science as a body of knowledge, a sealed bubble, within which all “great discoveries” have already been achieved, but the contrary could not be more true. Science is an unending series of dark curtains to pull back, each new finding more magnificent and imperceptible than the last. diff --git a/resources/00-taisei.pkgdir/bgm/stage6/boss_phase3.opus b/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.opus similarity index 99% rename from resources/00-taisei.pkgdir/bgm/stage6/boss_phase3.opus rename to resources/00-taisei.pkgdir/bgm/stage6boss_phase3.opus index 95395961..19b57029 100644 Binary files a/resources/00-taisei.pkgdir/bgm/stage6/boss_phase3.opus and b/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.opus differ diff --git a/src/audio/audio.c b/src/audio/audio.c index 9097df0d..54a785f0 100644 --- a/src/audio/audio.c +++ b/src/audio/audio.c @@ -17,9 +17,6 @@ #define B (_a_backend.funcs) -CurrentBGM current_bgm = { .name = NULL }; - -static char *saved_bgm; static ht_str2int_t sfx_volumes; static struct enqueued_sound { @@ -30,8 +27,12 @@ static struct enqueued_sound { bool replace; } *sound_queue; -static SoundID play_sound_internal(const char *name, bool is_ui, int cooldown, bool replace, int delay) { - if(!audio_output_works() || global.frameskip) { +static bool is_skip_mode(void) { + return global.frameskip || stage_is_skip_mode(); +} + +static SFXPlayID play_sound_internal(const char *name, bool is_ui, int cooldown, bool replace, int delay) { + if(!audio_output_works() || is_skip_mode()) { return 0; } @@ -45,25 +46,21 @@ static SoundID play_sound_internal(const char *name, bool is_ui, int cooldown, b return 0; } - if(taisei_is_skip_mode_enabled()) { + SFX *sfx = res_sfx(name); + + if(!sfx || (!is_ui && sfx->lastplayframe + 3 + cooldown >= global.frames)) { return 0; } - Sound *snd = res_sfx(name); + sfx->lastplayframe = global.frames; - if(!snd || (!is_ui && snd->lastplayframe + 3 + cooldown >= global.frames)) { - return 0; - } - - snd->lastplayframe = global.frames; - - AudioBackendSoundGroup group = is_ui ? SNDGROUP_UI : SNDGROUP_MAIN; - SoundID sid; + AudioBackendSFXGroup group = is_ui ? SFXGROUP_UI : SFXGROUP_MAIN; + SFXPlayID sid; if(replace) { - sid = B.sound_play_or_restart(snd->impl, group); + sid = B.sfx_play_or_restart(sfx->impl, group); } else { - sid = B.sound_play(snd->impl, group); + sid = B.sfx_play(sfx->impl, group); } return sid; @@ -77,7 +74,7 @@ static void* discard_enqueued_sound(List **queue, List *vsnd, void *arg) { } static void* play_enqueued_sound(struct enqueued_sound **queue, struct enqueued_sound *snd, void *arg) { - if(!taisei_is_skip_mode_enabled()) { + if(!is_skip_mode()) { play_sound_internal(snd->name, false, snd->cooldown, snd->replace, 0); } @@ -86,82 +83,82 @@ static void* play_enqueued_sound(struct enqueued_sound **queue, struct enqueued_ return NULL; } -SoundID play_sound(const char *name) { +SFXPlayID play_sfx(const char *name) { return play_sound_internal(name, false, 0, false, 0); } -SoundID play_sound_ex(const char *name, int cooldown, bool replace) { +SFXPlayID play_sfx_ex(const char *name, int cooldown, bool replace) { return play_sound_internal(name, false, cooldown, replace, 0); } -void play_sound_delayed(const char *name, int cooldown, bool replace, int delay) { +void play_sfx_delayed(const char *name, int cooldown, bool replace, int delay) { play_sound_internal(name, false, cooldown, replace, delay); } -void play_ui_sound(const char *name) { +void play_sfx_ui(const char *name) { play_sound_internal(name, true, 0, true, 0); } -void stop_sound(SoundID sid) { +void stop_sound(SFXPlayID sid) { if(sid) { - B.sound_stop_id(sid); + B.sfx_stop_id(sid); } } -void replace_sound(SoundID sid, const char *name) { +void replace_sfx(SFXPlayID sid, const char *name) { stop_sound(sid); - play_sound(name); + play_sfx(name); } -void play_loop(const char *name) { - if(!audio_output_works() || global.frameskip) { +void play_sfx_loop(const char *name) { + if(!audio_output_works() || is_skip_mode()) { return; } - Sound *snd = res_sfx(name); + SFX *sfx = res_sfx(name); - if(!snd) { + if(!sfx) { return; } - if(!snd->islooping) { - B.sound_loop(snd->impl, SNDGROUP_MAIN); - snd->islooping = true; + if(!sfx->islooping) { + B.sfx_loop(sfx->impl, SFXGROUP_MAIN); + sfx->islooping = true; } - if(snd->islooping == LS_LOOPING) { - snd->lastplayframe = global.frames; + if(sfx->islooping == LS_LOOPING) { + sfx->lastplayframe = global.frames; } } static void* reset_sounds_callback(const char *name, Resource *res, void *arg) { bool reset = (intptr_t)arg; - Sound *snd = res->data; + SFX *sfx = res->data; - if(snd) { + if(sfx) { if(reset) { - snd->lastplayframe = 0; + sfx->lastplayframe = 0; } - if(snd->islooping && (global.frames > snd->lastplayframe + LOOPTIMEOUTFRAMES || reset)) { - B.sound_stop_loop(snd->impl); - snd->islooping = LS_FADEOUT; + if(sfx->islooping && (global.frames > sfx->lastplayframe + LOOPTIMEOUTFRAMES || reset)) { + B.sfx_stop_loop(sfx->impl, SFXGROUP_MAIN); + sfx->islooping = LS_FADEOUT; } - if(snd->islooping && (global.frames > snd->lastplayframe + LOOPTIMEOUTFRAMES + LOOPFADEOUT*60/1000. || reset)) { - snd->islooping = LS_OFF; + if(sfx->islooping && (global.frames > sfx->lastplayframe + LOOPTIMEOUTFRAMES + LOOPFADEOUT*60/1000. || reset)) { + sfx->islooping = LS_OFF; } } return NULL; } -void reset_sounds(void) { +void reset_all_sfx(void) { resource_for_each(RES_SFX, reset_sounds_callback, (void*)true); list_foreach(&sound_queue, discard_enqueued_sound, NULL); } -void update_sounds(void) { +void update_all_sfx(void) { resource_for_each(RES_SFX, reset_sounds_callback, (void*)false); for(struct enqueued_sound *s = sound_queue, *next; s; s = next) { @@ -173,16 +170,16 @@ void update_sounds(void) { } } -void pause_sounds(void) { - B.sound_pause_all(SNDGROUP_MAIN); +void pause_all_sfx(void) { + B.sfx_pause_all(SFXGROUP_MAIN); } -void resume_sounds(void) { - B.sound_resume_all(SNDGROUP_MAIN); +void resume_all_sfx(void) { + B.sfx_resume_all(SFXGROUP_MAIN); } -void stop_sounds(void) { - B.sound_stop_all(SNDGROUP_MAIN); +void stop_all_sfx(void) { + B.sfx_stop_all(SFXGROUP_MAIN); } static bool store_sfx_volume(const char *key, const char *val, void *data) { @@ -207,118 +204,17 @@ static void load_config_files(void) { parse_keyvalue_file_cb(SFX_PATH_PREFIX "volumes.conf", store_sfx_volume, NULL); } -static inline char* get_bgm_title(char *name) { - MusicMetadata *meta = get_resource_data(RES_BGM_METADATA, name, RESF_OPTIONAL); - return meta ? meta->title : NULL; -} - int get_default_sfx_volume(const char *sfx) { return ht_get(&sfx_volumes, sfx, DEFAULT_SFX_VOLUME); } -void resume_bgm(void) { - start_bgm(current_bgm.name); // In most cases it just unpauses existing music. -} - -static void stop_bgm_internal(bool pause, double fadetime) { - if(!current_bgm.name) { - return; - } - - current_bgm.started_at = -1; - - if(!pause) { - stralloc(¤t_bgm.name, NULL); - } - - if(B.music_is_playing() && !B.music_is_paused()) { - if(pause) { - B.music_pause(); - log_debug("BGM paused"); - } else if(fadetime > 0) { - B.music_fade(fadetime); - log_debug("BGM fading out"); - } else { - B.music_stop(); - log_debug("BGM stopped"); - } - } else { - log_debug("No BGM was playing"); - } -} - -void stop_bgm(bool force) { - stop_bgm_internal(!force, false); -} - -void fade_bgm(double fadetime) { - stop_bgm_internal(false, fadetime); -} - -void save_bgm(void) { - // XXX: this is broken - stralloc(&saved_bgm, current_bgm.name); -} - -void restore_bgm(void) { - // XXX: this is broken - start_bgm(saved_bgm); - free(saved_bgm); - saved_bgm = NULL; -} - -void start_bgm(const char *name) { - if(!name || !*name) { - stop_bgm(false); - return; - } - - // if BGM has changed, change it and start from beginning - if(!current_bgm.name || strcmp(name, current_bgm.name)) { - B.music_stop(); - - stralloc(¤t_bgm.name, name); - - if((current_bgm.music = res_bgm(name)) == NULL) { - log_warn("BGM '%s' does not exist", current_bgm.name); - stop_bgm(true); - free(current_bgm.name); - current_bgm.name = NULL; - return; - } - } - - if(B.music_is_paused()) { - B.music_resume(); - } - - if(B.music_is_playing()) { - return; - } - - if(!B.music_play(current_bgm.music->impl)) { - return; - } - - // Support drawing BGM title in game loop (only when music changed!) - if((current_bgm.title = get_bgm_title(current_bgm.name)) != NULL) { - current_bgm.started_at = global.frames; - // Boss BGM title color may differ from the one at beginning of stage - current_bgm.isboss = strendswith(current_bgm.name, "boss"); - } else { - current_bgm.started_at = -1; - } - - log_info("Started %s", (current_bgm.title ? current_bgm.title : current_bgm.name)); -} - static bool audio_config_updated(SDL_Event *evt, void *arg) { if(config_get_int(CONFIG_MUTE_AUDIO)) { - B.sound_set_global_volume(0.0); - B.music_set_global_volume(0.0); + B.sfx_set_global_volume(0.0); + B.bgm_set_global_volume(0.0); } else { - B.sound_set_global_volume(config_get_float(CONFIG_SFX_VOLUME)); - B.music_set_global_volume(config_get_float(CONFIG_BGM_VOLUME)); + B.sfx_set_global_volume(config_get_float(CONFIG_SFX_VOLUME)); + B.bgm_set_global_volume(config_get_float(CONFIG_BGM_VOLUME)); } return false; @@ -343,6 +239,89 @@ bool audio_output_works(void) { return B.output_works(); } -void audio_music_set_position(double pos) { - B.music_set_position(pos); +double audioutil_loopaware_position(double rt_pos, double duration, double loop_start) { + loop_start = clamp(loop_start, 0, duration); + + if(rt_pos < loop_start) { + return fmax(0, rt_pos); + } + + return fmod(rt_pos - loop_start, duration - loop_start) + loop_start; +} + +bool audio_bgm_play(BGM *bgm, bool loop, double position, double fadein) { + BGM *current = B.bgm_current(); + + if(current == bgm) { + return false; + } + + return audio_bgm_play_or_restart(bgm, loop, position, fadein); +} + +bool audio_bgm_play_or_restart(BGM *bgm, bool loop, double position, double fadein) { + if(bgm == NULL) { + return B.bgm_stop(0); + } + + if(B.bgm_play(bgm, loop, position, fadein)) { + assert(B.bgm_current() == bgm); + events_emit(TE_AUDIO_BGM_STARTED, 0, bgm, NULL); + return true; + } + + return false; +} + +bool audio_bgm_stop(double fadeout) { + return B.bgm_stop(fadeout); +} + +bool audio_bgm_pause(void) { + return B.bgm_pause(); +} + +bool audio_bgm_resume(void) { + return B.bgm_resume(); +} + +double audio_bgm_seek(double pos) { + return B.bgm_seek(pos); +} + +double audio_bgm_seek_realtime(double rtpos) { + BGM *bgm = audio_bgm_current(); + + if(bgm == NULL) { + log_warn("No BGM is playing"); + return -1; + } + + double seekpos; + double duration = bgm_get_duration(bgm); + + if(audio_bgm_looping()) { + double loop_start = bgm_get_loop_start(bgm); + seekpos = audioutil_loopaware_position(rtpos, duration, loop_start); + } else { + seekpos = clamp(rtpos, 0, duration); + } + + return audio_bgm_seek(seekpos); +} + +double audio_bgm_tell(void) { + return B.bgm_tell(); +} + +BGMStatus audio_bgm_status(void) { + return B.bgm_status(); +} + +bool audio_bgm_looping(void) { + return B.bgm_looping(); +} + +BGM *audio_bgm_current(void) { + return B.bgm_current(); } diff --git a/src/audio/audio.h b/src/audio/audio.h index 40412807..fadd7270 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -13,15 +13,13 @@ #include "resource/sfx.h" #include "resource/bgm.h" -#include "resource/bgm_metadata.h" #define LOOPTIMEOUTFRAMES 10 #define DEFAULT_SFX_VOLUME 100 #define LOOPFADEOUT 50 -typedef struct MusicImpl MusicImpl; -typedef struct SoundImpl SoundImpl; +typedef struct SFXImpl SFXImpl; typedef enum { LS_OFF, @@ -29,57 +27,66 @@ typedef enum { LS_FADEOUT, } LoopState; -typedef struct Sound { - int lastplayframe; +typedef struct SFX { + SFXImpl *impl; LoopState islooping; - void *impl; -} Sound; + int lastplayframe; +} SFX; -typedef struct Music { - MusicImpl *impl; - MusicMetadata *meta; -} Music; +typedef enum BGMStatus { + BGM_STOPPED, + BGM_PLAYING, + BGM_PAUSED, +} BGMStatus; -typedef struct CurrentBGM { - char *name; - char *title; - int isboss; - int started_at; - Music *music; -} CurrentBGM; - -typedef uint64_t SoundID; - -extern CurrentBGM current_bgm; +typedef uint64_t SFXPlayID; void audio_init(void); void audio_shutdown(void); bool audio_output_works(void); -void audio_music_set_position(double pos); -SoundID play_sound(const char *name) attr_nonnull(1); -SoundID play_sound_ex(const char *name, int cooldown, bool replace) attr_nonnull(1); -void play_sound_delayed(const char *name, int cooldown, bool replace, int delay) attr_nonnull(1); -void play_loop(const char *name) attr_nonnull(1); -void play_ui_sound(const char *name) attr_nonnull(1); -void stop_sound(SoundID sid); -void replace_sound(SoundID sid, const char *name) attr_nonnull(2); -void reset_sounds(void); -void pause_sounds(void); -void resume_sounds(void); -void stop_sounds(void); -void update_sounds(void); // checks if loops need to be stopped +bool audio_bgm_play(BGM *bgm, bool loop, double position, double fadein); +bool audio_bgm_play_or_restart(BGM *bgm, bool loop, double position, double fadein); +bool audio_bgm_stop(double fadeout); +bool audio_bgm_pause(void); +bool audio_bgm_resume(void); +double audio_bgm_seek(double pos); +double audio_bgm_seek_realtime(double rtpos); +double audio_bgm_tell(void); +BGMStatus audio_bgm_status(void); +bool audio_bgm_looping(void); +BGM *audio_bgm_current(void); + +// TODO modernize sfx API + +SFXPlayID play_sfx(const char *name) attr_nonnull(1); +SFXPlayID play_sfx_ex(const char *name, int cooldown, bool replace) attr_nonnull(1); +void play_sfx_delayed(const char *name, int cooldown, bool replace, int delay) attr_nonnull(1); +void play_sfx_loop(const char *name) attr_nonnull(1); +void play_sfx_ui(const char *name) attr_nonnull(1); +void stop_sound(SFXPlayID sid); +void replace_sfx(SFXPlayID sid, const char *name) attr_nonnull(2); +void reset_all_sfx(void); +void pause_all_sfx(void); +void resume_all_sfx(void); +void stop_all_sfx(void); +void update_all_sfx(void); // checks if loops need to be stopped int get_default_sfx_volume(const char *sfx); -DEFINE_DEPRECATED_RESOURCE_GETTER(Sound, get_sound, res_sfx) -DEFINE_DEPRECATED_RESOURCE_GETTER(Music, get_music, res_bgm) +DEFINE_DEPRECATED_RESOURCE_GETTER(SFX, get_sound, res_sfx) +DEFINE_DEPRECATED_RESOURCE_GETTER(BGM, get_music, res_bgm) -void start_bgm(const char *name); -void stop_bgm(bool force); -void fade_bgm(double fadetime); -void resume_bgm(void); -void save_bgm(void); // XXX: this is broken -void restore_bgm(void); // XXX: this is broken +attr_deprecated("Use play_sfx() instead") +INLINE SFXPlayID play_sound(const char *name) { + return play_sfx(name); +} + +attr_deprecated("Use play_sfx_ex() instead") attr_nonnull(1) +INLINE SFXPlayID play_sound_ex(const char *name, int cooldown, bool replace) { + return play_sfx_ex(name, cooldown, replace); +} + +double audioutil_loopaware_position(double rt_pos, double duration, double loop_start); #endif // IGUARD_audio_audio_h diff --git a/src/audio/backend.h b/src/audio/backend.h index 601b5aea..0e08051f 100644 --- a/src/audio/backend.h +++ b/src/audio/backend.h @@ -14,42 +14,57 @@ #include "audio.h" typedef enum { - SNDGROUP_ALL, - SNDGROUP_MAIN, - SNDGROUP_UI, -} AudioBackendSoundGroup; + SFXGROUP_ALL, + SFXGROUP_MAIN, + SFXGROUP_UI, +} AudioBackendSFXGroup; + +typedef struct AudioBGMObjectFuncs { + const char *(*get_title)(BGM *bgm); + const char *(*get_artist)(BGM *bgm); + const char *(*get_comment)(BGM *bgm); + double (*get_duration)(BGM *bgm); + double (*get_loop_start)(BGM *bgm); +} AudioBGMObjectFuncs; + +typedef struct AudioSFXObjectFuncs { + bool (*set_volume)(SFXImpl *sfx, double vol); +} AudioSFXObjectFuncs; typedef struct AudioFuncs { bool (*init)(void); - bool (*music_fade)(double fadetime); - bool (*music_is_paused)(void); - bool (*music_is_playing)(void); - bool (*music_pause)(void); - bool (*music_play)(MusicImpl *mus); - bool (*music_resume)(void); - bool (*music_set_global_volume)(double gain); - bool (*music_set_loop_point)(MusicImpl *mus, double pos); - bool (*music_set_position)(double pos); - bool (*music_stop)(void); + bool (*bgm_play)(BGM *bgm, bool loop, double position, double fadein); + bool (*bgm_stop)(double fadeout); + bool (*bgm_pause)(void); + bool (*bgm_resume)(void); + double (*bgm_seek)(double pos); + double (*bgm_tell)(void); + BGMStatus (*bgm_status)(void); + BGM *(*bgm_current)(void); + bool (*bgm_looping)(void); + bool (*bgm_set_global_volume)(double gain); bool (*output_works)(void); - bool (*set_sfx_volume)(float gain); bool (*shutdown)(void); - SoundID (*sound_loop)(SoundImpl *snd, AudioBackendSoundGroup group); - bool (*sound_pause_all)(AudioBackendSoundGroup group); - SoundID (*sound_play)(SoundImpl *snd, AudioBackendSoundGroup group); - SoundID (*sound_play_or_restart)(SoundImpl *snd, AudioBackendSoundGroup group); - bool (*sound_resume_all)(AudioBackendSoundGroup group); - bool (*sound_set_global_volume)(double gain); - bool (*sound_set_volume)(SoundImpl *snd, double vol); - bool (*sound_stop_all)(AudioBackendSoundGroup group); - bool (*sound_stop_loop)(SoundImpl *snd); - bool (*sound_stop_id)(SoundID); - const char* const* (*get_supported_music_exts)(uint *out_numexts); - const char* const* (*get_supported_sound_exts)(uint *out_numexts); - MusicImpl* (*music_load)(const char *vfspath); - SoundImpl* (*sound_load)(const char *vfspath); - void (*music_unload)(MusicImpl *mus); - void (*sound_unload)(SoundImpl *snd); + SFXPlayID (*sfx_loop)(SFXImpl *sfx, AudioBackendSFXGroup group); + bool (*sfx_pause_all)(AudioBackendSFXGroup group); + SFXPlayID (*sfx_play)(SFXImpl *sfx, AudioBackendSFXGroup group); + SFXPlayID (*sfx_play_or_restart)(SFXImpl *sfx, AudioBackendSFXGroup group); + bool (*sfx_resume_all)(AudioBackendSFXGroup group); + bool (*sfx_set_global_volume)(double gain); + bool (*sfx_stop_all)(AudioBackendSFXGroup group); + bool (*sfx_stop_loop)(SFXImpl *sfx, AudioBackendSFXGroup group); + bool (*sfx_stop_id)(SFXPlayID); + const char *const *(*get_supported_bgm_exts)(uint *out_numexts); + const char *const *(*get_supported_sfx_exts)(uint *out_numexts); + BGM *(*bgm_load)(const char *vfspath); + SFXImpl *(*sfx_load)(const char *vfspath); + void (*bgm_unload)(BGM *bgm); + void (*sfx_unload)(SFXImpl *sfx); + + struct { + AudioBGMObjectFuncs bgm; + AudioSFXObjectFuncs sfx; + } object; } AudioFuncs; typedef struct AudioBackend { diff --git a/src/audio/meson.build b/src/audio/meson.build index e61c75ea..f58f753f 100644 --- a/src/audio/meson.build +++ b/src/audio/meson.build @@ -1,6 +1,10 @@ default_backend = get_option('a_default') +if default_backend == 'sdl2mixer' + error('Your build directory is outdated. Try running the following command:\n\n$ meson --wipe @0@ @1@ -Da_default=sdl\n'.format(meson.source_root(), meson.build_root())) +endif + if not get_option('a_@0@'.format(default_backend)) error('Default audio backend \'@0@\' is not enabled. Enable it with -Da_@0@=true, or set a_default to something else.'.format(default_backend)) endif @@ -10,35 +14,32 @@ audio_src = files( 'backend.c', ) -audio_deps = [] -enabled_audio_backends = [] - -# NOTE: Order matters here. -subdir('sdl2mixer') -subdir('null') - modules = [ - 'sdl2mixer', + 'sdl', 'null', ] +audio_deps = [] +enabled_audio_backends = [] included_deps = [] needed_deps = [] a_macro = [] foreach m : modules if get_option('a_@0@'.format(m)) - audio_src += get_variable('a_@0@_src'.format(m)) - a_macro += ['A(@0@)'.format(m)] - enabled_audio_backends += [m] - needed_deps += get_variable('a_@0@_deps'.format(m)) + subdir(m) included_deps += [m] + enabled_audio_backends += [m] + a_macro += ['A(@0@)'.format(m)] + audio_src += get_variable('a_@0@_src'.format(m)) + needed_deps += get_variable('a_@0@_deps'.format(m)) audio_deps += get_variable('a_@0@_libdeps'.format(m)) endif endforeach foreach dep : needed_deps if not included_deps.contains(dep) + subdir(dep) included_deps += [dep] audio_src += get_variable('a_@0@_src'.format(dep)) audio_deps += get_variable('a_@0@_libdeps'.format(dep)) diff --git a/src/audio/null/audio_null.c b/src/audio/null/audio_null.c index 255c569a..0a9898fb 100644 --- a/src/audio/null/audio_null.c +++ b/src/audio/null/audio_null.c @@ -19,68 +19,88 @@ static bool audio_null_init(void) { return true; } -#define FAKE_SOUND_ID ((SoundID)1) +#define FAKE_SOUND_ID ((SFXPlayID)1) -static bool audio_null_music_fade(double fadetime) { return true; } -static bool audio_null_music_is_paused(void) { return false; } -static bool audio_null_music_is_playing(void) { return false; } -static bool audio_null_music_pause(void) { return true; } -static bool audio_null_music_play(MusicImpl *impl) { return true; } -static bool audio_null_music_resume(void) { return true; } -static bool audio_null_music_set_global_volume(double gain) { return true; } -static bool audio_null_music_set_loop_point(MusicImpl *impl, double pos) { return true; } -static bool audio_null_music_set_position(double pos) { return true; } -static bool audio_null_music_stop(void) { return true; } +static BGM *audio_null_bgm_current(void) { return NULL; } +static double audio_null_bgm_seek(double pos) { return -1; } +static double audio_null_bgm_tell(void) { return -1; } +static BGMStatus audio_null_bgm_status(void) { return BGM_STOPPED; } +static bool audio_null_bgm_looping(void) { return false; } +static bool audio_null_bgm_pause(void) { return false; } +static bool audio_null_bgm_play(BGM *bgm, bool loop, double pos, double fadein) { return false; } +static bool audio_null_bgm_resume(void) { return false; } +static bool audio_null_bgm_set_global_volume(double gain) { return false; } +static bool audio_null_bgm_stop(double fadeout) { return false; } static bool audio_null_output_works(void) { return false; } static bool audio_null_shutdown(void) { return true; } -static SoundID audio_null_sound_loop(SoundImpl *impl, AudioBackendSoundGroup group) { return FAKE_SOUND_ID; } -static bool audio_null_sound_pause_all(AudioBackendSoundGroup group) { return true; } -static SoundID audio_null_sound_play(SoundImpl *impl, AudioBackendSoundGroup group) { return FAKE_SOUND_ID; } -static SoundID audio_null_sound_play_or_restart(SoundImpl *impl, AudioBackendSoundGroup group) { return FAKE_SOUND_ID; } -static bool audio_null_sound_resume_all(AudioBackendSoundGroup group) { return true; } -static bool audio_null_sound_set_global_volume(double gain) { return true; } -static bool audio_null_sound_set_volume(SoundImpl *snd, double gain) { return true; } -static bool audio_null_sound_stop_all(AudioBackendSoundGroup group) { return true; } -static bool audio_null_sound_stop_loop(SoundImpl *impl) { return true; } -static bool audio_null_sound_stop_id(SoundID sid) { return true; } +static SFXPlayID audio_null_sfx_loop(SFXImpl *impl, AudioBackendSFXGroup group) { return FAKE_SOUND_ID; } +static bool audio_null_sfx_pause_all(AudioBackendSFXGroup group) { return true; } +static SFXPlayID audio_null_sfx_play(SFXImpl *impl, AudioBackendSFXGroup group) { return FAKE_SOUND_ID; } +static SFXPlayID audio_null_sfx_play_or_restart(SFXImpl *impl, AudioBackendSFXGroup group) { return FAKE_SOUND_ID; } +static bool audio_null_sfx_resume_all(AudioBackendSFXGroup group) { return true; } +static bool audio_null_sfx_set_global_volume(double gain) { return true; } +static bool audio_null_sfx_stop_all(AudioBackendSFXGroup group) { return true; } +static bool audio_null_sfx_stop_loop(SFXImpl *impl, AudioBackendSFXGroup group) { return true; } +static bool audio_null_sfx_stop_id(SFXPlayID sid) { return true; } static const char* const* audio_null_get_supported_exts(uint *out_numexts) { *out_numexts = 0; return NULL; } -static MusicImpl* audio_null_music_load(const char *vfspath) { return NULL; } -static SoundImpl* audio_null_sound_load(const char *vfspath) { return NULL; } -static void audio_null_music_unload(MusicImpl *mus) { } -static void audio_null_sound_unload(SoundImpl *snd) { } +static BGM *audio_null_bgm_load(const char *vfspath) { return NULL; } +static SFXImpl *audio_null_sfx_load(const char *vfspath) { return NULL; } +static void audio_null_bgm_unload(BGM *mus) { } +static void audio_null_sfx_unload(SFXImpl *snd) { } + +static const char *audio_null_obj_bgm_get_title(BGM *bgm) { return NULL; } +static const char *audio_null_obj_bgm_get_artist(BGM *bgm) { return NULL; } +static const char *audio_null_obj_bgm_get_comment(BGM *bgm) { return NULL; } +static double audio_null_obj_bgm_get_duration(BGM *bgm) { return 1; } +static double audio_null_obj_bgm_get_loop_start(BGM *bgm) { return 0; } + +static bool audio_null_obj_sfx_set_volume(SFXImpl *impl, double gain) { return true; } AudioBackend _a_backend_null = { .name = "null", .funcs = { - .get_supported_music_exts = audio_null_get_supported_exts, - .get_supported_sound_exts = audio_null_get_supported_exts, + .bgm_current = audio_null_bgm_current, + .bgm_load = audio_null_bgm_load, + .bgm_looping = audio_null_bgm_looping, + .bgm_pause = audio_null_bgm_pause, + .bgm_play = audio_null_bgm_play, + .bgm_resume = audio_null_bgm_resume, + .bgm_seek = audio_null_bgm_seek, + .bgm_set_global_volume = audio_null_bgm_set_global_volume, + .bgm_status = audio_null_bgm_status, + .bgm_stop = audio_null_bgm_stop, + .bgm_tell = audio_null_bgm_tell, + .bgm_unload = audio_null_bgm_unload, + .get_supported_bgm_exts = audio_null_get_supported_exts, + .get_supported_sfx_exts = audio_null_get_supported_exts, .init = audio_null_init, - .music_fade = audio_null_music_fade, - .music_is_paused = audio_null_music_is_paused, - .music_is_playing = audio_null_music_is_playing, - .music_load = audio_null_music_load, - .music_pause = audio_null_music_pause, - .music_play = audio_null_music_play, - .music_resume = audio_null_music_resume, - .music_set_global_volume = audio_null_music_set_global_volume, - .music_set_loop_point = audio_null_music_set_loop_point, - .music_set_position = audio_null_music_set_position, - .music_stop = audio_null_music_stop, - .music_unload = audio_null_music_unload, .output_works = audio_null_output_works, + .sfx_load = audio_null_sfx_load, + .sfx_loop = audio_null_sfx_loop, + .sfx_pause_all = audio_null_sfx_pause_all, + .sfx_play = audio_null_sfx_play, + .sfx_play_or_restart = audio_null_sfx_play_or_restart, + .sfx_resume_all = audio_null_sfx_resume_all, + .sfx_set_global_volume = audio_null_sfx_set_global_volume, + .sfx_stop_all = audio_null_sfx_stop_all, + .sfx_stop_id = audio_null_sfx_stop_id, + .sfx_stop_loop = audio_null_sfx_stop_loop, + .sfx_unload = audio_null_sfx_unload, .shutdown = audio_null_shutdown, - .sound_load = audio_null_sound_load, - .sound_loop = audio_null_sound_loop, - .sound_pause_all = audio_null_sound_pause_all, - .sound_play = audio_null_sound_play, - .sound_play_or_restart = audio_null_sound_play_or_restart, - .sound_resume_all = audio_null_sound_resume_all, - .sound_set_global_volume = audio_null_sound_set_global_volume, - .sound_set_volume = audio_null_sound_set_volume, - .sound_stop_all = audio_null_sound_stop_all, - .sound_stop_loop = audio_null_sound_stop_loop, - .sound_stop_id = audio_null_sound_stop_id, - .sound_unload = audio_null_sound_unload, + + .object = { + .bgm = { + .get_title = audio_null_obj_bgm_get_title, + .get_artist = audio_null_obj_bgm_get_artist, + .get_comment = audio_null_obj_bgm_get_comment, + .get_duration = audio_null_obj_bgm_get_duration, + .get_loop_start = audio_null_obj_bgm_get_loop_start, + }, + + .sfx = { + .set_volume = audio_null_obj_sfx_set_volume, + }, + }, } }; diff --git a/src/audio/sdl/audio_sdl.c b/src/audio/sdl/audio_sdl.c new file mode 100644 index 00000000..2a1e00a7 --- /dev/null +++ b/src/audio/sdl/audio_sdl.c @@ -0,0 +1,731 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "../backend.h" +#include "../stream/stream.h" +#include "../stream/stream_pcm.h" +#include "../stream/player.h" + +#include "util.h" +#include "rwops/rwops_autobuf.h" +#include "config.h" + +#define AUDIO_FREQ 48000 +#define AUDIO_FORMAT AUDIO_F32SYS +#define AUDIO_CHANNELS 2 + +#define CHAN_BGM 0 + +#define NUM_BGM_CHANNELS 1 +#define NUM_SFX_MAIN_CHANNELS 28 +#define NUM_SFX_UI_CHANNELS 4 +#define NUM_SFX_CHANNELS (NUM_SFX_MAIN_CHANNELS + NUM_SFX_UI_CHANNELS) + +enum { + FIRST_SFX_GROUP = 0, + + G_SFX_MAIN = FIRST_SFX_GROUP, + G_SFX_UI, + + NUM_SFX_GROUPS, + + G_BGM = NUM_SFX_GROUPS, + + NUM_GROUPS, +}; + +struct SFXImpl { + size_t pcm_size; + AudioStreamSpec spec; + float gain; + + // TODO: maybe implement this tracking in the frontend instead. + // It's needed for things like sfx_stop_loop and sfx_play_or_restart to work, which are hacky at best. + struct { + SFXPlayID last_loop_id; + SFXPlayID last_play_id; + } per_group[NUM_SFX_GROUPS]; + + uint8_t pcm[]; +}; + +struct BGM { + AudioStream stream; +}; + +static_assert(offsetof(BGM, stream) == 0, ""); + +static struct { + StreamPlayer players[NUM_GROUPS]; + + StaticPCMAudioStream sfx_streams[NUM_SFX_CHANNELS]; + uint32_t chan_play_ids[NUM_SFX_CHANNELS]; + uint32_t play_counter; + + AudioStreamSpec spec; + SDL_AudioDeviceID audio_device; + uint8_t silence; +} mixer; + +#define IS_VALID_CHANNEL(chan) ((uint)(chan) < NUM_SFX_CHANNELS) + +// BEGIN MISC + +static void SDLCALL mixer_callback(void *ignore, uint8_t *stream, int len) { + memset(stream, mixer.silence, len); + + for(int i = 0; i < ARRAY_SIZE(mixer.players); ++i) { + StreamPlayer *plr = mixer.players + i; + splayer_process(plr, len, stream); + } +} + +static bool init_sdl_audio(void) { + uint num_drivers = SDL_GetNumAudioDrivers(); + void *buf; + SDL_RWops *out = SDL_RWAutoBuffer(&buf, 256); + + SDL_RWprintf(out, "Available audio drivers:"); + + for(uint i = 0; i < num_drivers; ++i) { + SDL_RWprintf(out, " %s", SDL_GetAudioDriver(i)); + } + + SDL_WriteU8(out, 0); + log_info("%s", (char*)buf); + SDL_RWclose(out); + + if(SDL_InitSubSystem(SDL_INIT_AUDIO)) { + log_sdl_error(LOG_ERROR, "SDL_InitSubSystem"); + return false; + } + + log_info("Using driver '%s'", SDL_GetCurrentAudioDriver()); + return true; +} + +static bool init_audio_device(void) { + SDL_AudioSpec want = { 0 }, have; + want.callback = mixer_callback; + want.channels = AUDIO_CHANNELS; + want.format = AUDIO_FORMAT; + want.freq = AUDIO_FREQ; + want.samples = config_get_int(CONFIG_MIXER_CHUNKSIZE); + + // NOTE: StreamPlayer expects stereo float32 samples. + uint allow_changes = SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE; + mixer.audio_device = SDL_OpenAudioDevice(NULL, false, &want, &have, allow_changes); + + if(mixer.audio_device == 0) { + log_sdl_error(LOG_ERROR, "SDL_OpenAudioDevice"); + return false; + } + + if(have.freq != want.freq || have.format != want.format) { + log_warn( + "Audio device spec doesn't match our request, " + "requested (freq=%i, fmt=0x%x), got (freq=%i, fmt=0x%x). " + "Sound may be distorted.", + want.freq, want.format, have.freq, have.format + ); + abort(); + } + + mixer.spec = astream_spec(have.format, have.channels, have.freq); + mixer.silence = have.silence; + + return true; +} + +static void shutdown_players(void) { + for(int i = 0; i < ARRAY_SIZE(mixer.players); ++i) { + StreamPlayer *plr = mixer.players + i; + + if(plr->channels) { + splayer_shutdown(plr); + memset(plr, 0, sizeof(*plr)); + } + } +} + +static bool init_players(void) { + if(!splayer_init(&mixer.players[G_BGM], NUM_BGM_CHANNELS, &mixer.spec)) { + log_error("splayer_init() failed"); + return false; + } + + if(!splayer_init(&mixer.players[G_SFX_MAIN], NUM_SFX_MAIN_CHANNELS, &mixer.spec)) { + log_error("splayer_init() failed"); + return false; + } + + if(!splayer_init(&mixer.players[G_SFX_UI], NUM_SFX_UI_CHANNELS, &mixer.spec)) { + log_error("splayer_init() failed"); + return false; + } + + return true; +} + +static void init_sfx_streams(void) { + for(int i = 0; i < ARRAY_SIZE(mixer.sfx_streams); ++i) { + astream_pcm_static_init(mixer.sfx_streams + i); + } +} + +static bool audio_sdl_init(void) { + if(!init_sdl_audio()) { + return false; + } + + if(!init_audio_device()) { + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + if(!init_players()) { + shutdown_players(); + SDL_CloseAudioDevice(mixer.audio_device); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + init_sfx_streams(); + + SDL_PauseAudioDevice(mixer.audio_device, false); + log_info("Audio subsystem initialized (SDL)"); + + return true; +} + +static bool audio_sdl_shutdown(void) { + SDL_PauseAudioDevice(mixer.audio_device, true); + shutdown_players(); + SDL_CloseAudioDevice(mixer.audio_device); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + + log_info("Audio subsystem deinitialized (SDL)"); + return true; +} + +static bool audio_sdl_output_works(void) { + return SDL_GetAudioDeviceStatus(mixer.audio_device) == SDL_AUDIO_PLAYING; +} + +static const char *const *audio_sdl_get_supported_exts(uint *numexts) { + // TODO figure this out dynamically + static const char *const exts[] = { ".opus" }; + *numexts = ARRAY_SIZE(exts); + return exts; +} + +INLINE void lock_audio(void) { + SDL_LockAudioDevice(mixer.audio_device); +} + +INLINE void unlock_audio(void) { + SDL_UnlockAudioDevice(mixer.audio_device); +} + +// END MISC + +// BEGIN BGM + +static BGMStatus audio_sdl_bgm_status(void) { + lock_audio(); + BGMStatus status = splayer_util_bgmstatus(&mixer.players[G_BGM], CHAN_BGM); + unlock_audio(); + return status; +} + +static bool audio_sdl_bgm_set_global_volume(double gain) { + lock_audio(); + mixer.players[G_BGM].gain = gain; + unlock_audio(); + return true; +} + +static bool audio_sdl_bgm_play(BGM *bgm, bool loop, double position, double fadein) { + lock_audio(); + bool status = splayer_play(&mixer.players[G_BGM], CHAN_BGM, &bgm->stream, loop, 1, position, fadein); + unlock_audio(); + return status; +} + +static bool audio_sdl_bgm_stop(double fadeout) { + lock_audio(); + + if(splayer_util_bgmstatus(&mixer.players[G_BGM], CHAN_BGM) == BGM_STOPPED) { + unlock_audio(); + log_warn("BGM is already stopped"); + return false; + } + + if(fadeout > 0) { + splayer_fadeout(&mixer.players[G_BGM], CHAN_BGM, fadeout); + } else { + splayer_halt(&mixer.players[G_BGM], CHAN_BGM); + } + + unlock_audio(); + return true; +} + +static bool audio_sdl_bgm_looping(void) { + lock_audio(); + + if(splayer_util_bgmstatus(&mixer.players[G_BGM], CHAN_BGM) == BGM_STOPPED) { + log_warn("BGM is stopped"); + return false; + } + + bool looping = splayer_is_looping(&mixer.players[G_BGM], CHAN_BGM); + unlock_audio(); + + return looping; +} + +static bool audio_sdl_bgm_pause(void) { + lock_audio(); + bool status = splayer_pause(&mixer.players[G_BGM], CHAN_BGM); + unlock_audio(); + return status; +} + +static bool audio_sdl_bgm_resume(void) { + lock_audio(); + bool status = splayer_resume(&mixer.players[G_BGM], CHAN_BGM); + unlock_audio(); + return status; +} + +static double audio_sdl_bgm_seek(double pos) { + lock_audio(); + pos = splayer_seek(&mixer.players[G_BGM], CHAN_BGM, pos); + unlock_audio(); + return pos; +} + +static double audio_sdl_bgm_tell(void) { + lock_audio(); + double pos = splayer_tell(&mixer.players[G_BGM], CHAN_BGM); + unlock_audio(); + return pos; +} + +static BGM *audio_sdl_bgm_current(void) { + BGM *bgm = NULL; + lock_audio(); + + if(splayer_util_bgmstatus(&mixer.players[G_BGM], CHAN_BGM) != BGM_STOPPED) { + bgm = UNION_CAST(AudioStream*, BGM*, mixer.players[G_BGM].channels[CHAN_BGM].stream); + } + + unlock_audio(); + return bgm; +} + +// END BGM + +// BEGIN BGM OBJECT + +static const char *audio_sdl_obj_bgm_get_title(BGM *bgm) { + return astream_get_meta_tag(&bgm->stream, STREAM_META_TITLE); +} + +static const char *audio_sdl_obj_bgm_get_artist(BGM *bgm) { + return astream_get_meta_tag(&bgm->stream, STREAM_META_ARTIST); +} + +static const char *audio_sdl_obj_bgm_get_comment(BGM *bgm) { + return astream_get_meta_tag(&bgm->stream, STREAM_META_COMMENT); +} + +static double audio_sdl_obj_bgm_get_duration(BGM *bgm) { + return astream_util_offset_to_time(&bgm->stream, bgm->stream.length); +} + +static double audio_sdl_obj_bgm_get_loop_start(BGM *bgm) { + return astream_util_offset_to_time(&bgm->stream, bgm->stream.loop_start); +} + +// END BGM OBJECT + +// BEGIN SFX + +static void flatchan_to_groupchan(int fchan, int *out_group, int *out_chan) { + assert(fchan >= 0); + assert(fchan < NUM_SFX_CHANNELS); + + if(fchan >= NUM_SFX_MAIN_CHANNELS) { + *out_group = G_SFX_UI; + *out_chan = fchan - NUM_SFX_MAIN_CHANNELS; + } else { + *out_group = G_SFX_MAIN; + *out_chan = fchan; + } +} + +static int groupchan_to_flatchan(int group, int chan) { + switch(group) { + case G_SFX_MAIN: + return chan; + + case G_SFX_UI: + return chan + NUM_SFX_MAIN_CHANNELS; + + default: + UNREACHABLE; + } +} + +static inline int translate_group(AudioBackendSFXGroup group) { + switch(group) { + case SFXGROUP_MAIN: return G_SFX_MAIN; + case SFXGROUP_UI: return G_SFX_UI; + default: UNREACHABLE; + } +} + +static bool audio_sdl_sfx_set_global_volume(double gain) { + lock_audio(); + mixer.players[G_SFX_MAIN].gain = gain; + mixer.players[G_SFX_UI].gain = gain; + unlock_audio(); + return true; +} + +INLINE SFXPlayID pack_playid(int chan, uint32_t play_id) { + assert(IS_VALID_CHANNEL(chan)); + return ((uint32_t)chan + 1) | ((uint64_t)play_id << 32ull); +} + +INLINE void unpack_playid(SFXPlayID pid, int *chan, uint32_t *play_id) { + int ch = (int)((uint32_t)(pid & 0xffffffff) - 1); + assert(IS_VALID_CHANNEL(ch)); + *chan = ch; + *play_id = pid >> 32ull; +} + +INLINE int get_playid_chan(SFXPlayID play_id) { + if(play_id == 0) { + return -1; + } + + int chan; + uint32_t counter; + unpack_playid(play_id, &chan, &counter); + + if(mixer.chan_play_ids[chan] == counter) { + return chan; + } + + return -1; +} + +static SFXPlayID register_sfx_playback(SFXImpl *sfx, bool loop, int group, int chan, int flat_chan) { + assume(IS_VALID_CHANNEL(flat_chan)); + assert(groupchan_to_flatchan(group, chan) == flat_chan); + + uint32_t cntr = ++mixer.play_counter; + mixer.chan_play_ids[flat_chan] = cntr; + SFXPlayID play_id = pack_playid(flat_chan, cntr); + + if(loop) { + sfx->per_group[group - FIRST_SFX_GROUP].last_loop_id = play_id; + } else { + sfx->per_group[group - FIRST_SFX_GROUP].last_play_id = play_id; + } + + return play_id; +} + +static SFXPlayID play_sfx_on_channel(SFXImpl *sfx, AudioBackendSFXGroup group, int flat_chan, bool loop, double pos, double fadein) { + SFXPlayID id = 0; + + lock_audio(); + + int chan; + int g; + StreamPlayer *plr; + + if(flat_chan >= 0) { + assume(IS_VALID_CHANNEL(flat_chan)); + assume(group == SFXGROUP_ALL); + flatchan_to_groupchan(flat_chan, &g, &chan); + plr = &mixer.players[g]; + } else { + g = translate_group(group); + plr = &mixer.players[g]; + chan = splayer_pick_channel(plr); + flat_chan = groupchan_to_flatchan(g, chan); + } + + StaticPCMAudioStream *stream = &mixer.sfx_streams[flat_chan]; + + if(astream_pcm_reopen(&stream->astream, &mixer.spec, sfx->pcm_size, sfx->pcm, 0)) { + if(splayer_play(plr, chan, &stream->astream, loop, sfx->gain, pos, fadein)) { + id = register_sfx_playback(sfx, loop, g, chan, flat_chan); + } + } + + unlock_audio(); + + return id; +} + +static bool fade_sfx_channel(int flat_chan, int fade_ms) { + int g, chan; + flatchan_to_groupchan(flat_chan, &g, &chan); + StreamPlayer *plr = &mixer.players[g]; + + lock_audio(); + bool result = splayer_fadeout(plr, chan, fade_ms / 1000.0); + unlock_audio(); + + return result; +} + +static SFXPlayID audio_sdl_sfx_play(SFXImpl *sfx, AudioBackendSFXGroup group) { + return play_sfx_on_channel(sfx, group, -1, false, 0, 0); +} + +static SFXPlayID audio_sdl_sfx_play_or_restart(SFXImpl *sfx, AudioBackendSFXGroup group) { + int g = translate_group(group); + int flat_chan = get_playid_chan(sfx->per_group[g - FIRST_SFX_GROUP].last_play_id); + + if(IS_VALID_CHANNEL(flat_chan)) { + return play_sfx_on_channel(sfx, SFXGROUP_ALL, flat_chan, false, 0, 0); + } else { + return audio_sdl_sfx_play(sfx, group); + } +} + +static SFXPlayID audio_sdl_sfx_loop(SFXImpl *sfx, AudioBackendSFXGroup group) { + return play_sfx_on_channel(sfx, group, -1, true, 0, 0); +} + +static bool audio_sdl_sfx_stop_loop(SFXImpl *sfx, AudioBackendSFXGroup group) { + int g = translate_group(group); + int flat_chan = get_playid_chan(sfx->per_group[g - FIRST_SFX_GROUP].last_loop_id); + + if(IS_VALID_CHANNEL(flat_chan)) { + return fade_sfx_channel(flat_chan, LOOPFADEOUT); + } + + return false; +} + +static bool audio_sdl_sfx_stop_id(SFXPlayID play_id) { + int flat_chan = get_playid_chan(play_id); + + if(IS_VALID_CHANNEL(flat_chan)) { + return fade_sfx_channel(flat_chan, LOOPFADEOUT * 3); + } + + return false; +} + +static bool audio_sdl_sfx_pause_all(AudioBackendSFXGroup group) { + int g = translate_group(group); + StreamPlayer *plr = &mixer.players[g]; + + lock_audio(); + bool result = splayer_global_pause(plr); + unlock_audio(); + + return result; +} + +static bool audio_sdl_sfx_resume_all(AudioBackendSFXGroup group) { + int g = translate_group(group); + StreamPlayer *plr = &mixer.players[g]; + + lock_audio(); + bool result = splayer_global_resume(plr); + unlock_audio(); + + return result; +} + +static bool audio_sdl_sfx_stop_all(AudioBackendSFXGroup group) { + int g = translate_group(group); + StreamPlayer *plr = &mixer.players[g]; + + lock_audio(); + + for(int i = 0; i < plr->num_channels; ++i) { + splayer_halt(plr, i); + } + + unlock_audio(); + + return true; +} + +// END SFX + +// BEGIN SFX OBJECT + +static bool audio_sdl_obj_sfx_set_volume(SFXImpl *sfx, double vol) { + sfx->gain = vol; + return true; +} + +// END SFX OBJECT + +// BEGIN LOAD + +static BGM *audio_sdl_bgm_load(const char *vfspath) { + SDL_RWops *rw = vfs_open(vfspath, VFS_MODE_READ | VFS_MODE_SEEKABLE); + + if(!rw) { + log_error("VFS error: %s", vfs_get_error()); + return NULL; + } + + BGM *bgm = calloc(1, sizeof(*bgm)); + + if(!astream_open(&bgm->stream, rw, vfspath)) { + SDL_RWclose(rw); + free(bgm); + return NULL; + } + + log_debug("Loaded stream from %s", vfspath); + return bgm; +} + +static void audio_sdl_bgm_unload(BGM *bgm) { + lock_audio(); + if(mixer.players[G_BGM].channels[CHAN_BGM].stream == &bgm->stream) { + splayer_halt(&mixer.players[G_BGM], CHAN_BGM); + } + unlock_audio(); + + astream_close(&bgm->stream); + free(bgm); +} + +static SFXImpl *audio_sdl_sfx_load(const char *vfspath) { + SDL_RWops *rw = vfs_open(vfspath, VFS_MODE_READ | VFS_MODE_SEEKABLE); + + if(!rw) { + log_error("VFS error: %s", vfs_get_error()); + return NULL; + } + + AudioStream stream; + + if(!astream_open(&stream, rw, vfspath)) { + return NULL; + } + + if(stream.length < 0) { + log_error("Indeterminate stream length"); + astream_close(&stream); + return NULL; + } + + if(stream.length == 0) { + log_error("Empty stream"); + astream_close(&stream); + return NULL; + } + + size_t pcm_size = stream.length * mixer.spec.frame_size; + + if(pcm_size > INT32_MAX) { + log_error("Stream is too large"); + astream_close(&stream); + return NULL; + } + + SFXImpl *isnd = calloc(1, sizeof(*isnd) + pcm_size); + + bool ok = astream_crystalize(&stream, &mixer.spec, pcm_size, &isnd->pcm); + astream_close(&stream); + + if(!ok) { + free(isnd); + return NULL; + } + + log_debug("Loaded SFX from %s", vfspath); + + isnd->pcm_size = pcm_size; + return isnd; +} + +static void audio_sdl_sfx_unload(SFXImpl *sfx) { + lock_audio(); + + for(int i = 0; i < ARRAY_SIZE(mixer.sfx_streams); ++i) { + StaticPCMAudioStream *s = &mixer.sfx_streams[i]; + + if(s->ctx.start == sfx->pcm) { + astream_close(&s->astream); + } + } + + unlock_audio(); + + free(sfx); +} + +// END LOAD + +AudioBackend _a_backend_sdl = { + .name = "sdl", + .funcs = { + .bgm_current = audio_sdl_bgm_current, + .bgm_load = audio_sdl_bgm_load, + .bgm_looping = audio_sdl_bgm_looping, + .bgm_pause = audio_sdl_bgm_pause, + .bgm_play = audio_sdl_bgm_play, + .bgm_resume = audio_sdl_bgm_resume, + .bgm_seek = audio_sdl_bgm_seek, + .bgm_set_global_volume = audio_sdl_bgm_set_global_volume, + .bgm_status = audio_sdl_bgm_status, + .bgm_stop = audio_sdl_bgm_stop, + .bgm_tell = audio_sdl_bgm_tell, + .bgm_unload = audio_sdl_bgm_unload, + .get_supported_bgm_exts = audio_sdl_get_supported_exts, + .get_supported_sfx_exts = audio_sdl_get_supported_exts, + .init = audio_sdl_init, + .output_works = audio_sdl_output_works, + .sfx_load = audio_sdl_sfx_load, + .sfx_loop = audio_sdl_sfx_loop, + .sfx_pause_all = audio_sdl_sfx_pause_all, + .sfx_play = audio_sdl_sfx_play, + .sfx_play_or_restart = audio_sdl_sfx_play_or_restart, + .sfx_resume_all = audio_sdl_sfx_resume_all, + .sfx_set_global_volume = audio_sdl_sfx_set_global_volume, + .sfx_stop_all = audio_sdl_sfx_stop_all, + .sfx_stop_id = audio_sdl_sfx_stop_id, + .sfx_stop_loop = audio_sdl_sfx_stop_loop, + .sfx_unload = audio_sdl_sfx_unload, + .shutdown = audio_sdl_shutdown, + + .object = { + .bgm = { + .get_artist = audio_sdl_obj_bgm_get_artist, + .get_comment = audio_sdl_obj_bgm_get_comment, + .get_duration = audio_sdl_obj_bgm_get_duration, + .get_loop_start = audio_sdl_obj_bgm_get_loop_start, + .get_title = audio_sdl_obj_bgm_get_title, + }, + + .sfx = { + .set_volume = audio_sdl_obj_sfx_set_volume, + }, + }, + } +}; diff --git a/src/audio/sdl/meson.build b/src/audio/sdl/meson.build new file mode 100644 index 00000000..dad8c2cb --- /dev/null +++ b/src/audio/sdl/meson.build @@ -0,0 +1,7 @@ + +a_sdl_src = files( + 'audio_sdl.c', +) + +a_sdl_deps = ['stream'] +a_sdl_libdeps = [] diff --git a/src/audio/sdl2mixer/audio_sdl2mixer.c b/src/audio/sdl2mixer/audio_sdl2mixer.c deleted file mode 100644 index 4aaa56c3..00000000 --- a/src/audio/sdl2mixer/audio_sdl2mixer.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * This software is licensed under the terms of the MIT License. - * See COPYING for further information. - * --- - * Copyright (c) 2011-2019, Lukas Weber . - * Copyright (c) 2012-2019, Andrei Alexeyev . - */ - -#include "taisei.h" - -#include -#include - -#include "../backend.h" -#include "global.h" -#include "list.h" -#include "util/opuscruft.h" - -#define B (_a_backend.funcs) - -#define AUDIO_FREQ 48000 -#define AUDIO_FORMAT AUDIO_F32SYS -#define AUDIO_CHANNELS 32 -#define UI_CHANNELS 4 -#define UI_CHANNEL_GROUP 0 -#define MAIN_CHANNEL_GROUP 1 - -#if AUDIO_FORMAT == AUDIO_S16SYS -typedef int16_t sample_t; -#define OPUS_READ_SAMPLES_STEREO op_read_stereo -#elif AUDIO_FORMAT == AUDIO_F32SYS -typedef float sample_t; -#define OPUS_READ_SAMPLES_STEREO op_read_float_stereo -#else -#error Audio format not recognized -#endif - - -struct SoundImpl { - Mix_Chunk *ch; - - // I needed to add this for supporting loop sounds since Mixer doesn’t remember - // what channel a sound is playing on. - laochailan - int loopchan; // channel the sound may be looping on. -1 if not looping - int playchan; // channel the sound was last played on (looping does NOT set this). -1 if never played -}; - -struct MusicImpl { - Mix_Music *loop; - double loop_point; -}; - -static struct { - Mix_Music *next_loop; - double next_loop_point; - uint32_t play_counter; - uint32_t chan_play_ids[AUDIO_CHANNELS]; - - struct { - uchar first; - uchar num; - } groups[2]; -} mixer; - -#define IS_VALID_CHANNEL(chan) ((uint)(chan) < AUDIO_CHANNELS) - -static bool audio_sdl2mixer_init(void) { - if(SDL_InitSubSystem(SDL_INIT_AUDIO)) { - log_sdl_error(LOG_ERROR, "SDL_InitSubSystem"); - return false; - } - - uint num_drivers = SDL_GetNumAudioDrivers(); - void *buf; - SDL_RWops *out = SDL_RWAutoBuffer(&buf, 256); - - SDL_RWprintf(out, "Available audio drivers:"); - - for(uint i = 0; i < num_drivers; ++i) { - SDL_RWprintf(out, " %s", SDL_GetAudioDriver(i)); - } - - SDL_WriteU8(out, 0); - log_info("%s", (char*)buf); - SDL_RWclose(out); - - if(Mix_OpenAudio(AUDIO_FREQ, AUDIO_FORMAT, 2, config_get_int(CONFIG_MIXER_CHUNKSIZE)) == -1) { - log_error("Mix_OpenAudio() failed: %s", Mix_GetError()); - Mix_Quit(); - return false; - } - - if(!(Mix_Init(MIX_INIT_OPUS) & MIX_INIT_OPUS)) { - log_error("Mix_Init() failed: %s", Mix_GetError()); - Mix_Quit(); - return false; - } - - log_info("Using driver '%s'", SDL_GetCurrentAudioDriver()); - - int channels = Mix_AllocateChannels(AUDIO_CHANNELS); - - if(!channels) { - log_error("Unable to allocate any channels"); - Mix_CloseAudio(); - Mix_Quit(); - return false; - } - - if(channels < AUDIO_CHANNELS) { - log_warn("Allocated only %d out of %d channels", channels, AUDIO_CHANNELS); - } - - int wanted_ui_channels = UI_CHANNELS; - - if(wanted_ui_channels > channels / 2) { - wanted_ui_channels = channels / 2; - log_warn("Will not reserve more than %i channels for UI sounds (wanted %i channels)", wanted_ui_channels, UI_CHANNELS); - } - - int ui_channels; - - if((ui_channels = Mix_GroupChannels(0, wanted_ui_channels - 1, UI_CHANNEL_GROUP)) != wanted_ui_channels) { - log_warn("Assigned only %d out of %d channels to the UI group", ui_channels, wanted_ui_channels); - } - - int wanted_main_channels = channels - ui_channels, main_channels; - - if((main_channels = Mix_GroupChannels(ui_channels, ui_channels + wanted_main_channels - 1, MAIN_CHANNEL_GROUP)) != wanted_main_channels) { - log_warn("Assigned only %d out of %d channels to the main group", main_channels, wanted_main_channels); - } - - int unused_channels = channels - ui_channels - main_channels; - - if(unused_channels) { - log_warn("%i channels not used", unused_channels); - } - - mixer.groups[UI_CHANNEL_GROUP].first = 0; - mixer.groups[UI_CHANNEL_GROUP].num = ui_channels; - mixer.groups[MAIN_CHANNEL_GROUP].first = ui_channels; - mixer.groups[MAIN_CHANNEL_GROUP].num = main_channels; - - B.sound_set_global_volume(config_get_float(CONFIG_SFX_VOLUME)); - B.music_set_global_volume(config_get_float(CONFIG_BGM_VOLUME)); - - int frequency = 0; - uint16_t format = 0; - - Mix_QuerySpec(&frequency, &format, &channels); - - if(frequency != AUDIO_FREQ || format != AUDIO_FORMAT) { - log_warn( - "Mixer spec doesn't match our request, " - "requested (freq=%i, fmt=%u), got (freq=%i, fmt=%u). " - "Sound may be distorted.", - AUDIO_FREQ, AUDIO_FORMAT, frequency, format - ); - } - - log_info("Audio subsystem initialized (SDL2_Mixer)"); - - SDL_version v; - const SDL_version *lv; - - SDL_MIXER_VERSION(&v); - log_info("Compiled against SDL_mixer %u.%u.%u", v.major, v.minor, v.patch); - - lv = Mix_Linked_Version(); - log_info("Using SDL_mixer %u.%u.%u", lv->major, lv->minor, lv->patch); - return true; -} - -static bool audio_sdl2mixer_shutdown(void) { - Mix_CloseAudio(); - Mix_Quit(); - SDL_QuitSubSystem(SDL_INIT_AUDIO); - - log_info("Audio subsystem deinitialized (SDL2_Mixer)"); - return true; -} - -static bool audio_sdl2mixer_output_works(void) { - return true; -} - -static inline int gain_to_mixvol(double gain) { - return iclamp(gain * SDL_MIX_MAXVOLUME, 0, SDL_MIX_MAXVOLUME); -} - -static bool audio_sdl2mixer_sound_set_global_volume(double gain) { - Mix_Volume(-1, gain_to_mixvol(gain)); - return true; -} - -static bool audio_sdl2mixer_music_set_global_volume(double gain) { - Mix_VolumeMusic(gain * gain_to_mixvol(gain)); - return true; -} - -static void mixer_music_finished(void) { - // XXX: there may be a race condition in here - // probably should protect next_loop with a mutex - - log_debug("%s stopped playing", mixer.next_loop_point ? "Loop" : "Intro"); - - if(mixer.next_loop) { - if(Mix_PlayMusic(mixer.next_loop, mixer.next_loop_point ? 0 : -1) == -1) { - log_error("Mix_PlayMusic() failed: %s", Mix_GetError()); - } else if(mixer.next_loop_point >= 0) { - Mix_SetMusicPosition(mixer.next_loop_point); - } else { - mixer.next_loop = NULL; - } - } -} - -static bool audio_sdl2mixer_music_stop(void) { - Mix_HookMusicFinished(NULL); - Mix_HaltMusic(); - return true; -} - -static bool audio_sdl2mixer_music_fade(double fadetime) { - Mix_HookMusicFinished(NULL); - Mix_FadeOutMusic(floor(1000 * fadetime)); - return true; -} - -static bool audio_sdl2mixer_music_is_paused(void) { - return Mix_PausedMusic(); -} - -static bool audio_sdl2mixer_music_is_playing(void) { - return Mix_PlayingMusic() && Mix_FadingMusic() != MIX_FADING_OUT; -} - -static bool audio_sdl2mixer_music_resume(void) { - Mix_HookMusicFinished(mixer_music_finished); - Mix_ResumeMusic(); - return true; -} - -static bool audio_sdl2mixer_music_pause(void) { - Mix_HookMusicFinished(NULL); - Mix_PauseMusic(); - return true; -} - -static bool audio_sdl2mixer_music_play(MusicImpl *imus) { - Mix_Music *mmus; - int loops; - - Mix_HookMusicFinished(NULL); - Mix_HaltMusic(); - - mmus = imus->loop; - mixer.next_loop_point = imus->loop_point; - - if(mixer.next_loop_point >= 0) { - loops = 0; - mixer.next_loop = imus->loop; - Mix_HookMusicFinished(mixer_music_finished); - } else { - loops = -1; - Mix_HookMusicFinished(NULL); - } - - bool result = (Mix_PlayMusic(mmus, loops) != -1); - - if(!result) { - log_error("Mix_PlayMusic() failed: %s", Mix_GetError()); - } - - return result; -} - -static bool audio_sdl2mixer_music_set_position(double pos) { - Mix_RewindMusic(); - - // TODO Handle looping here. - // Unfortunately SDL2_Mixer will not tell us the total length of the file, - // so a hack is required here. - - if(Mix_SetMusicPosition(pos)) { - log_error("Mix_SetMusicPosition() failed: %s", Mix_GetError()); - return false; - } - - return true; -} - -static bool audio_sdl2mixer_music_set_loop_point(MusicImpl *imus, double pos) { - imus->loop_point = pos; - return true; -} - -static int translate_group(AudioBackendSoundGroup group, int defmixgroup) { - switch(group) { - case SNDGROUP_MAIN: return MAIN_CHANNEL_GROUP; - case SNDGROUP_UI: return UI_CHANNEL_GROUP; - default: return defmixgroup; - } -} - -static int pick_channel(AudioBackendSoundGroup group, int defmixgroup) { - int mixgroup = translate_group(group, MAIN_CHANNEL_GROUP); - int channel = -1; - - if((channel = Mix_GroupAvailable(mixgroup)) < 0) { - // all channels busy? try to override the oldest playing sound - if((channel = Mix_GroupOldest(mixgroup)) < 0) { - log_warn("No suitable channel available in group %i to play the sample on", mixgroup); - } - } - - return channel; -} - -INLINE SoundID make_sound_id(int chan, uint32_t play_id) { - assert(IS_VALID_CHANNEL(chan)); - return ((uint32_t)chan + 1) | ((uint64_t)play_id << 32ull); -} - -INLINE void unpack_sound_id(SoundID sid, int *chan, uint32_t *play_id) { - int ch = (int)((uint32_t)(sid & 0xffffffff) - 1); - assert(IS_VALID_CHANNEL(ch)); - *chan = ch; - *play_id = sid >> 32ull; -} - -INLINE int get_soundid_chan(SoundID sid) { - int chan; - uint32_t play_id; - unpack_sound_id(sid, &chan, &play_id); - - if(mixer.chan_play_ids[chan] == play_id) { - return chan; - } - - return -1; -} - -static SoundID play_sound_tracked(uint32_t chan, SoundImpl *isnd, int loops) { - assert(IS_VALID_CHANNEL(chan)); - int actual_chan = Mix_PlayChannel(chan, isnd->ch, loops); - - if(actual_chan < 0) { - log_error("Mix_PlayChannel() failed: %s", Mix_GetError()); - return 0; - } - - Mix_UnregisterAllEffects(actual_chan); - - if(loops) { - isnd->loopchan = actual_chan; - } else { - isnd->playchan = chan; - } - - assert(IS_VALID_CHANNEL(actual_chan)); - chan = (uint32_t)actual_chan; - uint32_t play_id = ++mixer.play_counter; - mixer.chan_play_ids[chan] = play_id; - - return make_sound_id(chan, play_id); -} - -static SoundID audio_sdl2mixer_sound_play(SoundImpl *isnd, AudioBackendSoundGroup group) { - return play_sound_tracked(pick_channel(group, MAIN_CHANNEL_GROUP), isnd, 0); -} - -static SoundID audio_sdl2mixer_sound_play_or_restart(SoundImpl *isnd, AudioBackendSoundGroup group) { - int chan = isnd->playchan; - - if(chan < 0 || Mix_GetChunk(chan) != isnd->ch) { - chan = pick_channel(group, MAIN_CHANNEL_GROUP); - } - - return play_sound_tracked(chan, isnd, 0); -} - -static SoundID audio_sdl2mixer_sound_loop(SoundImpl *isnd, AudioBackendSoundGroup group) { - return play_sound_tracked(pick_channel(group, MAIN_CHANNEL_GROUP), isnd, -1); -} - -// XXX: This custom fading effect circumvents https://bugzilla.libsdl.org/show_bug.cgi?id=2904 -typedef struct CustomFadeout { - int duration; // in samples - int counter; -} CustomFadeout; - -static void custom_fadeout_free(int chan, void *udata) { - free(udata); -} - -static void custom_fadeout_proc(int chan, void *stream, int len, void *udata) { - CustomFadeout *e = udata; - - sample_t *data = stream; - len /= sizeof(sample_t); - - for(int i = 0; i < len; i++) { - e->counter++; - data[i]*=1.-fmin(1,(double)e->counter/(double)e->duration); - } -} - -static bool fade_channel(int chan, int fadeout) { - assert(IS_VALID_CHANNEL(chan)); - - if(mixer.chan_play_ids[chan] == 0) { - return false; - } - - CustomFadeout *effect = calloc(1, sizeof(CustomFadeout)); - effect->counter = 0; - effect->duration = (fadeout * AUDIO_FREQ) / 1000; - Mix_ExpireChannel(chan, fadeout); - Mix_RegisterEffect(chan, custom_fadeout_proc, custom_fadeout_free, effect); - mixer.chan_play_ids[chan] = 0; - - return true; -} - -static bool audio_sdl2mixer_sound_stop_loop(SoundImpl *isnd) { - if(isnd->loopchan < 0) { - return false; - } - - return fade_channel(isnd->loopchan, LOOPFADEOUT); -} - -static bool audio_sdl2mixer_sound_stop_id(SoundID sid) { - int chan = get_soundid_chan(sid); - - if(chan >= 0) { - return fade_channel(chan, LOOPFADEOUT * 3); - } - - return false; -} - -static bool audio_sdl2mixer_sound_pause_all(AudioBackendSoundGroup group) { - int mixgroup = translate_group(group, -1); - - if(mixgroup == -1) { - Mix_Pause(-1); - } else { - // why is there no Mix_PauseGroup? - - for(int i = mixer.groups[mixgroup].first; i < mixer.groups[mixgroup].first + mixer.groups[mixgroup].num; ++i) { - Mix_Pause(i); - } - } - - return true; -} - - -static bool audio_sdl2mixer_sound_resume_all(AudioBackendSoundGroup group) { - int mixgroup = translate_group(group, -1); - - if(mixgroup == -1) { - Mix_Resume(-1); - } else { - // why is there no Mix_ResumeGroup? - - for(int i = mixer.groups[mixgroup].first; i < mixer.groups[mixgroup].first + mixer.groups[mixgroup].num; ++i) { - Mix_Resume(i); - } - } - - return true; -} - -static bool audio_sdl2mixer_sound_stop_all(AudioBackendSoundGroup group) { - int mixgroup = translate_group(group, -1); - - if(mixgroup == -1) { - Mix_HaltChannel(-1); - } else { - Mix_HaltGroup(mixgroup); - } - - return true; -} - -static const char *const *audio_sdl2mixer_get_supported_exts(uint *numexts) { - static const char *const exts[] = { ".opus", ".ogg", ".wav" }; - *numexts = ARRAY_SIZE(exts); - return exts; -} - -static MusicImpl *audio_sdl2mixer_music_load(const char *vfspath) { - SDL_RWops *rw = vfs_open(vfspath, VFS_MODE_READ | VFS_MODE_SEEKABLE); - - if(!rw) { - log_error("VFS error: %s", vfs_get_error()); - return NULL; - } - - Mix_Music *mus = Mix_LoadMUS_RW(rw, true); - - if(!mus) { - log_error("Mix_LoadMUS_RW() failed: %s (%s)", Mix_GetError(), vfspath); - return NULL; - } - - MusicImpl *imus = calloc(1, sizeof(*imus)); - imus->loop = mus; - - log_debug("Loaded stream from %s", vfspath); - return imus; -} - -static void audio_sdl2mixer_music_unload(MusicImpl *imus) { - Mix_FreeMusic(imus->loop); - free(imus); -} - -static OggOpusFile *try_load_opus(SDL_RWops *rw) { - uint8_t buf[128]; - SDL_RWread(rw, buf, 1, sizeof(buf)); - int error = op_test(NULL, buf, sizeof(buf)); - - if(error != 0) { - log_debug("op_test() failed: %i", error); - goto fail; - } - - OggOpusFile *opus = op_open_callbacks(rw, &opusutil_rwops_callbacks, buf, sizeof(buf), &error); - - if(opus == NULL) { - log_debug("op_open_callbacks() failed: %i", error); - goto fail; - } - - return opus; - -fail: - SDL_RWseek(rw, 0, RW_SEEK_SET); - return NULL; -} - -static Mix_Chunk *read_opus_chunk(OggOpusFile *opus, const char *vfspath) { - int mix_frequency = 0; - int mix_channels; - uint16_t mix_format = 0; - Mix_QuerySpec(&mix_frequency, &mix_format, &mix_channels); - - SDL_AudioStream *stream = SDL_NewAudioStream(AUDIO_FORMAT, 2, 48000, mix_format, mix_channels, mix_frequency); - - for(;;) { - sample_t read_buf[1920]; - int r = OPUS_READ_SAMPLES_STEREO(opus, read_buf, sizeof(read_buf)); - - if(r == OP_HOLE) { - continue; - } - - if(r < 0) { - log_error("Opus decode error %i (%s)", r, vfspath); - SDL_FreeAudioStream(stream); - op_free(opus); - return NULL; - } - - if(r == 0) { - op_free(opus); - SDL_AudioStreamFlush(stream); - break; - } - - // r is the number of samples read !!PER CHANNEL!! - // since we tell Opusfile to downmix to stereo, exactly 2 channels are guaranteed. - int read = r * 2; - - if(SDL_AudioStreamPut(stream, read_buf, read * sizeof(*read_buf)) < 0) { - log_sdl_error(LOG_ERROR, "SDL_AudioStreamPut"); - SDL_FreeAudioStream(stream); - op_free(opus); - return NULL; - } - } - - int resampled_bytes = SDL_AudioStreamAvailable(stream); - - if(resampled_bytes <= 0) { - log_error("SDL_AudioStream returned no data"); - SDL_FreeAudioStream(stream); - return NULL; - } - - // WARNING: it's important to use the SDL_* allocation functions here for the pcm buffer, - // so that SDL_mixer can free it properly later. - - uint8_t *pcm = SDL_calloc(1, resampled_bytes); - - SDL_AudioStreamGet(stream, pcm, resampled_bytes); - SDL_FreeAudioStream(stream); - - Mix_Chunk *snd = Mix_QuickLoad_RAW(pcm, resampled_bytes); - - // hack to make SDL_mixer free the buffer for us - snd->allocated = true; - - return snd; -} - -static SoundImpl *audio_sdl2mixer_sound_load(const char *vfspath) { - SDL_RWops *rw = vfs_open(vfspath, VFS_MODE_READ | VFS_MODE_SEEKABLE); - - if(!rw) { - log_error("VFS error: %s", vfs_get_error()); - return NULL; - } - - // SDL2_Mixer as of 2.0.4 is too dumb to actually try to load Opus chunks, - // even if it was compiled with Opus support (which only works for music apparently /facepalm) - // So we'll try to load them via opusfile manually. - - Mix_Chunk *snd = NULL; - OggOpusFile *opus = try_load_opus(rw); - - if(opus) { - snd = read_opus_chunk(opus, vfspath); - - if(!snd) { - // error message printed by read_opus_chunk - return NULL; - } - } else { - snd = Mix_LoadWAV_RW(rw, true); - - if(!snd) { - log_error("Mix_LoadWAV_RW() failed: %s (%s)", Mix_GetError(), vfspath); - return NULL; - } - } - - SoundImpl *isnd = calloc(1, sizeof(*isnd)); - isnd->ch = snd; - isnd->loopchan = -1; - isnd->playchan = -1; - - log_debug("Loaded chunk from %s", vfspath); - return isnd; -} - -static void audio_sdl2mixer_sound_unload(SoundImpl *isnd) { - Mix_FreeChunk(isnd->ch); - free(isnd); -} - -static bool audio_sdl2mixer_sound_set_volume(SoundImpl *isnd, double gain) { - Mix_VolumeChunk(isnd->ch, gain_to_mixvol(gain)); - return true; -} - -AudioBackend _a_backend_sdl2mixer = { - .name = "sdl2mixer", - .funcs = { - .get_supported_music_exts = audio_sdl2mixer_get_supported_exts, - .get_supported_sound_exts = audio_sdl2mixer_get_supported_exts, - .init = audio_sdl2mixer_init, - .music_fade = audio_sdl2mixer_music_fade, - .music_is_paused = audio_sdl2mixer_music_is_paused, - .music_is_playing = audio_sdl2mixer_music_is_playing, - .music_load = audio_sdl2mixer_music_load, - .music_pause = audio_sdl2mixer_music_pause, - .music_play = audio_sdl2mixer_music_play, - .music_resume = audio_sdl2mixer_music_resume, - .music_set_global_volume = audio_sdl2mixer_music_set_global_volume, - .music_set_loop_point = audio_sdl2mixer_music_set_loop_point, - .music_set_position = audio_sdl2mixer_music_set_position, - .music_stop = audio_sdl2mixer_music_stop, - .music_unload = audio_sdl2mixer_music_unload, - .output_works = audio_sdl2mixer_output_works, - .shutdown = audio_sdl2mixer_shutdown, - .sound_load = audio_sdl2mixer_sound_load, - .sound_loop = audio_sdl2mixer_sound_loop, - .sound_pause_all = audio_sdl2mixer_sound_pause_all, - .sound_play = audio_sdl2mixer_sound_play, - .sound_play_or_restart = audio_sdl2mixer_sound_play_or_restart, - .sound_resume_all = audio_sdl2mixer_sound_resume_all, - .sound_set_global_volume = audio_sdl2mixer_sound_set_global_volume, - .sound_set_volume = audio_sdl2mixer_sound_set_volume, - .sound_stop_all = audio_sdl2mixer_sound_stop_all, - .sound_stop_loop = audio_sdl2mixer_sound_stop_loop, - .sound_stop_id = audio_sdl2mixer_sound_stop_id, - .sound_unload = audio_sdl2mixer_sound_unload, - } -}; diff --git a/src/audio/sdl2mixer/meson.build b/src/audio/sdl2mixer/meson.build deleted file mode 100644 index 6ff89a97..00000000 --- a/src/audio/sdl2mixer/meson.build +++ /dev/null @@ -1,12 +0,0 @@ - -a_sdl2mixer_src = files( - 'audio_sdl2mixer.c', -) - -a_sdl2mixer_deps = [] -a_sdl2mixer_libdeps = [dep_sdl2_mixer, dep_opusfile] - -if get_option('a_sdl2mixer') - assert(dep_sdl2_mixer.found(), 'sdl2mixer backend enabled, but SDL2_mixer was not found. Install SDL2_mixer or disable a_sdl2mixer') - assert(dep_opusfile.found(), 'sdl2mixer backend enabled, but opusfile was not found. Install opusfile or disable a_sdl2mixer') -endif diff --git a/src/audio/stream/meson.build b/src/audio/stream/meson.build new file mode 100644 index 00000000..2440474f --- /dev/null +++ b/src/audio/stream/meson.build @@ -0,0 +1,12 @@ + +a_stream_src = files( + 'player.c', + 'stream.c', + 'stream_opus.c', + 'stream_pcm.c', +) + +dep_opusfile = dependency('opusfile', required : true, static : static, fallback : ['opusfile', 'opusfile_dep']) + +a_stream_deps = [] +a_stream_libdeps = [dep_opusfile] diff --git a/src/audio/stream/player.c b/src/audio/stream/player.c new file mode 100644 index 00000000..835808dd --- /dev/null +++ b/src/audio/stream/player.c @@ -0,0 +1,500 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#include "taisei.h" + +#include "player.h" +#include "util.h" + +// #define SPAM(...) log_debug(__VA_ARGS__) +#define SPAM(...) ((void)0) + +typedef float sample_t; + +struct stereo_frame { + sample_t l, r; +}; + +union audio_buffer { + uint8_t *bytes; + sample_t *samples; + struct stereo_frame *frames; +}; + +bool splayer_init(StreamPlayer *plr, int num_channels, const AudioStreamSpec *dst_spec) { + memset(plr, 0, sizeof(*plr)); + + if(dst_spec->sample_format != AUDIO_F32SYS) { + log_error("Unsupported audio format: 0x%4x", dst_spec->sample_format); + return false; + } + + if(dst_spec->channels != 2) { + log_error("Unsupported number of audio channels: %i", dst_spec->channels); + return false; + } + + assert(dst_spec->frame_size == sizeof(struct stereo_frame)); + + plr->num_channels = num_channels, + plr->channels = calloc(sizeof(*plr->channels), num_channels); + plr->dst_spec = *dst_spec; + + for(int i = 0; i < num_channels; ++i) { + StreamPlayerChannel *chan = plr->channels + i; + chan->gain = 1; + chan->paused = true; + alist_append(&plr->channel_history, chan); + } + + log_debug("Player spec: %iHz; %i chans; format=%i", + plr->dst_spec.sample_rate, + plr->dst_spec.channels, + plr->dst_spec.sample_format + ); + + return true; +} + +static inline void splayer_history_move_to_front(StreamPlayer *plr, StreamPlayerChannel *pchan) { + alist_unlink(&plr->channel_history, pchan); + alist_push(&plr->channel_history, pchan); +} + +static inline void splayer_history_move_to_back(StreamPlayer *plr, StreamPlayerChannel *pchan) { + alist_unlink(&plr->channel_history, pchan); + alist_append(&plr->channel_history, pchan); +} + +static void free_channel(StreamPlayerChannel *chan) { + SDL_FreeAudioStream(chan->pipe); +} + +void splayer_shutdown(StreamPlayer *plr) { + for(int i = 0; i < plr->num_channels; ++i) { + free_channel(plr->channels + i); + } + + free(plr->channels); +} + +static inline void splayer_stream_ended(StreamPlayer *plr, int chan) { + SPAM("Audio stream ended on channel %i", chan); + splayer_halt(plr, chan); +} + +static size_t splayer_process_channel(StreamPlayer *plr, int chan, size_t bufsize, void *buffer) { + AudioStreamReadFlags rflags = 0; + StreamPlayerChannel *pchan = plr->channels + chan; + + if(pchan->paused || !pchan->stream) { + return 0; + } + + if(pchan->looping) { + rflags |= ASTREAM_READ_LOOP; + } + + uint8_t *buf = buffer; + uint8_t *buf_end = buf + bufsize; + AudioStream *astream = pchan->stream; + SDL_AudioStream *pipe = pchan->pipe; + + if(pipe) { + // convert/resample + + do { + uint8_t staging_buffer[bufsize]; + ssize_t read = SDL_AudioStreamGet(pipe, buf, buf_end - buf); + + if(UNLIKELY(read < 0)) { + log_sdl_error(LOG_ERROR, "SDL_AudioStreamGet"); + break; + } + + buf += read; + + if(buf >= buf_end) { + break; + } + + read = astream_read_into_sdl_stream(astream, pipe, sizeof(staging_buffer), staging_buffer, rflags); + + if(read <= 0) { + SDL_AudioStreamFlush(pipe); + + if(SDL_AudioStreamAvailable(pipe) <= 0) { + splayer_stream_ended(plr, chan); + break; + } + } + } while(buf < buf_end); + } else { + // direct stream + + ssize_t read = astream_read(astream, bufsize, buf, rflags | ASTREAM_READ_MAX_FILL); + + if(read > 0) { + buf += read; + } else if(read == 0) { + splayer_stream_ended(plr, chan); + } + } + + assert(buf <= buf_end); + return bufsize - (buf_end - buf); +} + +void splayer_process(StreamPlayer *plr, size_t bufsize, void *vbuffer) { + if(plr->paused) { + return; + } + + float gain = plr->gain; + int num_channels = plr->num_channels; + union audio_buffer out_buffer = { vbuffer }; + + for(int i = 0; i < num_channels; ++i) { + uint8_t staging_buffer_bytes[bufsize]; + union audio_buffer staging_buffer = { staging_buffer_bytes }; + size_t chan_bytes = splayer_process_channel(plr, i, sizeof(staging_buffer_bytes), staging_buffer_bytes); + + if(chan_bytes) { + assert(chan_bytes <= bufsize); + assert(chan_bytes % sizeof(struct stereo_frame) == 0); + + StreamPlayerChannel *pchan = plr->channels + i; + float chan_gain = gain * pchan->gain; + uint num_staging_frames = chan_bytes / sizeof(struct stereo_frame); + uint fade_steps = pchan->fade.num_steps; + + if(fade_steps) { + float fade_step = pchan->fade.step; + float fade_gain = pchan->fade.gain; + + if(fade_steps > num_staging_frames) { + fade_steps = num_staging_frames; + } + + for(uint i = 0; i < fade_steps; ++i) { + float g = fade_gain + fade_step * i; + staging_buffer.frames[i].l *= g; + staging_buffer.frames[i].r *= g; + } + + if((pchan->fade.num_steps -= fade_steps) == 0) { + // fade finished + + if(pchan->fade.target == 0) { + splayer_stream_ended(plr, i); + continue; + } + + pchan->fade.gain = pchan->fade.target; + chan_gain *= pchan->fade.gain; + } else { + pchan->fade.gain += fade_step * fade_steps; + } + } else { + chan_gain *= pchan->fade.gain; + } + + for(uint i = 0; i < num_staging_frames; ++i) { + out_buffer.frames[i].l += staging_buffer.frames[i].l * chan_gain; + out_buffer.frames[i].r += staging_buffer.frames[i].r * chan_gain; + } + } + } +} + +static bool splayer_validate_channel(StreamPlayer *plr, int chan) { + if(chan < 0 || chan >= plr->num_channels) { + log_error("Bad channel %i", chan); + return false; + } + + return true; +} + +static void splayer_set_fade(StreamPlayer *plr, StreamPlayerChannel *pchan, float from, float to, float time) { + if(time == 0) { + pchan->fade.target = to; + pchan->fade.gain = to; + pchan->fade.num_steps = 0; + pchan->fade.step = 0; + } else { + pchan->fade.target = to; + pchan->fade.gain = from; + pchan->fade.num_steps = lroundf(plr->dst_spec.sample_rate * time); + pchan->fade.step = (to - from) / pchan->fade.num_steps; + } +} + +bool splayer_play(StreamPlayer *plr, int chan, AudioStream *stream, bool loop, float gain, double position, double fadein) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + if(UNLIKELY(astream_seek(stream, astream_util_time_to_offset(stream, position)) < 0)) { + log_error("astream_seek() failed"); + goto fail; + } + + if(UNLIKELY(fadein < 0)) { + log_error("Bad fadein time value %f", fadein); + goto fail; + } + + StreamPlayerChannel *pchan = plr->channels + chan; + + pchan->stream = stream; + pchan->looping = loop; + pchan->paused = false; + pchan->gain = gain; + + splayer_history_move_to_back(plr, pchan); + splayer_set_fade(plr, pchan, 0, 1, fadein); + + SPAM("Stream spec: %iHz; %i chans; format=%i", + stream->spec.sample_rate, + stream->spec.channels, + stream->spec.sample_format + ); + + SPAM("Player spec: %iHz; %i chans; format=%i", + plr->dst_spec.sample_rate, + plr->dst_spec.channels, + plr->dst_spec.sample_format + ); + + if(LIKELY(astream_spec_equals(&stream->spec, &plr->dst_spec))) { + SPAM("Stream needs no conversion"); + + if(pchan->pipe) { + SDL_FreeAudioStream(pchan->pipe); + pchan->pipe = NULL; + SPAM("Destroyed conversion pipe"); + } + } else { + SPAM("Stream needs a conversion"); + + if(pchan->pipe && UNLIKELY(!astream_spec_equals(&stream->spec, &pchan->src_spec))) { + SDL_FreeAudioStream(pchan->pipe); + pchan->pipe = NULL; + SPAM("Existing conversion pipe can't be reused; destroyed"); + } + + if(pchan->pipe) { + SDL_AudioStreamClear(pchan->pipe); + SPAM("Reused an existing conversion pipe"); + } else { + pchan->pipe = astream_create_sdl_stream(stream, &plr->dst_spec); + pchan->src_spec = stream->spec; + SPAM("Created a new conversion pipe"); + } + } + + return true; + +fail: + splayer_halt(plr, chan); + return false; +} + +bool splayer_pause(StreamPlayer *plr, int chan) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + if(UNLIKELY(plr->channels[chan].paused)) { + log_warn("Channel %i is already paused", chan); + return false; + } + + plr->channels[chan].paused = true; + return true; +} + +bool splayer_resume(StreamPlayer *plr, int chan) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + StreamPlayerChannel *pchan = plr->channels + chan; + + if(UNLIKELY(!pchan->paused)) { + log_warn("Channel %i is not paused", chan); + return false; + } + + if(UNLIKELY(!pchan->stream)) { + log_error("Channel %i has no stream", chan); + return false; + } + + pchan->paused = false; + return true; +} + +void splayer_halt(StreamPlayer *plr, int chan) { + if(!splayer_validate_channel(plr, chan)) { + return; + } + + StreamPlayerChannel *pchan = plr->channels + chan; + pchan->stream = NULL; + pchan->paused = true; + pchan->looping = false; + pchan->gain = 0; + pchan->fade.gain = 0; + pchan->fade.step = 0; + pchan->fade.num_steps = 0; + pchan->fade.target = 0; + splayer_history_move_to_front(plr, pchan); +} + +bool splayer_fadeout(StreamPlayer *plr, int chan, double fadeout) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + if(UNLIKELY(fadeout <= 0)) { + log_error("Bad fadeout time value %f", fadeout); + return false; + } + + StreamPlayerChannel *pchan = plr->channels + chan; + splayer_set_fade(plr, pchan, pchan->fade.gain, 0, fadeout); + + return true; +} + +double splayer_seek(StreamPlayer *plr, int chan, double position) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + StreamPlayerChannel *pchan = plr->channels + chan; + + if(UNLIKELY(!pchan->stream)) { + log_error("Channel %i has no stream", chan); + return -1; + } + + ssize_t ofs = astream_util_time_to_offset(pchan->stream, position); + ofs = astream_seek(pchan->stream, ofs); + + if(UNLIKELY(ofs < 0)) { + log_error("astream_seek() failed"); + return -1; + } + + position = astream_util_offset_to_time(pchan->stream, ofs); + return position; +} + +double splayer_tell(StreamPlayer *plr, int chan) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + StreamPlayerChannel *pchan = plr->channels + chan; + + if(UNLIKELY(!pchan->stream)) { + log_error("Channel %i has no stream", chan); + return -1; + } + + ssize_t ofs = astream_tell(pchan->stream); + + if(UNLIKELY(ofs < 0)) { + log_error("astream_tell() failed"); + return -1; + } + + double position = astream_util_offset_to_time(pchan->stream, ofs); + return position; +} + +bool splayer_global_pause(StreamPlayer *plr) { + if(UNLIKELY(plr->paused)) { + log_warn("Player is already paused"); + return false; + } + + plr->paused = true; + return true; +} + +bool splayer_global_resume(StreamPlayer *plr) { + if(UNLIKELY(!plr->paused)) { + log_warn("Player is not paused"); + return false; + } + + plr->paused = false; + return true; +} + +bool splayer_is_looping(StreamPlayer *plr, int chan) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + StreamPlayerChannel *pchan = plr->channels + chan; + bool loop = pchan->stream && pchan->looping; + + return loop; +} + +static bool is_fading_out(StreamPlayerChannel *pchan) { + return pchan->fade.num_steps && pchan->fade.target == 0; +} + +BGMStatus splayer_util_bgmstatus(StreamPlayer *plr, int chan) { + if(!splayer_validate_channel(plr, chan)) { + return false; + } + + BGMStatus status = BGM_PLAYING; + StreamPlayerChannel *pchan = plr->channels + chan; + + if(!pchan->stream || is_fading_out(pchan)) { + status = BGM_STOPPED; + } else if(plr->paused) { + status = BGM_PAUSED; + } + + return status; +} + +int splayer_pick_channel(StreamPlayer *plr) { + StreamPlayerChannel *pick = plr->channel_history.first; + + for(StreamPlayerChannel *pchan = plr->channel_history.first; pchan; pchan = pchan->next) { + if(pchan->stream == NULL) { + // We have a free channel, perfect. + // If a free channel exists, then it is always at the head of the history list (if history works correctly). + assume(pchan == plr->channel_history.first); + pick = pchan; + break; + } + + // No free channel, so we try to pick the oldest one that isn't looping. + // We could do better, e.g. pick the one with least time remaining, but this should be ok for short sounds. + + if(!pchan->looping) { + pick = pchan; + break; + } + } + + int id = pick - plr->channels; + + return id; +} diff --git a/src/audio/stream/player.h b/src/audio/stream/player.h new file mode 100644 index 00000000..2fbc3e67 --- /dev/null +++ b/src/audio/stream/player.h @@ -0,0 +1,64 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#ifndef IGUARD_audio_stream_player_h +#define IGUARD_audio_stream_player_h + +#include "taisei.h" + +#include "stream.h" +#include "list.h" + +typedef struct StreamPlayerChannel StreamPlayerChannel; +typedef struct StreamPlayer StreamPlayer; + +struct StreamPlayerChannel { + LIST_INTERFACE(StreamPlayerChannel); + AudioStream *stream; + SDL_AudioStream *pipe; + AudioStreamSpec src_spec; + float gain; + struct { + float target; + float gain; + float step; + int num_steps; + } fade; + bool looping; + bool paused; +}; + +struct StreamPlayer { + StreamPlayerChannel *channels; + LIST_ANCHOR(StreamPlayerChannel) channel_history; + AudioStreamSpec dst_spec; + float gain; + int num_channels; + bool paused; +}; + +bool splayer_init(StreamPlayer *plr, int num_channels, const AudioStreamSpec *dst_spec) attr_nonnull_all; +void splayer_shutdown(StreamPlayer *plr) attr_nonnull_all; +void splayer_process(StreamPlayer *plr, size_t bufsize, void *buffer) attr_nonnull_all; +bool splayer_play(StreamPlayer *plr, int chan, AudioStream *stream, bool loop, float gain, double position, double fadein) attr_nonnull_all; +bool splayer_pause(StreamPlayer *plr, int chan) attr_nonnull_all; +bool splayer_resume(StreamPlayer *plr, int chan) attr_nonnull_all; +void splayer_halt(StreamPlayer *plr, int chan) attr_nonnull_all; +bool splayer_fadeout(StreamPlayer *plr, int chan, double fadeout) attr_nonnull_all; +double splayer_seek(StreamPlayer *plr, int chan, double position) attr_nonnull_all; +double splayer_tell(StreamPlayer *plr, int chan) attr_nonnull_all; +bool splayer_is_looping(StreamPlayer *plr, int chan) attr_nonnull_all; +bool splayer_global_pause(StreamPlayer *plr) attr_nonnull_all; +bool splayer_global_resume(StreamPlayer *plr) attr_nonnull_all; +int splayer_pick_channel(StreamPlayer *plr) attr_nonnull_all; + +#include "audio/audio.h" + +BGMStatus splayer_util_bgmstatus(StreamPlayer *plr, int chan) attr_nonnull_all; + +#endif // IGUARD_audio_stream_player_h diff --git a/src/audio/stream/stream.c b/src/audio/stream/stream.c new file mode 100644 index 00000000..aeb88615 --- /dev/null +++ b/src/audio/stream/stream.c @@ -0,0 +1,219 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#include "taisei.h" + +#include "stream.h" +#include "stream_opus.h" +#include "util.h" + +#define PROCS(stream) (*NOT_NULL((stream)->procs)) +#define PROC(stream, proc) (NOT_NULL(PROCS(stream).proc)) + +AudioStreamSpec astream_spec(SDL_AudioFormat sample_format, uint channels, uint sample_rate) { + AudioStreamSpec s; + s.sample_format = sample_format; + s.channels = channels; + s.sample_rate = sample_rate; + s.frame_size = channels * (SDL_AUDIO_BITSIZE(sample_format) / CHAR_BIT); + assert(s.frame_size > 0); + return s; +} + +bool astream_spec_equals(const AudioStreamSpec *s1, const AudioStreamSpec *s2) { + return + s1->channels == s2->channels && + s1->sample_format == s2->sample_format && + s1->sample_rate == s2->sample_rate; +} + +SDL_AudioStream *astream_create_sdl_stream(AudioStream *source, const AudioStreamSpec *dest_spec) { + return SDL_NewAudioStream( + source->spec.sample_format, + source->spec.channels, + source->spec.sample_rate, + dest_spec->sample_format, + dest_spec->channels, + dest_spec->sample_rate + ); +} + +bool astream_open(AudioStream *stream, SDL_RWops *rwops, const char *filename) { + bool ok = false; + + ok = astream_opus_open(stream, rwops); + + if(!ok) { + log_error("%s: failed to open audio stream", filename); + } + + return ok; +} + +void astream_close(AudioStream *stream) { + if(PROCS(stream).free) { + PROC(stream, free)(stream); + } +} + +ssize_t astream_read(AudioStream *stream, size_t bufsize, void *buffer, AudioStreamReadFlags flags) { + if(flags & ASTREAM_READ_MAX_FILL) { + flags &= ~ASTREAM_READ_MAX_FILL; + char *cbuf = buffer; + char *end = cbuf + bufsize; + ssize_t read; + + while(end - cbuf > 0 && (read = astream_read(stream, end - cbuf, cbuf, flags)) > 0) { + cbuf += read; + } + + return cbuf - (char*)buffer; + } + + if(flags & ASTREAM_READ_LOOP) { + ssize_t read = PROC(stream, read)(stream, bufsize, buffer); + + if(UNLIKELY(read == 0)) { + ssize_t loop_start = stream->loop_start; + if(loop_start < 0) { + loop_start = 0; + } + + if(UNLIKELY(astream_seek(stream, loop_start) < 0)) { + return -1; + } + + return PROC(stream, read)(stream, bufsize, buffer); + } + + return read; + } + + return PROC(stream, read)(stream, bufsize, buffer); +} + +ssize_t astream_read_into_sdl_stream(AudioStream *stream, SDL_AudioStream *sdlstream, size_t bufsize, void *buffer, AudioStreamReadFlags flags) { + char *buf = buffer; + ssize_t read_size = astream_read(stream, bufsize, buf, flags); + + if(LIKELY(read_size > 0)) { + if(UNLIKELY(SDL_AudioStreamPut(sdlstream, buf, read_size) < 0)) { + log_sdl_error(LOG_ERROR, "SDL_AudioStreamPut"); + return -1; + } + } + + return read_size; +} + +ssize_t astream_seek(AudioStream *stream, size_t pos) { + if(stream->length >= 0 && pos >= stream->length) { + log_error("Position %zu out of range", pos); + return -1; + } + + if(LIKELY(PROCS(stream).seek)) { + return PROC(stream, seek)(stream, pos); + } + + log_error("Not implemented"); + return -1; +} + +ssize_t astream_tell(AudioStream *stream) { + if(LIKELY(PROCS(stream).tell)) { + return PROC(stream, tell)(stream); + } + + log_error("Not implemented"); + return -1; +} + +const char *astream_get_meta_tag(AudioStream *stream, AudioStreamMetaTag tag) { + if(LIKELY(PROCS(stream).meta)) { + return PROC(stream, meta)(stream, tag); + } + + return NULL; +} + +ssize_t astream_util_time_to_offset(AudioStream *stream, double t) { + return t * stream->spec.sample_rate; +} + +double astream_util_offset_to_time(AudioStream *stream, ssize_t ofs) { + return ofs / (double)stream->spec.sample_rate; +} + +bool astream_crystalize(AudioStream *src, const AudioStreamSpec *spec, size_t buffer_size, void *buffer) { + size_t min_size = src->length * spec->frame_size; + + if(min_size > INT32_MAX) { + log_error("Stream is too large"); + return false; + } + + assert(buffer_size >= min_size); + + SDL_AudioStream *pipe = NULL; + SDL_RWops *rw = SDL_RWFromMem(buffer, min_size); + + if(UNLIKELY(!rw)) { + log_sdl_error(LOG_ERROR, "SDL_RWFromMem"); + goto fail; + } + + pipe = astream_create_sdl_stream(src, spec); + + if(UNLIKELY(!pipe)) { + log_sdl_error(LOG_ERROR, "SDL_CreateAudioStream"); + goto fail; + } + + for(;;) { + char staging[1 << 14]; + + ssize_t read = SDL_AudioStreamGet(pipe, staging, sizeof(staging)); + + if(UNLIKELY(read < 0)) { + log_sdl_error(LOG_ERROR, "SDL_AudioStreamGet"); + goto fail; + } + + if(read > 0) { + if(UNLIKELY(SDL_RWwrite(rw, staging, 1, read) < read)) { + log_sdl_error(LOG_ERROR, "SDL_RWwrite"); + goto fail; + } + } + + read = astream_read_into_sdl_stream(src, pipe, sizeof(staging), staging, 0); + + if(UNLIKELY(read < 0)) { + goto fail; + } + + if(read == 0) { + SDL_AudioStreamFlush(pipe); + if(SDL_AudioStreamAvailable(pipe) == 0) { + break; + } + } + } + + SDL_FreeAudioStream(pipe); + SDL_RWclose(rw); + return true; + +fail: + if(rw) { + SDL_RWclose(rw); + } + SDL_FreeAudioStream(pipe); + return false; +} diff --git a/src/audio/stream/stream.h b/src/audio/stream/stream.h new file mode 100644 index 00000000..5902de09 --- /dev/null +++ b/src/audio/stream/stream.h @@ -0,0 +1,72 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#ifndef IGUARD_audio_stream_stream_h +#define IGUARD_audio_stream_stream_h + +#include "taisei.h" + +#include + +typedef struct AudioStream AudioStream; +typedef struct AudioStreamProcs AudioStreamProcs; +typedef struct AudioStreamSpec AudioStreamSpec; + +typedef enum AudioStreamMetaTag { + STREAM_META_TITLE, + STREAM_META_ARTIST, + STREAM_META_COMMENT, +} AudioStreamMetaTag; + +struct AudioStreamProcs { + ssize_t (*read)(AudioStream *s, size_t bufsize, void *buffer); + ssize_t (*tell)(AudioStream *s); + ssize_t (*seek)(AudioStream *s, size_t pos); + const char *(*meta)(AudioStream *s, AudioStreamMetaTag tag); + void (*free)(AudioStream *s); +}; + +struct AudioStreamSpec { + SDL_AudioFormat sample_format; // typedef'd to uint16_t + uint16_t channels; + uint32_t sample_rate; + uint32_t frame_size; +}; + +struct AudioStream { + AudioStreamProcs *procs; + void *opaque; + AudioStreamSpec spec; + int32_t length; + int32_t loop_start; +}; + +typedef enum AudioStreamReadFlags { + ASTREAM_READ_MAX_FILL = (1 << 0), + ASTREAM_READ_LOOP = (1 << 1), +} AudioStreamReadFlags; + + +bool astream_open(AudioStream *stream, SDL_RWops *rwops, const char *filename) attr_nonnull_all; +void astream_close(AudioStream *stream) attr_nonnull_all; +ssize_t astream_read(AudioStream *stream, size_t bufsize, void *buffer, AudioStreamReadFlags flags) attr_nonnull_all; +ssize_t astream_read_into_sdl_stream(AudioStream *stream, SDL_AudioStream *sdlstream, size_t bufsize, void *buffer, AudioStreamReadFlags flags) attr_nonnull_all; +ssize_t astream_seek(AudioStream *stream, size_t pos) attr_nonnull_all; +ssize_t astream_tell(AudioStream *stream) attr_nonnull_all; +const char *astream_get_meta_tag(AudioStream *stream, AudioStreamMetaTag tag) attr_nonnull_all; + +SDL_AudioStream *astream_create_sdl_stream(AudioStream *source, const AudioStreamSpec *dest_spec); +bool astream_crystalize(AudioStream *src, const AudioStreamSpec *spec, size_t buffer_size, void *buffer); + +ssize_t astream_util_time_to_offset(AudioStream *stream, double t) attr_nonnull_all; +double astream_util_offset_to_time(AudioStream *stream, ssize_t ofs) attr_nonnull_all; + +AudioStreamSpec astream_spec(SDL_AudioFormat sample_format, uint channels, uint sample_rate); +bool astream_spec_equals(const AudioStreamSpec *s1, const AudioStreamSpec *s2); + +#endif // IGUARD_audio_stream_stream_h diff --git a/src/audio/stream/stream_opus.c b/src/audio/stream/stream_opus.c new file mode 100644 index 00000000..a586383a --- /dev/null +++ b/src/audio/stream/stream_opus.c @@ -0,0 +1,198 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#include "taisei.h" + +#include "stream_opus.h" +#include "util.h" + +#include + +#define OPUS_SAMPLE_RATE 48000 + +static ssize_t astream_opus_read(AudioStream *s, size_t bufsize, void *buffer) { + OggOpusFile *of = s->opaque; + + int r; + do { + r = op_read_float_stereo(of, buffer, bufsize / sizeof(float)); + } while(r == OP_HOLE); + + if(r < 0) { + log_error("op_read_float_stereo() failed (error %i)", r); + return -1; + } + + // r == num. samples read per channel + return r * 2 * sizeof(float); +} + +static ssize_t astream_opus_tell(AudioStream *s) { + ssize_t r = op_pcm_tell(s->opaque); + + if(r < 0) { + log_error("op_pcm_tell() failed (error %zi)", r); + return -1; + } + + return r; +} + +static ssize_t astream_opus_seek(AudioStream *s, size_t pos) { + ssize_t r = op_pcm_seek(s->opaque, pos); + + if(r < 0) { + log_error("op_pcm_seek() failed (error %zi)", r); + return -1; + } + + return r; +} + +static void astream_opus_free(AudioStream *s) { + op_free(s->opaque); +} + +static const char *get_opus_tag(OggOpusFile *of, const char *tag) { + const OpusTags *tags = op_tags(of, -1); + + if(UNLIKELY(tags == NULL)) { + log_error("op_tags() returned NULL"); + return NULL; + } + + int tag_len = strlen(tag); + assert(tag[tag_len - 1] == '='); + + for(int i = 0; i < tags->comments; ++i) { + const char *comment = tags->user_comments[i]; + + if(strlen(comment) < tag_len) { + continue; + } + + if(!SDL_strncasecmp(comment, tag, tag_len)) { + return comment + tag_len; + } + } + + return NULL; +} + +static const char *astream_opus_meta(AudioStream *s, AudioStreamMetaTag tag) { + OggOpusFile *of = s->opaque; + + static const char *tag_map[] = { + [STREAM_META_ARTIST] = "ARTIST=", + [STREAM_META_TITLE] = "TITLE=", + [STREAM_META_COMMENT] = "DESCRIPTION=", + }; + + uint idx = tag; + assert(idx < ARRAY_SIZE(tag_map)); + + return get_opus_tag(of, tag_map[tag]); +} + +static AudioStreamProcs astream_opus_procs = { + .free = astream_opus_free, + .meta = astream_opus_meta, + .read = astream_opus_read, + .seek = astream_opus_seek, + .tell = astream_opus_tell, +}; + +static bool astream_opus_init(AudioStream *stream, OggOpusFile *opus) { + if(!op_seekable(opus)) { + log_error("Opus stream is not seekable"); + return false; + } + + ssize_t length = op_pcm_total(opus, -1); + + if(length < 0) { + log_error("Can't determine length of Opus stream"); + return false; + } + + if(length > INT32_MAX) { + log_error("Opus stream is too large"); + return false; + } + + stream->length = length; + stream->spec = astream_spec(AUDIO_F32SYS, 2, OPUS_SAMPLE_RATE); + stream->procs = &astream_opus_procs; + stream->opaque = opus; + + const char *loop_tag = get_opus_tag(opus, "LOOPSTART="); + + if(loop_tag) { + // TODO: we assume this is the sample position, but it may also be in HH:MM:SS.mmm format. + stream->loop_start = strtoll(loop_tag, NULL, 10); + } else { + stream->loop_start = 0; + } + + // LOOPEND/LOOPLENGTH not supported (TODO?) + + return true; +} + +static int opus_rwops_read(void *_stream, unsigned char *_ptr, int _nbytes) { + SDL_RWops *rw = _stream; + return SDL_RWread(rw, _ptr, 1, _nbytes); +} + +static int opus_rwops_seek(void *_stream, opus_int64 _offset, int _whence) { + SDL_RWops *rw = _stream; + return SDL_RWseek(rw, _offset, _whence) < 0 ? -1 : 0; +} + +static opus_int64 opus_rwops_tell(void *_stream) { + SDL_RWops *rw = _stream; + return SDL_RWtell(rw); +} + +static int opus_rwops_close(void *_stream) { + SDL_RWops *rw = _stream; + return SDL_RWclose(rw) < 0 ? EOF : 0; +} + +bool astream_opus_open(AudioStream *stream, SDL_RWops *rw) { + uint8_t buf[128]; + SDL_RWread(rw, buf, 1, sizeof(buf)); + int error = op_test(NULL, buf, sizeof(buf)); + + if(error != 0) { + log_debug("op_test() failed: %i", error); + goto fail; + } + + OggOpusFile *opus = op_open_callbacks( + rw, + &(OpusFileCallbacks) { + .read = opus_rwops_read, + .seek = opus_rwops_seek, + .tell = opus_rwops_tell, + .close = opus_rwops_close, + }, + buf, sizeof(buf), &error + ); + + if(opus == NULL) { + log_error("op_open_callbacks() failed: %i", error); + goto fail; + } + + return astream_opus_init(stream, opus); + +fail: + SDL_RWseek(rw, 0, RW_SEEK_SET); + return false; +} diff --git a/src/util/opuscruft.h b/src/audio/stream/stream_opus.h similarity index 56% rename from src/util/opuscruft.h rename to src/audio/stream/stream_opus.h index 496e5f78..200f3c79 100644 --- a/src/util/opuscruft.h +++ b/src/audio/stream/stream_opus.h @@ -4,15 +4,15 @@ * --- * Copyright (c) 2011-2019, Lukas Weber . * Copyright (c) 2012-2019, Andrei Alexeyev . - */ +*/ -#ifndef IGUARD_util_opuscruft_h -#define IGUARD_util_opuscruft_h +#ifndef IGUARD_audio_stream_stream_opus_h +#define IGUARD_audio_stream_stream_opus_h #include "taisei.h" -#include +#include "stream.h" -extern OpusFileCallbacks opusutil_rwops_callbacks; +bool astream_opus_open(AudioStream *stream, SDL_RWops *rw); -#endif // IGUARD_util_opuscruft_h +#endif // IGUARD_audio_stream_stream_opus_h diff --git a/src/audio/stream/stream_pcm.c b/src/audio/stream/stream_pcm.c new file mode 100644 index 00000000..77f8f567 --- /dev/null +++ b/src/audio/stream/stream_pcm.c @@ -0,0 +1,125 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#include "taisei.h" + +#include "stream_pcm.h" + +#include "util.h" + +static ssize_t astream_pcm_read(AudioStream *stream, size_t buffer_size, void *buffer) { + PCMStreamContext *ctx = NOT_NULL(stream->opaque); + + assume(ctx->pos <= ctx->end); + size_t read_size = ctx->end - ctx->pos; + + if(buffer_size < read_size) { + size_t frame_size = stream->spec.frame_size; + read_size = (buffer_size / frame_size) * frame_size; + } + + if(read_size > 0) { + memcpy(buffer, ctx->pos, read_size); + ctx->pos += read_size; + } + + return read_size; +} + +static ssize_t astream_pcm_seek(AudioStream *stream, size_t pos) { + PCMStreamContext *ctx = NOT_NULL(stream->opaque); + + uint8_t *new_pos = ctx->start + pos * stream->spec.frame_size; + + if(UNLIKELY(new_pos > ctx->end || new_pos < ctx->start)) { + log_error("PCM seek out of range"); + return -1; + } + + ctx->pos = new_pos; + assert(pos == (ctx->pos - ctx->start) / stream->spec.frame_size); + + return pos; +} + +static ssize_t astream_pcm_tell(AudioStream *stream) { + PCMStreamContext *ctx = NOT_NULL(stream->opaque); + return (ctx->pos - ctx->start) / stream->spec.frame_size; +} + +static void astream_pcm_free(AudioStream *stream) { + PCMStreamContext *ctx = NOT_NULL(stream->opaque); + free(ctx); +} + +static void astream_pcm_static_close(AudioStream *stream) { + PCMStreamContext *ctx = NOT_NULL(stream->opaque); + stream->length = 0; + stream->loop_start = 0; + ctx->end = ctx->start = ctx->pos = NULL; +} + +static AudioStreamProcs astream_pcm_procs = { + .free = astream_pcm_free, + .read = astream_pcm_read, + .seek = astream_pcm_seek, + .tell = astream_pcm_tell, +}; + +static AudioStreamProcs astream_pcm_static_procs = { + .free = astream_pcm_static_close, + .read = astream_pcm_read, + .seek = astream_pcm_seek, + .tell = astream_pcm_tell, +}; + +bool astream_pcm_open(AudioStream *stream, const AudioStreamSpec *spec, size_t pcm_buffer_size, void *pcm_buffer, int32_t loop_start) { + stream->procs = &astream_pcm_procs; + stream->opaque = calloc(1, sizeof(PCMStreamContext)); + + if(!astream_pcm_reopen(stream, spec, pcm_buffer_size, pcm_buffer, loop_start)) { + free(stream->opaque); + return false; + } + + return true; +} + +bool astream_pcm_reopen(AudioStream *stream, const AudioStreamSpec *spec, size_t pcm_buffer_size, void *pcm_buffer, int32_t loop_start) { + assert(NOT_NULL(stream->procs)->read == astream_pcm_read); + PCMStreamContext *ctx = NOT_NULL(stream->opaque); + + if(pcm_buffer_size % spec->frame_size) { + log_error("Partial PCM data"); + return false; + } + + ssize_t length = pcm_buffer_size / spec->frame_size; + + if(length > INT32_MAX) { + log_error("PCM stream is too long"); + return false; + } + + stream->spec = *spec; + stream->length = length; + stream->loop_start = loop_start; + + ctx->start = pcm_buffer; + ctx->end = ctx->start + pcm_buffer_size; + ctx->pos = ctx->start; + + assert(ctx->end > ctx->start); + + return true; +} + +void astream_pcm_static_init(StaticPCMAudioStream *stream) { + stream->astream.procs = &astream_pcm_static_procs; + stream->astream.opaque = &stream->ctx; +} diff --git a/src/audio/stream/stream_pcm.h b/src/audio/stream/stream_pcm.h new file mode 100644 index 00000000..2a4f8270 --- /dev/null +++ b/src/audio/stream/stream_pcm.h @@ -0,0 +1,36 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#ifndef IGUARD_audio_stream_stream_pcm_h +#define IGUARD_audio_stream_stream_pcm_h + +#include "taisei.h" + +#include "stream.h" + +typedef struct PCMStreamContext { + uint8_t *start; + uint8_t *end; + uint8_t *pos; +} PCMStreamContext; + +typedef struct StaticPCMAudioStream { + AudioStream astream; + PCMStreamContext ctx; +} StaticPCMAudioStream; + +bool astream_pcm_open(AudioStream *stream, const AudioStreamSpec *spec, size_t pcm_buffer_size, void *pcm_buffer, int32_t loop_start) + attr_nonnull_all; + +bool astream_pcm_reopen(AudioStream *stream, const AudioStreamSpec *spec, size_t pcm_buffer_size, void *pcm_buffer, int32_t loop_start) + attr_nonnull_all; + +void astream_pcm_static_init(StaticPCMAudioStream *stream) + attr_nonnull_all; + +#endif // IGUARD_audio_stream_stream_pcm_h diff --git a/src/boss.c b/src/boss.c index da595efc..0f5c8d94 100644 --- a/src/boss.c +++ b/src/boss.c @@ -868,9 +868,9 @@ static DamageResult ent_damage_boss(EntityInterface *ent, const DamageInfo *dmg) boss->current->hp -= dmg->amount*factor; if(boss->current->hp < boss->current->maxhp * 0.1) { - play_loop("hit1"); + play_sfx_loop("hit1"); } else { - play_loop("hit0"); + play_sfx_loop("hit0"); } return DMG_RESULT_OK; @@ -925,10 +925,10 @@ static void boss_give_spell_bonus(Boss *boss, Attack *a, Player *plr) { stagetext_table_add_numeric(&tbl, "Total", bonus.total); stagetext_end_table(&tbl); - play_sound("spellend"); + play_sfx("spellend"); if(!bonus.failed) { - play_sound("spellclear"); + play_sfx("spellclear"); } } @@ -1039,7 +1039,7 @@ void process_boss(Boss **pboss) { int remaining = boss->current->timeout - time; if(boss->current->type != AT_Move && remaining <= 11*FPS && remaining > 0 && !(time % FPS)) { - play_sound(remaining <= 6*FPS ? "timeout2" : "timeout1"); + play_sfx(remaining <= 6*FPS ? "timeout2" : "timeout1"); } boss_call_rule(boss, time); @@ -1166,7 +1166,7 @@ void process_boss(Boss **pboss) { ); } - play_sound_ex("bossdeath", BOSS_DEATH_DELAY * 2, false); + play_sfx_ex("bossdeath", BOSS_DEATH_DELAY * 2, false); } if(boss_is_player_collision_active(boss) && cabs(boss->pos - global.plr.pos) < BOSS_HURT_RADIUS) { @@ -1334,7 +1334,7 @@ void boss_start_attack(Boss *b, Attack *a) { boss_call_rule(b, EVENT_BIRTH); if(ATTACK_IS_SPELL(a->type)) { - play_sound(a->type == AT_ExtraSpell ? "charge_extra" : "charge_generic"); + play_sfx(a->type == AT_ExtraSpell ? "charge_extra" : "charge_generic"); for(int i = 0; i < 10+5*(a->type == AT_ExtraSpell); i++) { RNG_ARRAY(rng, 4); diff --git a/src/common_tasks.c b/src/common_tasks.c index 9756089b..5485091a 100644 --- a/src/common_tasks.c +++ b/src/common_tasks.c @@ -87,10 +87,10 @@ DEFINE_EXTERN_TASK(common_charge) { const char *snd_charge = ARGS.sound.charge; const char *snd_discharge = ARGS.sound.discharge; - SoundID charge_snd_id = 0; + SFXPlayID charge_snd_id = 0; if(snd_charge) { - charge_snd_id = play_sound(snd_charge); + charge_snd_id = play_sfx(snd_charge); } Color local_color; @@ -152,7 +152,7 @@ DEFINE_EXTERN_TASK(common_charge) { } if(snd_discharge) { - replace_sound(charge_snd_id, snd_discharge); + replace_sfx(charge_snd_id, snd_discharge); } else { stop_sound(charge_snd_id); } diff --git a/src/credits.c b/src/credits.c index f971a852..3ed125a3 100644 --- a/src/credits.c +++ b/src/credits.c @@ -244,7 +244,7 @@ static void credits_init(void) { // presumably not desired anyway. credits.skipable = progress_times_any_ending_achieved() > 1; - start_bgm("credits"); + audio_bgm_play(res_bgm("credits"), false, 0, 0); } static double entry_height(CreditsEntry *e, double *head, double *body) { diff --git a/src/ending.c b/src/ending.c index a6ad4615..17b9c14a 100644 --- a/src/ending.c +++ b/src/ending.c @@ -443,7 +443,7 @@ static LogicFrameAction ending_logic_frame(void *arg) { if(e->pos < e->entries.num_elements - 1 && global.frames >= dynarray_get(&e->entries, e->pos + 1).time) { e->pos++; if(e->pos == e->entries.num_elements - 1) { - fade_bgm((FPS * ENDING_FADE_OUT) / 4000.0); + audio_bgm_stop((FPS * ENDING_FADE_OUT) / 4000.0); set_transition(TransFadeWhite, ENDING_FADE_OUT, ENDING_FADE_OUT); e->duration = global.frames+ENDING_FADE_OUT; } @@ -491,7 +491,7 @@ void ending_enter(CallChain next) { global.frames = 0; set_ortho(SCREEN_W, SCREEN_H); - start_bgm("ending"); + audio_bgm_play(res_bgm("ending"), true, 0, 0); eventloop_enter(e, ending_logic_frame, ending_render_frame, ending_loop_end, FPS); } diff --git a/src/enemy.c b/src/enemy.c index 2cb42540..89225f0c 100644 --- a/src/enemy.c +++ b/src/enemy.c @@ -157,7 +157,7 @@ static void* _delete_enemy(ListAnchor *enemies, List* enemy, void *arg) { Enemy *e = (Enemy*)enemy; if(e->hp <= 0 && e->hp != ENEMY_IMMUNE) { - play_sound("enemydeath"); + play_sfx("enemydeath"); enemy_death_effect(e->pos); for(Projectile *p = global.projs.first; p; p = p->next) { @@ -383,9 +383,9 @@ static DamageResult ent_damage_enemy(EntityInterface *ienemy, const DamageInfo * } if(enemy->hp < enemy->spawn_hp * 0.1) { - play_loop("hit1"); + play_sfx_loop("hit1"); } else { - play_loop("hit0"); + play_sfx_loop("hit0"); } return DMG_RESULT_OK; diff --git a/src/eventloop/eventloop.c b/src/eventloop/eventloop.c index 0fdd1aab..9ea2f5e9 100644 --- a/src/eventloop/eventloop.c +++ b/src/eventloop/eventloop.c @@ -81,14 +81,15 @@ LogicFrameAction handle_logic(LoopFrame **pframe, const FrameTimes *ftimes) { do { lframe_action = run_logic_frame(*pframe); - if(lframe_action == LFRAME_SKIP && taisei_is_skip_mode_enabled()) { + if(lframe_action == LFRAME_SKIP_ALWAYS) { + lframe_action = LFRAME_SKIP; cnt = 0; } while(evloop.stack_ptr != *pframe) { *pframe = evloop.stack_ptr; lframe_action = run_logic_frame(*pframe); - cnt = UINT_MAX; // break out of the outer loop + cnt = UINT_MAX - 1; // break out of the outer loop } } while( lframe_action == LFRAME_SKIP && @@ -108,10 +109,6 @@ LogicFrameAction handle_logic(LoopFrame **pframe, const FrameTimes *ftimes) { } RenderFrameAction run_render_frame(LoopFrame *frame) { - if(taisei_is_skip_mode_enabled()) { - return RFRAME_DROP; - } - attr_unused LoopFrame *stack_prev = evloop.stack_ptr; r_framebuffer_clear(NULL, CLEAR_ALL, RGBA(0, 0, 0, 1), 1); RenderFrameAction a = frame->render(frame->context); diff --git a/src/eventloop/eventloop.h b/src/eventloop/eventloop.h index 6a876d11..60bfe507 100644 --- a/src/eventloop/eventloop.h +++ b/src/eventloop/eventloop.h @@ -21,6 +21,7 @@ typedef enum RenderFrameAction { typedef enum LogicFrameAction { LFRAME_WAIT, LFRAME_SKIP, + LFRAME_SKIP_ALWAYS, LFRAME_STOP, } LogicFrameAction; diff --git a/src/events.h b/src/events.h index 093f2d79..5fcf20ee 100644 --- a/src/events.h +++ b/src/events.h @@ -45,6 +45,8 @@ typedef enum { TE_CLIPBOARD_PASTE, + TE_AUDIO_BGM_STARTED, + NUM_TAISEI_EVENTS } TaiseiEvent; diff --git a/src/global.c b/src/global.c index 147302c0..63094abe 100644 --- a/src/global.c +++ b/src/global.c @@ -61,19 +61,3 @@ void taisei_commit_persistent_data(void) { progress_save(); vfs_sync(VFS_SYNC_STORE, NO_CALLCHAIN); } - -#ifdef DEBUG -#include "audio/audio.h" -static bool _skip_mode; -bool taisei_is_skip_mode_enabled(void) { return _skip_mode; } -void taisei_set_skip_mode(bool state) { - if(_skip_mode && !state && current_bgm.started_at >= 0) { - audio_music_set_position((global.frames - current_bgm.started_at) / (double)FPS); - } - - _skip_mode = state; -} -#else -bool taisei_is_skip_mode_enabled(void) { return false; } -void taisei_set_skip_mode(bool state) { } -#endif diff --git a/src/global.h b/src/global.h index a4c7c7ee..5dc6a09c 100644 --- a/src/global.h +++ b/src/global.h @@ -146,9 +146,6 @@ void taisei_quit(void); bool taisei_quit_requested(void); void taisei_commit_persistent_data(void); -bool taisei_is_skip_mode_enabled(void); -void taisei_set_skip_mode(bool state); - // XXX: Move this somewhere? bool gamekeypressed(KeyIndex key); diff --git a/src/item.c b/src/item.c index 9f872d86..bf85809a 100644 --- a/src/item.c +++ b/src/item.c @@ -289,30 +289,30 @@ void process_items(void) { player_add_power(&global.plr, POWER_VALUE); player_add_points(&global.plr, 25, item->pos); player_extend_powersurge(&global.plr, PLR_POWERSURGE_POSITIVE_GAIN*3, PLR_POWERSURGE_NEGATIVE_GAIN*3); - play_sound("item_generic"); + play_sfx("item_generic"); break; case ITEM_POWER_MINI: player_add_power(&global.plr, POWER_VALUE_MINI); player_add_points(&global.plr, 5, item->pos); - play_sound("item_generic"); + play_sfx("item_generic"); break; case ITEM_SURGE: player_extend_powersurge(&global.plr, PLR_POWERSURGE_POSITIVE_GAIN, PLR_POWERSURGE_NEGATIVE_GAIN); player_add_points(&global.plr, 25, item->pos); - play_sound("item_generic"); + play_sfx("item_generic"); break; case ITEM_POINTS: player_add_points(&global.plr, round(global.plr.point_item_value * item->pickup_value), item->pos); - play_sound("item_generic"); + play_sfx("item_generic"); break; case ITEM_PIV: player_add_piv(&global.plr, 1, item->pos); - play_sound("item_generic"); + play_sfx("item_generic"); break; case ITEM_VOLTAGE: player_add_voltage(&global.plr, 1); player_add_piv(&global.plr, 10, item->pos); - play_sound("item_generic"); + play_sfx("item_generic"); break; case ITEM_LIFE: player_add_lives(&global.plr, 1); diff --git a/src/menu/charselect.c b/src/menu/charselect.c index dc039120..97c3beff 100644 --- a/src/menu/charselect.c +++ b/src/menu/charselect.c @@ -335,25 +335,25 @@ static bool char_menu_input_handler(SDL_Event *event, void *arg) { int prev_cursor = menu->cursor; if(type == TE_MENU_CURSOR_RIGHT) { - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); menu->cursor++; } else if(type == TE_MENU_CURSOR_LEFT) { - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); menu->cursor--; } else if(type == TE_MENU_CURSOR_DOWN) { - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); ctx->subshot++; } else if(type == TE_MENU_CURSOR_UP) { - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); ctx->subshot--; } else if(type == TE_MENU_ACCEPT) { - play_ui_sound("shot_special1"); + play_sfx_ui("shot_special1"); menu->selected = menu->cursor; menu->transition_in_time = FADE_TIME * 1.5; menu->transition_out_time = FADE_TIME * 3; close_menu(menu); } else if(type == TE_MENU_ABORT) { - play_ui_sound("hit"); + play_sfx_ui("hit"); close_menu(menu); } diff --git a/src/menu/common.c b/src/menu/common.c index 0b380e27..a1895278 100644 --- a/src/menu/common.c +++ b/src/menu/common.c @@ -171,7 +171,7 @@ static void start_game_do_cleanup(CallChainResult ccr) { global.gameover = GAMEOVER_NONE; replay_destroy(&global.replay); main_menu_update_practice_menus(); - start_bgm("menu"); + audio_bgm_play(res_bgm("menu"), true, 0, 0); } void start_game(MenuData *m, void *arg) { diff --git a/src/menu/mainmenu.c b/src/menu/mainmenu.c index 723ca8a9..8aeec261 100644 --- a/src/menu/mainmenu.c +++ b/src/menu/mainmenu.c @@ -61,7 +61,7 @@ void main_menu_update_practice_menus(void) { } static void begin_main_menu(MenuData *m) { - start_bgm("menu"); + audio_bgm_play(res_bgm("menu"), true, 0, 0); } static void update_main_menu(MenuData *menu) { @@ -79,7 +79,7 @@ static bool main_menu_input_handler(SDL_Event *event, void *arg) { static hrtime_t last_abort_time = 0; if(te == TE_MENU_ABORT) { - play_ui_sound("hit"); + play_sfx_ui("hit"); m->cursor = m->entries.num_elements - 1; hrtime_t t = time_get(); @@ -134,7 +134,7 @@ MenuData* create_main_menu(void) { main_menu_update_practice_menus(); progress_unlock_bgm("menu"); - start_bgm("menu"); + audio_bgm_play(res_bgm("menu"), true, 0, 0); return m; } diff --git a/src/menu/menu.c b/src/menu/menu.c index 27e08a57..7c490ffd 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -114,7 +114,7 @@ bool menu_input_handler(SDL_Event *event, void *arg) { switch(te) { case TE_MENU_CURSOR_DOWN: - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); do { if(++menu->cursor >= menu->entries.num_elements) { menu->cursor = 0; @@ -124,7 +124,7 @@ bool menu_input_handler(SDL_Event *event, void *arg) { return true; case TE_MENU_CURSOR_UP: - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); do { if(--menu->cursor < 0) { menu->cursor = menu->entries.num_elements - 1; @@ -134,7 +134,7 @@ bool menu_input_handler(SDL_Event *event, void *arg) { return true; case TE_MENU_ACCEPT: - play_ui_sound("shot_special1"); + play_sfx_ui("shot_special1"); if(dynarray_get(&menu->entries, menu->cursor).action) { menu->selected = menu->cursor; close_menu(menu); @@ -143,7 +143,7 @@ bool menu_input_handler(SDL_Event *event, void *arg) { return true; case TE_MENU_ABORT: - play_ui_sound("hit"); + play_sfx_ui("hit"); if(menu->flags & MF_Abortable) { menu->selected = -1; close_menu(menu); diff --git a/src/menu/musicroom.c b/src/menu/musicroom.c index c5dd2e73..bf943cbe 100644 --- a/src/menu/musicroom.c +++ b/src/menu/musicroom.c @@ -10,7 +10,6 @@ #include "musicroom.h" #include "resource/resource.h" -#include "resource/bgm_metadata.h" #include "resource/font.h" #include "audio/audio.h" #include "progress.h" @@ -30,8 +29,7 @@ enum { }; typedef struct MusicEntryParam { - MusicMetadata *bgm_meta; - const char *bgm; + BGM *bgm; ShaderProgram *text_shader; uint8_t state; } MusicEntryParam; @@ -54,6 +52,8 @@ static void musicroom_logic(MenuData *m) { fapproach_asymptotic_p(&m->drawdata[3], 0, 0.2, 1e-5); } + BGM *current_bgm = audio_bgm_current(); + dynarray_foreach(&m->entries, int i, MenuEntry *e, { MusicEntryParam *p = e->arg; @@ -62,11 +62,7 @@ static void musicroom_logic(MenuData *m) { p->state &= ~MSTATE_CONFIRM; } - if( - current_bgm.music && - current_bgm.music->meta && - current_bgm.music->meta == p->bgm_meta - ) { + if(current_bgm && current_bgm == p->bgm) { p->state |= MSTATE_PLAYING; } else { p->state &= ~MSTATE_PLAYING; @@ -163,7 +159,6 @@ static void musicroom_draw(MenuData *m) { const char *comment; MusicEntryParam *p = e->arg; - MusicMetadata *meta = p->bgm_meta; Color *clr = RGBA(a, a, a, a); if(p->state & MSTATE_CONFIRM) { @@ -175,9 +170,7 @@ static void musicroom_draw(MenuData *m) { clr->b *= 0.2; } else if(!(p->state & MSTATE_COMMENT_VISIBLE)) { continue; - } else if(meta && meta->comment) { - comment = meta->comment; - } else { + } else if(!(comment = bgm_get_comment(p->bgm))) { comment = "\nNo comment available"; } @@ -189,19 +182,23 @@ static void musicroom_draw(MenuData *m) { .align = ALIGN_CENTER, }); - if(meta->artist && (p->state & MSTATE_COMMENT_VISIBLE)) { - const char *prefix = "— "; - char buf[strlen(prefix) + strlen(meta->artist) + 1]; - strcpy(buf, prefix); - strcat(buf, meta->artist); + if(p->state & MSTATE_COMMENT_VISIBLE) { + const char *artist = bgm_get_artist(p->bgm); - text_draw(buf, &(TextParams) { - .pos = { SCREEN_W - text_x, SCREEN_H + comment_offset - font_get_lineskip(text_font) }, - .font_ptr = text_font, - .shader_ptr = text_shader, - .color = RGBA(a, a, a, a), - .align = ALIGN_RIGHT, - }); + if(artist) { + const char *prefix = "— "; + char buf[strlen(prefix) + strlen(artist) + 1]; + strcpy(buf, prefix); + strcat(buf, artist); + + text_draw(buf, &(TextParams) { + .pos = { SCREEN_W - text_x, SCREEN_H + comment_offset - font_get_lineskip(text_font) }, + .font_ptr = text_font, + .shader_ptr = text_shader, + .color = RGBA(a, a, a, a), + .align = ALIGN_RIGHT, + }); + } } }); } @@ -212,22 +209,30 @@ static void action_play_bgm(MenuData *m, void *arg) { if(p->state & (MSTATE_CONFIRM | MSTATE_UNLOCKED)) { p->state &= ~MSTATE_CONFIRM; - preload_resource(RES_BGM, p->bgm, RESF_OPTIONAL); - start_bgm(p->bgm); + audio_bgm_play(p->bgm, true, 0, 0); } else if (!(p->state & MSTATE_PLAYING)) { p->state |= MSTATE_CONFIRM; } } -static void add_bgm(MenuData *m, const char *bgm) { - MusicMetadata *meta = get_resource_data(RES_BGM_METADATA, bgm, RESF_OPTIONAL | RESF_PRELOAD); - const char *title = (meta && meta->title) ? meta->title : "Unknown track"; +static void add_bgm(MenuData *m, const char *bgm_name, bool preload) { + if(preload) { + preload_resource(RES_BGM, bgm_name, RESF_OPTIONAL); + return; + } + + BGM *bgm = res_bgm(bgm_name); + const char *title = bgm ? bgm_get_title(bgm) : NULL; + + if(!title) { + title = "Unknown track"; + } + MusicEntryParam *p = calloc(1, sizeof(*p)); p->bgm = bgm; - p->bgm_meta = meta; p->text_shader = res_shader("text_default"); - if(progress_is_bgm_unlocked(bgm)) { + if(progress_is_bgm_unlocked(bgm_name)) { p->state |= MSTATE_UNLOCKED; } @@ -249,25 +254,28 @@ MenuData* create_musicroom_menu(void) { m->transition = TransFadeBlack; m->flags = MF_Abortable; - add_bgm(m, "menu"); - add_bgm(m, "stage1"); - add_bgm(m, "stage1boss"); - add_bgm(m, "stage2"); - add_bgm(m, "stage2boss"); - add_bgm(m, "stage3"); - add_bgm(m, "stage3boss"); - add_bgm(m, "scuttle"); - add_bgm(m, "stage4"); - add_bgm(m, "stage4boss"); - add_bgm(m, "stage5"); - add_bgm(m, "stage5boss"); - add_bgm(m, "bonus0"); - add_bgm(m, "stage6"); - add_bgm(m, "stage6boss_phase1"); - add_bgm(m, "stage6boss_phase2"); - add_bgm(m, "stage6boss_phase3"); - add_bgm(m, "ending"); - add_bgm(m, "credits"); + for(int preload = 1; preload >= 0; --preload) { + add_bgm(m, "menu", preload); + add_bgm(m, "stage1", preload); + add_bgm(m, "stage1boss", preload); + add_bgm(m, "stage2", preload); + add_bgm(m, "stage2boss", preload); + add_bgm(m, "stage3", preload); + add_bgm(m, "stage3boss", preload); + add_bgm(m, "scuttle", preload); + add_bgm(m, "stage4", preload); + add_bgm(m, "stage4boss", preload); + add_bgm(m, "stage5", preload); + add_bgm(m, "stage5boss", preload); + add_bgm(m, "bonus0", preload); + add_bgm(m, "stage6", preload); + add_bgm(m, "stage6boss_phase1", preload); + add_bgm(m, "stage6boss_phase2", preload); + add_bgm(m, "stage6boss_phase3", preload); + add_bgm(m, "ending", preload); + add_bgm(m, "credits", preload); + } + add_menu_separator(m); add_menu_entry(m, "Back", menu_action_close, NULL); diff --git a/src/menu/options.c b/src/menu/options.c index 7dad79c0..006bb7ea 100644 --- a/src/menu/options.c +++ b/src/menu/options.c @@ -1338,7 +1338,7 @@ static bool options_rebind_input_handler(SDL_Event *event, void *arg) { if(b->type != BT_KeyBinding) { if(esc) { b->blockinput = false; - play_ui_sound("hit"); + play_sfx_ui("hit"); } return true; @@ -1352,9 +1352,9 @@ static bool options_rebind_input_handler(SDL_Event *event, void *arg) { } config_set_int(b->configentry, scan); - play_ui_sound("shot_special1"); + play_sfx_ui("shot_special1"); } else { - play_ui_sound("hit"); + play_sfx_ui("hit"); } b->blockinput = false; @@ -1362,7 +1362,7 @@ static bool options_rebind_input_handler(SDL_Event *event, void *arg) { } if(t == MAKE_TAISEI_EVENT(TE_MENU_ABORT)) { - play_ui_sound("hit"); + play_sfx_ui("hit"); b->blockinput = false; return true; } @@ -1383,7 +1383,7 @@ static bool options_rebind_input_handler(SDL_Event *event, void *arg) { if(button == GAMEPAD_BUTTON_BACK || button == GAMEPAD_BUTTON_START) { b->blockinput = false; - play_ui_sound("hit"); + play_sfx_ui("hit"); return true; } @@ -1395,7 +1395,7 @@ static bool options_rebind_input_handler(SDL_Event *event, void *arg) { config_set_int(b->configentry, button); b->blockinput = false; - play_ui_sound("shot_special1"); + play_sfx_ui("shot_special1"); return true; } @@ -1465,7 +1465,7 @@ static bool options_text_input_handler(SDL_Event *event, void *arg) { } free(text_allocated); - play_ui_sound(snd); + play_sfx_ui(snd); return true; } @@ -1473,14 +1473,14 @@ static bool options_text_input_handler(SDL_Event *event, void *arg) { SDL_Scancode scan = event->key.keysym.scancode; if(scan == SDL_SCANCODE_ESCAPE) { - play_ui_sound("hit"); + play_sfx_ui("hit"); stralloc(&b->strvalue, config_get_str(b->configentry)); b->blockinput = false; } else if(scan == SDL_SCANCODE_RETURN) { if(*b->strvalue) { - play_ui_sound("shot_special1"); + play_sfx_ui("shot_special1"); } else { - play_ui_sound("hit"); + play_sfx_ui("hit"); stralloc(&b->strvalue, "Player"); } @@ -1494,10 +1494,10 @@ static bool options_text_input_handler(SDL_Event *event, void *arg) { uint32_t *u = utf8_to_ucs4_alloc(b->strvalue); if(*u) { - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); *(ucs4chr(u, 0) - 1) = 0; } else { - play_ui_sound("hit"); + play_sfx_ui("hit"); } free(b->strvalue); @@ -1519,7 +1519,7 @@ static bool options_input_handler(SDL_Event *event, void *arg) { switch(type) { case TE_MENU_CURSOR_UP: case TE_MENU_CURSOR_DOWN: - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); menu->drawdata[3] = 10; do { menu->cursor += (type == TE_MENU_CURSOR_UP ? -1 : 1); @@ -1537,7 +1537,7 @@ static bool options_input_handler(SDL_Event *event, void *arg) { case TE_MENU_CURSOR_LEFT: case TE_MENU_CURSOR_RIGHT: - play_ui_sound("generic_shot"); + play_sfx_ui("generic_shot"); bool next = (type == TE_MENU_CURSOR_RIGHT); if(bind) { @@ -1565,7 +1565,7 @@ static bool options_input_handler(SDL_Event *event, void *arg) { break; case TE_MENU_ACCEPT: - play_ui_sound("shot_special1"); + play_sfx_ui("shot_special1"); menu->selected = menu->cursor; if(bind) switch(bind->type) { @@ -1585,7 +1585,7 @@ static bool options_input_handler(SDL_Event *event, void *arg) { break; case TE_MENU_ABORT: - play_ui_sound("hit"); + play_sfx_ui("hit"); menu->selected = -1; close_menu(menu); break; diff --git a/src/menu/replayview.c b/src/menu/replayview.c index b283169f..4bd56445 100644 --- a/src/menu/replayview.c +++ b/src/menu/replayview.c @@ -54,7 +54,7 @@ typedef struct { } startrpy_arg_t; static void on_replay_finished(CallChainResult ccr) { - start_bgm("menu"); + audio_bgm_play(res_bgm("menu"), true, 0, 0); } static void really_start_replay(void *varg) { diff --git a/src/meson.build b/src/meson.build index 424aaeb8..f364f0af 100644 --- a/src/meson.build +++ b/src/meson.build @@ -153,10 +153,6 @@ taisei_deps += [ util_deps, ] -if taisei_deps.contains(dep_opusfile) - taisei_src += util_opus_src -endif - taisei_basename = (macos_app_bundle ? 'Taisei' : 'taisei') if host_machine.system() == 'emscripten' diff --git a/src/player.c b/src/player.c index b05c584d..7d66b9bf 100644 --- a/src/player.c +++ b/src/player.c @@ -117,7 +117,7 @@ bool player_set_power(Player *plr, short npow) { plr->power_overflow = pow_overflow; if((oldpow + oldpow_over) / 100 < (pow_base + pow_overflow) / 100) { - play_sound("powerup"); + play_sfx("powerup"); } if(plr->power == PLR_MAX_POWER && oldpow < PLR_MAX_POWER) { @@ -692,7 +692,7 @@ static bool player_powersurge(Player *plr) { plr->powersurge.damage_accum = 0; player_add_power(plr, -PLR_POWERSURGE_POWERCOST); - play_sound("powersurge_start"); + play_sfx("powersurge_start"); collect_all_items(1); stagetext_add("Power Surge!", plr->pos - 64 * I, ALIGN_CENTER, res_font("standard"), RGBA(0.75, 0.75, 0.75, 0.75), 0, 45, 10, 20); @@ -769,7 +769,7 @@ static void player_powersurge_expired(Player *plr) { Sprite *blast = res_sprite("part/blast_huge_halo"); float scale = 2 * bonus.discharge_range / blast->w; - play_sound("powersurge_end"); + play_sfx("powersurge_end"); PARTICLE( .size = 1+I, @@ -947,7 +947,7 @@ void player_death(Player *plr) { return; } - play_sound("death"); + play_sfx("death"); for(int i = 0; i < 60; i++) { RNG_ARRAY(R, 2); @@ -1369,7 +1369,7 @@ void player_graze(Player *plr, cmplx pos, int pts, int effect_intensity, const C pos = (pos + plr->pos) * 0.5; player_add_points(plr, pts, pos); - play_sound("graze"); + play_sfx("graze"); Color *c = COLOR_COPY(color); color_add(c, RGBA(1, 1, 1, 1)); @@ -1411,13 +1411,13 @@ static void player_add_fragments(Player *plr, int frags, int *pwhole, int *pfrag *pfrags %= maxfrags; if(up) { - play_sound(upsnd); + play_sfx(upsnd); } if(frags) { // FIXME: when we have the extra life/bomb sounds, // don't play this if upsnd was just played. - play_sound(fragsnd); + play_sfx(fragsnd); } if(*pwhole >= maxwhole) { diff --git a/src/plrmodes/marisa.c b/src/plrmodes/marisa.c index 0d74b1e3..bf88b1b6 100644 --- a/src/plrmodes/marisa.c +++ b/src/plrmodes/marisa.c @@ -57,7 +57,7 @@ DEFINE_EXTERN_TASK(marisa_common_shot_forward) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.shoot); - play_loop("generic_shot"); + play_sfx_loop("generic_shot"); for(int i = -1; i < 2; i += 2) { PROJECTILE( diff --git a/src/plrmodes/marisa_a.c b/src/plrmodes/marisa_a.c index 2202ec7b..3c5d628d 100644 --- a/src/plrmodes/marisa_a.c +++ b/src/plrmodes/marisa_a.c @@ -617,7 +617,7 @@ TASK(marisa_laser_bomb_handler, { MarisaAController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.bomb_used); - play_sound("bomb_marisa_a"); + play_sfx("bomb_marisa_a"); INVOKE_SUBTASK(marisa_laser_bomb_background, ctrl); INVOKE_SUBTASK(marisa_laser_masterspark, ctrl); } diff --git a/src/plrmodes/marisa_b.c b/src/plrmodes/marisa_b.c index 4388674e..8caec345 100644 --- a/src/plrmodes/marisa_b.c +++ b/src/plrmodes/marisa_b.c @@ -465,7 +465,7 @@ TASK(marisa_star_bomb_handler, { MarisaBController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.bomb_used); - play_sound("bomb_marisa_b"); + play_sfx("bomb_marisa_b"); INVOKE_SUBTASK(marisa_star_bomb_background, ctrl); INVOKE_SUBTASK(marisa_star_bomb_controller, ctrl); } diff --git a/src/plrmodes/reimu_a.c b/src/plrmodes/reimu_a.c index c37612f1..a1136aeb 100644 --- a/src/plrmodes/reimu_a.c +++ b/src/plrmodes/reimu_a.c @@ -214,8 +214,8 @@ static void reimu_spirit_bomb_impact_balls(cmplx pos, int count) { TASK(reimu_spirit_bomb_orb_impact, { BoxedProjectile orb; }) { cmplx pos = ENT_UNBOX(ARGS.orb)->pos; - play_sound("boom"); - play_sound("spellend"); + play_sfx("boom"); + play_sfx("spellend"); stage_shake_view(200); @@ -357,7 +357,7 @@ TASK(reimu_spirit_bomb_orb, { BoxedPlayer plr; int index; real angle; }) { if(t == circletime) { target_homing = global.plr.pos - 256*I; orb->flags &= ~PFLAG_NOCOLLISION; - play_sound("redirect"); + play_sfx("redirect"); } cmplx target_circle = plr->pos + 10 * sqrt(t) * dir * (1 + 0.1 * sin(0.2 * t)); @@ -428,8 +428,8 @@ TASK(reimu_spirit_bomb_handler, { ReimuAController *ctrl; }) { WAIT_EVENT_OR_DIE(&plr->events.bomb_used); INVOKE_SUBTASK(reimu_spirit_bomb_background, ctrl); - play_sound("bomb_reimu_a"); - play_sound("bomb_marisa_b"); + play_sfx("bomb_reimu_a"); + play_sfx("bomb_marisa_b"); for(int i = 0; i < orb_count; i++) { INVOKE_TASK_DELAYED(1, reimu_spirit_bomb_orb, ENT_BOX(plr), i, M_TAU/orb_count*i); @@ -770,7 +770,7 @@ TASK(reimu_spirit_shot_forward, { ReimuAController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.shoot); - play_loop("generic_shot"); + play_sfx_loop("generic_shot"); INVOKE_TASK(reimu_spirit_ofuda, .pos = plr->pos + 10 * dir - 15.0*I, .vel = -20*I, @@ -783,7 +783,7 @@ TASK(reimu_spirit_shot_forward, { ReimuAController *ctrl; }) { } TASK(reimu_spirit_shot_volley_bullet, { Player *plr; cmplx offset; cmplx vel; real damage; ShaderProgram *shader; }) { - play_loop("generic_shot"); + play_sfx_loop("generic_shot"); PROJECTILE( .proto = pp_hakurei_seal, diff --git a/src/plrmodes/reimu_b.c b/src/plrmodes/reimu_b.c index 3d046728..4f8b8141 100644 --- a/src/plrmodes/reimu_b.c +++ b/src/plrmodes/reimu_b.c @@ -143,7 +143,7 @@ TASK(reimu_dream_gap_bomb_projectile, { TASK(reimu_dream_bomb_noise, { BoxedPlayer plr; }) { Player *plr = TASK_BIND(ARGS.plr); do { - play_sound("boon"); + play_sfx("boon"); WAIT(16); } while(player_is_bomb_active(plr)); } @@ -192,7 +192,7 @@ TASK(reimu_dream_bomb_handler, { ReimuBController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.bomb_used); - play_sound("bomb_marisa_a"); + play_sfx("bomb_marisa_a"); INVOKE_SUBTASK(reimu_dream_bomb_noise, ENT_BOX(plr)); INVOKE_SUBTASK(reimu_dream_bomb_barrage, ctrl); INVOKE_SUBTASK(reimu_dream_bomb_background, ctrl); @@ -568,7 +568,7 @@ TASK(reimu_dream_shot_forward, { ReimuBController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.shoot); - play_loop("generic_shot"); + play_sfx_loop("generic_shot"); for(int i = -1; i < 2; i += 2) { cmplx shot_dir = i * ((plr->inputflags & INFLAG_FOCUS) ? 1 : I); diff --git a/src/plrmodes/youmu_a.c b/src/plrmodes/youmu_a.c index 6f930835..01ab1de0 100644 --- a/src/plrmodes/youmu_a.c +++ b/src/plrmodes/youmu_a.c @@ -548,7 +548,7 @@ TASK(youmu_mirror_bomb_handler, { YoumuAController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.bomb_used); - play_sound("bomb_youmu_b"); + play_sfx("bomb_youmu_b"); INVOKE_SUBTASK(youmu_mirror_bomb_controller, ctrl); INVOKE_SUBTASK(youmu_common_bomb_background, ENT_BOX(plr), &ctrl->bomb_bg); INVOKE_SUBTASK(youmu_mirror_bomb_postprocess, ctrl); @@ -562,7 +562,7 @@ TASK(youmu_mirror_shot_forward, { YoumuAController *ctrl; }) { for(int t = 0;;) { WAIT_EVENT_OR_DIE(&plr->events.shoot); - play_loop("generic_shot"); + play_sfx_loop("generic_shot"); cmplx v = -20 * I; int power_rank = plr->power / 100; diff --git a/src/plrmodes/youmu_b.c b/src/plrmodes/youmu_b.c index 1d8636c0..3d6972b6 100644 --- a/src/plrmodes/youmu_b.c +++ b/src/plrmodes/youmu_b.c @@ -118,7 +118,7 @@ TASK(youmu_haunting_shot_basic, { YoumuBController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.shoot); - play_loop("generic_shot"); + play_sfx_loop("generic_shot"); cmplx v = -20 * I; @@ -552,7 +552,7 @@ TASK(youmu_haunting_bomb_handler, { YoumuBController *ctrl; }) { for(;;) { WAIT_EVENT_OR_DIE(&plr->events.bomb_used); - play_sound("bomb_youmu_b"); + play_sfx("bomb_youmu_b"); INVOKE_SUBTASK(youmu_haunting_bomb_controller, ctrl); INVOKE_SUBTASK(youmu_common_bomb_background, ENT_BOX(plr), &ctrl->bomb_bg); } diff --git a/src/resource/bgm.c b/src/resource/bgm.c index cdc51572..346ad15d 100644 --- a/src/resource/bgm.c +++ b/src/resource/bgm.c @@ -22,45 +22,59 @@ static bool check_bgm_path(const char *path) { return sfxbgm_check_path(BGM_PATH_PREFIX, path, true); } -static MusicImpl *load_music(const char *path) { - if(!path) { +static void load_bgm(ResourceLoadState *st) { + BGM *bgm = _a_backend.funcs.bgm_load(st->path); + + if(bgm) { + res_load_finished(st, bgm); + } else { + log_error("Failed to load bgm '%s'", st->path); + res_load_failed(st); + } +} + +static void unload_bgm(void *vbgm) { + _a_backend.funcs.bgm_unload(vbgm); +} + +const char *bgm_get_title(BGM *bgm) { + if(!bgm) { return NULL; } - return _a_backend.funcs.music_load(path); + return _a_backend.funcs.object.bgm.get_title(bgm); } -static void load_bgm(ResourceLoadState *st) { - Music *mus = calloc(1, sizeof(Music)); - - if(strendswith(st->path, ".bgm")) { - mus->meta = get_resource_data(RES_BGM_METADATA, st->name, st->flags); - - if(mus->meta) { - mus->impl = load_music(mus->meta->loop_path); - } - } else { - mus->impl = load_music(st->path); +const char *bgm_get_artist(BGM *bgm) { + if(!bgm) { + return NULL; } - if(!mus->impl) { - free(mus); - mus = NULL; - log_error("Failed to load bgm '%s'", st->path); - res_load_failed(st); - } else { - if(mus->meta && mus->meta->loop_point > 0) { - _a_backend.funcs.music_set_loop_point(mus->impl, mus->meta->loop_point); - } - - res_load_finished(st, mus); - } + return _a_backend.funcs.object.bgm.get_artist(bgm); } -static void unload_bgm(void *vmus) { - Music *mus = vmus; - _a_backend.funcs.music_unload(mus->impl); - free(mus); +const char *bgm_get_comment(BGM *bgm) { + if(!bgm) { + return NULL; + } + + return _a_backend.funcs.object.bgm.get_comment(bgm); +} + +double bgm_get_duration(BGM *bgm) { + if(!bgm) { + return -1; + } + + return _a_backend.funcs.object.bgm.get_duration(bgm); +} + +double bgm_get_loop_start(BGM *bgm) { + if(!bgm) { + return -1; + } + + return _a_backend.funcs.object.bgm.get_loop_start(bgm); } ResourceHandler bgm_res_handler = { diff --git a/src/resource/bgm.h b/src/resource/bgm.h index aaff62e3..dcd3d16b 100644 --- a/src/resource/bgm.h +++ b/src/resource/bgm.h @@ -17,8 +17,14 @@ extern ResourceHandler bgm_res_handler; #define BGM_PATH_PREFIX "res/bgm/" -typedef struct Music Music; +typedef struct BGM BGM; -DEFINE_OPTIONAL_RESOURCE_GETTER(Music, res_bgm, RES_BGM) +DEFINE_OPTIONAL_RESOURCE_GETTER(BGM, res_bgm, RES_BGM) + +const char *bgm_get_title(BGM *bgm); +const char *bgm_get_artist(BGM *bgm); +const char *bgm_get_comment(BGM *bgm); +double bgm_get_duration(BGM *bgm); +double bgm_get_loop_start(BGM *bgm); #endif // IGUARD_resource_bgm_h diff --git a/src/resource/bgm_metadata.c b/src/resource/bgm_metadata.c deleted file mode 100644 index f4a5da25..00000000 --- a/src/resource/bgm_metadata.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This software is licensed under the terms of the MIT License. - * See COPYING for further information. - * --- - * Copyright (c) 2011-2019, Lukas Weber . - * Copyright (c) 2012-2019, Andrei Alexeyev . - */ - -#include "taisei.h" - -#include "bgm_metadata.h" -#include "bgm.h" -#include "util.h" - -static char *bgm_meta_path(const char *name) { - return strjoin(BGM_PATH_PREFIX, name, ".bgm", NULL); -} - -static bool check_bgm_meta_path(const char *path) { - return strendswith(path, ".bgm") && strstartswith(path, BGM_PATH_PREFIX); -} - -static void free_metadata_fields(MusicMetadata *meta) { - free(meta->artist); - free(meta->comment); - free(meta->loop_path); - free(meta->title); -} - -static void load_bgm_meta(ResourceLoadState *st) { - MusicMetadata meta = { 0 }; - - if(!parse_keyvalue_file_with_spec(st->path, (KVSpec[]) { - { "artist", .out_str = &meta.artist }, - { "title", .out_str = &meta.title }, - { "comment", .out_str = &meta.comment }, - { "loop", .out_str = &meta.loop_path }, - { "loop_point", .out_double = &meta.loop_point }, - { NULL } - })) { - log_error("Failed to parse BGM metadata '%s'", st->path); - free_metadata_fields(&meta); - res_load_failed(st); - return; - } - - if(meta.comment) { - expand_escape_sequences(meta.comment); - } - - res_load_finished(st, memdup(&meta, sizeof(meta))); -} - -static void unload_bgm_meta(void *vmus) { - MusicMetadata *meta = vmus; - free_metadata_fields(meta); - free(meta); -} - -ResourceHandler bgm_metadata_res_handler = { - .type = RES_BGM_METADATA, - .typename = "bgm metadata", - .subdir = BGM_PATH_PREFIX, - - .procs = { - .find = bgm_meta_path, - .check = check_bgm_meta_path, - .load = load_bgm_meta, - .unload = unload_bgm_meta, - }, -}; diff --git a/src/resource/bgm_metadata.h b/src/resource/bgm_metadata.h deleted file mode 100644 index c61f5ce9..00000000 --- a/src/resource/bgm_metadata.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This software is licensed under the terms of the MIT License. - * See COPYING for further information. - * --- - * Copyright (c) 2011-2019, Lukas Weber . - * Copyright (c) 2012-2019, Andrei Alexeyev . - */ - -#ifndef IGUARD_resource_bgm_metadata_h -#define IGUARD_resource_bgm_metadata_h - -#include "taisei.h" - -#include "resource.h" - -typedef struct MusicMetadata { - char *title; - char *artist; - char *comment; - char *loop_path; - double loop_point; -} MusicMetadata; - -extern ResourceHandler bgm_metadata_res_handler; - -DEFINE_OPTIONAL_RESOURCE_GETTER(MusicMetadata, res_bgm_metadata, RES_BGM_METADATA) - -#endif // IGUARD_resource_bgm_metadata_h diff --git a/src/resource/meson.build b/src/resource/meson.build index 8a7db6ef..5866d7e4 100644 --- a/src/resource/meson.build +++ b/src/resource/meson.build @@ -2,7 +2,6 @@ resource_src = files( 'animation.c', 'bgm.c', - 'bgm_metadata.c', 'font.c', 'model.c', 'postprocess.c', diff --git a/src/resource/resource.c b/src/resource/resource.c index 770e6d53..fadc72e6 100644 --- a/src/resource/resource.c +++ b/src/resource/resource.c @@ -19,7 +19,6 @@ #include "animation.h" #include "sfx.h" #include "bgm.h" -#include "bgm_metadata.h" #include "shader_object.h" #include "shader_program.h" #include "model.h" @@ -34,7 +33,6 @@ ResourceHandler *_handlers[] = { [RES_ANIM] = &animation_res_handler, [RES_SFX] = &sfx_res_handler, [RES_BGM] = &bgm_res_handler, - [RES_BGM_METADATA] = &bgm_metadata_res_handler, [RES_MODEL] = &model_res_handler, [RES_POSTPROCESS] = &postprocess_res_handler, [RES_SPRITE] = &sprite_res_handler, diff --git a/src/resource/resource.h b/src/resource/resource.h index b279c570..8b3066fe 100644 --- a/src/resource/resource.h +++ b/src/resource/resource.h @@ -18,7 +18,6 @@ typedef enum ResourceType { RES_ANIM, RES_SFX, RES_BGM, - RES_BGM_METADATA, RES_SHADER_OBJECT, RES_SHADER_PROGRAM, RES_MODEL, diff --git a/src/resource/sfx.c b/src/resource/sfx.c index bfb56e4d..2eef570f 100644 --- a/src/resource/sfx.c +++ b/src/resource/sfx.c @@ -14,7 +14,7 @@ #include "sfxbgm_common.h" #include "util.h" -static char* sound_path(const char *name) { +static char *sound_path(const char *name) { return sfxbgm_make_path(SFX_PATH_PREFIX, name, false); } @@ -23,24 +23,24 @@ static bool check_sound_path(const char *path) { } static void load_sound(ResourceLoadState *st) { - SoundImpl *sound = _a_backend.funcs.sound_load(st->path); + SFXImpl *sound = _a_backend.funcs.sfx_load(st->path); if(!sound) { res_load_failed(st); return; } - _a_backend.funcs.sound_set_volume(sound, get_default_sfx_volume(st->name) / 128.0); + _a_backend.funcs.object.sfx.set_volume(sound, get_default_sfx_volume(st->name) / 128.0); - Sound *snd = calloc(1, sizeof(Sound)); + SFX *snd = calloc(1, sizeof(SFX)); snd->impl = sound; res_load_finished(st, snd); } static void unload_sound(void *vsnd) { - Sound *snd = vsnd; - _a_backend.funcs.sound_unload(snd->impl); + SFX *snd = vsnd; + _a_backend.funcs.sfx_unload(snd->impl); free(snd); } diff --git a/src/resource/sfx.h b/src/resource/sfx.h index 3a155361..f8c99b93 100644 --- a/src/resource/sfx.h +++ b/src/resource/sfx.h @@ -17,8 +17,8 @@ extern ResourceHandler sfx_res_handler; #define SFX_PATH_PREFIX "res/sfx/" -typedef struct Sound Sound; +typedef struct SFX SFX; -DEFINE_OPTIONAL_RESOURCE_GETTER(Sound, res_sfx, RES_SFX) +DEFINE_OPTIONAL_RESOURCE_GETTER(SFX, res_sfx, RES_SFX) #endif // IGUARD_resource_sfx_h diff --git a/src/resource/sfxbgm_common.c b/src/resource/sfxbgm_common.c index 951b632f..34d434b5 100644 --- a/src/resource/sfxbgm_common.c +++ b/src/resource/sfxbgm_common.c @@ -14,8 +14,8 @@ static const char *const *get_exts(bool isbgm, uint *numexts) { const char *const *exts = (isbgm - ? _a_backend.funcs.get_supported_music_exts(numexts) - : _a_backend.funcs.get_supported_sound_exts(numexts) + ? _a_backend.funcs.get_supported_bgm_exts(numexts) + : _a_backend.funcs.get_supported_sfx_exts(numexts) ); if(!exts) { @@ -25,13 +25,9 @@ static const char *const *get_exts(bool isbgm, uint *numexts) { return exts; } -char* sfxbgm_make_path(const char *prefix, const char *name, bool isbgm) { +char *sfxbgm_make_path(const char *prefix, const char *name, bool isbgm) { char *p = NULL; - if(isbgm && (p = try_path(prefix, name, ".bgm"))) { - return p; - } - uint numexts; const char *const *exts = get_exts(isbgm, &numexts); @@ -49,10 +45,6 @@ bool sfxbgm_check_path(const char *prefix, const char *path, bool isbgm) { return false; } - if(isbgm && strendswith(path, ".bgm")) { - return true; - } - uint numexts; const char *const *exts = get_exts(isbgm, &numexts); diff --git a/src/resource/sfxbgm_common.h b/src/resource/sfxbgm_common.h index a5727760..21df04fa 100644 --- a/src/resource/sfxbgm_common.h +++ b/src/resource/sfxbgm_common.h @@ -11,7 +11,7 @@ #include "taisei.h" -char* sfxbgm_make_path(const char *prefix, const char *name, bool isbgm); +char *sfxbgm_make_path(const char *prefix, const char *name, bool isbgm); bool sfxbgm_check_path(const char *prefix, const char *path, bool isbgm); #endif // IGUARD_resource_sfxbgm_common_h diff --git a/src/stage.c b/src/stage.c index 32d347e3..27098852 100644 --- a/src/stage.c +++ b/src/stage.c @@ -27,6 +27,81 @@ #include "common_tasks.h" #include "stageinfo.h" +#ifdef HAVE_SKIP_MODE + +static struct { + const char *skip_to_bookmark; + bool skip_to_dialog; + bool was_skip_mode; + int bgm_start_time; + double bgm_start_pos; +} skip_state; + +void _stage_bookmark(const char *name) { + log_debug("Bookmark [%s] reached at %i", name, global.frames); + + if(skip_state.skip_to_bookmark && !strcmp(skip_state.skip_to_bookmark, name)) { + skip_state.skip_to_bookmark = NULL; + global.plr.iddqd = false; + } +} + +DEFINE_EXTERN_TASK(stage_bookmark) { + _stage_bookmark(ARGS.name); +} + +bool stage_is_skip_mode(void) { + return skip_state.skip_to_bookmark || skip_state.skip_to_dialog; +} + +static void skipstate_init(void) { + skip_state.skip_to_dialog = env_get_int("TAISEI_SKIP_TO_DIALOG", 0); + skip_state.skip_to_bookmark = env_get_string_nonempty("TAISEI_SKIP_TO_BOOKMARK", NULL); +} + +static LogicFrameAction skipstate_handle_frame(void) { + if(skip_state.skip_to_dialog && dialog_is_active(global.dialog)) { + skip_state.skip_to_dialog = false; + global.plr.iddqd = false; + } + + bool skip_mode = stage_is_skip_mode(); + + if(!skip_mode && skip_state.was_skip_mode) { + audio_bgm_seek_realtime(skip_state.bgm_start_pos + (global.frames - skip_state.bgm_start_time) / (double)FPS); + } + + skip_state.was_skip_mode = skip_mode; + + if(skip_mode) { + return LFRAME_SKIP_ALWAYS; + } + + if(gamekeypressed(KEY_SKIP)) { + return LFRAME_SKIP; + } + + return LFRAME_WAIT; +} + +static void skipstate_shutdown(void) { + memset(&skip_state, 0, sizeof(skip_state)); +} + +static void skipstate_handle_bgm_change(void) { + skip_state.bgm_start_time = global.frames; + skip_state.bgm_start_pos = audio_bgm_tell(); +} + +#else + +INLINE LogicFrameAction skipstate_handle_frame(void) { return LFRAME_WAIT; } +INLINE void skipstate_handle_bgm_change(void) { } +INLINE void skipstate_init(void) { } +INLINE void skipstate_shutdown(void) { } + +#endif + static void stage_start(StageInfo *stage) { global.timer = 0; global.frames = 0; @@ -56,7 +131,7 @@ static void stage_start(StageInfo *stage) { global.plr.power = PLR_MAX_POWER; } - reset_sounds(); + reset_all_sfx(); } static bool ingame_menu_interrupts_bgm(void) { @@ -64,7 +139,7 @@ static bool ingame_menu_interrupts_bgm(void) { } static void stage_fade_bgm(void) { - fade_bgm((FPS * FADE_TIME) / 2000.0); + audio_bgm_stop((FPS * FADE_TIME) / 2000.0); } static void stage_leave_ingame_menu(CallChainResult ccr) { @@ -75,16 +150,17 @@ static void stage_leave_ingame_menu(CallChainResult ccr) { } if(global.gameover > 0) { - stop_sounds(); + stop_all_sfx(); if(ingame_menu_interrupts_bgm() || global.gameover != GAMEOVER_RESTART) { stage_fade_bgm(); } } else { - resume_sounds(); - resume_bgm(); + audio_bgm_resume(); } + resume_all_sfx(); + CallChain *cc = ccr.ctx; run_call_chain(cc, NULL); free(cc); @@ -92,15 +168,15 @@ static void stage_leave_ingame_menu(CallChainResult ccr) { static void stage_enter_ingame_menu(MenuData *m, CallChain next) { if(ingame_menu_interrupts_bgm()) { - stop_bgm(false); + audio_bgm_pause(); } - pause_sounds(); + pause_all_sfx(); enter_menu(m, CALLCHAIN(stage_leave_ingame_menu, memdup(&next, sizeof(next)))); } void stage_pause(void) { - if(global.gameover == GAMEOVER_TRANSITIONING || taisei_is_skip_mode_enabled()) { + if(global.gameover == GAMEOVER_TRANSITIONING || stage_is_skip_mode()) { return; } @@ -271,42 +347,38 @@ static void replay_input(void) { player_applymovement(&global.plr); } +static void display_bgm_title(void) { + BGM *bgm = audio_bgm_current(); + const char *title = bgm ? bgm_get_title(bgm) : NULL; + + if(title) { + char txt[strlen(title) + 6]; + snprintf(txt, sizeof(txt), "BGM: %s", title); + stagetext_add(txt, VIEWPORT_W-15 + I * (VIEWPORT_H-20), ALIGN_RIGHT, res_font("standard"), RGB(1, 1, 1), 30, 180, 35, 35); + } +} + +static bool stage_handle_bgm_change(SDL_Event *evt, void *a) { + if(dialog_is_active(global.dialog)) { + INVOKE_TASK_WHEN(&global.dialog->events.fadeout_began, common_call_func, display_bgm_title); + } else { + display_bgm_title(); + } + + skipstate_handle_bgm_change(); + return false; +} + static void stage_input(void) { events_poll((EventHandler[]){ { .proc = stage_input_handler_gameplay }, + { .proc = stage_handle_bgm_change, .event_type = MAKE_TAISEI_EVENT(TE_AUDIO_BGM_STARTED) }, {NULL} }, EFLAG_GAME); player_fix_input(&global.plr); player_applymovement(&global.plr); } -#ifdef DEBUG -static const char *_skip_to_bookmark; -bool _skip_to_dialog; - -void _stage_bookmark(const char *name) { - log_debug("Bookmark [%s] reached at %i", name, global.frames); - - if(_skip_to_bookmark && !strcmp(_skip_to_bookmark, name)) { - _skip_to_bookmark = NULL; - global.plr.iddqd = false; - } -} - -DEFINE_EXTERN_TASK(stage_bookmark) { - _stage_bookmark(ARGS.name); -} -#endif - -static bool _stage_should_skip(void) { -#ifdef DEBUG - if(_skip_to_bookmark || _skip_to_dialog) { - return true; - } -#endif - return false; -} - static void stage_logic(void) { process_boss(&global.boss); process_enemies(&global.enemies); @@ -323,7 +395,7 @@ static void stage_logic(void) { } } - if(_stage_should_skip()) { + if(stage_is_skip_mode()) { if(dialog_is_active(global.dialog)) { dialog_page(global.dialog); } @@ -333,7 +405,7 @@ static void stage_logic(void) { } } - update_sounds(); + update_all_sfx(); global.frames++; @@ -531,13 +603,8 @@ static void display_stage_title(StageInfo *info) { stagetext_add(info->subtitle, VIEWPORT_W/2 + I * (VIEWPORT_H/2), ALIGN_CENTER, res_font("standard"), RGB(1, 1, 1), 60, 85, 35, 35); } -static void display_bgm_title(void) { - char txt[strlen(current_bgm.title) + 6]; - snprintf(txt, sizeof(txt), "BGM: %s", current_bgm.title); - stagetext_add(txt, VIEWPORT_W-15 + I * (VIEWPORT_H-20), ALIGN_RIGHT, res_font("standard"), RGB(1, 1, 1), 30, 180, 35, 35); -} - void stage_start_bgm(const char *bgm) { +#if 0 char *old_title = NULL; if(current_bgm.title && global.stage->type == STAGE_SPELL) { @@ -555,6 +622,9 @@ void stage_start_bgm(const char *bgm) { } free(old_title); +#else + audio_bgm_play(res_bgm(bgm), true, 0, 0); +#endif } void stage_set_voltage_thresholds(uint easy, uint normal, uint hard, uint lunatic) { @@ -636,7 +706,7 @@ static LogicFrameAction stage_logic_frame(void *arg) { stage_update_fps(fstate); - if(_stage_should_skip()) { + if(stage_is_skip_mode()) { global.plr.iddqd = true; } @@ -685,19 +755,11 @@ static LogicFrameAction stage_logic_frame(void *arg) { return LFRAME_STOP; } -#ifdef DEBUG - if(_skip_to_dialog && dialog_is_active(global.dialog)) { - _skip_to_dialog = false; - global.plr.iddqd = false; + LogicFrameAction skipmode = skipstate_handle_frame(); + if(skipmode != LFRAME_WAIT) { + return skipmode; } - taisei_set_skip_mode(_stage_should_skip()); - - if(taisei_is_skip_mode_enabled() || gamekeypressed(KEY_SKIP)) { - return LFRAME_SKIP; - } -#endif - if(global.frameskip || (global.replaymode == REPLAY_PLAY && gamekeypressed(KEY_SKIP))) { return LFRAME_SKIP; } @@ -709,7 +771,7 @@ static RenderFrameAction stage_render_frame(void *arg) { StageFrameState *fstate = arg; StageInfo *stage = fstate->stage; - if(_stage_should_skip()) { + if(stage_is_skip_mode()) { return RFRAME_DROP; } @@ -818,11 +880,7 @@ void stage_enter(StageInfo *stage, CallChain next) { _current_stage_state = fstate; - #ifdef DEBUG - _skip_to_dialog = env_get_int("TAISEI_SKIP_TO_DIALOG", 0); - _skip_to_bookmark = env_get_string_nonempty("TAISEI_SKIP_TO_BOOKMARK", NULL); - taisei_set_skip_mode(_stage_should_skip()); - #endif + skipstate_init(); stage->procs->begin(); player_stage_post_init(&global.plr); @@ -856,10 +914,10 @@ void stage_end_loop(void* ctx) { free_all_refs(); ent_shutdown(); stage_objpools_free(); - stop_sounds(); + stop_all_sfx(); taisei_commit_persistent_data(); - taisei_set_skip_mode(false); + skipstate_shutdown(); if(taisei_quit_requested()) { global.gameover = GAMEOVER_ABORT; diff --git a/src/stage.h b/src/stage.h index aa8a716e..ab5a1d1c 100644 --- a/src/stage.h +++ b/src/stage.h @@ -55,11 +55,11 @@ // This is newest addition to the macro zoo! It allows you to loop a sound like // you loop your French- I mean your danmaku code. Nothing strange going on here. -#define PLAY_FOR(name,start, end) FROM_TO(start,end,2) { play_loop(name); } +#define PLAY_FOR(name,start, end) FROM_TO(start,end,2) { play_sfx_loop(name); } // easy to soundify versions of FROM_TO and friends. Note how I made FROM_TO_INT even more complicated! #define FROM_TO_SND(snd,start,end,step) PLAY_FOR(snd,start,end); FROM_TO(start,end,step) -#define FROM_TO_INT_SND(snd,start,end,step,dur,istep) FROM_TO_INT(start,end,step,dur,2) { play_loop(snd); }FROM_TO_INT(start,end,step,dur,istep) +#define FROM_TO_INT_SND(snd,start,end,step,dur,istep) FROM_TO_INT(start,end,step,dur,2) { play_sfx_loop(snd); }FROM_TO_INT(start,end,step,dur,istep) typedef struct StageClearBonus { uint64_t base; @@ -105,13 +105,19 @@ void stage_shake_view(float strength); float stage_get_view_shake_strength(void); #ifdef DEBUG -void _stage_bookmark(const char *name); -#define STAGE_BOOKMARK(name) _stage_bookmark(#name) -DECLARE_EXTERN_TASK(stage_bookmark, { const char *name; }); -#define STAGE_BOOKMARK_DELAYED(delay, name) INVOKE_TASK_DELAYED(delay, stage_bookmark, #name) +#define HAVE_SKIP_MODE +#endif + +#ifdef HAVE_SKIP_MODE + void _stage_bookmark(const char *name); + #define STAGE_BOOKMARK(name) _stage_bookmark(#name) + DECLARE_EXTERN_TASK(stage_bookmark, { const char *name; }); + #define STAGE_BOOKMARK_DELAYED(delay, name) INVOKE_TASK_DELAYED(delay, stage_bookmark, #name) + bool stage_is_skip_mode(void); #else -#define STAGE_BOOKMARK(name) ((void)0) -#define STAGE_BOOKMARK_DELAYED(delay, name) ((void)0) + #define STAGE_BOOKMARK(name) ((void)0) + #define STAGE_BOOKMARK_DELAYED(delay, name) ((void)0) + INLINE bool stage_is_skip_mode(void) { return false; } #endif #endif // IGUARD_stage_h diff --git a/src/stages/stage1/nonspells/boss_nonspell_1.c b/src/stages/stage1/nonspells/boss_nonspell_1.c index 5c67e1a3..30497828 100644 --- a/src/stages/stage1/nonspells/boss_nonspell_1.c +++ b/src/stages/stage1/nonspells/boss_nonspell_1.c @@ -23,7 +23,7 @@ DEFINE_EXTERN_TASK(stage1_boss_nonspell_1) { WAIT(20); aniplayer_queue(&boss->ani, "(9)", 3); aniplayer_queue(&boss->ani, "main", 0); - play_sound("shot_special1"); + play_sfx("shot_special1"); const int num_shots = 5; const int num_projs = difficulty_value(9, 10, 11, 12); @@ -49,7 +49,7 @@ DEFINE_EXTERN_TASK(stage1_boss_nonspell_1) { WAIT(10); for(int t = 0, i = 0; t < 60; ++i) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); real speed0 = difficulty_value(4.0, 6.0, 6.0, 6.0); real speed1 = difficulty_value(3.0, 5.0, 6.0, 8.0); real angle = rng_sreal() * M_PI/8.0; @@ -71,7 +71,7 @@ DEFINE_EXTERN_TASK(stage1_boss_nonspell_1) { WAIT(30); for(int t = 0, i = 0; t < 150; ++i) { - play_sound("shot1"); + play_sfx("shot1"); float dif = rng_angle(); for(int shot = 0; shot < 20; ++shot) { cmplx aim = cdir(M_TAU/8 * shot + dif); diff --git a/src/stages/stage1/nonspells/boss_nonspell_2.c b/src/stages/stage1/nonspells/boss_nonspell_2.c index f4a2f857..9de914dd 100644 --- a/src/stages/stage1/nonspells/boss_nonspell_2.c +++ b/src/stages/stage1/nonspells/boss_nonspell_2.c @@ -17,7 +17,7 @@ TASK(snowburst, { BoxedBoss boss; }) { Boss *boss = TASK_BIND(ARGS.boss); - play_sound("shot_special1"); + play_sfx("shot_special1"); aniplayer_queue(&boss->ani, "(9)", 0); int rounds = difficulty_value(3, 4, 5, 6); @@ -86,7 +86,7 @@ TASK(spiralshot, { ); for(int b = 0; b < count; ++b) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); real dist = b * dist_per_bullet; real angle = angle_ofs + b * angle_per_bullet; @@ -102,8 +102,8 @@ TASK(spiralshot, { } WAIT(fire_delay); - play_sound("shot_special1"); - play_sound("redirect"); + play_sfx("shot_special1"); + play_sfx("redirect"); ENT_ARRAY_FOREACH(&projs, Projectile *p, { spawn_projectile_highlight_effect(p); p->move = move_linear(cdir(p->angle) * ARGS.bullet_speed); @@ -152,7 +152,7 @@ DEFINE_EXTERN_TASK(stage1_boss_nonspell_2) { for(int t = 0, i = 0; t < 150;) { float dif = rng_angle(); - play_sound("shot1"); + play_sfx("shot1"); for(int j = 0; j < 20; ++j) { PROJECTILE( .proto = pp_plainball, diff --git a/src/stages/stage1/nonspells/midboss_nonspell_1.c b/src/stages/stage1/nonspells/midboss_nonspell_1.c index 7a58593b..fdaadf23 100644 --- a/src/stages/stage1/nonspells/midboss_nonspell_1.c +++ b/src/stages/stage1/nonspells/midboss_nonspell_1.c @@ -34,7 +34,7 @@ TASK(make_snowflake, { int t = 0; for(int j = 0; j < ARGS.size; j++) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); for(int i = 0; i < SNOWFLAKE_ARMS; i++) { real ang = M_TAU / SNOWFLAKE_ARMS * i + ARGS.rot_angle; @@ -126,8 +126,8 @@ DEFINE_EXTERN_TASK(stage1_midboss_nonspell_1) { WAIT(65 - 4 * (size_base + size_oscillation - size)); - play_sound("redirect"); - // play_sound("shot_special1"); + play_sfx("redirect"); + // play_sfx("shot_special1"); ENT_ARRAY_FOREACH(&snowflake_projs, Projectile *p, { spawn_projectile_highlight_effect(p)->opacity = 0.25; diff --git a/src/stages/stage1/spells/crystal_blizzard.c b/src/stages/stage1/spells/crystal_blizzard.c index 19d69aad..a722ec3a 100644 --- a/src/stages/stage1/spells/crystal_blizzard.c +++ b/src/stages/stage1/spells/crystal_blizzard.c @@ -21,7 +21,7 @@ TASK(crystal_wall, NO_ARGS) { real ofs = rng_real() - 1; for(int i = 0; i < 30; ++i) { - play_sound("shot1"); + play_sfx("shot1"); for(int c = 0; c < num_crystals; ++c) { cmplx accel = 0.02*I + 0.01*I * ((c % 2) ? 1 : -1) * sin((c * 3 + global.frames) / 30.0); @@ -84,7 +84,7 @@ DEFINE_EXTERN_TASK(stage1_spell_crystal_blizzard) { boss->move.attraction_max_speed = 128; for(int t = 0; t < 370; ++t) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); boss->move.attraction_point = global.plr.pos; if(!(t % frostbolt_period)) { @@ -94,7 +94,7 @@ DEFINE_EXTERN_TASK(stage1_spell_crystal_blizzard) { } if(!(t % 7)) { - play_sound("shot1"); + play_sfx("shot1"); int cnt = global.diff - 1; for(int i = 0; i < cnt; ++i) { PROJECTILE( diff --git a/src/stages/stage1/spells/crystal_rain.c b/src/stages/stage1/spells/crystal_rain.c index a8020210..c58ef7bd 100644 --- a/src/stages/stage1/spells/crystal_rain.c +++ b/src/stages/stage1/spells/crystal_rain.c @@ -19,7 +19,7 @@ TASK(crystal_rain_drops, NO_ARGS) { const int nshots = difficulty_value(1, 2, 4, 5); for(;;) { - play_sound("shot2"); + play_sfx("shot2"); for(int i = 0; i < nshots; ++i) { RNG_ARRAY(rng, 2); @@ -58,7 +58,7 @@ TASK(crystal_rain_cirno_shoot, { BoxedBoss boss; int charge_time; }) { real n = (difficulty_value(1, 2, 6, 11) + odd)/2.0; cmplx org = boss->pos + shot_ofs; - play_sound("shot_special1"); + play_sfx("shot_special1"); for(real i = -n; i <= n; i++) { PROJECTILE( .proto = odd? pp_plainball : pp_bigball, diff --git a/src/stages/stage1/spells/icicle_cascade.c b/src/stages/stage1/spells/icicle_cascade.c index a97f363f..3b38e8e8 100644 --- a/src/stages/stage1/spells/icicle_cascade.c +++ b/src/stages/stage1/spells/icicle_cascade.c @@ -32,7 +32,7 @@ TASK(cirno_icicle, { cmplx pos; cmplx vel; }) { p->move = move_asymptotic_simple(v, 2); p->angle = carg(p->move.velocity); - play_sound("redirect"); + play_sfx("redirect"); spawn_projectile_highlight_effect(p); } @@ -50,7 +50,7 @@ DEFINE_EXTERN_TASK(stage1_spell_icicle_cascade) { for(int round = 0;; ++round) { WAIT(icicle_interval); - play_sound("shot1"); + play_sfx("shot1"); for(int i = 0; i < icicles; ++i) { real speed = 8 + 3 * i; diff --git a/src/stages/stage1/spells/perfect_freeze.c b/src/stages/stage1/spells/perfect_freeze.c index 39a5917d..55b6d86c 100644 --- a/src/stages/stage1/spells/perfect_freeze.c +++ b/src/stages/stage1/spells/perfect_freeze.c @@ -28,7 +28,7 @@ TASK(move_frozen, { BoxedProjectileArray *parray; }) { stage1_spawn_stain(p->pos, p->angle, 30); spawn_projectile_highlight_effect(p); - play_sound_ex("shot2", 0, false); + play_sfx_ex("shot2", 0, false); if(rng_chance(0.4)) { YIELD; @@ -52,7 +52,7 @@ DEFINE_EXTERN_TASK(stage1_spell_perfect_freeze) { DECLARE_ENT_ARRAY(Projectile, projs, nfrog); for(int i = 0; i < nfrog/n; i++) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); float r = rng_f32(); float g = rng_f32(); @@ -76,7 +76,7 @@ DEFINE_EXTERN_TASK(stage1_spell_perfect_freeze) { stage1_spawn_stain(p->pos, p->angle, 30); stage1_spawn_stain(p->pos, p->angle, 30); spawn_projectile_highlight_effect(p); - play_sound("shot_special1"); + play_sfx("shot_special1"); p->color = *RGB(0.9, 0.9, 0.9); p->move.retention = 0.8; @@ -102,7 +102,7 @@ DEFINE_EXTERN_TASK(stage1_spell_perfect_freeze) { int d = imax(0, global.diff - D_Normal); for(int i = 0; i < 30+10*d; i++) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); float r1, r2; if(global.diff > D_Normal) { diff --git a/src/stages/stage1/spells/snow_halation.c b/src/stages/stage1/spells/snow_halation.c index fc39fd57..506e6b18 100644 --- a/src/stages/stage1/spells/snow_halation.c +++ b/src/stages/stage1/spells/snow_halation.c @@ -94,23 +94,23 @@ TASK(halation_orb, { create_halation_laser(pos[0], pos[2], 15, phase_time, phase_time * 1.5, NULL); WAIT(phase_time / 2); - play_sound("laser1"); + play_sfx("laser1"); WAIT(phase_time / 2); create_halation_laser(pos[0], pos[1], 12, phase_time, phase_time * 1.5, NULL); WAIT(phase_time); - play_sound("shot1"); + play_sfx("shot1"); create_halation_laser(pos[0], pos[3], 15, phase_time, phase_time * 1.5, NULL); create_halation_laser(pos[1], pos[3], 15, phase_time, phase_time * 1.5, NULL); WAIT(phase_time); - play_sound("shot1"); + play_sfx("shot1"); create_halation_laser(pos[0], pos[1], 12, phase_time, phase_time * 1.5, NULL); create_halation_laser(pos[0], pos[2], 15, phase_time, phase_time * 1.5, NULL); WAIT(phase_time); - play_sound("shot1"); - play_sound("shot_special1"); + play_sfx("shot1"); + play_sfx("shot_special1"); Color colors[] = { // PRECISE colors, VERY important!!! @@ -173,7 +173,7 @@ DEFINE_EXTERN_TASK(stage1_spell_snow_halation) { } for(int i = 0; i < orbs; ++i) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); INVOKE_TASK(halation_orb, .pos = { orb_positions[i], diff --git a/src/stages/stage1/timeline.c b/src/stages/stage1/timeline.c index 7e2a9b7d..d1bc6e3f 100644 --- a/src/stages/stage1/timeline.c +++ b/src/stages/stage1/timeline.c @@ -32,7 +32,7 @@ TASK(burst_fairy, { cmplx pos; cmplx dir; }) { WAIT(60); - play_sound("shot1"); + play_sfx("shot1"); int n = 1.5 * global.diff - 1; for(int i = -n; i <= n; i++) { @@ -60,7 +60,7 @@ TASK(circletoss_shoot_circle, { BoxedEnemy e; int duration; int interval; }) { double angle_step = M_TAU / cnt; for(int i = 0; i < cnt; ++i) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); e->move.velocity *= 0.8; cmplx aim = cdir(angle_step * i); @@ -81,7 +81,7 @@ TASK(circletoss_shoot_toss, { BoxedEnemy e; int times; int duration; int period; while(ARGS.times--) { for(int i = ARGS.duration; i--;) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); double aim_angle = carg(global.plr.pos - e->pos); aim_angle += 0.05 * global.diff * rng_real(); @@ -157,7 +157,7 @@ TASK(sinepass_swirl, { cmplx pos; cmplx vel; cmplx svel; }) { int shot_interval = difficulty_value(120, 40, 30, 20); for(;;) { - play_sound("shot1"); + play_sfx("shot1"); cmplx aim = cnormalize(global.plr.pos - e->pos); aim *= difficulty_value(2, 2, 2.5, 3); @@ -207,7 +207,7 @@ TASK(circle_fairy, { cmplx pos; cmplx target_pos; }) { .move = move_asymptotic_simple(aim, i * 0.5), ); - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); WAIT(shot_interval); } @@ -239,7 +239,7 @@ TASK(drop_swirl, { cmplx pos; cmplx vel; cmplx accel; }) { cmplx aim = cnormalize(global.plr.pos - e->pos); aim *= 1 + 0.3 * global.diff + rng_real(); - play_sound("shot1"); + play_sfx("shot1"); PROJECTILE( .proto = pp_ball, .pos = e->pos, @@ -269,7 +269,7 @@ TASK(multiburst_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) { int bursts = 4; for(int i = 0; i < bursts; ++i) { - play_sound("shot1"); + play_sfx("shot1"); int n = global.diff - 1; for(int j = -n; j <= n; j++) { @@ -295,7 +295,7 @@ TASK(multiburst_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) { TASK(instantcircle_fairy_shoot, { BoxedEnemy e; int cnt; double speed; double boost; }) { Enemy *e = TASK_BIND(ARGS.e); - play_sound("shot_special1"); + play_sfx("shot_special1"); for(int i = 0; i < ARGS.cnt; ++i) { cmplx vel = ARGS.speed * circle_dir(i, ARGS.cnt); @@ -344,7 +344,7 @@ TASK(waveshot, { cmplx pos; real angle; real spread; real freq; int shots; int i for(int i = 0; i < ARGS.shots; ++i) { cmplx v = 4 * cdir(ARGS.angle + ARGS.spread * triangle(ARGS.freq * i)); - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); PROJECTILE( .proto = pp_thickrice, .pos = ARGS.pos, @@ -411,7 +411,7 @@ TASK(explosion_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) { real speed = difficulty_value(2, 2, 4, 4); real ofs = rng_angle(); - play_sound("shot_special1"); + play_sfx("shot_special1"); for(int i = 0; i < cnt; ++i) { cmplx aim = speed * circle_dir_ofs(i, cnt, ofs); @@ -622,7 +622,7 @@ TASK(tritoss_fairy, { cmplx pos; cmplx velocity; cmplx end_velocity; }) { int interval = difficulty_value(12, 9, 5, 3); int rounds = 680/interval; for(int k = 0; k < rounds; k++) { - play_sound("shot1"); + play_sfx("shot1"); float a = M_PI / 30.0 * ((k/7) % 30) + 0.1 * rng_f32(); int n = difficulty_value(3,4,4,5); @@ -637,7 +637,7 @@ TASK(tritoss_fairy, { cmplx pos; cmplx velocity; cmplx end_velocity; }) { } if(k == rounds/2 || k == rounds-1) { - play_sound("shot_special1"); + play_sfx("shot_special1"); int n2 = difficulty_value(20, 23, 26, 30); for(int i = 0; i < n2; i++) { PROJECTILE( diff --git a/src/stages/stage2_events.c b/src/stages/stage2_events.c index a2dceba7..34b7dabc 100644 --- a/src/stages/stage2_events.c +++ b/src/stages/stage2_events.c @@ -623,7 +623,7 @@ static int hina_monty_slave(Enemy *s, int time) { } if(time > 60 && time < 720-140 + 20*(global.diff-D_Lunatic) && !(time % (int)(fmax(2 + (global.diff < D_Normal), (120 - 0.5 * time))))) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); PROJECTILE( .proto = pp_crystal, diff --git a/src/stages/stage3.c b/src/stages/stage3.c index 84d01fbf..bb287dca 100644 --- a/src/stages/stage3.c +++ b/src/stages/stage3.c @@ -383,7 +383,7 @@ static void stage3_spellpractice_events(void) { void stage3_skip(int t) { skip_background_anim(stage3_update, t, &global.timer, &global.frames); - audio_music_set_position(global.timer / (double)FPS); + audio_bgm_seek_realtime(global.timer / (double)FPS); } ShaderRule stage3_shaders[] = { stage3_fog, stage3_tunnel, NULL }; diff --git a/src/stages/stage3_events.c b/src/stages/stage3_events.c index 2fe5d284..e4f44dd0 100644 --- a/src/stages/stage3_events.c +++ b/src/stages/stage3_events.c @@ -623,7 +623,7 @@ void scuttle_deadly_dance(Boss *boss, int time) { AT(0) { aniplayer_queue(&boss->ani, "dance", 0); } - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); FROM_TO(0, 120, 1) GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/2, 0.03) @@ -1156,7 +1156,7 @@ void wriggle_light_singularity(Boss *boss, int time) { } if(time > 120) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); } if(time == 0) { diff --git a/src/stages/stage4.c b/src/stages/stage4.c index 92cb1424..5e8ddf2c 100644 --- a/src/stages/stage4.c +++ b/src/stages/stage4.c @@ -310,7 +310,7 @@ static void stage4_spellpractice_events(void) { void stage4_skip(int t) { skip_background_anim(stage4_update, t, &global.timer, &global.frames); - audio_music_set_position(global.timer / (double)FPS); + audio_bgm_seek_realtime(global.timer / (double)FPS); } ShaderRule stage4_shaders[] = { stage4_fog, NULL }; diff --git a/src/stages/stage5.c b/src/stages/stage5.c index e6fa1859..c2cdc130 100644 --- a/src/stages/stage5.c +++ b/src/stages/stage5.c @@ -202,7 +202,7 @@ void stage5_skip(int t) { mskip += 1100; } - audio_music_set_position(mskip / (double)FPS); + audio_bgm_seek_realtime(mskip / (double)FPS); } static void stage5_spellpractice_start(void) { diff --git a/src/stages/stage5_events.c b/src/stages/stage5_events.c index dc81ce3c..b5d7d64d 100644 --- a/src/stages/stage5_events.c +++ b/src/stages/stage5_events.c @@ -193,7 +193,7 @@ static int stage5_laserfairy(Enemy *e, int t) { .rule = accelerated, .args = { fac*4*n, fac*0.05*n } ); - play_sound_ex("shot_special1", 0, true); + play_sfx_ex("shot_special1", 0, true); } return 1; @@ -217,7 +217,7 @@ static int stage5_miner(Enemy *e, int t) { .rule = linear, .args = { cexp(2.0*I*M_PI*afrand(1)) } ); - play_sound_ex("shot3", 0, false); + play_sfx_ex("shot3", 0, false); } return 1; @@ -259,7 +259,7 @@ static int stage5_magnetto(Enemy *e, int t) { AT(140) { play_sound("redirect"); - play_sound_delayed("redirect", 0, false, 180); + play_sfx_delayed("redirect", 0, false, 180); } FROM_TO(140, 320, 1) { @@ -361,7 +361,7 @@ static int stage5_explosion(Enemy *e, int t) { FROM_TO(500-30*(global.diff-D_Easy), 800, 100-10*global.diff) { create_laserline(e->pos, 10*cexp(I*carg(global.plr.pos-e->pos)+0.04*I*(1-2*frand())), 60, 120, RGBA(1, 0.3, 1, 0)); - play_sound_delayed("laser1", 0, true, 45); + play_sfx_delayed("laser1", 0, true, 45); } return 1; @@ -657,7 +657,7 @@ static void iku_bolts2(Boss *b, int time) { aniplayer_queue(&b->ani, flip_laser ? "dashdown_left" : "dashdown_right", 1); aniplayer_queue(&b->ani, "main", 0); create_lasercurve3c(creal(global.plr.pos), 100, 200, RGBA(0.3, 1, 1, 0), bolts2_laser, global.plr.pos, flip_laser*2-1, global.diff); - play_sound_ex("laser1", 0, false); + play_sfx_ex("laser1", 0, false); } FROM_TO_SND("shot1_loop", 0, 400, 5-global.diff) @@ -713,7 +713,7 @@ static int lightning_slave(Enemy *e, int t) { lightning_particle(p->pos + 5 * afrand(0) * cexp(I*M_PI*2*afrand(1)), 0); } - play_sound_ex("shot3", 0, false); + play_sfx_ex("shot3", 0, false); // play_sound_ex("redirect", 0, true); } } @@ -1070,7 +1070,7 @@ static int iku_extra_trigger_bullet(Projectile *p, int t) { } } else { p->args[2] = approach(creal(p->args[2]), 0, 1); - play_loop("charge_generic"); + play_sfx_loop("charge_generic"); } if(creal(p->args[2]) == 0) { diff --git a/src/stages/stage6_events.c b/src/stages/stage6_events.c index ad64cae2..967e0786 100644 --- a/src/stages/stage6_events.c +++ b/src/stages/stage6_events.c @@ -79,7 +79,7 @@ static int stage6_side(Enemy *e, int t) { AT(70) { int i; int c = 15+10*global.diff; - play_sound_ex("shot1",4,true); + play_sfx_ex("shot1",4,true); for(i = 0; i < c; i++) { PROJECTILE( .proto = (i%2 == 0) ? pp_rice : pp_flea, @@ -105,7 +105,7 @@ static int wait_proj(Projectile *p, int t) { if(t > creal(p->args[1])) { if(t == creal(p->args[1]) + 1) { - play_sound_ex("redirect", 4, false); + play_sfx_ex("redirect", 4, false); spawn_projectile_highlight_effect(p); } @@ -140,7 +140,7 @@ static int stage6_flowermine(Enemy *e, int t) { }, .angle = 0.6*_i, ); - play_sound("shot1"); + play_sfx("shot1"); } return 1; @@ -191,7 +191,7 @@ static int scythe_mid(Enemy *e, int t) { ); if(projectile_in_viewport(p)) { - play_sound("shot1"); + play_sfx("shot1"); } } @@ -402,7 +402,7 @@ static int scythe_newton(Enemy *e, int t) { ) { e->args[3] += 1; //p->args[0] /= 2; - play_sound_ex("redirect",4,false); + play_sfx_ex("redirect",4,false); p->birthtime=global.frames; p->pos0=p->pos; p->args[0] = (2+0.125*global.diff)*cexp(I*2*M_PI*frand()); @@ -473,7 +473,7 @@ void elly_newton(Boss *b, int t) { .layer = LAYER_BULLET | 0xffff, // force them to render on top of the other bullets ); - play_sound("shot3"); + play_sfx("shot3"); } FROM_TO(0, 100000, 20+10*(global.diff>D_Normal)) { @@ -481,7 +481,7 @@ void elly_newton(Boss *b, int t) { int x, y; float w = global.diff/2.0+1.5; - play_sound("shot_special1"); + play_sfx("shot_special1"); for(x = -w; x <= w; x++) { for(y = -w; y <= w; y++) { @@ -567,7 +567,7 @@ static int kepler_bullet(Projectile *p, int t) { if(global.diff == D_Easy) n=7; - play_sound("redirect"); + play_sfx("redirect"); if(tier <= 1+(global.diff>D_Hard) && cimag(p->args[1])*(tier+1) < n) { PROJECTILE( .proto = kepler_pick_bullet(tier + 1), @@ -613,7 +613,7 @@ void elly_kepler(Boss *b, int t) { FROM_TO(0, 100000, 20) { int c = 2; - play_sound("shot_special1"); + play_sfx("shot_special1"); for(int i = 0; i < c; i++) { cmplx n = cexp(I*2*M_PI/c*i+I*0.6*_i); @@ -689,7 +689,7 @@ void elly_maxwell(Boss *b, int t) { AT(250) { elly_clap(b,50); - play_sound("laser1"); + play_sfx("laser1"); } FROM_TO(40, 159, 5) { @@ -926,7 +926,7 @@ static int scythe_explode(Enemy *e, int t) { if(t == 100) { petal_explosion(100, e->pos); stage_shake_view(16); - play_sound("boom"); + play_sfx("boom"); scythe_common(e, t); return ACTION_DESTROY; @@ -962,7 +962,7 @@ static void elly_paradigm_shift(Boss *b, int t) { if(global.stage->type == STAGE_SPELL) { GO_TO(b, BOSS_DEFAULT_GO_POS, 0.1) } else if(t == 0) { - play_sound("bossdeath"); + play_sfx("bossdeath"); } TIMER(&t); @@ -976,7 +976,7 @@ static void elly_paradigm_shift(Boss *b, int t) { if(global.stage->type != STAGE_SPELL) { AT(80) { - fade_bgm(0.5); + audio_bgm_stop(0.5); } } @@ -1034,8 +1034,8 @@ static int baryon_eigenstate(Enemy *e, int t) { int i, j; int c = 9; - play_sound("shot_special1"); - play_sound_delayed("redirect",4,true,60); + play_sfx("shot_special1"); + play_sfx_delayed("redirect",4,true,60); for(i = 0; i < c; i++) { cmplx n = cexp(2.0*I*_i+I*M_PI/2+I*creal(e->args[2])); for(j = 0; j < 3; j++) { @@ -1122,7 +1122,7 @@ static int broglie_particle(Projectile *p, int t) { p->angle += angle_ampl * sin(t * angle_freq) * cos(2 * t * angle_freq); p->args[2] = -cabs(p->args[2]) * cexp(I*p->angle); - play_sound("redirect"); + play_sfx("redirect"); } p->angle = carg(p->args[2]); @@ -1153,7 +1153,7 @@ static void broglie_laser_logic(Laser *l, int t) { static int broglie_charge(Projectile *p, int t) { if(t == EVENT_BIRTH) { - play_sound_ex("shot3", 10, false); + play_sfx_ex("shot3", 10, false); } if(t < 0) { @@ -1163,8 +1163,8 @@ static int broglie_charge(Projectile *p, int t) { int firetime = creal(p->args[1]); if(t == firetime) { - play_sound_ex("laser1", 10, true); - play_sound("boom"); + play_sfx_ex("laser1", 10, true); + play_sfx("boom"); stage_shake_view(20); @@ -1229,7 +1229,7 @@ static int broglie_charge(Projectile *p, int t) { p->pos = o + p->args[0] * 15; if(f > 0.1) { - play_loop("charge_generic"); + play_sfx_loop("charge_generic"); cmplx n = cexp(2.0*I*M_PI*frand()); float l = 50*frand()+25; @@ -1434,7 +1434,7 @@ static int ricci_proj2(Projectile *p, int t) { TIMER(&t); AT(EVENT_BIRTH) { - play_sound("shot3"); + play_sfx("shot3"); } AT(EVENT_DEATH) { @@ -1445,7 +1445,7 @@ static int ricci_proj2(Projectile *p, int t) { } // play_sound_ex("shot3",8, false); - play_sound("shot_special1"); + play_sfx("shot_special1"); double rad = SAFE_RADIUS_MAX * (0.6 - 0.2 * (double)(D_Lunatic - global.diff) / 3); @@ -1666,7 +1666,7 @@ static void elly_baryonattack2(Boss *b, int t) { set_baryon_rule(baryon_reset); FROM_TO(100, 100000, 200-5*global.diff) { - play_sound("shot_special1"); + play_sfx("shot_special1"); if(_i % 2) { int cnt = 5; @@ -1733,7 +1733,7 @@ static int baryon_lhc(Enemy *e, int t) { e->args[3] = 100.0*I+400.0*I*((t/400)&1); if(g == 2 || g == 5) { - play_sound_delayed("laser1",10,true,200); + play_sfx_delayed("laser1",10,true,200); Laser *l = create_laser(e->pos, 200, 300, RGBA(0.1+0.9*(g>3), 0, 1-0.9*(g>3), 0), las_linear, lhc_laser_logic, (1-2*(g>3))*VIEWPORT_W*0.005, 200+30.0*I, add_ref(e), 0); l->unclearable = true; @@ -1764,7 +1764,7 @@ void elly_lhc(Boss *b, int t) { cmplx pos = VIEWPORT_W/2 + 100.0*I+400.0*I*((t/400)&1); stage_shake_view(160); - play_sound("boom"); + play_sfx("boom"); for(i = 0; i < c; i++) { cmplx v = 3*cexp(2.0*I*M_PI*frand()); @@ -1805,7 +1805,7 @@ void elly_lhc(Boss *b, int t) { } FROM_TO(0, 100000,7-global.diff) { - play_sound_ex("shot2",10,false); + play_sfx_ex("shot2",10,false); PROJECTILE( .proto = pp_ball, .pos = b->pos, @@ -1821,7 +1821,7 @@ static int baryon_explode(Enemy *e, int t) { AT(EVENT_DEATH) { free_ref(e->args[1]); petal_explosion(24, e->pos); - play_sound("boom"); + play_sfx("boom"); stage_shake_view(15); for(uint i = 0; i < 3; ++i) { @@ -1989,7 +1989,7 @@ static double saw(double t) { static int curvature_slave(Enemy *e, int t) { e->args[0] = -(e->args[1] - global.plr.pos); e->args[1] = global.plr.pos; - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); if(t % (2+(global.diff < D_Hard)) == 0) { tsrand_fill(2); @@ -2012,7 +2012,7 @@ static int curvature_slave(Enemy *e, int t) { } if(global.diff >= D_Hard && !(t%20)) { - play_sound_ex("shot2",10,false); + play_sfx_ex("shot2",10,false); Projectile *p =PROJECTILE( .proto = pp_bigball, .pos = global.boss->pos, @@ -2076,7 +2076,7 @@ static void elly_baryon_explode(Boss *b, int t) { } AT(42) { - fade_bgm(1.0); + audio_bgm_stop(1.0); } FROM_TO(0, 200, 1) { @@ -2086,14 +2086,14 @@ static void elly_baryon_explode(Boss *b, int t) { stage_shake_view(_i / 200.0f); if(_i > 30) { - play_loop("charge_generic"); + play_sfx_loop("charge_generic"); } } AT(200) { tsrand_fill(2); stage_shake_view(40); - play_sound("boom"); + play_sfx("boom"); petal_explosion(100, b->pos + 100*afrand(0)*cexp(2.0*I*M_PI*afrand(1))); enemy_kill_all(&global.enemies); } @@ -2185,8 +2185,8 @@ static int elly_toe_boson(Projectile *p, int t) { } if(t == activate_time && num_in_trail == 2) { - play_sound("shot_special1"); - play_sound("redirect"); + play_sfx("shot_special1"); + play_sfx("redirect"); PARTICLE( .sprite = "blast", @@ -2221,7 +2221,7 @@ static int elly_toe_boson(Projectile *p, int t) { p->args[1] = warps_left + warps_initial * I; if(num_in_trail == 3) { - play_sound_ex("warp", 0, false); + play_sfx_ex("warp", 0, false); // play_sound("redirect"); } @@ -2370,7 +2370,7 @@ static int elly_toe_fermion(Projectile *p, int t) { if(!p->args[3]) { projectile_set_prototype(p, pp_bigball); p->args[3]=1; - play_sound_ex("shot_special1", 5, false); + play_sfx_ex("shot_special1", 5, false); PARTICLE( .sprite = "blast", @@ -2673,7 +2673,7 @@ void elly_theory(Boss *b, int time) { } FROM_TO_INT(start_time, end_time, cycle_step, cycle_time, step) { - play_sound("shot2"); + play_sfx("shot2"); int pnum = _ni; int num_warps = 0; @@ -2712,7 +2712,7 @@ void elly_theory(Boss *b, int time) { FROM_TO(fermiontime,yukawatime+250,2) { // play_loop("noise1"); - play_sound_ex("shot1", 5, false); + play_sfx_ex("shot1", 5, false); cmplx dest = 100*cexp(I*1*_i); for(int clr = 0; clr < 3; clr++) { @@ -2733,13 +2733,13 @@ void elly_theory(Boss *b, int time) { } AT(fermiontime) { - play_sound("charge_generic"); + play_sfx("charge_generic"); stage_shake_view(10); } AT(symmetrytime) { - play_sound("charge_generic"); - play_sound("boom"); + play_sfx("charge_generic"); + play_sfx("boom"); stagetext_add("Symmetry broken!", VIEWPORT_W/2+I*VIEWPORT_H/4, ALIGN_CENTER, get_font("big"), RGB(1,1,1), 0,100,10,20); stage_shake_view(10); @@ -2767,20 +2767,20 @@ void elly_theory(Boss *b, int time) { } AT(yukawatime) { - play_sound("charge_generic"); + play_sfx("charge_generic"); stagetext_add("Coupling the Higgs!", VIEWPORT_W/2+I*VIEWPORT_H/4, ALIGN_CENTER, get_font("big"), RGB(1,1,1), 0,100,10,20); stage_shake_view(10); } AT(breaktime) { - play_sound("charge_generic"); + play_sfx("charge_generic"); stagetext_add("Perturbation theory", VIEWPORT_W/2+I*VIEWPORT_H/4, ALIGN_CENTER, get_font("big"), RGB(1,1,1), 0,100,10,20); stagetext_add("breaking down!", VIEWPORT_W/2+I*VIEWPORT_H/4+30*I, ALIGN_CENTER, get_font("big"), RGB(1,1,1), 0,100,10,20); stage_shake_view(10); } FROM_TO(higgstime,yukawatime+100,4+4*(time>symmetrytime)) { - play_loop("shot1_loop"); + play_sfx_loop("shot1_loop"); int arms; @@ -2812,7 +2812,7 @@ void elly_theory(Boss *b, int time) { } FROM_TO(breaktime,breaktime+10000,100) { - play_sound_ex("laser1", 0, true); + play_sfx_ex("laser1", 0, true); cmplx phase = cexp(2*I*M_PI*frand()); int count = 8; @@ -2828,7 +2828,7 @@ void elly_theory(Boss *b, int time) { } FROM_TO(breaktime+35, breaktime+10000, 14) { - play_sound("redirect"); + play_sfx("redirect"); // play_sound("shot_special1"); for(int clr = 0; clr < 3; clr++) { diff --git a/src/util/meson.build b/src/util/meson.build index a82c470d..2dfbcaa5 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -19,10 +19,6 @@ util_src = files( 'stringops.c', ) -util_opus_src = files( - 'opuscruft.c', -) - if is_developer_build util_src += files('debug.c') endif diff --git a/src/util/opuscruft.c b/src/util/opuscruft.c deleted file mode 100644 index fee9cd4a..00000000 --- a/src/util/opuscruft.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This software is licensed under the terms of the MIT License. - * See COPYING for further information. - * --- - * Copyright (c) 2011-2019, Lukas Weber . - * Copyright (c) 2012-2019, Andrei Alexeyev . - */ - -#include "taisei.h" - -#include "opuscruft.h" - -#include -#include - -static int opusutil_rwops_read(void *_stream, unsigned char *_ptr, int _nbytes) { - SDL_RWops *rw = _stream; - return SDL_RWread(rw, _ptr, 1, _nbytes); -} - -static int opusutil_rwops_seek(void *_stream, opus_int64 _offset, int _whence) { - SDL_RWops *rw = _stream; - return SDL_RWseek(rw, _offset, _whence) < 0 ? -1 : 0; -} - -static opus_int64 opusutil_rwops_tell(void *_stream) { - SDL_RWops *rw = _stream; - return SDL_RWtell(rw); -} - -static int opusutil_rwops_close(void *_stream) { - SDL_RWops *rw = _stream; - return SDL_RWclose(rw) < 0 ? EOF : 0; -} - -OpusFileCallbacks opusutil_rwops_callbacks = { - .read = opusutil_rwops_read, - .seek = opusutil_rwops_seek, - .tell = opusutil_rwops_tell, - .close = opusutil_rwops_close, -};