Reorder some context fields and remove unnecessary update parameters
[alure.git] / src / context.cpp
blob28aa4ee229917fdcb93153d130fe3a95f2811d93
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 #ifdef _WIN32
53 // Windows' std::ifstream fails with non-ANSI paths since the standard only
54 // specifies names using const char* (or std::string). MSVC has a non-standard
55 // extension using const wchar_t* (or std::wstring?) to handle Unicode paths,
56 // but not all Windows compilers support it. So we have to make our own istream
57 // that accepts UTF-8 paths and forwards to Unicode-aware I/O functions.
58 class StreamBuf : public std::streambuf {
59 alure::Array<traits_type::char_type,4096> mBuffer;
60 HANDLE mFile;
62 int_type underflow() override final
64 if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr())
66 // Read in the next chunk of data, and set the pointers on success
67 DWORD got = 0;
68 if(!ReadFile(mFile, mBuffer.data(), mBuffer.size(), &got, NULL))
69 got = 0;
70 setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
72 if(gptr() == egptr())
73 return traits_type::eof();
74 return traits_type::to_int_type(*gptr());
77 pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override final
79 if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
80 return traits_type::eof();
82 LARGE_INTEGER fpos;
83 switch(whence)
85 case std::ios_base::beg:
86 fpos.QuadPart = offset;
87 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
88 return traits_type::eof();
89 break;
91 case std::ios_base::cur:
92 // If the offset remains in the current buffer range, just
93 // update the pointer.
94 if((offset >= 0 && offset < off_type(egptr()-gptr())) ||
95 (offset < 0 && -offset <= off_type(gptr()-eback())))
97 // Get the current file offset to report the correct read
98 // offset.
99 fpos.QuadPart = 0;
100 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
101 return traits_type::eof();
102 setg(eback(), gptr()+offset, egptr());
103 return fpos.QuadPart - off_type(egptr()-gptr());
105 // Need to offset for the file offset being at egptr() while
106 // the requested offset is relative to gptr().
107 offset -= off_type(egptr()-gptr());
108 fpos.QuadPart = offset;
109 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
110 return traits_type::eof();
111 break;
113 case std::ios_base::end:
114 fpos.QuadPart = offset;
115 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END))
116 return traits_type::eof();
117 break;
119 default:
120 return traits_type::eof();
122 setg(0, 0, 0);
123 return fpos.QuadPart;
126 pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override final
128 // Simplified version of seekoff
129 if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
130 return traits_type::eof();
132 LARGE_INTEGER fpos;
133 fpos.QuadPart = pos;
134 if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
135 return traits_type::eof();
137 setg(0, 0, 0);
138 return fpos.QuadPart;
141 public:
142 bool open(const char *filename)
144 alure::Vector<wchar_t> wname;
145 int wnamelen;
147 wnamelen = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
148 if(wnamelen <= 0) return false;
150 wname.resize(wnamelen);
151 MultiByteToWideChar(CP_UTF8, 0, filename, -1, wname.data(), wnamelen);
153 mFile = CreateFileW(wname.data(), GENERIC_READ, FILE_SHARE_READ, NULL,
154 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
155 if(mFile == INVALID_HANDLE_VALUE) return false;
156 return true;
159 bool is_open() const { return mFile != INVALID_HANDLE_VALUE; }
161 StreamBuf() : mFile(INVALID_HANDLE_VALUE)
163 ~StreamBuf() override final
165 if(mFile != INVALID_HANDLE_VALUE)
166 CloseHandle(mFile);
167 mFile = INVALID_HANDLE_VALUE;
171 // Inherit from std::istream to use our custom streambuf
172 class Stream : public std::istream {
173 public:
174 Stream(const char *filename) : std::istream(new StreamBuf())
176 // Set the failbit if the file failed to open.
177 if(!(static_cast<StreamBuf*>(rdbuf())->open(filename)))
178 clear(failbit);
180 ~Stream() override final
181 { delete rdbuf(); }
183 bool is_open() const { return static_cast<StreamBuf*>(rdbuf())->is_open(); }
185 #endif
189 namespace alure
192 Decoder::~Decoder() { }
193 DecoderFactory::~DecoderFactory() { }
195 static const std::pair<String,UniquePtr<DecoderFactory>> sDefaultDecoders[] = {
196 { "_alure_int_wave", MakeUnique<WaveDecoderFactory>() },
198 #ifdef HAVE_VORBISFILE
199 { "_alure_int_vorbis", MakeUnique<VorbisFileDecoderFactory>() },
200 #endif
201 #ifdef HAVE_LIBFLAC
202 { "_alure_int_flac", MakeUnique<FlacDecoderFactory>() },
203 #endif
204 #ifdef HAVE_OPUSFILE
205 { "_alure_int_opus", MakeUnique<OpusFileDecoderFactory>() },
206 #endif
207 #ifdef HAVE_LIBSNDFILE
208 { "_alure_int_sndfile", MakeUnique<SndFileDecoderFactory>() },
209 #endif
210 #ifdef HAVE_MPG123
211 { "_alure_int_mpg123", MakeUnique<Mpg123DecoderFactory>() },
212 #endif
215 static std::map<String,UniquePtr<DecoderFactory>> sDecoders;
218 template<typename T>
219 static SharedPtr<Decoder> GetDecoder(const String &name, UniquePtr<std::istream> &file, T start, T end)
221 while(start != end)
223 DecoderFactory *factory = start->second.get();
224 auto decoder = factory->createDecoder(file);
225 if(decoder) return decoder;
227 if(!file || !(file->clear(),file->seekg(0)))
228 throw std::runtime_error("Failed to rewind "+name+" for the next decoder factory");
230 ++start;
233 return nullptr;
236 static SharedPtr<Decoder> GetDecoder(const String &name, UniquePtr<std::istream> file)
238 auto decoder = GetDecoder(name, file, sDecoders.begin(), sDecoders.end());
239 if(!decoder) decoder = GetDecoder(name, file, std::begin(sDefaultDecoders), std::end(sDefaultDecoders));
240 if(!decoder) throw std::runtime_error("No decoder for "+name);
241 return decoder;
244 void RegisterDecoder(const String &name, UniquePtr<DecoderFactory> factory)
246 while(sDecoders.find(name) != sDecoders.end())
247 throw std::runtime_error("Decoder factory \""+name+"\" already registered");
248 sDecoders.insert(std::make_pair(name, std::move(factory)));
251 UniquePtr<DecoderFactory> UnregisterDecoder(const String &name)
253 auto iter = sDecoders.find(name);
254 if(iter != sDecoders.end())
256 UniquePtr<DecoderFactory> factory = std::move(iter->second);
257 sDecoders.erase(iter);
258 return factory;
260 return nullptr;
264 FileIOFactory::~FileIOFactory() { }
266 class DefaultFileIOFactory : public FileIOFactory {
267 UniquePtr<std::istream> openFile(const String &name) override final
269 #ifdef _WIN32
270 auto file = MakeUnique<Stream>(name.c_str());
271 #else
272 auto file = MakeUnique<std::ifstream>(name.c_str(), std::ios::binary);
273 #endif
274 if(!file->is_open()) file = nullptr;
275 return std::move(file);
278 static DefaultFileIOFactory sDefaultFileFactory;
280 static UniquePtr<FileIOFactory> sFileFactory;
281 UniquePtr<FileIOFactory> FileIOFactory::set(UniquePtr<FileIOFactory> factory)
283 std::swap(sFileFactory, factory);
284 return factory;
287 FileIOFactory &FileIOFactory::get()
289 FileIOFactory *factory = sFileFactory.get();
290 if(factory) return *factory;
291 return sDefaultFileFactory;
295 // Default message handler methods are no-ops.
296 MessageHandler::~MessageHandler()
300 void MessageHandler::deviceDisconnected(Device)
304 void MessageHandler::sourceStopped(Source)
308 void MessageHandler::sourceForceStopped(Source)
312 void MessageHandler::bufferLoading(const String&, ChannelConfig, SampleType, ALuint, const ArrayView<ALbyte>)
316 String MessageHandler::resourceNotFound(const String&)
318 return String();
322 template<typename T>
323 static inline void LoadALFunc(T **func, const char *name)
324 { *func = reinterpret_cast<T*>(alGetProcAddress(name)); }
326 static void LoadNothing(ContextImpl*) { }
328 static void LoadEFX(ContextImpl *ctx)
330 LoadALFunc(&ctx->alGenEffects, "alGenEffects");
331 LoadALFunc(&ctx->alDeleteEffects, "alDeleteEffects");
332 LoadALFunc(&ctx->alIsEffect, "alIsEffect");
333 LoadALFunc(&ctx->alEffecti, "alEffecti");
334 LoadALFunc(&ctx->alEffectiv, "alEffectiv");
335 LoadALFunc(&ctx->alEffectf, "alEffectf");
336 LoadALFunc(&ctx->alEffectfv, "alEffectfv");
337 LoadALFunc(&ctx->alGetEffecti, "alGetEffecti");
338 LoadALFunc(&ctx->alGetEffectiv, "alGetEffectiv");
339 LoadALFunc(&ctx->alGetEffectf, "alGetEffectf");
340 LoadALFunc(&ctx->alGetEffectfv, "alGetEffectfv");
342 LoadALFunc(&ctx->alGenFilters, "alGenFilters");
343 LoadALFunc(&ctx->alDeleteFilters, "alDeleteFilters");
344 LoadALFunc(&ctx->alIsFilter, "alIsFilter");
345 LoadALFunc(&ctx->alFilteri, "alFilteri");
346 LoadALFunc(&ctx->alFilteriv, "alFilteriv");
347 LoadALFunc(&ctx->alFilterf, "alFilterf");
348 LoadALFunc(&ctx->alFilterfv, "alFilterfv");
349 LoadALFunc(&ctx->alGetFilteri, "alGetFilteri");
350 LoadALFunc(&ctx->alGetFilteriv, "alGetFilteriv");
351 LoadALFunc(&ctx->alGetFilterf, "alGetFilterf");
352 LoadALFunc(&ctx->alGetFilterfv, "alGetFilterfv");
354 LoadALFunc(&ctx->alGenAuxiliaryEffectSlots, "alGenAuxiliaryEffectSlots");
355 LoadALFunc(&ctx->alDeleteAuxiliaryEffectSlots, "alDeleteAuxiliaryEffectSlots");
356 LoadALFunc(&ctx->alIsAuxiliaryEffectSlot, "alIsAuxiliaryEffectSlot");
357 LoadALFunc(&ctx->alAuxiliaryEffectSloti, "alAuxiliaryEffectSloti");
358 LoadALFunc(&ctx->alAuxiliaryEffectSlotiv, "alAuxiliaryEffectSlotiv");
359 LoadALFunc(&ctx->alAuxiliaryEffectSlotf, "alAuxiliaryEffectSlotf");
360 LoadALFunc(&ctx->alAuxiliaryEffectSlotfv, "alAuxiliaryEffectSlotfv");
361 LoadALFunc(&ctx->alGetAuxiliaryEffectSloti, "alGetAuxiliaryEffectSloti");
362 LoadALFunc(&ctx->alGetAuxiliaryEffectSlotiv, "alGetAuxiliaryEffectSlotiv");
363 LoadALFunc(&ctx->alGetAuxiliaryEffectSlotf, "alGetAuxiliaryEffectSlotf");
364 LoadALFunc(&ctx->alGetAuxiliaryEffectSlotfv, "alGetAuxiliaryEffectSlotfv");
367 static void LoadSourceResampler(ContextImpl *ctx)
369 LoadALFunc(&ctx->alGetStringiSOFT, "alGetStringiSOFT");
372 static void LoadSourceLatency(ContextImpl *ctx)
374 LoadALFunc(&ctx->alGetSourcei64vSOFT, "alGetSourcei64vSOFT");
375 LoadALFunc(&ctx->alGetSourcedvSOFT, "alGetSourcedvSOFT");
378 static const struct {
379 enum ALExtension extension;
380 const char name[32];
381 void (&loader)(ContextImpl*);
382 } ALExtensionList[] = {
383 { EXT_EFX, "ALC_EXT_EFX", LoadEFX },
385 { EXT_FLOAT32, "AL_EXT_FLOAT32", LoadNothing },
386 { EXT_MCFORMATS, "AL_EXT_MCFORMATS", LoadNothing },
387 { EXT_BFORMAT, "AL_EXT_BFORMAT", LoadNothing },
389 { EXT_MULAW, "AL_EXT_MULAW", LoadNothing },
390 { EXT_MULAW_MCFORMATS, "AL_EXT_MULAW_MCFORMATS", LoadNothing },
391 { EXT_MULAW_BFORMAT, "AL_EXT_MULAW_BFORMAT", LoadNothing },
393 { SOFT_loop_points, "AL_SOFT_loop_points", LoadNothing },
394 { SOFT_source_latency, "AL_SOFT_source_latency", LoadSourceLatency },
395 { SOFT_source_resampler, "AL_SOFT_source_resampler", LoadSourceResampler },
396 { SOFT_source_spatialize, "AL_SOFT_source_spatialize", LoadNothing },
398 { EXT_disconnect, "ALC_EXT_disconnect", LoadNothing },
400 { EXT_SOURCE_RADIUS, "AL_EXT_SOURCE_RADIUS", LoadNothing },
401 { EXT_STEREO_ANGLES, "AL_EXT_STEREO_ANGLES", LoadNothing },
405 ContextImpl *ContextImpl::sCurrentCtx = nullptr;
406 thread_local ContextImpl *ContextImpl::sThreadCurrentCtx = nullptr;
408 void ContextImpl::MakeCurrent(ContextImpl *context)
410 std::unique_lock<std::mutex> lock1, lock2;
411 if(sCurrentCtx)
412 lock1 = std::unique_lock<std::mutex>(sCurrentCtx->mContextMutex);
413 if(context && context != sCurrentCtx)
414 lock2 = std::unique_lock<std::mutex>(context->mContextMutex);
416 if(alcMakeContextCurrent(context ? context->getContext() : 0) == ALC_FALSE)
417 throw std::runtime_error("Call to alcMakeContextCurrent failed");
418 if(context)
420 context->addRef();
421 std::call_once(context->mSetExts, std::mem_fn(&ContextImpl::setupExts), context);
423 std::swap(sCurrentCtx, context);
424 if(context) context->decRef();
426 if(sThreadCurrentCtx)
427 sThreadCurrentCtx->decRef();
428 sThreadCurrentCtx = 0;
430 if(sCurrentCtx && sCurrentCtx != context)
432 lock2.unlock();
433 sCurrentCtx->mWakeThread.notify_all();
437 void ContextImpl::MakeThreadCurrent(ContextImpl *context)
439 if(!DeviceManagerImpl::SetThreadContext)
440 throw std::runtime_error("Thread-local contexts unsupported");
441 if(DeviceManagerImpl::SetThreadContext(context ? context->getContext() : nullptr) == ALC_FALSE)
442 throw std::runtime_error("Call to alcSetThreadContext failed");
443 if(context)
445 context->addRef();
446 std::call_once(context->mSetExts, std::mem_fn(&ContextImpl::setupExts), context);
448 if(sThreadCurrentCtx)
449 sThreadCurrentCtx->decRef();
450 sThreadCurrentCtx = context;
453 void ContextImpl::setupExts()
455 ALCdevice *device = mDevice->getDevice();
456 std::fill(std::begin(mHasExt), std::end(mHasExt), false);
457 for(const auto &entry : ALExtensionList)
459 mHasExt[entry.extension] = (strncmp(entry.name, "ALC", 3) == 0) ?
460 alcIsExtensionPresent(device, entry.name) :
461 alIsExtensionPresent(entry.name);
462 if(mHasExt[entry.extension]) entry.loader(this);
467 void ContextImpl::backgroundProc()
469 if(DeviceManagerImpl::SetThreadContext && mDevice->hasExtension(EXT_thread_local_context))
470 DeviceManagerImpl::SetThreadContext(getContext());
472 std::chrono::steady_clock::time_point basetime = std::chrono::steady_clock::now();
473 std::chrono::milliseconds waketime(0);
474 std::unique_lock<std::mutex> ctxlock(mContextMutex);
475 while(!mQuitThread.load(std::memory_order_acquire))
478 std::lock_guard<std::mutex> srclock(mSourceStreamMutex);
479 mStreamingSources.erase(
480 std::remove_if(mStreamingSources.begin(), mStreamingSources.end(),
481 [](SourceImpl *source) -> bool
482 { return !source->updateAsync(); }
483 ), mStreamingSources.end()
487 // Only do one pending buffer at a time. In case there's several large
488 // buffers to load, we still need to process streaming sources so they
489 // don't underrun.
490 RingBuffer::Data ringdata = mPendingBuffers.get_read_vector()[0];
491 if(ringdata.len > 0)
493 PendingBuffer *pb = reinterpret_cast<PendingBuffer*>(ringdata.buf);
494 pb->mBuffer->load(pb->mFrames, pb->mFormat, pb->mDecoder, pb->mName, this);
495 pb->~PendingBuffer();
496 mPendingBuffers.read_advance(1);
497 continue;
500 std::unique_lock<std::mutex> wakelock(mWakeMutex);
501 if(!mQuitThread.load(std::memory_order_acquire) && mPendingBuffers.read_space() == 0)
503 ctxlock.unlock();
505 std::chrono::milliseconds interval = mWakeInterval.load(std::memory_order_relaxed);
506 if(interval.count() == 0)
507 mWakeThread.wait(wakelock);
508 else
510 auto now = std::chrono::steady_clock::now() - basetime;
511 if(now > waketime)
513 auto mult = (now-waketime + interval-std::chrono::milliseconds(1)) / interval;
514 waketime += interval * mult;
515 mWakeThread.wait_until(wakelock, waketime + basetime);
518 wakelock.unlock();
520 ctxlock.lock();
521 while(!mQuitThread.load(std::memory_order_acquire) &&
522 alcGetCurrentContext() != getContext())
523 mWakeThread.wait(ctxlock);
526 ctxlock.unlock();
528 if(DeviceManagerImpl::SetThreadContext)
529 DeviceManagerImpl::SetThreadContext(nullptr);
533 ContextImpl::ContextImpl(ALCcontext *context, DeviceImpl *device)
534 : mListener(this), mContext(context), mDevice(device),
535 mWakeInterval(std::chrono::milliseconds::zero()),
536 mPendingBuffers(16, sizeof(PendingBuffer)), mQuitThread(false),
537 mRefs(0), mHasExt{false}, mIsConnected(true), mIsBatching(false),
538 alGetSourcei64vSOFT(0), alGetSourcedvSOFT(0),
539 alGenEffects(0), alDeleteEffects(0), alIsEffect(0),
540 alEffecti(0), alEffectiv(0), alEffectf(0), alEffectfv(0),
541 alGetEffecti(0), alGetEffectiv(0), alGetEffectf(0), alGetEffectfv(0),
542 alGenFilters(0), alDeleteFilters(0), alIsFilter(0),
543 alFilteri(0), alFilteriv(0), alFilterf(0), alFilterfv(0),
544 alGetFilteri(0), alGetFilteriv(0), alGetFilterf(0), alGetFilterfv(0),
545 alGenAuxiliaryEffectSlots(0), alDeleteAuxiliaryEffectSlots(0), alIsAuxiliaryEffectSlot(0),
546 alAuxiliaryEffectSloti(0), alAuxiliaryEffectSlotiv(0), alAuxiliaryEffectSlotf(0), alAuxiliaryEffectSlotfv(0),
547 alGetAuxiliaryEffectSloti(0), alGetAuxiliaryEffectSlotiv(0), alGetAuxiliaryEffectSlotf(0), alGetAuxiliaryEffectSlotfv(0)
551 ContextImpl::~ContextImpl()
553 auto ringdata = mPendingBuffers.get_read_vector();
554 if(ringdata[0].len > 0)
556 PendingBuffer *pb = reinterpret_cast<PendingBuffer*>(ringdata[0].buf);
557 for(size_t i = 0;i < ringdata[0].len;i++)
558 pb[i].~PendingBuffer();
559 pb = reinterpret_cast<PendingBuffer*>(ringdata[1].buf);
560 for(size_t i = 0;i < ringdata[1].len;i++)
561 pb[i].~PendingBuffer();
563 mPendingBuffers.read_advance(ringdata[0].len + ringdata[1].len);
568 void ContextImpl::destroy()
570 if(mRefs.load() != 0)
571 throw std::runtime_error("Context is in use");
572 if(!mBuffers.empty())
573 throw std::runtime_error("Trying to destroy a context with buffers");
575 if(mThread.joinable())
577 std::unique_lock<std::mutex> lock(mWakeMutex);
578 mQuitThread.store(true, std::memory_order_release);
579 lock.unlock();
580 mWakeThread.notify_all();
581 mThread.join();
584 alcDestroyContext(mContext);
585 mContext = nullptr;
587 mDevice->removeContext(this);
591 void ContextImpl::startBatch()
593 alcSuspendContext(mContext);
594 mIsBatching = true;
597 void ContextImpl::endBatch()
599 alcProcessContext(mContext);
600 mIsBatching = false;
604 SharedPtr<MessageHandler> ContextImpl::setMessageHandler(SharedPtr<MessageHandler> handler)
606 std::lock_guard<std::mutex> lock(mContextMutex);
607 mMessage.swap(handler);
608 return handler;
612 void ContextImpl::setAsyncWakeInterval(std::chrono::milliseconds msec)
614 if(msec.count() < 0 || msec > std::chrono::milliseconds(1000))
615 throw std::runtime_error("Async wake interval out of range");
616 mWakeInterval.store(msec);
617 mWakeMutex.lock(); mWakeMutex.unlock();
618 mWakeThread.notify_all();
622 SharedPtr<Decoder> ContextImpl::createDecoder(const String &name)
624 CheckContext(this);
625 auto file = FileIOFactory::get().openFile(name);
626 if(file) return GetDecoder(name, std::move(file));
628 // Resource not found. Try to find a substitute.
629 if(!mMessage.get()) throw std::runtime_error("Failed to open "+name);
630 String oldname = name;
631 do {
632 String newname(mMessage->resourceNotFound(oldname));
633 if(newname.empty())
634 throw std::runtime_error("Failed to open "+oldname);
635 file = FileIOFactory::get().openFile(newname);
636 oldname = std::move(newname);
637 } while(!file);
639 return GetDecoder(oldname, std::move(file));
643 bool ContextImpl::isSupported(ChannelConfig channels, SampleType type) const
645 CheckContext(this);
646 return GetFormat(channels, type) != AL_NONE;
650 ArrayView<String> ContextImpl::getAvailableResamplers()
652 CheckContext(this);
653 if(mResamplers.empty() && hasExtension(SOFT_source_resampler))
655 ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
656 mResamplers.reserve(num_resamplers);
657 for(int i = 0;i < num_resamplers;i++)
658 mResamplers.emplace_back(alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i));
659 if(mResamplers.empty())
660 mResamplers.emplace_back();
662 return mResamplers;
665 ALsizei ContextImpl::getDefaultResamplerIndex() const
667 CheckContext(this);
668 if(!hasExtension(SOFT_source_resampler))
669 return 0;
670 return alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
674 BufferOrExceptT ContextImpl::doCreateBuffer(const String &name, Vector<UniquePtr<BufferImpl>>::iterator iter, SharedPtr<Decoder> decoder)
676 BufferOrExceptT retval;
677 ALuint srate = decoder->getFrequency();
678 ChannelConfig chans = decoder->getChannelConfig();
679 SampleType type = decoder->getSampleType();
680 ALuint frames = decoder->getLength();
682 Vector<ALbyte> data(FramesToBytes(frames, chans, type));
683 frames = decoder->read(data.data(), frames);
684 if(!frames) return (retval = std::runtime_error("No samples for buffer"));
685 data.resize(FramesToBytes(frames, chans, type));
687 std::pair<uint64_t,uint64_t> loop_pts = decoder->getLoopPoints();
688 if(loop_pts.first >= loop_pts.second)
689 loop_pts = std::make_pair(0, frames);
690 else
692 loop_pts.second = std::min<uint64_t>(loop_pts.second, frames);
693 loop_pts.first = std::min<uint64_t>(loop_pts.first, loop_pts.second-1);
696 // Get the format before calling the bufferLoading message handler, to
697 // ensure it's something OpenAL can handle.
698 ALenum format = GetFormat(chans, type);
699 if(format == AL_NONE)
701 std::stringstream sstr;
702 sstr<< "Format not supported ("<<GetSampleTypeName(type)<<", "<<GetChannelConfigName(chans)<<")";
703 return (retval = std::runtime_error(sstr.str()));
706 if(mMessage.get())
707 mMessage->bufferLoading(name, chans, type, srate, data);
709 alGetError();
710 ALuint bid = 0;
711 alGenBuffers(1, &bid);
712 alBufferData(bid, format, data.data(), data.size(), srate);
713 if(hasExtension(SOFT_loop_points))
715 ALint pts[2]{(ALint)loop_pts.first, (ALint)loop_pts.second};
716 alBufferiv(bid, AL_LOOP_POINTS_SOFT, pts);
718 if(alGetError() != AL_NO_ERROR)
720 alDeleteBuffers(1, &bid);
721 return (retval = std::runtime_error("Failed to buffer data"));
724 return (retval = mBuffers.insert(iter,
725 MakeUnique<BufferImpl>(this, bid, srate, chans, type, true, name)
726 )->get());
729 BufferOrExceptT ContextImpl::doCreateBufferAsync(const String &name, Vector<UniquePtr<BufferImpl>>::iterator iter, SharedPtr<Decoder> decoder)
731 BufferOrExceptT retval;
732 ALuint srate = decoder->getFrequency();
733 ChannelConfig chans = decoder->getChannelConfig();
734 SampleType type = decoder->getSampleType();
735 ALuint frames = decoder->getLength();
736 if(!frames) return (retval = std::runtime_error("No samples for buffer"));
738 ALenum format = GetFormat(chans, type);
739 if(format == AL_NONE)
741 std::stringstream sstr;
742 sstr<< "Format not supported ("<<GetSampleTypeName(type)<<", "<<GetChannelConfigName(chans)<<")";
743 return (retval = std::runtime_error(sstr.str()));
746 alGetError();
747 ALuint bid = 0;
748 alGenBuffers(1, &bid);
749 if(alGetError() != AL_NO_ERROR)
750 return (retval = std::runtime_error("Failed to create buffer"));
752 auto buffer = MakeUnique<BufferImpl>(this, bid, srate, chans, type, false, name);
754 if(mThread.get_id() == std::thread::id())
755 mThread = std::thread(std::mem_fn(&ContextImpl::backgroundProc), this);
757 while(mPendingBuffers.write_space() == 0)
759 mWakeThread.notify_all();
760 std::this_thread::yield();
763 RingBuffer::Data ringdata = mPendingBuffers.get_write_vector()[0];
764 new(ringdata.buf) PendingBuffer{name, buffer.get(), decoder, format, frames};
765 mPendingBuffers.write_advance(1);
767 return (retval = mBuffers.insert(iter, std::move(buffer))->get());
770 Buffer ContextImpl::getBuffer(const String &name)
772 CheckContext(this);
774 auto hasher = std::hash<String>();
775 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
776 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
777 { return hasher(lhs->getName()) < rhs; }
779 if(iter != mBuffers.end() && (*iter)->getName() == name)
781 // Ensure the buffer is loaded before returning. getBuffer guarantees
782 // the returned buffer is loaded.
783 BufferImpl *buffer = iter->get();
784 while(buffer->getLoadStatus() == BufferLoadStatus::Pending)
785 std::this_thread::yield();
786 return Buffer(buffer);
789 BufferOrExceptT ret = doCreateBuffer(name, iter, createDecoder(name));
790 Buffer *buffer = std::get_if<Buffer>(&ret);
791 if(EXPECT(!buffer, false))
792 throw std::get<std::runtime_error>(ret);
793 return *buffer;
796 Buffer ContextImpl::getBufferAsync(const String &name)
798 CheckContext(this);
800 auto hasher = std::hash<String>();
801 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
802 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
803 { return hasher(lhs->getName()) < rhs; }
805 if(iter != mBuffers.end() && (*iter)->getName() == name)
806 return Buffer(iter->get());
808 BufferOrExceptT ret = doCreateBufferAsync(name, iter, createDecoder(name));
809 Buffer *buffer = std::get_if<Buffer>(&ret);
810 if(EXPECT(!buffer, false))
811 throw std::get<std::runtime_error>(ret);
812 mWakeMutex.lock(); mWakeMutex.unlock();
813 mWakeThread.notify_all();
814 return *buffer;
817 void ContextImpl::precacheBuffersAsync(ArrayView<String> names)
819 CheckContext(this);
821 auto hasher = std::hash<String>();
822 for(const String &name : names)
824 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
825 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
826 { return hasher(lhs->getName()) < rhs; }
828 if(iter != mBuffers.end() && (*iter)->getName() == name)
829 continue;
831 doCreateBufferAsync(name, iter, createDecoder(name));
833 mWakeMutex.lock(); mWakeMutex.unlock();
834 mWakeThread.notify_all();
837 Buffer ContextImpl::createBufferFrom(const String &name, SharedPtr<Decoder> decoder)
839 CheckContext(this);
841 auto hasher = std::hash<String>();
842 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
843 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
844 { return hasher(lhs->getName()) < rhs; }
846 if(iter != mBuffers.end() && (*iter)->getName() == name)
847 throw std::runtime_error("Buffer \""+name+"\" already exists");
849 BufferOrExceptT ret = doCreateBuffer(name, iter, std::move(decoder));
850 Buffer *buffer = std::get_if<Buffer>(&ret);
851 if(EXPECT(!buffer, false))
852 throw std::get<std::runtime_error>(ret);
853 return *buffer;
856 Buffer ContextImpl::createBufferAsyncFrom(const String &name, SharedPtr<Decoder> decoder)
858 CheckContext(this);
860 auto hasher = std::hash<String>();
861 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
862 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
863 { return hasher(lhs->getName()) < rhs; }
865 if(iter != mBuffers.end() && (*iter)->getName() == name)
866 throw std::runtime_error("Buffer \""+name+"\" already exists");
868 BufferOrExceptT ret = doCreateBufferAsync(name, iter, std::move(decoder));
869 Buffer *buffer = std::get_if<Buffer>(&ret);
870 if(EXPECT(!buffer, false))
871 throw std::get<std::runtime_error>(ret);
872 mWakeMutex.lock(); mWakeMutex.unlock();
873 mWakeThread.notify_all();
874 return *buffer;
878 void ContextImpl::removeBuffer(const String &name)
880 CheckContext(this);
881 auto hasher = std::hash<String>();
882 auto iter = std::lower_bound(mBuffers.begin(), mBuffers.end(), hasher(name),
883 [hasher](const UniquePtr<BufferImpl> &lhs, size_t rhs) -> bool
884 { return hasher(lhs->getName()) < rhs; }
886 if(iter != mBuffers.end() && (*iter)->getName() == name)
888 (*iter)->cleanup();
889 mBuffers.erase(iter);
894 ALuint ContextImpl::getSourceId(ALuint maxprio)
896 ALuint id = 0;
897 if(mSourceIds.empty())
899 alGetError();
900 alGenSources(1, &id);
901 if(alGetError() == AL_NO_ERROR)
902 return id;
904 SourceImpl *lowest = nullptr;
905 for(SourceBufferUpdateEntry &entry : mPlaySources)
907 if(!lowest || entry.mSource->getPriority() < lowest->getPriority())
908 lowest = entry.mSource;
910 for(SourceStreamUpdateEntry &entry : mStreamSources)
912 if(!lowest || entry.mSource->getPriority() < lowest->getPriority())
913 lowest = entry.mSource;
915 if(lowest && lowest->getPriority() < maxprio)
917 lowest->stop();
918 if(mMessage.get())
919 mMessage->sourceForceStopped(lowest);
922 if(mSourceIds.empty())
923 throw std::runtime_error("No available sources");
925 id = mSourceIds.top();
926 mSourceIds.pop();
927 return id;
931 Source ContextImpl::createSource()
933 CheckContext(this);
935 SourceImpl *source;
936 if(!mFreeSources.empty())
938 source = mFreeSources.front();
939 mFreeSources.pop();
941 else
943 mAllSources.emplace_back(this);
944 source = &mAllSources.back();
946 return Source(source);
950 void ContextImpl::addPlayingSource(SourceImpl *source, ALuint id)
952 auto iter = std::lower_bound(mPlaySources.begin(), mPlaySources.end(), source,
953 [](const SourceBufferUpdateEntry &lhs, SourceImpl *rhs) -> bool
954 { return lhs.mSource < rhs; }
956 if(iter == mPlaySources.end() || iter->mSource != source)
957 mPlaySources.insert(iter, {source,id});
960 void ContextImpl::addPlayingSource(SourceImpl *source)
962 auto iter = std::lower_bound(mStreamSources.begin(), mStreamSources.end(), source,
963 [](const SourceStreamUpdateEntry &lhs, SourceImpl *rhs) -> bool
964 { return lhs.mSource < rhs; }
966 if(iter == mStreamSources.end() || iter->mSource != source)
967 mStreamSources.insert(iter, {source});
970 void ContextImpl::removePlayingSource(SourceImpl *source)
972 auto iter0 = std::lower_bound(mPlaySources.begin(), mPlaySources.end(), source,
973 [](const SourceBufferUpdateEntry &lhs, SourceImpl *rhs) -> bool
974 { return lhs.mSource < rhs; }
976 if(iter0 != mPlaySources.end() && iter0->mSource == source)
977 mPlaySources.erase(iter0);
978 else
980 auto iter1 = std::lower_bound(mStreamSources.begin(), mStreamSources.end(), source,
981 [](const SourceStreamUpdateEntry &lhs, SourceImpl *rhs) -> bool
982 { return lhs.mSource < rhs; }
984 if(iter1 != mStreamSources.end() && iter1->mSource == source)
985 mStreamSources.erase(iter1);
990 void ContextImpl::addStream(SourceImpl *source)
992 std::lock_guard<std::mutex> lock(mSourceStreamMutex);
993 if(mThread.get_id() == std::thread::id())
994 mThread = std::thread(std::mem_fn(&ContextImpl::backgroundProc), this);
995 auto iter = std::lower_bound(mStreamingSources.begin(), mStreamingSources.end(), source);
996 if(iter == mStreamingSources.end() || *iter != source)
997 mStreamingSources.insert(iter, source);
1000 void ContextImpl::removeStream(SourceImpl *source)
1002 std::lock_guard<std::mutex> lock(mSourceStreamMutex);
1003 auto iter = std::lower_bound(mStreamingSources.begin(), mStreamingSources.end(), source);
1004 if(iter != mStreamingSources.end() && *iter == source)
1005 mStreamingSources.erase(iter);
1008 void ContextImpl::removeStreamNoLock(SourceImpl *source)
1010 auto iter = std::lower_bound(mStreamingSources.begin(), mStreamingSources.end(), source);
1011 if(iter != mStreamingSources.end() && *iter == source)
1012 mStreamingSources.erase(iter);
1016 AuxiliaryEffectSlot ContextImpl::createAuxiliaryEffectSlot()
1018 if(!hasExtension(EXT_EFX) || !alGenAuxiliaryEffectSlots)
1019 throw std::runtime_error("AuxiliaryEffectSlots not supported");
1020 CheckContext(this);
1022 alGetError();
1023 ALuint id = 0;
1024 alGenAuxiliaryEffectSlots(1, &id);
1025 if(alGetError() != AL_NO_ERROR)
1026 throw std::runtime_error("Failed to create AuxiliaryEffectSlot");
1027 try {
1028 return AuxiliaryEffectSlot(new AuxiliaryEffectSlotImpl(this, id));
1030 catch(...) {
1031 alDeleteAuxiliaryEffectSlots(1, &id);
1032 throw;
1037 Effect ContextImpl::createEffect()
1039 if(!hasExtension(EXT_EFX))
1040 throw std::runtime_error("Effects not supported");
1041 CheckContext(this);
1043 alGetError();
1044 ALuint id = 0;
1045 alGenEffects(1, &id);
1046 if(alGetError() != AL_NO_ERROR)
1047 throw std::runtime_error("Failed to create Effect");
1048 try {
1049 return Effect(new EffectImpl(this, id));
1051 catch(...) {
1052 alDeleteEffects(1, &id);
1053 throw;
1058 SourceGroup ContextImpl::createSourceGroup(String name)
1060 auto iter = std::lower_bound(mSourceGroups.begin(), mSourceGroups.end(), name,
1061 [](const UniquePtr<SourceGroupImpl> &lhs, const String &rhs) -> bool
1062 { return lhs->getName() < rhs; }
1064 if(iter != mSourceGroups.end() && (*iter)->getName() == name)
1065 throw std::runtime_error("Duplicate source group name");
1066 iter = mSourceGroups.insert(iter, MakeUnique<SourceGroupImpl>(this, std::move(name)));
1067 return SourceGroup(iter->get());
1070 SourceGroup ContextImpl::getSourceGroup(const String &name)
1072 auto iter = std::lower_bound(mSourceGroups.begin(), mSourceGroups.end(), name,
1073 [](const UniquePtr<SourceGroupImpl> &lhs, const String &rhs) -> bool
1074 { return lhs->getName() < rhs; }
1076 if(iter == mSourceGroups.end() || (*iter)->getName() != name)
1077 throw std::runtime_error("Source group not found");
1078 return SourceGroup(iter->get());
1081 void ContextImpl::freeSourceGroup(SourceGroupImpl *group)
1083 auto iter = std::lower_bound(mSourceGroups.begin(), mSourceGroups.end(), group->getName(),
1084 [](const UniquePtr<SourceGroupImpl> &lhs, const String &rhs) -> bool
1085 { return lhs->getName() < rhs; }
1087 if(iter != mSourceGroups.end() && iter->get() == group)
1088 mSourceGroups.erase(iter);
1092 void ContextImpl::setDopplerFactor(ALfloat factor)
1094 if(!(factor >= 0.0f))
1095 throw std::runtime_error("Doppler factor out of range");
1096 CheckContext(this);
1097 alDopplerFactor(factor);
1101 void ContextImpl::setSpeedOfSound(ALfloat speed)
1103 if(!(speed > 0.0f))
1104 throw std::runtime_error("Speed of sound out of range");
1105 CheckContext(this);
1106 alSpeedOfSound(speed);
1110 void ContextImpl::setDistanceModel(DistanceModel model)
1112 CheckContext(this);
1113 alDistanceModel((ALenum)model);
1117 void ContextImpl::update()
1119 CheckContext(this);
1120 mPlaySources.erase(
1121 std::remove_if(mPlaySources.begin(), mPlaySources.end(),
1122 [](const SourceBufferUpdateEntry &entry) -> bool
1123 { return !entry.mSource->playUpdate(entry.mId); }
1124 ), mPlaySources.end()
1126 mStreamSources.erase(
1127 std::remove_if(mStreamSources.begin(), mStreamSources.end(),
1128 [](const SourceStreamUpdateEntry &entry) -> bool
1129 { return !entry.mSource->playUpdate(); }
1130 ), mStreamSources.end()
1132 if(!mWakeInterval.load(std::memory_order_relaxed).count())
1134 // For performance reasons, don't wait for the thread's mutex. This
1135 // should be called often enough to keep up with any and all streams
1136 // regardless.
1137 mWakeThread.notify_all();
1140 if(hasExtension(EXT_disconnect) && mIsConnected)
1142 ALCint connected;
1143 alcGetIntegerv(alcGetContextsDevice(mContext), ALC_CONNECTED, 1, &connected);
1144 mIsConnected = connected;
1145 if(!connected && mMessage.get()) mMessage->deviceDisconnected(Device(mDevice));
1149 void Context::destroy()
1151 pImpl->destroy();
1152 pImpl = nullptr;
1154 DECL_THUNK0(Device, Context, getDevice,)
1155 DECL_THUNK0(void, Context, startBatch,)
1156 DECL_THUNK0(void, Context, endBatch,)
1157 DECL_THUNK0(Listener, Context, getListener,)
1158 DECL_THUNK1(SharedPtr<MessageHandler>, Context, setMessageHandler,, SharedPtr<MessageHandler>)
1159 DECL_THUNK0(SharedPtr<MessageHandler>, Context, getMessageHandler, const)
1160 DECL_THUNK1(void, Context, setAsyncWakeInterval,, std::chrono::milliseconds)
1161 DECL_THUNK0(std::chrono::milliseconds, Context, getAsyncWakeInterval, const)
1162 DECL_THUNK1(SharedPtr<Decoder>, Context, createDecoder,, const String&)
1163 DECL_THUNK2(bool, Context, isSupported, const, ChannelConfig, SampleType)
1164 DECL_THUNK0(ArrayView<String>, Context, getAvailableResamplers,)
1165 DECL_THUNK0(ALsizei, Context, getDefaultResamplerIndex, const)
1166 DECL_THUNK1(Buffer, Context, getBuffer,, const String&)
1167 DECL_THUNK1(Buffer, Context, getBufferAsync,, const String&)
1168 DECL_THUNK1(void, Context, precacheBuffersAsync,, ArrayView<String>)
1169 DECL_THUNK2(Buffer, Context, createBufferFrom,, const String&, SharedPtr<Decoder>)
1170 DECL_THUNK2(Buffer, Context, createBufferAsyncFrom,, const String&, SharedPtr<Decoder>)
1171 DECL_THUNK1(void, Context, removeBuffer,, const String&)
1172 DECL_THUNK1(void, Context, removeBuffer,, Buffer)
1173 DECL_THUNK0(Source, Context, createSource,)
1174 DECL_THUNK0(AuxiliaryEffectSlot, Context, createAuxiliaryEffectSlot,)
1175 DECL_THUNK0(Effect, Context, createEffect,)
1176 DECL_THUNK1(SourceGroup, Context, createSourceGroup,, String)
1177 DECL_THUNK1(SourceGroup, Context, getSourceGroup,, const String&)
1178 DECL_THUNK1(void, Context, setDopplerFactor,, ALfloat)
1179 DECL_THUNK1(void, Context, setSpeedOfSound,, ALfloat)
1180 DECL_THUNK1(void, Context, setDistanceModel,, DistanceModel)
1181 DECL_THUNK0(void, Context, update,)
1184 void Context::MakeCurrent(Context context)
1185 { ContextImpl::MakeCurrent(context.pImpl); }
1187 Context Context::GetCurrent()
1188 { return Context(ContextImpl::GetCurrent()); }
1190 void Context::MakeThreadCurrent(Context context)
1191 { ContextImpl::MakeThreadCurrent(context.pImpl); }
1193 Context Context::GetThreadCurrent()
1194 { return Context(ContextImpl::GetThreadCurrent()); }
1197 void ListenerImpl::setGain(ALfloat gain)
1199 if(!(gain >= 0.0f))
1200 throw std::runtime_error("Gain out of range");
1201 CheckContext(mContext);
1202 alListenerf(AL_GAIN, gain);
1206 void ListenerImpl::set3DParameters(const Vector3 &position, const Vector3 &velocity, std::pair<Vector3,Vector3> orientation)
1208 static_assert(sizeof(orientation) == sizeof(ALfloat[6]), "Invalid Vector3 pair size");
1209 CheckContext(mContext);
1210 Batcher batcher = mContext->getBatcher();
1211 alListenerfv(AL_POSITION, position.getPtr());
1212 alListenerfv(AL_VELOCITY, velocity.getPtr());
1213 alListenerfv(AL_ORIENTATION, orientation.first.getPtr());
1216 void ListenerImpl::setPosition(ALfloat x, ALfloat y, ALfloat z)
1218 CheckContext(mContext);
1219 alListener3f(AL_POSITION, x, y, z);
1222 void ListenerImpl::setPosition(const ALfloat *pos)
1224 CheckContext(mContext);
1225 alListenerfv(AL_POSITION, pos);
1228 void ListenerImpl::setVelocity(ALfloat x, ALfloat y, ALfloat z)
1230 CheckContext(mContext);
1231 alListener3f(AL_VELOCITY, x, y, z);
1234 void ListenerImpl::setVelocity(const ALfloat *vel)
1236 CheckContext(mContext);
1237 alListenerfv(AL_VELOCITY, vel);
1240 void ListenerImpl::setOrientation(ALfloat x1, ALfloat y1, ALfloat z1, ALfloat x2, ALfloat y2, ALfloat z2)
1242 CheckContext(mContext);
1243 ALfloat ori[6] = { x1, y1, z1, x2, y2, z2 };
1244 alListenerfv(AL_ORIENTATION, ori);
1247 void ListenerImpl::setOrientation(const ALfloat *at, const ALfloat *up)
1249 CheckContext(mContext);
1250 ALfloat ori[6] = { at[0], at[1], at[2], up[0], up[1], up[2] };
1251 alListenerfv(AL_ORIENTATION, ori);
1254 void ListenerImpl::setOrientation(const ALfloat *ori)
1256 CheckContext(mContext);
1257 alListenerfv(AL_ORIENTATION, ori);
1260 void ListenerImpl::setMetersPerUnit(ALfloat m_u)
1262 if(!(m_u > 0.0f))
1263 throw std::runtime_error("Invalid meters per unit");
1264 CheckContext(mContext);
1265 if(mContext->hasExtension(EXT_EFX))
1266 alListenerf(AL_METERS_PER_UNIT, m_u);
1270 using Vector3Pair = std::pair<Vector3,Vector3>;
1272 DECL_THUNK1(void, Listener, setGain,, ALfloat)
1273 DECL_THUNK3(void, Listener, set3DParameters,, const Vector3&, const Vector3&, Vector3Pair)
1274 DECL_THUNK3(void, Listener, setPosition,, ALfloat, ALfloat, ALfloat)
1275 DECL_THUNK1(void, Listener, setPosition,, const ALfloat*)
1276 DECL_THUNK3(void, Listener, setVelocity,, ALfloat, ALfloat, ALfloat)
1277 DECL_THUNK1(void, Listener, setVelocity,, const ALfloat*)
1278 DECL_THUNK6(void, Listener, setOrientation,, ALfloat, ALfloat, ALfloat, ALfloat, ALfloat, ALfloat)
1279 DECL_THUNK2(void, Listener, setOrientation,, const ALfloat*, const ALfloat*)
1280 DECL_THUNK1(void, Listener, setOrientation,, const ALfloat*)
1281 DECL_THUNK1(void, Listener, setMetersPerUnit,, ALfloat)