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);
};