/* ** 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 #include #include "c_cvars.h" #include "templates.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 (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 Data; ALsizei SampleRate; ALenum Format; ALsizei FrameSize; static const int BufferCount = 4; ALuint Buffers[BufferCount]; ALuint Source; std::atomic 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->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), 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 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 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 static void LoadALFunc(const char *name, T *x) { *x = reinterpret_cast(alGetProcAddress(name)); } template static void LoadALCFunc(ALCdevice *device, const char *name, T *x) { *x = reinterpret_cast(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 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(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 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(snd_channels, 2); int numSources = numMono + numStereo; if (0 == numSources) { numSources = numChannels; } Sources.Resize(std::min(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 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 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 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 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) { 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; FindLoopTags(sfxdata, length, &loop_start, &startass, &loop_end, &endass); 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 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); 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(loop_start), static_cast(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, int 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)*PITCH_MULT); else alSourcef(source, AL_PITCH, PITCH(pitch)); if(!reuse_chan || reuse_chan->StartTime == 0) { float st = (chanflags&SNDF_LOOP) ? fmod(startTime, (float)GetMSLength(sfx) / 1000.f) : clamp(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::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 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, (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); 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. */ float gain = GetRolloff(rolloff, AREA_SOUND_RADIUS); alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ? // Clamp in case the max distance is <= the area radius ((gain > 0.00001f) ? 1.f/gain : 100000.f) : 0.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 { float gain = GetRolloff(rolloff, sqrtf(dist_sqr) * distscale); FVector3 dir = pos - listener->position; dir.MakeResize((gain > 0.00001f) ? 1.f/gain : 100000.f); dir += listener->position; alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); alSource3f(source, AL_POSITION, dir[0], dir[1], -dir[2]); } } else { if(AL.EXT_SOURCE_RADIUS) alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ? AREA_SOUND_RADIUS : 0.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); 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) { float st = (chanflags & SNDF_LOOP) ? fmod(startTime, (float)GetMSLength(sfx) / 1000.f) : clamp(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::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::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); 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 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; float dist_sqr = (float)dir.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 gain = GetRolloff(&chan->Rolloff, sqrtf(dist_sqr)*chan->DistanceScale); dir.MakeResize((gain > 0.00001f) ? 1.f/gain : 100000.f); } dir += listener->position; 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(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(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(startTime); auto diff = steady_clock::now().time_since_epoch() - startTimeDuration; chan->StartTime = static_cast(duration_cast(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(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/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(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 }