mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-26 11:40:44 +00:00
3914eb5f85
I'm not sure if this is working out as the original "reverb" was just too crappy and generic. It may be best to just disable it.
2369 lines
64 KiB
C++
2369 lines
64 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.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#include <functional>
|
|
#include <chrono>
|
|
|
|
#include "templates.h"
|
|
#include "oalsound.h"
|
|
#include "c_dispatch.h"
|
|
#include "v_text.h"
|
|
#include "i_module.h"
|
|
#include "cmdlib.h"
|
|
#include "c_cvars.h"
|
|
#include "printf.h"
|
|
#include "zmusic/sounddecoder.h"
|
|
#include "filereadermusicinterface.h"
|
|
|
|
const char *GetSampleTypeName(SampleType type);
|
|
const char *GetChannelConfigName(ChannelConfig chan);
|
|
|
|
FModule OpenALModule{"OpenAL"};
|
|
|
|
#include "oalload.h"
|
|
|
|
CUSTOM_CVAR(Int, snd_channels, 128, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // number of channels available
|
|
{
|
|
if (self < 64) self = 64;
|
|
}
|
|
CVAR(Bool, snd_waterreverb, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|
CVAR (String, snd_aldevice, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
CVAR (Bool, snd_efx, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
CVAR (String, snd_alresampler, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
|
|
#ifdef _WIN32
|
|
#define OPENALLIB "openal32.dll"
|
|
#elif defined(__OpenBSD__)
|
|
#define OPENALLIB "libopenal.so"
|
|
#else
|
|
#define OPENALLIB "libopenal.so.1"
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
// User's library (like OpenAL Soft installed manually or via Homebrew) has precedence
|
|
// over Apple's OpenAL framework which lacks several important features
|
|
#define OPENALLIB1 "libopenal.1.dylib"
|
|
#define OPENALLIB2 "OpenAL.framework/OpenAL"
|
|
#else // !__APPLE__
|
|
#define OPENALLIB1 NicePath("$PROGDIR/" OPENALLIB)
|
|
#define OPENALLIB2 OPENALLIB
|
|
#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({OPENALLIB1, OPENALLIB2});
|
|
}
|
|
return cached_result;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
ReverbContainer *ForcedEnvironment;
|
|
|
|
|
|
#ifndef NO_OPENAL
|
|
|
|
|
|
EXTERN_CVAR (Int, snd_samplerate)
|
|
EXTERN_CVAR (Bool, snd_pitched)
|
|
EXTERN_CVAR (Int, snd_hrtf)
|
|
|
|
|
|
#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;
|
|
|
|
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->ForceStopChannel(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_DOPPLER_FACTOR, 0.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);
|
|
if(Renderer->AL.SOFT_source_spatialize)
|
|
alSourcei(Source, AL_SOURCE_SPATIALIZE_SOFT, AL_AUTO_SOFT);
|
|
|
|
alGenBuffers(BufferCount, Buffers);
|
|
return (getALError() == AL_NO_ERROR);
|
|
}
|
|
|
|
public:
|
|
OpenALSoundStream(OpenALSoundRenderer *renderer)
|
|
: Renderer(renderer), Source(0), Playing(false), Looping(false), Volume(1.0f)
|
|
{
|
|
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();
|
|
}
|
|
|
|
|
|
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 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;
|
|
}
|
|
|
|
lock.unlock();
|
|
|
|
stats = (state == AL_INITIAL) ? "Buffering" : (state == AL_STOPPED) ? "Underrun" :
|
|
(state == AL_PLAYING || state == AL_PAUSED) ? "Ready" : "Unknown state";
|
|
|
|
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;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
#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)
|
|
{
|
|
return soundEngine->GetRolloff(rolloff, distance);
|
|
}
|
|
|
|
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;
|
|
|
|
ALC.EXT_EFX = !!alcIsExtensionPresent(Device, "ALC_EXT_EFX");
|
|
ALC.EXT_disconnect = !!alcIsExtensionPresent(Device, "ALC_EXT_disconnect");
|
|
ALC.SOFT_HRTF = !!alcIsExtensionPresent(Device, "ALC_SOFT_HRTF");
|
|
ALC.SOFT_pause_device = !!alcIsExtensionPresent(Device, "ALC_SOFT_pause_device");
|
|
|
|
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(std::max<ALCint>(snd_channels, 2) - 1);
|
|
attribs.Push(ALC_STEREO_SOURCES);
|
|
attribs.Push(1);
|
|
if(ALC.SOFT_HRTF)
|
|
{
|
|
attribs.Push(ALC_HRTF_SOFT);
|
|
if(*snd_hrtf == 0)
|
|
attribs.Push(ALC_FALSE);
|
|
else if(*snd_hrtf > 0)
|
|
attribs.Push(ALC_TRUE);
|
|
else
|
|
attribs.Push(ALC_DONT_CARE_SOFT);
|
|
}
|
|
// 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();
|
|
|
|
const ALchar *const version = alGetString(AL_VERSION);
|
|
|
|
if (strstr(version, "ALSOFT") == nullptr)
|
|
{
|
|
Printf(TEXTCOLOR_RED " You are using an unsupported OpenAL implementation\n"
|
|
" Install OpenAL Soft library for a better experience\n");
|
|
}
|
|
|
|
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", version);
|
|
DPrintf(DMSG_SPAMMY, " Extensions: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_EXTENSIONS));
|
|
|
|
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");
|
|
AL.SOFT_source_resampler = !!alIsExtensionPresent("AL_SOFT_source_resampler");
|
|
AL.SOFT_source_spatialize = !!alIsExtensionPresent("AL_SOFT_source_spatialize");
|
|
|
|
// Speed of sound is in units per second. Presuming we want to simulate a
|
|
// typical speed of sound of 343.3 meters per second, multiply it by the
|
|
// units per meter scale (1), and set the meters per unit to the scale's
|
|
// reciprocal. It's important to set these correctly for both doppler
|
|
// effects and reverb.
|
|
alSpeedOfSound(343.3f);
|
|
if(ALC.EXT_EFX)
|
|
alListenerf(AL_METERS_PER_UNIT, 1.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(AL.SOFT_source_resampler)
|
|
LOAD_FUNC(alGetStringiSOFT);
|
|
|
|
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 refresh=0;
|
|
alcGetIntegerv(Device, ALC_REFRESH, 1, &refresh);
|
|
if(refresh > 0)
|
|
{
|
|
// Round up instead of down
|
|
UpdateTimeMS = (1000+refresh-1) / refresh;
|
|
}
|
|
|
|
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 = std::max<int>(snd_channels, 2);
|
|
int numSources = numMono + numStereo;
|
|
|
|
if (0 == numSources)
|
|
{
|
|
numSources = numChannels;
|
|
}
|
|
|
|
Sources.Resize(std::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");
|
|
|
|
if(AL.SOFT_source_resampler && strcmp(*snd_alresampler, "Default") != 0)
|
|
{
|
|
const ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
|
|
ALint ridx = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
|
|
ALint i;
|
|
|
|
for(i = 0;i < num_resamplers;i++)
|
|
{
|
|
if(strcmp(alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i), *snd_alresampler) == 0)
|
|
{
|
|
ridx = i;
|
|
break;
|
|
}
|
|
}
|
|
if(i == num_resamplers)
|
|
Printf(TEXTCOLOR_RED" Failed to find resampler " TEXTCOLOR_ORANGE"%s\n", *snd_alresampler);
|
|
else for(ALint src : Sources)
|
|
alSourcei(src, AL_SOURCE_RESAMPLER_SOFT, ridx);
|
|
}
|
|
}
|
|
#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;
|
|
|
|
if (!soundEngine) return;
|
|
FSoundChan *schan = soundEngine->GetChannels();
|
|
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);
|
|
|
|
/* Only downmix to mono if we can't spatialize multi-channel sounds. */
|
|
monoize = monoize && !AL.SOFT_source_spatialize;
|
|
|
|
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, AL.SOFT_source_spatialize || channels==1);
|
|
}
|
|
|
|
std::pair<SoundHandle,bool> OpenALSoundRenderer::LoadSound(uint8_t *sfxdata, int length, bool monoize, FSoundLoadBuffer *pBuffer)
|
|
{
|
|
SoundHandle retval = { NULL };
|
|
ALenum format = AL_NONE;
|
|
ChannelConfig chans;
|
|
SampleType type;
|
|
int srate;
|
|
uint32_t loop_start = 0, loop_end = ~0u;
|
|
bool startass = false, endass = false;
|
|
|
|
/* Only downmix to mono if we can't spatialize multi-channel sounds. */
|
|
monoize = monoize && !AL.SOFT_source_spatialize;
|
|
|
|
auto mreader = new MusicIO::MemoryReader(sfxdata, length);
|
|
FindLoopTags(mreader, &loop_start, &startass, &loop_end, &endass);
|
|
mreader->seek(0, SEEK_SET);
|
|
std::unique_ptr<SoundDecoder> decoder(SoundDecoder::CreateDecoder(mreader));
|
|
if (!decoder)
|
|
{
|
|
delete mreader;
|
|
return std::make_pair(retval, true);
|
|
}
|
|
// the decode will take ownership of the reader here.
|
|
|
|
decoder->getInfo(&srate, &chans, &type);
|
|
int samplesize = 1;
|
|
if (chans == ChannelConfig_Mono || monoize)
|
|
{
|
|
if (type == SampleType_UInt8) format = AL_FORMAT_MONO8, samplesize = 1;
|
|
if (type == SampleType_Int16) format = AL_FORMAT_MONO16, samplesize = 2;
|
|
}
|
|
else if (chans == ChannelConfig_Stereo)
|
|
{
|
|
if (type == SampleType_UInt8) format = AL_FORMAT_STEREO8, samplesize = 2;
|
|
if (type == SampleType_Int16) format = AL_FORMAT_STEREO16, samplesize = 4;
|
|
}
|
|
|
|
if (format == AL_NONE)
|
|
{
|
|
Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
|
|
GetSampleTypeName(type));
|
|
return std::make_pair(retval, true);
|
|
}
|
|
|
|
auto 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((data.size()/chancount));
|
|
}
|
|
|
|
ALenum err;
|
|
ALuint buffer = 0;
|
|
alGenBuffers(1, &buffer);
|
|
alBufferData(buffer, format, &data[0], (ALsizei)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);
|
|
}
|
|
|
|
if (!startass) loop_start = uint32_t(int64_t(loop_start) * srate / 1000);
|
|
if (!endass && loop_end != ~0u) loop_end = uint32_t(int64_t(loop_end) * srate / 1000);
|
|
const uint32_t samples = (uint32_t)data.size() / samplesize;
|
|
if (loop_start > samples) loop_start = 0;
|
|
if (loop_end > samples) loop_end = samples;
|
|
|
|
if ((loop_start > 0 || loop_end > 0) && loop_end > loop_start && AL.SOFT_loop_points)
|
|
{
|
|
ALint loops[2] = { static_cast<ALint>(loop_start), static_cast<ALint>(loop_end) };
|
|
DPrintf(DMSG_NOTIFY, "Setting loop points %d -> %d\n", loops[0], loops[1]);
|
|
alBufferiv(buffer, AL_LOOP_POINTS_SOFT, loops);
|
|
// no console messages here, please!
|
|
}
|
|
|
|
retval.data = MAKE_PTRID(buffer);
|
|
if (pBuffer != nullptr)
|
|
{
|
|
pBuffer->mBuffer = std::move(data);
|
|
pBuffer->loop_start = loop_start;
|
|
pBuffer->loop_end = loop_end;
|
|
pBuffer->chans = chans;
|
|
pBuffer->type = type;
|
|
pBuffer->srate = srate;
|
|
}
|
|
return std::make_pair(retval, AL.SOFT_source_spatialize || chans == ChannelConfig_Mono || monoize);
|
|
}
|
|
|
|
std::pair<SoundHandle, bool> OpenALSoundRenderer::LoadSoundBuffered(FSoundLoadBuffer *pBuffer, bool monoize)
|
|
{
|
|
SoundHandle retval = { NULL };
|
|
ALenum format = AL_NONE;
|
|
int srate = pBuffer->srate;
|
|
auto type = pBuffer->type;
|
|
auto chans = pBuffer->chans;
|
|
uint32_t loop_start = pBuffer->loop_start, loop_end = pBuffer->loop_end;
|
|
|
|
/* Only downmix to mono if we can't spatialize multi-channel sounds. */
|
|
monoize = monoize && !AL.SOFT_source_spatialize;
|
|
|
|
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);
|
|
}
|
|
|
|
auto &data = pBuffer->mBuffer;
|
|
|
|
if (pBuffer->chans == ChannelConfig_Stereo && 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(data.size() / chancount);
|
|
}
|
|
|
|
ALenum err;
|
|
ALuint buffer = 0;
|
|
alGenBuffers(1, &buffer);
|
|
alBufferData(buffer, format, &data[0], (ALsizei)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);
|
|
}
|
|
|
|
// the loop points were already validated by the previous load.
|
|
if ((loop_start > 0 || loop_end > 0) && loop_end > loop_start && AL.SOFT_loop_points)
|
|
{
|
|
ALint loops[2] = { static_cast<ALint>(loop_start), static_cast<ALint>(loop_end) };
|
|
DPrintf(DMSG_NOTIFY, "Setting loop points %d -> %d\n", loops[0], loops[1]);
|
|
alBufferiv(buffer, AL_LOOP_POINTS_SOFT, loops);
|
|
// no console messages here, please!
|
|
}
|
|
|
|
retval.data = MAKE_PTRID(buffer);
|
|
return std::make_pair(retval, AL.SOFT_source_spatialize || chans == ChannelConfig_Mono || monoize);
|
|
}
|
|
|
|
void OpenALSoundRenderer::UnloadSound(SoundHandle sfx)
|
|
{
|
|
if(!sfx.data)
|
|
return;
|
|
|
|
ALuint buffer = GET_PTRID(sfx.data);
|
|
FSoundChan *schan = soundEngine->GetChannels();
|
|
while(schan)
|
|
{
|
|
if(schan->SysChannel)
|
|
{
|
|
ALint bufID = 0;
|
|
alGetSourcei(GET_PTRID(schan->SysChannel), AL_BUFFER, &bufID);
|
|
if((ALuint)bufID == buffer)
|
|
{
|
|
FSoundChan *next = schan->NextChan;
|
|
ForceStopChannel(schan);
|
|
schan = next;
|
|
continue;
|
|
}
|
|
}
|
|
schan = schan->NextChan;
|
|
}
|
|
|
|
// Make sure to kill any currently fading sounds too
|
|
for(auto iter = FadingSources.begin();iter != FadingSources.end();)
|
|
{
|
|
ALint bufID = 0;
|
|
alGetSourcei(iter->first, AL_BUFFER, &bufID);
|
|
if(static_cast<ALuint>(bufID) == buffer)
|
|
{
|
|
FreeSource(iter->first);
|
|
iter = FadingSources.erase(iter);
|
|
}
|
|
else
|
|
++iter;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, int pitch, int chanflags, FISoundChannel *reuse_chan)
|
|
{
|
|
if(FreeSfx.Size() == 0)
|
|
{
|
|
FSoundChan *lowest = FindLowestChannel();
|
|
if(lowest) ForceStopChannel(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_DOPPLER_FACTOR, 0.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(AL.SOFT_source_spatialize)
|
|
alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_AUTO_SOFT);
|
|
|
|
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 == 0)
|
|
alSourcef(source, AL_SEC_OFFSET, 0.f);
|
|
else
|
|
{
|
|
if((chanflags&SNDF_ABSTIME))
|
|
alSourcei(source, AL_SAMPLE_OFFSET, ALint(reuse_chan->StartTime));
|
|
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)
|
|
).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 = soundEngine->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))
|
|
ForceStopChannel(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/std::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);
|
|
alSourcef(source, AL_DOPPLER_FACTOR, 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(AL.SOFT_source_spatialize)
|
|
alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
|
|
|
|
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 == 0)
|
|
alSourcef(source, AL_SEC_OFFSET, 0.f);
|
|
else
|
|
{
|
|
if((chanflags&SNDF_ABSTIME))
|
|
alSourcei(source, AL_SAMPLE_OFFSET, ALint(reuse_chan->StartTime));
|
|
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)
|
|
).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 = soundEngine->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::ChannelPitch(FISoundChannel *chan, float pitch)
|
|
{
|
|
if (chan == NULL || chan->SysChannel == NULL)
|
|
return;
|
|
|
|
alDeferUpdatesSOFT();
|
|
|
|
ALuint source = GET_PTRID(chan->SysChannel);
|
|
if (WasInWater && !(chan->ChanFlags & CHANF_UI))
|
|
alSourcef(source, AL_PITCH, std::max(pitch, 0.0001f)*PITCH_MULT);
|
|
else
|
|
alSourcef(source, AL_PITCH, std::max(pitch, 0.0001f));
|
|
}
|
|
|
|
void OpenALSoundRenderer::FreeSource(ALuint source)
|
|
{
|
|
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);
|
|
if((i=SfxGroup.Find(source)) < SfxGroup.Size())
|
|
SfxGroup.Delete(i);
|
|
|
|
FreeSfx.Push(source);
|
|
}
|
|
|
|
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 killed
|
|
soundEngine->ChannelEnded(chan);
|
|
|
|
ALint state = AL_INITIAL;
|
|
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
|
if(state != AL_PLAYING)
|
|
FreeSource(source);
|
|
else
|
|
{
|
|
// The sound is being killed while playing, so set its gain to 0 and track it
|
|
// as it fades.
|
|
alSourcef(source, AL_GAIN, 0.f);
|
|
getALError();
|
|
|
|
FadingSources.insert(std::make_pair(
|
|
source, std::chrono::steady_clock::now().time_since_epoch().count()
|
|
));
|
|
}
|
|
}
|
|
|
|
void OpenALSoundRenderer::ForceStopChannel(FISoundChannel *chan)
|
|
{
|
|
ALuint source = GET_PTRID(chan->SysChannel);
|
|
if(!source) return;
|
|
|
|
soundEngine->ChannelEnded(chan);
|
|
FreeSource(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
|
|
FSoundChan *schan = soundEngine->GetChannels();
|
|
while (schan)
|
|
{
|
|
ALuint source = GET_PTRID(schan->SysChannel);
|
|
if (source && !(schan->ChanFlags & CHANF_UI))
|
|
{
|
|
alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]);
|
|
alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
|
|
}
|
|
schan = schan->NextChan;
|
|
}
|
|
}
|
|
|
|
FSoundChan *schan = soundEngine->GetChannels();
|
|
while (schan)
|
|
{
|
|
ALuint source = GET_PTRID(schan->SysChannel);
|
|
if (source && !(schan->ChanFlags & CHANF_UI))
|
|
alSourcef(source, AL_PITCH, schan->Pitch / 128.0f * PITCH_MULT);
|
|
schan = schan->NextChan;
|
|
}
|
|
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);
|
|
|
|
FSoundChan *schan = soundEngine->GetChannels();
|
|
while (schan)
|
|
{
|
|
ALuint source = GET_PTRID(schan->SysChannel);
|
|
if (source && !(schan->ChanFlags & CHANF_UI))
|
|
{
|
|
alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]);
|
|
alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
|
|
}
|
|
schan = schan->NextChan;
|
|
}
|
|
}
|
|
|
|
FSoundChan *schan = soundEngine->GetChannels();
|
|
while (schan)
|
|
{
|
|
ALuint source = GET_PTRID(schan->SysChannel);
|
|
if (source && !(schan->ChanFlags & CHANF_UI))
|
|
alSourcef(source, AL_PITCH, schan->Pitch / 128.0f);
|
|
schan = schan->NextChan;
|
|
}
|
|
getALError();
|
|
}
|
|
}
|
|
|
|
void OpenALSoundRenderer::UpdateSounds()
|
|
{
|
|
alProcessUpdatesSOFT();
|
|
|
|
if(!FadingSources.empty())
|
|
{
|
|
auto cur_time = std::chrono::steady_clock::now().time_since_epoch();
|
|
for(auto iter = FadingSources.begin();iter != FadingSources.end();)
|
|
{
|
|
auto time_diff = std::chrono::duration_cast<std::chrono::milliseconds>(cur_time -
|
|
std::chrono::steady_clock::time_point::duration(iter->second));
|
|
if(time_diff.count() >= UpdateTimeMS)
|
|
{
|
|
FreeSource(iter->first);
|
|
iter = FadingSources.erase(iter);
|
|
}
|
|
else
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
if(ALC.EXT_disconnect)
|
|
{
|
|
ALCint connected = ALC_TRUE;
|
|
alcGetIntegerv(Device, ALC_CONNECTED, 1, &connected);
|
|
if(connected == ALC_FALSE)
|
|
{
|
|
Printf("Sound device disconnected; restarting...\n");
|
|
soundEngine->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 = 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()
|
|
{
|
|
FString out;
|
|
|
|
ALCint refresh = 1;
|
|
alcGetIntegerv(Device, ALC_REFRESH, 1, &refresh);
|
|
getALCError(Device);
|
|
|
|
uint32_t total = Sources.Size();
|
|
uint32_t used = SfxGroup.Size()+Streams.Size();
|
|
uint32_t unused = FreeSfx.Size();
|
|
|
|
out.Format("%u sources (" TEXTCOLOR_YELLOW"%u" TEXTCOLOR_NORMAL" active, " TEXTCOLOR_YELLOW"%u" TEXTCOLOR_NORMAL" free), Update interval: " TEXTCOLOR_YELLOW"%.1f" TEXTCOLOR_NORMAL"ms",
|
|
total, used, unused, 1000.f/static_cast<float>(refresh));
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 = soundEngine->GetChannels();
|
|
while(schan)
|
|
{
|
|
if(schan->SysChannel != NULL && src == GET_PTRID(schan->SysChannel))
|
|
{
|
|
ForceStopChannel(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 = soundEngine->GetChannels();
|
|
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;
|
|
}
|
|
|
|
|
|
#include "menu/menu.h"
|
|
|
|
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
|
|
}
|
|
|
|
void I_BuildALResamplersList(FOptionValues* opt)
|
|
{
|
|
opt->mValues.Resize(1);
|
|
opt->mValues[0].TextValue = "Default";
|
|
opt->mValues[0].Text = "Default";
|
|
|
|
#ifndef NO_OPENAL
|
|
if (!IsOpenALPresent())
|
|
return;
|
|
if (!alcGetCurrentContext() || !alIsExtensionPresent("AL_SOFT_source_resampler"))
|
|
return;
|
|
|
|
LPALGETSTRINGISOFT alGetStringiSOFT = reinterpret_cast<LPALGETSTRINGISOFT>(alGetProcAddress("alGetStringiSOFT"));
|
|
ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
|
|
|
|
unsigned int idx = opt->mValues.Reserve(num_resamplers);
|
|
for (ALint i = 0; i < num_resamplers; ++i)
|
|
{
|
|
const ALchar* name = alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i);
|
|
opt->mValues[idx].TextValue = name;
|
|
opt->mValues[idx].Text = name;
|
|
++idx;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#endif // NO_OPENAL
|