Implement audio fading in libretro builds

This commit is contained in:
刘皓 2025-04-07 14:33:50 -04:00
parent 9f3329e1cb
commit 8fa20c660a
No known key found for this signature in database
GPG key ID: 7901753DB465B711
6 changed files with 335 additions and 180 deletions

View file

@ -27,10 +27,6 @@
namespace mkxp_sandbox {
SANDBOX_COROUTINE(audio_binding_init,
static VALUE todo(int32_t argc, wasm_ptr_t argv, VALUE self) {
return SANDBOX_NIL;
}
static VALUE bgm_play(int32_t argc, wasm_ptr_t argv, VALUE self) {
SANDBOX_COROUTINE(coro,
wasm_ptr_t filename;
@ -81,6 +77,28 @@ namespace mkxp_sandbox {
return SANDBOX_NIL;
}
static VALUE bgm_fade(int32_t argc, wasm_ptr_t argv, VALUE self) {
SANDBOX_COROUTINE(coro,
int32_t time;
int32_t track;
VALUE operator()(int32_t argc, wasm_ptr_t argv, VALUE self) {
BOOST_ASIO_CORO_REENTER (this) {
track = -127;
SANDBOX_AWAIT_AND_SET(time, rb_num2int, ((VALUE *)(**sb() + argv))[0]);
if (argc >= 2) {
SANDBOX_AWAIT_AND_SET(track, rb_num2int, ((VALUE *)(**sb() + argv))[1]);
}
mkxp_retro::audio->bgmFade(time, track);
}
return SANDBOX_NIL;
}
)
return sb()->bind<struct coro>()()(argc, argv, self);
}
static VALUE bgm_pos(int32_t argc, wasm_ptr_t argv, VALUE self) {
SANDBOX_COROUTINE(coro,
int32_t track;
@ -188,6 +206,23 @@ namespace mkxp_sandbox {
return SANDBOX_NIL;
}
static VALUE bgs_fade(VALUE self, VALUE value) {
SANDBOX_COROUTINE(coro,
int32_t time;
VALUE operator()(VALUE self, VALUE value) {
BOOST_ASIO_CORO_REENTER (this) {
SANDBOX_AWAIT_AND_SET(time, rb_num2int, value);
mkxp_retro::audio->bgsFade(time);
}
return SANDBOX_NIL;
}
)
return sb()->bind<struct coro>()()(self, value);
}
static VALUE bgs_pos(VALUE self) {
SANDBOX_COROUTINE(coro,
double pos;
@ -240,6 +275,23 @@ namespace mkxp_sandbox {
return SANDBOX_NIL;
}
static VALUE me_fade(VALUE self, VALUE value) {
SANDBOX_COROUTINE(coro,
int32_t time;
VALUE operator()(VALUE self, VALUE value) {
BOOST_ASIO_CORO_REENTER (this) {
SANDBOX_AWAIT_AND_SET(time, rb_num2int, value);
mkxp_retro::audio->meFade(time);
}
return SANDBOX_NIL;
}
)
return sb()->bind<struct coro>()()(self, value);
}
static VALUE se_play(int32_t argc, wasm_ptr_t argv, VALUE self) {
SANDBOX_COROUTINE(coro,
wasm_ptr_t filename;
@ -281,17 +333,17 @@ namespace mkxp_sandbox {
SANDBOX_AWAIT_AND_SET(module, rb_define_module, "Audio");
SANDBOX_AWAIT(rb_define_module_function, module, "bgm_play", (VALUE (*)(ANYARGS))bgm_play, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgm_stop", (VALUE (*)(ANYARGS))bgm_stop, 0);
SANDBOX_AWAIT(rb_define_module_function, module, "bgm_fade", (VALUE (*)(ANYARGS))todo, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgm_fade", (VALUE (*)(ANYARGS))bgm_fade, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgm_pos", (VALUE (*)(ANYARGS))bgm_pos, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgm_volume", (VALUE (*)(ANYARGS))bgm_volume, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgm_set_volume", (VALUE (*)(ANYARGS))bgm_set_volume, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgs_play", (VALUE (*)(ANYARGS))bgs_play, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgs_stop", (VALUE (*)(ANYARGS))bgs_stop, 0);
SANDBOX_AWAIT(rb_define_module_function, module, "bgs_fade", (VALUE (*)(ANYARGS))todo, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgs_fade", (VALUE (*)(ANYARGS))bgs_fade, 1);
SANDBOX_AWAIT(rb_define_module_function, module, "bgs_pos", (VALUE (*)(ANYARGS))bgs_pos, 0);
SANDBOX_AWAIT(rb_define_module_function, module, "me_play", (VALUE (*)(ANYARGS))me_play, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "me_stop", (VALUE (*)(ANYARGS))me_stop, 0);
SANDBOX_AWAIT(rb_define_module_function, module, "me_fade", (VALUE (*)(ANYARGS))todo, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "me_fade", (VALUE (*)(ANYARGS))me_fade, 1);
SANDBOX_AWAIT(rb_define_module_function, module, "se_play", (VALUE (*)(ANYARGS))se_play, -1);
SANDBOX_AWAIT(rb_define_module_function, module, "se_stop", (VALUE (*)(ANYARGS))se_stop, 0);
}

View file

@ -42,6 +42,8 @@ namespace mkxp_retro {
extern retro_input_state_t input_state;
extern struct retro_perf_callback perf;
extern struct retro_hw_render_callback hw_render;
uint64_t get_ticks() noexcept;
}
#endif // MKXPZ_CORE_H

View file

@ -65,14 +65,14 @@ struct AudioPrivate
BgmFadingIn
};
#ifndef MKXPZ_RETRO
struct
{
#ifndef MKXPZ_RETRO
SDL_Thread *thread;
#endif // MKXPZ_RETRO
AtomicFlag termReq;
MeWatchState state;
} meWatch;
#endif // MKXPZ_RETRO
#ifdef MKXPZ_RETRO
AudioPrivate()
@ -96,10 +96,10 @@ struct AudioPrivate
bgmTracks.push_back(new AudioStream(ALStream::Looped, id.c_str()));
}
#ifndef MKXPZ_RETRO
meWatch.state = MeNotPlaying;
#ifndef MKXPZ_RETRO
meWatch.thread = createSDLThread
<AudioPrivate, &AudioPrivate::meWatchFun>(this, "audio_mewatch");
<AudioPrivate, &AudioPrivate::meWatchThread>(this, "audio_mewatch");
#endif // MKXPZ_RETRO
}
@ -121,21 +121,18 @@ struct AudioPrivate
return bgmTracks[index];
}
#ifndef MKXPZ_RETRO
void meWatchFun()
void meWatchProc()
{
#ifdef MKXPZ_RETRO // TODO: use FPS
const float fadeOutStep = 1.f / (200 / 17);
const float fadeInStep = 1.f / (1000 / 17);
#else
const float fadeOutStep = 1.f / (200 / AUDIO_SLEEP);
const float fadeInStep = 1.f / (1000 / AUDIO_SLEEP);
#endif // MKXPZ_RETRO
while (true)
switch (meWatch.state)
{
syncPoint.passSecondarySync();
if (meWatch.termReq)
return;
switch (meWatch.state)
{
case MeNotPlaying:
{
me.lockStream();
@ -143,9 +140,9 @@ struct AudioPrivate
if (me.stream.queryState() == ALStream::Playing)
{
/* ME playing detected. -> FadeOutBGM */
for (auto track : bgmTracks)
track->extPaused = true;
for (auto track : bgmTracks)
track->extPaused = true;
meWatch.state = BgmFadingOut;
}
@ -166,45 +163,45 @@ struct AudioPrivate
break;
}
bool shouldBreak = false;
for (int i = 0; i < (int)(bgmTracks.size()); i++) {
AudioStream *track = bgmTracks[i];
track->lockStream();
float vol = track->getVolume(AudioStream::External);
vol -= fadeOutStep;
if (vol < 0 || track->stream.queryState() != ALStream::Playing) {
/* Either BGM has fully faded out, or stopped midway. -> MePlaying */
track->setVolume(AudioStream::External, 0);
track->stream.pause();
track->unlockStream();
// check to see if there are any tracks still playing,
// and if the last one was ended this round, this branch should exit
std::vector<AudioStream*> playingTracks;
for (auto t : bgmTracks)
if (t->stream.queryState() == ALStream::Playing)
playingTracks.push_back(t);
if (playingTracks.size() <= 0 && !shouldBreak) shouldBreak = true;
continue;
}
track->setVolume(AudioStream::External, vol);
track->unlockStream();
}
if (shouldBreak) {
meWatch.state = MePlaying;
me.unlockStream();
break;
}
bool shouldBreak = false;
for (int i = 0; i < (int)(bgmTracks.size()); i++) {
AudioStream *track = bgmTracks[i];
track->lockStream();
float vol = track->getVolume(AudioStream::External);
vol -= fadeOutStep;
if (vol < 0 || track->stream.queryState() != ALStream::Playing) {
/* Either BGM has fully faded out, or stopped midway. -> MePlaying */
track->setVolume(AudioStream::External, 0);
track->stream.pause();
track->unlockStream();
// check to see if there are any tracks still playing,
// and if the last one was ended this round, this branch should exit
std::vector<AudioStream*> playingTracks;
for (auto t : bgmTracks)
if (t->stream.queryState() == ALStream::Playing)
playingTracks.push_back(t);
if (playingTracks.size() <= 0 && !shouldBreak) shouldBreak = true;
continue;
}
track->setVolume(AudioStream::External, vol);
track->unlockStream();
}
if (shouldBreak) {
meWatch.state = MePlaying;
me.unlockStream();
break;
}
me.unlockStream();
break;
@ -215,51 +212,51 @@ struct AudioPrivate
me.lockStream();
if (me.stream.queryState() != ALStream::Playing)
{
/* ME has ended */
for (auto track : bgmTracks) {
track->lockStream();
track->extPaused = false;
ALStream::State sState = track->stream.queryState();
if (sState == ALStream::Paused) {
/* BGM is paused. -> FadeInBGM */
track->stream.play();
meWatch.state = BgmFadingIn;
}
else {
/* BGM is stopped. -> MeNotPlaying */
track->setVolume(AudioStream::External, 1.0f);
if (!track->noResumeStop)
track->stream.play();
meWatch.state = MeNotPlaying;
}
track->unlockStream();
}
{
/* ME has ended */
for (auto track : bgmTracks) {
track->lockStream();
track->extPaused = false;
ALStream::State sState = track->stream.queryState();
if (sState == ALStream::Paused) {
/* BGM is paused. -> FadeInBGM */
track->stream.play();
meWatch.state = BgmFadingIn;
}
else {
/* BGM is stopped. -> MeNotPlaying */
track->setVolume(AudioStream::External, 1.0f);
if (!track->noResumeStop)
track->stream.play();
meWatch.state = MeNotPlaying;
}
track->unlockStream();
}
}
me.unlockStream();
me.unlockStream();
break;
}
case BgmFadingIn :
{
for (auto track : bgmTracks)
track->lockStream();
for (auto track : bgmTracks)
track->lockStream();
if (bgmTracks[0]->stream.queryState() == ALStream::Stopped)
{
/* BGM stopped midway fade in. -> MeNotPlaying */
for (auto track : bgmTracks)
track->setVolume(AudioStream::External, 1.0f);
for (auto track : bgmTracks)
track->setVolume(AudioStream::External, 1.0f);
meWatch.state = MeNotPlaying;
for (auto track : bgmTracks)
track->unlockStream();
for (auto track : bgmTracks)
track->unlockStream();
break;
}
@ -269,12 +266,12 @@ struct AudioPrivate
if (me.stream.queryState() == ALStream::Playing)
{
/* ME started playing midway BGM fade in. -> FadeOutBGM */
for (auto track : bgmTracks)
track->extPaused = true;
for (auto track : bgmTracks)
track->extPaused = true;
meWatch.state = BgmFadingOut;
me.unlockStream();
for (auto track : bgmTracks)
track->unlockStream();
for (auto track : bgmTracks)
track->unlockStream();
break;
}
@ -289,16 +286,29 @@ struct AudioPrivate
meWatch.state = MeNotPlaying;
}
for (auto track : bgmTracks)
track->setVolume(AudioStream::External, vol);
for (auto track : bgmTracks)
track->setVolume(AudioStream::External, vol);
me.unlockStream();
for (auto track : bgmTracks)
track->unlockStream();
for (auto track : bgmTracks)
track->unlockStream();
break;
}
}
}
}
#ifndef MKXPZ_RETRO
void meWatchThread()
{
while (true)
{
syncPoint.passSecondarySync();
if (meWatch.termReq)
return;
meWatchProc();
SDL_Delay(AUDIO_SLEEP);
}
@ -318,13 +328,14 @@ Audio::Audio(RGSSThreadData &rtData)
#ifdef MKXPZ_RETRO
void Audio::render() {
p->meWatchProc();
for (int i = 0; i < (int)p->bgmTracks.size(); i++) {
p->bgmTracks[i]->stream.render();
p->bgmTracks[i]->render();
}
p->bgs.stream.render();
p->me.stream.render();
p->bgs.render();
p->me.render();
}
#endif
#endif // MKXPZ_RETRO
void Audio::bgmPlay(const char *filename,
int volume,
@ -464,9 +475,7 @@ void Audio::reset()
p->bgs.stop();
p->me.stop();
#ifndef MKXPZ_RETRO
p->se.stop();
#endif // MKXPZ_RETRO
}
Audio::~Audio() { delete p; }

View file

@ -24,7 +24,9 @@
#include "util.h"
#include "exception.h"
#ifndef MKXPZ_RETRO
#ifdef MKXPZ_RETRO
# include "core.h"
#else
# include <SDL_mutex.h>
# include <SDL_thread.h>
# include <SDL_timer.h>
@ -42,7 +44,10 @@ AudioStream::AudioStream(ALStream::LoopMode loopMode,
for (size_t i = 0; i < VolumeTypeCount; ++i)
volumes[i] = 1.0f;
#ifndef MKXPZ_RETRO
#ifdef MKXPZ_RETRO
fade.enabled = false;
fadeIn.enabled = false;
#else
fade.thread = 0;
fade.threadName = std::string("audio_fadeout (") + threadId + ")";
@ -184,14 +189,12 @@ void AudioStream::fadeOut(int duration)
ALStream::State sState = stream.queryState();
noResumeStop = true;
#ifndef MKXPZ_RETRO
if (fade.active)
{
unlockStream();
return;
}
#endif // MKXPZ_RETRO
if (sState == ALStream::Paused)
{
@ -208,20 +211,34 @@ void AudioStream::fadeOut(int duration)
return;
}
#ifndef MKXPZ_RETRO
#ifdef MKXPZ_RETRO
if (fade.enabled)
#else
if (fade.thread)
#endif // MKXPZ_RETRO
{
fade.reqFini.set();
fadeOutProc();
fade.enabled = false;
#ifndef MKXPZ_RETRO
SDL_WaitThread(fade.thread, 0);
fade.thread = 0;
#endif // MKXPZ_RETRO
}
fade.active.set();
fade.msStep = 1.0f / duration;
fade.reqFini.clear();
fade.reqTerm.clear();
#ifdef MKXPZ_RETRO
fade.startTicks = mkxp_retro::get_ticks();
#else
fade.startTicks = SDL_GetTicks64();
#endif // MKXPZ_RETRO
#ifdef MKXPZ_RETRO
fade.enabled = true;
#else
fade.thread = createSDLThread
<AudioStream, &AudioStream::fadeOutThread>(this, fade.threadName);
#endif // MKXPZ_RETRO
@ -281,107 +298,158 @@ void AudioStream::updateVolume()
void AudioStream::finiFadeOutInt()
{
#ifndef MKXPZ_RETRO
#ifdef MKXPZ_RETRO
if (fade.enabled)
#else
if (fade.thread)
#endif // MKXPZ_RETRO
{
fade.reqFini.set();
fadeOutProc();
fade.enabled = false;
#ifndef MKXPZ_RETRO
SDL_WaitThread(fade.thread, 0);
fade.thread = 0;
#endif // MKXPZ_RETRO
}
#ifdef MKXPZ_RETRO
if (fadeIn.enabled)
#else
if (fadeIn.thread)
#endif // MKXPZ_RETRO
{
fadeIn.rqFini.set();
fadeInProc();
fadeIn.enabled = false;
#ifndef MKXPZ_RETRO
SDL_WaitThread(fadeIn.thread, 0);
fadeIn.thread = 0;
}
#endif // MKXPZ_RETRO
}
}
void AudioStream::startFadeIn()
{
#ifndef MKXPZ_RETRO
/* Previous fadein should always be terminated in play() */
#ifdef MKXPZ_RETRO
assert(!fadeIn.enabled);
#else
assert(!fadeIn.thread);
#endif // MKXPZ_RETRO
fadeIn.rqFini.clear();
fadeIn.rqTerm.clear();
#ifdef MKXPZ_RETRO
fadeIn.startTicks = mkxp_retro::get_ticks();
#else
fadeIn.startTicks = SDL_GetTicks64();
#endif // MKXPZ_RETRO
#ifdef MKXPZ_RETRO
fadeIn.enabled = true;
#else
fadeIn.thread = createSDLThread
<AudioStream, &AudioStream::fadeInThread>(this, fadeIn.threadName);
#endif // MKXPZ_RETRO
}
#ifndef MKXPZ_RETRO
void AudioStream::fadeOutThread()
bool AudioStream::fadeOutProc()
{
while (true)
/* Just immediately terminate on request */
if (fade.reqTerm)
{
/* Just immediately terminate on request */
if (fade.reqTerm)
break;
lockStream();
uint64_t curDur = SDL_GetTicks64() - fade.startTicks;
float resVol = 1.0f - (curDur*fade.msStep);
ALStream::State state = stream.queryState();
if (state != ALStream::Playing
|| resVol < 0
|| fade.reqFini)
{
if (state != ALStream::Paused)
stream.stop();
setVolume(FadeOut, 1.0f);
unlockStream();
break;
}
setVolume(FadeOut, resVol);
unlockStream();
SDL_Delay(AUDIO_SLEEP);
fade.active.clear();
return false;
}
fade.active.clear();
lockStream();
#ifdef MKXPZ_RETRO
uint64_t curDur = mkxp_retro::get_ticks() - fade.startTicks;
#else
uint64_t curDur = SDL_GetTicks64() - fade.startTicks;
#endif // MKXPZ_RETRO
float resVol = 1.0f - (curDur*fade.msStep);
ALStream::State state = stream.queryState();
if (state != ALStream::Playing
|| resVol < 0
|| fade.reqFini)
{
if (state != ALStream::Paused)
stream.stop();
setVolume(FadeOut, 1.0f);
unlockStream();
fade.active.clear();
return false;
}
setVolume(FadeOut, resVol);
unlockStream();
return true;
}
bool AudioStream::fadeInProc()
{
if (fadeIn.rqTerm)
return false;
lockStream();
/* Fade in duration is always 1 second */
#ifdef MKXPZ_RETRO
uint64_t cur = mkxp_retro::get_ticks() - fadeIn.startTicks;
#else
uint64_t cur = SDL_GetTicks64() - fadeIn.startTicks;
#endif // MKXPZ_RETRO
float prog = cur / 1000.0f;
ALStream::State state = stream.queryState();
if (state != ALStream::Playing
|| prog >= 1.0f
|| fadeIn.rqFini)
{
setVolume(FadeIn, 1.0f);
unlockStream();
return false;
}
setVolume(FadeIn, prog);
unlockStream();
return true;
}
#ifdef MKXPZ_RETRO
void AudioStream::render()
{
if (fade.enabled && !fadeOutProc())
fade.enabled = false;
if (fadeIn.enabled && !fadeInProc())
fadeIn.enabled = false;
stream.render();
}
#else
void AudioStream::fadeOutThread()
{
while (fadeOutProc())
SDL_Delay(AUDIO_SLEEP);
}
void AudioStream::fadeInThread()
{
while (true)
{
if (fadeIn.rqTerm)
break;
lockStream();
/* Fade in duration is always 1 second */
uint64_t cur = SDL_GetTicks64() - fadeIn.startTicks;
float prog = cur / 1000.0f;
ALStream::State state = stream.queryState();
if (state != ALStream::Playing
|| prog >= 1.0f
|| fadeIn.rqFini)
{
setVolume(FadeIn, 1.0f);
unlockStream();
break;
}
setVolume(FadeIn, prog);
unlockStream();
while (fadeInProc())
SDL_Delay(AUDIO_SLEEP);
}
}
#endif // MKXPZ_RETRO

View file

@ -84,6 +84,7 @@ struct AudioStream
#ifndef MKXPZ_RETRO
SDL_mutex *streamMut;
#endif // MKXPZ_RETRO
/* Fade out */
struct
@ -99,8 +100,12 @@ struct AudioStream
* immediately */
AtomicFlag reqTerm;
#ifdef MKXPZ_RETRO
bool enabled;
#else
SDL_Thread *thread;
std::string threadName;
#endif // MKXPZ_RETRO
/* Amount of reduced absolute volume
* per ms of fade time */
@ -116,12 +121,15 @@ struct AudioStream
AtomicFlag rqFini;
AtomicFlag rqTerm;
#ifdef MKXPZ_RETRO
bool enabled;
#else
SDL_Thread *thread;
std::string threadName;
#endif // MKXPZ_RETRO
uint64_t startTicks;
} fadeIn;
#endif // MKXPZ_RETRO
AudioStream(ALStream::LoopMode loopMode,
const std::string &threadId);
@ -146,6 +154,10 @@ struct AudioStream
double playingOffset();
#ifdef MKXPZ_RETRO
void render();
#endif // MKXPZ_RETRO
private:
float volumes[VolumeTypeCount];
void updateVolume();
@ -153,6 +165,9 @@ private:
void finiFadeOutInt();
void startFadeIn();
bool fadeOutProc();
bool fadeInProc();
#ifndef MKXPZ_RETRO
void fadeOutThread();
void fadeInThread();

View file

@ -43,6 +43,8 @@
using namespace mkxp_retro;
using namespace mkxp_sandbox;
static uint64_t frame_count;
namespace mkxp_retro {
retro_log_printf_t log_printf;
retro_video_refresh_t video_refresh;
@ -52,6 +54,10 @@ namespace mkxp_retro {
retro_input_state_t input_state;
struct retro_perf_callback perf;
retro_hw_render_callback hw_render;
uint64_t get_ticks() noexcept {
return (frame_count * 1000) / 60;
}
}
static inline void *malloc_align(size_t alignment, size_t size) {
@ -314,6 +320,7 @@ static bool init_sandbox() {
return false;
}
frame_count = 0;
shared_state_initialized = false;
return true;
@ -469,6 +476,8 @@ extern "C" RETRO_API void retro_run() {
alcRenderSamplesSOFT(al_device, sound_buf, 735);
audio_sample_batch(sound_buf, 735);
}
++frame_count;
}
extern "C" RETRO_API size_t retro_serialize_size() {