Handle stereo panning in OAL manually for streams

This commit is contained in:
Sergeanur 2021-01-04 22:46:50 +02:00
parent 047f9c49ec
commit 150f5302b7
3 changed files with 189 additions and 65 deletions

View File

@ -19,6 +19,64 @@
#include "crossplatform.h" #include "crossplatform.h"
#endif #endif
/*
As we ran onto an issue of having different volume levels for mono streams
and stereo streams we are now handling all the stereo panning ourselves.
Each stream now has two sources - one panned to the left and one to the right,
and uses two separate buffers to store data for each individual channel.
For that we also have to reshuffle all decoded PCM stereo data from LRLRLRLR to
LLLLRRRR (handled by CSortStereoBuffer).
*/
class CSortStereoBuffer
{
uint16* PcmBuf;
size_t BufSize;
public:
CSortStereoBuffer() : PcmBuf(nil), BufSize(0) {}
~CSortStereoBuffer()
{
if (PcmBuf)
free(PcmBuf);
}
uint16* GetBuffer(size_t size)
{
if (size == 0) return nil;
if (!PcmBuf)
{
BufSize = size;
PcmBuf = (uint16*)malloc(BufSize);
}
else if (BufSize < size)
{
BufSize = size;
PcmBuf = (uint16*)realloc(PcmBuf, size);
}
return PcmBuf;
}
void SortStereo(void* buf, size_t size)
{
uint16* InBuf = (uint16*)buf;
uint16* OutBuf = GetBuffer(size);
if (!OutBuf) return;
size_t rightStart = size / 4;
for (size_t i = 0; i < size / 4; i++)
{
OutBuf[i] = InBuf[i*2];
OutBuf[i+rightStart] = InBuf[i*2+1];
}
memcpy(InBuf, OutBuf, size);
}
};
CSortStereoBuffer SortStereoBuffer;
#ifndef AUDIO_OPUS #ifndef AUDIO_OPUS
class CSndFile : public IDecoder class CSndFile : public IDecoder
{ {
@ -81,7 +139,11 @@ public:
uint32 Decode(void *buffer) uint32 Decode(void *buffer)
{ {
if ( !IsOpened() ) return 0; if ( !IsOpened() ) return 0;
return sf_read_short(m_pfSound, (short *)buffer, GetBufferSamples()) * GetSampleSize();
size_t size = sf_read_short(m_pfSound, (short*)buffer, GetBufferSamples()) * GetSampleSize();
if (GetChannels()==2)
SortStereoBuffer.SortStereo(buffer, size);
return size;
} }
}; };
@ -101,6 +163,8 @@ public:
m_pMH = mpg123_new(nil, nil); m_pMH = mpg123_new(nil, nil);
if ( m_pMH ) if ( m_pMH )
{ {
mpg123_param(m_pMH, MPG123_FLAGS, MPG123_FUZZY | MPG123_SEEKBUFFER | MPG123_GAPLESS, 0.0);
long rate = 0; long rate = 0;
int channels = 0; int channels = 0;
int encoding = 0; int encoding = 0;
@ -176,6 +240,8 @@ public:
assert("We can't handle audio files more then 2 GB yet :shrug:" && (size < UINT32_MAX)); assert("We can't handle audio files more then 2 GB yet :shrug:" && (size < UINT32_MAX));
#endif #endif
if (err != MPG123_OK && err != MPG123_DONE) return 0; if (err != MPG123_OK && err != MPG123_DONE) return 0;
if (GetChannels() == 2)
SortStereoBuffer.SortStereo(buffer, size);
return (uint32)size; return (uint32)size;
} }
}; };
@ -267,6 +333,9 @@ public:
if (size < 0) if (size < 0)
return 0; return 0;
if (GetChannels() == 2)
SortStereoBuffer.SortStereo(buffer, size * m_nChannels * GetSampleSize());
return size * m_nChannels * GetSampleSize(); return size * m_nChannels * GetSampleSize();
} }
}; };
@ -286,8 +355,8 @@ void CStream::Terminate()
#endif #endif
} }
CStream::CStream(char *filename, ALuint &source, ALuint (&buffers)[NUM_STREAMBUFFERS]) : CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]) :
m_alSource(source), m_pAlSources(sources),
m_alBuffers(buffers), m_alBuffers(buffers),
m_pBuffer(nil), m_pBuffer(nil),
m_bPaused(false), m_bPaused(false),
@ -368,7 +437,7 @@ void CStream::Delete()
bool CStream::HasSource() bool CStream::HasSource()
{ {
return m_alSource != AL_NONE; return (m_pAlSources[0] != AL_NONE) && (m_pAlSources[1] != AL_NONE);
} }
bool CStream::IsOpened() bool CStream::IsOpened()
@ -382,9 +451,10 @@ bool CStream::IsPlaying()
if ( !m_bPaused ) if ( !m_bPaused )
{ {
ALint sourceState; ALint sourceState[2];
alGetSourcei(m_alSource, AL_SOURCE_STATE, &sourceState); alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState[0]);
if ( m_bActive || sourceState == AL_PLAYING ) alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]);
if ( m_bActive || sourceState[0] == AL_PLAYING || sourceState[1] == AL_PLAYING)
return true; return true;
} }
@ -395,9 +465,12 @@ void CStream::Pause()
{ {
if ( !HasSource() ) return; if ( !HasSource() ) return;
ALint sourceState = AL_PAUSED; ALint sourceState = AL_PAUSED;
alGetSourcei(m_alSource, AL_SOURCE_STATE, &sourceState); alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState);
if (sourceState != AL_PAUSED ) if (sourceState != AL_PAUSED )
alSourcePause(m_alSource); alSourcePause(m_pAlSources[0]);
alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState);
if (sourceState != AL_PAUSED)
alSourcePause(m_pAlSources[1]);
} }
void CStream::SetPause(bool bPause) void CStream::SetPause(bool bPause)
@ -419,19 +492,21 @@ void CStream::SetPause(bool bPause)
void CStream::SetPitch(float pitch) void CStream::SetPitch(float pitch)
{ {
if ( !HasSource() ) return; if ( !HasSource() ) return;
alSourcef(m_alSource, AL_PITCH, pitch); alSourcef(m_pAlSources[0], AL_PITCH, pitch);
alSourcef(m_pAlSources[1], AL_PITCH, pitch);
} }
void CStream::SetGain(float gain) void CStream::SetGain(float gain)
{ {
if ( !HasSource() ) return; if ( !HasSource() ) return;
alSourcef(m_alSource, AL_GAIN, gain); alSourcef(m_pAlSources[0], AL_GAIN, gain);
alSourcef(m_pAlSources[1], AL_GAIN, gain);
} }
void CStream::SetPosition(float x, float y, float z) void CStream::SetPosition(int i, float x, float y, float z)
{ {
if ( !HasSource() ) return; if ( !HasSource() ) return;
alSource3f(m_alSource, AL_POSITION, x, y, z); alSource3f(m_pAlSources[i], AL_POSITION, x, y, z);
} }
void CStream::SetVolume(uint32 nVol) void CStream::SetVolume(uint32 nVol)
@ -442,8 +517,13 @@ void CStream::SetVolume(uint32 nVol)
void CStream::SetPan(uint8 nPan) void CStream::SetPan(uint8 nPan)
{ {
m_nPan = clamp((int8)nPan - 63, 0, 63);
SetPosition(0, (m_nPan - 63) / 64.0f, 0.0f, Sqrt(1.0f - SQR((m_nPan - 63) / 64.0f)));
m_nPan = clamp((int8)nPan + 64, 64, 127);
SetPosition(1, (m_nPan - 63) / 64.0f, 0.0f, Sqrt(1.0f - SQR((m_nPan - 63) / 64.0f)));
m_nPan = nPan; m_nPan = nPan;
SetPosition((nPan - 63)/64.0f, 0.0f, Sqrt(1.0f-SQR((nPan-63)/64.0f)));
} }
void CStream::SetPosMS(uint32 nPos) void CStream::SetPosMS(uint32 nPos)
@ -460,10 +540,10 @@ uint32 CStream::GetPosMS()
ALint offset; ALint offset;
//alGetSourcei(m_alSource, AL_SAMPLE_OFFSET, &offset); //alGetSourcei(m_alSource, AL_SAMPLE_OFFSET, &offset);
alGetSourcei(m_alSource, AL_BYTE_OFFSET, &offset); alGetSourcei(m_pAlSources[0], AL_BYTE_OFFSET, &offset);
return m_pSoundFile->Tell() return m_pSoundFile->Tell()
- m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples() * (NUM_STREAMBUFFERS-1)) / m_pSoundFile->GetChannels() - m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples() * (NUM_STREAMBUFFERS/2-1)) / m_pSoundFile->GetChannels()
+ m_pSoundFile->samples2ms(offset/m_pSoundFile->GetSampleSize()) / m_pSoundFile->GetChannels(); + m_pSoundFile->samples2ms(offset/m_pSoundFile->GetSampleSize()) / m_pSoundFile->GetChannels();
} }
@ -473,33 +553,41 @@ uint32 CStream::GetLengthMS()
return m_pSoundFile->GetLength(); return m_pSoundFile->GetLength();
} }
bool CStream::FillBuffer(ALuint alBuffer) bool CStream::FillBuffer(ALuint *alBuffer)
{ {
if ( !HasSource() ) if ( !HasSource() )
return false; return false;
if ( !IsOpened() ) if ( !IsOpened() )
return false; return false;
if ( !(alBuffer != AL_NONE && alIsBuffer(alBuffer)) ) if ( !(alBuffer[0] != AL_NONE && alIsBuffer(alBuffer[0])) )
return false;
if ( !(alBuffer[1] != AL_NONE && alIsBuffer(alBuffer[1])) )
return false; return false;
uint32 size = m_pSoundFile->Decode(m_pBuffer); uint32 size = m_pSoundFile->Decode(m_pBuffer);
if( size == 0 ) if( size == 0 )
return false; return false;
alBufferData(alBuffer, m_pSoundFile->GetChannels() == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, uint32 channelSize = size / m_pSoundFile->GetChannels();
m_pBuffer, size, m_pSoundFile->GetSampleRate());
alBufferData(alBuffer[0], AL_FORMAT_MONO16, m_pBuffer, channelSize, m_pSoundFile->GetSampleRate());
// TODO: use just one buffer if we play mono
if (m_pSoundFile->GetChannels() == 1)
alBufferData(alBuffer[1], AL_FORMAT_MONO16, m_pBuffer, channelSize, m_pSoundFile->GetSampleRate());
else
alBufferData(alBuffer[1], AL_FORMAT_MONO16, (uint8*)m_pBuffer + channelSize, channelSize, m_pSoundFile->GetSampleRate());
return true; return true;
} }
int32 CStream::FillBuffers() int32 CStream::FillBuffers()
{ {
int32 i = 0; int32 i = 0;
for ( i = 0; i < NUM_STREAMBUFFERS; i++ ) for ( i = 0; i < NUM_STREAMBUFFERS/2; i++ )
{ {
if ( !FillBuffer(m_alBuffers[i]) ) if ( !FillBuffer(&m_alBuffers[i*2]) )
break; break;
alSourceQueueBuffers(m_alSource, 1, &m_alBuffers[i]); alSourceQueueBuffers(m_pAlSources[0], 1, &m_alBuffers[i*2]);
alSourceQueueBuffers(m_pAlSources[1], 1, &m_alBuffers[i*2+1]);
} }
return i; return i;
@ -509,12 +597,15 @@ void CStream::ClearBuffers()
{ {
if ( !HasSource() ) return; if ( !HasSource() ) return;
ALint buffersQueued; ALint buffersQueued[2];
alGetSourcei(m_alSource, AL_BUFFERS_QUEUED, &buffersQueued); alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &buffersQueued[0]);
alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &buffersQueued[1]);
ALuint value; ALuint value;
while (buffersQueued--) while (buffersQueued[0]--)
alSourceUnqueueBuffers(m_alSource, 1, &value); alSourceUnqueueBuffers(m_pAlSources[0], 1, &value);
while (buffersQueued[1]--)
alSourceUnqueueBuffers(m_pAlSources[1], 1, &value);
} }
bool CStream::Setup() bool CStream::Setup()
@ -522,7 +613,6 @@ bool CStream::Setup()
if ( IsOpened() ) if ( IsOpened() )
{ {
m_pSoundFile->Seek(0); m_pSoundFile->Seek(0);
alSourcei(m_alSource, AL_SOURCE_RELATIVE, AL_TRUE);
//SetPosition(0.0f, 0.0f, 0.0f); //SetPosition(0.0f, 0.0f, 0.0f);
SetPitch(1.0f); SetPitch(1.0f);
//SetPan(m_nPan); //SetPan(m_nPan);
@ -538,17 +628,29 @@ void CStream::SetPlay(bool state)
if ( state ) if ( state )
{ {
ALint sourceState = AL_PLAYING; ALint sourceState = AL_PLAYING;
alGetSourcei(m_alSource, AL_SOURCE_STATE, &sourceState); alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState);
if (sourceState != AL_PLAYING ) if (sourceState != AL_PLAYING )
alSourcePlay(m_alSource); alSourcePlay(m_pAlSources[0]);
sourceState = AL_PLAYING;
alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState);
if (sourceState != AL_PLAYING)
alSourcePlay(m_pAlSources[1]);
m_bActive = true; m_bActive = true;
} }
else else
{ {
ALint sourceState = AL_STOPPED; ALint sourceState = AL_STOPPED;
alGetSourcei(m_alSource, AL_SOURCE_STATE, &sourceState); alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState);
if (sourceState != AL_STOPPED ) if (sourceState != AL_STOPPED)
alSourceStop(m_alSource); alSourceStop(m_pAlSources[0]);
sourceState = AL_STOPPED;
alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState);
if (sourceState != AL_STOPPED)
alSourceStop(m_pAlSources[1]);
m_bActive = false; m_bActive = false;
} }
} }
@ -579,35 +681,48 @@ void CStream::Update()
if ( !m_bPaused ) if ( !m_bPaused )
{ {
ALint sourceState; ALint sourceState[2];
ALint buffersProcessed = 0; ALint buffersProcessed[2] = { 0, 0 };
alGetSourcei(m_alSource, AL_SOURCE_STATE, &sourceState); // Relying a lot on left buffer states in here
alGetSourcei(m_alSource, AL_BUFFERS_PROCESSED, &buffersProcessed);
//alSourcef(m_pAlSources[0], AL_ROLLOFF_FACTOR, 0.0f);
alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState[0]);
alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed[0]);
//alSourcef(m_pAlSources[1], AL_ROLLOFF_FACTOR, 0.0f);
alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]);
alGetSourcei(m_pAlSources[1], AL_BUFFERS_PROCESSED, &buffersProcessed[1]);
ALint looping = AL_FALSE; ALint looping = AL_FALSE;
alGetSourcei(m_alSource, AL_LOOPING, &looping); alGetSourcei(m_pAlSources[0], AL_LOOPING, &looping);
if ( looping == AL_TRUE ) if ( looping == AL_TRUE )
{ {
TRACE("stream set looping"); TRACE("stream set looping");
alSourcei(m_alSource, AL_LOOPING, AL_TRUE); alSourcei(m_pAlSources[0], AL_LOOPING, AL_TRUE);
alSourcei(m_pAlSources[1], AL_LOOPING, AL_TRUE);
} }
while( buffersProcessed-- ) assert(buffersProcessed[0] == buffersProcessed[1]);
while( buffersProcessed[0]-- )
{ {
ALuint buffer; ALuint buffer[2];
alSourceUnqueueBuffers(m_alSource, 1, &buffer); alSourceUnqueueBuffers(m_pAlSources[0], 1, &buffer[0]);
alSourceUnqueueBuffers(m_pAlSources[1], 1, &buffer[1]);
if ( m_bActive && FillBuffer(buffer) ) if (m_bActive && FillBuffer(buffer))
alSourceQueueBuffers(m_alSource, 1, &buffer); {
alSourceQueueBuffers(m_pAlSources[0], 1, &buffer[0]);
alSourceQueueBuffers(m_pAlSources[1], 1, &buffer[1]);
}
} }
if ( sourceState != AL_PLAYING ) if ( sourceState[0] != AL_PLAYING )
{ {
alGetSourcei(m_alSource, AL_BUFFERS_PROCESSED, &buffersProcessed); alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed[0]);
SetPlay(buffersProcessed!=0); SetPlay(buffersProcessed[0]!=0);
} }
} }
} }

View File

@ -3,7 +3,7 @@
#ifdef AUDIO_OAL #ifdef AUDIO_OAL
#include <AL/al.h> #include <AL/al.h>
#define NUM_STREAMBUFFERS 4 #define NUM_STREAMBUFFERS 8
class IDecoder class IDecoder
{ {
@ -57,7 +57,7 @@ public:
class CStream class CStream
{ {
char m_aFilename[128]; char m_aFilename[128];
ALuint &m_alSource; ALuint *m_pAlSources;
ALuint (&m_alBuffers)[NUM_STREAMBUFFERS]; ALuint (&m_alBuffers)[NUM_STREAMBUFFERS];
bool m_bPaused; bool m_bPaused;
@ -73,20 +73,20 @@ class CStream
IDecoder *m_pSoundFile; IDecoder *m_pSoundFile;
bool HasSource(); bool HasSource();
void SetPosition(float x, float y, float z); void SetPosition(int i, float x, float y, float z);
void SetPitch(float pitch); void SetPitch(float pitch);
void SetGain(float gain); void SetGain(float gain);
void Pause(); void Pause();
void SetPlay(bool state); void SetPlay(bool state);
bool FillBuffer(ALuint alBuffer); bool FillBuffer(ALuint *alBuffer);
int32 FillBuffers(); int32 FillBuffers();
void ClearBuffers(); void ClearBuffers();
public: public:
static void Initialise(); static void Initialise();
static void Terminate(); static void Terminate();
CStream(char *filename, ALuint &source, ALuint (&buffers)[NUM_STREAMBUFFERS]); CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]);
~CStream(); ~CStream();
void Delete(); void Delete();

View File

@ -102,7 +102,7 @@ CChannel aChannel[MAXCHANNELS+MAX2DCHANNELS];
uint8 nChannelVolume[MAXCHANNELS+MAX2DCHANNELS]; uint8 nChannelVolume[MAXCHANNELS+MAX2DCHANNELS];
uint32 nStreamLength[TOTAL_STREAMED_SOUNDS]; uint32 nStreamLength[TOTAL_STREAMED_SOUNDS];
ALuint ALStreamSources[MAX_STREAMS]; ALuint ALStreamSources[MAX_STREAMS][2];
ALuint ALStreamBuffers[MAX_STREAMS][NUM_STREAMBUFFERS]; ALuint ALStreamBuffers[MAX_STREAMS][NUM_STREAMBUFFERS];
struct tMP3Entry struct tMP3Entry
@ -245,9 +245,9 @@ release_existing()
if (stream) if (stream)
stream->ProviderTerm(); stream->ProviderTerm();
alDeleteSources(1, &ALStreamSources[i]);
alDeleteBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]); alDeleteBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]);
} }
alDeleteSources(MAX_STREAMS*2, ALStreamSources[0]);
CChannel::DestroyChannels(); CChannel::DestroyChannels();
@ -287,7 +287,10 @@ set_new_provider(int index)
//TODO: //TODO:
_maxSamples = MAXCHANNELS; _maxSamples = MAXCHANNELS;
ALCint attr[] = {ALC_FREQUENCY,MAX_FREQ,0}; ALCint attr[] = {ALC_FREQUENCY,MAX_FREQ,
ALC_MONO_SOURCES, MAX_STREAMS * 2 + MAXCHANNELS,
0,
};
ALDevice = alcOpenDevice(providers[index].id); ALDevice = alcOpenDevice(providers[index].id);
ASSERT(ALDevice != NULL); ASSERT(ALDevice != NULL);
@ -320,10 +323,16 @@ set_new_provider(int index)
alGenEffects(1, &ALEffect); alGenEffects(1, &ALEffect);
} }
alGenSources(MAX_STREAMS*2, ALStreamSources[0]);
for ( int32 i = 0; i < MAX_STREAMS; i++ ) for ( int32 i = 0; i < MAX_STREAMS; i++ )
{ {
alGenSources(1, &ALStreamSources[i]);
alGenBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]); alGenBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]);
alSourcei(ALStreamSources[i][0], AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(ALStreamSources[i][0], AL_POSITION, 0.0f, 0.0f, 0.0f);
alSourcef(ALStreamSources[i][0], AL_GAIN, 1.0f);
alSourcei(ALStreamSources[i][1], AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(ALStreamSources[i][1], AL_POSITION, 0.0f, 0.0f, 0.0f);
alSourcef(ALStreamSources[i][1], AL_GAIN, 1.0f);
CStream *stream = aStream[i]; CStream *stream = aStream[i];
if (stream) if (stream)