gzdoom-gles/src/sound/oalsound.cpp
Christoph Oelckers 921abc404d - fixed GCC warnings and errors
(Is there anyway to tone down GCC's warning level? It outputs too many false positives for potentially uninitialized variables in which the genuine errors get drowned.)
2017-03-11 19:02:35 +01:00

2215 lines
58 KiB
C++

/*
** oalsound.cpp
** System interface for sound; uses OpenAL
**
**---------------------------------------------------------------------------
** Copyright 2008-2010 Chris Robinson
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. 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.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
**---------------------------------------------------------------------------
**
*/
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <memory>
#include <chrono>
#include "except.h"
#include "doomstat.h"
#include "templates.h"
#include "oalsound.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "i_system.h"
#include "v_text.h"
#include "gi.h"
#include "actor.h"
#include "r_state.h"
#include "w_wad.h"
#include "i_module.h"
#include "i_music.h"
#include "i_musicinterns.h"
#include "tempfiles.h"
#include "cmdlib.h"
FModule OpenALModule{"OpenAL"};
#include "oalload.h"
CVAR (String, snd_aldevice, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, snd_efx, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
#ifdef _WIN32
#define OPENALLIB "openal32.dll"
#elif defined(__APPLE__)
#define OPENALLIB "OpenAL.framework/OpenAL"
#else
#define OPENALLIB "libopenal.so.1"
#endif
bool IsOpenALPresent()
{
#ifdef NO_OPENAL
return false;
#elif !defined DYN_OPENAL
return true;
#else
static bool cached_result = false;
static bool done = false;
if (!done)
{
done = true;
cached_result = OpenALModule.Load({NicePath("$PROGDIR/" OPENALLIB), OPENALLIB});
}
return cached_result;
#endif
}
void I_BuildALDeviceList(FOptionValues *opt)
{
opt->mValues.Resize(1);
opt->mValues[0].TextValue = "Default";
opt->mValues[0].Text = "Default";
#ifndef NO_OPENAL
if (IsOpenALPresent())
{
const ALCchar *names = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ?
alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) :
alcGetString(NULL, ALC_DEVICE_SPECIFIER));
if (!names)
Printf("Failed to get device list: %s\n", alcGetString(NULL, alcGetError(NULL)));
else while (*names)
{
unsigned int i = opt->mValues.Reserve(1);
opt->mValues[i].TextValue = names;
opt->mValues[i].Text = names;
names += strlen(names) + 1;
}
}
#endif
}
#ifndef NO_OPENAL
EXTERN_CVAR (Int, snd_channels)
EXTERN_CVAR (Int, snd_samplerate)
EXTERN_CVAR (Bool, snd_waterreverb)
EXTERN_CVAR (Bool, snd_pitched)
#define MAKE_PTRID(x) ((void*)(uintptr_t)(x))
#define GET_PTRID(x) ((uint32_t)(uintptr_t)(x))
static ALenum checkALError(const char *fn, unsigned int ln)
{
ALenum err = alGetError();
if(err != AL_NO_ERROR)
{
if(strchr(fn, '/'))
fn = strrchr(fn, '/')+1;
else if(strchr(fn, '\\'))
fn = strrchr(fn, '\\')+1;
Printf(">>>>>>>>>>>> Received AL error %s (%#x), %s:%u\n", alGetString(err), err, fn, ln);
}
return err;
}
#define getALError() checkALError(__FILE__, __LINE__)
static ALCenum checkALCError(ALCdevice *device, const char *fn, unsigned int ln)
{
ALCenum err = alcGetError(device);
if(err != ALC_NO_ERROR)
{
if(strchr(fn, '/'))
fn = strrchr(fn, '/')+1;
else if(strchr(fn, '\\'))
fn = strrchr(fn, '\\')+1;
Printf(">>>>>>>>>>>> Received ALC error %s (%#x), %s:%u\n", alcGetString(device, err), err, fn, ln);
}
return err;
}
#define getALCError(d) checkALCError((d), __FILE__, __LINE__)
// Fallback methods for when AL_SOFT_deferred_updates isn't available. In most
// cases these don't actually do anything, except on some Creative drivers
// where they act as appropriate fallbacks.
static ALvoid AL_APIENTRY _wrap_DeferUpdatesSOFT(void)
{
alcSuspendContext(alcGetCurrentContext());
}
static ALvoid AL_APIENTRY _wrap_ProcessUpdatesSOFT(void)
{
alcProcessContext(alcGetCurrentContext());
}
class OpenALSoundStream : public SoundStream
{
OpenALSoundRenderer *Renderer;
SoundStreamCallback Callback;
void *UserData;
TArray<ALubyte> Data;
ALsizei SampleRate;
ALenum Format;
ALsizei FrameSize;
static const int BufferCount = 4;
ALuint Buffers[BufferCount];
ALuint Source;
std::atomic<bool> Playing;
bool Looping;
ALfloat Volume;
FileReader *Reader;
SoundDecoder *Decoder;
static bool DecoderCallback(SoundStream *_sstream, void *ptr, int length, void *user)
{
OpenALSoundStream *self = static_cast<OpenALSoundStream*>(_sstream);
if(length < 0) return false;
size_t got = self->Decoder->read((char*)ptr, length);
if(got < (unsigned int)length)
{
if(!self->Looping || !self->Decoder->seek(0))
return false;
got += self->Decoder->read((char*)ptr+got, length-got);
}
return (got == (unsigned int)length);
}
bool SetupSource()
{
/* Get a source, killing the farthest, lowest-priority sound if needed */
if(Renderer->FreeSfx.Size() == 0)
{
FSoundChan *lowest = Renderer->FindLowestChannel();
if(lowest) Renderer->StopChannel(lowest);
if(Renderer->FreeSfx.Size() == 0)
return false;
}
Renderer->FreeSfx.Pop(Source);
/* Set the default properties for localized playback */
alSource3f(Source, AL_DIRECTION, 0.f, 0.f, 0.f);
alSource3f(Source, AL_VELOCITY, 0.f, 0.f, 0.f);
alSource3f(Source, AL_POSITION, 0.f, 0.f, 0.f);
alSourcef(Source, AL_MAX_GAIN, 1.f);
alSourcef(Source, AL_GAIN, 1.f);
alSourcef(Source, AL_PITCH, 1.f);
alSourcef(Source, AL_ROLLOFF_FACTOR, 0.f);
alSourcef(Source, AL_SEC_OFFSET, 0.f);
alSourcei(Source, AL_SOURCE_RELATIVE, AL_TRUE);
alSourcei(Source, AL_LOOPING, AL_FALSE);
if(Renderer->EnvSlot)
{
alSourcef(Source, AL_ROOM_ROLLOFF_FACTOR, 0.f);
alSourcef(Source, AL_AIR_ABSORPTION_FACTOR, 0.f);
alSourcei(Source, AL_DIRECT_FILTER, AL_FILTER_NULL);
alSource3i(Source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL);
}
if(Renderer->AL.EXT_SOURCE_RADIUS)
alSourcef(Source, AL_SOURCE_RADIUS, 0.f);
alGenBuffers(BufferCount, Buffers);
return (getALError() == AL_NO_ERROR);
}
public:
OpenALSoundStream(OpenALSoundRenderer *renderer)
: Renderer(renderer), Source(0), Playing(false), Looping(false), Volume(1.0f), Reader(NULL), Decoder(NULL)
{
memset(Buffers, 0, sizeof(Buffers));
Renderer->AddStream(this);
}
virtual ~OpenALSoundStream()
{
Renderer->RemoveStream(this);
if(Source)
{
alSourceRewind(Source);
alSourcei(Source, AL_BUFFER, 0);
Renderer->FreeSfx.Push(Source);
Source = 0;
}
if(Buffers[0])
{
alDeleteBuffers(BufferCount, &Buffers[0]);
memset(Buffers, 0, sizeof(Buffers));
}
getALError();
delete Decoder;
delete Reader;
}
virtual bool Play(bool loop, float vol)
{
SetVolume(vol);
if(Playing.load())
return true;
/* Clear the buffer queue, then fill and queue each buffer */
alSourcei(Source, AL_BUFFER, 0);
for(int i = 0;i < BufferCount;i++)
{
if(!Callback(this, &Data[0], Data.Size(), UserData))
{
if(i == 0)
return false;
break;
}
alBufferData(Buffers[i], Format, &Data[0], Data.Size(), SampleRate);
alSourceQueueBuffers(Source, 1, &Buffers[i]);
}
if(getALError() != AL_NO_ERROR)
return false;
alSourcePlay(Source);
if(getALError() != AL_NO_ERROR)
return false;
Playing.store(true);
return true;
}
virtual void Stop()
{
if(!Playing.load())
return;
std::unique_lock<std::mutex> lock(Renderer->StreamLock);
alSourceStop(Source);
alSourcei(Source, AL_BUFFER, 0);
getALError();
Playing.store(false);
}
virtual void SetVolume(float vol)
{
Volume = vol;
UpdateVolume();
}
void UpdateVolume()
{
alSourcef(Source, AL_GAIN, Renderer->MusicVolume*Volume);
getALError();
}
virtual bool SetPaused(bool pause)
{
if(pause)
alSourcePause(Source);
else
alSourcePlay(Source);
return (getALError()==AL_NO_ERROR);
}
virtual bool SetPosition(unsigned int ms_pos)
{
std::unique_lock<std::mutex> lock(Renderer->StreamLock);
if(!Decoder->seek(ms_pos))
return false;
if(!Playing.load())
return true;
// Stop the source so that all buffers become processed, which will
// allow the next update to restart the source queue with the new
// position.
alSourceStop(Source);
getALError();
lock.unlock();
Renderer->StreamWake.notify_all();
return true;
}
virtual unsigned int GetPosition()
{
std::unique_lock<std::mutex> lock(Renderer->StreamLock);
ALint offset, queued, state;
alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset);
alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);
alGetSourcei(Source, AL_SOURCE_STATE, &state);
if(getALError() != AL_NO_ERROR)
return 0;
size_t pos = Decoder->getSampleOffset();
lock.unlock();
if(state != AL_STOPPED)
{
size_t rem = queued*(Data.Size()/FrameSize) - offset;
if(pos > rem) pos -= rem;
else pos = 0;
}
return (unsigned int)(pos * 1000.0 / SampleRate);
}
virtual bool IsEnded()
{
return !Playing.load();
}
virtual FString GetStats()
{
FString stats;
size_t pos = 0, len = 0;
ALfloat volume;
ALint offset;
ALint processed;
ALint queued;
ALint state;
ALenum err;
std::unique_lock<std::mutex> lock(Renderer->StreamLock);
alGetSourcef(Source, AL_GAIN, &volume);
alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset);
alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);
alGetSourcei(Source, AL_SOURCE_STATE, &state);
if((err=alGetError()) != AL_NO_ERROR)
{
lock.unlock();
stats = "Error getting stats: ";
stats += alGetString(err);
return stats;
}
if (Decoder != nullptr)
{
pos = Decoder->getSampleOffset();
len = Decoder->getSampleLength();
}
lock.unlock();
stats = (state == AL_INITIAL) ? "Buffering" : (state == AL_STOPPED) ? "Underrun" :
(state == AL_PLAYING || state == AL_PAUSED) ? "Ready" : "Unknown state";
if (Decoder != nullptr)
{
if (state == AL_STOPPED)
offset = BufferCount * (Data.Size() / FrameSize);
else
{
size_t rem = queued*(Data.Size() / FrameSize) - offset;
if (pos > rem) pos -= rem;
else if (len > 0) pos += len - rem;
else pos = 0;
}
pos = (size_t)(pos * 1000.0 / SampleRate);
len = (size_t)(len * 1000.0 / SampleRate);
stats.AppendFormat(",%3u%% buffered", 100 - 100 * offset / (BufferCount*(Data.Size() / FrameSize)));
stats.AppendFormat(", %zu.%03zu", pos / 1000, pos % 1000);
if (len > 0)
stats.AppendFormat(" / %zu.%03zu", len / 1000, len % 1000);
}
if(state == AL_PAUSED)
stats += ", paused";
if(state == AL_PLAYING)
stats += ", playing";
stats.AppendFormat(", %uHz", SampleRate);
if(!Playing)
stats += " XX";
return stats;
}
bool Process()
{
if(!Playing.load())
return false;
ALint state, processed;
alGetSourcei(Source, AL_SOURCE_STATE, &state);
alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed);
if(getALError() != AL_NO_ERROR)
{
Playing.store(false);
return false;
}
// For each processed buffer in the queue...
while(processed > 0)
{
ALuint bufid;
// Unqueue the oldest buffer, fill it with more data, and queue it
// on the end
alSourceUnqueueBuffers(Source, 1, &bufid);
processed--;
if(Callback(this, &Data[0], Data.Size(), UserData))
{
alBufferData(bufid, Format, &Data[0], Data.Size(), SampleRate);
alSourceQueueBuffers(Source, 1, &bufid);
}
}
// If the source is not playing or paused, and there are buffers queued,
// then there was an underrun. Restart the source.
bool ok = (getALError()==AL_NO_ERROR);
if(ok && state != AL_PLAYING && state != AL_PAUSED)
{
ALint queued = 0;
alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);
ok = (getALError() == AL_NO_ERROR) && (queued > 0);
if(ok)
{
alSourcePlay(Source);
ok = (getALError()==AL_NO_ERROR);
}
}
Playing.store(ok);
return ok;
}
bool Init(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata)
{
if(!SetupSource())
return false;
Callback = callback;
UserData = userdata;
SampleRate = samplerate;
Format = AL_NONE;
if((flags&Bits8)) /* Signed or unsigned? We assume unsigned 8-bit... */
{
if((flags&Mono)) Format = AL_FORMAT_MONO8;
else Format = AL_FORMAT_STEREO8;
}
else if((flags&Float))
{
if(alIsExtensionPresent("AL_EXT_FLOAT32"))
{
if((flags&Mono)) Format = AL_FORMAT_MONO_FLOAT32;
else Format = AL_FORMAT_STEREO_FLOAT32;
}
}
else if((flags&Bits32))
{
}
else
{
if((flags&Mono)) Format = AL_FORMAT_MONO16;
else Format = AL_FORMAT_STEREO16;
}
if(Format == AL_NONE)
{
Printf("Unsupported format: 0x%x\n", flags);
return false;
}
FrameSize = 1;
if((flags&Bits8))
FrameSize *= 1;
else if((flags&(Bits32|Float)))
FrameSize *= 4;
else
FrameSize *= 2;
if((flags&Mono))
FrameSize *= 1;
else
FrameSize *= 2;
buffbytes += FrameSize-1;
buffbytes -= buffbytes%FrameSize;
Data.Resize(buffbytes);
return true;
}
bool Init(FileReader *reader, bool loop)
{
if(!SetupSource())
{
delete reader;
return false;
}
if(Decoder) delete Decoder;
if(Reader) delete Reader;
Reader = reader;
Decoder = Renderer->CreateDecoder(Reader);
if(!Decoder) return false;
Callback = DecoderCallback;
UserData = NULL;
Format = AL_NONE;
FrameSize = 1;
ChannelConfig chans;
SampleType type;
int srate;
Decoder->getInfo(&srate, &chans, &type);
if(chans == ChannelConfig_Mono)
{
if(type == SampleType_UInt8) Format = AL_FORMAT_MONO8;
if(type == SampleType_Int16) Format = AL_FORMAT_MONO16;
FrameSize *= 1;
}
if(chans == ChannelConfig_Stereo)
{
if(type == SampleType_UInt8) Format = AL_FORMAT_STEREO8;
if(type == SampleType_Int16) Format = AL_FORMAT_STEREO16;
FrameSize *= 2;
}
if(type == SampleType_UInt8) FrameSize *= 1;
if(type == SampleType_Int16) FrameSize *= 2;
if(Format == AL_NONE)
{
Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
GetSampleTypeName(type));
return false;
}
SampleRate = srate;
Looping = loop;
Data.Resize((SampleRate / 5) * FrameSize);
return true;
}
};
extern ReverbContainer *ForcedEnvironment;
#define AREA_SOUND_RADIUS (32.f)
#define PITCH_MULT (0.7937005f) /* Approx. 4 semitones lower; what Nash suggested */
#define PITCH(pitch) (snd_pitched ? (pitch)/128.f : 1.f)
static size_t GetChannelCount(ChannelConfig chans)
{
switch(chans)
{
case ChannelConfig_Mono: return 1;
case ChannelConfig_Stereo: return 2;
}
return 0;
}
static float GetRolloff(const FRolloffInfo *rolloff, float distance)
{
if(distance <= rolloff->MinDistance)
return 1.f;
// Logarithmic rolloff has no max distance where it goes silent.
if(rolloff->RolloffType == ROLLOFF_Log)
return rolloff->MinDistance /
(rolloff->MinDistance + rolloff->RolloffFactor*(distance-rolloff->MinDistance));
if(distance >= rolloff->MaxDistance)
return 0.f;
float volume = (rolloff->MaxDistance - distance) / (rolloff->MaxDistance - rolloff->MinDistance);
if(rolloff->RolloffType == ROLLOFF_Linear)
return volume;
if(rolloff->RolloffType == ROLLOFF_Custom && S_SoundCurve != NULL)
return S_SoundCurve[int(S_SoundCurveSize * (1.f - volume))] / 127.f;
return (powf(10.f, volume) - 1.f) / 9.f;
}
ALCdevice *OpenALSoundRenderer::InitDevice()
{
ALCdevice *device = NULL;
if (IsOpenALPresent())
{
if(strcmp(snd_aldevice, "Default") != 0)
{
device = alcOpenDevice(*snd_aldevice);
if(!device)
Printf(TEXTCOLOR_BLUE" Failed to open device " TEXTCOLOR_BOLD"%s" TEXTCOLOR_BLUE". Trying default.\n", *snd_aldevice);
}
if(!device)
{
device = alcOpenDevice(NULL);
if(!device)
{
Printf(TEXTCOLOR_RED" Could not open audio device\n");
}
}
}
else
{
Printf(TEXTCOLOR_ORANGE"Failed to load openal32.dll\n");
}
return device;
}
template<typename T>
static void LoadALFunc(const char *name, T *x)
{
*x = reinterpret_cast<T>(alGetProcAddress(name));
}
template<typename T>
static void LoadALCFunc(ALCdevice *device, const char *name, T *x)
{
*x = reinterpret_cast<T>(alcGetProcAddress(device, name));
}
#define LOAD_FUNC(x) (LoadALFunc(#x, &x))
#define LOAD_DEV_FUNC(d, x) (LoadALCFunc(d, #x, &x))
OpenALSoundRenderer::OpenALSoundRenderer()
: QuitThread(false), Device(NULL), Context(NULL), SFXPaused(0), PrevEnvironment(NULL), EnvSlot(0)
{
EnvFilters[0] = EnvFilters[1] = 0;
Printf("I_InitSound: Initializing OpenAL\n");
Device = InitDevice();
if (Device == NULL) return;
const ALCchar *current = NULL;
if(alcIsExtensionPresent(Device, "ALC_ENUMERATE_ALL_EXT"))
current = alcGetString(Device, ALC_ALL_DEVICES_SPECIFIER);
if(alcGetError(Device) != ALC_NO_ERROR || !current)
current = alcGetString(Device, ALC_DEVICE_SPECIFIER);
Printf(" Opened device " TEXTCOLOR_ORANGE"%s\n", current);
ALCint major=0, minor=0;
alcGetIntegerv(Device, ALC_MAJOR_VERSION, 1, &major);
alcGetIntegerv(Device, ALC_MINOR_VERSION, 1, &minor);
DPrintf(DMSG_SPAMMY, " ALC Version: " TEXTCOLOR_BLUE"%d.%d\n", major, minor);
DPrintf(DMSG_SPAMMY, " ALC Extensions: " TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_EXTENSIONS));
TArray<ALCint> attribs;
if(*snd_samplerate > 0)
{
attribs.Push(ALC_FREQUENCY);
attribs.Push(*snd_samplerate);
}
// Make sure one source is capable of stereo output with the rest doing
// mono, without running out of voices
attribs.Push(ALC_MONO_SOURCES);
attribs.Push(MAX<ALCint>(*snd_channels, 2) - 1);
attribs.Push(ALC_STEREO_SOURCES);
attribs.Push(1);
// Other attribs..?
attribs.Push(0);
Context = alcCreateContext(Device, &attribs[0]);
if(!Context || alcMakeContextCurrent(Context) == ALC_FALSE)
{
Printf(TEXTCOLOR_RED" Failed to setup context: %s\n", alcGetString(Device, alcGetError(Device)));
if(Context)
alcDestroyContext(Context);
Context = NULL;
alcCloseDevice(Device);
Device = NULL;
return;
}
attribs.Clear();
DPrintf(DMSG_SPAMMY, " Vendor: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VENDOR));
DPrintf(DMSG_SPAMMY, " Renderer: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_RENDERER));
DPrintf(DMSG_SPAMMY, " Version: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VERSION));
DPrintf(DMSG_SPAMMY, " Extensions: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_EXTENSIONS));
ALC.EXT_EFX = !!alcIsExtensionPresent(Device, "ALC_EXT_EFX");
ALC.EXT_disconnect = !!alcIsExtensionPresent(Device, "ALC_EXT_disconnect");
ALC.SOFT_pause_device = !!alcIsExtensionPresent(Device, "ALC_SOFT_pause_device");
AL.EXT_source_distance_model = !!alIsExtensionPresent("AL_EXT_source_distance_model");
AL.EXT_SOURCE_RADIUS = !!alIsExtensionPresent("AL_EXT_SOURCE_RADIUS");
AL.SOFT_deferred_updates = !!alIsExtensionPresent("AL_SOFT_deferred_updates");
AL.SOFT_loop_points = !!alIsExtensionPresent("AL_SOFT_loop_points");
alDopplerFactor(0.5f);
alSpeedOfSound(343.3f * 96.0f);
alDistanceModel(AL_INVERSE_DISTANCE);
if(AL.EXT_source_distance_model)
alEnable(AL_SOURCE_DISTANCE_MODEL);
if(AL.SOFT_deferred_updates)
{
LOAD_FUNC(alDeferUpdatesSOFT);
LOAD_FUNC(alProcessUpdatesSOFT);
}
else
{
alDeferUpdatesSOFT = _wrap_DeferUpdatesSOFT;
alProcessUpdatesSOFT = _wrap_ProcessUpdatesSOFT;
}
if(ALC.SOFT_pause_device)
{
LOAD_DEV_FUNC(Device, alcDevicePauseSOFT);
LOAD_DEV_FUNC(Device, alcDeviceResumeSOFT);
}
ALenum err = getALError();
if(err != AL_NO_ERROR)
{
alcMakeContextCurrent(NULL);
alcDestroyContext(Context);
Context = NULL;
alcCloseDevice(Device);
Device = NULL;
return;
}
ALCint numMono=0, numStereo=0;
alcGetIntegerv(Device, ALC_MONO_SOURCES, 1, &numMono);
alcGetIntegerv(Device, ALC_STEREO_SOURCES, 1, &numStereo);
// OpenAL specification doesn't require alcGetIntegerv() to return
// meaningful values for ALC_MONO_SOURCES and ALC_MONO_SOURCES.
// At least Apple's OpenAL implementation returns zeroes,
// although it can generate reasonable number of sources.
const int numChannels = MAX<int>(*snd_channels, 2);
int numSources = numMono + numStereo;
if (0 == numSources)
{
numSources = numChannels;
}
Sources.Resize(MIN<int>(numChannels, numSources));
for(unsigned i = 0;i < Sources.Size();i++)
{
alGenSources(1, &Sources[i]);
if(getALError() != AL_NO_ERROR)
{
Sources.Resize(i);
Sources.ShrinkToFit();
break;
}
}
if(Sources.Size() == 0)
{
Printf(TEXTCOLOR_RED" Error: could not generate any sound sources!\n");
alcMakeContextCurrent(NULL);
alcDestroyContext(Context);
Context = NULL;
alcCloseDevice(Device);
Device = NULL;
return;
}
FreeSfx = Sources;
DPrintf(DMSG_NOTIFY, " Allocated " TEXTCOLOR_BLUE"%u" TEXTCOLOR_NORMAL" sources\n", Sources.Size());
WasInWater = false;
if(*snd_efx && ALC.EXT_EFX)
{
// EFX function pointers
LOAD_FUNC(alGenEffects);
LOAD_FUNC(alDeleteEffects);
LOAD_FUNC(alIsEffect);
LOAD_FUNC(alEffecti);
LOAD_FUNC(alEffectiv);
LOAD_FUNC(alEffectf);
LOAD_FUNC(alEffectfv);
LOAD_FUNC(alGetEffecti);
LOAD_FUNC(alGetEffectiv);
LOAD_FUNC(alGetEffectf);
LOAD_FUNC(alGetEffectfv);
LOAD_FUNC(alGenFilters);
LOAD_FUNC(alDeleteFilters);
LOAD_FUNC(alIsFilter);
LOAD_FUNC(alFilteri);
LOAD_FUNC(alFilteriv);
LOAD_FUNC(alFilterf);
LOAD_FUNC(alFilterfv);
LOAD_FUNC(alGetFilteri);
LOAD_FUNC(alGetFilteriv);
LOAD_FUNC(alGetFilterf);
LOAD_FUNC(alGetFilterfv);
LOAD_FUNC(alGenAuxiliaryEffectSlots);
LOAD_FUNC(alDeleteAuxiliaryEffectSlots);
LOAD_FUNC(alIsAuxiliaryEffectSlot);
LOAD_FUNC(alAuxiliaryEffectSloti);
LOAD_FUNC(alAuxiliaryEffectSlotiv);
LOAD_FUNC(alAuxiliaryEffectSlotf);
LOAD_FUNC(alAuxiliaryEffectSlotfv);
LOAD_FUNC(alGetAuxiliaryEffectSloti);
LOAD_FUNC(alGetAuxiliaryEffectSlotiv);
LOAD_FUNC(alGetAuxiliaryEffectSlotf);
LOAD_FUNC(alGetAuxiliaryEffectSlotfv);
if(getALError() == AL_NO_ERROR)
{
ALuint envReverb;
alGenEffects(1, &envReverb);
if(getALError() == AL_NO_ERROR)
{
alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
if(alGetError() == AL_NO_ERROR)
DPrintf(DMSG_SPAMMY, " EAX Reverb found\n");
alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
if(alGetError() == AL_NO_ERROR)
DPrintf(DMSG_SPAMMY, " Standard Reverb found\n");
alDeleteEffects(1, &envReverb);
getALError();
}
alGenAuxiliaryEffectSlots(1, &EnvSlot);
alGenFilters(2, EnvFilters);
if(getALError() == AL_NO_ERROR)
{
alFilteri(EnvFilters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
alFilteri(EnvFilters[1], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
if(getALError() == AL_NO_ERROR)
DPrintf(DMSG_SPAMMY, " Lowpass found\n");
else
{
alDeleteFilters(2, EnvFilters);
EnvFilters[0] = EnvFilters[1] = 0;
alDeleteAuxiliaryEffectSlots(1, &EnvSlot);
EnvSlot = 0;
getALError();
}
}
else
{
alDeleteFilters(2, EnvFilters);
alDeleteAuxiliaryEffectSlots(1, &EnvSlot);
EnvFilters[0] = EnvFilters[1] = 0;
EnvSlot = 0;
getALError();
}
}
}
if(EnvSlot)
Printf(" EFX enabled\n");
}
#undef LOAD_DEV_FUNC
#undef LOAD_FUNC
OpenALSoundRenderer::~OpenALSoundRenderer()
{
if(!Device)
return;
if(StreamThread.joinable())
{
std::unique_lock<std::mutex> lock(StreamLock);
QuitThread.store(true);
lock.unlock();
StreamWake.notify_all();
StreamThread.join();
}
while(Streams.Size() > 0)
delete Streams[0];
alDeleteSources(Sources.Size(), &Sources[0]);
Sources.Clear();
FreeSfx.Clear();
SfxGroup.Clear();
PausableSfx.Clear();
ReverbSfx.Clear();
if(EnvEffects.CountUsed() > 0)
{
EffectMapIter iter(EnvEffects);
EffectMap::Pair *pair;
while(iter.NextPair(pair))
alDeleteEffects(1, &(pair->Value));
}
EnvEffects.Clear();
if(EnvSlot)
{
alDeleteAuxiliaryEffectSlots(1, &EnvSlot);
alDeleteFilters(2, EnvFilters);
}
EnvSlot = 0;
EnvFilters[0] = EnvFilters[1] = 0;
alcMakeContextCurrent(NULL);
alcDestroyContext(Context);
Context = NULL;
alcCloseDevice(Device);
Device = NULL;
}
void OpenALSoundRenderer::BackgroundProc()
{
std::unique_lock<std::mutex> lock(StreamLock);
while(!QuitThread.load())
{
if(Streams.Size() == 0)
{
// If there's nothing to play, wait indefinitely.
StreamWake.wait(lock);
}
else
{
// Else, process all active streams and sleep for 100ms
for(size_t i = 0;i < Streams.Size();i++)
Streams[i]->Process();
StreamWake.wait_for(lock, std::chrono::milliseconds(100));
}
}
}
void OpenALSoundRenderer::AddStream(OpenALSoundStream *stream)
{
std::unique_lock<std::mutex> lock(StreamLock);
Streams.Push(stream);
lock.unlock();
// There's a stream to play, make sure the background thread is aware
StreamWake.notify_all();
}
void OpenALSoundRenderer::RemoveStream(OpenALSoundStream *stream)
{
std::unique_lock<std::mutex> lock(StreamLock);
unsigned int idx = Streams.Find(stream);
if(idx < Streams.Size())
Streams.Delete(idx);
}
void OpenALSoundRenderer::SetSfxVolume(float volume)
{
SfxVolume = volume;
FSoundChan *schan = Channels;
while(schan)
{
if(schan->SysChannel != NULL)
{
ALuint source = GET_PTRID(schan->SysChannel);
volume = SfxVolume;
alDeferUpdatesSOFT();
alSourcef(source, AL_MAX_GAIN, volume);
alSourcef(source, AL_GAIN, volume * schan->Volume);
}
schan = schan->NextChan;
}
alProcessUpdatesSOFT();
getALError();
}
void OpenALSoundRenderer::SetMusicVolume(float volume)
{
MusicVolume = volume;
for(uint32_t i = 0;i < Streams.Size();++i)
Streams[i]->UpdateVolume();
}
unsigned int OpenALSoundRenderer::GetMSLength(SoundHandle sfx)
{
if(sfx.data)
{
ALuint buffer = GET_PTRID(sfx.data);
if(alIsBuffer(buffer))
{
ALint bits, channels, freq, size;
alGetBufferi(buffer, AL_BITS, &bits);
alGetBufferi(buffer, AL_CHANNELS, &channels);
alGetBufferi(buffer, AL_FREQUENCY, &freq);
alGetBufferi(buffer, AL_SIZE, &size);
if(getALError() == AL_NO_ERROR)
return (unsigned int)(size / (channels*bits/8) * 1000. / freq);
}
}
return 0;
}
unsigned int OpenALSoundRenderer::GetSampleLength(SoundHandle sfx)
{
if(sfx.data)
{
ALuint buffer = GET_PTRID(sfx.data);
ALint bits, channels, size;
alGetBufferi(buffer, AL_BITS, &bits);
alGetBufferi(buffer, AL_CHANNELS, &channels);
alGetBufferi(buffer, AL_SIZE, &size);
if(getALError() == AL_NO_ERROR)
return (ALsizei)(size / (channels * bits / 8));
}
return 0;
}
float OpenALSoundRenderer::GetOutputRate()
{
ALCint rate = 44100; // Default, just in case
alcGetIntegerv(Device, ALC_FREQUENCY, 1, &rate);
return (float)rate;
}
std::pair<SoundHandle,bool> OpenALSoundRenderer::LoadSoundRaw(uint8_t *sfxdata, int length, int frequency, int channels, int bits, int loopstart, int loopend, bool monoize)
{
SoundHandle retval = { NULL };
if(length == 0) return std::make_pair(retval, true);
if(bits == -8)
{
// Simple signed->unsigned conversion
for(int i = 0;i < length;i++)
sfxdata[i] ^= 0x80;
bits = -bits;
}
if(channels > 1 && monoize)
{
size_t frames = length / channels * 8 / bits;
if(bits == 16)
{
for(size_t i = 0;i < frames;i++)
{
int sum = 0;
for(int c = 0;c < channels;c++)
sum += ((short*)sfxdata)[i*channels + c];
((short*)sfxdata)[i] = sum / channels;
}
}
else if(bits == 8)
{
for(size_t i = 0;i < frames;i++)
{
int sum = 0;
for(int c = 0;c < channels;c++)
sum += sfxdata[i*channels + c] - 128;
sfxdata[i] = (sum / channels) + 128;
}
}
length /= channels;
channels = 1;
}
ALenum format = AL_NONE;
if(bits == 16)
{
if(channels == 1) format = AL_FORMAT_MONO16;
if(channels == 2) format = AL_FORMAT_STEREO16;
}
else if(bits == 8)
{
if(channels == 1) format = AL_FORMAT_MONO8;
if(channels == 2) format = AL_FORMAT_STEREO8;
}
if(format == AL_NONE || frequency <= 0)
{
Printf("Unhandled format: %d bit, %d channel, %d hz\n", bits, channels, frequency);
return std::make_pair(retval, true);
}
length -= length%(channels*bits/8);
ALenum err;
ALuint buffer = 0;
alGenBuffers(1, &buffer);
alBufferData(buffer, format, sfxdata, length, frequency);
if((err=getALError()) != AL_NO_ERROR)
{
Printf("Failed to buffer data: %s\n", alGetString(err));
alDeleteBuffers(1, &buffer);
getALError();
return std::make_pair(retval, true);
}
if((loopstart > 0 || loopend > 0) && AL.SOFT_loop_points)
{
if(loopstart < 0)
loopstart = 0;
if(loopend < loopstart)
loopend = length / (channels*bits/8);
ALint loops[2] = { loopstart, loopend };
DPrintf(DMSG_NOTIFY, "Setting loop points %d -> %d\n", loops[0], loops[1]);
alBufferiv(buffer, AL_LOOP_POINTS_SOFT, loops);
getALError();
}
else if(loopstart > 0 || loopend > 0)
{
static bool warned = false;
if(!warned)
Printf(DMSG_WARNING, "Loop points not supported!\n");
warned = true;
}
retval.data = MAKE_PTRID(buffer);
return std::make_pair(retval, channels==1);
}
std::pair<SoundHandle,bool> OpenALSoundRenderer::LoadSound(uint8_t *sfxdata, int length, bool monoize)
{
SoundHandle retval = { NULL };
MemoryReader reader((const char*)sfxdata, length);
ALenum format = AL_NONE;
ChannelConfig chans;
SampleType type;
int srate;
std::unique_ptr<SoundDecoder> decoder(CreateDecoder(&reader));
if(!decoder) return std::make_pair(retval, true);
decoder->getInfo(&srate, &chans, &type);
if(chans == ChannelConfig_Mono || monoize)
{
if(type == SampleType_UInt8) format = AL_FORMAT_MONO8;
if(type == SampleType_Int16) format = AL_FORMAT_MONO16;
}
else if(chans == ChannelConfig_Stereo)
{
if(type == SampleType_UInt8) format = AL_FORMAT_STEREO8;
if(type == SampleType_Int16) format = AL_FORMAT_STEREO16;
}
if(format == AL_NONE)
{
Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
GetSampleTypeName(type));
return std::make_pair(retval, true);
}
TArray<char> data = decoder->readAll();
if(chans != ChannelConfig_Mono && monoize)
{
size_t chancount = GetChannelCount(chans);
size_t frames = data.Size() / chancount /
(type == SampleType_Int16 ? 2 : 1);
if(type == SampleType_Int16)
{
short *sfxdata = (short*)&data[0];
for(size_t i = 0;i < frames;i++)
{
int sum = 0;
for(size_t c = 0;c < chancount;c++)
sum += sfxdata[i*chancount + c];
sfxdata[i] = short(sum / chancount);
}
}
else if(type == SampleType_UInt8)
{
uint8_t *sfxdata = (uint8_t*)&data[0];
for(size_t i = 0;i < frames;i++)
{
int sum = 0;
for(size_t c = 0;c < chancount;c++)
sum += sfxdata[i*chancount + c] - 128;
sfxdata[i] = uint8_t((sum / chancount) + 128);
}
}
data.Resize(unsigned(data.Size()/chancount));
}
ALenum err;
ALuint buffer = 0;
alGenBuffers(1, &buffer);
alBufferData(buffer, format, &data[0], data.Size(), srate);
if((err=getALError()) != AL_NO_ERROR)
{
Printf("Failed to buffer data: %s\n", alGetString(err));
alDeleteBuffers(1, &buffer);
getALError();
return std::make_pair(retval, true);
}
retval.data = MAKE_PTRID(buffer);
return std::make_pair(retval, (chans == ChannelConfig_Mono || monoize));
}
void OpenALSoundRenderer::UnloadSound(SoundHandle sfx)
{
if(!sfx.data)
return;
ALuint buffer = GET_PTRID(sfx.data);
FSoundChan *schan = Channels;
while(schan)
{
if(schan->SysChannel)
{
ALint bufID = 0;
alGetSourcei(GET_PTRID(schan->SysChannel), AL_BUFFER, &bufID);
if((ALuint)bufID == buffer)
{
FSoundChan *next = schan->NextChan;
StopChannel(schan);
schan = next;
continue;
}
}
schan = schan->NextChan;
}
alDeleteBuffers(1, &buffer);
getALError();
}
SoundStream *OpenALSoundRenderer::CreateStream(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata)
{
if(StreamThread.get_id() == std::thread::id())
StreamThread = std::thread(std::mem_fn(&OpenALSoundRenderer::BackgroundProc), this);
OpenALSoundStream *stream = new OpenALSoundStream(this);
if (!stream->Init(callback, buffbytes, flags, samplerate, userdata))
{
delete stream;
return NULL;
}
return stream;
}
SoundStream *OpenALSoundRenderer::OpenStream(FileReader *reader, int flags)
{
if(StreamThread.get_id() == std::thread::id())
StreamThread = std::thread(std::mem_fn(&OpenALSoundRenderer::BackgroundProc), this);
OpenALSoundStream *stream = new OpenALSoundStream(this);
if (!stream->Init(reader, !!(flags&SoundStream::Loop)))
{
delete stream;
return NULL;
}
return stream;
}
FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, int pitch, int chanflags, FISoundChannel *reuse_chan)
{
if(FreeSfx.Size() == 0)
{
FSoundChan *lowest = FindLowestChannel();
if(lowest) StopChannel(lowest);
if(FreeSfx.Size() == 0)
return NULL;
}
ALuint buffer = GET_PTRID(sfx.data);
ALuint source = FreeSfx.Last();
alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
alSource3f(source, AL_VELOCITY, 0.f, 0.f, 0.f);
alSource3f(source, AL_DIRECTION, 0.f, 0.f, 0.f);
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSourcei(source, AL_LOOPING, (chanflags&SNDF_LOOP) ? AL_TRUE : AL_FALSE);
alSourcef(source, AL_REFERENCE_DISTANCE, 1.f);
alSourcef(source, AL_MAX_DISTANCE, 1000.f);
alSourcef(source, AL_ROLLOFF_FACTOR, 0.f);
alSourcef(source, AL_MAX_GAIN, SfxVolume);
alSourcef(source, AL_GAIN, SfxVolume*vol);
if(AL.EXT_SOURCE_RADIUS)
alSourcef(source, AL_SOURCE_RADIUS, 0.f);
if(EnvSlot)
{
if(!(chanflags&SNDF_NOREVERB))
{
alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]);
alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
}
else
{
alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL);
alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL);
}
alSourcef(source, AL_ROOM_ROLLOFF_FACTOR, 0.f);
}
if(WasInWater && !(chanflags&SNDF_NOREVERB))
alSourcef(source, AL_PITCH, PITCH(pitch)*PITCH_MULT);
else
alSourcef(source, AL_PITCH, PITCH(pitch));
if(!reuse_chan || reuse_chan->StartTime.AsOne == 0)
alSourcef(source, AL_SEC_OFFSET, 0.f);
else
{
if((chanflags&SNDF_ABSTIME))
alSourcef(source, AL_SEC_OFFSET, reuse_chan->StartTime.Lo/1000.f);
else
{
float offset = std::chrono::duration_cast<std::chrono::duration<float>>(
std::chrono::steady_clock::now().time_since_epoch() -
std::chrono::steady_clock::time_point::duration(reuse_chan->StartTime.AsOne)
).count();
if(offset > 0.f) alSourcef(source, AL_SEC_OFFSET, offset);
}
}
if(getALError() != AL_NO_ERROR)
return NULL;
alSourcei(source, AL_BUFFER, buffer);
if((chanflags&SNDF_NOPAUSE) || !SFXPaused)
alSourcePlay(source);
if(getALError() != AL_NO_ERROR)
{
alSourcei(source, AL_BUFFER, 0);
getALError();
return NULL;
}
if(!(chanflags&SNDF_NOREVERB))
ReverbSfx.Push(source);
if(!(chanflags&SNDF_NOPAUSE))
PausableSfx.Push(source);
SfxGroup.Push(source);
FreeSfx.Pop();
FISoundChannel *chan = reuse_chan;
if(!chan) chan = S_GetChannel(MAKE_PTRID(source));
else chan->SysChannel = MAKE_PTRID(source);
chan->Rolloff.RolloffType = ROLLOFF_Log;
chan->Rolloff.RolloffFactor = 0.f;
chan->Rolloff.MinDistance = 1.f;
chan->DistanceSqr = 0.f;
chan->ManualRolloff = false;
return chan;
}
FISoundChannel *OpenALSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener *listener, float vol,
FRolloffInfo *rolloff, float distscale, int pitch, int priority, const FVector3 &pos, const FVector3 &vel,
int channum, int chanflags, FISoundChannel *reuse_chan)
{
float dist_sqr = (float)(pos - listener->position).LengthSquared();
if(FreeSfx.Size() == 0)
{
FSoundChan *lowest = FindLowestChannel();
if(lowest)
{
if(lowest->Priority < priority || (lowest->Priority == priority &&
lowest->DistanceSqr > dist_sqr))
StopChannel(lowest);
}
if(FreeSfx.Size() == 0)
return NULL;
}
bool manualRolloff = true;
ALuint buffer = GET_PTRID(sfx.data);
ALuint source = FreeSfx.Last();
if(rolloff->RolloffType == ROLLOFF_Log)
{
if(AL.EXT_source_distance_model)
alSourcei(source, AL_DISTANCE_MODEL, AL_INVERSE_DISTANCE);
alSourcef(source, AL_REFERENCE_DISTANCE, rolloff->MinDistance/distscale);
alSourcef(source, AL_MAX_DISTANCE, (1000.f+rolloff->MinDistance)/distscale);
alSourcef(source, AL_ROLLOFF_FACTOR, rolloff->RolloffFactor);
manualRolloff = false;
}
else if(rolloff->RolloffType == ROLLOFF_Linear && AL.EXT_source_distance_model)
{
alSourcei(source, AL_DISTANCE_MODEL, AL_LINEAR_DISTANCE);
alSourcef(source, AL_REFERENCE_DISTANCE, rolloff->MinDistance/distscale);
alSourcef(source, AL_MAX_DISTANCE, rolloff->MaxDistance/distscale);
alSourcef(source, AL_ROLLOFF_FACTOR, 1.f);
manualRolloff = false;
}
if(manualRolloff)
{
// How manual rolloff works:
//
// If a sound is using Custom or Doom style rolloff, or Linear style
// when AL_EXT_source_distance_model is not supported, we have to play
// around a bit to get appropriate distance attenation. What we do is
// calculate the attenuation that should be applied, then given an
// Inverse Distance rolloff model with OpenAL, reverse the calculation
// to get the distance needed for that much attenuation. The Inverse
// Distance calculation is:
//
// Gain = MinDist / (MinDist + RolloffFactor*(Distance - MinDist))
//
// Thus, the reverse is:
//
// Distance = (MinDist/Gain - MinDist)/RolloffFactor + MinDist
//
// This can be simplified by using a MinDist and RolloffFactor of 1,
// which makes it:
//
// Distance = 1.0f/Gain;
//
// The source position is then set that many units away from the
// listener position, and OpenAL takes care of the rest.
if(AL.EXT_source_distance_model)
alSourcei(source, AL_DISTANCE_MODEL, AL_INVERSE_DISTANCE);
alSourcef(source, AL_REFERENCE_DISTANCE, 1.f);
alSourcef(source, AL_MAX_DISTANCE, 100000.f);
alSourcef(source, AL_ROLLOFF_FACTOR, 1.f);
FVector3 dir = pos - listener->position;
if(dir.DoesNotApproximatelyEqual(FVector3(0.f, 0.f, 0.f)))
{
float gain = GetRolloff(rolloff, sqrtf(dist_sqr) * distscale);
dir.MakeResize((gain > 0.00001f) ? 1.f/gain : 100000.f);
}
if(AL.EXT_SOURCE_RADIUS)
{
/* Since the OpenAL distance is decoupled from the sound's distance, get the OpenAL
* distance that corresponds to the area radius. */
alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ?
// Clamp in case the max distance is <= the area radius
1.f/MAX<float>(GetRolloff(rolloff, AREA_SOUND_RADIUS), 0.00001f) : 0.f
);
}
else if((chanflags&SNDF_AREA) && dist_sqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
{
FVector3 amb(0.f, !(dir.Y>=0.f) ? -1.f : 1.f, 0.f);
float a = sqrtf(dist_sqr) / AREA_SOUND_RADIUS;
dir = amb + (dir-amb)*a;
}
dir += listener->position;
if(dist_sqr < (0.0004f*0.0004f))
{
// Head relative
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
}
else
{
alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
alSource3f(source, AL_POSITION, dir[0], dir[1], -dir[2]);
}
}
else
{
FVector3 dir = pos;
if(AL.EXT_SOURCE_RADIUS)
alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ? AREA_SOUND_RADIUS : 0.f);
else if((chanflags&SNDF_AREA) && dist_sqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
{
dir -= listener->position;
float mindist = rolloff->MinDistance/distscale;
FVector3 amb(0.f, !(dir.Y>=0.f) ? -mindist : mindist, 0.f);
float a = sqrtf(dist_sqr) / AREA_SOUND_RADIUS;
dir = amb + (dir-amb)*a;
dir += listener->position;
}
if(dist_sqr < (0.0004f*0.0004f))
{
// Head relative
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
}
else
{
alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
alSource3f(source, AL_POSITION, dir[0], dir[1], -dir[2]);
}
}
alSource3f(source, AL_VELOCITY, vel[0], vel[1], -vel[2]);
alSource3f(source, AL_DIRECTION, 0.f, 0.f, 0.f);
alSourcei(source, AL_LOOPING, (chanflags&SNDF_LOOP) ? AL_TRUE : AL_FALSE);
alSourcef(source, AL_MAX_GAIN, SfxVolume);
alSourcef(source, AL_GAIN, SfxVolume*vol);
if(EnvSlot)
{
if(!(chanflags&SNDF_NOREVERB))
{
alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]);
alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
}
else
{
alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL);
alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL);
}
alSourcef(source, AL_ROOM_ROLLOFF_FACTOR, 0.f);
}
if(WasInWater && !(chanflags&SNDF_NOREVERB))
alSourcef(source, AL_PITCH, PITCH(pitch)*PITCH_MULT);
else
alSourcef(source, AL_PITCH, PITCH(pitch));
if(!reuse_chan || reuse_chan->StartTime.AsOne == 0)
alSourcef(source, AL_SEC_OFFSET, 0.f);
else
{
if((chanflags&SNDF_ABSTIME))
alSourcef(source, AL_SEC_OFFSET, reuse_chan->StartTime.Lo/1000.f);
else
{
float offset = std::chrono::duration_cast<std::chrono::duration<float>>(
std::chrono::steady_clock::now().time_since_epoch() -
std::chrono::steady_clock::time_point::duration(reuse_chan->StartTime.AsOne)
).count();
if(offset > 0.f) alSourcef(source, AL_SEC_OFFSET, offset);
}
}
if(getALError() != AL_NO_ERROR)
return NULL;
alSourcei(source, AL_BUFFER, buffer);
if((chanflags&SNDF_NOPAUSE) || !SFXPaused)
alSourcePlay(source);
if(getALError() != AL_NO_ERROR)
{
alSourcei(source, AL_BUFFER, 0);
getALError();
return NULL;
}
if(!(chanflags&SNDF_NOREVERB))
ReverbSfx.Push(source);
if(!(chanflags&SNDF_NOPAUSE))
PausableSfx.Push(source);
SfxGroup.Push(source);
FreeSfx.Pop();
FISoundChannel *chan = reuse_chan;
if(!chan) chan = S_GetChannel(MAKE_PTRID(source));
else chan->SysChannel = MAKE_PTRID(source);
chan->Rolloff = *rolloff;
chan->DistanceSqr = dist_sqr;
chan->ManualRolloff = manualRolloff;
return chan;
}
void OpenALSoundRenderer::ChannelVolume(FISoundChannel *chan, float volume)
{
if(chan == NULL || chan->SysChannel == NULL)
return;
alDeferUpdatesSOFT();
ALuint source = GET_PTRID(chan->SysChannel);
alSourcef(source, AL_GAIN, SfxVolume * volume);
}
void OpenALSoundRenderer::StopChannel(FISoundChannel *chan)
{
if(chan == NULL || chan->SysChannel == NULL)
return;
ALuint source = GET_PTRID(chan->SysChannel);
// Release first, so it can be properly marked as evicted if it's being
// forcefully killed
S_ChannelEnded(chan);
alSourceRewind(source);
alSourcei(source, AL_BUFFER, 0);
getALError();
uint32_t i;
if((i=PausableSfx.Find(source)) < PausableSfx.Size())
PausableSfx.Delete(i);
if((i=ReverbSfx.Find(source)) < ReverbSfx.Size())
ReverbSfx.Delete(i);
SfxGroup.Delete(SfxGroup.Find(source));
FreeSfx.Push(source);
}
unsigned int OpenALSoundRenderer::GetPosition(FISoundChannel *chan)
{
if(chan == NULL || chan->SysChannel == NULL)
return 0;
ALint pos;
alGetSourcei(GET_PTRID(chan->SysChannel), AL_SAMPLE_OFFSET, &pos);
if(getALError() == AL_NO_ERROR)
return pos;
return 0;
}
void OpenALSoundRenderer::SetSfxPaused(bool paused, int slot)
{
int oldslots = SFXPaused;
if(paused)
{
SFXPaused |= 1 << slot;
if(oldslots == 0 && PausableSfx.Size() > 0)
{
alSourcePausev(PausableSfx.Size(), &PausableSfx[0]);
getALError();
PurgeStoppedSources();
}
}
else
{
SFXPaused &= ~(1 << slot);
if(SFXPaused == 0 && oldslots != 0 && PausableSfx.Size() > 0)
{
alSourcePlayv(PausableSfx.Size(), &PausableSfx[0]);
getALError();
}
}
}
void OpenALSoundRenderer::SetInactive(SoundRenderer::EInactiveState state)
{
switch(state)
{
case SoundRenderer::INACTIVE_Active:
alListenerf(AL_GAIN, 1.0f);
if(ALC.SOFT_pause_device)
{
alcDeviceResumeSOFT(Device);
getALCError(Device);
}
break;
case SoundRenderer::INACTIVE_Complete:
if(ALC.SOFT_pause_device)
{
alcDevicePauseSOFT(Device);
getALCError(Device);
}
/* fall-through */
case SoundRenderer::INACTIVE_Mute:
alListenerf(AL_GAIN, 0.0f);
break;
}
}
void OpenALSoundRenderer::Sync(bool sync)
{
if(sync)
{
if(SfxGroup.Size() > 0)
{
alSourcePausev(SfxGroup.Size(), &SfxGroup[0]);
getALError();
PurgeStoppedSources();
}
}
else
{
// Might already be something to handle this; basically, get a vector
// of all values in SfxGroup that are not also in PausableSfx (when
// SFXPaused is non-0).
TArray<ALuint> toplay = SfxGroup;
if(SFXPaused)
{
uint32_t i = 0;
while(i < toplay.Size())
{
uint32_t p = PausableSfx.Find(toplay[i]);
if(p < PausableSfx.Size())
toplay.Delete(i);
else
i++;
}
}
if(toplay.Size() > 0)
{
alSourcePlayv(toplay.Size(), &toplay[0]);
getALError();
}
}
}
void OpenALSoundRenderer::UpdateSoundParams3D(SoundListener *listener, FISoundChannel *chan, bool areasound, const FVector3 &pos, const FVector3 &vel)
{
if(chan == NULL || chan->SysChannel == NULL)
return;
FVector3 dir = pos - listener->position;
chan->DistanceSqr = (float)dir.LengthSquared();
if(chan->ManualRolloff)
{
if(!AL.EXT_SOURCE_RADIUS && areasound &&
chan->DistanceSqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
{
FVector3 amb(0.f, !(dir.Y>=0.f) ? -1.f : 1.f, 0.f);
float a = sqrtf(chan->DistanceSqr) / AREA_SOUND_RADIUS;
dir = amb + (dir-amb)*a;
}
if(dir.DoesNotApproximatelyEqual(FVector3(0.f, 0.f, 0.f)))
{
float gain = GetRolloff(&chan->Rolloff, sqrtf(chan->DistanceSqr)*chan->DistanceScale);
dir.MakeResize((gain > 0.00001f) ? 1.f/gain : 100000.f);
}
}
else if(!AL.EXT_SOURCE_RADIUS && areasound &&
chan->DistanceSqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
{
float mindist = chan->Rolloff.MinDistance / chan->DistanceScale;
FVector3 amb(0.f, !(dir.Y>=0.f) ? -mindist : mindist, 0.f);
float a = sqrtf(chan->DistanceSqr) / AREA_SOUND_RADIUS;
dir = amb + (dir-amb)*a;
}
dir += listener->position;
alDeferUpdatesSOFT();
ALuint source = GET_PTRID(chan->SysChannel);
if(chan->DistanceSqr < (0.0004f*0.0004f))
{
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
}
else
{
alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
alSource3f(source, AL_POSITION, dir[0], dir[1], -dir[2]);
}
alSource3f(source, AL_VELOCITY, vel[0], vel[1], -vel[2]);
getALError();
}
void OpenALSoundRenderer::UpdateListener(SoundListener *listener)
{
if(!listener->valid)
return;
alDeferUpdatesSOFT();
float angle = listener->angle;
ALfloat orient[6];
// forward
orient[0] = cosf(angle);
orient[1] = 0.f;
orient[2] = -sinf(angle);
// up
orient[3] = 0.f;
orient[4] = 1.f;
orient[5] = 0.f;
alListenerfv(AL_ORIENTATION, orient);
alListener3f(AL_POSITION, listener->position.X,
listener->position.Y,
-listener->position.Z);
alListener3f(AL_VELOCITY, listener->velocity.X,
listener->velocity.Y,
-listener->velocity.Z);
getALError();
const ReverbContainer *env = ForcedEnvironment;
if(!env)
{
env = listener->Environment;
if(!env)
env = DefaultEnvironments[0];
}
if(env != PrevEnvironment || env->Modified)
{
PrevEnvironment = env;
DPrintf(DMSG_NOTIFY, "Reverb Environment %s\n", env->Name);
if(EnvSlot != 0)
LoadReverb(env);
const_cast<ReverbContainer*>(env)->Modified = false;
}
// NOTE: Moving into and out of water will undo pitch variations on sounds.
if(listener->underwater || env->SoftwareWater)
{
if(!WasInWater)
{
WasInWater = true;
if(EnvSlot != 0 && *snd_waterreverb)
{
// Find the "Underwater" reverb environment
env = S_FindEnvironment(0x1600);
LoadReverb(env ? env : DefaultEnvironments[0]);
alFilterf(EnvFilters[0], AL_LOWPASS_GAIN, 1.f);
alFilterf(EnvFilters[0], AL_LOWPASS_GAINHF, 0.125f);
alFilterf(EnvFilters[1], AL_LOWPASS_GAIN, 1.f);
alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.f);
// Apply the updated filters on the sources
for(uint32_t i = 0;i < ReverbSfx.Size();++i)
{
alSourcei(ReverbSfx[i], AL_DIRECT_FILTER, EnvFilters[0]);
alSource3i(ReverbSfx[i], AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
}
}
for(uint32_t i = 0;i < ReverbSfx.Size();++i)
alSourcef(ReverbSfx[i], AL_PITCH, PITCH_MULT);
getALError();
}
}
else if(WasInWater)
{
WasInWater = false;
if(EnvSlot != 0)
{
LoadReverb(env);
alFilterf(EnvFilters[0], AL_LOWPASS_GAIN, 1.f);
alFilterf(EnvFilters[0], AL_LOWPASS_GAINHF, 1.f);
alFilterf(EnvFilters[1], AL_LOWPASS_GAIN, 1.f);
alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.f);
for(uint32_t i = 0;i < ReverbSfx.Size();++i)
{
alSourcei(ReverbSfx[i], AL_DIRECT_FILTER, EnvFilters[0]);
alSource3i(ReverbSfx[i], AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
}
}
for(uint32_t i = 0;i < ReverbSfx.Size();++i)
alSourcef(ReverbSfx[i], AL_PITCH, 1.f);
getALError();
}
}
void OpenALSoundRenderer::UpdateSounds()
{
alProcessUpdatesSOFT();
if(ALC.EXT_disconnect)
{
ALCint connected = ALC_TRUE;
alcGetIntegerv(Device, ALC_CONNECTED, 1, &connected);
if(connected == ALC_FALSE)
{
Printf("Sound device disconnected; restarting...\n");
static char snd_reset[] = "snd_reset";
AddCommandString(snd_reset);
return;
}
}
PurgeStoppedSources();
}
bool OpenALSoundRenderer::IsValid()
{
return Device != NULL;
}
void OpenALSoundRenderer::MarkStartTime(FISoundChannel *chan)
{
// FIXME: Get current time (preferably from the audio clock, but the system
// time will have to do)
chan->StartTime.AsOne = std::chrono::steady_clock::now().time_since_epoch().count();
}
float OpenALSoundRenderer::GetAudibility(FISoundChannel *chan)
{
if(chan == NULL || chan->SysChannel == NULL)
return 0.f;
ALuint source = GET_PTRID(chan->SysChannel);
ALfloat volume = 0.f;
alGetSourcef(source, AL_GAIN, &volume);
getALError();
volume *= GetRolloff(&chan->Rolloff, sqrtf(chan->DistanceSqr) * chan->DistanceScale);
return volume;
}
void OpenALSoundRenderer::PrintStatus()
{
Printf("Output device: " TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_DEVICE_SPECIFIER));
getALCError(Device);
ALCint frequency, major, minor, mono, stereo;
alcGetIntegerv(Device, ALC_FREQUENCY, 1, &frequency);
alcGetIntegerv(Device, ALC_MAJOR_VERSION, 1, &major);
alcGetIntegerv(Device, ALC_MINOR_VERSION, 1, &minor);
alcGetIntegerv(Device, ALC_MONO_SOURCES, 1, &mono);
alcGetIntegerv(Device, ALC_STEREO_SOURCES, 1, &stereo);
if(getALCError(Device) == AL_NO_ERROR)
{
Printf("Device sample rate: " TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL"hz\n", frequency);
Printf("ALC Version: " TEXTCOLOR_BLUE"%d.%d\n", major, minor);
Printf("ALC Extensions: " TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_EXTENSIONS));
Printf("Available sources: " TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL" (" TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL" mono, " TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL" stereo)\n", mono+stereo, mono, stereo);
}
if(!alcIsExtensionPresent(Device, "ALC_EXT_EFX"))
Printf("EFX not found\n");
else
{
ALCint sends;
alcGetIntegerv(Device, ALC_EFX_MAJOR_VERSION, 1, &major);
alcGetIntegerv(Device, ALC_EFX_MINOR_VERSION, 1, &minor);
alcGetIntegerv(Device, ALC_MAX_AUXILIARY_SENDS, 1, &sends);
if(getALCError(Device) == AL_NO_ERROR)
{
Printf("EFX Version: " TEXTCOLOR_BLUE"%d.%d\n", major, minor);
Printf("Auxiliary sends: " TEXTCOLOR_BLUE"%d\n", sends);
}
}
Printf("Vendor: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VENDOR));
Printf("Renderer: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_RENDERER));
Printf("Version: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VERSION));
Printf("Extensions: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_EXTENSIONS));
getALError();
}
FString OpenALSoundRenderer::GatherStats()
{
ALCint updates = 1;
alcGetIntegerv(Device, ALC_REFRESH, 1, &updates);
getALCError(Device);
uint32_t total = Sources.Size();
uint32_t used = SfxGroup.Size()+Streams.Size();
uint32_t unused = FreeSfx.Size();
FString out;
out.Format("%u sources (" TEXTCOLOR_YELLOW"%u" TEXTCOLOR_NORMAL" active, " TEXTCOLOR_YELLOW"%u" TEXTCOLOR_NORMAL" free), Update interval: " TEXTCOLOR_YELLOW"%d" TEXTCOLOR_NORMAL"ms",
total, used, unused, 1000/updates);
return out;
}
void OpenALSoundRenderer::PrintDriversList()
{
const ALCchar *drivers = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ?
alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) :
alcGetString(NULL, ALC_DEVICE_SPECIFIER));
if(drivers == NULL)
{
Printf(TEXTCOLOR_YELLOW"Failed to retrieve device list: %s\n", alcGetString(NULL, alcGetError(NULL)));
return;
}
const ALCchar *current = NULL;
if(alcIsExtensionPresent(Device, "ALC_ENUMERATE_ALL_EXT"))
current = alcGetString(Device, ALC_ALL_DEVICES_SPECIFIER);
if(alcGetError(Device) != ALC_NO_ERROR || !current)
current = alcGetString(Device, ALC_DEVICE_SPECIFIER);
if(current == NULL)
{
Printf(TEXTCOLOR_YELLOW"Failed to retrieve device name: %s\n", alcGetString(Device, alcGetError(Device)));
return;
}
Printf("%c%s%2d. %s\n", ' ', ((strcmp(snd_aldevice, "Default") == 0) ? TEXTCOLOR_BOLD : ""), 0,
"Default");
for(int i = 1;*drivers;i++)
{
Printf("%c%s%2d. %s\n", ((strcmp(current, drivers)==0) ? '*' : ' '),
((strcmp(*snd_aldevice, drivers)==0) ? TEXTCOLOR_BOLD : ""), i,
drivers);
drivers += strlen(drivers)+1;
}
}
MIDIDevice* OpenALSoundRenderer::CreateMIDIDevice() const
{
#ifdef _WIN32
extern unsigned mididevice;
return CreateWinMIDIDevice(mididevice);
#elif defined __APPLE__
return CreateAudioToolboxMIDIDevice();
#else
return new OPLMIDIDevice(nullptr);
#endif
}
void OpenALSoundRenderer::PurgeStoppedSources()
{
// Release channels that are stopped
for(uint32_t i = 0;i < SfxGroup.Size();++i)
{
ALuint src = SfxGroup[i];
ALint state = AL_INITIAL;
alGetSourcei(src, AL_SOURCE_STATE, &state);
if(state == AL_INITIAL || state == AL_PLAYING || state == AL_PAUSED)
continue;
FSoundChan *schan = Channels;
while(schan)
{
if(schan->SysChannel != NULL && src == GET_PTRID(schan->SysChannel))
{
StopChannel(schan);
break;
}
schan = schan->NextChan;
}
}
getALError();
}
void OpenALSoundRenderer::LoadReverb(const ReverbContainer *env)
{
ALuint *envReverb = EnvEffects.CheckKey(env->ID);
bool doLoad = (env->Modified || !envReverb);
if(!envReverb)
{
bool ok = false;
envReverb = &EnvEffects.Insert(env->ID, 0);
alGenEffects(1, envReverb);
if(getALError() == AL_NO_ERROR)
{
alEffecti(*envReverb, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
ok = (alGetError() == AL_NO_ERROR);
if(!ok)
{
alEffecti(*envReverb, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
ok = (alGetError() == AL_NO_ERROR);
}
if(!ok)
{
alEffecti(*envReverb, AL_EFFECT_TYPE, AL_EFFECT_NULL);
ok = (alGetError() == AL_NO_ERROR);
}
if(!ok)
{
alDeleteEffects(1, envReverb);
getALError();
}
}
if(!ok)
{
*envReverb = 0;
doLoad = false;
}
}
if(doLoad)
{
const REVERB_PROPERTIES &props = env->Properties;
ALint type = AL_EFFECT_NULL;
alGetEffecti(*envReverb, AL_EFFECT_TYPE, &type);
#define mB2Gain(x) ((float)pow(10., (x)/2000.))
if(type == AL_EFFECT_EAXREVERB)
{
ALfloat reflectpan[3] = { props.ReflectionsPan0,
props.ReflectionsPan1,
props.ReflectionsPan2 };
ALfloat latepan[3] = { props.ReverbPan0, props.ReverbPan1,
props.ReverbPan2 };
#undef SETPARAM
#define SETPARAM(e,t,v) alEffectf((e), AL_EAXREVERB_##t, clamp((v), AL_EAXREVERB_MIN_##t, AL_EAXREVERB_MAX_##t))
SETPARAM(*envReverb, DIFFUSION, props.EnvDiffusion);
SETPARAM(*envReverb, DENSITY, powf(props.EnvSize, 3.0f) * 0.0625f);
SETPARAM(*envReverb, GAIN, mB2Gain(props.Room));
SETPARAM(*envReverb, GAINHF, mB2Gain(props.RoomHF));
SETPARAM(*envReverb, GAINLF, mB2Gain(props.RoomLF));
SETPARAM(*envReverb, DECAY_TIME, props.DecayTime);
SETPARAM(*envReverb, DECAY_HFRATIO, props.DecayHFRatio);
SETPARAM(*envReverb, DECAY_LFRATIO, props.DecayLFRatio);
SETPARAM(*envReverb, REFLECTIONS_GAIN, mB2Gain(props.Reflections));
SETPARAM(*envReverb, REFLECTIONS_DELAY, props.ReflectionsDelay);
alEffectfv(*envReverb, AL_EAXREVERB_REFLECTIONS_PAN, reflectpan);
SETPARAM(*envReverb, LATE_REVERB_GAIN, mB2Gain(props.Reverb));
SETPARAM(*envReverb, LATE_REVERB_DELAY, props.ReverbDelay);
alEffectfv(*envReverb, AL_EAXREVERB_LATE_REVERB_PAN, latepan);
SETPARAM(*envReverb, ECHO_TIME, props.EchoTime);
SETPARAM(*envReverb, ECHO_DEPTH, props.EchoDepth);
SETPARAM(*envReverb, MODULATION_TIME, props.ModulationTime);
SETPARAM(*envReverb, MODULATION_DEPTH, props.ModulationDepth);
SETPARAM(*envReverb, AIR_ABSORPTION_GAINHF, mB2Gain(props.AirAbsorptionHF));
SETPARAM(*envReverb, HFREFERENCE, props.HFReference);
SETPARAM(*envReverb, LFREFERENCE, props.LFReference);
SETPARAM(*envReverb, ROOM_ROLLOFF_FACTOR, props.RoomRolloffFactor);
alEffecti(*envReverb, AL_EAXREVERB_DECAY_HFLIMIT,
(props.Flags&REVERB_FLAGS_DECAYHFLIMIT)?AL_TRUE:AL_FALSE);
#undef SETPARAM
}
else if(type == AL_EFFECT_REVERB)
{
#define SETPARAM(e,t,v) alEffectf((e), AL_REVERB_##t, clamp((v), AL_REVERB_MIN_##t, AL_REVERB_MAX_##t))
SETPARAM(*envReverb, DIFFUSION, props.EnvDiffusion);
SETPARAM(*envReverb, DENSITY, powf(props.EnvSize, 3.0f) * 0.0625f);
SETPARAM(*envReverb, GAIN, mB2Gain(props.Room));
SETPARAM(*envReverb, GAINHF, mB2Gain(props.RoomHF));
SETPARAM(*envReverb, DECAY_TIME, props.DecayTime);
SETPARAM(*envReverb, DECAY_HFRATIO, props.DecayHFRatio);
SETPARAM(*envReverb, REFLECTIONS_GAIN, mB2Gain(props.Reflections));
SETPARAM(*envReverb, REFLECTIONS_DELAY, props.ReflectionsDelay);
SETPARAM(*envReverb, LATE_REVERB_GAIN, mB2Gain(props.Reverb));
SETPARAM(*envReverb, LATE_REVERB_DELAY, props.ReverbDelay);
SETPARAM(*envReverb, AIR_ABSORPTION_GAINHF, mB2Gain(props.AirAbsorptionHF));
SETPARAM(*envReverb, ROOM_ROLLOFF_FACTOR, props.RoomRolloffFactor);
alEffecti(*envReverb, AL_REVERB_DECAY_HFLIMIT,
(props.Flags&REVERB_FLAGS_DECAYHFLIMIT)?AL_TRUE:AL_FALSE);
#undef SETPARAM
}
#undef mB2Gain
}
alAuxiliaryEffectSloti(EnvSlot, AL_EFFECTSLOT_EFFECT, *envReverb);
getALError();
}
FSoundChan *OpenALSoundRenderer::FindLowestChannel()
{
FSoundChan *schan = Channels;
FSoundChan *lowest = NULL;
while(schan)
{
if(schan->SysChannel != NULL)
{
if(!lowest || schan->Priority < lowest->Priority ||
(schan->Priority == lowest->Priority &&
schan->DistanceSqr > lowest->DistanceSqr))
lowest = schan;
}
schan = schan->NextChan;
}
return lowest;
}
#endif // NO_OPENAL