From 8480d41b7b65490f5392de6657a375380e1f6238 Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev Date: Mon, 22 Jun 2020 17:41:03 +0300 Subject: [PATCH] Audio rewrite (#236) This replaces SDL_mixer with an internal streaming and mixing system, relying only on basic SDL audio support. It's also a partial refactor of the audio API, most notably BGM-related. The BGM metadata resource type is gone, as well as the `.bgm` files. The metadata is now stored inside the `.opus` files directly, using standard Opus tags. --- README.rst | 1 - meson.build | 2 - meson_options.txt | 6 +- resources/00-taisei.pkgdir/bgm/bonus0.bgm | 5 - resources/00-taisei.pkgdir/bgm/bonus0.opus | Bin 2866557 -> 2867180 bytes resources/00-taisei.pkgdir/bgm/credits.bgm | 4 - resources/00-taisei.pkgdir/bgm/credits.opus | Bin 2180078 -> 2180330 bytes resources/00-taisei.pkgdir/bgm/ending.bgm | 4 - resources/00-taisei.pkgdir/bgm/ending.opus | Bin 1724024 -> 1724297 bytes resources/00-taisei.pkgdir/bgm/menu.bgm | 5 - resources/00-taisei.pkgdir/bgm/menu.opus | Bin 1578067 -> 1578284 bytes resources/00-taisei.pkgdir/bgm/scuttle.bgm | 5 - resources/00-taisei.pkgdir/bgm/scuttle.opus | Bin 1696834 -> 1697317 bytes resources/00-taisei.pkgdir/bgm/stage1.bgm | 5 - .../bgm/{stage1/stage.opus => stage1.opus} | Bin 1882701 -> 1882999 bytes resources/00-taisei.pkgdir/bgm/stage1boss.bgm | 5 - .../bgm/{stage1/boss.opus => stage1boss.opus} | Bin 1233854 -> 1234193 bytes resources/00-taisei.pkgdir/bgm/stage2.bgm | 5 - .../bgm/{stage2/stage.opus => stage2.opus} | Bin 1302705 -> 1303086 bytes resources/00-taisei.pkgdir/bgm/stage2boss.bgm | 4 - .../bgm/{stage2/boss.opus => stage2boss.opus} | Bin 2239708 -> 2239989 bytes resources/00-taisei.pkgdir/bgm/stage3.bgm | 4 - .../bgm/{stage3/stage.opus => stage3.opus} | Bin 1965517 -> 1965888 bytes resources/00-taisei.pkgdir/bgm/stage3boss.bgm | 5 - .../bgm/{stage3/boss.opus => stage3boss.opus} | Bin 1765344 -> 1765742 bytes resources/00-taisei.pkgdir/bgm/stage4.bgm | 5 - .../bgm/{stage4/stage.opus => stage4.opus} | Bin 1793730 -> 1794126 bytes resources/00-taisei.pkgdir/bgm/stage4boss.bgm | 5 - .../bgm/{stage4/boss.opus => stage4boss.opus} | Bin 2366202 -> 2366556 bytes resources/00-taisei.pkgdir/bgm/stage5.bgm | 4 - .../bgm/{stage5/stage.opus => stage5.opus} | Bin 2235096 -> 2235562 bytes resources/00-taisei.pkgdir/bgm/stage5boss.bgm | 5 - .../bgm/{stage5/boss.opus => stage5boss.opus} | Bin 1732193 -> 1732579 bytes resources/00-taisei.pkgdir/bgm/stage6.bgm | 5 - .../bgm/{stage6/stage.opus => stage6.opus} | Bin 1762606 -> 1762961 bytes .../bgm/stage6boss_phase1.bgm | 4 - ...oss_phase1.opus => stage6boss_phase1.opus} | Bin 1914629 -> 1915076 bytes .../bgm/stage6boss_phase2.bgm | 5 - ...oss_phase2.opus => stage6boss_phase2.opus} | Bin 2960832 -> 2961260 bytes .../bgm/stage6boss_phase3.bgm | 5 - ...oss_phase3.opus => stage6boss_phase3.opus} | Bin 1451615 -> 1452135 bytes src/audio/audio.c | 295 ++++--- src/audio/audio.h | 93 +-- src/audio/backend.h | 77 +- src/audio/meson.build | 25 +- src/audio/null/audio_null.c | 122 +-- src/audio/sdl/audio_sdl.c | 731 ++++++++++++++++++ src/audio/sdl/meson.build | 7 + src/audio/sdl2mixer/audio_sdl2mixer.c | 694 ----------------- src/audio/sdl2mixer/meson.build | 12 - src/audio/stream/meson.build | 12 + src/audio/stream/player.c | 500 ++++++++++++ src/audio/stream/player.h | 64 ++ src/audio/stream/stream.c | 219 ++++++ src/audio/stream/stream.h | 72 ++ src/audio/stream/stream_opus.c | 198 +++++ .../stream/stream_opus.h} | 12 +- src/audio/stream/stream_pcm.c | 125 +++ src/audio/stream/stream_pcm.h | 36 + src/boss.c | 14 +- src/common_tasks.c | 6 +- src/credits.c | 2 +- src/ending.c | 4 +- src/enemy.c | 6 +- src/eventloop/eventloop.c | 9 +- src/eventloop/eventloop.h | 1 + src/events.h | 2 + src/global.c | 16 - src/global.h | 3 - src/item.c | 12 +- src/menu/charselect.c | 12 +- src/menu/common.c | 2 +- src/menu/mainmenu.c | 6 +- src/menu/menu.c | 8 +- src/menu/musicroom.c | 108 +-- src/menu/options.c | 32 +- src/menu/replayview.c | 2 +- src/meson.build | 4 - src/player.c | 14 +- src/plrmodes/marisa.c | 2 +- src/plrmodes/marisa_a.c | 2 +- src/plrmodes/marisa_b.c | 2 +- src/plrmodes/reimu_a.c | 14 +- src/plrmodes/reimu_b.c | 6 +- src/plrmodes/youmu_a.c | 4 +- src/plrmodes/youmu_b.c | 4 +- src/resource/bgm.c | 74 +- src/resource/bgm.h | 10 +- src/resource/bgm_metadata.c | 71 -- src/resource/bgm_metadata.h | 28 - src/resource/meson.build | 1 - src/resource/resource.c | 2 - src/resource/resource.h | 1 - src/resource/sfx.c | 12 +- src/resource/sfx.h | 4 +- src/resource/sfxbgm_common.c | 14 +- src/resource/sfxbgm_common.h | 2 +- src/stage.c | 184 +++-- src/stage.h | 22 +- src/stages/stage1/nonspells/boss_nonspell_1.c | 6 +- src/stages/stage1/nonspells/boss_nonspell_2.c | 10 +- .../stage1/nonspells/midboss_nonspell_1.c | 6 +- src/stages/stage1/spells/crystal_blizzard.c | 6 +- src/stages/stage1/spells/crystal_rain.c | 4 +- src/stages/stage1/spells/icicle_cascade.c | 4 +- src/stages/stage1/spells/perfect_freeze.c | 8 +- src/stages/stage1/spells/snow_halation.c | 12 +- src/stages/stage1/timeline.c | 24 +- src/stages/stage2_events.c | 2 +- src/stages/stage3.c | 2 +- src/stages/stage3_events.c | 4 +- src/stages/stage4.c | 2 +- src/stages/stage5.c | 2 +- src/stages/stage5_events.c | 14 +- src/stages/stage6_events.c | 92 +-- src/util/meson.build | 4 - src/util/opuscruft.c | 41 - 117 files changed, 2729 insertions(+), 1628 deletions(-) delete mode 100644 resources/00-taisei.pkgdir/bgm/bonus0.bgm delete mode 100644 resources/00-taisei.pkgdir/bgm/credits.bgm delete mode 100644 resources/00-taisei.pkgdir/bgm/ending.bgm delete mode 100644 resources/00-taisei.pkgdir/bgm/menu.bgm delete mode 100644 resources/00-taisei.pkgdir/bgm/scuttle.bgm delete mode 100644 resources/00-taisei.pkgdir/bgm/stage1.bgm rename resources/00-taisei.pkgdir/bgm/{stage1/stage.opus => stage1.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage1boss.bgm rename resources/00-taisei.pkgdir/bgm/{stage1/boss.opus => stage1boss.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage2.bgm rename resources/00-taisei.pkgdir/bgm/{stage2/stage.opus => stage2.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage2boss.bgm rename resources/00-taisei.pkgdir/bgm/{stage2/boss.opus => stage2boss.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage3.bgm rename resources/00-taisei.pkgdir/bgm/{stage3/stage.opus => stage3.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage3boss.bgm rename resources/00-taisei.pkgdir/bgm/{stage3/boss.opus => stage3boss.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage4.bgm rename resources/00-taisei.pkgdir/bgm/{stage4/stage.opus => stage4.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage4boss.bgm rename resources/00-taisei.pkgdir/bgm/{stage4/boss.opus => stage4boss.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage5.bgm rename resources/00-taisei.pkgdir/bgm/{stage5/stage.opus => stage5.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage5boss.bgm rename resources/00-taisei.pkgdir/bgm/{stage5/boss.opus => stage5boss.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage6.bgm rename resources/00-taisei.pkgdir/bgm/{stage6/stage.opus => stage6.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm rename resources/00-taisei.pkgdir/bgm/{stage6/boss_phase1.opus => stage6boss_phase1.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm rename resources/00-taisei.pkgdir/bgm/{stage6/boss_phase2.opus => stage6boss_phase2.opus} (99%) delete mode 100644 resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm rename resources/00-taisei.pkgdir/bgm/{stage6/boss_phase3.opus => stage6boss_phase3.opus} (99%) create mode 100644 src/audio/sdl/audio_sdl.c create mode 100644 src/audio/sdl/meson.build delete mode 100644 src/audio/sdl2mixer/audio_sdl2mixer.c delete mode 100644 src/audio/sdl2mixer/meson.build create mode 100644 src/audio/stream/meson.build create mode 100644 src/audio/stream/player.c create mode 100644 src/audio/stream/player.h create mode 100644 src/audio/stream/stream.c create mode 100644 src/audio/stream/stream.h create mode 100644 src/audio/stream/stream_opus.c rename src/{util/opuscruft.h => audio/stream/stream_opus.h} (56%) create mode 100644 src/audio/stream/stream_pcm.c create mode 100644 src/audio/stream/stream_pcm.h delete mode 100644 src/resource/bgm_metadata.c delete mode 100644 src/resource/bgm_metadata.h delete mode 100644 src/util/opuscruft.c 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 c19b9b806aefd907e84281a5919c9db6385789ec..06eecad08be619180317814363992dacb68f8673 100644 GIT binary patch delta 4399 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 CcdDHL delta 3771 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 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 09460c1624cc13fb0b6a8f814c146fb7d74a39c1..741dcf9f400c8df79c69eb168e572bc92c22943a 100644 GIT binary patch delta 2728 zcmXZec~nz(76)*F5FiO5>;x-3s}|hY1Y{ARY#LnHA}E6LATQwoNhAvt5SKYKI&?Vh zRz!9k>i;xgT90e7)_kkdL?F!zJ@ax8NHgRF%_aYkbZ zlWaC&wJjD~qQ@^LUdu7_Y>fuJiPh+JywPN2Y=O#H6~i({9a~xvVq5lv4QKFH@nc=C zb@QcC%jT~iM8rz0;DGS`)~CyQrp8^HXA=Pmze2RGyJmB|4IB}^y?4yNXJ>a-j7@|n zhYfqidPX0eq9!Ml{Nl|C&=X|GN7;&;k)#JQEp?f0YFELK+J4g<+bk*4uT}1m zU~0A>r&(@DyUNd7p!TZ$OClN4*Y{tgLA}7iiOBjSwQig`6Sl-SoF(!=YSVc=gd(j& z6_F>>{v6%{18ojRiL4LTu@_ewq3NFvEfm5V>3RE-E{GHfu2NJVBp2oKYH&*uT#c|r z^+g(P%_xCK20;`x`XS})*S5mUqk;$;FGo^8nHvrKfS{PjA1N~NbspT3Ix2`JBK=p` zb{=M=ICj(c50Jud^^JnX>^MwSlaR*5IbCr3m}4_l1t5j3OdIHE__t%UEmj~(r+J49 zG@`4e}V8AC7q0P>8AW9WY-Iy)2I)TZuGg`g6uA#fU2e-{cdvMR$zY? zCKF9Xdi#m13{HAEU8crqNSpuXaTxx%z)340V- zWubXco4gv5?C|*IYYz)fX z`j}qGPZT>*hFGL)B@G`zxmj$X@Nr1xy*E5zRi}6>RmCHvgkH9T@t?$JXx2QW=pI)U z{MAn~KvfAy`PXLG!O={~D@r;a>2msg6CC@4L`&lnkt%cF=EIcJk}7IULOQNcFNA}m zk}_)i7|9|QoB*$Bt|L^HjI^_1Nij$ZU85*O3R3&{q(R8s<*KLgsYpJByJtdwpDRa= zX-FpwQI{b8t!o~gMmmyH+?xz&ohxmmD=t7ve)n@DL};Y})VL5S=~==pC_F4}p*<^* zwpw=Qg5XE#AT?$nY4x`~VZ_z#5YZx}d;j{$4_Z>(BIslnBaPIptb>6%w=SY3NC~Mo zZ^Eu_w?P`e6lu1uy8_nz;`W?wC=)3*y8S$?n<#6e^jS!T+N5xpxlFc(W@RIN7cBRN zjwadn6e0)7^PnsUOn;LdjkY}!xky9z55Tl>*=j0MqJ##0yB@ZMxbw8sWk{cYxz__6 zHSXn6w!S>1Cbxr7*W%tAXL}FwQGS1+Ne1z^-4#U3k#+?qsbQPDM-F9Qfn<1Gu7HtL zk0+G90O{o&-06dL9vkWJ#!962w7`5Yp7H3TS%pYP)PFC6^p_qQidBU4+c!pgI341- zj>fM-DyYsbg_IJ{HafD^NF7;An_+CPryJd3G15cj)nO1{@%$5wXOSKhhuwoi&R#8) zs|0D-`I*FGH&EsCfsb-QGNrjeqJ|EB(E0I#u4-~);)jmL*&>|_jPSwNa6Fxn3Lpr2;nfUOg z5BYpWyVoO4@AWGL`#|4=L~D@tt!8gP*Gk{RwEHTg*EiE;P_)Z8p2i!HS}$Z?fFu3B zk7&_Gq|l69``%{1&nT%0Wn2+34D(|Ac2bcUX+hYbFX67%Pe$RZk@k%|UJk!@_+=B- zAl;D<{s77$zcgCcCrHNr@fXnUE%%~-N@|fhw`Y6-=d$Fn3AWRyLmKPtybGc&@>6uO p^+@R--e`g=eey`UpS4KuVt1;0rT$lFH4P{ekgCDMPJ{Qrt9Nyq>I delta 2456 zcmXZecT`h%6bJAOLIOz$2^owaQ5@6(8VF^&KtT(FfJQ4=Mj=FG2!aX%qT;}TO3|vI z#f7U1M0!O%r%)V-ry_MA+G>&0mIF#rR8-p9n_T&e_c`}|-+lL&cap7#_3tY5_F+0* zB!k8H^tWVv(R|~-ns+V7cH2998Lydm%`Thk!7K{P$j*w2(`7lLEZ4`UKa-fD zs0!xZH;7kn_B9CyWk$sxDTZT)a4(aXqm0a}>8pEwk(#&&FXo@$0#{v_dX1@uhxD|m zW(d5OGF^x)kk*>sD#7b>}Dbf(w*6Zo{*NtUO{Ao6rxBw4Sh%0Cy1m-DVstXpt6HqK|{!p@?D$U zVHV%4jYgFt?N16w0@Vz&HnnL~1(J(Gu>gj%%rw+!i`2U^=q&hEo2e<@4r$%G@iCD5 z*esUF9_dPFEN6k#zE>a>#sUK9guP(m2L&GBnD#KU3ovq}r5=YhZscS3p%`kuq}m z2~eBI?W3x3NN;~jiG!2pxS2GU3zA=E#{(GK%^gXNpCJ_$?rw)jIj@~)JW|u4^Uq-N zTwV#$1f(qtSqogx=lw?qG!f}RYn?aTG4K}BCMF>%tEbn4x|gT_+_W55q^KPIGpHVE zaf%wbS1o!dUX9e19=#pHhbfnX>$lf-?7FTyR#$l3B21*lSxD3SOGZP;N8v&`joC;p(ai;L&dX{m-7yGBdAwE#0clnv zs4*Dn_@pVfAoZA41?_nblF;H+1IRk9o>1dlq~D8%eBq;&=m^mlNE#wLN3T0<( zdnQ!5iS@M91xO_W5xYR1EMBHD^({o&kTU?=D#i6aruRULqP=&$8bTh5=MXJIQf4G` zph_Z%pxGBAZF?x!!N*yW=QR75NTrX*FN2{%NfCW-EJ1QOQq~R|uSjlCRy5N6`qq1( z9g^s1tQe%nfk!H!)y<}W;+G=1zA0>ks05oTI=n-R)a;+96g0jtK z8^y;V4aOuCfx$vrL370;?NI-c3QGc{J#-ieNDBt$*+bZBX)~?)YowbFH{D=FjZ`qz zw24HdGtQB>!Lwg_lH!w)H2Z6m(CZ@m6t6=X7qLp0+DBdgEPVsuA zKH;reaJ((sO0*1VKx`$3I*$Brq7-9Qp8PI+?=M%<9hW10==dfW?yr&qZ6X!vVBp|E z`1YLKKo69L#N4GF1Z}sxhIXHhv}t(x5ZI^`-w|aX1&HT3!jI94qqO@KNQqDWb%wNZ zg)hZtBDJwDNTB|fqL&t(g~V_U+XH8qwtH#Rl_;^H4dD>#ZCgfF*+^YZzaE9>X|~oh zeGbx3s-M;Z@09I)qE$%2zYG6>#BSSJw6E1jTUc=!@Pph=Nmc+j;w& zjw2T-lDD)697^pj($VH2z3`nNfu}8Y9`rzKkvfBVeZYxnf18$*k2J$9W+%MY*zdCY EAFtIr+yDRo 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 fa4780e462b3eaa2bef520933f77a6d41548df33..9786155a97810d31d3065edc1f9b2c978acd308b 100644 GIT binary patch delta 2411 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 WB$o;oUoZ(szgDFc!bFeIN%%jz`k+Ao 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 21faf9a746052ab5c0658fb04646bf11382f1010..070fad7defd913f7c324a86c999dff6a4c3eea86 100644 GIT binary patch delta 2760 zcmXZc2~?9;764$D1Of?LfCLgLzqo@2!lGb|2nrHqlLQD7HhFy?Kn6c?TppdaYn00C~k*3)t1rW;EJ_#lWWf5octg6-gn=9@6UG= zmZwiFlAOv)wTqj}>(8#R}V%3_mb@|YN2r@e6F3{;IulbNQ}RIyBYZ6%{JuQS+KE0b@wvaFfOGFbIh2DZYH zo|30gsWnQCU0=f#1UVoxH9t$K(HX3)fyuShRZN(AT#YIx+gZ25V6oa5 zt4&wQ-fel`%Gjz{6D#xfPT9yB$_5K#v((zlj4V@)6_{E({#7vR3~Ymp$urc| z8F5PpHiHSj)afv!TxZtjvy4t}skK@i`?NJNnYtQQ=8rqb%*n}BYjDHLxY#6le5~JV zOUquSOh}52Pgo##o`OrupYcEVaZKHDBtK1boIG~#s|5lFxFghwO_v7-5@-y{jPENnS|bFJEN@*=Me~6y=RnDxFmV?PIQ=EOJKikq%w=`596p-Abvv04Ye+ zEP?(iw`?NEal5QITm`&CZb0OV60V!7g^NGB#Zd2#sjPlCX)}1va-U62IEMGUlRF8D z3ik`tL;zCP-3v_+@UgpkiL(hI(u!eW7u1cqPp9&MNN2sCErA0OoS&#w#}&)pv;N<( zr-XBaY6T-rW=^hv6@TKSP@)LwRiAqh>>cL(P2nsrMv}V+Gy^N<9;T=er2YR5Nr%t^ z?yZH+s8FP%M~iO(r;{5_BtdG=yKoGmFLK|a6{SeyT3#8S1U&fAE$Xl z(9t*cmgMuVrAR*XPepOM@IH3OcviKZc$hxuht_{h_jXgboSl7kzdelG8AnuO#1 zPR!K~0;h^MmgL;_OeDdWpcsDb=B=TqSx5)BooRuJ5#HO>gySb=#<%BsC*0(oR#WfXw-X>rYc7olHF{F zJJb0hI_+2_zuvTa;I8LiB8o#gv*vLrjCAwWMDa*+Q_Or=bepfDNhBamzkHX0PN_h! z%sH?5NXw&x%b>SJFih8X0a8wp`4sFvAkflq5|JL&oF0IQ5doL_NGGLFmuV4_$E8C`*mlF`8MUfJdi00!1+X*3_aP-N zM$)?{OQB8cdz5}Tl975i+MBTS1K&bwbqP}2r$eiuXV`ZiHIahUH#T+>GDG~NG}Tlj z-pXfRf;G>NOS4~!v`1Ik51ZTlmeX*SA(it6qTu@1e(z9J8j`6r_ZqBw<(Egb(vh_4 zztqFkM1Spk=LnZ0nHBvPA+gr~DNzQ}KMN0i4~iatf7+u8Y5hpxmrywBUqd%%1=9Yl zIgxNFETEQFT#1x&sv!JKUZD2n|OauLS;s zq6(4v_MZO`9{2{`qb9USo6faJ;RjXFGFSdH}e4^w*~Bs#c;64xNz=Us7yj>_N-wBlN%n3>FM5FHFQQB(<1 zW1?vubPNUCiAs^4rd?|S|3HzA5_L#*td$piS$S=AAJ@9 z2L?qW)K@u*1Zl=q4M zN{LlS{(b*ZL(Hhyk0xP2now;Ug_U6;GOAULH1N$I&%>6YkZzi44U(5v^KFRj49TKI zBa(e+b1Q7W5Yj_rLh9%A904UK^lM5qBjx;jSP#C7Li4D6EmEXsbtM=ZL&cO>hqPni z76Wt~3*AVG^+-4FWqbva`=R%VEJ#9m+g0$JDhZ)PE7Gs)#|9y;Sh9o2hIIMtn#XYQ z9mx?QJ5ua>;c{sGPLfWv4r%N3mQc9jA(hbmYCtlH`(*GuMarcSHX>#0W!Hjtvs6Ic zHzCD;-kuB*C#A2qy&j2wO7jA){!i*di5rmAzYJ`Kv8XAhiJFm4L_D;>7Zp=h5&aHn O@57EF5Oz;dg#I7;cd9f1 delta 2461 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 O(wTG}F7zpf-2Vr1wQ>jm delta 1643 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 Vi$*$$I3%|f57)!}-z`Ff{{v3)ceDTi 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 bfb61460239c0aff5fd6cfc43b31fc4a50fa0762..1333b31eb81e2604a1e56e73022cfcb1eae52db5 100644 GIT binary patch delta 3142 zcmXZcd0fov8UXP2Wm=|f+NShN3T=vL(W=s}WE8SAD+@;={~rZxW{tT#TD80QK{&vkSy8GGxM0gX1?=0&-?tI_x-((?r&8cf4EiEIzBUV zk&2qiw~x!PxuN2#&in??N^6Jdir=dEyZT}YN9}8Me13sUAk35*AjJyTX3X|+pXuxF z>EWSOS&+P8I;tQ_5E~vK5V1_Ch-IZ21vyNDl$Ei0=`173V1!JVP@2s|NLDLKgd_^0 z76}3b1?kyLl9>Wx=Piy45D3LGR*ZR)EH+(!9X%|3QD|aRf*>k>K|rK9PsqdwGsQ*1 zTw!lnm5h;#*j(0~vwmvv>ta^+vboE)YgpOH(LbdeByaOZHMzWu}Gh~@Ge zmdRzM>0(8nGD$A0NS3)WX$5koNK(MaqJ7iI};Hm%uUM?=Vdav;!Keoqws0-B@!uC!3oIRq->6uUCT;)%j%UwS1J1!J{o=$ zLssknKkH#_)4-+(eFdl^-1vRZ>Xw4Y{qvQgfl~B#&gK@Yod<)JqKQ)XPA+V@aO0Y< zQnXNvr=%T#YF$teG6V2RVU^uNf)W5bY&#?omK55 z;vfkIYqP-egX&EmWt1M$`%NisFz%`Ll%n*JG^dVjg8p2!MruN_wV$t)%!TR(wKgKf zp*pl<;xe#3YEG2MMe^9N^fCO+MtwVx5z>n~FCCb%M4eA78Y6X`U#FQj#4~;L`<>aHmhJn`$vg{2A+v;Jl;u7?C{^!wXh}woL7xh$bVoUbgK3 zQKNPOkpt3T%)vppIHYYvGzH0~yJ{IcanXsR6&)3cPa9r?Z?=wBfU*fEq^Z|a)FI|) zoh+iMNU27<@4$tAoq06qX-Hj(S?R!?teZ%irnnnoFN&@~?h4&Y6y=QM{U0+22&~rq zNZa9pqsb54>1MrDit<9bE9O;#J4B-_`^V_~FQ{|hDhB9)u&Ujy&?2CilT*N5Pb zT?Wk*<&RWzyu%K*{MW#WY6Tz}7_4f8f9M!~q^%7^iq5Xrgyu*?Aw>lt-Ko5N2kbT* z>Qk*?qz9gJSnxh?c#{%CkgW4PT3gMyODJz1iprAiHYoJxUZJ)^QG9DwJ%pIG+N{_u5$uYa`7^M8ubSp(AAuW`eoQ2Vsrdbq~j5Ip&TL`djW@o7TB}g4U zXQqOS*z6Wfbt%&NAzuJat=YE=a~aZ{R;_ce`+?azDxZQR9~(4+eLUVRYIQl%&rC`- z$QST>DQX4M@tuNrNUPvA5UoTKwfm|A?;`KTOy!_gA#rCtwg!WDJTYCXsYvfGfAoQJ zXLCDhRfuHIbN7N{ta&M2Ole5!QSsNoyTQDauGMs;FP+ma!G$67mlU-cX@{>!8_v2~ zydh$d!fwYU!Pz{EBHHZ?q(@FC&0t=O#ZfxYnMhH!x2s|7wZ#Y>CJ|D3qv~=P_ORqo zEin?`zc~ferIr(PzOs;759XJEZj0q?s+EmYyz%T2h<??|Z01m@5~P%?{?6d|$tI3wvmPmI*oc8rU)uL#`m7*!kg#zC+O7gMoJDoU;-zc IC%dWr2@^Q@j{pDw delta 2861 zcmXZddpOi-8wYSR7-pC;hM6&AFxW^AF|;XT98#)vN^+`KPUWzQO_3$hK}t5$w$-kc zRtII@w}a8%m3>o5+a_CTuMVUw+f{Apu(6dx>}_uS)$enC@B4Xv_w&rpKi%bw`}fKj z((tsjg*r@~iGS+W^oa5Q+V8tMNiDVa9)D)wbJXB=38OSTXH9NYQd+JRN>)a4Ha^Nd z+&o?66A#N)rpVph6mA|&_3{|%tW(lF-cLtoywmtHsjRP)HDip8!N%jFi}Hm{+kMSf z^Y+fsiXKYqprb;gsQTVtEBYw0r)F=|blvEfp%oU2jJp5uWFDnWaFGlHzl;XW5oQY!59v(xxeh4qWjauy36eB^^BXu}rMsPokHpg- zJqvT=bS-H`QzRqB!$Qbb>wZls0n&S4wjJ;~bc-luhIFQA+aB1huUAAXnj^hiGDiW{ zetO|F2_e#=gV(&FpinQ8CLux!ymI~y+&QHe=cRo|F;dN`4Hck#u4n0~B@3kIlYVl7 z6f6Bl)Lw#Q)v8K@h#39)Ky6Bj^whdC33R{H_nW09OQgt}_k9r7qrW;pOERQing*KS zH)~cK9pm_=sVX{Q0kun6Nwc*nYovnB>LXx$h;_$LOEySP7p&LP%k;sP{pe` zaL>V@pU4*JtVhNpxSMWJLu7~4eq-4oC~Yu^AhJhF?E0n|dR`dti9SIx)!fs8K^MbN zT5%H6y1%}^2$5NaEMM&=9FVGRx_=I-M-4NH9Famyl9|x;#4wNsJsD|R^xoYt*`6Iq zhvtN|_c%Wh$`aUZl$wGR@JZ%th^b}|({VT>ahq;5Lc(qKEgG~7k|6fS8t~_eka2Otc+#-mg?1F2zWP9T_C zaYLwZCelg8?IFmB0XNKX@HRX}YAS3{SjV8}j+RHQ>X$N<2CUE{XVbh9pkcQQF z)L`SnpE_Ne3P#%6u)H1OQ}|q3aV`?~|6VnaTg#Wx?B^k^Dtj;os_*le^s}0eBnq$1 zhNCi5d-{ZahO{oEFCFeCo8Ar5whuvy9#EZxpX*Iu(!N5GdPKrfIQ7JIChaQ>>AYXp zC=A;Qs_BKokyuNgJ%qX?0vUaV#{WRDBbGJ8)m?%#n#2MmL3_?)@cm7orou=hU5U#j z=rS}jqr!zqhZ5~tVbI@9NvS9#+w8iB5Kw65NUyyJ$teFxC9DRsa!N%b{c|}d2n^qv zWl|~ziLWe3f?_xGv$XqIq~!StP7su7ev76WhxF(}L>>^6T- z?H3~nW&2~{m`Hewwz>o<$}9UmRE7(CDfJ(uceiFoK;brFJy8OZ)64x5aBCCxDYS!L ziX?UxoB@Xq!VLPZCL-xTVgpn=i)?7CNhp5Zk@Mm5a#1mTnUaxAQ@2imh$Et6`mUxR znH(J`fvy*#HcbXz-?FY0MRm}0am>nZfA=N=(Lw39X@wq4J>XFAEFzbhP2OL zsRZKz@hiGa=}5xvT_Rv9EI8CF11X9#vheDW(04#;K$MGg(En{4jQt^Xpc7h+ zl(&9r0C-3(H&AK~(s5<=3uuY9{ELnw4=Fo-Uo153w6vhYe57NIS?}QPHOm+(T#Mvd zF)agp*fJFr79j1~|1_?CbI`;u^h=buoGZ4VE|o2%aTOxD_XcIb$a&eP)T#*SWZR8F zP<@nz(rnftdCFc`!k(E{!IUaSit^iY51!{)CD4NFk?a!GRS?x^^#f4}QvQu;j=*?j zHI;gmBFPr?_kz;dx|>oPkR(i=80^!mbLoz6L>i9#rVQ$8tw$)e32E`S2h5?N+xl0c z%}6^3-{!%3vCSqr>@p;)_VvAB5@)lLR@{PgefkkDY~5`WMup`_#TC1F@MotDgC?;R zsnb0^z5WefPN8imAGs-$VB|A-9Z?0+7ERA7XxJhLI*jc|!3Ba|SaDJAMGICUg^y6=zXM6ME3gP$a&2EzuPUUNt-qIn<1yPVnzI^7>>H2^ X+!waT>DKQ=O78r2A6#*^b7lSugEt7v 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 424a68529b9c6cc73c9cb7029f59f02b459a405a..a98d788f0df627e2f105622b1a3974cc133ce0e6 100644 GIT binary patch delta 2966 zcmYM#3s6&M764!g39s-9j{t$_MJ>J{0wVG-Ac)#1f`x!@A;1ML1Omwo2qO4k*71Rg zx)e&S72l#r*XmKJmQiu-xZ;Xz%XF1i?24_rRdAIG=$_;_(+o4@emUp-=Rf~H$?f6N z_IIVS#GIVuF@iB4Uh6Gq4Vd2c{&8hrt!%;+)3XIWJFfNKFL?hdvB+ppYjO+@Nb#DI z?3rPT;BZAyV4zj4acR|5RJBSSKTkQID`z#vz6}iqWgbl>aOP-sgTB24ZCMeap zEW_t=`Ygu4YjRlgDts{Pt@*=R6owAwO;*K?Wq_)o_eSeD@mF_C4mH2Qo6 zqvDw&qgHER%5+Bj#GhdcbBx+-^RqvrlZgIR?JHo#Dz!#urd8`)Y-0jpqQjl8J` z1H*F#tUtr?OaWJg+ha0V{4?N(I;KdkNjrvO!|M-tP~685WPi%~7^U7tTd~4tGDdctVMG zNPmoSQP9zCv4#@GNaaW35@EW7Acn{u$#+_#6I9F-bW$zTo!RsEfmTSZ5^M|?n>OQ! z^ty8i^j#3dM3@(JLh*aBa6L>P6fi{2NSE6hM6fczGJ>j0kYqL!>){}4d4$LX>EF#F zBP4xc8A%JeBGp7^_(H=EmZLO1(>49|Mu7`7xd~6xu-uUr2i5e$$|T_{N|Yisekpta zD|ZN+sk{f$vi~gD1uh-JZW@FqlH4NUBn*rQc}ny`60C5$1o}`>Gj%URdiZpp3#Jr{ zb`yCcNxt{~7&@9o)|BXjbouhW6VUUE=mxbaNAhu<^At|{TKQ4bSR~ta(ObB`!s<<= zd0yj?glqB=;HNq(KOzPx(P32VX+>Y8-)g`QF4$W~(zeGVomp#Wf%huwIEtEp zB=4G41Bunv$+Y4`r23kJ*P-RIHBa0A2x)HM_+n7}&w4B+`XN=i-(L;OrrWU8#3ZDk ze^n~syF8mHikgfR(DT_Ia6MwPm1<2vI#9A=7>?h!*-D9~@1e&JMGHXbX)7a|hNSD3 z)<9;mt(uO*A8EX{E*P$Dw_QN<3P5Vwqn`=Vo3`Il_X?zN&r_N3(8BIKB~C{&s();Q zLlJhLQD1>b_s&;mfXZm+Nh<~+{aU+YE)=xd)e!|F9T?dc22RiHTB6OfpMf;gXH^H^ zO%q?FS|LdPJUqQ0c4mu@Q{qgd^yN)EA+|{zN)(C|`_mU;Q2juBg=iMi3}$!+BzW0( z5``fh9eZ&YCa2ghp+gHtiY|p&P`$^#n6@2(C_EwH#_zmq5uskZ6FFf@uCbkx^u zq>~5Vq=Iz5Lju)OBE21yeg#FF9m;9ND5UY(8=k=HD-O45gwaTo-uNov&XB`VikgE| z_9A5~Yz%R{M^Q0Ix4Jfd3+aW9$7s;8NO>8zlfdy#UVx2 zyG?}=foP}GOWNZCBjTpQ!9inc1fr5$w*!M%1=PkUtBnfQX@TlS8wWT8B$QU?;=P$>?)+h zkUqpON&1=5+p>lJX_+0B(`CDPEBS>EtyzS}QUeihOtZ7>V>D&0P!R#zjf znjLKm!Zx?f)an|fx^8PR?0xMvg(wZ_<=vIOa5KpLDs4L*>BPcqkx-ZKE}}!zAZgZ} zcn!ynxPQ3h8Av(rxGIS0bGN4rWg_k8OO&u&CQYGQSxC}y8UZ-cU@Xb8_Gelt3SF88b_qNs8%jgc#U=}gob)NB;t?;h0F*{DDvo} zT6suq@;*6?9{1>=ALx9f+Tk{D=L?^m_S_L`rUIm0)yhEV&+~j0 zW2Qo+Pa~dZL;opH+j(ZvA>F;(^Atv2dUjA^5mL$NEmF80;uT5rDn{x{yS4z_bY8pZ z%Sw;5=;DRt(Awg4mC75Cp1ep2g31?Or>HL;>2;vpO$ZH=38=ggX~m_99K;!9r&Q)9 zN|3xN)CL&&M%GN{Sc=pu?iqr!L$W}c#9E{q>w7r(Jy1s2oYz zcWX18Kj!_I?$x|*2rTvT>7@I$9%;|501*VG`h57At3-+o z?%fYPyM4Tt<8 delta 2593 zcmXZddsq`>7XWY=Y~!+x+cwz7V1yu`+>7f3h8Iu?6hXO32?Fr}jyr~}P*B4U0>qDp zL`)$Ry&+^si&i(sifKF@?`vh=gSab$_NrBc%7^78ZctP2FTlG)8*1$xaVicnk#*2We+c zdn^30f}y5bT%^$5`wzkSGRAg)W6fcBNVW${A@&SI5un}B1ci0aJrG)E88RYMBth4c zAW*yM1yFS}B*mSnWZ-A$wGo*kF&B5W!|-Q%fwZ9oQk})U^0d^rB+X2bM^=lR! ze#*9?L`S5jb}7Yh(Ze8(nplYBVfw@gy`X75XANDNKho&=>4z{^&&i`>2O!-X={N|5 zS2>>$1tJYuo*V}rm#d_{kL2f3GN zguzHYBO9+l=yUE7iV8sz9v|ev>|4AW6s16N5E*mfhLU%b1|5o2u69g;UF|#*idu@) zQT5>r9KOqYPPLXHDLsY0a8znyM{i;|(t-W4!!Qoc98CL)l6CYpy~{CCq~YV~cT7e=y3=x{L;(2kKv$7B(|!G%<_OiGMGiqpwyhu|i& zIhw>8q)f->wGcXHHb}Eyi}Z>U9|}Bk^BQU*8Yzu6UJMJZbx4P<6%2#mWAjXk zia~nu%H%wGsi+g zeZ?V#_1YeR>unbLlo*ecob^=?O#Wc8k|+Ub^roHz_?q)Ah!T-rjxMZ&_2K+`R6Ysm z_lJL-g%xFd2WoXA(l;SyGf;AtUrwziBW+e0MFZ<^zBAD~NPFjt1|ZN|Fhs|G7ikq= za1{)51WdZLO-K!*-K`)#DtP_KQ;-&~ovMOUQvwbhC>1F!Pd);dB*Iv#wHYZreA~C+ z8Y}#iqSBBguCoPjy;@jLl#XQ1mJLGQh_H?hlz}9bj^75}ys(C9y@zz{j7pw^VR}B9TxyLMr=?^*!$u=P=9u(7-MIqW7%0^n?8QuUx&Q?{S zTFODPkNCM760)sk66i zkHhC1tY81leTWp)BdUefd#y#(#79V9yy2PxxfiU{iOP^N*vWF3WJpzXkK2(t6VD|= vmR!1z9>5MwRKHI&7#B*-sMT_$P}RU$a5^r{qpPhzvi(z@2yKs~O=kZBHwZ?~ 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 d01a3bf909c8eb1169adfab98ecd518de0323378..d8810747958b6c2e3ec4f16e40d9006fb2f7e6d8 100644 GIT binary patch delta 2877 zcmX}tc~sL^76)(>Ab|u3gf)Qr0~Hq(*))J6h+&B$Ac>_SN<;D^34{c*V5u9{T1D+t zsEk_c;Mj_?Xj_4E@u<@|E~7FG%<15`k7KHJtkt4mb*jSLo|C7<)X_r3exFUh~} zRy?&=gyd)lt2$Kq*%! zQnymV2m6g>nn22T+I@TFeSsh8nYN}#PWzM&2O=g28 zoUzyLVRB6~8H=fsH5plkHCR|iZ$TKV)Ek}gO1(ydYh$sBvD%DA))37Uuv(QF)#8Y6 z!uAzL(^`w8l2NOSjK;*Mj5Ssr9t5K=b8?l+H1$XBqSrxGma>-e@(OG&Z$WZ!+3zn?;V>$jeL0S19pV zQ>1Y*F)?G7pOGs|nUf@)i{!lOakZy0jW5U5I_|x(cbI=}^CqB6nM5gWTJ z-YKpq?|D66(s4!eYn)TuQ2zBQyP@N+d#gxzD4i0E{mZN#D&iv;AC4R8P`p)>xT9Ek zABo^x45ysP18J({oBNQc;q*}>0a8M5i5IZm2@l;G8@g~nF5t;tTBi1g`x;R}d-;HFJ-5`&Z_*=vLS6L*2(%+yY zi$6$PjYKNGIc*SDZsfn2?@Uca3Z0R-57ejm<0K~grKUj?7Edgv(?i)0zJhXMDVhmI%?>HFQk zjX-sfU>co7Jkq7d<6lE-p5O&-A_1v=`r#eWwoTAQmo^8fYSPzHU^px2q+WB8{<6v* z05gXLO;jjFYM9Wy9j-+Qo2h*w5;ybXXHcgR@+g&rbo6;k8XPzvJVTU>R4@MBT~Pl+ zIM!<((z@Zc67Uv!{z|>(BW0gzuL2*LXCzSy(n@)}4bs(@t^-7@ji;$qc z`#Z?&_X?ipd`x6Wz1wm$P%iKeqf`dc`a|*Cp&{LSKHXy`(xHHB>sSB zAY^xW^QbTzse#`rf}$thTZrUHJy|dJ!=ovp!$gabb{?PgPuRO!w3Lo;3DVDtE#WZO zC~Bb@bCBA^a(6h=D~hLkT#B^6Ik*lq;4I6(YsxhFYN_NxYrfzl(Hy z=!y|W*N87rY6a3)dzyDce~UPcs0bnFu=!{$)g^NIem+kHVW~|Ig`rtVViM?#aU8TmEuNX^`6AKFx%e z0Rfrx#IX)qz0d&iWdYyPHI^Ygt@XMGylnw1=xnq|Oa)HbaC>&(EgHKV=~YT{3Y;$sl+lb8NOiwdGT?V8aO|87NGnFm{{uf<3;c|F zRU)NLF3E=vMM0aX(1;YdWami`$%BGv6DFj`vdl_|-yGCMg;hug44oq|+#R%(#$JOo zeBz-UmJS7t4P-{TP&fY~oQw))=_lHPboo@=E|{bXUPgywMS3v#r7P5a8Qf1R+K}Eq z_5fhz?O>Lu8tLPCSDrwxza*Wux)v$xSZq6l9-S%+a!auuNtKDef=d+ zeok_mR{Q{|pTA`S6pTnlsQo%5vBza86iY({G~;?Cqwl5G-a+t8nUY WNCf?_ZbXVYyLARU90>Vu$o~KjBsq%! delta 2475 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 delta 2439 zcmXZedsNJ67zgm0noBh`HTRjKp;m597eyCMqC{m%gj7t5QA$dsNZD|9v*#eRRC%b!ja7Yq zSiC$*<*xQrd$3CNb1B(m-Fai4iHR}E*rvp=Ojn)7+Um+TiWy22@8Fwi8~43xM$A!a zZ?BkBU9;fbI7Zkg&$EMTs?TmaOTs~!cq-{hb#9ZXCsV^k85bnCsb1fAiE4N#hXcB* zAabl}1r=E!agU1ffVEd#bg3XuV;KS%5+EJ&wfBZr11rX#AzP$3qRTr$-N|aA zDm$dFAJh)kIGH^nF`mJRwGCbHLT8rZ$E*_}Im-r8Aa0M@OR5qh`RbO2gZ80W1kE5p zS`ttl21h04jYLwU)8`&ef$V7W>okK5Y3mN29Ktr6kMd#STU2 zykn0i8j3WQo#PDQYK}RPBhsRI`MV%4g_BGjRY*eihhVsIh_i)g7}D-k`(Rk|l;c4| z3`f%XH}T<+lIu!UPDsAFNsjP%HrL?G?9myizo~u&ly2r0P{%Kj0$#SpgW@`O8!a4x zR5AHNK7cuIkjMqeCn-1t8UuNGL?e;PKgF_P>T2FxqESdQu7$>e;SBEpbsUY96BAty zbGmu$baBQYU29qU34BIa?55v-ERyGzk45lwfrUL~xgs?l?kom&xy2r;QX^%pcKZn` z+AQ`FxgkACaBG4XOG_WxkUP@i8%5Dj9%9)-LwF$F(<%}mv&b@m$P;N-tG@;w)mkapygA-s^PVveteeXdr+iM)|Gr2~y{LvNK%3&$fR{rnCf_<&VB?QsH9 z!`t2}sQATdCsp|%$^Yw^29ZL(GfnS{w06&35fp{-L#fIS$!A@W3EWxFj|*fD%^xZ4 z?+b@&JeoBT1po)5GhDk zIvG-Gt-~fURg;heb?>=wvCn!s4H1Ntn=)-3h&^py(}pG^-GAZ|0sK@ODP6fKNS8&r zE>It}VG#u*JxhEM3llqR&Jcwlg%s~R4Ff|3mE)O?Q;`CT2Hc<~M!@xEC=}^-S=gV@ zyh#vBRnw3%{HEK0LxUiZXgbn(?WluL(kqxiKVcZstYok4(BfwMo<E zO#!T3tXW7o&xh=Vs|w+K%9@R& zY4(2wW3)mARmC7h6yHjPs0~5`(HtcI*PK_tJ}*q9j&qS7Uzk|~Sv|r6I<$F6H~W)Z zpnr_$Gz}4p^sS>I8fub71=Mjql4@b-H=rpOxlnu@lD((v5XihGdP5YCv}Z?&7E(E4 zZ;DSq+CS>V12`EdenwdfkTTi}lVNAJSWM@*5XtM_{FeX*aRp`Rkkmclg^=+~e2gd& zspg;jZ7^IW>7uMfNV}q1kAXZwQbkosNZN*rm2kgU(oU3&G-6hXGr&2?aO$`iX^PnE zAar(1Dk*CT()+b7W$?X|bS_otks4Lb9l(l}9wb_d#6Iqp0jD=h+4NeaAl3K2ErEt+ z>0-LRsYoH7t3H7zN7k%iF4Hok*_y3>zzvaE(QB24G)JS4gQ3N;tCW?Fr0Lx_8UCu1 z?W0$8Ins|C9!9{fJ{g-9W+15y_sqfIEf1iME0B&Xn&b{Wnev-NnMl2#bZ?=hQvQLm zvXF}Pp>k;bL*7q!VkMGp@R|$U9i|XaRW{Pa`qvwwJ5EtZ=a_@^BB7@f^2!t^Xw+P! zbGQ1VK;5R0`dJzOm8?RlbuWZSE2WCo<)KI_elUSG!OAwGe5CdJ6$@crp>iEHT#Xbv liLHm=PXSUxd(&0uR@>`5{s%PiGzkCz 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 9865b639cae69a925e27c0c3423752a69b763ffd..5eed3339bfa73db73fad76b553acb188b599a1ce 100644 GIT binary patch delta 3444 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 delta 3032 zcmXZedpy+n8V7JQjN6zQ=39VKO zmdeC)x~Nr3WxJqMD|ITRPVMX1quY_}d1jvc<@bI)&*%Gmzw@2nYmS~NW;|#vW=Q6$ zR10*NI$!@Rta|M=|4QPdR_}r}ye8k9`yDlRwa)l~QjuK%nDUBCF|s#)8y&sXp%I`aPQvnQvZ|r#K%M2qiu8_HHP{za=9A+?MzJOfz`j3i%el`&GX4iv+i-x*3GHd1BaRtIntGFe0>NN4N* zxC;JrnC?Uzr1I0JM&O4c=ARVLMY?6^ej73_GRtUE9+GsZ(HwZA%mNzK6e-g^V-f80 z)fGZe4Z>^bYB^&|wLYSlgoZ!)_-% zE6Nffu^(q0fJ>|NHc!#UnRv5a4)QuyYLGTwigcs@nHkjfv-;?8WJr0f@oykstlvQ! z(%hM>X#)=-YJt894Pk|(*s?hU)|Bfnrg&?lblKFW&~shih#D10rs<+;c%^6HM`VNa zS0+iPhW$n3!!4Mpd>*nTyxf!cQ#*zfje9Bx&H}ap3o-fg6z>(&|q+ zS&->uIOMI}kmegOp1;2sQWFf1(}o<7T1%%pgHxR$pJ)Qoa9M~gg!UT7QR6pADVuiO zg=UUXJjFXAz0Z2@2DTwaU398WND{pn(v~8lFP_>3ol(Tshj)O{d82f?OB0a}$vsX& z$vdO5ZkcvL_g<48 zs`5i>>fUw@PIa4fQIZrxlN2%^UvW1=9Wbtew511ACZ5oJw9;{SHS6$Bw%eX5#i2&;XBRyiG(ik#xM1e}z@^ zd9jo=3nk0%fDPO!<|(Nv1Sw6p_%iS=@U9YtA~8j0XTsBA-WwV<3@Knw&M9~>!E`ER z%|_~sYI6j;<)(Sm7>+c5w5bW2s!Xp@;~b=+JDC`Jplq6nmbDFG9qv);^^R-BLYynTWr=-xH6r>X@=AE*2~ z1w4)Uo^%T%k>*HpmqO+=el=A^A-&n~q#VQ>_}28TT8MN40*qnWNxltLMI*_V`?*8P zOFoxYT!ggGp)(gsq=Ias7^Js9cU%Y42tgdtVkBQiK@Dsy5|~kZERycMByZT$D#)a& zB}iUZRx3d|EZ9mP?NX#~Zl~XaCVSy-5A7vihP3fQkQ^4q2rtliEk}~vm-c|7O!%BA z4k>=+nsDG<7KT&&3Zza>LL#U>3HzxkUelNna0TW~6aiHwAl-P8?+$_l(bsQUB9iOL zq?f?kC90vhl8^+Ssun`?4UrRNC2O)oeFxy}m?(lsh2%Eid>KNO=2k>2k&Zp-`wVF+ z^8}(*NZcNiE%2x&0gx@0nZB>+5ewzj`_ZLor{ROCQH-q(0Z7VGxFj zBdKwXhH5(WAw6Hrq8H3sq^{BWHYjWrM^a-7(ktttWAI{FyqE68I;61jq-Rj-Y!OTE zobQo-J9Dfb-X&YuQ+z7Y#1+vaV87p@nkWs)zQ=1AIv!e75~U-F?ItK8SzyT^%0S{Y zu(BX6+|rdO6Y1_&Uk_+3v^-5itVilxF-Zn%FIZ+1{eV_i4yl|d4=JNu`WkF?WlN|r zAE_kz!4=r*Cv%~f`%g%oRT0r}B~xZjdn`a|bykRBuu-;*s1V7ab6PA!49X&Ck3~r5 zO4@UwQ6b+>({Dx^ZAttV!eZqEG(<7d{UtVo;I~Wukg7_MK7Bk>0oL8}r$k$jL`Ukn zz=UHplYWP6Mf&jj<`Gyn+v*TiZ9_UD81n*dk<}uKFGV_QcPbT9+pK(OLuE+kZ+ja< z%NMH=YAi=G964PH=Y6cZiMAts&dp1Pnl$SeI_L_dj2`?k^w&x2EUMap6x{6l5DMQ| z2N6{w&AT*M2wirHcA_dI%LymEp&?FDL+4eE)Kzvq8fNWLSX0$bq_)n2E0EfwVAAxv zki;pT0J&V7<5aa9$^UGa417avZW8T5N{+v}9ry({W_0#7NTqu#kAiEfO#p4X7U@a! NhdZ$Te>O_n{{u51xUc{K 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 4b1cb8e581dd1a6dabec26169528fbccedb396b1..a4cb15299e8a611d136c40959ae337b319c4cb0a 100644 GIT binary patch delta 2759 zcmXZcc~nzp765SeB?}NBB!p3)3#e2uAVCl#B1=S35F*T|P#@$ac|h{$%Yr&8*j9@k z57xrCjoPVt#B*Gxf_Uq=VawQ~PPNs#))^OYmfE95=ZG>YbCYZSN`BwH_q+FdU*3be zeBV7@Q*5!6`S|&~`y|J2@AZ6bcs2BDvu13f=eIBZo|m$`#(#L&zqrBWFtQeh9Ic3T zn{^q62zK7#bTC%d%`r}aF$u{`p^dfhc8;m3=WW$k zmSJ(F5oc}U?cPiqZ!*<$j9|tUH($dGF3)~AJSRGwf@ot{yNPj%%Ne_XTM3Mr(Vo(q-wR^$LrI45?Ao!d7PeS9a1&n>Cu0dX39L^k8r%Ls=X_3+C`E8l)gXz z^>vrV*+11A6NqxSJTk9Ka;PiaD?unZcXJ+fU3hpl!z;lkibUW4%0Hsnt$22d=kRDt) zEy54^en~{0Te$ke4_S~j`>mta!;mTh{g=Yd6Mk((o>MWH%b!8kbH7*`$8&n^zdU^m zOB4OqQmYE22`l5IFi`CuLllYhM0xBMME%kKB~cX8n_2rNgX%|r4VCv?+Vb<-6QGv| zxM@Zu(wR!-_i%Q0K)&AFeGJmz)$)FbSQoH?x>q5MFKQkQ`+EW^C{c}cQ60AnLSF~m zq#a|Cj;t+J!7pin7l<@S?n=dVIA;r-NVVdS7N5H*!i$c;0*d+#lH=F@eK7le;AI+n z1k$s?D{o+oI%ok(x6OA9EJ3veQ+=A-w^bGW@M0NK8aZaZM{M96g3)Y z_)St3^u7*iB^rYis4Fr-l`eQMC5}b?}`ie)|eQ(h$ zSaCo2C0&37B=y+?)v$U*NHismM+zD}R}Q<&Lk`l86OjHjSoIAo+!XR2)tZQ8XcAqp z@q9=JQ6kdXuV$Tu?6)DcM4mU5+y3b#=+=dX6HP+OxYOMa-`9uUBT7QrZ%X?aPVNiM zB1%Tes`aS=zq_H$^iP%iJW&eL#@^Isc-KlRZx@;Y0r>&2HsXm6X-dnBZY4; z=Yw{ebO&9gsYq?+;m2^}nsks7GmwVI9dW^Jsf?vl%|u$gc~K@54S)C@KqS$+=^}P~0sWMLSMM`e{efI#}^k_L1q zp;F#4RqlD;WF!4$Rm(MYYqU%@0p|We{+zm-i3^63?#D3aJ9!bM=O8`!d*Wi)6Bza} zwK5Coz;kUkq-KX*qr_aKTZTUkfTA&Ma+Xg1O}&x$HRvN?PdB^DyJ_R98v?m~DTo!T6v z=ey3tz}n&P?G!Z^DKADN>Z*It46x9{o8Hi!@y|=@tYgDJKzCBT1~fUqNqH-ltA$kaq2> z=3(L&N(Dukkdz6fC!yeu@?BpXQeBTG9Ykf!DT*>9&1`L757UZc#!@YdC(1l9ANch# ztuwt>)rxdB-6sP4FUIuIj670oe`hB=3sl{pi&Kkq{Bep3e$=bx(V^BMtvMb#29n&W z7P^G>NXnl!uYz-jRCbE8Azj-8KSS0d)ifeI(#VFEBd}(K+D?fAQo;M5eFy(5Q`b>c z1CqZXZvluK)e|Y|1EkpG&RppDTJ5I3M5Iyfmseookor1BIgkvIN2Y)zC3Y8)6KO@} z%5>Y L+n^}Xl#Kj8In9~M delta 2370 zcmXZcdsvKV6aesMnrf=KG&M~#t(29NG^ERPq0o)ox=<;k3ze9%>GHU(EDEDkkBHnU zy0B}k6V;+@+lHP{+T9{F?0ST3?UqgKnK{j0^Zw?X^Pcz2`+dDVNzc2J%$Fo2glaLh z#y*$bGxgM8Ro}(#Eij+xrd~5}Z4u`3JtKQbYI@qzn1nP7l=VrmDY&$9lsZkd8e6Q9 z$5~C6&X77X3lhSqveu4Tbv-RDbtUySqI*FV%0y;}J4Y?rDCaY^yOd3yEhH?IzAaOv z%F=X|OcSGn;w)QpL8+)#x@trh#e2JKj&gzOrkh68P3LX-HWEY@jA9}-l3+(L3u=}z zl07w1`bec^>{^&s!07SPkO9)?Atxc2w=piw)Q}<4z8mafXy-7~JT$~XYVc7!g3th_ z0}&U=c%g{w6;6p_q)O=@b+CVw)k!-_kS=Z>tp>TP z&aXt~NYT2@JHaPK$Bt@!g%o}wLLag&>iARCI3&OB)^2EduG31#R^OS9>fm8GC((7H z#PLY`@5?qqZ>X+2C0Zgqtl54IY!$lC>5NuLKBqF*f@zcPB#N4VbfKZ>H28nk%_I66 z$(sGe2A;XLi~S4VwKLW+nL-h)ri^@iyJ*dXcOCUt9-$UepByN1#2eZobJ&B}9Pv7S{!LBFzJLo}} zhNPH$Dj$r51_z0zBZ<83ZicErgC-(Jq>-x+{b75yK{nNzfz+0KpcM?S8!(8R)U}4= zlpyXm@TN05BUOl1(;?2*P(V>GNZ~zYfv_Xm(1!j-SERW16Q*#b#PBHHB^eTTwV)Lu zIt+&>(GAJ^b^j~Xs_UG)ORy~l8IYI|SZsR0Vlqb@-f){V$!8ML0 z?Kl%DAc@%qpZYn4E}A<&3yFWnF$Ip=a2HVbUP!(rNh{#PDsH)qtA5>hBYphh)*o}Y zn9H$+`^DS=>TWjvusCYvdbrohT}bJ3kXrQ%ZNXZPw~Jbti)0gN?*Uh5^Bz&657MWM zzx|*ni#N?#(~2)rXk?}*v{vyFDaubB<(anz=BjwTOG=!N)T7ui3edv$rKtrXIsAE833?2p5{g=YbnnLNR;Y3} zs-Y$pBJrA*w?pbiqeO~Ygmh}?WHhKQ8mVZ<#YhFh(|k}C+Hd4(V?m-dO_&251%Q7F=-{dURVIU?w!9hV|` z%fS$iHgp+9Le&-xw-IWrAa-Nk3^@s^w*eH~kUk>=gtw$z9RDA`~p@qA0 zp^+=JqNtTfhVjZsct1}#Hk(yQ>(8_Wf^)X;2qi`%*>~CU;kO&YVWQPY^TVBspnFhw zk|+l0>hD)M*@2<-5&&Pj5qOhPI@ z@AV8e>6tyIi?a@CWyr={2=g{uL_fd7r@`O>u^mNiM6xyzyaT&C z;tcBR8zgg8;47#c5qD5j8dA#74;n!+LvoyG6VgkDGy`0cBsoOsNCR3A%AonAL`*wo qART4-d<%V@l1eI{iFE9-&OtcKH&@Nk%qt5iJM2jc6a||HjsG9iRSKQ} 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 d3cd17addac98048318434d7627af3f899478677..046137e79498838323c5c38ac7aabb7386553974 100644 GIT binary patch delta 2853 zcmXZdc~nz(76)+lkc6GE3iwb_P)b=t06_t9p+E!zVzt^BUP2%wn3sSIB6_5_9Z+Ua zR2&_PR;wHo(Oz1p)~yu;w+dKWR2&9dXFA8R#|6icxyhA3Abj5U-rs${_kJhseI<

> z85b529&X=gTvjs$RklQyk(QWiEG< z#FnyQ0ztCD^pP>>%9t8ljy_kg`cS4)XJiy|EyJ;R86y=sy{0gfk!uSXxq+!D!e4qd zS27QKFe<&7^@<|30S7SZ!iw(+0zc!?H7O!oLGm9-s*B{zvoxGFoVzbzx z)QJ{~J7WG9$1s|_%fd|hm8>qblQuc%B<*+WqYLLkEp7})5?l&*|d8@U%iw=JkL6dQm#nDJAy)>DaksHZrTlLb4XAQ{8VB6 zejnVC6lE)epuEF+9t~l7O`Rid=fK4$*2P2~NIso4w;?vbW{AiWY5QPY9{A+hxY7__ zNSc#FwIHsysiz^lkzT}>1i;NJHqDgsK{^uLas~ANv*FWRzDT~Ay4A2nY#U4|)7fe? z<~KpN*7iQF=!X&Dw{GHupqUlI&%xEVB z$MU|U6=xug1-LwdkuqL2Q4rG0n6U$(Ji&WQG!yBLbbAYE$9O?B*DR!Y=id&(p{e$n zlnOTW;tu`+uM6xOX$wP;_O|#vg5A69qbN1o)a%6SLAcv%e}iZalAho80@|(l)>IgZ zlseS+JA50@ucXumNTTMgb0EdQ7tw8pA^rAAvjWz%^IuZohe)5DT6YQx$N7##;YduA z_B>Sk3#7CY5l92K_;GMAPmn;VNTih+LxRgg|AMk7t%BHji~ zlY$mX#UKfH96tdG65$~tG19=MTu&G;6aGr=W0A!2e+J1~>F53ZA+T+SgPzXzTqOSNu5sX9bV#AX zM5GYiga+~^94u-2B&5;tfpJ(9<(N*Xc}NQX!6VRB;wYz4=Ocwh&e{z@t&WF?l9BXr zd#}T-`;PylAySaK{cZNZ4G{dUY?fR=F&u zCzOS>cGh+Qyy|e79cey%Ql#th-?i|;n9GM$xD=^v*Sl>{2r||3KawJQ;+LiO39EK`&HNA>*ubmM0rS>W9d~;6zry@_W4K` zRNL3X<5g~hQRcapA=qbprUIm3x8o{!_Pv{dQp=G#2DkV@*&8>0w9xd0 z^D)xO(>+id=6;$&D^OBw{NrK%8uyz-E0ON}?QR=P+2dYC8}SK}TKaGTKELLkK?|-z zs@4nd!JD`4f1$$FNZqR)8em_zsE<;gB5nG&RslLz)I%gkYH3J*2(kM`tBKYieQhns zg3oV=z9Lc}t@}~HK%uoq0JSefl14cH9U88&hBIwq>VV;$K0WlJ{t3yhX5lCt`rd~}hp`IjyA_-ToO$Q- zzWsWnOvA+m_!a6~ONG@)lcTdk!K>7FfM^5K)%-pGf@iJ1T3WFN>FCPK{!sk0?-l<4 E0cw_=p8x;= delta 2495 zcmXZdcT`h%6bEoJ7#WQ05D-B|5d?t*2_(vJu?mfdC{7fNMFkCt3W~M0C`Uze6cH&x ztJF%VgNWBtb1d$GAc9(}V%?}vk5($yI(n09{>uBD@4dhKe((LxGq%m&e{sGdRj*I8 zVcLB91^7hyS^n191aplFZ?)x`f!AZ!#04|fq%K-oFfmJCFbHMAm$UQn(lazzIn49Z zMNZxv&ye7-;83P`RbUZpN);BlVzEznglmf0`t6@ZxI&Ci6ftz4&1bKGvt~v<5f5oxWl$o-_A|<P)53SeF% zazqMEE)#$`i&;U1PDr|>?58ljis?t>jI`^0U>j`iU^3~JU69tcWCTKu11p760;Gbt z+UGD3&5ER(79zdBTiprT6|7JDAVNCS>Uk6XXktav5Mrct{Q6q(dCJNqk|32{@y~&s zgW1=Kq)0_y2XSHLB({KtkRfS1YfnK{IeQZgAxCPt<0OEvv+PPrxgxRjb;WS)1Div0 zDUg2bUY!KbB5eIB<%V?hn~ZzlvB>r&t>}(anBQd&?7g<7M3#5v!(y9xu)W(hmU?+0 zX|mmK!UL(@8;$j$4MwUvUiTb&CfKD!S;-S=lwr9UVmH|diM)^+a<-m?ndj_uG{g|3 zX?tYX!Tizg6p=U5L0v^4Y*5?pqZNISih|09LX5$F6_GEJnw_;CF8pl&j>vKXQtt@e z;mRX>Uz+PPq=FVL3z%LGpHs>oDcwCu3R5#3jI@PAkv?p5^@TS^2PLHfkhpWM)pTVNu~Qq`=(^gTPbESwX2Fq}LH0KSTXej+kyc7|GP$I0?=i;Jl{7 z;YjsMZoh(aJsd}(5Tu|fV}0SS2RD^=A{417;Z-nbGPoK_g(2zH-uvO}a;}I{N~GYb z@20``XSs>Aq6(=bUNWRI$#83VF}N*s%%V|a zkVY=PT>|U(IMx!yBKbr-I1I|$j=eNQ9FoWO_HYQ0I*F;*XrxeH`2mjz=opwWZpq)rW;-5I)4V^cfPf%eJ(ym_bc~C2GnM4!4Zx1MP9aRZ2}ns_30~2;BPxyNS|~ zwr$)o3tEQ?^QiqKq`Y)(JB*zryr#6ym4TGJO`w&{P!PiVIz^dyeQb(KIC0%6K)DREmmeBc>xg z`A(h=wiiXowBQV+SMQRlVEPBqRw|r{G5q$9@KI_R7$HWOtbZ76vD z5_VRLXA;duatLV}0NW1n5u!Or#~y4~!#TFZgW6{!g>3b&f|n5zKFyVbwC7Tz892F; zTuRMFTE;Jx!Ri``fl_*;aWB0u!u!h-ccOVn{g1kT0biz6PlaD1)d^Q7!;Wz2dP?P5 z=)QL}44WrCNi-j+$o0@Pc)L>?NrelLv>&^BA-Y{Ufl>=CsUNrOZmyAaQYa54y_hkkRjj4DRY)#>AIXHGJVhH(3DV-B@^_F{ugIeXS0ibA MtEA9&TXCB6KM4cF82|tP 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 3a4f9ed78e9be02b4c5f587547ea9b0aa5ba3dc6..e9197bf0b39d3eb6ab5a51312447d43a10861796 100644 GIT binary patch delta 3024 zcmXZc2~<;O764#KSQ6H-LjZY-3!oq%BC-hrjwpgc1Q$>r;bnm=kVTAuxLd0&cnYQ3 zL2E&))@mu%i)gX6Sak-g_Grh>IO-stB09D%BPwdg$ByTqXpmfyDcThF(1=9|rFd6h=Jgq7=E(Q??;vgrv? zu`{D$qN5#l8H?&;Q6<@uoUD`#tzNBFY2`{Ot72xe27`)Y)-e(V#}u+sqfyOdum)Cd z&}l0boTXNJz9hRqk|HrmE0{uW3*^jRydXuwD)pR_S*X*Nb5cV&c9NM@kddFgP?DXO zo06qcnfll7)-y7tPH$ig^$JeSML9X8YZ$GnjFIV-oTe;_$!1tpHEY5!tx;3Ps7-oK zC1aEteWg;zl`*9z#(;e(%Q%+N${3cBYSoo0&cLZm7$ntd^jwvZ(-@Sv6RCpLu~Gx4 z!{S;s7SzeL1_P&I3`#YpuVEEaPBURuDy*Z`SR(L`#Yx~Y)@$r&MeBCUlrkBo!woVON*oWGbxn=7 zI?8%BY}TK~`+*lm}A0PfQ=2+|1__c_M{Q^s0pOJ^Z~?-V15oqv%th9p>+( zT9)(wVxaT@IL6p@5cwd9YQNbJz2$cPv`1g0!dIJkFnGxBDAn>q>X~q+13vqYU0brX zdw-VpkveXrTA?V#LxF z&Xy-566xSiMX>U=P)=<{p~RvWD5w7h>+X9hFjjA zBXtIT8x9@q&I43F9toNRy|Dj|^G%`|NKrn+3fLlcDW#_>0jcb~JAYhw}p0embhjNb_3aq|lk=`j`?^kW7lc zRq$24>kOh)B;m&2uEL*PuDfYNvycSw+q|LqiR+IPm4>wIdjC}@o9q@x<b?lcIH6)63~RiOQ{ob&%)Qs&hi{H}{Z5HXk>uNE-GT!HUY}6nGNguOxz&&z;w_+; z<8mbP!pcr4De)ddQN>7!x!?%fTD%L1N|5+l^?!v2*SwQyUMrAFjt<8`jL@f!qE;e3 zzot+@cZSb<)YmGcJ)Z<6LTJANRBj4mdb}A zk>4Y#r9ko(_l$@BY`=VJLW#uhR@g(@X1{|JRgSd${`ht9^o(CFMO7f>_qnD)&amI; zqpdq!rajZXIV!phx51PKg?%V{`wP0Gh-8J~VqR(ok(5xvF{uK#JngTAm72tJ?M<4AqNVm)1 zF9g5m;wsvZ32DLP4mmVM1@0zVi!?p9s}g1^18-6jHAvqE&Uy~PZGm^G`&y(!``*0< z;jO^YLDwOebiZLhII`E?RLt(yOl`N`U<# g=nqIC z#6rab!2=bSu>xhK%9f(Ft+u5u9<3Ft@~DN?rH=iFjZ)e^%Ix0u+&t6}!$J9abM^{o@nLud z8;FY(wkO09Zf<1E_#23)j|z=Ug8RoAiVy?wk!lL;5`q7SA*MtDl0!#8COA1WlPJ*~ z=~l+X7;H~xa_K}N(ob%4lc2ek+2v=bB|@rj-m@K&Mwtg`J~2{A&1VC^vSN8sEej;^ zFh>BP%UG@yWr@<)){!PyYd#IlM@Unum1j3xN&v9O0z~2=q6@9m%%R57c*gT58J! z<^7W%zJYorKauwQH>7s?iIp%J&j+gRi6qTA{tf(d2j7Jfy^zG^xhi;gnLkJedL!8{ zmBazO;ol*ek91>|!%JxM6}S@lAZb<=&W6!E!A_0g!(M=NERpvZ@{bCBr}7#kw;e6N zK>DwOQ6gWYo|e#Aut;T|LAS~e36}S_!r5f=owQVcq}3_4g|K9w`F$EF0O=)@Hv)mz z%!4T^5XtJ;*eTe`7yd@CY7o+#h4)v&2hqYOlo*UOf1Yy;=qiQ2L?KAI(SeJg>w>U} z1`0(|gg^KXBuojvqNp&Wg~JKi;OH*$q4MEKQ_tqiz`87vD>V^;G-BcW8k!D^{D>AJ zox1jkFWkB#;?dZPkcuQGb}*?B_fb?N(%(B9I^aU0_zh7M(*B29iyWdQNFy5ADmYtg5l*xeN!m8VhMBV# z=V5uvii=ijqs*4g=A@$u8 zorH}itR7HeGE&KwZR4)8j#u1kYxUu%&nu4k0PGrp{hqSBE1UhMUN;z(Hy^_7nFrru2f!b+Kh z`g#{hEZqGTgJRls$(t|eNJ^t-OT1llsKjo$#7 zNQ+$lU;(SudXUP$htwCocsYDOYJG)Bi*!4(@HKQuY$mAI`$!h+^M!D5sm)4iA`2gs;wEH}y;Hinl5c^ObLnr1Vo!~znfi?%lURvQsBybnaf{0Yb zFhzZUqz*`r0-FZKRXS1syG2~cxdP9wDNYd;ARUZyI}M#YWfj#bL`qb%UVwk3(s=nc zBi)XjVnct0a+;z(L~1EIIR+PdmBzbWg!F3S-gEfrxpE^7RE#9pHR%NLp0-UyB}g5| zTld0rw(Tf2QHpfqK$R1W9k#to-ETpfSWy)LU*EDdF1ien^S>QTibLWQfgG^>BNtaZl4jRfc;mhr*vW^Qh8$a2-v<>)e(J+6x`MO40<$n HBIf@A5#2cl 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 2c283a9d17a22f6662cdf3c593dd6f3905fe8bec..5b697b3d52c5e705f73b3a5597ff0ec12ef5b84e 100644 GIT binary patch delta 4352 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 O_Vy>R+wYmrrvD!y!HHe~ delta 2077 zcmXZcc~F#f7zc3nU=MbeW!Xi9NVyS(MeaN9MHE>M;}TFVfkpu_tkFBs-jn(G{r?ZFr1JMm+cw#o|-Wp5{GLjg7sO9 z8cOkzDtj_YL2`o8>#9lVAQh}-S;Ig53?pX^36So@E~*2Wfcc$^hJ;AnJyG|d*N^!X z4T_NbZqL-imO|zL41MTKq(_6&;N!-;NlDR zH$=ur3RU#CkZYseMf4uhwS8J=!8=(ykGf2dRI@j&puIxdj8f`%=H)7bA(gzG-Qc%mYH4)jltYjA}b^(&W0zDT*Tc-Z;myR?x0~5 z4779eson-@EWd#PvCp}_G`I+pw4yB%x2r$}?Mb|Clv<4Br8iLq{)c%( zlv;u`9k{s)n%A=h1C>kvPIQjSQ{;t>TrFw*g+134kp8V$yRO{7i?)jK0a7jk8= z`*WQw)a8QIRnrmx!izf2)a8oQJjF7F6|Z%CXwVI*=tNT#99k?;P|6)?;?HBPFq$f` zau=w7>K;haQ9m%K5y&aziE=iP$?#EAT969GtDOI6u0xliOogMmn+y(2tu{Yj)vD+VEDhW=a*iZ`MFS}_>OY+qdo z9FprAQz`_hso2{aI?{FLu3mw3t;R9%WUVe`LJ>ag>^~2}ZrwtnWk?O|%Y*O*TYQ}; z4C%2DLLkjcEO*sh>2jn2_zRBaiB%L@fs!fj`2-Rh#im5zNH=qhCt>%1IGzr;5-In2 z*GYIL(F>>FVgwTBqo?N~P@(5RDJ9ZpJth{gyGU>DMT$f^c>Coe7;DpOpfg7y8LxO= z4Ygx>IdoakNF%5796)R-X``+fB%fbS*g^MdiGl`KA@Qm=900Q~CA%o~A=3T(ew*Rk zb%`x?#Uhokn!Mo=LqCux4(VWMRVui6>9ojsQP*0eyB^aK;ASZ^AxcFutT1$j#3#&o$Rf(Q2kam_b;A_#BE!06v7=0zNY$hNcK-63*b$r tfr{=c3#szl^j?5^g9+MwHq!XhGdn?b+hB-NIY_0-(35anJn!oK{{bI3u*U!Z 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, -};