mirror of
https://github.com/ZDoom/Raze.git
synced 2024-12-01 00:22:03 +00:00
2159 lines
58 KiB
C++
2159 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.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#include <functional>
|
|
#include <chrono>
|
|
|
|
#include "c_cvars.h"
|
|
|
|
#include "oalsound.h"
|
|
#include "c_dispatch.h"
|
|
#include "v_text.h"
|
|
#include "i_module.h"
|
|
#include "cmdlib.h"
|
|
#include "m_fixed.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_channels)
|
|
EXTERN_CVAR (Int, snd_samplerate)
|
|
EXTERN_CVAR (Bool, snd_waterreverb)
|
|
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;
|
|
uint64_t Offset = 0;
|
|
std::mutex Mutex;
|
|
|
|
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_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), 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 */
|
|
Offset = 0;
|
|
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();
|
|
|
|
Offset = 0;
|
|
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();
|
|
}
|
|
|
|
Position GetPlayPosition() override
|
|
{
|
|
using namespace std::chrono;
|
|
|
|
std::lock_guard _{Mutex};
|
|
ALint64SOFT offset[2]{};
|
|
ALint state{}, queued{};
|
|
alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);
|
|
if(Renderer->AL.SOFT_source_latency)
|
|
{
|
|
// AL_SAMPLE_OFFSET_LATENCY_SOFT fills offset[0] with the source sample
|
|
// offset in 32.32 fixed-point (which we chop off the sub-sample position
|
|
// since it's not crucial), and offset[1] with the playback latency in
|
|
// nanoseconds (how many nanoseconds until the sample point in offset[0]
|
|
// reaches the DAC).
|
|
Renderer->alGetSourcei64vSOFT(Source, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset);
|
|
offset[0] >>= 32;
|
|
}
|
|
else
|
|
{
|
|
// Without AL_SOFT_source_latency, we can only get the sample offset, no
|
|
// latency info.
|
|
ALint ioffset{};
|
|
alGetSourcei(Source, AL_SAMPLE_OFFSET, &ioffset);
|
|
offset[0] = ioffset;
|
|
offset[1] = 0;
|
|
}
|
|
alGetSourcei(Source, AL_SOURCE_STATE, &state);
|
|
// If the source is stopped, there was an underrun, so the play position is
|
|
// the end of the queue.
|
|
if(state == AL_STOPPED)
|
|
return Position{Offset + queued*(Data.Size()/FrameSize), nanoseconds{offset[1]}};
|
|
// The offset is otherwise valid as long as the source has been started.
|
|
if(state != AL_INITIAL)
|
|
return Position{Offset + offset[0], nanoseconds{offset[1]}};
|
|
return Position{0, nanoseconds{0}};
|
|
}
|
|
|
|
virtual FString GetStats()
|
|
{
|
|
FString stats;
|
|
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
|
|
{
|
|
std::lock_guard _{Mutex};
|
|
alSourceUnqueueBuffers(Source, 1, &bufid);
|
|
Offset += Data.Size() / FrameSize;
|
|
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 */
|
|
|
|
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(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_latency = !!alIsExtensionPresent("AL_SOFT_source_latency");
|
|
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_latency)
|
|
LOAD_FUNC(alGetSourcei64vSOFT);
|
|
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 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");
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
SoundHandle OpenALSoundRenderer::LoadSoundRaw(uint8_t *sfxdata, int length, int frequency, int channels, int bits, int loopstart, int loopend)
|
|
{
|
|
SoundHandle retval = { NULL };
|
|
|
|
if(length == 0) return retval;
|
|
|
|
if(bits == -8)
|
|
{
|
|
// Simple signed->unsigned conversion
|
|
for(int i = 0;i < length;i++)
|
|
sfxdata[i] ^= 0x80;
|
|
bits = -bits;
|
|
}
|
|
|
|
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 retval;
|
|
}
|
|
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 retval;
|
|
}
|
|
|
|
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 retval;
|
|
}
|
|
|
|
SoundHandle OpenALSoundRenderer::LoadSound(uint8_t *sfxdata, int length, int def_loop_start, int def_loop_end)
|
|
{
|
|
SoundHandle retval = { NULL };
|
|
ALenum format = AL_NONE;
|
|
ChannelConfig chans;
|
|
SampleType type;
|
|
int srate;
|
|
uint32_t loop_start = 0, loop_end = ~0u;
|
|
zmusic_bool startass = false, endass = false;
|
|
|
|
if (def_loop_start < 0)
|
|
{
|
|
FindLoopTags(sfxdata, length, &loop_start, &startass, &loop_end, &endass);
|
|
}
|
|
else
|
|
{
|
|
loop_start = def_loop_start;
|
|
loop_end = def_loop_end;
|
|
startass = endass = true;
|
|
}
|
|
auto decoder = CreateDecoder(sfxdata, length, true);
|
|
if (!decoder)
|
|
return retval;
|
|
|
|
SoundDecoder_GetInfo(decoder, &srate, &chans, &type);
|
|
int samplesize = 1;
|
|
if (chans == ChannelConfig_Mono)
|
|
{
|
|
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)
|
|
{
|
|
SoundDecoder_Close(decoder);
|
|
Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
|
|
GetSampleTypeName(type));
|
|
return retval;
|
|
}
|
|
|
|
std::vector<uint8_t> data;
|
|
unsigned total = 0;
|
|
unsigned got;
|
|
|
|
data.resize(total + 32768);
|
|
while ((got = (unsigned)SoundDecoder_Read(decoder, (char*)&data[total], data.size() - total)) > 0)
|
|
{
|
|
total += got;
|
|
data.resize(total * 2);
|
|
}
|
|
data.resize(total);
|
|
if (total == 0)
|
|
{
|
|
return retval;
|
|
}
|
|
SoundDecoder_Close(decoder);
|
|
|
|
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 retval;
|
|
}
|
|
|
|
if (!startass) loop_start = Scale(loop_start, srate, 1000);
|
|
if (!endass && loop_end != ~0u) loop_end = Scale(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);
|
|
return retval;
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, float pitch, int chanflags, FISoundChannel *reuse_chan, float startTime)
|
|
{
|
|
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_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_MULT);
|
|
else
|
|
alSourcef(source, AL_PITCH, pitch);
|
|
|
|
if(!reuse_chan || reuse_chan->StartTime == 0)
|
|
{
|
|
float st = (chanflags&SNDF_LOOP) ? fmod(startTime, (float)GetMSLength(sfx) / 1000.f) : clamp<float>(startTime, 0.f, (float)GetMSLength(sfx) / 1000.f);
|
|
alSourcef(source, AL_SEC_OFFSET, st);
|
|
}
|
|
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, float pitch, int priority, const FVector3 &pos, const FVector3 &vel,
|
|
int channum, int chanflags, FISoundChannel *reuse_chan, float startTime)
|
|
{
|
|
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, std::numeric_limits<float>::max());
|
|
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, calculate the reference
|
|
// distance that will achieve that much attenuation with the current
|
|
// distance. The Inverse Distance calculation is:
|
|
//
|
|
// Gain = MinDist / (MinDist + RolloffFactor*(Distance - MinDist))
|
|
//
|
|
// Simplifying for RolloffFactor=1, it can be broken down by:
|
|
//
|
|
// Gain = MinDist / (MinDist + (Distance - MinDist))
|
|
// Gain = MinDist / Distance
|
|
// Gain * Distance = MinDist
|
|
//
|
|
// The source's reference distance is then set according to the desired
|
|
// gain and effective distance from the listener, and OpenAL takes care
|
|
// of the rest.
|
|
if(AL.EXT_source_distance_model)
|
|
alSourcei(source, AL_DISTANCE_MODEL, AL_INVERSE_DISTANCE);
|
|
|
|
float dist = sqrtf(dist_sqr);
|
|
float gain = GetRolloff(rolloff, dist * distscale);
|
|
// Don't let the ref distance go to 0, or else distance attenuation is
|
|
// lost with the inverse distance model.
|
|
alSourcef(source, AL_REFERENCE_DISTANCE, max<float>(gain*dist, 0.0004f));
|
|
alSourcef(source, AL_MAX_DISTANCE, std::numeric_limits<float>::max());
|
|
alSourcef(source, AL_ROLLOFF_FACTOR, 1.f);
|
|
}
|
|
|
|
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, pos[0], pos[1], -pos[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);
|
|
if(AL.EXT_SOURCE_RADIUS)
|
|
alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ? AREA_SOUND_RADIUS : 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_MULT);
|
|
else
|
|
alSourcef(source, AL_PITCH, pitch);
|
|
|
|
if(!reuse_chan || reuse_chan->StartTime == 0)
|
|
{
|
|
float sfxlength = (float)GetMSLength(sfx) / 1000.f;
|
|
float st = (chanflags & SNDF_LOOP)
|
|
? (sfxlength > 0 ? fmod(startTime, sfxlength) : 0)
|
|
: clamp<float>(startTime, 0.f, sfxlength);
|
|
alSourcef(source, AL_SEC_OFFSET, st);
|
|
}
|
|
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, max(pitch, 0.0001f)*PITCH_MULT);
|
|
else
|
|
alSourcef(source, AL_PITCH, max(pitch, 0.0001f));
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
if (!(chan->ChanFlags & CHANF_EVICTED))
|
|
soundEngine->SoundDone(chan);
|
|
|
|
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;
|
|
|
|
float dist_sqr = (float)(pos - listener->position).LengthSquared();
|
|
chan->DistanceSqr = dist_sqr;
|
|
|
|
alDeferUpdatesSOFT();
|
|
ALuint source = GET_PTRID(chan->SysChannel);
|
|
|
|
if(dist_sqr < (0.0004f*0.0004f))
|
|
{
|
|
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
|
|
alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
|
|
}
|
|
else
|
|
{
|
|
if(chan->ManualRolloff)
|
|
{
|
|
float dist = sqrtf(dist_sqr);
|
|
float gain = GetRolloff(&chan->Rolloff, dist * chan->DistanceScale);
|
|
alSourcef(source, AL_REFERENCE_DISTANCE, max<float>(gain*dist, 0.0004f));
|
|
}
|
|
|
|
alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
|
|
alSource3f(source, AL_POSITION, pos[0], pos[1], -pos[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 * 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);
|
|
schan = schan->NextChan;
|
|
}
|
|
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");
|
|
S_SoundReset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
PurgeStoppedSources();
|
|
}
|
|
|
|
bool OpenALSoundRenderer::IsValid()
|
|
{
|
|
return Device != NULL;
|
|
}
|
|
|
|
void OpenALSoundRenderer::MarkStartTime(FISoundChannel *chan, float startTime)
|
|
{
|
|
// FIXME: Get current time (preferably from the audio clock, but the system
|
|
// time will have to do)
|
|
using namespace std::chrono;
|
|
auto startTimeDuration = duration<double>(startTime);
|
|
auto diff = steady_clock::now().time_since_epoch() - startTimeDuration;
|
|
chan->StartTime = static_cast<uint64_t>(duration_cast<nanoseconds>(diff).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))
|
|
{
|
|
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 = 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;
|
|
}
|
|
|
|
#endif // NO_OPENAL
|
|
|
|
#include "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
|
|
}
|
|
|