shithub: candycrisis

Download patch

ref: 8abfdf1aeef6a20ad1051220cd59e63faf18edeb
parent: fa3b56a5571fef108333c58c12caf99d200bb6f0
author: Iliyas Jorio <iliyas@jor.io>
date: Sun Jan 8 16:13:07 EST 2023

More pleasant audio mix + cmixer stability update

--- a/src/music.cpp
+++ b/src/music.cpp
@@ -80,11 +80,8 @@
 
 void ChooseMusic( short which )
 {
-    if (s_musicChannel != NULL)
-    {
-        delete s_musicChannel;
-        s_musicChannel = NULL;
-    }
+    // Kill existing song first, if any
+    ShutdownMusic();
 
     musicSelection = -1;
     
@@ -115,6 +112,7 @@
 {
     if (s_musicChannel)
     {
+        s_musicChannel->RemoveFromMixer();
         delete s_musicChannel;
         s_musicChannel = NULL;
     }
--- a/src/soundfx.cpp
+++ b/src/soundfx.cpp
@@ -7,10 +7,12 @@
 #include "support/cmixer.h"
 #include <stdio.h>
 
-static std::vector<cmixer::WavStream> soundBank;
-MBoolean                   soundOn = true;
-float playerStereoSeparation = 1.0;
+MBoolean soundOn = true;
 
+static std::vector<cmixer::WavStream> s_soundBank;
+static constexpr float k_playerStereoSeparation = 0.5f;
+static constexpr float k_soundEffectGain = 0.7f;
+
 void InitSound()
 {
     cmixer::InitWithSDL();
@@ -23,14 +25,18 @@
             Error(path);
         }
 
-        soundBank.emplace_back();
-        soundBank.back().InitFromWAVFile(path) ;
+        s_soundBank.emplace_back(cmixer::LoadWAVFromFile(path));
+        s_soundBank.back().SetInterpolation(true);
     }
 }
 
 void ShutdownSound()
 {
-    soundBank.clear();
+    for (auto& wavStream : s_soundBank)
+    {
+        wavStream.RemoveFromMixer();
+    }
+    s_soundBank.clear();
     cmixer::ShutdownWithSDL();
 }
 
@@ -48,19 +54,22 @@
 {
     if (soundOn)
     {
-        auto& effect = soundBank.at(which);
-        
+        auto& effect = s_soundBank.at(which);
+
         double pan;
-        switch (player) {
-            case 0: pan = -playerStereoSeparation; break;
-            case 1: pan = +playerStereoSeparation; break;
+        switch (player)
+        {
+            case 0: pan = -k_playerStereoSeparation; break;
+            case 1: pan = +k_playerStereoSeparation; break;
             default: pan = 0.0; break;
         }
-        
+
+        effect.Stop();
+        effect.SetGain(k_soundEffectGain);
         effect.SetPan(pan);
         effect.SetPitch(1.0 + freq/16.0);
         effect.Play();
-        
+
         UpdateSound();
     }
 }
--- a/src/soundfx.h
+++ b/src/soundfx.h
@@ -30,7 +30,4 @@
 	kNumSounds
 };
 
-namespace FMOD { class System; }
-
 extern MBoolean         soundOn;
-extern FMOD::System    *g_fmod;
--- a/src/support/cmixer.cpp
+++ b/src/support/cmixer.cpp
@@ -44,6 +44,30 @@
 
 #define BUFFER_MASK (BUFFER_SIZE - 1)
 
+static inline int16_t UnpackI16BE(const void* data)
+{
+#if __BIG_ENDIAN__
+	// no-op on big-endian systems
+	return *(const uint16_t*) data;
+#else
+	const uint8_t* p = (uint8_t*) data;
+	return	( p[0] << 8 )
+		|	( p[1]      );
+#endif
+}
+
+static inline int16_t UnpackI16LE(const void* data)
+{
+#if __BIG_ENDIAN__
+	const uint8_t* p = (uint8_t*) data;
+	return	( p[0]      )
+		|	( p[1] << 8 );
+#else
+	// no-op on little-endian systems
+	return *(const uint16_t*) data;
+#endif
+}
+
 //-----------------------------------------------------------------------------
 // Global mixer
 
@@ -86,11 +110,12 @@
 	// Init SDL audio
 	SDL_AudioSpec fmt = {};
 	fmt.freq = 44100;
-	fmt.format = AUDIO_S16;
+	fmt.format = AUDIO_S16SYS;
 	fmt.channels = 2;
 	fmt.samples = 1024;
 	fmt.callback = [](void* udata, Uint8* stream, int size)
 	{
+		(void) udata;
 		gMixer.Process((int16_t*) stream, size / 2);
 	};
 
@@ -161,7 +186,7 @@
 {
 	if (newGain < 0)
 		newGain = 0;
-	gain = FX_FROM_FLOAT(newGain);
+	gain = (int) FX_FROM_FLOAT(newGain);
 }
 
 void Mixer::Process(int16_t* dst, int len)
@@ -245,6 +270,7 @@
 {
 	this->samplerate = theSampleRate;
 	this->length = theLength;
+	this->sustainOffset = 0;
 	SetGain(1);
 	SetPan(0);
 	SetPitch(1);
@@ -252,16 +278,29 @@
 	Stop();
 }
 
-Source::~Source()
+void Source::RemoveFromMixer()
 {
 	gMixer.Lock();
 	if (active)
 	{
 		gMixer.sources.remove(this);
+		active = false;
 	}
 	gMixer.Unlock();
 }
 
+Source::~Source()
+{
+	if (active)
+	{
+		// You MUST call RemoveFromMixer before destroying a source. If you get here, your program is incorrect.
+		fprintf(stderr, "Source wasn't removed from mixer prior to destruction!\n");
+#if _DEBUG
+		std::terminate();
+#endif
+	}
+}
+
 void Source::Rewind()
 {
 	RewindImplementation();
@@ -395,8 +434,8 @@
 {
 	double l = this->gain * (pan <= 0. ? 1. : 1. - pan);
 	double r = this->gain * (pan >= 0. ? 1. : 1. + pan);
-	this->lgain = FX_FROM_FLOAT(l);
-	this->rgain = FX_FROM_FLOAT(r);
+	this->lgain = (int) FX_FROM_FLOAT(l);
+	this->rgain = (int) FX_FROM_FLOAT(r);
 }
 
 void Source::SetGain(double newGain)
@@ -422,7 +461,7 @@
 	{
 		newRate = 0.001;
 	}
-	rate = FX_FROM_FLOAT(newRate);
+	rate = (int) FX_FROM_FLOAT(newRate);
 }
 
 void Source::SetLoop(bool newLoop)
@@ -437,6 +476,13 @@
 
 void Source::Play()
 {
+	if (length == 0)
+	{
+		// Don't attempt to play an empty source as this would result
+		// in instant starvation when filling mixer buffer
+		return;
+	}
+
 	gMixer.Lock();
 	state = CM_STATE_PLAYING;
 	if (!active)
@@ -488,36 +534,88 @@
 	bitdepth = 0;
 	channels = 0;
 	idx = 0;
+
+#if __BIG_ENDIAN__		// default to native endianness
+	bigEndian = true;
+#else
+	bigEndian = false;
+#endif
+
 	userBuffer.clear();
 }
 
+void WavStream::Init(
+	int theSampleRate,
+	int theBitDepth,
+	int theNChannels,
+	bool theBigEndian,
+	std::span<char> theSpan)
+{
+	Clear();
+	Source::Init(theSampleRate, int((theSpan.size() / (theBitDepth / 8)) / theNChannels));
+	this->bitdepth = theBitDepth;
+	this->channels = theNChannels;
+	this->idx = 0;
+	this->span = theSpan;
+	this->bigEndian = theBigEndian;
+}
+
+std::span<char> WavStream::GetBuffer(int nBytesOut)
+{
+	userBuffer.clear();
+	userBuffer.reserve(nBytesOut);
+	return std::span(userBuffer.data(), nBytesOut);
+}
+
+std::span<char> WavStream::SetBuffer(std::vector<char>&& data)
+{
+	userBuffer = std::move(data);
+	return std::span(userBuffer.data(), userBuffer.size());
+}
+
 void WavStream::RewindImplementation()
 {
 	idx = 0;
 }
 
-void WavStream::FillBuffer(int16_t* dst, int len)
+void WavStream::FillBuffer(int16_t* dst, int fillLength)
 {
 	int x, n;
 
-	len /= 2;
+	fillLength /= 2;
 
-	while (len > 0)
+	while (fillLength > 0)
 	{
-		n = MIN(len, length - idx);
-		len -= n;
-        if (bitdepth == 16 && channels == 1)
+		n = MIN(fillLength, length - idx);
+
+		fillLength -= n;
+
+		if (bigEndian && bitdepth == 16 && channels == 1)
 		{
 			WAV_PROCESS_LOOP({
-				dst[0] = dst[1] = data16()[idx];
+				dst[0] = dst[1] = UnpackI16BE(&data16()[idx]);
 			});
 		}
+		else if (bigEndian && bitdepth == 16 && channels == 2)
+		{
+			WAV_PROCESS_LOOP({
+				x = idx * 2;
+				dst[0] = UnpackI16BE(&data16()[x]);
+				dst[1] = UnpackI16BE(&data16()[x + 1]);
+			});
+		}
+		else if (bitdepth == 16 && channels == 1)
+		{
+			WAV_PROCESS_LOOP({
+				dst[0] = dst[1] = UnpackI16LE(&data16()[idx]);
+			});
+		}
 		else if (bitdepth == 16 && channels == 2)
 		{
 			WAV_PROCESS_LOOP({
 				x = idx * 2;
-				dst[0] = data16()[x];
-				dst[1] = data16()[x + 1];
+				dst[0] = UnpackI16LE(&data16()[x]);
+				dst[1] = UnpackI16LE(&data16()[x + 1]);
 			});
 		}
 		else if (bitdepth == 8 && channels == 1)
@@ -535,15 +633,15 @@
 			});
 		}
 		// Loop back and continue filling buffer if we didn't fill the buffer
-		if (len > 0)
+		if (fillLength > 0)
 		{
-			idx = 0;
+			idx = sustainOffset;
 		}
 	}
 }
 
 //-----------------------------------------------------------------------------
-// LoadWAVFromFile for testing
+// LoadWAVFromFile
 
 static std::vector<char> LoadFile(char const* filename)
 {
@@ -570,7 +668,7 @@
 	return p + 8;
 }
 
-void cmixer::WavStream::InitFromWAVFile(const char* path)
+WavStream cmixer::LoadWAVFromFile(const char* path)
 {
 	int sz;
 	auto filebuf = LoadFile(path);
@@ -581,7 +679,7 @@
 	// Check header
 	if (memcmp(p, "RIFF", 4) || memcmp(p + 8, "WAVE", 4))
 		throw std::invalid_argument("bad wav header");
-	
+
 	// Find fmt subchunk
 	p = FindChunk(data, len, "fmt ", &sz);
 	if (!p)
@@ -602,15 +700,12 @@
 	if (!p)
 		throw std::invalid_argument("no data subchunk");
 
-    Clear();
-    Init(samplerate, sz / (channels * bitdepth / 8));
-
-    this->bitdepth = bitdepth;
-    this->channels = channels;
-    this->idx = 0;
-	this->interpolate = true;
-
-    userBuffer.reserve(sz);
-    for (int i = 0; i < sz; i++)
-        userBuffer.push_back(p[i]);
+	WavStream wavStream;
+	wavStream.Init(
+		samplerate,
+		bitdepth,
+		channels,
+		false,
+		wavStream.SetBuffer(std::vector<char>(p, p + sz)));
+	return wavStream;
 }
--- a/src/support/cmixer.h
+++ b/src/support/cmixer.h
@@ -27,6 +27,7 @@
 #include <vector>
 #include <functional>
 #include <cstdint>
+#include <span>
 
 #define BUFFER_SIZE (512)
 
@@ -45,6 +46,7 @@
 		int16_t pcmbuf[BUFFER_SIZE];    // Internal buffer with raw stereo PCM
 		int samplerate;                 // Stream's native samplerate
 		int length;                     // Stream's length in frames
+		int sustainOffset;              // Offset of the sustain loop in frames
 		int end;                        // End index for the current play-through
 		int state;                      // Current state (playing|paused|stopped)
 		int64_t position;               // Current playhead position (fixed point)
@@ -75,6 +77,8 @@
 	public:
 		virtual ~Source();
 
+		void RemoveFromMixer();
+
 		void Clear();
 
 		void Rewind();
@@ -114,7 +118,9 @@
 	{
 		int bitdepth;
 		int channels;
+		bool bigEndian;
 		int idx;
+		std::span<char> span;
 		std::vector<char> userBuffer;
 
 		void ClearImplementation() override;
@@ -123,16 +129,28 @@
 
 		void FillBuffer(int16_t* buffer, int length) override;
 
-		inline const uint8_t* data8() const
-		{ return reinterpret_cast<const uint8_t*>(userBuffer.data()); }
+		inline uint8_t* data8() const
+		{ return reinterpret_cast<uint8_t*>(span.data()); }
 
-		inline const int16_t* data16() const
-		{ return reinterpret_cast<const int16_t*>(userBuffer.data()); }
+		inline int16_t* data16() const
+		{ return reinterpret_cast<int16_t*>(span.data()); }
 
 	public:
 		WavStream();
 
-        void InitFromWAVFile(const char* path);
+		WavStream(WavStream&&) = default;	// move constructor ensures span stays in sync with userBuffer!
+
+		void Init(
+			int theSampleRate,
+			int theBitDepth,
+			int nChannels,
+			bool bigEndian,
+			std::span<char> data
+		);
+
+		std::span<char> GetBuffer(int nBytesOut);
+
+		std::span<char> SetBuffer(std::vector<char>&& data);
 	};