From 51a0f3903c812b36f482dbaf289042deb430796b Mon Sep 17 00:00:00 2001
From: Jonas Kulla <Nyocurio@gmail.com>
Date: Tue, 30 Sep 2014 09:13:12 +0200
Subject: [PATCH] Audio: Clean up threading and add AudioStream fadein (RGSS3)

---
 CMakeLists.txt      |   1 +
 README.md           |   1 -
 mkxp.pro            |   3 +-
 src/alstream.cpp    |  33 ++++------
 src/alstream.h      |  12 ++--
 src/audio.cpp       |  34 ++++------
 src/audiostream.cpp | 149 +++++++++++++++++++++++++++++++-------------
 src/audiostream.h   |  64 ++++++++++++-------
 src/eventthread.h   |  29 +--------
 src/sdl-util.h      |  48 ++++++++++++++
 10 files changed, 228 insertions(+), 146 deletions(-)
 create mode 100644 src/sdl-util.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 386452d..85e58e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -153,6 +153,7 @@ set(MAIN_HEADERS
 	src/tileatlasvx.h
 	src/sharedmidistate.h
 	src/fluid-fun.h
+	src/sdl-util.h
 )
 
 set(MAIN_SOURCE
diff --git a/README.md b/README.md
index cb3af89..f4ba120 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,6 @@ Missing RGSS3 functionality:
 
 * Text outline
 * Movie playback
-* Audio fade-in
 
 Some other things might be implemented, but simply not bound yet.
 
diff --git a/mkxp.pro b/mkxp.pro
index 37c348c..0adb918 100644
--- a/mkxp.pro
+++ b/mkxp.pro
@@ -131,7 +131,8 @@ HEADERS += \
 	src/tilemapvx.h \
 	src/tileatlasvx.h \
 	src/sharedmidistate.h \
-	src/fluid-fun.h
+	src/fluid-fun.h \
+	src/sdl-util.h
 
 SOURCES += \
 	src/main.cpp \
diff --git a/src/alstream.cpp b/src/alstream.cpp
index 6ab6358..da1e844 100644
--- a/src/alstream.cpp
+++ b/src/alstream.cpp
@@ -26,6 +26,7 @@
 #include "filesystem.h"
 #include "aldatasource.h"
 #include "fluid-fun.h"
+#include "sdl-util.h"
 
 #include <SDL_mutex.h>
 #include <SDL_thread.h>
@@ -38,8 +39,6 @@ ALStream::ALStream(LoopMode loopMode,
 	  source(0),
 	  thread(0),
 	  preemptPause(false),
-	  streamInited(false),
-	  needsRewind(false),
       pitch(1.0)
 {
 	alSrc = AL::Source::gen();
@@ -198,7 +197,7 @@ void ALStream::openSource(const std::string &filename)
 {
 	const char *ext;
 	shState->fileSystem().openRead(srcOps, filename.c_str(), FileSystem::Audio, false, &ext);
-	needsRewind = false;
+	needsRewind.clear();
 
 	/* Try to read ogg file signature */
 	char sig[5] = { 0 };
@@ -227,13 +226,13 @@ void ALStream::openSource(const std::string &filename)
 
 void ALStream::stopStream()
 {
-	threadTermReq = true;
+	threadTermReq.set();
 
 	if (thread)
 	{
 		SDL_WaitThread(thread, 0);
 		thread = 0;
-		needsRewind = true;
+		needsRewind.set();
 	}
 
 	/* Need to stop the source _after_ the thread has terminated,
@@ -249,14 +248,15 @@ void ALStream::startStream(float offset)
 	AL::Source::clearQueue(alSrc);
 
 	preemptPause = false;
-	streamInited = false;
-	sourceExhausted = false;
-	threadTermReq = false;
+	streamInited.clear();
+	sourceExhausted.clear();
+	threadTermReq.clear();
 
 	startOffset = offset;
 	procFrames = offset * source->sampleRate();
 
-	thread = SDL_CreateThread(streamDataFun, threadName.c_str(), this);
+	thread = createSDLThread
+		<ALStream, &ALStream::streamData>(this, threadName);
 }
 
 void ALStream::pauseStream()
@@ -344,7 +344,7 @@ void ALStream::streamData()
 			resumeStream();
 
 			firstBuffer = false;
-			streamInited = true;
+			streamInited.set();
 		}
 
 		if (threadTermReq)
@@ -352,7 +352,7 @@ void ALStream::streamData()
 
 		if (status == ALDataSource::EndOfStream)
 		{
-			sourceExhausted = true;
+			sourceExhausted.set();
 			break;
 		}
 	}
@@ -400,7 +400,7 @@ void ALStream::streamData()
 
 			if (status == ALDataSource::Error)
 			{
-				sourceExhausted = true;
+				sourceExhausted.set();
 				return;
 			}
 
@@ -419,7 +419,7 @@ void ALStream::streamData()
 				lastBuf = buf;
 
 			if (status == ALDataSource::EndOfStream)
-				sourceExhausted = true;
+				sourceExhausted.set();
 		}
 
 		if (threadTermReq)
@@ -428,10 +428,3 @@ void ALStream::streamData()
 		SDL_Delay(AUDIO_SLEEP);
 	}
 }
-
-int ALStream::streamDataFun(void *_self)
-{
-	ALStream &self = *static_cast<ALStream*>(_self);
-	self.streamData();
-	return 0;
-}
diff --git a/src/alstream.h b/src/alstream.h
index 7de277d..e8c1606 100644
--- a/src/alstream.h
+++ b/src/alstream.h
@@ -23,12 +23,11 @@
 #define ALSTREAM_H
 
 #include "al-util.h"
+#include "sdl-util.h"
 
 #include <string>
 #include <SDL_rwops.h>
 
-struct SDL_mutex;
-struct SDL_thread;
 struct ALDataSource;
 
 #define STREAM_BUFS 3
@@ -59,12 +58,12 @@ struct ALStream
 	/* When this flag isn't set and alSrc is
 	 * in 'STOPPED' state, stream isn't over
 	 * (it just hasn't started yet) */
-	bool streamInited;
-	bool sourceExhausted;
+	AtomicFlag streamInited;
+	AtomicFlag sourceExhausted;
 
-	bool threadTermReq;
+	AtomicFlag threadTermReq;
 
-	bool needsRewind;
+	AtomicFlag needsRewind;
 	float startOffset;
 
 	float pitch;
@@ -118,7 +117,6 @@ private:
 
 	/* thread func */
 	void streamData();
-	static int streamDataFun(void *);
 };
 
 #endif // ALSTREAM_H
diff --git a/src/audio.cpp b/src/audio.cpp
index dcc0b90..aa16e7d 100644
--- a/src/audio.cpp
+++ b/src/audio.cpp
@@ -25,6 +25,7 @@
 #include "soundemitter.h"
 #include "sharedstate.h"
 #include "sharedmidistate.h"
+#include "sdl-util.h"
 
 #include <string>
 
@@ -55,8 +56,7 @@ struct AudioPrivate
 	struct
 	{
 		SDL_Thread *thread;
-		bool active;
-		bool termReq;
+		AtomicFlag termReq;
 		MeWatchState state;
 	} meWatch;
 
@@ -66,19 +66,18 @@ struct AudioPrivate
 	      me(ALStream::NotLooped, "me"),
 	      se(conf)
 	{
-		meWatch.active = false;
-		meWatch.termReq = false;
 		meWatch.state = MeNotPlaying;
-		meWatch.thread = SDL_CreateThread(meWatchFun, "audio_mewatch", this);
+		meWatch.thread = createSDLThread
+			<AudioPrivate, &AudioPrivate::meWatchFun>(this, "audio_mewatch");
 	}
 
 	~AudioPrivate()
 	{
-		meWatch.termReq = true;
+		meWatch.termReq.set();
 		SDL_WaitThread(meWatch.thread, 0);
 	}
 
-	void meWatchFunInt()
+	void meWatchFun()
 	{
 		const float fadeOutStep = 1.f / (200  / AUDIO_SLEEP);
 		const float fadeInStep  = 1.f / (1000 / AUDIO_SLEEP);
@@ -121,13 +120,13 @@ struct AudioPrivate
 
 				bgm.lockStream();
 
-				float vol = bgm.extVolume;
+				float vol = bgm.getVolume(AudioStream::External);
 				vol -= fadeOutStep;
 
 				if (vol < 0 || bgm.stream.queryState() != ALStream::Playing)
 				{
 					/* Either BGM has fully faded out, or stopped midway. -> MePlaying */
-					bgm.setExtVolume1(0);
+					bgm.setVolume(AudioStream::External, 0);
 					bgm.stream.pause();
 					meWatch.state = MePlaying;
 					bgm.unlockStream();
@@ -136,7 +135,7 @@ struct AudioPrivate
 					break;
 				}
 
-				bgm.setExtVolume1(vol);
+				bgm.setVolume(AudioStream::External, vol);
 				bgm.unlockStream();
 				me.unlockStream();
 
@@ -165,7 +164,7 @@ struct AudioPrivate
 					else
 					{
 						/* BGM is stopped. -> MeNotPlaying */
-						bgm.setExtVolume1(1.0);
+						bgm.setVolume(AudioStream::External, 1.0);
 
 						if (!bgm.noResumeStop)
 							bgm.stream.play();
@@ -188,7 +187,7 @@ struct AudioPrivate
 				if (bgm.stream.queryState() == ALStream::Stopped)
 				{
 					/* BGM stopped midway fade in. -> MeNotPlaying */
-					bgm.setExtVolume1(1.0);
+					bgm.setVolume(AudioStream::External, 1.0);
 					meWatch.state = MeNotPlaying;
 					bgm.unlockStream();
 
@@ -208,7 +207,7 @@ struct AudioPrivate
 					break;
 				}
 
-				float vol = bgm.extVolume;
+				float vol = bgm.getVolume(AudioStream::External);
 				vol += fadeInStep;
 
 				if (vol >= 1)
@@ -218,7 +217,7 @@ struct AudioPrivate
 					meWatch.state = MeNotPlaying;
 				}
 
-				bgm.setExtVolume1(vol);
+				bgm.setVolume(AudioStream::External, vol);
 
 				me.unlockStream();
 				bgm.unlockStream();
@@ -230,13 +229,6 @@ struct AudioPrivate
 			SDL_Delay(AUDIO_SLEEP);
 		}
 	}
-
-	static int meWatchFun(void *self)
-	{
-		static_cast<AudioPrivate*>(self)->meWatchFunInt();
-
-		return 0;
-	}
 };
 
 Audio::Audio(const Config &conf)
diff --git a/src/audiostream.cpp b/src/audiostream.cpp
index d5975e9..16cade7 100644
--- a/src/audiostream.cpp
+++ b/src/audiostream.cpp
@@ -30,19 +30,21 @@
 
 AudioStream::AudioStream(ALStream::LoopMode loopMode,
                          const std::string &threadId)
-	: baseVolume(1.0),
-	  fadeVolume(1.0),
-	  extVolume(1.0),
-	  extPaused(false),
+	: extPaused(false),
 	  noResumeStop(false),
 	  stream(loopMode, threadId)
 {
 	current.volume = 1.0;
 	current.pitch = 1.0;
 
-	fade.active = false;
+	for (size_t i = 0; i < VolumeTypeCount; ++i)
+		volumes[i] = 1.0;
+
 	fade.thread = 0;
-	fade.threadName = std::string("audio_fade (") + threadId + ")";
+	fade.threadName = std::string("audio_fadeout (") + threadId + ")";
+
+	fadeIn.thread = 0;
+	fadeIn.threadName = std::string("audio_fadein (") + threadId + ")";
 
 	streamMut = SDL_CreateMutex();
 }
@@ -51,10 +53,16 @@ AudioStream::~AudioStream()
 {
 	if (fade.thread)
 	{
-		fade.reqTerm = true;
+		fade.reqTerm.set();
 		SDL_WaitThread(fade.thread, 0);
 	}
 
+	if (fadeIn.thread)
+	{
+		fadeIn.rqTerm.set();
+		SDL_WaitThread(fadeIn.thread, 0);
+	}
+
 	lockStream();
 
 	stream.stop();
@@ -70,7 +78,7 @@ void AudioStream::play(const std::string &filename,
                        int pitch,
                        float offset)
 {
-	finiFadeInt();
+	finiFadeOutInt();
 
 	lockStream();
 
@@ -96,7 +104,7 @@ void AudioStream::play(const std::string &filename,
 	&&  _pitch   == current.pitch
 	&&  (sState == ALStream::Playing || sState == ALStream::Paused))
 	{
-		setBaseVolume(_volume);
+		setVolume(Base, _volume);
 		current.volume = _volume;
 		unlockStream();
 		return;
@@ -132,9 +140,15 @@ void AudioStream::play(const std::string &filename,
 		break;
 	}
 
-	setBaseVolume(_volume);
+	setVolume(Base, _volume);
 	stream.setPitch(_pitch);
 
+	if (offset > 0)
+	{
+		setVolume(FadeIn, 0);
+		startFadeIn();
+	}
+
 	current.filename = filename;
 	current.volume = _volume;
 	current.pitch = _pitch;
@@ -149,7 +163,7 @@ void AudioStream::play(const std::string &filename,
 
 void AudioStream::stop()
 {
-	finiFadeInt();
+	finiFadeOutInt();
 
 	lockStream();
 
@@ -190,18 +204,19 @@ void AudioStream::fadeOut(int duration)
 
 	if (fade.thread)
 	{
-		fade.reqFini = true;
+		fade.reqFini.set();
 		SDL_WaitThread(fade.thread, 0);
 		fade.thread = 0;
 	}
 
-	fade.active = true;
+	fade.active.set();
 	fade.msStep = (1.0) / duration;
-	fade.reqFini = false;
-	fade.reqTerm = false;
+	fade.reqFini.clear();
+	fade.reqTerm.clear();
 	fade.startTicks = SDL_GetTicks();
 
-	fade.thread = SDL_CreateThread(fadeThreadFun, fade.threadName.c_str(), this);
+	fade.thread = createSDLThread
+		<AudioStream, &AudioStream::fadeOutThread>(this, fade.threadName);
 
 	unlockStream();
 }
@@ -219,16 +234,15 @@ void AudioStream::unlockStream()
 	SDL_UnlockMutex(streamMut);
 }
 
-void AudioStream::setFadeVolume(float value)
+void AudioStream::setVolume(VolumeType type, float value)
 {
-	fadeVolume = value;
+	volumes[type] = value;
 	updateVolume();
 }
 
-void AudioStream::setExtVolume1(float value)
+float AudioStream::getVolume(VolumeType type)
 {
-	extVolume = value;
-	updateVolume();
+	return volumes[type];
 }
 
 float AudioStream::playingOffset()
@@ -236,28 +250,47 @@ float AudioStream::playingOffset()
 	return stream.queryOffset();
 }
 
-void AudioStream::finiFadeInt()
-{
-	if (!fade.thread)
-		return;
-
-	fade.reqFini = true;
-	SDL_WaitThread(fade.thread, 0);
-	fade.thread = 0;
-}
-
 void AudioStream::updateVolume()
 {
-	stream.setVolume(baseVolume * fadeVolume * extVolume);
+	float vol = 1.0;
+
+	for (size_t i = 0; i < VolumeTypeCount; ++i)
+		vol *= volumes[i];
+
+	stream.setVolume(vol);
 }
 
-void AudioStream::setBaseVolume(float value)
+void AudioStream::finiFadeOutInt()
 {
-	baseVolume = value;
-	updateVolume();
+	if (fade.thread)
+	{
+		fade.reqFini.set();
+		SDL_WaitThread(fade.thread, 0);
+		fade.thread = 0;
+	}
+
+	if (fadeIn.thread)
+	{
+		fadeIn.rqFini.set();
+		SDL_WaitThread(fadeIn.thread, 0);
+		fadeIn.thread = 0;
+	}
 }
 
-void AudioStream::fadeThread()
+void AudioStream::startFadeIn()
+{
+	/* Previous fadein should always be terminated in play() */
+	assert(!fadeIn.thread);
+
+	fadeIn.rqFini.clear();
+	fadeIn.rqTerm.clear();
+	fadeIn.startTicks = SDL_GetTicks();
+
+	fadeIn.thread = createSDLThread
+		<AudioStream, &AudioStream::fadeInThread>(this, fadeIn.threadName);
+}
+
+void AudioStream::fadeOutThread()
 {
 	while (true)
 	{
@@ -273,31 +306,59 @@ void AudioStream::fadeThread()
 		ALStream::State state = stream.queryState();
 
 		if (state != ALStream::Playing
-		||  resVol < 0
-		||  fade.reqFini)
+		|| resVol < 0
+		|| fade.reqFini)
 		{
 			if (state != ALStream::Paused)
 				stream.stop();
 
-			setFadeVolume(1.0);
+			setVolume(FadeOut, 1.0);
 			unlockStream();
 
 			break;
 		}
 
-		setFadeVolume(resVol);
+		setVolume(FadeOut, resVol);
 
 		unlockStream();
 
 		SDL_Delay(AUDIO_SLEEP);
 	}
 
-	fade.active = false;
+	fade.active.clear();
 }
 
-int AudioStream::fadeThreadFun(void *self)
+void AudioStream::fadeInThread()
 {
-	static_cast<AudioStream*>(self)->fadeThread();
+	while (true)
+	{
+		if (fadeIn.rqTerm)
+			break;
 
-	return 0;
+		lockStream();
+
+		/* Fade in duration is always 1 second */
+		uint32_t cur = SDL_GetTicks() - fadeIn.startTicks;
+		float prog = cur / 1000.0;
+
+		ALStream::State state = stream.queryState();
+
+		if (state != ALStream::Playing
+		||  prog >= 1.0
+		||  fadeIn.rqFini)
+		{
+			setVolume(FadeIn, 1.0);
+			unlockStream();
+
+			break;
+		}
+
+		/* Quadratic increase (not really the same as
+		 * in RMVXA, but close enough) */
+		setVolume(FadeIn, prog*prog);
+
+		unlockStream();
+
+		SDL_Delay(AUDIO_SLEEP);
+	}
 }
diff --git a/src/audiostream.h b/src/audiostream.h
index 79a2509..d968af3 100644
--- a/src/audiostream.h
+++ b/src/audiostream.h
@@ -24,12 +24,10 @@
 
 #include "al-util.h"
 #include "alstream.h"
+#include "sdl-util.h"
 
 #include <string>
 
-struct SDL_mutex;
-struct SDL_Thread;
-
 struct AudioStream
 {
 	struct
@@ -39,17 +37,21 @@ struct AudioStream
 		float pitch;
 	} current;
 
-	/* Volume set with 'play()' */
-	float baseVolume;
-
-	/* Volume set by external threads,
+	/* Volumes set by external threads,
 	 * such as for fade-in/out.
-	 * Multiplied with intVolume for final
-	 * playback volume.
-	 * fadeVolume: used by fade-out thread.
-	 * extVolume: used by MeWatch. */
-	float fadeVolume;
-	float extVolume;
+	 * Multiplied together for final
+	 * playback volume. Used with setVolume().
+	 * Base is set by play().
+	 * External is used by MeWatch */
+	enum VolumeType
+	{
+		Base = 0,
+		FadeOut,
+		FadeIn,
+		External,
+
+		VolumeTypeCount
+	};
 
 	/* Note that 'extPaused' and 'noResumeStop' are
 	 * effectively only used with the AudioStream
@@ -76,18 +78,19 @@ struct AudioStream
 	ALStream stream;
 	SDL_mutex *streamMut;
 
+	/* Fade out */
 	struct
 	{
-		/* Fade is in progress */
-		bool active;
+		/* Fade out is in progress */
+		AtomicFlag active;
 
 		/* Request fade thread to finish and
 		 * cleanup (like it normally would) */
-		bool reqFini;
+		AtomicFlag reqFini;
 
 		/* Request fade thread to terminate
 		 * immediately */
-		bool reqTerm;
+		AtomicFlag reqTerm;
 
 		SDL_Thread *thread;
 		std::string threadName;
@@ -100,6 +103,18 @@ struct AudioStream
 		uint32_t startTicks;
 	} fade;
 
+	/* Fade in */
+	struct
+	{
+		AtomicFlag rqFini;
+		AtomicFlag rqTerm;
+
+		SDL_Thread *thread;
+		std::string threadName;
+
+		uint32_t startTicks;
+	} fadeIn;
+
 	AudioStream(ALStream::LoopMode loopMode,
 	            const std::string &threadId);
 	~AudioStream();
@@ -117,19 +132,20 @@ struct AudioStream
 	void lockStream();
 	void unlockStream();
 
-	void setFadeVolume(float value);
-	void setExtVolume1(float value);
+	void setVolume(VolumeType type, float value);
+	float getVolume(VolumeType type);
 
 	float playingOffset();
 
 private:
-	void finiFadeInt();
-
+	float volumes[VolumeTypeCount];
 	void updateVolume();
-	void setBaseVolume(float value);
 
-	void fadeThread();
-	static int fadeThreadFun(void *);
+	void finiFadeOutInt();
+	void startFadeIn();
+
+	void fadeOutThread();
+	void fadeInThread();
 };
 
 #endif // AUDIOSTREAM_H
diff --git a/src/eventthread.h b/src/eventthread.h
index 23402c5..013f60b 100644
--- a/src/eventthread.h
+++ b/src/eventthread.h
@@ -24,47 +24,20 @@
 
 #include "config.h"
 #include "etc-internal.h"
+#include "sdl-util.h"
 
 #include <SDL_scancode.h>
 #include <SDL_joystick.h>
 #include <SDL_mouse.h>
 #include <SDL_mutex.h>
-#include <SDL_atomic.h>
 
 #include <string>
 
 #include <stdint.h>
 
 struct RGSSThreadData;
-struct SDL_Thread;
 struct SDL_Window;
 
-struct AtomicFlag
-{
-	AtomicFlag()
-	{
-		clear();
-	}
-
-	void set()
-	{
-		SDL_AtomicSet(&atom, 1);
-	}
-
-	void clear()
-	{
-		SDL_AtomicSet(&atom, 0);
-	}
-
-	operator bool() const
-	{
-		return SDL_AtomicGet(&atom);
-	}
-
-private:
-	mutable SDL_atomic_t atom;
-};
-
 class EventThread
 {
 public:
diff --git a/src/sdl-util.h b/src/sdl-util.h
new file mode 100644
index 0000000..ba02046
--- /dev/null
+++ b/src/sdl-util.h
@@ -0,0 +1,48 @@
+#ifndef SDLUTIL_H
+#define SDLUTIL_H
+
+#include <SDL_atomic.h>
+#include <SDL_thread.h>
+
+#include <string>
+
+struct AtomicFlag
+{
+	AtomicFlag()
+	{
+		clear();
+	}
+
+	void set()
+	{
+		SDL_AtomicSet(&atom, 1);
+	}
+
+	void clear()
+	{
+		SDL_AtomicSet(&atom, 0);
+	}
+
+	operator bool() const
+	{
+		return SDL_AtomicGet(&atom);
+	}
+
+private:
+	mutable SDL_atomic_t atom;
+};
+
+template<class C, void (C::*func)()>
+int __sdlThreadFun(void *obj)
+{
+	(static_cast<C*>(obj)->*func)();
+	return 0;
+}
+
+template<class C, void (C::*func)()>
+SDL_Thread *createSDLThread(C *obj, const std::string &name = std::string())
+{
+	return SDL_CreateThread(__sdlThreadFun<C, func>, name.c_str(), obj);
+}
+
+#endif // SDLUTIL_H