Put local functions and variables into an anonymous namespace
[alure.git] / src / context.cpp
blob2f8a44bce9f7697244fa84ec73cb807c64d36a6c
2 #include "config.h"
4 #include "context.h"
6 #include <stdexcept>
7 #include <algorithm>
8 #include <functional>
9 #include <memory>
10 #include <iostream>
11 #include <sstream>
12 #include <fstream>
13 #include <cstring>
14 #include <map>
15 #include <new>
17 #include "alc.h"
19 #ifdef HAVE_VORBISFILE
20 #include "decoders/vorbisfile.hpp"
21 #endif
22 #ifdef HAVE_LIBFLAC
23 #include "decoders/flac.hpp"
24 #endif
25 #ifdef HAVE_OPUSFILE
26 #include "decoders/opusfile.hpp"
27 #endif
28 #ifdef HAVE_LIBSNDFILE
29 #include "decoders/sndfile.hpp"
30 #endif
31 #ifdef HAVE_MPG123
32 #include "decoders/mpg123.hpp"
33 #endif
34 #include "decoders/wave.hpp"
36 #include "devicemanager.h"
37 #include "device.h"
38 #include "buffer.h"
39 #include "source.h"
40 #include "auxeffectslot.h"
41 #include "effect.h"
42 #include "sourcegroup.h"
44 #ifdef _WIN32
45 #define WIN32_LEAN_AND_MEAN
46 #include <windows.h>
47 #endif
49 namespace
52 // Global mutex to protect global context changes
53 std::mutex mGlobalCtxMutex;
55 #ifdef _WIN32
56 // Windows' std::ifstream fails with non-ANSI paths since the standard only
57 // specifies names using const char* (or std::string). MSVC has a non-standard
58 // extension using const wchar_t* (or std::wstring?) to handle Unicode paths,
59 // but not all Windows compilers support it. So we have to make our own istream
60 // that accepts UTF-8 paths and forwards to Unicode-aware I/O functions.
61 class StreamBuf : public std::streambuf {
62 alure::Array<traits_type::char_type,4096> mBuffer;
63 HANDLE mFile;
65 int_type underflow() override final
67 if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr())
69 // Read in the next chunk of data, and set the pointers on success
70 DWORD got = 0;
71 if(!ReadFile(mFile, mBuffer.data(), mBuffer.size(), &got, NULL))
72 got = 0;
73 setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
75 if(gptr() == egptr())
76 return traits_type::eof();
77 return traits_type::to_int_type(*gptr());
80 pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override final
82 if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
83 return traits_type::eof();
85 LARGE_INTEGER fpos;
86 switch(whence)
88 case std::ios_base::beg:
89 fpos.QuadPart = offset;
90 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
91 return traits_type::eof();
92 break;
94 case std::ios_base::cur:
95 // If the offset remains in the current buffer range, just
96 // update the pointer.
97 if((offset >= 0 && offset < off_type(egptr()-gptr())) ||
98 (offset < 0 && -offset <= off_type(gptr()-eback())))
100 // Get the current file offset to report the correct read
101 // offset.
102 fpos.QuadPart = 0;
103 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
104 return traits_type::eof();
105 setg(eback(), gptr()+offset, egptr());
106 return fpos.QuadPart - off_type(egptr()-gptr());
108 // Need to offset for the file offset being at egptr() while
109 // the requested offset is relative to gptr().
110 offset -= off_type(egptr()-gptr());
111 fpos.QuadPart = offset;
112 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
113 return traits_type::eof();
114 break;
116 case std::ios_base::end:
117 fpos.QuadPart = offset;
118 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END))
119 return traits_type::eof();
120 break;
122 default:
123 return traits_type::eof();
125 setg(0, 0, 0);
126 return fpos.QuadPart;
129 pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override final
131 // Simplified version of seekoff
132 if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
133 return traits_type::eof();
135 LARGE_INTEGER fpos;
136 fpos.QuadPart = pos;
137 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
138 return traits_type::eof();
140 setg(0, 0, 0);
141 return fpos.QuadPart;
144 public:
145 bool open(const char *filename)
147 alure::Vector<wchar_t> wname;
148 int wnamelen;
150 wnamelen = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
151 if(wnamelen <= 0) return false;
153 wname.resize(wnamelen);
154 MultiByteToWideChar(CP_UTF8, 0, filename, -1, wname.data(), wnamelen);
156 mFile = CreateFileW(wname.data(), GENERIC_READ, FILE_SHARE_READ, NULL,
157 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
158 if(mFile == INVALID_HANDLE_VALUE) return false;
159 return true;
162 bool is_open() const { return mFile != INVALID_HANDLE_VALUE; }
164 StreamBuf() : mFile(INVALID_HANDLE_VALUE)
166 ~StreamBuf() override final
168 if(mFile != INVALID_HANDLE_VALUE)
169 CloseHandle(mFile);
170 mFile = INVALID_HANDLE_VALUE;
174 // Inherit from std::istream to use our custom streambuf
175 class Stream : public std::istream {
176 public:
177 Stream(const char *filename) : std::istream(new StreamBuf())
179 // Set the failbit if the file failed to open.
180 if(!(static_cast<StreamBuf*>(rdbuf())->open(filename)))
181 clear(failbit);
183 ~Stream() override final
184 { delete rdbuf(); }
186 bool is_open() const { return static_cast<StreamBuf*>(rdbuf())->is_open(); }
188 #endif
190 const std::pair<alure::String,alure::UniquePtr<alure::DecoderFactory>> sDefaultDecoders[] = {
191 { "_alure_int_wave", alure::MakeUnique<alure::WaveDecoderFactory>() },
193 #ifdef HAVE_VORBISFILE
194 { "_alure_int_vorbis", alure::MakeUnique<alure::VorbisFileDecoderFactory>() },
195 #endif
196 #ifdef HAVE_LIBFLAC
197 { "_alure_int_flac", alure::MakeUnique<alure::FlacDecoderFactory>() },
198 #endif
199 #ifdef HAVE_OPUSFILE
200 { "_alure_int_opus", alure::MakeUnique<alure::OpusFileDecoderFactory>() },
201 #endif
202 #ifdef HAVE_LIBSNDFILE
203 { "_alure_int_sndfile", alure::MakeUnique<alure::SndFileDecoderFactory>() },
204 #endif
205 #ifdef HAVE_MPG123
206 { "_alure_int_mpg123", alure::MakeUnique<alure::Mpg123DecoderFactory>() },
207 #endif
209 std::map<alure::String,alure::UniquePtr<alure::DecoderFactory>> sDecoders;
212 template<typename T>
213 alure::DecoderOrExceptT GetDecoder(const alure::String &name, alure::UniquePtr<std::istream> &file,
214 T start, T end)
216 alure::DecoderOrExceptT ret;
217 while(start != end)
219 alure::DecoderFactory *factory = start->second.get();
220 auto decoder = factory->createDecoder(file);
221 if(decoder) return (ret = std::move(decoder));
223 if(!file || !(file->clear(),file->seekg(0)))
224 return (ret = std::runtime_error("Failed to rewind "+name+" for the next decoder factory"));
226 ++start;
229 return (ret = nullptr);
232 static alure::DecoderOrExceptT GetDecoder(const alure::String &name, alure::UniquePtr<std::istream> file)
234 auto decoder = GetDecoder(name, file, sDecoders.begin(), sDecoders.end());
235 if(std::holds_alternative<std::runtime_error>(decoder)) return decoder;
236 if(std::get<alure::SharedPtr<alure::Decoder>>(decoder)) return decoder;
237 decoder = GetDecoder(name, file, std::begin(sDefaultDecoders), std::end(sDefaultDecoders));
238 if(std::holds_alternative<std::runtime_error>(decoder)) return decoder;
239 if(std::get<alure::SharedPtr<alure::Decoder>>(decoder)) return decoder;
240 return (decoder = std::runtime_error("No decoder for "+name));
243 class DefaultFileIOFactory : public alure::FileIOFactory {
244 alure::UniquePtr<std::istream> openFile(const alure::String &name) override final
246 #ifdef _WIN32
247 auto file = alure::MakeUnique<Stream>(name.c_str());
248 #else
249 auto file = alure::MakeUnique<std::ifstream>(name.c_str(), std::ios::binary);
250 #endif
251 if(!file->is_open()) file = nullptr;
252 return std::move(file);
255 DefaultFileIOFactory sDefaultFileFactory;
257 alure::UniquePtr<alure::FileIOFactory> sFileFactory;
261 namespace alure
264 Decoder::~Decoder() { }
265 DecoderFactory::~DecoderFactory() { }
267 void RegisterDecoder(const String &name, UniquePtr<DecoderFactory> factory)
269 while(sDecoders.find(name) != sDecoders.end())
270 throw std::runtime_error("Decoder factory \""+name+"\" already registered");
271 sDecoders.insert(std::make_pair(name, std::move(factory)));
274 UniquePtr<DecoderFactory> UnregisterDecoder(const String &name)
276 auto iter = sDecoders.find(name);
277 if(iter != sDecoders.end())
279 UniquePtr<DecoderFactory> factory = std::move(iter->second);
280 sDecoders.erase(iter);
281 return factory;
283 return nullptr;
287 FileIOFactory::~FileIOFactory() { }
289 UniquePtr<FileIOFactory> FileIOFactory::set(UniquePtr<FileIOFactory> factory)
291 std::swap(sFileFactory, factory);
292 return factory;
295 FileIOFactory &FileIOFactory::get()
297 FileIOFactory *factory = sFileFactory.get();
298 if(factory) return *factory;
299 return sDefaultFileFactory;
303 // Default message handler methods are no-ops.
304 MessageHandler::~MessageHandler()
308 void MessageHandler::deviceDisconnected(Device)
312 void MessageHandler::sourceStopped(Source)
316 void MessageHandler::sourceForceStopped(Source)
320 void MessageHandler::bufferLoading(StringView, ChannelConfig, SampleType, ALuint, ArrayView<ALbyte>)
324 String MessageHandler::resourceNotFound(StringView)
326 return String();
330 template<typename T>
331 static inline void LoadALFunc(T **func, const char *name)
332 { *func = reinterpret_cast<T*>(alGetProcAddress(name)); }
334 static void LoadNothing(ContextImpl*) { }
336 static void LoadEFX(ContextImpl *ctx)
338 LoadALFunc(&ctx->alGenEffects, "alGenEffects");
339 LoadALFunc(&ctx->alDeleteEffects, "alDeleteEffects");
340 LoadALFunc(&ctx->alIsEffect, "alIsEffect");
341 LoadALFunc(&ctx->alEffecti, "alEffecti");
342 LoadALFunc(&ctx->alEffectiv, "alEffectiv");
343 LoadALFunc(&ctx->alEffectf, "alEffectf");
344 LoadALFunc(&ctx->alEffectfv, "alEffectfv");
345 LoadALFunc(&ctx->alGetEffecti, "alGetEffecti");
346 LoadALFunc(&ctx->alGetEffectiv, "alGetEffectiv");
347 LoadALFunc(&ctx->alGetEffectf, "alGetEffectf");
348 LoadALFunc(&ctx->alGetEffectfv, "alGetEffectfv");
350 LoadALFunc(&ctx->alGenFilters, "alGenFilters");
351 LoadALFunc(&ctx->alDeleteFilters, "alDeleteFilters");
352 LoadALFunc(&ctx->alIsFilter, "alIsFilter");
353 LoadALFunc(&ctx->alFilteri, "alFilteri");
354 LoadALFunc(&ctx->alFilteriv, "alFilteriv");
355 LoadALFunc(&ctx->alFilterf, "alFilterf");
356 LoadALFunc(&ctx->alFilterfv, "alFilterfv");
357 LoadALFunc(&ctx->alGetFilteri, "alGetFilteri");
358 LoadALFunc(&ctx->alGetFilteriv, "alGetFilteriv");
359 LoadALFunc(&ctx->alGetFilterf, "alGetFilterf");
360 LoadALFunc(&ctx->alGetFilterfv, "alGetFilterfv");
362 LoadALFunc(&ctx->alGenAuxiliaryEffectSlots, "alGenAuxiliaryEffectSlots");
363 LoadALFunc(&ctx->alDeleteAuxiliaryEffectSlots, "alDeleteAuxiliaryEffectSlots");
364 LoadALFunc(&ctx->alIsAuxiliaryEffectSlot, "alIsAuxiliaryEffectSlot");
365 LoadALFunc(&ctx->alAuxiliaryEffectSloti, "alAuxiliaryEffectSloti");
366 LoadALFunc(&ctx->alAuxiliaryEffectSlotiv, "alAuxiliaryEffectSlotiv");
367 LoadALFunc(&ctx->alAuxiliaryEffectSlotf, "alAuxiliaryEffectSlotf");
368 LoadALFunc(&ctx->alAuxiliaryEffectSlotfv, "alAuxiliaryEffectSlotfv");
369 LoadALFunc(&ctx->alGetAuxiliaryEffectSloti, "alGetAuxiliaryEffectSloti");
370 LoadALFunc(&ctx->alGetAuxiliaryEffectSlotiv, "alGetAuxiliaryEffectSlotiv");
371 LoadALFunc(&ctx->alGetAuxiliaryEffectSlotf, "alGetAuxiliaryEffectSlotf");
372 LoadALFunc(&ctx->alGetAuxiliaryEffectSlotfv, "alGetAuxiliaryEffectSlotfv");
375 static void LoadSourceResampler(ContextImpl *ctx)
377 LoadALFunc(&ctx->alGetStringiSOFT, "alGetStringiSOFT");
380 static void LoadSourceLatency(ContextImpl *ctx)
382 LoadALFunc(&ctx->alGetSourcei64vSOFT, "alGetSourcei64vSOFT");
383 LoadALFunc(&ctx->alGetSourcedvSOFT, "alGetSourcedvSOFT");
386 static const struct {
387 enum ALExtension extension;
388 const char name[32];
389 void (&loader)(ContextImpl*);
390 } ALExtensionList[] = {
391 { EXT_EFX, "ALC_EXT_EFX", LoadEFX },
393 { EXT_FLOAT32, "AL_EXT_FLOAT32", LoadNothing },
394 { EXT_MCFORMATS, "AL_EXT_MCFORMATS", LoadNothing },
395 { EXT_BFORMAT, "AL_EXT_BFORMAT", LoadNothing },
397 { EXT_MULAW, "AL_EXT_MULAW", LoadNothing },
398 { EXT_MULAW_MCFORMATS, "AL_EXT_MULAW_MCFORMATS", LoadNothing },
399 { EXT_MULAW_BFORMAT, "AL_EXT_MULAW_BFORMAT", LoadNothing },
401 { SOFT_loop_points, "AL_SOFT_loop_points", LoadNothing },
402 { SOFT_source_latency, "AL_SOFT_source_latency", LoadSourceLatency },
403 { SOFT_source_resampler, "AL_SOFT_source_resampler", LoadSourceResampler },
404 { SOFT_source_spatialize, "AL_SOFT_source_spatialize", LoadNothing },
406 { EXT_disconnect, "ALC_EXT_disconnect", LoadNothing },
408 { EXT_SOURCE_RADIUS, "AL_EXT_SOURCE_RADIUS", LoadNothing },
409 { EXT_STEREO_ANGLES, "AL_EXT_STEREO_ANGLES", LoadNothing },
413 ContextImpl *ContextImpl::sCurrentCtx = nullptr;
414 thread_local ContextImpl *ContextImpl::sThreadCurrentCtx = nullptr;
416 void ContextImpl::MakeCurrent(ContextImpl *context)
418 std::unique_lock<std::mutex> ctxlock(mGlobalCtxMutex);
420 if(alcMakeContextCurrent(context ? context->getContext() : nullptr) == ALC_FALSE)
421 throw std::runtime_error("Call to alcMakeContextCurrent failed");
422 if(context)
424 context->addRef();
425 std::call_once(context->mSetExts, std::mem_fn(&ContextImpl::setupExts), context);
427 std::swap(sCurrentCtx, context);
428 if(context) context->decRef();
430 if(sThreadCurrentCtx)
431 sThreadCurrentCtx->decRef();
432 sThreadCurrentCtx = 0;
434 if((context = sCurrentCtx) != nullptr)
436 ctxlock.unlock();
437 context->mWakeThread.notify_all();
441 void ContextImpl::MakeThreadCurrent(ContextImpl *context)
443 if(!DeviceManagerImpl::SetThreadContext)
444 throw std::runtime_error("Thread-local contexts unsupported");
445 if(DeviceManagerImpl::SetThreadContext(context ? context->getContext() : nullptr) == ALC_FALSE)
446 throw std::runtime_error("Call to alcSetThreadContext failed");
447 if(context)
449 context->addRef();
450 std::call_once(context->mSetExts, std::mem_fn(&ContextImpl::setupExts), context);
452 if(sThreadCurrentCtx)
453 sThreadCurrentCtx->decRef();
454 sThreadCurrentCtx = context;
457 void ContextImpl::setupExts()
459 ALCdevice *device = mDevice->getDevice();
460 std::fill(std::begin(mHasExt), std::end(mHasExt), false);
461 for(const auto &entry : ALExtensionList)
463 mHasExt[entry.extension] = (strncmp(entry.name, "ALC", 3) == 0) ?
464 alcIsExtensionPresent(device, entry.name) :
465 alIsExtensionPresent(entry.name);
466 if(mHasExt[entry.extension]) entry.loader(this);
471 void ContextImpl::backgroundProc()
473 if(DeviceManagerImpl::SetThreadContext && mDevice->hasExtension(EXT_thread_local_context))
474 DeviceManagerImpl::SetThreadContext(getContext());
476 std::chrono::steady_clock::time_point basetime = std::chrono::steady_clock::now();
477 std::chrono::milliseconds waketime(0);
478 std::unique_lock<std::mutex> ctxlock(mGlobalCtxMutex);
479 while(!mQuitThread.load(std::memory_order_acquire))
482 std::lock_guard<std::mutex> srclock(mSourceStreamMutex);
483 mStreamingSources.erase(
484 std::remove_if(mStreamingSources.begin(), mStreamingSources.end(),
485 [](SourceImpl *source) -> bool
486 { return !source->updateAsync(); }
487 ), mStreamingSources.end()
491 // Only do one pending buffer at a time. In case there's several large
492 // buffers to load, we still need to process streaming sources so they
493 // don't underrun.
494 RingBuffer::Data ringdata = mPendingBuffers.get_read_vector()[0];
495 if(ringdata.len > 0)
497 PendingBuffer *pb = reinterpret_cast<PendingBuffer*>(ringdata.buf);
498 pb->mBuffer->load(pb->mFrames, pb->mFormat, pb->mDecoder, pb->mName, this);
499 pb->~PendingBuffer();
500 mPendingBuffers.read_advance(1);
501 continue;
504 std::unique_lock<std::mutex> wakelock(mWakeMutex);
505 if(!mQuitThread.load(std::memory_order_acquire) && mPendingBuffers.read_space() == 0)
507 ctxlock.unlock();
509 std::chrono::milliseconds interval = mWakeInterval.load(std::memory_order_relaxed);
510 if(interval.count() == 0)
511 mWakeThread.wait(wakelock);
512 else
514 auto now = std::chrono::steady_clock::now() - basetime;
515 if(now > waketime)
517 auto mult = (now-waketime + interval-std::chrono::milliseconds(1)) / interval;
518 waketime += interval * mult;
519 mWakeThread.wait_until(wakelock, waketime + basetime);
522 wakelock.unlock();
524 ctxlock.lock();
525 while(!mQuitThread.load(std::memory_order_acquire) &&
526 alcGetCurrentContext() != getContext())
527 mWakeThread.wait(ctxlock);
530 ctxlock.unlock();
532 if(DeviceManagerImpl::SetThreadContext)
533 DeviceManagerImpl::SetThreadContext(nullptr);
537 ContextImpl::ContextImpl(ALCcontext *context, DeviceImpl *device)
538 : mListener(this), mContext(context), mDevice(device),
539 mWakeInterval(std::chrono::milliseconds::zero()),
540 mPendingBuffers(16, sizeof(PendingBuffer)), mQuitThread(false),
541 mRefs(0), mHasExt{false}, mIsConnected(true), mIsBatching(false),
542 alGetSourcei64vSOFT(0), alGetSourcedvSOFT(0),
543 alGenEffects(0), alDeleteEffects(0), alIsEffect(0),
544 alEffecti(0), alEffectiv(0), alEffectf(0), alEffectfv(0),
545 alGetEffecti(0), alGetEffectiv(0), alGetEffectf(0), alGetEffectfv(0),
546 alGenFilters(0), alDeleteFilters(0), alIsFilter(0),
547 alFilteri(0), alFilteriv(0), alFilterf(0), alFilterfv(0),
548 alGetFilteri(0), alGetFilteriv(0), alGetFilterf(0), alGetFilterfv(0),
549 alGenAuxiliaryEffectSlots(0), alDeleteAuxiliaryEffectSlots(0), alIsAuxiliaryEffectSlot(0),
550 alAuxiliaryEffectSloti(0), alAuxiliaryEffectSlotiv(0), alAuxiliaryEffectSlotf(0), alAuxiliaryEffectSlotfv(0),
551 alGetAuxiliaryEffectSloti(0), alGetAuxiliaryEffectSlotiv(0), alGetAuxiliaryEffectSlotf(0), alGetAuxiliaryEffectSlotfv(0)
555 ContextImpl::~ContextImpl()
557 auto ringdata = mPendingBuffers.get_read_vector();
558 if(ringdata[0].len > 0)
560 PendingBuffer *pb = reinterpret_cast<PendingBuffer*>(ringdata[0].buf);
561 for(size_t i = 0;i < ringdata[0].len;i++)
562 pb[i].~PendingBuffer();
563 pb = reinterpret_cast<PendingBuffer*>(ringdata[1].buf);
564 for(size_t i = 0;i < ringdata[1].len;i++)
565 pb[i].~PendingBuffer();
567 mPendingBuffers.read_advance(ringdata[0].len + ringdata[1].len);
572 void ContextImpl::destroy()
574 if(mRefs.load() != 0)
575 throw std::runtime_error("Context is in use");
576 if(!mBuffers.empty())
577 throw std::runtime_error("Trying to destroy a context with buffers");
579 if(mThread.joinable())
581 std::unique_lock<std::mutex> lock(mWakeMutex);
582 mQuitThread.store(true, std::memory_order_release);
583 lock.unlock();
584 mWakeThread.notify_all();
585 mThread.join();
588 alcDestroyContext(mContext);
589 mContext = nullptr;
591 mDevice->removeContext(this);
595 void ContextImpl::startBatch()
597 alcSuspendContext(mContext);
598 mIsBatching = true;
601 void ContextImpl::endBatch()
603 alcProcessContext(mContext);
604 mIsBatching = false;
608 SharedPtr<MessageHandler> ContextImpl::setMessageHandler(SharedPtr<MessageHandler> handler)
610 std::lock_guard<std::mutex> lock(mGlobalCtxMutex);
611 mMessage.swap(handler);
612 return handler;
616 void ContextImpl::setAsyncWakeInterval(std::chrono::milliseconds interval)
618 if(interval.count() < 0 || interval > std::chrono::seconds(1))
619 throw std::runtime_error("Async wake interval out of range");
620 mWakeInterval.store(interval);
621 mWakeMutex.lock(); mWakeMutex.unlock();
622 mWakeThread.notify_all();
626 DecoderOrExceptT ContextImpl::findDecoder(const String &name)
628 DecoderOrExceptT ret;
630 CheckContext(this);
631 auto file = FileIOFactory::get().openFile(name);
632 if(file) return (ret = GetDecoder(name, std::move(file)));
634 // Resource not found. Try to find a substitute.
635 if(!mMessage.get()) return (ret = std::runtime_error("Failed to open "+name));
636 String oldname = name;
637 do {
638 String newname(mMessage->resourceNotFound(oldname));
639 if(newname.empty())
640 return (ret = std::runtime_error("Failed to open "+oldname));
641 file = FileIOFactory::get().openFile(newname);
642 oldname = std::move(newname);
643 } while(!file);
645 return (ret = GetDecoder(oldname, std::move(file)));
648 SharedPtr<Decoder> ContextImpl::createDecoder(const String &name)
650 CheckContext(this);
651 DecoderOrExceptT dec = findDecoder(name);
652 if(SharedPtr<Decoder> *decoder = std::get_if<SharedPtr<Decoder>>(&dec))
653 return *decoder;
654 throw std::get<std::runtime_error>(dec);
658 bool ContextImpl::isSupported(ChannelConfig channels, SampleType type) const
660 CheckContext(this);
661 return GetFormat(channels, type) != AL_NONE;
665 ArrayView<String> ContextImpl::getAvailableResamplers()
667 CheckContext(this);
668 if(mResamplers.empty() && hasExtension(SOFT_source_resampler))
670 ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
671 mResamplers.reserve(num_resamplers);
672 for(int i = 0;i < num_resamplers;i++)
673 mResamplers.emplace_back(alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i));
674 if(mResamplers.empty())
675 mResamplers.emplace_back();
677 return mResamplers;
680 ALsizei ContextImpl::getDefaultResamplerIndex() const
682 CheckContext(this);
683 if(!hasExtension(SOFT_source_resampler))
684 return 0;
685 return alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
689 BufferOrExceptT ContextImpl::doCreateBuffer(const String &name, Vector<UniquePtr<BufferImpl>>::iterator iter, SharedPtr<Decoder> decoder)
691 BufferOrExceptT retval;
692 ALuint srate = decoder->getFrequency();
693 ChannelConfig chans = decoder->getChannelConfig();
694 SampleType type = decoder->getSampleType();
695 ALuint frames = decoder->getLength();
697 Vector<ALbyte> data(FramesToBytes(frames, chans, type));
698 frames = decoder->read(data.data(), frames);
699 if(!frames) return (retval = std::runtime_error("No samples for buffer"));
700 data.resize(FramesToBytes(frames, chans, type));
702 std::pair<uint64_t,uint64_t> loop_pts = decoder->getLoopPoints();
703 if(loop_pts.first >= loop_pts.second)
704 loop_pts = std::make_pair(0, frames);
705 else
707 loop_pts.second = std::min<uint64_t>(loop_pts.second, frames);
708 loop_pts.first = std::min<uint64_t>(loop_pts.first, loop_pts.second-1);
711 // Get the format before calling the bufferLoading message handler, to
712 // ensure it's something OpenAL can handle.
713 ALenum format = GetFormat(chans, type);
714 if(format == AL_NONE)
716 std::stringstream sstr;
717 sstr<< "Format not supported ("<<GetSampleTypeName(type)<<", "<<GetChannelConfigName(chans)<<")";
718 return (retval = std::runtime_error(sstr.str()));
721 if(mMessage.get())
722 mMessage->bufferLoading(name, chans, type, srate, data);
724 alGetError();
725 ALuint bid = 0;
726 alGenBuffers(1, &bid);
727 alBufferData(bid, format, data.data(), data.size(), srate);
728 if(hasExtension(SOFT_loop_points))
730 ALint pts[2]{(ALint)loop_pts.first, (ALint)loop_pts.second};
731 alBufferiv(bid, AL_LOOP_POINTS_SOFT, pts);
733 if(alGetError() != AL_NO_ERROR)
735 alDeleteBuffers(1, &bid);
736 return (retval = std::runtime_error("Failed to buffer data"));
739 return (retval = mBuffers.insert(iter,
740 MakeUnique<BufferImpl>(this, bid, srate, chans, type, true, name)
741 )->get());
744 BufferOrExceptT ContextImpl::doCreateBufferAsync(const String &name, Vector<UniquePtr<BufferImpl>>::iterator iter, SharedPtr<Decoder> decoder)
746 BufferOrExceptT retval;
747 ALuint srate = decoder->getFrequency();
748 ChannelConfig chans = decoder->getChannelConfig();
749 SampleType type = decoder->getSampleType();
750 ALuint frames = decoder->getLength();
751 if(!frames) return (retval = std::runtime_error("No samples for buffer"));
753 ALenum format = GetFormat(chans, type);
754 if(format == AL_NONE)
756 std::stringstream sstr;
757 sstr<< "Format not supported ("<<GetSampleTypeName(type)<<", "<<GetChannelConfigName(chans)<<")";
758 return (retval = std::runtime_error(sstr.str()));
761 alGetError();
762 ALuint bid = 0;
763 alGenBuffers(1, &bid);
764 if(alGetError() != AL_NO_ERROR)
765 return (retval = std::runtime_error("Failed to create buffer"));
767 auto buffer = MakeUnique<BufferImpl>(this, bid, srate, chans, type, false, name);
769 if(mThread.get_id() == std::thread::id())
770 mThread = std::thread(std::mem_fn(&ContextImpl::backgroundProc), this);
772 while(mPendingBuffers.write_space() == 0)
774 mWakeThread.notify_all();
775 std::this_thread::yield();
778 RingBuffer::Data ringdata = mPendingBuffers.get_write_vector()[0];
779 new(ringdata.buf) PendingBuffer{name, buffer.get(), decoder, format, frames};
780 mPendingBuffers.write_advance(1);
782 return (retval = mBuffers.insert(iter, std::move(buffer))->get());
785 Buffer ContextImpl::getBuffer(const String &name)
787 CheckContext(this);
789 auto hasher = std::hash<String>();
790 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
791 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
792 { return hasher(lhs->getName()) < rhs; }
794 if(iter != mBuffers.end() && (*iter)->getName() == name)
796 // Ensure the buffer is loaded before returning. getBuffer guarantees
797 // the returned buffer is loaded.
798 BufferImpl *buffer = iter->get();
799 while(buffer->getLoadStatus() == BufferLoadStatus::Pending)
800 std::this_thread::yield();
801 return Buffer(buffer);
804 BufferOrExceptT ret = doCreateBuffer(name, iter, createDecoder(name));
805 Buffer *buffer = std::get_if<Buffer>(&ret);
806 if(EXPECT(!buffer, false))
807 throw std::get<std::runtime_error>(ret);
808 return *buffer;
811 Buffer ContextImpl::getBufferAsync(const String &name)
813 CheckContext(this);
815 auto hasher = std::hash<String>();
816 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
817 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
818 { return hasher(lhs->getName()) < rhs; }
820 if(iter != mBuffers.end() && (*iter)->getName() == name)
821 return Buffer(iter->get());
823 BufferOrExceptT ret = doCreateBufferAsync(name, iter, createDecoder(name));
824 Buffer *buffer = std::get_if<Buffer>(&ret);
825 if(EXPECT(!buffer, false))
826 throw std::get<std::runtime_error>(ret);
827 mWakeMutex.lock(); mWakeMutex.unlock();
828 mWakeThread.notify_all();
829 return *buffer;
832 void ContextImpl::precacheBuffersAsync(ArrayView<String> names)
834 CheckContext(this);
836 auto hasher = std::hash<String>();
837 for(const String &name : names)
839 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
840 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
841 { return hasher(lhs->getName()) < rhs; }
843 if(iter != mBuffers.end() && (*iter)->getName() == name)
844 continue;
846 DecoderOrExceptT dec = findDecoder(name);
847 if(SharedPtr<Decoder> *decoder = std::get_if<SharedPtr<Decoder>>(&dec))
848 doCreateBufferAsync(name, iter, *decoder);
850 mWakeMutex.lock(); mWakeMutex.unlock();
851 mWakeThread.notify_all();
854 Buffer ContextImpl::createBufferFrom(const String &name, SharedPtr<Decoder> decoder)
856 CheckContext(this);
858 auto hasher = std::hash<String>();
859 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
860 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
861 { return hasher(lhs->getName()) < rhs; }
863 if(iter != mBuffers.end() && (*iter)->getName() == name)
864 throw std::runtime_error("Buffer \""+name+"\" already exists");
866 BufferOrExceptT ret = doCreateBuffer(name, iter, std::move(decoder));
867 Buffer *buffer = std::get_if<Buffer>(&ret);
868 if(EXPECT(!buffer, false))
869 throw std::get<std::runtime_error>(ret);
870 return *buffer;
873 Buffer ContextImpl::createBufferAsyncFrom(const String &name, SharedPtr<Decoder> decoder)
875 CheckContext(this);
877 auto hasher = std::hash<String>();
878 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
879 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
880 { return hasher(lhs->getName()) < rhs; }
882 if(iter != mBuffers.end() && (*iter)->getName() == name)
883 throw std::runtime_error("Buffer \""+name+"\" already exists");
885 BufferOrExceptT ret = doCreateBufferAsync(name, iter, std::move(decoder));
886 Buffer *buffer = std::get_if<Buffer>(&ret);
887 if(EXPECT(!buffer, false))
888 throw std::get<std::runtime_error>(ret);
889 mWakeMutex.lock(); mWakeMutex.unlock();
890 mWakeThread.notify_all();
891 return *buffer;
895 void ContextImpl::removeBuffer(const String &name)
897 CheckContext(this);
898 auto hasher = std::hash<String>();
899 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
900 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
901 { return hasher(lhs->getName()) < rhs; }
903 if(iter != mBuffers.end() && (*iter)->getName() == name)
905 (*iter)->cleanup();
906 mBuffers.erase(iter);
911 ALuint ContextImpl::getSourceId(ALuint maxprio)
913 ALuint id = 0;
914 if(mSourceIds.empty())
916 alGetError();
917 alGenSources(1, &id);
918 if(alGetError() == AL_NO_ERROR)
919 return id;
921 SourceImpl *lowest = nullptr;
922 for(SourceBufferUpdateEntry &entry : mPlaySources)
924 if(!lowest || entry.mSource->getPriority() < lowest->getPriority())
925 lowest = entry.mSource;
927 for(SourceStreamUpdateEntry &entry : mStreamSources)
929 if(!lowest || entry.mSource->getPriority() < lowest->getPriority())
930 lowest = entry.mSource;
932 if(lowest && lowest->getPriority() < maxprio)
934 lowest->stop();
935 if(mMessage.get())
936 mMessage->sourceForceStopped(lowest);
939 if(mSourceIds.empty())
940 throw std::runtime_error("No available sources");
942 id = mSourceIds.top();
943 mSourceIds.pop();
944 return id;
948 Source ContextImpl::createSource()
950 CheckContext(this);
952 SourceImpl *source;
953 if(!mFreeSources.empty())
955 source = mFreeSources.front();
956 mFreeSources.pop();
958 else
960 mAllSources.emplace_back(this);
961 source = &mAllSources.back();
963 return Source(source);
967 void ContextImpl::addPlayingSource(SourceImpl *source, ALuint id)
969 auto iter = std::lower_bound(mPlaySources.begin(), mPlaySources.end(), source,
970 [](const SourceBufferUpdateEntry &lhs, SourceImpl *rhs) -> bool
971 { return lhs.mSource < rhs; }
973 if(iter == mPlaySources.end() || iter->mSource != source)
974 mPlaySources.insert(iter, {source,id});
977 void ContextImpl::addPlayingSource(SourceImpl *source)
979 auto iter = std::lower_bound(mStreamSources.begin(), mStreamSources.end(), source,
980 [](const SourceStreamUpdateEntry &lhs, SourceImpl *rhs) -> bool
981 { return lhs.mSource < rhs; }
983 if(iter == mStreamSources.end() || iter->mSource != source)
984 mStreamSources.insert(iter, {source});
987 void ContextImpl::removePlayingSource(SourceImpl *source)
989 auto iter0 = std::lower_bound(mPlaySources.begin(), mPlaySources.end(), source,
990 [](const SourceBufferUpdateEntry &lhs, SourceImpl *rhs) -> bool
991 { return lhs.mSource < rhs; }
993 if(iter0 != mPlaySources.end() && iter0->mSource == source)
994 mPlaySources.erase(iter0);
995 else
997 auto iter1 = std::lower_bound(mStreamSources.begin(), mStreamSources.end(), source,
998 [](const SourceStreamUpdateEntry &lhs, SourceImpl *rhs) -> bool
999 { return lhs.mSource < rhs; }
1001 if(iter1 != mStreamSources.end() && iter1->mSource == source)
1002 mStreamSources.erase(iter1);
1007 void ContextImpl::addStream(SourceImpl *source)
1009 std::lock_guard<std::mutex> lock(mSourceStreamMutex);
1010 if(mThread.get_id() == std::thread::id())
1011 mThread = std::thread(std::mem_fn(&ContextImpl::backgroundProc), this);
1012 auto iter = std::lower_bound(mStreamingSources.begin(), mStreamingSources.end(), source);
1013 if(iter == mStreamingSources.end() || *iter != source)
1014 mStreamingSources.insert(iter, source);
1017 void ContextImpl::removeStream(SourceImpl *source)
1019 std::lock_guard<std::mutex> lock(mSourceStreamMutex);
1020 auto iter = std::lower_bound(mStreamingSources.begin(), mStreamingSources.end(), source);
1021 if(iter != mStreamingSources.end() && *iter == source)
1022 mStreamingSources.erase(iter);
1025 void ContextImpl::removeStreamNoLock(SourceImpl *source)
1027 auto iter = std::lower_bound(mStreamingSources.begin(), mStreamingSources.end(), source);
1028 if(iter != mStreamingSources.end() && *iter == source)
1029 mStreamingSources.erase(iter);
1033 AuxiliaryEffectSlot ContextImpl::createAuxiliaryEffectSlot()
1035 if(!hasExtension(EXT_EFX) || !alGenAuxiliaryEffectSlots)
1036 throw std::runtime_error("AuxiliaryEffectSlots not supported");
1037 CheckContext(this);
1039 alGetError();
1040 ALuint id = 0;
1041 alGenAuxiliaryEffectSlots(1, &id);
1042 if(alGetError() != AL_NO_ERROR)
1043 throw std::runtime_error("Failed to create AuxiliaryEffectSlot");
1044 try {
1045 return AuxiliaryEffectSlot(new AuxiliaryEffectSlotImpl(this, id));
1047 catch(...) {
1048 alDeleteAuxiliaryEffectSlots(1, &id);
1049 throw;
1054 Effect ContextImpl::createEffect()
1056 if(!hasExtension(EXT_EFX))
1057 throw std::runtime_error("Effects not supported");
1058 CheckContext(this);
1060 alGetError();
1061 ALuint id = 0;
1062 alGenEffects(1, &id);
1063 if(alGetError() != AL_NO_ERROR)
1064 throw std::runtime_error("Failed to create Effect");
1065 try {
1066 return Effect(new EffectImpl(this, id));
1068 catch(...) {
1069 alDeleteEffects(1, &id);
1070 throw;
1075 SourceGroup ContextImpl::createSourceGroup(String name)
1077 auto iter = std::lower_bound(mSourceGroups.begin(), mSourceGroups.end(), name,
1078 [](const UniquePtr<SourceGroupImpl> &lhs, const String &rhs) -> bool
1079 { return lhs->getName() < rhs; }
1081 if(iter != mSourceGroups.end() && (*iter)->getName() == name)
1082 throw std::runtime_error("Duplicate source group name");
1083 iter = mSourceGroups.insert(iter, MakeUnique<SourceGroupImpl>(this, std::move(name)));
1084 return SourceGroup(iter->get());
1087 SourceGroup ContextImpl::getSourceGroup(const String &name)
1089 auto iter = std::lower_bound(mSourceGroups.begin(), mSourceGroups.end(), name,
1090 [](const UniquePtr<SourceGroupImpl> &lhs, const String &rhs) -> bool
1091 { return lhs->getName() < rhs; }
1093 if(iter == mSourceGroups.end() || (*iter)->getName() != name)
1094 throw std::runtime_error("Source group not found");
1095 return SourceGroup(iter->get());
1098 void ContextImpl::freeSourceGroup(SourceGroupImpl *group)
1100 auto iter = std::lower_bound(mSourceGroups.begin(), mSourceGroups.end(), group->getName(),
1101 [](const UniquePtr<SourceGroupImpl> &lhs, const String &rhs) -> bool
1102 { return lhs->getName() < rhs; }
1104 if(iter != mSourceGroups.end() && iter->get() == group)
1105 mSourceGroups.erase(iter);
1109 void ContextImpl::setDopplerFactor(ALfloat factor)
1111 if(!(factor >= 0.0f))
1112 throw std::runtime_error("Doppler factor out of range");
1113 CheckContext(this);
1114 alDopplerFactor(factor);
1118 void ContextImpl::setSpeedOfSound(ALfloat speed)
1120 if(!(speed > 0.0f))
1121 throw std::runtime_error("Speed of sound out of range");
1122 CheckContext(this);
1123 alSpeedOfSound(speed);
1127 void ContextImpl::setDistanceModel(DistanceModel model)
1129 CheckContext(this);
1130 alDistanceModel((ALenum)model);
1134 void ContextImpl::update()
1136 CheckContext(this);
1137 mPlaySources.erase(
1138 std::remove_if(mPlaySources.begin(), mPlaySources.end(),
1139 [](const SourceBufferUpdateEntry &entry) -> bool
1140 { return !entry.mSource->playUpdate(entry.mId); }
1141 ), mPlaySources.end()
1143 mStreamSources.erase(
1144 std::remove_if(mStreamSources.begin(), mStreamSources.end(),
1145 [](const SourceStreamUpdateEntry &entry) -> bool
1146 { return !entry.mSource->playUpdate(); }
1147 ), mStreamSources.end()
1149 if(!mWakeInterval.load(std::memory_order_relaxed).count())
1151 // For performance reasons, don't wait for the thread's mutex. This
1152 // should be called often enough to keep up with any and all streams
1153 // regardless.
1154 mWakeThread.notify_all();
1157 if(hasExtension(EXT_disconnect) && mIsConnected)
1159 ALCint connected;
1160 alcGetIntegerv(alcGetContextsDevice(mContext), ALC_CONNECTED, 1, &connected);
1161 mIsConnected = connected;
1162 if(!connected && mMessage.get()) mMessage->deviceDisconnected(Device(mDevice));
1166 void Context::destroy()
1168 pImpl->destroy();
1169 pImpl = nullptr;
1171 DECL_THUNK0(Device, Context, getDevice,)
1172 DECL_THUNK0(void, Context, startBatch,)
1173 DECL_THUNK0(void, Context, endBatch,)
1174 DECL_THUNK0(Listener, Context, getListener,)
1175 DECL_THUNK1(SharedPtr<MessageHandler>, Context, setMessageHandler,, SharedPtr<MessageHandler>)
1176 DECL_THUNK0(SharedPtr<MessageHandler>, Context, getMessageHandler, const)
1177 DECL_THUNK1(void, Context, setAsyncWakeInterval,, std::chrono::milliseconds)
1178 DECL_THUNK0(std::chrono::milliseconds, Context, getAsyncWakeInterval, const)
1179 DECL_THUNK1(SharedPtr<Decoder>, Context, createDecoder,, const String&)
1180 DECL_THUNK2(bool, Context, isSupported, const, ChannelConfig, SampleType)
1181 DECL_THUNK0(ArrayView<String>, Context, getAvailableResamplers,)
1182 DECL_THUNK0(ALsizei, Context, getDefaultResamplerIndex, const)
1183 DECL_THUNK1(Buffer, Context, getBuffer,, const String&)
1184 DECL_THUNK1(Buffer, Context, getBufferAsync,, const String&)
1185 DECL_THUNK1(void, Context, precacheBuffersAsync,, ArrayView<String>)
1186 DECL_THUNK2(Buffer, Context, createBufferFrom,, const String&, SharedPtr<Decoder>)
1187 DECL_THUNK2(Buffer, Context, createBufferAsyncFrom,, const String&, SharedPtr<Decoder>)
1188 DECL_THUNK1(void, Context, removeBuffer,, const String&)
1189 DECL_THUNK1(void, Context, removeBuffer,, Buffer)
1190 DECL_THUNK0(Source, Context, createSource,)
1191 DECL_THUNK0(AuxiliaryEffectSlot, Context, createAuxiliaryEffectSlot,)
1192 DECL_THUNK0(Effect, Context, createEffect,)
1193 DECL_THUNK1(SourceGroup, Context, createSourceGroup,, String)
1194 DECL_THUNK1(SourceGroup, Context, getSourceGroup,, const String&)
1195 DECL_THUNK1(void, Context, setDopplerFactor,, ALfloat)
1196 DECL_THUNK1(void, Context, setSpeedOfSound,, ALfloat)
1197 DECL_THUNK1(void, Context, setDistanceModel,, DistanceModel)
1198 DECL_THUNK0(void, Context, update,)
1201 void Context::MakeCurrent(Context context)
1202 { ContextImpl::MakeCurrent(context.pImpl); }
1204 Context Context::GetCurrent()
1205 { return Context(ContextImpl::GetCurrent()); }
1207 void Context::MakeThreadCurrent(Context context)
1208 { ContextImpl::MakeThreadCurrent(context.pImpl); }
1210 Context Context::GetThreadCurrent()
1211 { return Context(ContextImpl::GetThreadCurrent()); }
1214 void ListenerImpl::setGain(ALfloat gain)
1216 if(!(gain >= 0.0f))
1217 throw std::runtime_error("Gain out of range");
1218 CheckContext(mContext);
1219 alListenerf(AL_GAIN, gain);
1223 void ListenerImpl::set3DParameters(const Vector3 &position, const Vector3 &velocity, std::pair<Vector3,Vector3> orientation)
1225 static_assert(sizeof(orientation) == sizeof(ALfloat[6]), "Invalid Vector3 pair size");
1226 CheckContext(mContext);
1227 Batcher batcher = mContext->getBatcher();
1228 alListenerfv(AL_POSITION, position.getPtr());
1229 alListenerfv(AL_VELOCITY, velocity.getPtr());
1230 alListenerfv(AL_ORIENTATION, orientation.first.getPtr());
1233 void ListenerImpl::setPosition(ALfloat x, ALfloat y, ALfloat z)
1235 CheckContext(mContext);
1236 alListener3f(AL_POSITION, x, y, z);
1239 void ListenerImpl::setPosition(const ALfloat *pos)
1241 CheckContext(mContext);
1242 alListenerfv(AL_POSITION, pos);
1245 void ListenerImpl::setVelocity(ALfloat x, ALfloat y, ALfloat z)
1247 CheckContext(mContext);
1248 alListener3f(AL_VELOCITY, x, y, z);
1251 void ListenerImpl::setVelocity(const ALfloat *vel)
1253 CheckContext(mContext);
1254 alListenerfv(AL_VELOCITY, vel);
1257 void ListenerImpl::setOrientation(ALfloat x1, ALfloat y1, ALfloat z1, ALfloat x2, ALfloat y2, ALfloat z2)
1259 CheckContext(mContext);
1260 ALfloat ori[6] = { x1, y1, z1, x2, y2, z2 };
1261 alListenerfv(AL_ORIENTATION, ori);
1264 void ListenerImpl::setOrientation(const ALfloat *at, const ALfloat *up)
1266 CheckContext(mContext);
1267 ALfloat ori[6] = { at[0], at[1], at[2], up[0], up[1], up[2] };
1268 alListenerfv(AL_ORIENTATION, ori);
1271 void ListenerImpl::setOrientation(const ALfloat *ori)
1273 CheckContext(mContext);
1274 alListenerfv(AL_ORIENTATION, ori);
1277 void ListenerImpl::setMetersPerUnit(ALfloat m_u)
1279 if(!(m_u > 0.0f))
1280 throw std::runtime_error("Invalid meters per unit");
1281 CheckContext(mContext);
1282 if(mContext->hasExtension(EXT_EFX))
1283 alListenerf(AL_METERS_PER_UNIT, m_u);
1287 using Vector3Pair = std::pair<Vector3,Vector3>;
1289 DECL_THUNK1(void, Listener, setGain,, ALfloat)
1290 DECL_THUNK3(void, Listener, set3DParameters,, const Vector3&, const Vector3&, Vector3Pair)
1291 DECL_THUNK3(void, Listener, setPosition,, ALfloat, ALfloat, ALfloat)
1292 DECL_THUNK1(void, Listener, setPosition,, const ALfloat*)
1293 DECL_THUNK3(void, Listener, setVelocity,, ALfloat, ALfloat, ALfloat)
1294 DECL_THUNK1(void, Listener, setVelocity,, const ALfloat*)
1295 DECL_THUNK6(void, Listener, setOrientation,, ALfloat, ALfloat, ALfloat, ALfloat, ALfloat, ALfloat)
1296 DECL_THUNK2(void, Listener, setOrientation,, const ALfloat*, const ALfloat*)
1297 DECL_THUNK1(void, Listener, setOrientation,, const ALfloat*)
1298 DECL_THUNK1(void, Listener, setMetersPerUnit,, ALfloat)