summaryrefslogtreecommitdiff
path: root/src/audio/oal
diff options
context:
space:
mode:
authorclaude-bot <[email protected]>2026-07-13 12:27:07 +0000
committerclaude-bot <[email protected]>2026-07-13 12:27:07 +0000
commit9f61c9e6ac6b1ac5692cf6352d2ebbd47a31a686 (patch)
treea84756b82513739a2672db3a1f0ec579db6d18ff /src/audio/oal
downloadre3-miami.tar.gz
re3-miami.zip
Import Cai1Hsu/re3 @ miami (reVC / GTA:VC decompilation)HEADmiami
Snapshot import (no upstream history) into git.ancap.in.ua/claude, per @lzcnt. Source: https://github.com/Cai1Hsu/re3 branch miami.
Diffstat (limited to 'src/audio/oal')
-rw-r--r--src/audio/oal/aldlist.cpp305
-rw-r--r--src/audio/oal/aldlist.h69
-rw-r--r--src/audio/oal/channel.cpp278
-rw-r--r--src/audio/oal/channel.h54
-rw-r--r--src/audio/oal/oal_utils.cpp181
-rw-r--r--src/audio/oal/oal_utils.h54
-rw-r--r--src/audio/oal/stream.cpp1413
-rw-r--r--src/audio/oal/stream.h114
8 files changed, 2468 insertions, 0 deletions
diff --git a/src/audio/oal/aldlist.cpp b/src/audio/oal/aldlist.cpp
new file mode 100644
index 0000000..881418c
--- /dev/null
+++ b/src/audio/oal/aldlist.cpp
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2006, Creative Labs Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this list of conditions and
+ * the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+ * and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "aldlist.h"
+
+#ifndef _WIN32
+#define _stricmp strcasecmp
+#define _strnicmp strncasecmp
+#define _strdup strdup
+#endif
+
+#ifdef AUDIO_OAL
+/*
+ * Init call
+ */
+ALDeviceList::ALDeviceList()
+{
+ char *devices;
+ int index;
+ const char *defaultDeviceName;
+ const char *actualDeviceName;
+
+ // DeviceInfo vector stores, for each enumerated device, it's device name, selection status, spec version #, and extension support
+ nNumOfDevices = 0;
+
+ defaultDeviceIndex = 0;
+
+ if (alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT")) {
+ devices = (char *)alcGetString(NULL, ALC_DEVICE_SPECIFIER);
+ defaultDeviceName = (char *)alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+
+ index = 0;
+ // go through device list (each device terminated with a single NULL, list terminated with double NULL)
+ while (*devices != '\0') {
+ if (strcmp(defaultDeviceName, devices) == 0) {
+ defaultDeviceIndex = index;
+ }
+ ALCdevice *device = alcOpenDevice(devices);
+ if (device) {
+ ALCcontext *context = alcCreateContext(device, NULL);
+ if (context) {
+ alcMakeContextCurrent(context);
+ // if new actual device name isn't already in the list, then add it...
+ actualDeviceName = alcGetString(device, ALC_DEVICE_SPECIFIER);
+ bool bNewName = true;
+ for (unsigned int i = 0; i < GetNumDevices(); i++) {
+ if (strcmp(GetDeviceName(i), actualDeviceName) == 0) {
+ bNewName = false;
+ }
+ }
+ if ((bNewName) && (actualDeviceName != NULL) && (strlen(actualDeviceName) > 0)) {
+ ALDEVICEINFO ALDeviceInfo;
+ ALDeviceInfo.bSelected = true;
+ ALDeviceInfo.strDeviceName = _strdup(actualDeviceName);
+ alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(int), &ALDeviceInfo.iMajorVersion);
+ alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(int), &ALDeviceInfo.iMinorVersion);
+
+ // Check for ALC Extensions
+ if (alcIsExtensionPresent(device, "ALC_EXT_CAPTURE") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EXT_CAPTURE;
+ if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EXT_EFX;
+
+ // Check for AL Extensions
+ if (alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EXT_OFFSET;
+
+ if (alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EXT_LINEAR_DISTANCE;
+ if (alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EXT_EXPONENT_DISTANCE;
+
+ if (alIsExtensionPresent("EAX2.0") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EAX2;
+ if (alIsExtensionPresent("EAX3.0") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EAX3;
+ if (alIsExtensionPresent("EAX4.0") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EAX4;
+ if (alIsExtensionPresent("EAX5.0") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EAX5;
+
+ if (alIsExtensionPresent("EAX-RAM") == AL_TRUE)
+ ALDeviceInfo.Extensions |= ADEXT_EAX_RAM;
+
+ // Get Source Count
+ ALDeviceInfo.uiSourceCount = GetMaxNumSources();
+
+ aDeviceInfo[nNumOfDevices++] = ALDeviceInfo;
+ }
+ alcMakeContextCurrent(NULL);
+ alcDestroyContext(context);
+ }
+ alcCloseDevice(device);
+ }
+ devices += strlen(devices) + 1;
+ index += 1;
+ }
+ }
+
+ ResetFilters();
+}
+
+/*
+ * Exit call
+ */
+ALDeviceList::~ALDeviceList()
+{
+}
+
+/*
+ * Returns the number of devices in the complete device list
+ */
+unsigned int ALDeviceList::GetNumDevices()
+{
+ return nNumOfDevices;
+}
+
+/*
+ * Returns the device name at an index in the complete device list
+ */
+const char * ALDeviceList::GetDeviceName(unsigned int index)
+{
+ if (index < GetNumDevices())
+ return aDeviceInfo[index].strDeviceName;
+ else
+ return NULL;
+}
+
+/*
+ * Returns the major and minor version numbers for a device at a specified index in the complete list
+ */
+void ALDeviceList::GetDeviceVersion(unsigned int index, int *major, int *minor)
+{
+ if (index < GetNumDevices()) {
+ if (major)
+ *major = aDeviceInfo[index].iMajorVersion;
+ if (minor)
+ *minor = aDeviceInfo[index].iMinorVersion;
+ }
+ return;
+}
+
+/*
+ * Returns the maximum number of Sources that can be generate on the given device
+ */
+unsigned int ALDeviceList::GetMaxNumSources(unsigned int index)
+{
+ if (index < GetNumDevices())
+ return aDeviceInfo[index].uiSourceCount;
+ else
+ return 0;
+}
+
+/*
+ * Checks if the extension is supported on the given device
+ */
+bool ALDeviceList::IsExtensionSupported(int index, unsigned short ext)
+{
+ return !!(aDeviceInfo[index].Extensions & ext);
+}
+
+/*
+ * returns the index of the default device in the complete device list
+ */
+int ALDeviceList::GetDefaultDevice()
+{
+ return defaultDeviceIndex;
+}
+
+/*
+ * Deselects devices which don't have the specified minimum version
+ */
+void ALDeviceList::FilterDevicesMinVer(int major, int minor)
+{
+ int dMajor, dMinor;
+ for (unsigned int i = 0; i < nNumOfDevices; i++) {
+ GetDeviceVersion(i, &dMajor, &dMinor);
+ if ((dMajor < major) || ((dMajor == major) && (dMinor < minor))) {
+ aDeviceInfo[i].bSelected = false;
+ }
+ }
+}
+
+/*
+ * Deselects devices which don't have the specified maximum version
+ */
+void ALDeviceList::FilterDevicesMaxVer(int major, int minor)
+{
+ int dMajor, dMinor;
+ for (unsigned int i = 0; i < nNumOfDevices; i++) {
+ GetDeviceVersion(i, &dMajor, &dMinor);
+ if ((dMajor > major) || ((dMajor == major) && (dMinor > minor))) {
+ aDeviceInfo[i].bSelected = false;
+ }
+ }
+}
+
+/*
+ * Deselects device which don't support the given extension name
+ */
+void
+ALDeviceList::FilterDevicesExtension(unsigned short ext)
+{
+ for (unsigned int i = 0; i < nNumOfDevices; i++) {
+ if (!IsExtensionSupported(i, ext))
+ aDeviceInfo[i].bSelected = false;
+ }
+}
+
+/*
+ * Resets all filtering, such that all devices are in the list
+ */
+void ALDeviceList::ResetFilters()
+{
+ for (unsigned int i = 0; i < GetNumDevices(); i++) {
+ aDeviceInfo[i].bSelected = true;
+ }
+ filterIndex = 0;
+}
+
+/*
+ * Gets index of first filtered device
+ */
+int ALDeviceList::GetFirstFilteredDevice()
+{
+ unsigned int i;
+
+ for (i = 0; i < GetNumDevices(); i++) {
+ if (aDeviceInfo[i].bSelected == true) {
+ break;
+ }
+ }
+ filterIndex = i + 1;
+ return i;
+}
+
+/*
+ * Gets index of next filtered device
+ */
+int ALDeviceList::GetNextFilteredDevice()
+{
+ unsigned int i;
+
+ for (i = filterIndex; i < GetNumDevices(); i++) {
+ if (aDeviceInfo[i].bSelected == true) {
+ break;
+ }
+ }
+ filterIndex = i + 1;
+ return i;
+}
+
+/*
+ * Internal function to detemine max number of Sources that can be generated
+ */
+unsigned int ALDeviceList::GetMaxNumSources()
+{
+ ALuint uiSources[256];
+ unsigned int iSourceCount = 0;
+
+ // Clear AL Error Code
+ alGetError();
+
+ // Generate up to 256 Sources, checking for any errors
+ for (iSourceCount = 0; iSourceCount < 256; iSourceCount++)
+ {
+ alGenSources(1, &uiSources[iSourceCount]);
+ if (alGetError() != AL_NO_ERROR)
+ break;
+ }
+
+ // Release the Sources
+ alDeleteSources(iSourceCount, uiSources);
+ if (alGetError() != AL_NO_ERROR)
+ {
+ for (unsigned int i = 0; i < 256; i++)
+ {
+ alDeleteSources(1, &uiSources[i]);
+ }
+ }
+
+ return iSourceCount;
+}
+#endif
diff --git a/src/audio/oal/aldlist.h b/src/audio/oal/aldlist.h
new file mode 100644
index 0000000..417bd31
--- /dev/null
+++ b/src/audio/oal/aldlist.h
@@ -0,0 +1,69 @@
+#ifndef ALDEVICELIST_H
+#define ALDEVICELIST_H
+
+#include "oal_utils.h"
+
+#ifdef AUDIO_OAL
+#pragma warning(disable: 4786) //disable warning "identifier was truncated to '255' characters in the browser information"
+
+enum
+{
+ ADEXT_EXT_CAPTURE = (1 << 0),
+ ADEXT_EXT_EFX = (1 << 1),
+ ADEXT_EXT_OFFSET = (1 << 2),
+ ADEXT_EXT_LINEAR_DISTANCE = (1 << 3),
+ ADEXT_EXT_EXPONENT_DISTANCE = (1 << 4),
+ ADEXT_EAX2 = (1 << 5),
+ ADEXT_EAX3 = (1 << 6),
+ ADEXT_EAX4 = (1 << 7),
+ ADEXT_EAX5 = (1 << 8),
+ ADEXT_EAX_RAM = (1 << 9),
+};
+
+struct ALDEVICEINFO {
+ const char *strDeviceName;
+ int iMajorVersion;
+ int iMinorVersion;
+ unsigned int uiSourceCount;
+ unsigned short Extensions;
+ bool bSelected;
+
+ ALDEVICEINFO() : iMajorVersion(0), iMinorVersion(0), uiSourceCount(0), bSelected(false)
+ {
+ strDeviceName = NULL;
+ Extensions = 0;
+ }
+};
+
+typedef ALDEVICEINFO *LPALDEVICEINFO;
+
+class ALDeviceList
+{
+private:
+ ALDEVICEINFO aDeviceInfo[64];
+ unsigned int nNumOfDevices;
+ int defaultDeviceIndex;
+ int filterIndex;
+
+public:
+ ALDeviceList ();
+ ~ALDeviceList ();
+ unsigned int GetNumDevices();
+ const char *GetDeviceName(unsigned int index);
+ void GetDeviceVersion(unsigned int index, int *major, int *minor);
+ unsigned int GetMaxNumSources(unsigned int index);
+ bool IsExtensionSupported(int index, unsigned short ext);
+ int GetDefaultDevice();
+ void FilterDevicesMinVer(int major, int minor);
+ void FilterDevicesMaxVer(int major, int minor);
+ void FilterDevicesExtension(unsigned short ext);
+ void ResetFilters();
+ int GetFirstFilteredDevice();
+ int GetNextFilteredDevice();
+
+private:
+ unsigned int GetMaxNumSources();
+};
+#endif
+
+#endif // ALDEVICELIST_H
diff --git a/src/audio/oal/channel.cpp b/src/audio/oal/channel.cpp
new file mode 100644
index 0000000..d1fd0ae
--- /dev/null
+++ b/src/audio/oal/channel.cpp
@@ -0,0 +1,278 @@
+#include "common.h"
+
+#ifdef AUDIO_OAL
+#include "channel.h"
+#include "sampman.h"
+
+#ifndef _WIN32
+#include <float.h>
+#endif
+
+extern bool IsFXSupported();
+
+ALuint alSources[MAXCHANNELS+MAX2DCHANNELS];
+ALuint alFilters[MAXCHANNELS+MAX2DCHANNELS];
+ALuint alBuffers[MAXCHANNELS+MAX2DCHANNELS];
+bool bChannelsCreated = false;
+
+int32 CChannel::channelsThatNeedService = 0;
+
+void
+CChannel::InitChannels()
+{
+ alGenSources(MAXCHANNELS+MAX2DCHANNELS, alSources);
+ alGenBuffers(MAXCHANNELS+MAX2DCHANNELS, alBuffers);
+ if (IsFXSupported())
+ alGenFilters(MAXCHANNELS + MAX2DCHANNELS, alFilters);
+ bChannelsCreated = true;
+}
+
+void
+CChannel::DestroyChannels()
+{
+ if (bChannelsCreated)
+ {
+ alDeleteSources(MAXCHANNELS + MAX2DCHANNELS, alSources);
+ memset(alSources, 0, sizeof(alSources));
+ alDeleteBuffers(MAXCHANNELS + MAX2DCHANNELS, alBuffers);
+ memset(alBuffers, 0, sizeof(alBuffers));
+ if (IsFXSupported())
+ {
+ alDeleteFilters(MAXCHANNELS + MAX2DCHANNELS, alFilters);
+ memset(alFilters, 0, sizeof(alFilters));
+ }
+ bChannelsCreated = false;
+ }
+}
+
+
+CChannel::CChannel()
+{
+ Data = nil;
+ DataSize = 0;
+ SetDefault();
+}
+
+void CChannel::SetDefault()
+{
+ Pitch = 1.0f;
+ Gain = 1.0f;
+ Mix = 0.0f;
+
+ Position[0] = 0.0f; Position[1] = 0.0f; Position[2] = 0.0f;
+ Distances[0] = 0.0f; Distances[1] = FLT_MAX;
+
+ LoopCount = 1;
+ LastProcessedOffset = UINT32_MAX;
+ LoopPoints[0] = 0; LoopPoints[1] = -1;
+
+ Frequency = MAX_FREQ;
+}
+
+void CChannel::Reset()
+{
+ // Here is safe because ctor don't call this
+ if (LoopCount > 1)
+ channelsThatNeedService--;
+
+ ClearBuffer();
+ SetDefault();
+}
+
+void CChannel::Init(uint32 _id, bool Is2D)
+{
+ id = _id;
+ if ( HasSource() )
+ {
+ alSourcei(alSources[id], AL_SOURCE_RELATIVE, AL_TRUE);
+ if ( IsFXSupported() )
+ alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL);
+
+ if ( Is2D )
+ {
+ alSource3f(alSources[id], AL_POSITION, 0.0f, 0.0f, 0.0f);
+ alSourcef(alSources[id], AL_GAIN, 1.0f);
+ }
+ }
+}
+
+void CChannel::Term()
+{
+ Stop();
+ if ( HasSource() )
+ {
+ if ( IsFXSupported() )
+ {
+ alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL);
+ }
+ }
+}
+
+void CChannel::Start()
+{
+ if ( !HasSource() ) return;
+ if ( !Data ) return;
+
+ alBufferData(alBuffers[id], AL_FORMAT_MONO16, Data, DataSize, Frequency);
+ if ( LoopPoints[0] != 0 && LoopPoints[0] != -1 )
+ alBufferiv(alBuffers[id], AL_LOOP_POINTS_SOFT, LoopPoints);
+ alSourcei(alSources[id], AL_BUFFER, alBuffers[id]);
+ alSourcePlay(alSources[id]);
+}
+
+void CChannel::Stop()
+{
+ if ( HasSource() )
+ alSourceStop(alSources[id]);
+
+ Reset();
+}
+
+bool CChannel::HasSource()
+{
+ return alSources[id] != AL_NONE;
+}
+
+bool CChannel::IsUsed()
+{
+ if ( HasSource() )
+ {
+ ALint sourceState;
+ alGetSourcei(alSources[id], AL_SOURCE_STATE, &sourceState);
+ return sourceState == AL_PLAYING;
+ }
+ return false;
+}
+
+void CChannel::SetPitch(float pitch)
+{
+ if ( !HasSource() ) return;
+ alSourcef(alSources[id], AL_PITCH, pitch);
+}
+
+void CChannel::SetGain(float gain)
+{
+ if ( !HasSource() ) return;
+ alSourcef(alSources[id], AL_GAIN, gain);
+}
+
+void CChannel::SetVolume(int32 vol)
+{
+ SetGain(ALfloat(vol) / MAX_VOLUME);
+}
+
+void CChannel::SetSampleData(void *_data, size_t _DataSize, int32 freq)
+{
+ Data = _data;
+ DataSize = _DataSize;
+ Frequency = freq;
+}
+
+void CChannel::SetCurrentFreq(uint32 freq)
+{
+ SetPitch(ALfloat(freq) / Frequency);
+}
+
+void CChannel::SetLoopCount(int32 count)
+{
+ if ( !HasSource() ) return;
+
+ // 0: loop indefinitely, 1: play one time, 2: play two times etc...
+ // only > 1 needs manual processing
+
+ if (LoopCount > 1 && count < 2)
+ channelsThatNeedService--;
+ else if (LoopCount < 2 && count > 1)
+ channelsThatNeedService++;
+
+ alSourcei(alSources[id], AL_LOOPING, count == 1 ? AL_FALSE : AL_TRUE);
+ LoopCount = count;
+}
+
+bool CChannel::Update()
+{
+ if (!HasSource()) return false;
+ if (LoopCount < 2) return false;
+
+ ALint state;
+ alGetSourcei(alSources[id], AL_SOURCE_STATE, &state);
+ if (state == AL_STOPPED) {
+ debug("Looping channels(%d in this case) shouldn't report AL_STOPPED, but nvm\n", id);
+ SetLoopCount(1);
+ return true;
+ }
+
+ assert(channelsThatNeedService > 0 && "Ref counting is broken");
+
+ ALint offset;
+ alGetSourcei(alSources[id], AL_SAMPLE_OFFSET, &offset);
+
+ // Rewound
+ if (offset < LastProcessedOffset) {
+ LoopCount--;
+ if (LoopCount == 1) {
+ // Playing last tune...
+ channelsThatNeedService--;
+ alSourcei(alSources[id], AL_LOOPING, AL_FALSE);
+ }
+ }
+ LastProcessedOffset = offset;
+ return true;
+}
+
+void CChannel::SetLoopPoints(ALint start, ALint end)
+{
+ LoopPoints[0] = start;
+ LoopPoints[1] = end;
+}
+
+void CChannel::SetPosition(float x, float y, float z)
+{
+ if ( !HasSource() ) return;
+ alSource3f(alSources[id], AL_POSITION, x, y, z);
+}
+
+void CChannel::SetDistances(float max, float min)
+{
+ if ( !HasSource() ) return;
+ alSourcef (alSources[id], AL_MAX_DISTANCE, max);
+ alSourcef (alSources[id], AL_REFERENCE_DISTANCE, min);
+ alSourcef (alSources[id], AL_MAX_GAIN, 1.0f);
+ alSourcef (alSources[id], AL_ROLLOFF_FACTOR, 1.0f);
+}
+
+void CChannel::SetPan(int32 pan)
+{
+ SetPosition((pan-63)/64.0f, 0.0f, Sqrt(1.0f-SQR((pan-63)/64.0f)));
+}
+
+void CChannel::ClearBuffer()
+{
+ if ( !HasSource() ) return;
+ alSourcei(alSources[id], AL_LOOPING, AL_FALSE);
+ alSourcei(alSources[id], AL_BUFFER, AL_NONE);
+ Data = nil;
+ DataSize = 0;
+}
+
+void CChannel::SetReverbMix(ALuint slot, float mix)
+{
+ if ( !IsFXSupported() ) return;
+ if ( !HasSource() ) return;
+ if ( alFilters[id] == AL_FILTER_NULL ) return;
+
+ Mix = mix;
+ EAX3_SetReverbMix(alFilters[id], mix);
+ alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, slot, 0, alFilters[id]);
+}
+
+void CChannel::UpdateReverb(ALuint slot)
+{
+ if ( !IsFXSupported() ) return;
+ if ( !HasSource() ) return;
+ if ( alFilters[id] == AL_FILTER_NULL ) return;
+ EAX3_SetReverbMix(alFilters[id], Mix);
+ alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, slot, 0, alFilters[id]);
+}
+
+#endif
diff --git a/src/audio/oal/channel.h b/src/audio/oal/channel.h
new file mode 100644
index 0000000..b081be2
--- /dev/null
+++ b/src/audio/oal/channel.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#ifdef AUDIO_OAL
+#include "oal/oal_utils.h"
+#include <AL/al.h>
+#include <AL/alext.h>
+#include <AL/efx.h>
+
+
+class CChannel
+{
+ uint32 id;
+ float Pitch, Gain;
+ float Mix;
+ void *Data;
+ size_t DataSize;
+ int32 Frequency;
+ float Position[3];
+ float Distances[2];
+ int32 LoopCount;
+ ALint LoopPoints[2];
+ ALint LastProcessedOffset;
+public:
+ static int32 channelsThatNeedService;
+
+ static void InitChannels();
+ static void DestroyChannels();
+
+ CChannel();
+ void SetDefault();
+ void Reset();
+ void Init(uint32 _id, bool Is2D = false);
+ void Term();
+ void Start();
+ void Stop();
+ bool HasSource();
+ bool IsUsed();
+ void SetPitch(float pitch);
+ void SetGain(float gain);
+ void SetVolume(int32 vol);
+ void SetSampleData(void *_data, size_t _DataSize, int32 freq);
+ void SetCurrentFreq(uint32 freq);
+ void SetLoopCount(int32 count);
+ void SetLoopPoints(ALint start, ALint end);
+ void SetPosition(float x, float y, float z);
+ void SetDistances(float max, float min);
+ void SetPan(int32 pan);
+ void ClearBuffer();
+ void SetReverbMix(ALuint slot, float mix);
+ void UpdateReverb(ALuint slot);
+ bool Update();
+};
+
+#endif \ No newline at end of file
diff --git a/src/audio/oal/oal_utils.cpp b/src/audio/oal/oal_utils.cpp
new file mode 100644
index 0000000..e4cb0b7
--- /dev/null
+++ b/src/audio/oal/oal_utils.cpp
@@ -0,0 +1,181 @@
+#include "common.h"
+#include "oal_utils.h"
+
+#ifdef AUDIO_OAL
+
+/*
+ * When linking to a static openal-soft library,
+ * the extension function inside the openal library conflict with the variables here.
+ * Therefore declare these re3 owned symbols in a private namespace.
+ */
+
+namespace re3_openal {
+
+LPALGENEFFECTS alGenEffects;
+LPALDELETEEFFECTS alDeleteEffects;
+LPALISEFFECT alIsEffect;
+LPALEFFECTI alEffecti;
+LPALEFFECTIV alEffectiv;
+LPALEFFECTF alEffectf;
+LPALEFFECTFV alEffectfv;
+LPALGETEFFECTI alGetEffecti;
+LPALGETEFFECTIV alGetEffectiv;
+LPALGETEFFECTF alGetEffectf;
+LPALGETEFFECTFV alGetEffectfv;
+LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
+LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
+LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
+LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
+LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
+LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
+LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
+LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
+LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
+LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
+LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
+LPALGENFILTERS alGenFilters;
+LPALDELETEFILTERS alDeleteFilters;
+LPALISFILTER alIsFilter;
+LPALFILTERI alFilteri;
+LPALFILTERIV alFilteriv;
+LPALFILTERF alFilterf;
+LPALFILTERFV alFilterfv;
+LPALGETFILTERI alGetFilteri;
+LPALGETFILTERIV alGetFilteriv;
+LPALGETFILTERF alGetFilterf;
+LPALGETFILTERFV alGetFilterfv;
+
+}
+
+using namespace re3_openal;
+
+void EFXInit()
+{
+ /* Define a macro to help load the function pointers. */
+#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x))
+ LOAD_PROC(LPALGENEFFECTS, alGenEffects);
+ LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects);
+ LOAD_PROC(LPALISEFFECT, alIsEffect);
+ LOAD_PROC(LPALEFFECTI, alEffecti);
+ LOAD_PROC(LPALEFFECTIV, alEffectiv);
+ LOAD_PROC(LPALEFFECTF, alEffectf);
+ LOAD_PROC(LPALEFFECTFV, alEffectfv);
+ LOAD_PROC(LPALGETEFFECTI, alGetEffecti);
+ LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv);
+ LOAD_PROC(LPALGETEFFECTF, alGetEffectf);
+ LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv);
+
+ LOAD_PROC(LPALGENFILTERS, alGenFilters);
+ LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters);
+ LOAD_PROC(LPALISFILTER, alIsFilter);
+ LOAD_PROC(LPALFILTERI, alFilteri);
+ LOAD_PROC(LPALFILTERIV, alFilteriv);
+ LOAD_PROC(LPALFILTERF, alFilterf);
+ LOAD_PROC(LPALFILTERFV, alFilterfv);
+ LOAD_PROC(LPALGETFILTERI, alGetFilteri);
+ LOAD_PROC(LPALGETFILTERIV, alGetFilteriv);
+ LOAD_PROC(LPALGETFILTERF, alGetFilterf);
+ LOAD_PROC(LPALGETFILTERFV, alGetFilterfv);
+
+ LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots);
+ LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots);
+ LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv);
+#undef LOAD_PROC
+}
+
+void SetEffectsLevel(ALuint uiFilter, float level)
+{
+ alFilteri(uiFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
+ alFilterf(uiFilter, AL_LOWPASS_GAIN, 1.0f);
+ alFilterf(uiFilter, AL_LOWPASS_GAINHF, level);
+}
+
+static inline float gain_to_mB(float gain)
+{
+ return (gain > 1e-5f) ? (float)(log10f(gain) * 2000.0f) : -10000l;
+}
+
+static inline float mB_to_gain(float millibels)
+{
+ return (millibels > -10000.0f) ? powf(10.0f, millibels/2000.0f) : 0.0f;
+}
+
+static inline float clampF(float val, float minval, float maxval)
+{
+ if(val >= maxval) return maxval;
+ if(val <= minval) return minval;
+ return val;
+}
+
+void EAX3_Set(ALuint effect, const EAXLISTENERPROPERTIES *props)
+{
+ alEffecti (effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
+ alEffectf (effect, AL_EAXREVERB_DENSITY, clampF(powf(props->flEnvironmentSize, 3.0f) / 16.0f, 0.0f, 1.0f));
+ alEffectf (effect, AL_EAXREVERB_DIFFUSION, props->flEnvironmentDiffusion);
+ alEffectf (effect, AL_EAXREVERB_GAIN, mB_to_gain((float)props->lRoom));
+ alEffectf (effect, AL_EAXREVERB_GAINHF, mB_to_gain((float)props->lRoomHF));
+ alEffectf (effect, AL_EAXREVERB_GAINLF, mB_to_gain((float)props->lRoomLF));
+ alEffectf (effect, AL_EAXREVERB_DECAY_TIME, props->flDecayTime);
+ alEffectf (effect, AL_EAXREVERB_DECAY_HFRATIO, props->flDecayHFRatio);
+ alEffectf (effect, AL_EAXREVERB_DECAY_LFRATIO, props->flDecayLFRatio);
+ alEffectf (effect, AL_EAXREVERB_REFLECTIONS_GAIN, clampF(mB_to_gain((float)props->lReflections), AL_EAXREVERB_MIN_REFLECTIONS_GAIN, AL_EAXREVERB_MAX_REFLECTIONS_GAIN));
+ alEffectf (effect, AL_EAXREVERB_REFLECTIONS_DELAY, props->flReflectionsDelay);
+ alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, &props->vReflectionsPan.x);
+ alEffectf (effect, AL_EAXREVERB_LATE_REVERB_GAIN, clampF(mB_to_gain((float)props->lReverb), AL_EAXREVERB_MIN_LATE_REVERB_GAIN, AL_EAXREVERB_MAX_LATE_REVERB_GAIN));
+ alEffectf (effect, AL_EAXREVERB_LATE_REVERB_DELAY, props->flReverbDelay);
+ alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, &props->vReverbPan.x);
+ alEffectf (effect, AL_EAXREVERB_ECHO_TIME, props->flEchoTime);
+ alEffectf (effect, AL_EAXREVERB_ECHO_DEPTH, props->flEchoDepth);
+ alEffectf (effect, AL_EAXREVERB_MODULATION_TIME, props->flModulationTime);
+ alEffectf (effect, AL_EAXREVERB_MODULATION_DEPTH, props->flModulationDepth);
+ alEffectf (effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, clampF(mB_to_gain(props->flAirAbsorptionHF), AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF));
+ alEffectf (effect, AL_EAXREVERB_HFREFERENCE, props->flHFReference);
+ alEffectf (effect, AL_EAXREVERB_LFREFERENCE, props->flLFReference);
+ alEffectf (effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, props->flRoomRolloffFactor);
+ alEffecti (effect, AL_EAXREVERB_DECAY_HFLIMIT, (props->ulFlags&EAXLISTENERFLAGS_DECAYHFLIMIT) ? AL_TRUE : AL_FALSE);
+}
+
+void EFX_Set(ALuint effect, const EAXLISTENERPROPERTIES *props)
+{
+ alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
+
+ alEffectf(effect, AL_REVERB_DENSITY, clampF(powf(props->flEnvironmentSize, 3.0f) / 16.0f, 0.0f, 1.0f));
+ alEffectf(effect, AL_REVERB_DIFFUSION, props->flEnvironmentDiffusion);
+ alEffectf(effect, AL_REVERB_GAIN, mB_to_gain((float)props->lRoom));
+ alEffectf(effect, AL_REVERB_GAINHF, mB_to_gain((float)props->lRoomHF));
+ alEffectf(effect, AL_REVERB_DECAY_TIME, props->flDecayTime);
+ alEffectf(effect, AL_REVERB_DECAY_HFRATIO, props->flDecayHFRatio);
+ alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, clampF(mB_to_gain((float)props->lReflections), AL_EAXREVERB_MIN_REFLECTIONS_GAIN, AL_EAXREVERB_MAX_REFLECTIONS_GAIN));
+ alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, props->flReflectionsDelay);
+ alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, clampF(mB_to_gain((float)props->lReverb), AL_EAXREVERB_MIN_LATE_REVERB_GAIN, AL_EAXREVERB_MAX_LATE_REVERB_GAIN));
+ alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, props->flReverbDelay);
+ alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, clampF(mB_to_gain(props->flAirAbsorptionHF), AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF));
+ alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, props->flRoomRolloffFactor);
+ alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, (props->ulFlags&EAXLISTENERFLAGS_DECAYHFLIMIT) ? AL_TRUE : AL_FALSE);
+}
+
+void EAX3_SetReverbMix(ALuint filter, float mix)
+{
+ //long vol=(long)linear_to_dB(mix);
+ //DSPROPERTY_EAXBUFFER_ROOMHF,
+ //DSPROPERTY_EAXBUFFER_ROOM,
+ //DSPROPERTY_EAXBUFFER_REVERBMIX,
+
+ long mbvol = gain_to_mB(mix);
+ float mb = mbvol;
+ float mbhf = mbvol;
+
+ alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
+ alFilterf(filter, AL_LOWPASS_GAIN, mB_to_gain(Min(mb, 0.0f)));
+ alFilterf(filter, AL_LOWPASS_GAINHF, mB_to_gain(mbhf));
+}
+
+#endif \ No newline at end of file
diff --git a/src/audio/oal/oal_utils.h b/src/audio/oal/oal_utils.h
new file mode 100644
index 0000000..f0fa090
--- /dev/null
+++ b/src/audio/oal/oal_utils.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#ifdef AUDIO_OAL
+#include "eax.h"
+#include "AL/efx.h"
+
+
+void EFXInit();
+void EAX3_Set(ALuint effect, const EAXLISTENERPROPERTIES *props);
+void EFX_Set(ALuint effect, const EAXLISTENERPROPERTIES *props);
+void EAX3_SetReverbMix(ALuint filter, float mix);
+void SetEffectsLevel(ALuint uiFilter, float level);
+
+namespace re3_openal {
+
+extern LPALGENEFFECTS alGenEffects;
+extern LPALDELETEEFFECTS alDeleteEffects;
+extern LPALISEFFECT alIsEffect;
+extern LPALEFFECTI alEffecti;
+extern LPALEFFECTIV alEffectiv;
+extern LPALEFFECTF alEffectf;
+extern LPALEFFECTFV alEffectfv;
+extern LPALGETEFFECTI alGetEffecti;
+extern LPALGETEFFECTIV alGetEffectiv;
+extern LPALGETEFFECTF alGetEffectf;
+extern LPALGETEFFECTFV alGetEffectfv;
+extern LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
+extern LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
+extern LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
+extern LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
+extern LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
+extern LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
+extern LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
+extern LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
+extern LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
+extern LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
+extern LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
+extern LPALGENFILTERS alGenFilters;
+extern LPALDELETEFILTERS alDeleteFilters;
+extern LPALISFILTER alIsFilter;
+extern LPALFILTERI alFilteri;
+extern LPALFILTERIV alFilteriv;
+extern LPALFILTERF alFilterf;
+extern LPALFILTERFV alFilterfv;
+extern LPALGETFILTERI alGetFilteri;
+extern LPALGETFILTERIV alGetFilteriv;
+extern LPALGETFILTERF alGetFilterf;
+extern LPALGETFILTERFV alGetFilterfv;
+
+}
+
+using namespace re3_openal;
+
+#endif
diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp
new file mode 100644
index 0000000..ed73e94
--- /dev/null
+++ b/src/audio/oal/stream.cpp
@@ -0,0 +1,1413 @@
+#include "common.h"
+
+#ifdef AUDIO_OAL
+#include "stream.h"
+#include "sampman.h"
+
+#if defined _MSC_VER && !defined CMAKE_NO_AUTOLINK
+#ifdef AUDIO_OAL_USE_SNDFILE
+#pragma comment( lib, "libsndfile-1.lib" )
+#endif
+#ifdef AUDIO_OAL_USE_MPG123
+#pragma comment( lib, "libmpg123-0.lib" )
+#endif
+#endif
+#ifdef AUDIO_OAL_USE_SNDFILE
+#include <sndfile.h>
+#endif
+#ifdef AUDIO_OAL_USE_MPG123
+#include <mpg123.h>
+#endif
+#ifdef AUDIO_OAL_USE_OPUS
+#include <opusfile.h>
+#endif
+
+#ifndef _WIN32
+#include "crossplatform.h"
+#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;
+
+class CImaADPCMDecoder
+{
+ const uint16 StepTable[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
+ 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
+ 32767
+ };
+
+ int16 Sample, StepIndex;
+
+public:
+ CImaADPCMDecoder()
+ {
+ Init(0, 0);
+ }
+
+ void Init(int16 _Sample, int16 _StepIndex)
+ {
+ Sample = _Sample;
+ StepIndex = _StepIndex;
+ }
+
+ void Decode(uint8 *inbuf, int16 *_outbuf, size_t size)
+ {
+ int16* outbuf = _outbuf;
+ for (size_t i = 0; i < size; i++)
+ {
+ *(outbuf++) = DecodeSample(inbuf[i] & 0xF);
+ *(outbuf++) = DecodeSample(inbuf[i] >> 4);
+ }
+ }
+
+ int16 DecodeSample(uint8 adpcm)
+ {
+ uint16 step = StepTable[StepIndex];
+
+ if (adpcm & 4)
+ StepIndex += ((adpcm & 3) + 1) * 2;
+ else
+ StepIndex--;
+
+ StepIndex = clamp(StepIndex, 0, 88);
+
+ int delta = step >> 3;
+ if (adpcm & 1) delta += step >> 2;
+ if (adpcm & 2) delta += step >> 1;
+ if (adpcm & 4) delta += step;
+ if (adpcm & 8) delta = -delta;
+
+ int newSample = Sample + delta;
+ Sample = clamp(newSample, -32768, 32767);
+ return Sample;
+ }
+};
+
+class CWavFile : public IDecoder
+{
+ enum
+ {
+ WAVEFMT_PCM = 1,
+ WAVEFMT_IMA_ADPCM = 0x11,
+ WAVEFMT_XBOX_ADPCM = 0x69,
+ };
+
+ struct tDataHeader
+ {
+ uint32 ID;
+ uint32 Size;
+ };
+
+ struct tFormatHeader
+ {
+ uint16 AudioFormat;
+ uint16 NumChannels;
+ uint32 SampleRate;
+ uint32 ByteRate;
+ uint16 BlockAlign;
+ uint16 BitsPerSample;
+ uint16 extra[2]; // adpcm only
+
+ tFormatHeader() { memset(this, 0, sizeof(*this)); }
+ };
+
+ FILE *m_pFile;
+ bool m_bIsOpen;
+
+ tFormatHeader m_FormatHeader;
+
+ uint32 m_DataStartOffset; // TODO: 64 bit?
+ uint32 m_nSampleCount;
+ uint32 m_nSamplesPerBlock;
+
+ // ADPCM things
+ uint8 *m_pAdpcmBuffer;
+ int16 **m_ppPcmBuffers;
+ CImaADPCMDecoder *m_pAdpcmDecoders;
+
+ void Close()
+ {
+ if (m_pFile) {
+ fclose(m_pFile);
+ m_pFile = nil;
+ }
+ delete[] m_pAdpcmBuffer;
+ delete[] m_ppPcmBuffers;
+ delete[] m_pAdpcmDecoders;
+ }
+
+ uint32 GetCurrentSample() const
+ {
+ // TODO: 64 bit?
+ uint32 FilePos = ftell(m_pFile);
+ if (FilePos <= m_DataStartOffset)
+ return 0;
+ return (FilePos - m_DataStartOffset) / m_FormatHeader.BlockAlign * m_nSamplesPerBlock;
+ }
+
+public:
+ CWavFile(const char* path) : m_bIsOpen(false), m_DataStartOffset(0), m_nSampleCount(0), m_nSamplesPerBlock(0), m_pAdpcmBuffer(nil), m_ppPcmBuffers(nil), m_pAdpcmDecoders(nil)
+ {
+ m_pFile = fopen(path, "rb");
+ if (!m_pFile) return;
+
+#define CLOSE_ON_ERROR(op)\
+ if (op) { \
+ Close(); \
+ return; \
+ }
+
+ tDataHeader DataHeader;
+
+ CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(DataHeader.ID != 'FFIR');
+
+ // TODO? validate filesizes
+
+ int WAVE;
+ CLOSE_ON_ERROR(fread(&WAVE, 4, 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(WAVE != 'EVAW')
+ CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(DataHeader.ID != ' tmf');
+
+ CLOSE_ON_ERROR(fread(&m_FormatHeader, Min(DataHeader.Size, sizeof(tFormatHeader)), 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(DataHeader.Size > sizeof(tFormatHeader));
+
+ switch (m_FormatHeader.AudioFormat)
+ {
+ case WAVEFMT_XBOX_ADPCM:
+ m_FormatHeader.AudioFormat = WAVEFMT_IMA_ADPCM;
+ case WAVEFMT_IMA_ADPCM:
+ m_nSamplesPerBlock = (m_FormatHeader.BlockAlign / m_FormatHeader.NumChannels - 4) * 2 + 1;
+ m_pAdpcmBuffer = new uint8[m_FormatHeader.BlockAlign];
+ m_ppPcmBuffers = new int16*[m_FormatHeader.NumChannels];
+ m_pAdpcmDecoders = new CImaADPCMDecoder[m_FormatHeader.NumChannels];
+ break;
+ case WAVEFMT_PCM:
+ m_nSamplesPerBlock = 1;
+ if (m_FormatHeader.BitsPerSample != 16)
+ {
+ debug("Unsupported PCM (%d bits), only signed 16-bit is supported (%s)\n", m_FormatHeader.BitsPerSample, path);
+ Close();
+ return;
+ }
+ break;
+ default:
+ debug("Unsupported wav format 0x%x (%s)\n", m_FormatHeader.AudioFormat, path);
+ Close();
+ return;
+ }
+
+ while (true) {
+ CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0);
+ if (DataHeader.ID == 'atad')
+ break;
+ fseek(m_pFile, DataHeader.Size, SEEK_CUR);
+ // TODO? validate data size
+ // maybe check if there no extreme custom headers that might break this
+ }
+
+ m_DataStartOffset = ftell(m_pFile);
+ m_nSampleCount = DataHeader.Size / m_FormatHeader.BlockAlign * m_nSamplesPerBlock;
+
+ m_bIsOpen = true;
+#undef CLOSE_ON_ERROR
+ }
+
+ ~CWavFile()
+ {
+ Close();
+ }
+
+ bool IsOpened()
+ {
+ return m_bIsOpen;
+ }
+
+ uint32 GetSampleSize()
+ {
+ return sizeof(uint16);
+ }
+
+ uint32 GetSampleCount()
+ {
+ return m_nSampleCount;
+ }
+
+ uint32 GetSampleRate()
+ {
+ return m_FormatHeader.SampleRate;
+ }
+
+ uint32 GetChannels()
+ {
+ return m_FormatHeader.NumChannels;
+ }
+
+ void Seek(uint32 milliseconds)
+ {
+ if (!IsOpened()) return;
+ fseek(m_pFile, m_DataStartOffset + ms2samples(milliseconds) / m_nSamplesPerBlock * m_FormatHeader.BlockAlign, SEEK_SET);
+ }
+
+ uint32 Tell()
+ {
+ if (!IsOpened()) return 0;
+ return samples2ms(GetCurrentSample());
+ }
+
+#define SAMPLES_IN_LINE (8)
+
+ uint32 Decode(void* buffer)
+ {
+ if (!IsOpened()) return 0;
+
+ if (m_FormatHeader.AudioFormat == WAVEFMT_PCM)
+ {
+ // just read the file and sort the samples
+ uint32 size = fread(buffer, 1, GetBufferSize(), m_pFile);
+ if (m_FormatHeader.NumChannels == 2)
+ SortStereoBuffer.SortStereo(buffer, size);
+ return size;
+ }
+ else if (m_FormatHeader.AudioFormat == WAVEFMT_IMA_ADPCM)
+ {
+ // trim the buffer size if we're at the end of our file
+ uint32 nMaxSamples = GetBufferSamples() / m_FormatHeader.NumChannels;
+ uint32 nSamplesLeft = m_nSampleCount - GetCurrentSample();
+ nMaxSamples = Min(nMaxSamples, nSamplesLeft);
+
+ // align sample count to our block
+ nMaxSamples = nMaxSamples / m_nSamplesPerBlock * m_nSamplesPerBlock;
+
+ // count the size of output buffer
+ uint32 OutBufSizePerChannel = nMaxSamples * GetSampleSize();
+ uint32 OutBufSize = OutBufSizePerChannel * m_FormatHeader.NumChannels;
+
+ // calculate the pointers to individual channel buffers
+ for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++)
+ m_ppPcmBuffers[i] = (int16*)((int8*)buffer + OutBufSizePerChannel * i);
+
+ uint32 samplesRead = 0;
+ while (samplesRead < nMaxSamples)
+ {
+ // read the file
+ uint8 *pAdpcmBuf = m_pAdpcmBuffer;
+ if (fread(m_pAdpcmBuffer, 1, m_FormatHeader.BlockAlign, m_pFile) == 0)
+ return 0;
+
+ // get the first sample in adpcm block and initialise the decoder(s)
+ for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++)
+ {
+ int16 Sample = *(int16*)pAdpcmBuf;
+ pAdpcmBuf += sizeof(int16);
+ int16 Step = *(int16*)pAdpcmBuf;
+ pAdpcmBuf += sizeof(int16);
+ m_pAdpcmDecoders[i].Init(Sample, Step);
+ *(m_ppPcmBuffers[i]) = Sample;
+ m_ppPcmBuffers[i]++;
+ }
+ samplesRead++;
+
+ // decode the rest of the block
+ for (uint32 s = 1; s < m_nSamplesPerBlock; s += SAMPLES_IN_LINE)
+ {
+ for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++)
+ {
+ m_pAdpcmDecoders[i].Decode(pAdpcmBuf, m_ppPcmBuffers[i], SAMPLES_IN_LINE / 2);
+ pAdpcmBuf += SAMPLES_IN_LINE / 2;
+ m_ppPcmBuffers[i] += SAMPLES_IN_LINE;
+ }
+ samplesRead += SAMPLES_IN_LINE;
+ }
+ }
+ return OutBufSize;
+ }
+ return 0;
+ }
+};
+
+#ifdef AUDIO_OAL_USE_SNDFILE
+class CSndFile : public IDecoder
+{
+ SNDFILE *m_pfSound;
+ SF_INFO m_soundInfo;
+public:
+ CSndFile(const char *path) :
+ m_pfSound(nil)
+ {
+ memset(&m_soundInfo, 0, sizeof(m_soundInfo));
+ m_pfSound = sf_open(path, SFM_READ, &m_soundInfo);
+ }
+
+ ~CSndFile()
+ {
+ if ( m_pfSound )
+ {
+ sf_close(m_pfSound);
+ m_pfSound = nil;
+ }
+ }
+
+ bool IsOpened()
+ {
+ return m_pfSound != nil;
+ }
+
+ uint32 GetSampleSize()
+ {
+ return sizeof(uint16);
+ }
+
+ uint32 GetSampleCount()
+ {
+ return m_soundInfo.frames;
+ }
+
+ uint32 GetSampleRate()
+ {
+ return m_soundInfo.samplerate;
+ }
+
+ uint32 GetChannels()
+ {
+ return m_soundInfo.channels;
+ }
+
+ void Seek(uint32 milliseconds)
+ {
+ if ( !IsOpened() ) return;
+ sf_seek(m_pfSound, ms2samples(milliseconds), SF_SEEK_SET);
+ }
+
+ uint32 Tell()
+ {
+ if ( !IsOpened() ) return 0;
+ return samples2ms(sf_seek(m_pfSound, 0, SF_SEEK_CUR));
+ }
+
+ uint32 Decode(void *buffer)
+ {
+ if ( !IsOpened() ) return 0;
+
+ size_t size = sf_read_short(m_pfSound, (short*)buffer, GetBufferSamples()) * GetSampleSize();
+ if (GetChannels()==2)
+ SortStereoBuffer.SortStereo(buffer, size);
+ return size;
+ }
+};
+#endif
+
+#ifdef AUDIO_OAL_USE_MPG123
+// fuzzy seek eliminates stutter when playing ADF but spams errors a lot (and breaks radio sometimes)
+//#define MP3_USE_FUZZY_SEEK
+
+class CMP3File : public IDecoder
+{
+protected:
+ mpg123_handle *m_pMH;
+ bool m_bOpened;
+ uint32 m_nRate;
+ uint32 m_nChannels;
+
+ CMP3File() :
+ m_pMH(nil),
+ m_bOpened(false),
+ m_nRate(0),
+ m_nChannels(0) {}
+public:
+ CMP3File(const char *path) :
+ m_pMH(nil),
+ m_bOpened(false),
+ m_nRate(0),
+ m_nChannels(0)
+ {
+ m_pMH = mpg123_new(nil, nil);
+ if ( m_pMH )
+ {
+#ifdef MP3_USE_FUZZY_SEEK
+ mpg123_param(m_pMH, MPG123_FLAGS, MPG123_FUZZY | MPG123_SEEKBUFFER | MPG123_GAPLESS | MPG123_QUIET, 0.0);
+#else
+ mpg123_param(m_pMH, MPG123_FLAGS, MPG123_SEEKBUFFER | MPG123_GAPLESS, 0.0);
+#endif
+ long rate = 0;
+ int channels = 0;
+ int encoding = 0;
+
+ m_bOpened = mpg123_open(m_pMH, path) == MPG123_OK
+ && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK;
+
+ m_nRate = rate;
+ m_nChannels = channels;
+
+ if ( IsOpened() )
+ {
+ mpg123_format_none(m_pMH);
+ mpg123_format(m_pMH, rate, channels, encoding);
+ }
+ }
+ }
+
+ ~CMP3File()
+ {
+ if ( m_pMH )
+ {
+ mpg123_close(m_pMH);
+ mpg123_delete(m_pMH);
+ m_pMH = nil;
+ }
+ }
+
+ bool IsOpened()
+ {
+ return m_bOpened;
+ }
+
+ uint32 GetSampleSize()
+ {
+ return sizeof(uint16);
+ }
+
+ uint32 GetSampleCount()
+ {
+ if ( !IsOpened() ) return 0;
+ return mpg123_length(m_pMH);
+ }
+
+ uint32 GetSampleRate()
+ {
+ return m_nRate;
+ }
+
+ uint32 GetChannels()
+ {
+ return m_nChannels;
+ }
+
+ void Seek(uint32 milliseconds)
+ {
+ if ( !IsOpened() ) return;
+ mpg123_seek(m_pMH, ms2samples(milliseconds), SEEK_SET);
+ }
+
+ uint32 Tell()
+ {
+ if ( !IsOpened() ) return 0;
+ return samples2ms(mpg123_tell(m_pMH));
+ }
+
+ uint32 Decode(void *buffer)
+ {
+ if ( !IsOpened() ) return 0;
+
+ size_t size;
+ int err = mpg123_read(m_pMH, (unsigned char *)buffer, GetBufferSize(), &size);
+#if defined(__LP64__) || defined(_WIN64)
+ assert("We can't handle audio files more then 2 GB yet :shrug:" && (size < UINT32_MAX));
+#endif
+ if (err != MPG123_OK && err != MPG123_DONE) return 0;
+ if (GetChannels() == 2)
+ SortStereoBuffer.SortStereo(buffer, size);
+ return (uint32)size;
+ }
+};
+
+class CADFFile : public CMP3File
+{
+ static ssize_t r_read(void* fh, void* buf, size_t size)
+ {
+ size_t bytesRead = fread(buf, 1, size, (FILE*)fh);
+ uint8* _buf = (uint8*)buf;
+ for (size_t i = 0; i < size; i++)
+ _buf[i] ^= 0x22;
+ return bytesRead;
+ }
+ static off_t r_seek(void* fh, off_t pos, int seekType)
+ {
+ fseek((FILE*)fh, pos, seekType);
+ return ftell((FILE*)fh);
+ }
+ static void r_close(void* fh)
+ {
+ fclose((FILE*)fh);
+ }
+public:
+ CADFFile(const char* path)
+ {
+ m_pMH = mpg123_new(nil, nil);
+ if (m_pMH)
+ {
+#ifdef MP3_USE_FUZZY_SEEK
+ mpg123_param(m_pMH, MPG123_FLAGS, MPG123_FUZZY | MPG123_SEEKBUFFER | MPG123_GAPLESS | MPG123_QUIET, 0.0);
+#else
+ mpg123_param(m_pMH, MPG123_FLAGS, MPG123_SEEKBUFFER | MPG123_GAPLESS, 0.0);
+#endif
+ long rate = 0;
+ int channels = 0;
+ int encoding = 0;
+
+ FILE* f = fopen(path, "rb");
+
+ m_bOpened = mpg123_replace_reader_handle(m_pMH, r_read, r_seek, r_close) == MPG123_OK
+ && mpg123_open_handle(m_pMH, f) == MPG123_OK && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK;
+ m_nRate = rate;
+ m_nChannels = channels;
+
+ if (IsOpened())
+ {
+ mpg123_format_none(m_pMH);
+ mpg123_format(m_pMH, rate, channels, encoding);
+ }
+ }
+ }
+};
+
+#endif
+#define VAG_LINE_SIZE (0x10)
+#define VAG_SAMPLES_IN_LINE (28)
+
+class CVagDecoder
+{
+ const double f[5][2] = { { 0.0, 0.0 },
+ { 60.0 / 64.0, 0.0 },
+ { 115.0 / 64.0, -52.0 / 64.0 },
+ { 98.0 / 64.0, -55.0 / 64.0 },
+ { 122.0 / 64.0, -60.0 / 64.0 } };
+
+ double s_1;
+ double s_2;
+public:
+ CVagDecoder()
+ {
+ ResetState();
+ }
+
+ void ResetState()
+ {
+ s_1 = s_2 = 0.0;
+ }
+
+ static short quantize(double sample)
+ {
+ int a = int(sample + 0.5);
+ return short(clamp(a, -32768, 32767));
+ }
+
+ void Decode(void* _inbuf, int16* _outbuf, size_t size)
+ {
+ uint8* inbuf = (uint8*)_inbuf;
+ int16* outbuf = _outbuf;
+ size &= ~(VAG_LINE_SIZE - 1);
+
+ while (size > 0) {
+ double samples[VAG_SAMPLES_IN_LINE];
+
+ int predict_nr, shift_factor, flags;
+ predict_nr = *(inbuf++);
+ shift_factor = predict_nr & 0xf;
+ predict_nr >>= 4;
+ flags = *(inbuf++);
+ if (flags == 7) // TODO: ignore?
+ break;
+ for (int i = 0; i < VAG_SAMPLES_IN_LINE; i += 2) {
+ int d = *(inbuf++);
+ int16 s = int16((d & 0xf) << 12);
+ samples[i] = (double)(s >> shift_factor);
+ s = int16((d & 0xf0) << 8);
+ samples[i + 1] = (double)(s >> shift_factor);
+ }
+
+ for (int i = 0; i < VAG_SAMPLES_IN_LINE; i++) {
+ samples[i] = samples[i] + s_1 * f[predict_nr][0] + s_2 * f[predict_nr][1];
+ s_2 = s_1;
+ s_1 = samples[i];
+ *(outbuf++) = quantize(samples[i] + 0.5);
+ }
+ size -= VAG_LINE_SIZE;
+ }
+ }
+};
+
+#define VB_BLOCK_SIZE (0x2000)
+#define NUM_VAG_LINES_IN_BLOCK (VB_BLOCK_SIZE / VAG_LINE_SIZE)
+#define NUM_VAG_SAMPLES_IN_BLOCK (NUM_VAG_LINES_IN_BLOCK * VAG_SAMPLES_IN_LINE)
+
+class CVbFile : public IDecoder
+{
+ FILE *m_pFile;
+ CVagDecoder *m_pVagDecoders;
+
+ size_t m_FileSize;
+ size_t m_nNumberOfBlocks;
+
+ uint32 m_nSampleRate;
+ uint8 m_nChannels;
+ bool m_bBlockRead;
+ uint16 m_LineInBlock;
+ size_t m_CurrentBlock;
+
+ uint8 **m_ppVagBuffers; // buffers that cache actual ADPCM file data
+ int16 **m_ppPcmBuffers;
+
+ void ReadBlock(int32 block = -1)
+ {
+ // just read next block if -1
+ if (block != -1)
+ fseek(m_pFile, block * m_nChannels * VB_BLOCK_SIZE, SEEK_SET);
+
+ for (int i = 0; i < m_nChannels; i++)
+ fread(m_ppVagBuffers[i], VB_BLOCK_SIZE, 1, m_pFile);
+ m_bBlockRead = true;
+ }
+
+public:
+ CVbFile(const char* path, uint32 nSampleRate = 32000, uint8 nChannels = 2) : m_nSampleRate(nSampleRate), m_nChannels(nChannels), m_pVagDecoders(nil), m_ppVagBuffers(nil), m_ppPcmBuffers(nil),
+ m_FileSize(0), m_nNumberOfBlocks(0), m_bBlockRead(false), m_LineInBlock(0), m_CurrentBlock(0)
+ {
+ m_pFile = fopen(path, "rb");
+ if (!m_pFile) return;
+
+ fseek(m_pFile, 0, SEEK_END);
+ m_FileSize = ftell(m_pFile);
+ fseek(m_pFile, 0, SEEK_SET);
+
+ m_nNumberOfBlocks = m_FileSize / (nChannels * VB_BLOCK_SIZE);
+ m_pVagDecoders = new CVagDecoder[nChannels];
+ m_ppVagBuffers = new uint8*[nChannels];
+ m_ppPcmBuffers = new int16*[nChannels];
+ for (uint8 i = 0; i < nChannels; i++)
+ m_ppVagBuffers[i] = new uint8[VB_BLOCK_SIZE];
+ }
+
+ ~CVbFile()
+ {
+ if (m_pFile)
+ {
+ fclose(m_pFile);
+
+ delete[] m_pVagDecoders;
+ for (int i = 0; i < m_nChannels; i++)
+ delete[] m_ppVagBuffers[i];
+ delete[] m_ppVagBuffers;
+ delete[] m_ppPcmBuffers;
+ }
+ }
+
+ bool IsOpened()
+ {
+ return m_pFile != nil;
+ }
+
+ uint32 GetSampleSize()
+ {
+ return sizeof(uint16);
+ }
+
+ uint32 GetSampleCount()
+ {
+ if (!IsOpened()) return 0;
+ return m_nNumberOfBlocks * NUM_VAG_LINES_IN_BLOCK * VAG_SAMPLES_IN_LINE;
+ }
+
+ uint32 GetSampleRate()
+ {
+ return m_nSampleRate;
+ }
+
+ uint32 GetChannels()
+ {
+ return m_nChannels;
+ }
+
+ void Seek(uint32 milliseconds)
+ {
+ if (!IsOpened()) return;
+ uint32 samples = ms2samples(milliseconds);
+
+ // find the block of our sample
+ uint32 block = samples / NUM_VAG_SAMPLES_IN_BLOCK;
+ if (block > m_nNumberOfBlocks)
+ {
+ samples = 0;
+ block = 0;
+ }
+ if (block != m_CurrentBlock)
+ m_bBlockRead = false;
+
+ // find a line of our sample within our block
+ uint32 remainingSamples = samples - block * NUM_VAG_SAMPLES_IN_BLOCK;
+ uint32 newLine = remainingSamples / VAG_SAMPLES_IN_LINE / VAG_LINE_SIZE;
+
+ if (m_CurrentBlock != block || m_LineInBlock != newLine)
+ {
+ m_CurrentBlock = block;
+ m_LineInBlock = newLine;
+ for (uint32 i = 0; i < GetChannels(); i++)
+ m_pVagDecoders[i].ResetState();
+ }
+
+ }
+
+ uint32 Tell()
+ {
+ if (!IsOpened()) return 0;
+ uint32 pos = (m_CurrentBlock * NUM_VAG_LINES_IN_BLOCK + m_LineInBlock) * VAG_SAMPLES_IN_LINE;
+ return samples2ms(pos);
+ }
+
+ uint32 Decode(void* buffer)
+ {
+ if (!IsOpened()) return 0;
+
+ if (m_CurrentBlock >= m_nNumberOfBlocks) return 0;
+
+ // cache current ADPCM block
+ if (!m_bBlockRead)
+ ReadBlock(m_CurrentBlock);
+
+ // trim the buffer size if we're at the end of our file
+ int numberOfRequiredLines = GetBufferSamples() / m_nChannels / VAG_SAMPLES_IN_LINE;
+ int numberOfRemainingLines = (m_nNumberOfBlocks - m_CurrentBlock) * NUM_VAG_LINES_IN_BLOCK - m_LineInBlock;
+ int bufSizePerChannel = Min(numberOfRequiredLines, numberOfRemainingLines) * VAG_SAMPLES_IN_LINE * GetSampleSize();
+
+ // calculate the pointers to individual channel buffers
+ for (uint32 i = 0; i < m_nChannels; i++)
+ m_ppPcmBuffers[i] = (int16*)((int8*)buffer + bufSizePerChannel * i);
+
+ int size = 0;
+ while (size < bufSizePerChannel)
+ {
+ // decode the VAG lines
+ for (uint32 i = 0; i < m_nChannels; i++)
+ {
+ m_pVagDecoders[i].Decode(m_ppVagBuffers[i] + m_LineInBlock * VAG_LINE_SIZE, m_ppPcmBuffers[i], VAG_LINE_SIZE);
+ m_ppPcmBuffers[i] += VAG_SAMPLES_IN_LINE;
+ }
+ size += VAG_SAMPLES_IN_LINE * GetSampleSize();
+ m_LineInBlock++;
+
+ // block is over, read the next block
+ if (m_LineInBlock >= NUM_VAG_LINES_IN_BLOCK)
+ {
+ m_CurrentBlock++;
+ if (m_CurrentBlock >= m_nNumberOfBlocks) // end of file
+ break;
+ m_LineInBlock = 0;
+ ReadBlock();
+ }
+ }
+
+ return bufSizePerChannel * m_nChannels;
+ }
+};
+#ifdef AUDIO_OAL_USE_OPUS
+class COpusFile : public IDecoder
+{
+ OggOpusFile *m_FileH;
+ bool m_bOpened;
+ uint32 m_nRate;
+ uint32 m_nChannels;
+public:
+ COpusFile(const char *path) : m_FileH(nil),
+ m_bOpened(false),
+ m_nRate(0),
+ m_nChannels(0)
+ {
+ int ret;
+ m_FileH = op_open_file(path, &ret);
+
+ if (m_FileH) {
+ m_nChannels = op_head(m_FileH, 0)->channel_count;
+ m_nRate = 48000;
+ const OpusTags *tags = op_tags(m_FileH, 0);
+ for (int i = 0; i < tags->comments; i++) {
+ if (strncmp(tags->user_comments[i], "SAMPLERATE", sizeof("SAMPLERATE")-1) == 0)
+ {
+ sscanf(tags->user_comments[i], "SAMPLERATE=%i", &m_nRate);
+ break;
+ }
+ }
+
+ m_bOpened = true;
+ }
+ }
+
+ ~COpusFile()
+ {
+ if (m_FileH)
+ {
+ op_free(m_FileH);
+ m_FileH = nil;
+ }
+ }
+
+ bool IsOpened()
+ {
+ return m_bOpened;
+ }
+
+ uint32 GetSampleSize()
+ {
+ return sizeof(uint16);
+ }
+
+ uint32 GetSampleCount()
+ {
+ if ( !IsOpened() ) return 0;
+ return op_pcm_total(m_FileH, 0);
+ }
+
+ uint32 GetSampleRate()
+ {
+ return m_nRate;
+ }
+
+ uint32 GetChannels()
+ {
+ return m_nChannels;
+ }
+
+ void Seek(uint32 milliseconds)
+ {
+ if ( !IsOpened() ) return;
+ op_pcm_seek(m_FileH, ms2samples(milliseconds) / GetChannels());
+ }
+
+ uint32 Tell()
+ {
+ if ( !IsOpened() ) return 0;
+ return samples2ms(op_pcm_tell(m_FileH) * GetChannels());
+ }
+
+ uint32 Decode(void *buffer)
+ {
+ if ( !IsOpened() ) return 0;
+
+ int size = op_read(m_FileH, (opus_int16 *)buffer, GetBufferSamples(), NULL);
+
+ if (size < 0)
+ return 0;
+
+ if (GetChannels() == 2)
+ SortStereoBuffer.SortStereo(buffer, size * m_nChannels * GetSampleSize());
+
+ return size * m_nChannels * GetSampleSize();
+ }
+};
+#endif
+
+void CStream::Initialise()
+{
+#ifdef AUDIO_OAL_USE_MPG123
+ mpg123_init();
+#endif
+}
+
+void CStream::Terminate()
+{
+#ifdef AUDIO_OAL_USE_MPG123
+ mpg123_exit();
+#endif
+}
+
+CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate) :
+ m_pAlSources(sources),
+ m_alBuffers(buffers),
+ m_pBuffer(nil),
+ m_bPaused(false),
+ m_bActive(false),
+ m_pSoundFile(nil),
+ m_bReset(false),
+ m_nVolume(0),
+ m_nPan(0),
+ m_nPosBeforeReset(0),
+ m_nLoopCount(1)
+
+{
+// Be case-insensitive on linux (from https://github.com/OneSadCookie/fcaseopen/)
+#if !defined(_WIN32)
+ char *real = casepath(filename);
+ if (real) {
+ strcpy(m_aFilename, real);
+ free(real);
+ } else {
+#else
+ {
+#endif
+ strcpy(m_aFilename, filename);
+ }
+
+ DEV("Stream %s\n", m_aFilename);
+
+ if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".wav")], ".wav"))
+#ifdef AUDIO_OAL_USE_SNDFILE
+ m_pSoundFile = new CSndFile(m_aFilename);
+#else
+ m_pSoundFile = new CWavFile(m_aFilename);
+#endif
+#ifdef AUDIO_OAL_USE_MPG123
+ else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".mp3")], ".mp3"))
+ m_pSoundFile = new CMP3File(m_aFilename);
+ else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".adf")], ".adf"))
+ m_pSoundFile = new CADFFile(m_aFilename);
+#endif
+ else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".vb")], ".VB"))
+ m_pSoundFile = new CVbFile(m_aFilename, overrideSampleRate);
+#ifdef AUDIO_OAL_USE_OPUS
+ else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".opus")], ".opus"))
+ m_pSoundFile = new COpusFile(m_aFilename);
+#endif
+ else
+ m_pSoundFile = nil;
+
+ if ( IsOpened() )
+ {
+ m_pBuffer = malloc(m_pSoundFile->GetBufferSize());
+ ASSERT(m_pBuffer!=nil);
+
+ DEV("AvgSamplesPerSec: %d\n", m_pSoundFile->GetAvgSamplesPerSec());
+ DEV("SampleCount: %d\n", m_pSoundFile->GetSampleCount());
+ DEV("SampleRate: %d\n", m_pSoundFile->GetSampleRate());
+ DEV("Channels: %d\n", m_pSoundFile->GetChannels());
+ DEV("Buffer Samples: %d\n", m_pSoundFile->GetBufferSamples());
+ DEV("Buffer sec: %f\n", (float(m_pSoundFile->GetBufferSamples()) / float(m_pSoundFile->GetChannels())/ float(m_pSoundFile->GetSampleRate())));
+ DEV("Length MS: %02d:%02d\n", (m_pSoundFile->GetLength() / 1000) / 60, (m_pSoundFile->GetLength() / 1000) % 60);
+
+ return;
+ }
+}
+
+CStream::~CStream()
+{
+ Delete();
+}
+
+void CStream::Delete()
+{
+ Stop();
+ ClearBuffers();
+
+ if ( m_pSoundFile )
+ {
+ delete m_pSoundFile;
+ m_pSoundFile = nil;
+ }
+
+ if ( m_pBuffer )
+ {
+ free(m_pBuffer);
+ m_pBuffer = nil;
+ }
+}
+
+bool CStream::HasSource()
+{
+ return (m_pAlSources[0] != AL_NONE) && (m_pAlSources[1] != AL_NONE);
+}
+
+bool CStream::IsOpened()
+{
+ return m_pSoundFile && m_pSoundFile->IsOpened();
+}
+
+bool CStream::IsPlaying()
+{
+ if ( !HasSource() || !IsOpened() ) return false;
+
+ if ( !m_bPaused )
+ {
+ ALint sourceState[2];
+ alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState[0]);
+ alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]);
+ if (sourceState[0] == AL_PLAYING || sourceState[1] == AL_PLAYING)
+ return true;
+ }
+
+ return false;
+}
+
+void CStream::Pause()
+{
+ if ( !HasSource() ) return;
+ ALint sourceState = AL_PAUSED;
+ alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState);
+ if (sourceState != AL_PAUSED)
+ 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)
+{
+ if ( !HasSource() ) return;
+ if ( bPause )
+ {
+ Pause();
+ m_bPaused = true;
+ }
+ else
+ {
+ if (m_bPaused)
+ SetPlay(true);
+ m_bPaused = false;
+ }
+}
+
+void CStream::SetPitch(float pitch)
+{
+ if ( !HasSource() ) return;
+ alSourcef(m_pAlSources[0], AL_PITCH, pitch);
+ alSourcef(m_pAlSources[1], AL_PITCH, pitch);
+}
+
+void CStream::SetGain(float gain)
+{
+ if ( !HasSource() ) return;
+ alSourcef(m_pAlSources[0], AL_GAIN, gain);
+ alSourcef(m_pAlSources[1], AL_GAIN, gain);
+}
+
+void CStream::SetPosition(int i, float x, float y, float z)
+{
+ if ( !HasSource() ) return;
+ alSource3f(m_pAlSources[i], AL_POSITION, x, y, z);
+}
+
+void CStream::SetVolume(uint32 nVol)
+{
+ m_nVolume = nVol;
+ SetGain(ALfloat(nVol) / MAX_VOLUME);
+}
+
+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;
+}
+
+// Should only be called if source is stopped
+void CStream::SetPosMS(uint32 nPos)
+{
+ if ( !IsOpened() ) return;
+ m_pSoundFile->Seek(nPos);
+ ClearBuffers();
+}
+
+uint32 CStream::GetPosMS()
+{
+ if ( !HasSource() ) return 0;
+ if ( !IsOpened() ) return 0;
+
+ ALint offset;
+ //alGetSourcei(m_alSource, AL_SAMPLE_OFFSET, &offset);
+ alGetSourcei(m_pAlSources[0], AL_BYTE_OFFSET, &offset);
+
+ return m_pSoundFile->Tell()
+ - m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples() * (NUM_STREAMBUFFERS/2-1)) / m_pSoundFile->GetChannels()
+ + m_pSoundFile->samples2ms(offset/m_pSoundFile->GetSampleSize()) / m_pSoundFile->GetChannels();
+}
+
+uint32 CStream::GetLengthMS()
+{
+ if ( !IsOpened() ) return 0;
+ return m_pSoundFile->GetLength();
+}
+
+bool CStream::FillBuffer(ALuint *alBuffer)
+{
+ if ( !HasSource() )
+ return false;
+ if ( !IsOpened() )
+ return false;
+ if ( !(alBuffer[0] != AL_NONE && alIsBuffer(alBuffer[0])) )
+ return false;
+ if ( !(alBuffer[1] != AL_NONE && alIsBuffer(alBuffer[1])) )
+ return false;
+
+ uint32 size = m_pSoundFile->Decode(m_pBuffer);
+ if( size == 0 )
+ return false;
+
+ uint32 channelSize = size / m_pSoundFile->GetChannels();
+
+ 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;
+}
+
+int32 CStream::FillBuffers()
+{
+ int32 i = 0;
+ for ( i = 0; i < NUM_STREAMBUFFERS/2; i++ )
+ {
+ if ( !FillBuffer(&m_alBuffers[i*2]) )
+ break;
+ alSourceQueueBuffers(m_pAlSources[0], 1, &m_alBuffers[i*2]);
+ alSourceQueueBuffers(m_pAlSources[1], 1, &m_alBuffers[i*2+1]);
+ }
+
+ return i;
+}
+
+void CStream::ClearBuffers()
+{
+ if ( !HasSource() ) return;
+
+ ALint buffersQueued[2];
+ alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &buffersQueued[0]);
+ alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &buffersQueued[1]);
+
+ ALuint value;
+ while (buffersQueued[0]--)
+ alSourceUnqueueBuffers(m_pAlSources[0], 1, &value);
+ while (buffersQueued[1]--)
+ alSourceUnqueueBuffers(m_pAlSources[1], 1, &value);
+}
+
+bool CStream::Setup(bool imSureQueueIsEmpty)
+{
+ if ( IsOpened() )
+ {
+ alSourcei(m_pAlSources[0], AL_LOOPING, AL_FALSE);
+ alSourcei(m_pAlSources[1], AL_LOOPING, AL_FALSE);
+ if (!imSureQueueIsEmpty) {
+ SetPlay(false);
+ ClearBuffers();
+ }
+ m_pSoundFile->Seek(0);
+ //SetPosition(0.0f, 0.0f, 0.0f);
+ SetPitch(1.0f);
+ //SetPan(m_nPan);
+ //SetVolume(100);
+ }
+
+ return IsOpened();
+}
+
+void CStream::SetLoopCount(int32 count)
+{
+ if ( !HasSource() ) return;
+
+ m_nLoopCount = count;
+}
+
+void CStream::SetPlay(bool state)
+{
+ if ( !HasSource() ) return;
+ if ( state )
+ {
+ ALint sourceState = AL_PLAYING;
+ alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState);
+ if (sourceState != AL_PLAYING )
+ 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;
+ }
+ else
+ {
+ ALint sourceState = AL_STOPPED;
+ alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState);
+ if (sourceState != AL_STOPPED )
+ 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;
+ }
+}
+
+void CStream::Start()
+{
+ if ( !HasSource() ) return;
+ if ( FillBuffers() != 0 )
+ SetPlay(true);
+}
+
+void CStream::Stop()
+{
+ if ( !HasSource() ) return;
+ SetPlay(false);
+}
+
+void CStream::Update()
+{
+ if ( !IsOpened() )
+ return;
+
+ if ( !HasSource() )
+ return;
+
+ if ( m_bReset )
+ return;
+
+ if ( !m_bPaused )
+ {
+ ALint totalBuffers[2] = { 0, 0 };
+ ALint buffersProcessed[2] = { 0, 0 };
+
+ // Relying a lot on left buffer states in here
+
+ do
+ {
+ //alSourcef(m_pAlSources[0], AL_ROLLOFF_FACTOR, 0.0f);
+ alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &totalBuffers[0]);
+ alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed[0]);
+ //alSourcef(m_pAlSources[1], AL_ROLLOFF_FACTOR, 0.0f);
+ alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &totalBuffers[1]);
+ alGetSourcei(m_pAlSources[1], AL_BUFFERS_PROCESSED, &buffersProcessed[1]);
+ } while (buffersProcessed[0] != buffersProcessed[1]);
+
+ assert(buffersProcessed[0] == buffersProcessed[1]);
+
+ // Correcting OpenAL concepts here:
+ // AL_BUFFERS_QUEUED = Number of *all* buffers in queue, including processed, processing and pending
+ // AL_BUFFERS_PROCESSED = Index of the buffer being processing right now. Buffers coming after that(have greater index) are pending buffers.
+ // which means: totalBuffers[0] - buffersProcessed[0] = pending buffers
+
+ bool buffersRefilled = false;
+
+ // We should wait queue to be cleared to loop track, because position calculation relies on queue.
+ if (m_nLoopCount != 1 && m_bActive && totalBuffers[0] == 0)
+ {
+ Setup(true);
+ buffersRefilled = FillBuffers() != 0;
+ if (m_nLoopCount != 0)
+ m_nLoopCount--;
+ }
+ else
+ {
+ while( buffersProcessed[0]-- )
+ {
+ ALuint buffer[2];
+
+ alSourceUnqueueBuffers(m_pAlSources[0], 1, &buffer[0]);
+ alSourceUnqueueBuffers(m_pAlSources[1], 1, &buffer[1]);
+
+ if (m_bActive && FillBuffer(buffer))
+ {
+ buffersRefilled = true;
+ alSourceQueueBuffers(m_pAlSources[0], 1, &buffer[0]);
+ alSourceQueueBuffers(m_pAlSources[1], 1, &buffer[1]);
+ }
+ }
+ }
+
+ // Two reasons: 1-Source may be starved to audio and stopped itself, 2- We're already waiting it to starve and die for looping track!
+ if (m_bActive && (buffersRefilled || (totalBuffers[1] - buffersProcessed[1] != 0)))
+ SetPlay(true);
+ }
+}
+
+void CStream::ProviderInit()
+{
+ if ( m_bReset )
+ {
+ if ( Setup(true) )
+ {
+ SetPan(m_nPan);
+ SetVolume(m_nVolume);
+ SetLoopCount(m_nLoopCount);
+ SetPosMS(m_nPosBeforeReset);
+ if (m_bActive)
+ FillBuffers();
+ SetPlay(m_bActive);
+ if ( m_bPaused )
+ Pause();
+ }
+
+ m_bReset = false;
+ }
+}
+
+void CStream::ProviderTerm()
+{
+ m_bReset = true;
+ m_nPosBeforeReset = GetPosMS();
+
+ ClearBuffers();
+}
+
+#endif
diff --git a/src/audio/oal/stream.h b/src/audio/oal/stream.h
new file mode 100644
index 0000000..9a2a2fb
--- /dev/null
+++ b/src/audio/oal/stream.h
@@ -0,0 +1,114 @@
+#pragma once
+
+#ifdef AUDIO_OAL
+#include <AL/al.h>
+
+#define NUM_STREAMBUFFERS 8
+
+class IDecoder
+{
+public:
+ virtual ~IDecoder() { }
+
+ virtual bool IsOpened() = 0;
+
+ virtual uint32 GetSampleSize() = 0;
+ virtual uint32 GetSampleCount() = 0;
+ virtual uint32 GetSampleRate() = 0;
+ virtual uint32 GetChannels() = 0;
+
+ uint32 GetAvgSamplesPerSec()
+ {
+ return GetChannels() * GetSampleRate();
+ }
+
+ uint32 ms2samples(uint32 ms)
+ {
+ return float(ms) / 1000.0f * float(GetSampleRate());
+ }
+
+ uint32 samples2ms(uint32 sm)
+ {
+ return float(sm) * 1000.0f / float(GetSampleRate());
+ }
+
+ uint32 GetBufferSamples()
+ {
+ //return (GetAvgSamplesPerSec() >> 2) - (GetSampleCount() % GetChannels());
+ return (GetAvgSamplesPerSec() / 4); // 250ms
+ }
+
+ uint32 GetBufferSize()
+ {
+ return GetBufferSamples() * GetSampleSize();
+ }
+
+ virtual void Seek(uint32 milliseconds) = 0;
+ virtual uint32 Tell() = 0;
+
+ uint32 GetLength()
+ {
+ return float(GetSampleCount()) * 1000.0f / float(GetSampleRate());
+ }
+
+ virtual uint32 Decode(void *buffer) = 0;
+};
+
+class CStream
+{
+ char m_aFilename[128];
+ ALuint *m_pAlSources;
+ ALuint (&m_alBuffers)[NUM_STREAMBUFFERS];
+
+ bool m_bPaused;
+ bool m_bActive;
+
+ void *m_pBuffer;
+
+ bool m_bReset;
+ uint32 m_nVolume;
+ uint8 m_nPan;
+ uint32 m_nPosBeforeReset;
+ int32 m_nLoopCount;
+
+ IDecoder *m_pSoundFile;
+
+ bool HasSource();
+ void SetPosition(int i, float x, float y, float z);
+ void SetPitch(float pitch);
+ void SetGain(float gain);
+ void Pause();
+ void SetPlay(bool state);
+
+ bool FillBuffer(ALuint *alBuffer);
+ int32 FillBuffers();
+ void ClearBuffers();
+public:
+ static void Initialise();
+ static void Terminate();
+
+ CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate = 32000);
+ ~CStream();
+ void Delete();
+
+ bool IsOpened();
+ bool IsPlaying();
+ void SetPause (bool bPause);
+ void SetVolume(uint32 nVol);
+ void SetPan (uint8 nPan);
+ void SetPosMS (uint32 nPos);
+ uint32 GetPosMS();
+ uint32 GetLengthMS();
+
+ bool Setup(bool imSureQueueIsEmpty = false);
+ void Start();
+ void Stop();
+ void Update(void);
+ void SetLoopCount(int32);
+
+
+ void ProviderInit();
+ void ProviderTerm();
+};
+
+#endif