]> git.tdb.fi Git - ext/openal.git/blob - al/buffer.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / al / buffer.cpp
1 /**
2  * OpenAL cross platform audio library
3  * Copyright (C) 1999-2007 by authors.
4  * This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Library General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  *  License along with this library; if not, write to the
16  *  Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  * Or go to http://www.gnu.org/copyleft/lgpl.html
19  */
20
21 #include "config.h"
22
23 #include "buffer.h"
24
25 #include <algorithm>
26 #include <array>
27 #include <atomic>
28 #include <cassert>
29 #include <cstdint>
30 #include <cstdlib>
31 #include <cstring>
32 #include <iterator>
33 #include <limits>
34 #include <memory>
35 #include <mutex>
36 #include <new>
37 #include <numeric>
38 #include <stdexcept>
39 #include <utility>
40
41 #include "AL/al.h"
42 #include "AL/alc.h"
43 #include "AL/alext.h"
44
45 #include "albit.h"
46 #include "albyte.h"
47 #include "alc/context.h"
48 #include "alc/device.h"
49 #include "alc/inprogext.h"
50 #include "almalloc.h"
51 #include "alnumeric.h"
52 #include "aloptional.h"
53 #include "atomic.h"
54 #include "core/except.h"
55 #include "core/logging.h"
56 #include "core/voice.h"
57 #include "opthelpers.h"
58
59 #ifdef ALSOFT_EAX
60 #include "eax/globals.h"
61 #include "eax/x_ram.h"
62 #endif // ALSOFT_EAX
63
64
65 namespace {
66
67 al::optional<AmbiLayout> AmbiLayoutFromEnum(ALenum layout)
68 {
69     switch(layout)
70     {
71     case AL_FUMA_SOFT: return AmbiLayout::FuMa;
72     case AL_ACN_SOFT: return AmbiLayout::ACN;
73     }
74     return al::nullopt;
75 }
76 ALenum EnumFromAmbiLayout(AmbiLayout layout)
77 {
78     switch(layout)
79     {
80     case AmbiLayout::FuMa: return AL_FUMA_SOFT;
81     case AmbiLayout::ACN: return AL_ACN_SOFT;
82     }
83     throw std::runtime_error{"Invalid AmbiLayout: "+std::to_string(int(layout))};
84 }
85
86 al::optional<AmbiScaling> AmbiScalingFromEnum(ALenum scale)
87 {
88     switch(scale)
89     {
90     case AL_FUMA_SOFT: return AmbiScaling::FuMa;
91     case AL_SN3D_SOFT: return AmbiScaling::SN3D;
92     case AL_N3D_SOFT: return AmbiScaling::N3D;
93     }
94     return al::nullopt;
95 }
96 ALenum EnumFromAmbiScaling(AmbiScaling scale)
97 {
98     switch(scale)
99     {
100     case AmbiScaling::FuMa: return AL_FUMA_SOFT;
101     case AmbiScaling::SN3D: return AL_SN3D_SOFT;
102     case AmbiScaling::N3D: return AL_N3D_SOFT;
103     case AmbiScaling::UHJ: break;
104     }
105     throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))};
106 }
107
108 #ifdef ALSOFT_EAX
109 al::optional<EaxStorage> EaxStorageFromEnum(ALenum scale)
110 {
111     switch(scale)
112     {
113     case AL_STORAGE_AUTOMATIC: return EaxStorage::Automatic;
114     case AL_STORAGE_ACCESSIBLE: return EaxStorage::Accessible;
115     case AL_STORAGE_HARDWARE: return EaxStorage::Hardware;
116     }
117     return al::nullopt;
118 }
119 ALenum EnumFromEaxStorage(EaxStorage storage)
120 {
121     switch(storage)
122     {
123     case EaxStorage::Automatic: return AL_STORAGE_AUTOMATIC;
124     case EaxStorage::Accessible: return AL_STORAGE_ACCESSIBLE;
125     case EaxStorage::Hardware: return AL_STORAGE_HARDWARE;
126     }
127     throw std::runtime_error{"Invalid EaxStorage: "+std::to_string(int(storage))};
128 }
129
130
131 bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer,
132     const ALuint newsize) noexcept
133 {
134     ALuint freemem{device.eax_x_ram_free_size};
135     /* If the buffer is currently in "hardware", add its memory to the free
136      * pool since it'll be "replaced".
137      */
138     if(buffer.eax_x_ram_is_hardware)
139         freemem += buffer.OriginalSize;
140     return freemem >= newsize;
141 }
142
143 void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept
144 {
145     if(buffer.eax_x_ram_is_hardware)
146         return;
147
148     if(device.eax_x_ram_free_size >= buffer.OriginalSize)
149     {
150         device.eax_x_ram_free_size -= buffer.OriginalSize;
151         buffer.eax_x_ram_is_hardware = true;
152     }
153 }
154
155 void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer)
156 {
157     if(al_buffer.eax_x_ram_is_hardware)
158         al_device.eax_x_ram_free_size += al_buffer.OriginalSize;
159     al_buffer.eax_x_ram_is_hardware = false;
160 }
161 #endif // ALSOFT_EAX
162
163
164 constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT |
165     AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)};
166 constexpr ALbitfieldSOFT MAP_READ_WRITE_FLAGS{AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT};
167 constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT |
168     AL_MAP_PERSISTENT_BIT_SOFT)};
169
170
171 bool EnsureBuffers(ALCdevice *device, size_t needed)
172 {
173     size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), size_t{0},
174         [](size_t cur, const BufferSubList &sublist) noexcept -> size_t
175         { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
176
177     while(needed > count)
178     {
179         if(device->BufferList.size() >= 1<<25) UNLIKELY
180             return false;
181
182         device->BufferList.emplace_back();
183         auto sublist = device->BufferList.end() - 1;
184         sublist->FreeMask = ~0_u64;
185         sublist->Buffers = static_cast<ALbuffer*>(al_calloc(alignof(ALbuffer), sizeof(ALbuffer)*64));
186         if(!sublist->Buffers) UNLIKELY
187         {
188             device->BufferList.pop_back();
189             return false;
190         }
191         count += 64;
192     }
193     return true;
194 }
195
196 ALbuffer *AllocBuffer(ALCdevice *device)
197 {
198     auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(),
199         [](const BufferSubList &entry) noexcept -> bool
200         { return entry.FreeMask != 0; });
201     auto lidx = static_cast<ALuint>(std::distance(device->BufferList.begin(), sublist));
202     auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
203     ASSUME(slidx < 64);
204
205     ALbuffer *buffer{al::construct_at(sublist->Buffers + slidx)};
206
207     /* Add 1 to avoid buffer ID 0. */
208     buffer->id = ((lidx<<6) | slidx) + 1;
209
210     sublist->FreeMask &= ~(1_u64 << slidx);
211
212     return buffer;
213 }
214
215 void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
216 {
217 #ifdef ALSOFT_EAX
218     eax_x_ram_clear(*device, *buffer);
219 #endif // ALSOFT_EAX
220
221     const ALuint id{buffer->id - 1};
222     const size_t lidx{id >> 6};
223     const ALuint slidx{id & 0x3f};
224
225     al::destroy_at(buffer);
226
227     device->BufferList[lidx].FreeMask |= 1_u64 << slidx;
228 }
229
230 inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id)
231 {
232     const size_t lidx{(id-1) >> 6};
233     const ALuint slidx{(id-1) & 0x3f};
234
235     if(lidx >= device->BufferList.size()) UNLIKELY
236         return nullptr;
237     BufferSubList &sublist = device->BufferList[lidx];
238     if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
239         return nullptr;
240     return sublist.Buffers + slidx;
241 }
242
243
244 ALuint SanitizeAlignment(FmtType type, ALuint align)
245 {
246     if(align == 0)
247     {
248         if(type == FmtIMA4)
249         {
250             /* Here is where things vary:
251              * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel
252              * Most PC sound software uses 2040+1 sample frames per block -> block_size=1024 bytes per channel
253              */
254             return 65;
255         }
256         if(type == FmtMSADPCM)
257             return 64;
258         return 1;
259     }
260
261     if(type == FmtIMA4)
262     {
263         /* IMA4 block alignment must be a multiple of 8, plus 1. */
264         if((align&7) == 1) return static_cast<ALuint>(align);
265         return 0;
266     }
267     if(type == FmtMSADPCM)
268     {
269         /* MSADPCM block alignment must be a multiple of 2. */
270         if((align&1) == 0) return static_cast<ALuint>(align);
271         return 0;
272     }
273
274     return static_cast<ALuint>(align);
275 }
276
277
278 /** Loads the specified data into the buffer, using the specified format. */
279 void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
280     const FmtChannels DstChannels, const FmtType DstType, const al::byte *SrcData,
281     ALbitfieldSOFT access)
282 {
283     if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY
284         return context->setError(AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u",
285             ALBuf->id);
286
287     const ALuint unpackalign{ALBuf->UnpackAlign};
288     const ALuint align{SanitizeAlignment(DstType, unpackalign)};
289     if(align < 1) UNLIKELY
290         return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples",
291             unpackalign, NameFromFormat(DstType));
292
293     const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder :
294         (IsUHJ(DstChannels) ? 1 : 0)};
295
296     if((access&AL_PRESERVE_DATA_BIT_SOFT))
297     {
298         /* Can only preserve data with the same format and alignment. */
299         if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) UNLIKELY
300             return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched format");
301         if(ALBuf->mBlockAlign != align) UNLIKELY
302             return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched alignment");
303         if(ALBuf->mAmbiOrder != ambiorder) UNLIKELY
304             return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched order");
305     }
306
307     /* Convert the size in bytes to blocks using the unpack block alignment. */
308     const ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)};
309     const ALuint BlockSize{NumChannels *
310         ((DstType == FmtIMA4) ? (align-1)/2 + 4 :
311         (DstType == FmtMSADPCM) ? (align-2)/2 + 7 :
312         (align * BytesFromFmt(DstType)))};
313     if((size%BlockSize) != 0) UNLIKELY
314         return context->setError(AL_INVALID_VALUE,
315             "Data size %d is not a multiple of frame size %d (%d unpack alignment)",
316             size, BlockSize, align);
317     const ALuint blocks{size / BlockSize};
318
319     if(blocks > std::numeric_limits<ALsizei>::max()/align) UNLIKELY
320         return context->setError(AL_OUT_OF_MEMORY,
321             "Buffer size overflow, %d blocks x %d samples per block", blocks, align);
322     if(blocks > std::numeric_limits<size_t>::max()/BlockSize) UNLIKELY
323         return context->setError(AL_OUT_OF_MEMORY,
324             "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize);
325
326     const size_t newsize{static_cast<size_t>(blocks) * BlockSize};
327
328 #ifdef ALSOFT_EAX
329     if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware)
330     {
331         ALCdevice &device = *context->mALDevice;
332         if(!eax_x_ram_check_availability(device, *ALBuf, size))
333             return context->setError(AL_OUT_OF_MEMORY,
334                 "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size);
335     }
336 #endif
337
338     /* This could reallocate only when increasing the size or the new size is
339      * less than half the current, but then the buffer's AL_SIZE would not be
340      * very reliable for accounting buffer memory usage, and reporting the real
341      * size could cause problems for apps that use AL_SIZE to try to get the
342      * buffer's play length.
343      */
344     if(newsize != ALBuf->mDataStorage.size())
345     {
346         auto newdata = al::vector<al::byte,16>(newsize, al::byte{});
347         if((access&AL_PRESERVE_DATA_BIT_SOFT))
348         {
349             const size_t tocopy{minz(newdata.size(), ALBuf->mDataStorage.size())};
350             std::copy_n(ALBuf->mDataStorage.begin(), tocopy, newdata.begin());
351         }
352         newdata.swap(ALBuf->mDataStorage);
353     }
354     ALBuf->mData = ALBuf->mDataStorage;
355 #ifdef ALSOFT_EAX
356     eax_x_ram_clear(*context->mALDevice, *ALBuf);
357 #endif
358
359     if(SrcData != nullptr && !ALBuf->mData.empty())
360         std::copy_n(SrcData, blocks*BlockSize, ALBuf->mData.begin());
361     ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1;
362
363     ALBuf->OriginalSize = size;
364
365     ALBuf->Access = access;
366
367     ALBuf->mSampleRate = static_cast<ALuint>(freq);
368     ALBuf->mChannels = DstChannels;
369     ALBuf->mType = DstType;
370     ALBuf->mAmbiOrder = ambiorder;
371
372     ALBuf->mCallback = nullptr;
373     ALBuf->mUserData = nullptr;
374
375     ALBuf->mSampleLen = blocks * align;
376     ALBuf->mLoopStart = 0;
377     ALBuf->mLoopEnd = ALBuf->mSampleLen;
378
379 #ifdef ALSOFT_EAX
380     if(eax_g_is_enabled && ALBuf->eax_x_ram_mode == EaxStorage::Hardware)
381         eax_x_ram_apply(*context->mALDevice, *ALBuf);
382 #endif
383 }
384
385 /** Prepares the buffer to use the specified callback, using the specified format. */
386 void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
387     const FmtChannels DstChannels, const FmtType DstType, ALBUFFERCALLBACKTYPESOFT callback,
388     void *userptr)
389 {
390     if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY
391         return context->setError(AL_INVALID_OPERATION, "Modifying callback for in-use buffer %u",
392             ALBuf->id);
393
394     const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder :
395         (IsUHJ(DstChannels) ? 1 : 0)};
396
397     const ALuint unpackalign{ALBuf->UnpackAlign};
398     const ALuint align{SanitizeAlignment(DstType, unpackalign)};
399     const ALuint BlockSize{ChannelsFromFmt(DstChannels, ambiorder) *
400         ((DstType == FmtIMA4) ? (align-1)/2 + 4 :
401         (DstType == FmtMSADPCM) ? (align-2)/2 + 7 :
402         (align * BytesFromFmt(DstType)))};
403
404     /* The maximum number of samples a callback buffer may need to store is a
405      * full mixing line * max pitch * channel count, since it may need to hold
406      * a full line's worth of sample frames before downsampling. An additional
407      * MaxResamplerEdge is needed for "future" samples during resampling (the
408      * voice will hold a history for the past samples).
409      */
410     static constexpr size_t line_size{DeviceBase::MixerLineSize*MaxPitch + MaxResamplerEdge};
411     const size_t line_blocks{(line_size + align-1) / align};
412
413     using BufferVectorType = decltype(ALBuf->mDataStorage);
414     BufferVectorType(line_blocks*BlockSize).swap(ALBuf->mDataStorage);
415     ALBuf->mData = ALBuf->mDataStorage;
416
417 #ifdef ALSOFT_EAX
418     eax_x_ram_clear(*context->mALDevice, *ALBuf);
419 #endif
420
421     ALBuf->mCallback = callback;
422     ALBuf->mUserData = userptr;
423
424     ALBuf->OriginalSize = 0;
425     ALBuf->Access = 0;
426
427     ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1;
428     ALBuf->mSampleRate = static_cast<ALuint>(freq);
429     ALBuf->mChannels = DstChannels;
430     ALBuf->mType = DstType;
431     ALBuf->mAmbiOrder = ambiorder;
432
433     ALBuf->mSampleLen = 0;
434     ALBuf->mLoopStart = 0;
435     ALBuf->mLoopEnd = ALBuf->mSampleLen;
436 }
437
438 /** Prepares the buffer to use caller-specified storage. */
439 void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
440     const FmtChannels DstChannels, const FmtType DstType, al::byte *sdata, const ALuint sdatalen)
441 {
442     if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY
443         return context->setError(AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u",
444             ALBuf->id);
445
446     const ALuint unpackalign{ALBuf->UnpackAlign};
447     const ALuint align{SanitizeAlignment(DstType, unpackalign)};
448     if(align < 1) UNLIKELY
449         return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples",
450             unpackalign, NameFromFormat(DstType));
451
452     auto get_type_alignment = [](const FmtType type) noexcept -> ALuint
453     {
454         /* NOTE: This only needs to be the required alignment for the CPU to
455          * read/write the given sample type in the mixer.
456          */
457         switch(type)
458         {
459         case FmtUByte: return alignof(ALubyte);
460         case FmtShort: return alignof(ALshort);
461         case FmtFloat: return alignof(ALfloat);
462         case FmtDouble: return alignof(ALdouble);
463         case FmtMulaw: return alignof(ALubyte);
464         case FmtAlaw: return alignof(ALubyte);
465         case FmtIMA4: break;
466         case FmtMSADPCM: break;
467         }
468         return 1;
469     };
470     const auto typealign = get_type_alignment(DstType);
471     if((reinterpret_cast<uintptr_t>(sdata) & (typealign-1)) != 0)
472         return context->setError(AL_INVALID_VALUE, "Pointer %p is misaligned for %s samples (%u)",
473             static_cast<void*>(sdata), NameFromFormat(DstType), typealign);
474
475     const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder :
476         (IsUHJ(DstChannels) ? 1 : 0)};
477
478     /* Convert the size in bytes to blocks using the unpack block alignment. */
479     const ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)};
480     const ALuint BlockSize{NumChannels *
481         ((DstType == FmtIMA4) ? (align-1)/2 + 4 :
482         (DstType == FmtMSADPCM) ? (align-2)/2 + 7 :
483         (align * BytesFromFmt(DstType)))};
484     if((sdatalen%BlockSize) != 0) UNLIKELY
485         return context->setError(AL_INVALID_VALUE,
486             "Data size %u is not a multiple of frame size %u (%u unpack alignment)",
487             sdatalen, BlockSize, align);
488     const ALuint blocks{sdatalen / BlockSize};
489
490     if(blocks > std::numeric_limits<ALsizei>::max()/align) UNLIKELY
491         return context->setError(AL_OUT_OF_MEMORY,
492             "Buffer size overflow, %d blocks x %d samples per block", blocks, align);
493     if(blocks > std::numeric_limits<size_t>::max()/BlockSize) UNLIKELY
494         return context->setError(AL_OUT_OF_MEMORY,
495             "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize);
496
497 #ifdef ALSOFT_EAX
498     if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware)
499     {
500         ALCdevice &device = *context->mALDevice;
501         if(!eax_x_ram_check_availability(device, *ALBuf, sdatalen))
502             return context->setError(AL_OUT_OF_MEMORY,
503                 "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size,
504                 sdatalen);
505     }
506 #endif
507
508     decltype(ALBuf->mDataStorage){}.swap(ALBuf->mDataStorage);
509     ALBuf->mData = {static_cast<al::byte*>(sdata), sdatalen};
510
511 #ifdef ALSOFT_EAX
512     eax_x_ram_clear(*context->mALDevice, *ALBuf);
513 #endif
514
515     ALBuf->mCallback = nullptr;
516     ALBuf->mUserData = nullptr;
517
518     ALBuf->OriginalSize = sdatalen;
519     ALBuf->Access = 0;
520
521     ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1;
522     ALBuf->mSampleRate = static_cast<ALuint>(freq);
523     ALBuf->mChannels = DstChannels;
524     ALBuf->mType = DstType;
525     ALBuf->mAmbiOrder = ambiorder;
526
527     ALBuf->mSampleLen = blocks * align;
528     ALBuf->mLoopStart = 0;
529     ALBuf->mLoopEnd = ALBuf->mSampleLen;
530
531 #ifdef ALSOFT_EAX
532     if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware)
533         eax_x_ram_apply(*context->mALDevice, *ALBuf);
534 #endif
535 }
536
537
538 struct DecompResult { FmtChannels channels; FmtType type; };
539 al::optional<DecompResult> DecomposeUserFormat(ALenum format)
540 {
541     struct FormatMap {
542         ALenum format;
543         FmtChannels channels;
544         FmtType type;
545     };
546     static const std::array<FormatMap,63> UserFmtList{{
547         { AL_FORMAT_MONO8,             FmtMono, FmtUByte   },
548         { AL_FORMAT_MONO16,            FmtMono, FmtShort   },
549         { AL_FORMAT_MONO_FLOAT32,      FmtMono, FmtFloat   },
550         { AL_FORMAT_MONO_DOUBLE_EXT,   FmtMono, FmtDouble  },
551         { AL_FORMAT_MONO_IMA4,         FmtMono, FmtIMA4    },
552         { AL_FORMAT_MONO_MSADPCM_SOFT, FmtMono, FmtMSADPCM },
553         { AL_FORMAT_MONO_MULAW,        FmtMono, FmtMulaw   },
554         { AL_FORMAT_MONO_ALAW_EXT,     FmtMono, FmtAlaw    },
555
556         { AL_FORMAT_STEREO8,             FmtStereo, FmtUByte   },
557         { AL_FORMAT_STEREO16,            FmtStereo, FmtShort   },
558         { AL_FORMAT_STEREO_FLOAT32,      FmtStereo, FmtFloat   },
559         { AL_FORMAT_STEREO_DOUBLE_EXT,   FmtStereo, FmtDouble  },
560         { AL_FORMAT_STEREO_IMA4,         FmtStereo, FmtIMA4    },
561         { AL_FORMAT_STEREO_MSADPCM_SOFT, FmtStereo, FmtMSADPCM },
562         { AL_FORMAT_STEREO_MULAW,        FmtStereo, FmtMulaw   },
563         { AL_FORMAT_STEREO_ALAW_EXT,     FmtStereo, FmtAlaw    },
564
565         { AL_FORMAT_REAR8,      FmtRear, FmtUByte },
566         { AL_FORMAT_REAR16,     FmtRear, FmtShort },
567         { AL_FORMAT_REAR32,     FmtRear, FmtFloat },
568         { AL_FORMAT_REAR_MULAW, FmtRear, FmtMulaw },
569
570         { AL_FORMAT_QUAD8_LOKI,  FmtQuad, FmtUByte },
571         { AL_FORMAT_QUAD16_LOKI, FmtQuad, FmtShort },
572
573         { AL_FORMAT_QUAD8,      FmtQuad, FmtUByte },
574         { AL_FORMAT_QUAD16,     FmtQuad, FmtShort },
575         { AL_FORMAT_QUAD32,     FmtQuad, FmtFloat },
576         { AL_FORMAT_QUAD_MULAW, FmtQuad, FmtMulaw },
577
578         { AL_FORMAT_51CHN8,      FmtX51, FmtUByte },
579         { AL_FORMAT_51CHN16,     FmtX51, FmtShort },
580         { AL_FORMAT_51CHN32,     FmtX51, FmtFloat },
581         { AL_FORMAT_51CHN_MULAW, FmtX51, FmtMulaw },
582
583         { AL_FORMAT_61CHN8,      FmtX61, FmtUByte },
584         { AL_FORMAT_61CHN16,     FmtX61, FmtShort },
585         { AL_FORMAT_61CHN32,     FmtX61, FmtFloat },
586         { AL_FORMAT_61CHN_MULAW, FmtX61, FmtMulaw },
587
588         { AL_FORMAT_71CHN8,      FmtX71, FmtUByte },
589         { AL_FORMAT_71CHN16,     FmtX71, FmtShort },
590         { AL_FORMAT_71CHN32,     FmtX71, FmtFloat },
591         { AL_FORMAT_71CHN_MULAW, FmtX71, FmtMulaw },
592
593         { AL_FORMAT_BFORMAT2D_8,       FmtBFormat2D, FmtUByte },
594         { AL_FORMAT_BFORMAT2D_16,      FmtBFormat2D, FmtShort },
595         { AL_FORMAT_BFORMAT2D_FLOAT32, FmtBFormat2D, FmtFloat },
596         { AL_FORMAT_BFORMAT2D_MULAW,   FmtBFormat2D, FmtMulaw },
597
598         { AL_FORMAT_BFORMAT3D_8,       FmtBFormat3D, FmtUByte },
599         { AL_FORMAT_BFORMAT3D_16,      FmtBFormat3D, FmtShort },
600         { AL_FORMAT_BFORMAT3D_FLOAT32, FmtBFormat3D, FmtFloat },
601         { AL_FORMAT_BFORMAT3D_MULAW,   FmtBFormat3D, FmtMulaw },
602
603         { AL_FORMAT_UHJ2CHN8_SOFT,        FmtUHJ2, FmtUByte   },
604         { AL_FORMAT_UHJ2CHN16_SOFT,       FmtUHJ2, FmtShort   },
605         { AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, FmtUHJ2, FmtFloat   },
606         { AL_FORMAT_UHJ2CHN_MULAW_SOFT,   FmtUHJ2, FmtMulaw   },
607         { AL_FORMAT_UHJ2CHN_ALAW_SOFT,    FmtUHJ2, FmtAlaw    },
608         { AL_FORMAT_UHJ2CHN_IMA4_SOFT,    FmtUHJ2, FmtIMA4    },
609         { AL_FORMAT_UHJ2CHN_MSADPCM_SOFT, FmtUHJ2, FmtMSADPCM },
610
611         { AL_FORMAT_UHJ3CHN8_SOFT,        FmtUHJ3, FmtUByte },
612         { AL_FORMAT_UHJ3CHN16_SOFT,       FmtUHJ3, FmtShort },
613         { AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, FmtUHJ3, FmtFloat },
614         { AL_FORMAT_UHJ3CHN_MULAW_SOFT,   FmtUHJ3, FmtMulaw },
615         { AL_FORMAT_UHJ3CHN_ALAW_SOFT,    FmtUHJ3, FmtAlaw  },
616
617         { AL_FORMAT_UHJ4CHN8_SOFT,        FmtUHJ4, FmtUByte },
618         { AL_FORMAT_UHJ4CHN16_SOFT,       FmtUHJ4, FmtShort },
619         { AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, FmtUHJ4, FmtFloat },
620         { AL_FORMAT_UHJ4CHN_MULAW_SOFT,   FmtUHJ4, FmtMulaw },
621         { AL_FORMAT_UHJ4CHN_ALAW_SOFT,    FmtUHJ4, FmtAlaw  },
622     }};
623
624     for(const auto &fmt : UserFmtList)
625     {
626         if(fmt.format == format)
627             return al::make_optional<DecompResult>({fmt.channels, fmt.type});
628     }
629     return al::nullopt;
630 }
631
632 } // namespace
633
634
635 AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers)
636 START_API_FUNC
637 {
638     ContextRef context{GetContextRef()};
639     if(!context) UNLIKELY return;
640
641     if(n < 0) UNLIKELY
642         context->setError(AL_INVALID_VALUE, "Generating %d buffers", n);
643     if(n <= 0) UNLIKELY return;
644
645     ALCdevice *device{context->mALDevice.get()};
646     std::lock_guard<std::mutex> _{device->BufferLock};
647     if(!EnsureBuffers(device, static_cast<ALuint>(n)))
648     {
649         context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, (n==1)?"":"s");
650         return;
651     }
652
653     if(n == 1) LIKELY
654     {
655         /* Special handling for the easy and normal case. */
656         ALbuffer *buffer{AllocBuffer(device)};
657         buffers[0] = buffer->id;
658     }
659     else
660     {
661         /* Store the allocated buffer IDs in a separate local list, to avoid
662          * modifying the user storage in case of failure.
663          */
664         al::vector<ALuint> ids;
665         ids.reserve(static_cast<ALuint>(n));
666         do {
667             ALbuffer *buffer{AllocBuffer(device)};
668             ids.emplace_back(buffer->id);
669         } while(--n);
670         std::copy(ids.begin(), ids.end(), buffers);
671     }
672 }
673 END_API_FUNC
674
675 AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers)
676 START_API_FUNC
677 {
678     ContextRef context{GetContextRef()};
679     if(!context) UNLIKELY return;
680
681     if(n < 0) UNLIKELY
682         context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n);
683     if(n <= 0) UNLIKELY return;
684
685     ALCdevice *device{context->mALDevice.get()};
686     std::lock_guard<std::mutex> _{device->BufferLock};
687
688     /* First try to find any buffers that are invalid or in-use. */
689     auto validate_buffer = [device, &context](const ALuint bid) -> bool
690     {
691         if(!bid) return true;
692         ALbuffer *ALBuf{LookupBuffer(device, bid)};
693         if(!ALBuf) UNLIKELY
694         {
695             context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", bid);
696             return false;
697         }
698         if(ReadRef(ALBuf->ref) != 0) UNLIKELY
699         {
700             context->setError(AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid);
701             return false;
702         }
703         return true;
704     };
705     const ALuint *buffers_end = buffers + n;
706     auto invbuf = std::find_if_not(buffers, buffers_end, validate_buffer);
707     if(invbuf != buffers_end) UNLIKELY return;
708
709     /* All good. Delete non-0 buffer IDs. */
710     auto delete_buffer = [device](const ALuint bid) -> void
711     {
712         ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr};
713         if(buffer) FreeBuffer(device, buffer);
714     };
715     std::for_each(buffers, buffers_end, delete_buffer);
716 }
717 END_API_FUNC
718
719 AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer)
720 START_API_FUNC
721 {
722     ContextRef context{GetContextRef()};
723     if(context) LIKELY
724     {
725         ALCdevice *device{context->mALDevice.get()};
726         std::lock_guard<std::mutex> _{device->BufferLock};
727         if(!buffer || LookupBuffer(device, buffer))
728             return AL_TRUE;
729     }
730     return AL_FALSE;
731 }
732 END_API_FUNC
733
734
735 AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
736 START_API_FUNC
737 { alBufferStorageSOFT(buffer, format, data, size, freq, 0); }
738 END_API_FUNC
739
740 AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags)
741 START_API_FUNC
742 {
743     ContextRef context{GetContextRef()};
744     if(!context) UNLIKELY return;
745
746     ALCdevice *device{context->mALDevice.get()};
747     std::lock_guard<std::mutex> _{device->BufferLock};
748
749     ALbuffer *albuf = LookupBuffer(device, buffer);
750     if(!albuf) UNLIKELY
751         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
752     else if(size < 0) UNLIKELY
753         context->setError(AL_INVALID_VALUE, "Negative storage size %d", size);
754     else if(freq < 1) UNLIKELY
755         context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq);
756     else if((flags&INVALID_STORAGE_MASK) != 0) UNLIKELY
757         context->setError(AL_INVALID_VALUE, "Invalid storage flags 0x%x",
758             flags&INVALID_STORAGE_MASK);
759     else if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) UNLIKELY
760         context->setError(AL_INVALID_VALUE,
761             "Declaring persistently mapped storage without read or write access");
762     else
763     {
764         auto usrfmt = DecomposeUserFormat(format);
765         if(!usrfmt) UNLIKELY
766             context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
767         else
768         {
769             LoadData(context.get(), albuf, freq, static_cast<ALuint>(size), usrfmt->channels,
770                 usrfmt->type, static_cast<const al::byte*>(data), flags);
771         }
772     }
773 }
774 END_API_FUNC
775
776 void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size,
777     ALsizei freq)
778 START_API_FUNC
779 {
780     ContextRef context{GetContextRef()};
781     if(!context) UNLIKELY return;
782
783     ALCdevice *device{context->mALDevice.get()};
784     std::lock_guard<std::mutex> _{device->BufferLock};
785
786     ALbuffer *albuf = LookupBuffer(device, buffer);
787     if(!albuf) UNLIKELY
788         return context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
789     if(size < 0) UNLIKELY
790         return context->setError(AL_INVALID_VALUE, "Negative storage size %d", size);
791     if(freq < 1) UNLIKELY
792         return context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq);
793
794     auto usrfmt = DecomposeUserFormat(format);
795     if(!usrfmt) UNLIKELY
796         return context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
797
798     PrepareUserPtr(context.get(), albuf, freq, usrfmt->channels, usrfmt->type,
799         static_cast<al::byte*>(data), static_cast<ALuint>(size));
800 }
801 END_API_FUNC
802
803 AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access)
804 START_API_FUNC
805 {
806     ContextRef context{GetContextRef()};
807     if(!context) UNLIKELY return nullptr;
808
809     ALCdevice *device{context->mALDevice.get()};
810     std::lock_guard<std::mutex> _{device->BufferLock};
811
812     ALbuffer *albuf = LookupBuffer(device, buffer);
813     if(!albuf) UNLIKELY
814         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
815     else if((access&INVALID_MAP_FLAGS) != 0) UNLIKELY
816         context->setError(AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS);
817     else if(!(access&MAP_READ_WRITE_FLAGS)) UNLIKELY
818         context->setError(AL_INVALID_VALUE, "Mapping buffer %u without read or write access",
819             buffer);
820     else
821     {
822         ALbitfieldSOFT unavailable = (albuf->Access^access) & access;
823         if(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) UNLIKELY
824             context->setError(AL_INVALID_OPERATION,
825                 "Mapping in-use buffer %u without persistent mapping", buffer);
826         else if(albuf->MappedAccess != 0) UNLIKELY
827             context->setError(AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer);
828         else if((unavailable&AL_MAP_READ_BIT_SOFT)) UNLIKELY
829             context->setError(AL_INVALID_VALUE,
830                 "Mapping buffer %u for reading without read access", buffer);
831         else if((unavailable&AL_MAP_WRITE_BIT_SOFT)) UNLIKELY
832             context->setError(AL_INVALID_VALUE,
833                 "Mapping buffer %u for writing without write access", buffer);
834         else if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) UNLIKELY
835             context->setError(AL_INVALID_VALUE,
836                 "Mapping buffer %u persistently without persistent access", buffer);
837         else if(offset < 0 || length <= 0
838             || static_cast<ALuint>(offset) >= albuf->OriginalSize
839             || static_cast<ALuint>(length) > albuf->OriginalSize - static_cast<ALuint>(offset))
840             UNLIKELY
841             context->setError(AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u",
842                 offset, length, buffer);
843         else
844         {
845             void *retval{albuf->mData.data() + offset};
846             albuf->MappedAccess = access;
847             albuf->MappedOffset = offset;
848             albuf->MappedSize = length;
849             return retval;
850         }
851     }
852
853     return nullptr;
854 }
855 END_API_FUNC
856
857 AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer)
858 START_API_FUNC
859 {
860     ContextRef context{GetContextRef()};
861     if(!context) UNLIKELY return;
862
863     ALCdevice *device{context->mALDevice.get()};
864     std::lock_guard<std::mutex> _{device->BufferLock};
865
866     ALbuffer *albuf = LookupBuffer(device, buffer);
867     if(!albuf) UNLIKELY
868         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
869     else if(albuf->MappedAccess == 0) UNLIKELY
870         context->setError(AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer);
871     else
872     {
873         albuf->MappedAccess = 0;
874         albuf->MappedOffset = 0;
875         albuf->MappedSize = 0;
876     }
877 }
878 END_API_FUNC
879
880 AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length)
881 START_API_FUNC
882 {
883     ContextRef context{GetContextRef()};
884     if(!context) UNLIKELY return;
885
886     ALCdevice *device{context->mALDevice.get()};
887     std::lock_guard<std::mutex> _{device->BufferLock};
888
889     ALbuffer *albuf = LookupBuffer(device, buffer);
890     if(!albuf) UNLIKELY
891         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
892     else if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) UNLIKELY
893         context->setError(AL_INVALID_OPERATION, "Flushing buffer %u while not mapped for writing",
894             buffer);
895     else if(offset < albuf->MappedOffset || length <= 0
896         || offset >= albuf->MappedOffset+albuf->MappedSize
897         || length > albuf->MappedOffset+albuf->MappedSize-offset) UNLIKELY
898         context->setError(AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", offset,
899             length, buffer);
900     else
901     {
902         /* FIXME: Need to use some method of double-buffering for the mixer and
903          * app to hold separate memory, which can be safely transfered
904          * asynchronously. Currently we just say the app shouldn't write where
905          * OpenAL's reading, and hope for the best...
906          */
907         std::atomic_thread_fence(std::memory_order_seq_cst);
908     }
909 }
910 END_API_FUNC
911
912 AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length)
913 START_API_FUNC
914 {
915     ContextRef context{GetContextRef()};
916     if(!context) UNLIKELY return;
917
918     ALCdevice *device{context->mALDevice.get()};
919     std::lock_guard<std::mutex> _{device->BufferLock};
920
921     ALbuffer *albuf = LookupBuffer(device, buffer);
922     if(!albuf) UNLIKELY
923         return context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
924
925     auto usrfmt = DecomposeUserFormat(format);
926     if(!usrfmt) UNLIKELY
927         return context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
928
929     const ALuint unpack_align{albuf->UnpackAlign};
930     const ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)};
931     if(align < 1) UNLIKELY
932         return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align);
933     if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) UNLIKELY
934         return context->setError(AL_INVALID_ENUM, "Unpacking data with mismatched format");
935     if(align != albuf->mBlockAlign) UNLIKELY
936         return context->setError(AL_INVALID_VALUE,
937             "Unpacking data with alignment %u does not match original alignment %u", align,
938             albuf->mBlockAlign);
939     if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) UNLIKELY
940         return context->setError(AL_INVALID_VALUE,
941             "Unpacking data with mismatched ambisonic order");
942     if(albuf->MappedAccess != 0) UNLIKELY
943         return context->setError(AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u",
944             buffer);
945
946     const ALuint num_chans{albuf->channelsFromFmt()};
947     const ALuint byte_align{
948         (albuf->mType == FmtIMA4) ? ((align-1)/2 + 4) * num_chans :
949         (albuf->mType == FmtMSADPCM) ? ((align-2)/2 + 7) * num_chans :
950         (align * albuf->bytesFromFmt() * num_chans)};
951
952     if(offset < 0 || length < 0 || static_cast<ALuint>(offset) > albuf->OriginalSize
953         || static_cast<ALuint>(length) > albuf->OriginalSize-static_cast<ALuint>(offset))
954         UNLIKELY
955         return context->setError(AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u",
956             offset, length, buffer);
957     if((static_cast<ALuint>(offset)%byte_align) != 0) UNLIKELY
958         return context->setError(AL_INVALID_VALUE,
959             "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)",
960             offset, byte_align, align);
961     if((static_cast<ALuint>(length)%byte_align) != 0) UNLIKELY
962         return context->setError(AL_INVALID_VALUE,
963             "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)",
964             length, byte_align, align);
965
966     assert(al::to_underlying(usrfmt->type) == al::to_underlying(albuf->mType));
967     memcpy(albuf->mData.data()+offset, data, static_cast<ALuint>(length));
968 }
969 END_API_FUNC
970
971
972 AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/,
973     ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/,
974     const ALvoid* /*data*/)
975 START_API_FUNC
976 {
977     ContextRef context{GetContextRef()};
978     if(!context) UNLIKELY return;
979
980     context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported");
981 }
982 END_API_FUNC
983
984 AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/,
985     ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/)
986 START_API_FUNC
987 {
988     ContextRef context{GetContextRef()};
989     if(!context) UNLIKELY return;
990
991     context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported");
992 }
993 END_API_FUNC
994
995 AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/,
996     ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/)
997 START_API_FUNC
998 {
999     ContextRef context{GetContextRef()};
1000     if(!context) UNLIKELY return;
1001
1002     context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported");
1003 }
1004 END_API_FUNC
1005
1006 AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/)
1007 START_API_FUNC
1008 {
1009     ContextRef context{GetContextRef()};
1010     if(!context) UNLIKELY return AL_FALSE;
1011
1012     context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported");
1013     return AL_FALSE;
1014 }
1015 END_API_FUNC
1016
1017
1018 AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat /*value*/)
1019 START_API_FUNC
1020 {
1021     ContextRef context{GetContextRef()};
1022     if(!context) UNLIKELY return;
1023
1024     ALCdevice *device{context->mALDevice.get()};
1025     std::lock_guard<std::mutex> _{device->BufferLock};
1026
1027     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1028         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1029     else switch(param)
1030     {
1031     default:
1032         context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param);
1033     }
1034 }
1035 END_API_FUNC
1036
1037 AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param,
1038     ALfloat /*value1*/, ALfloat /*value2*/, ALfloat /*value3*/)
1039 START_API_FUNC
1040 {
1041     ContextRef context{GetContextRef()};
1042     if(!context) UNLIKELY return;
1043
1044     ALCdevice *device{context->mALDevice.get()};
1045     std::lock_guard<std::mutex> _{device->BufferLock};
1046
1047     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1048         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1049     else switch(param)
1050     {
1051     default:
1052         context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param);
1053     }
1054 }
1055 END_API_FUNC
1056
1057 AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values)
1058 START_API_FUNC
1059 {
1060     ContextRef context{GetContextRef()};
1061     if(!context) UNLIKELY return;
1062
1063     ALCdevice *device{context->mALDevice.get()};
1064     std::lock_guard<std::mutex> _{device->BufferLock};
1065
1066     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1067         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1068     else if(!values) UNLIKELY
1069         context->setError(AL_INVALID_VALUE, "NULL pointer");
1070     else switch(param)
1071     {
1072     default:
1073         context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param);
1074     }
1075 }
1076 END_API_FUNC
1077
1078
1079 AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value)
1080 START_API_FUNC
1081 {
1082     ContextRef context{GetContextRef()};
1083     if(!context) UNLIKELY return;
1084
1085     ALCdevice *device{context->mALDevice.get()};
1086     std::lock_guard<std::mutex> _{device->BufferLock};
1087
1088     ALbuffer *albuf = LookupBuffer(device, buffer);
1089     if(!albuf) UNLIKELY
1090         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1091     else switch(param)
1092     {
1093     case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
1094         if(value < 0) UNLIKELY
1095             context->setError(AL_INVALID_VALUE, "Invalid unpack block alignment %d", value);
1096         else
1097             albuf->UnpackAlign = static_cast<ALuint>(value);
1098         break;
1099
1100     case AL_PACK_BLOCK_ALIGNMENT_SOFT:
1101         if(value < 0) UNLIKELY
1102             context->setError(AL_INVALID_VALUE, "Invalid pack block alignment %d", value);
1103         else
1104             albuf->PackAlign = static_cast<ALuint>(value);
1105         break;
1106
1107     case AL_AMBISONIC_LAYOUT_SOFT:
1108         if(ReadRef(albuf->ref) != 0) UNLIKELY
1109             context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic layout",
1110                 buffer);
1111         else if(const auto layout = AmbiLayoutFromEnum(value))
1112             albuf->mAmbiLayout = layout.value();
1113         else UNLIKELY
1114             context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value);
1115         break;
1116
1117     case AL_AMBISONIC_SCALING_SOFT:
1118         if(ReadRef(albuf->ref) != 0) UNLIKELY
1119             context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic scaling",
1120                 buffer);
1121         else if(const auto scaling = AmbiScalingFromEnum(value))
1122             albuf->mAmbiScaling = scaling.value();
1123         else UNLIKELY
1124             context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value);
1125         break;
1126
1127     case AL_UNPACK_AMBISONIC_ORDER_SOFT:
1128         if(value < 1 || value > 14) UNLIKELY
1129             context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value);
1130         else
1131             albuf->UnpackAmbiOrder = static_cast<ALuint>(value);
1132         break;
1133
1134     default:
1135         context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param);
1136     }
1137 }
1138 END_API_FUNC
1139
1140 AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param,
1141     ALint /*value1*/, ALint /*value2*/, ALint /*value3*/)
1142 START_API_FUNC
1143 {
1144     ContextRef context{GetContextRef()};
1145     if(!context) UNLIKELY return;
1146
1147     ALCdevice *device{context->mALDevice.get()};
1148     std::lock_guard<std::mutex> _{device->BufferLock};
1149
1150     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1151         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1152     else switch(param)
1153     {
1154     default:
1155         context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param);
1156     }
1157 }
1158 END_API_FUNC
1159
1160 AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values)
1161 START_API_FUNC
1162 {
1163     if(values)
1164     {
1165         switch(param)
1166         {
1167         case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
1168         case AL_PACK_BLOCK_ALIGNMENT_SOFT:
1169         case AL_AMBISONIC_LAYOUT_SOFT:
1170         case AL_AMBISONIC_SCALING_SOFT:
1171         case AL_UNPACK_AMBISONIC_ORDER_SOFT:
1172             alBufferi(buffer, param, values[0]);
1173             return;
1174         }
1175     }
1176
1177     ContextRef context{GetContextRef()};
1178     if(!context) UNLIKELY return;
1179
1180     ALCdevice *device{context->mALDevice.get()};
1181     std::lock_guard<std::mutex> _{device->BufferLock};
1182
1183     ALbuffer *albuf = LookupBuffer(device, buffer);
1184     if(!albuf) UNLIKELY
1185         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1186     else if(!values) UNLIKELY
1187         context->setError(AL_INVALID_VALUE, "NULL pointer");
1188     else switch(param)
1189     {
1190     case AL_LOOP_POINTS_SOFT:
1191         if(ReadRef(albuf->ref) != 0) UNLIKELY
1192             context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points",
1193                 buffer);
1194         else if(values[0] < 0 || values[0] >= values[1]
1195             || static_cast<ALuint>(values[1]) > albuf->mSampleLen) UNLIKELY
1196             context->setError(AL_INVALID_VALUE, "Invalid loop point range %d -> %d on buffer %u",
1197                 values[0], values[1], buffer);
1198         else
1199         {
1200             albuf->mLoopStart = static_cast<ALuint>(values[0]);
1201             albuf->mLoopEnd = static_cast<ALuint>(values[1]);
1202         }
1203         break;
1204
1205     default:
1206         context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param);
1207     }
1208 }
1209 END_API_FUNC
1210
1211
1212 AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value)
1213 START_API_FUNC
1214 {
1215     ContextRef context{GetContextRef()};
1216     if(!context) UNLIKELY return;
1217
1218     ALCdevice *device{context->mALDevice.get()};
1219     std::lock_guard<std::mutex> _{device->BufferLock};
1220
1221     ALbuffer *albuf = LookupBuffer(device, buffer);
1222     if(!albuf) UNLIKELY
1223         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1224     else if(!value) UNLIKELY
1225         context->setError(AL_INVALID_VALUE, "NULL pointer");
1226     else switch(param)
1227     {
1228     case AL_SEC_LENGTH_SOFT:
1229         *value = (albuf->mSampleRate < 1) ? 0.0f :
1230             (static_cast<float>(albuf->mSampleLen) / static_cast<float>(albuf->mSampleRate));
1231         break;
1232
1233     default:
1234         context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param);
1235     }
1236 }
1237 END_API_FUNC
1238
1239 AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
1240 START_API_FUNC
1241 {
1242     ContextRef context{GetContextRef()};
1243     if(!context) UNLIKELY return;
1244
1245     ALCdevice *device{context->mALDevice.get()};
1246     std::lock_guard<std::mutex> _{device->BufferLock};
1247
1248     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1249         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1250     else if(!value1 || !value2 || !value3) UNLIKELY
1251         context->setError(AL_INVALID_VALUE, "NULL pointer");
1252     else switch(param)
1253     {
1254     default:
1255         context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param);
1256     }
1257 }
1258 END_API_FUNC
1259
1260 AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values)
1261 START_API_FUNC
1262 {
1263     switch(param)
1264     {
1265     case AL_SEC_LENGTH_SOFT:
1266         alGetBufferf(buffer, param, values);
1267         return;
1268     }
1269
1270     ContextRef context{GetContextRef()};
1271     if(!context) UNLIKELY return;
1272
1273     ALCdevice *device{context->mALDevice.get()};
1274     std::lock_guard<std::mutex> _{device->BufferLock};
1275
1276     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1277         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1278     else if(!values) UNLIKELY
1279         context->setError(AL_INVALID_VALUE, "NULL pointer");
1280     else switch(param)
1281     {
1282     default:
1283         context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param);
1284     }
1285 }
1286 END_API_FUNC
1287
1288
1289 AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value)
1290 START_API_FUNC
1291 {
1292     ContextRef context{GetContextRef()};
1293     if(!context) UNLIKELY return;
1294
1295     ALCdevice *device{context->mALDevice.get()};
1296     std::lock_guard<std::mutex> _{device->BufferLock};
1297     ALbuffer *albuf = LookupBuffer(device, buffer);
1298     if(!albuf) UNLIKELY
1299         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1300     else if(!value) UNLIKELY
1301         context->setError(AL_INVALID_VALUE, "NULL pointer");
1302     else switch(param)
1303     {
1304     case AL_FREQUENCY:
1305         *value = static_cast<ALint>(albuf->mSampleRate);
1306         break;
1307
1308     case AL_BITS:
1309         *value = (albuf->mType == FmtIMA4 || albuf->mType == FmtMSADPCM) ? 4
1310             : static_cast<ALint>(albuf->bytesFromFmt() * 8);
1311         break;
1312
1313     case AL_CHANNELS:
1314         *value = static_cast<ALint>(albuf->channelsFromFmt());
1315         break;
1316
1317     case AL_SIZE:
1318         *value = albuf->mCallback ? 0 : static_cast<ALint>(albuf->mData.size());
1319         break;
1320
1321     case AL_BYTE_LENGTH_SOFT:
1322         *value = static_cast<ALint>(albuf->mSampleLen / albuf->mBlockAlign
1323             * albuf->blockSizeFromFmt());
1324         break;
1325
1326     case AL_SAMPLE_LENGTH_SOFT:
1327         *value = static_cast<ALint>(albuf->mSampleLen);
1328         break;
1329
1330     case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
1331         *value = static_cast<ALint>(albuf->UnpackAlign);
1332         break;
1333
1334     case AL_PACK_BLOCK_ALIGNMENT_SOFT:
1335         *value = static_cast<ALint>(albuf->PackAlign);
1336         break;
1337
1338     case AL_AMBISONIC_LAYOUT_SOFT:
1339         *value = EnumFromAmbiLayout(albuf->mAmbiLayout);
1340         break;
1341
1342     case AL_AMBISONIC_SCALING_SOFT:
1343         *value = EnumFromAmbiScaling(albuf->mAmbiScaling);
1344         break;
1345
1346     case AL_UNPACK_AMBISONIC_ORDER_SOFT:
1347         *value = static_cast<int>(albuf->UnpackAmbiOrder);
1348         break;
1349
1350     default:
1351         context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param);
1352     }
1353 }
1354 END_API_FUNC
1355
1356 AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3)
1357 START_API_FUNC
1358 {
1359     ContextRef context{GetContextRef()};
1360     if(!context) UNLIKELY return;
1361
1362     ALCdevice *device{context->mALDevice.get()};
1363     std::lock_guard<std::mutex> _{device->BufferLock};
1364     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1365         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1366     else if(!value1 || !value2 || !value3) UNLIKELY
1367         context->setError(AL_INVALID_VALUE, "NULL pointer");
1368     else switch(param)
1369     {
1370     default:
1371         context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param);
1372     }
1373 }
1374 END_API_FUNC
1375
1376 AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values)
1377 START_API_FUNC
1378 {
1379     switch(param)
1380     {
1381     case AL_FREQUENCY:
1382     case AL_BITS:
1383     case AL_CHANNELS:
1384     case AL_SIZE:
1385     case AL_INTERNAL_FORMAT_SOFT:
1386     case AL_BYTE_LENGTH_SOFT:
1387     case AL_SAMPLE_LENGTH_SOFT:
1388     case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
1389     case AL_PACK_BLOCK_ALIGNMENT_SOFT:
1390     case AL_AMBISONIC_LAYOUT_SOFT:
1391     case AL_AMBISONIC_SCALING_SOFT:
1392     case AL_UNPACK_AMBISONIC_ORDER_SOFT:
1393         alGetBufferi(buffer, param, values);
1394         return;
1395     }
1396
1397     ContextRef context{GetContextRef()};
1398     if(!context) UNLIKELY return;
1399
1400     ALCdevice *device{context->mALDevice.get()};
1401     std::lock_guard<std::mutex> _{device->BufferLock};
1402     ALbuffer *albuf = LookupBuffer(device, buffer);
1403     if(!albuf) UNLIKELY
1404         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1405     else if(!values) UNLIKELY
1406         context->setError(AL_INVALID_VALUE, "NULL pointer");
1407     else switch(param)
1408     {
1409     case AL_LOOP_POINTS_SOFT:
1410         values[0] = static_cast<ALint>(albuf->mLoopStart);
1411         values[1] = static_cast<ALint>(albuf->mLoopEnd);
1412         break;
1413
1414     default:
1415         context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param);
1416     }
1417 }
1418 END_API_FUNC
1419
1420
1421 AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq,
1422     ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr)
1423 START_API_FUNC
1424 {
1425     ContextRef context{GetContextRef()};
1426     if(!context) UNLIKELY return;
1427
1428     ALCdevice *device{context->mALDevice.get()};
1429     std::lock_guard<std::mutex> _{device->BufferLock};
1430
1431     ALbuffer *albuf = LookupBuffer(device, buffer);
1432     if(!albuf) UNLIKELY
1433         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1434     else if(freq < 1) UNLIKELY
1435         context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq);
1436     else if(callback == nullptr) UNLIKELY
1437         context->setError(AL_INVALID_VALUE, "NULL callback");
1438     else
1439     {
1440         auto usrfmt = DecomposeUserFormat(format);
1441         if(!usrfmt) UNLIKELY
1442             context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
1443         else
1444             PrepareCallback(context.get(), albuf, freq, usrfmt->channels, usrfmt->type, callback,
1445                 userptr);
1446     }
1447 }
1448 END_API_FUNC
1449
1450 AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **value)
1451 START_API_FUNC
1452 {
1453     ContextRef context{GetContextRef()};
1454     if(!context) UNLIKELY return;
1455
1456     ALCdevice *device{context->mALDevice.get()};
1457     std::lock_guard<std::mutex> _{device->BufferLock};
1458     ALbuffer *albuf = LookupBuffer(device, buffer);
1459     if(!albuf) UNLIKELY
1460         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1461     else if(!value) UNLIKELY
1462         context->setError(AL_INVALID_VALUE, "NULL pointer");
1463     else switch(param)
1464     {
1465     case AL_BUFFER_CALLBACK_FUNCTION_SOFT:
1466         *value = reinterpret_cast<void*>(albuf->mCallback);
1467         break;
1468     case AL_BUFFER_CALLBACK_USER_PARAM_SOFT:
1469         *value = albuf->mUserData;
1470         break;
1471
1472     default:
1473         context->setError(AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param);
1474     }
1475 }
1476 END_API_FUNC
1477
1478 AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3)
1479 START_API_FUNC
1480 {
1481     ContextRef context{GetContextRef()};
1482     if(!context) UNLIKELY return;
1483
1484     ALCdevice *device{context->mALDevice.get()};
1485     std::lock_guard<std::mutex> _{device->BufferLock};
1486     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1487         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1488     else if(!value1 || !value2 || !value3) UNLIKELY
1489         context->setError(AL_INVALID_VALUE, "NULL pointer");
1490     else switch(param)
1491     {
1492     default:
1493         context->setError(AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param);
1494     }
1495 }
1496 END_API_FUNC
1497
1498 AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **values)
1499 START_API_FUNC
1500 {
1501     switch(param)
1502     {
1503     case AL_BUFFER_CALLBACK_FUNCTION_SOFT:
1504     case AL_BUFFER_CALLBACK_USER_PARAM_SOFT:
1505         alGetBufferPtrSOFT(buffer, param, values);
1506         return;
1507     }
1508
1509     ContextRef context{GetContextRef()};
1510     if(!context) UNLIKELY return;
1511
1512     ALCdevice *device{context->mALDevice.get()};
1513     std::lock_guard<std::mutex> _{device->BufferLock};
1514     if(LookupBuffer(device, buffer) == nullptr) UNLIKELY
1515         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
1516     else if(!values) UNLIKELY
1517         context->setError(AL_INVALID_VALUE, "NULL pointer");
1518     else switch(param)
1519     {
1520     default:
1521         context->setError(AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", param);
1522     }
1523 }
1524 END_API_FUNC
1525
1526
1527 BufferSubList::~BufferSubList()
1528 {
1529     uint64_t usemask{~FreeMask};
1530     while(usemask)
1531     {
1532         const int idx{al::countr_zero(usemask)};
1533         al::destroy_at(Buffers+idx);
1534         usemask &= ~(1_u64 << idx);
1535     }
1536     FreeMask = ~usemask;
1537     al_free(Buffers);
1538     Buffers = nullptr;
1539 }
1540
1541
1542 #ifdef ALSOFT_EAX
1543 FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint* buffers, ALint value)
1544 START_API_FUNC
1545 {
1546 #define EAX_PREFIX "[EAXSetBufferMode] "
1547
1548     const auto context = ContextRef{GetContextRef()};
1549     if(!context)
1550     {
1551         ERR(EAX_PREFIX "%s\n", "No current context.");
1552         return ALC_FALSE;
1553     }
1554
1555     if(!eax_g_is_enabled)
1556     {
1557         context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled.");
1558         return ALC_FALSE;
1559     }
1560
1561     const auto storage = EaxStorageFromEnum(value);
1562     if(!storage)
1563     {
1564         context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value);
1565         return ALC_FALSE;
1566     }
1567
1568     if(n == 0)
1569         return ALC_TRUE;
1570
1571     if(n < 0)
1572     {
1573         context->setError(AL_INVALID_VALUE, EAX_PREFIX "Buffer count %d out of range", n);
1574         return ALC_FALSE;
1575     }
1576
1577     if(!buffers)
1578     {
1579         context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Null AL buffers");
1580         return ALC_FALSE;
1581     }
1582
1583     auto device = context->mALDevice.get();
1584     std::lock_guard<std::mutex> device_lock{device->BufferLock};
1585     size_t total_needed{0};
1586
1587     // Validate the buffers.
1588     //
1589     for(auto i = 0;i < n;++i)
1590     {
1591         const auto bufid = buffers[i];
1592         if(bufid == AL_NONE)
1593             continue;
1594
1595         const auto buffer = LookupBuffer(device, bufid);
1596         if(!buffer) UNLIKELY
1597         {
1598             ERR(EAX_PREFIX "Invalid buffer ID %u.\n", bufid);
1599             return ALC_FALSE;
1600         }
1601
1602         /* TODO: Is the store location allowed to change for in-use buffers, or
1603          * only when not set/queued on a source?
1604          */
1605
1606         if(*storage == EaxStorage::Hardware && !buffer->eax_x_ram_is_hardware)
1607         {
1608             /* FIXME: This doesn't account for duplicate buffers. When the same
1609              * buffer ID is specified multiple times in the provided list, it
1610              * counts each instance as more memory that needs to fit in X-RAM.
1611              */
1612             if(std::numeric_limits<size_t>::max()-buffer->OriginalSize < total_needed) UNLIKELY
1613             {
1614                 context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Size overflow (%u + %zu)\n",
1615                     buffer->OriginalSize, total_needed);
1616                 return ALC_FALSE;
1617             }
1618             total_needed += buffer->OriginalSize;
1619         }
1620     }
1621     if(total_needed > device->eax_x_ram_free_size)
1622     {
1623         context->setError(AL_OUT_OF_MEMORY,EAX_PREFIX "Out of X-RAM memory (need: %zu, avail: %u)",
1624             total_needed, device->eax_x_ram_free_size);
1625         return ALC_FALSE;
1626     }
1627
1628     // Update the mode.
1629     //
1630     for(auto i = 0;i < n;++i)
1631     {
1632         const auto bufid = buffers[i];
1633         if(bufid == AL_NONE)
1634             continue;
1635
1636         const auto buffer = LookupBuffer(device, bufid);
1637         assert(buffer);
1638
1639         if(*storage == EaxStorage::Hardware)
1640             eax_x_ram_apply(*device, *buffer);
1641         else
1642             eax_x_ram_clear(*device, *buffer);
1643         buffer->eax_x_ram_mode = *storage;
1644     }
1645
1646     return AL_TRUE;
1647
1648 #undef EAX_PREFIX
1649 }
1650 END_API_FUNC
1651
1652 FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint* pReserved)
1653 START_API_FUNC
1654 {
1655 #define EAX_PREFIX "[EAXGetBufferMode] "
1656
1657     const auto context = ContextRef{GetContextRef()};
1658     if(!context)
1659     {
1660         ERR(EAX_PREFIX "%s\n", "No current context.");
1661         return AL_NONE;
1662     }
1663
1664     if(!eax_g_is_enabled)
1665     {
1666         context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled.");
1667         return AL_NONE;
1668     }
1669
1670     if(pReserved)
1671     {
1672         context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Non-null reserved parameter");
1673         return AL_NONE;
1674     }
1675
1676     auto device = context->mALDevice.get();
1677     std::lock_guard<std::mutex> device_lock{device->BufferLock};
1678
1679     const auto al_buffer = LookupBuffer(device, buffer);
1680     if(!al_buffer)
1681     {
1682         context->setError(AL_INVALID_NAME, EAX_PREFIX "Invalid buffer ID %u", buffer);
1683         return AL_NONE;
1684     }
1685
1686     return EnumFromEaxStorage(al_buffer->eax_x_ram_mode);
1687
1688 #undef EAX_PREFIX
1689 }
1690 END_API_FUNC
1691
1692 #endif // ALSOFT_EAX