* BugFix: Correct convexDecomp compilation by setting the LINUX flag when necessary.

* BugFix: Update OpenAL to correct a compilation error on Linux.
This commit is contained in:
Robert MacGregor 2022-05-30 16:32:45 -04:00
parent e071f1d901
commit 7380161054
234 changed files with 30864 additions and 7523 deletions

View file

@ -1,8 +1,5 @@
# Ask CMake to perform builds in a temporary directory for all of these. # Ask CMake to perform builds in a temporary directory for all of these.
# We also use EXCLUDE_FROM_ALL to ensure we only build and install what we want # We also use EXCLUDE_FROM_ALL to ensure we only build and install what we want
set(BUILD_SHARED_LIBS off CACHE STRING "")
add_subdirectory(assimp ${CMAKE_BINARY_DIR}/temp/assimp EXCLUDE_FROM_ALL)
set(SDL_SHARED on CACHE BOOL "" FORCE) set(SDL_SHARED on CACHE BOOL "" FORCE)
add_subdirectory(sdl ${CMAKE_BINARY_DIR}/temp/sdl2 EXCLUDE_FROM_ALL) add_subdirectory(sdl ${CMAKE_BINARY_DIR}/temp/sdl2 EXCLUDE_FROM_ALL)
@ -16,6 +13,14 @@ set(PNG_BUILD_ZLIB on CACHE BOOL "" FORCE)
get_filename_component(ZLIB_ROOT "zlib" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") get_filename_component(ZLIB_ROOT "zlib" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(ZLIB_ROOT "${ZLIB_ROOT}" CACHE STRING "ZLib root location" FORCE) set(ZLIB_ROOT "${ZLIB_ROOT}" CACHE STRING "ZLib root location" FORCE)
# Assimp depends on zlib
set(BUILD_SHARED_LIBS off CACHE STRING "")
set(ASSIMP_BUILD_ZLIB on CACHE STRING "")
set(ASSIMP_HUNTER_ENABLED off CACHE STRING "")
add_subdirectory(assimp ${CMAKE_BINARY_DIR}/temp/assimp EXCLUDE_FROM_ALL)
add_subdirectory(lpng ${CMAKE_BINARY_DIR}/temp/lpng EXCLUDE_FROM_ALL) add_subdirectory(lpng ${CMAKE_BINARY_DIR}/temp/lpng EXCLUDE_FROM_ALL)
add_subdirectory(ljpeg ${CMAKE_BINARY_DIR}/temp/ljpeg EXCLUDE_FROM_ALL) add_subdirectory(ljpeg ${CMAKE_BINARY_DIR}/temp/ljpeg EXCLUDE_FROM_ALL)

View file

@ -1,3 +1,7 @@
file(GLOB CONVEX_DECOMP_SOURCES "*.cpp") file(GLOB CONVEX_DECOMP_SOURCES "*.cpp")
add_library(convexDecomp STATIC ${CONVEX_DECOMP_SOURCES}) add_library(convexDecomp STATIC ${CONVEX_DECOMP_SOURCES})
target_include_directories(convexDecomp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(convexDecomp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
if (UNIX AND NOT APPLE)
target_compile_definitions(convexDecomp PUBLIC LINUX)
endif (UNIX AND NOT APPLE)

View file

@ -0,0 +1,66 @@
name: CI
on: [push]
jobs:
build:
name: ${{matrix.config.name}}
runs-on: ${{matrix.config.os}}
strategy:
fail-fast: false
matrix:
config:
- {
name: "Visual Studio 64-bit",
os: windows-latest,
cmake_opts: "-A x64 \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Release"
}
- {
name: "macOS",
os: macos-latest,
cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON",
build_type: "Release"
}
- {
name: "Linux",
os: ubuntu-latest,
cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \
-DALSOFT_REQUIRE_ALSA=ON \
-DALSOFT_REQUIRE_OSS=ON \
-DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_JACK=ON",
deps_cmdline: "sudo apt update && sudo apt-get install -qq \
libpulse-dev \
portaudio19-dev \
libasound2-dev \
libjack-dev \
qtbase5-dev \
libdbus-1-dev",
build_type: "Release"
}
steps:
- uses: actions/checkout@v1
- name: Install Dependencies
shell: bash
run: |
if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then
eval ${{matrix.config.deps_cmdline}}
fi
- name: Configure
shell: bash
run: |
cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} .
- name: Build
shell: bash
run: |
cmake --build build --config ${{matrix.config.build_type}}

View file

@ -26,7 +26,8 @@ install:
portaudio19-dev \ portaudio19-dev \
libasound2-dev \ libasound2-dev \
libjack-dev \ libjack-dev \
qtbase5-dev qtbase5-dev \
libdbus-1-dev
fi fi
- > - >
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then

View file

@ -1,282 +0,0 @@
#ifndef ALCONTEXT_H
#define ALCONTEXT_H
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <thread>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "al/listener.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alu.h"
#include "atomic.h"
#include "inprogext.h"
#include "intrusive_ptr.h"
#include "threads.h"
#include "vecmat.h"
#include "vector.h"
struct ALeffectslot;
struct ALsource;
struct EffectSlot;
struct EffectSlotProps;
struct RingBuffer;
struct Voice;
struct VoiceChange;
struct VoicePropsItem;
enum class DistanceModel : unsigned char {
Disable,
Inverse, InverseClamped,
Linear, LinearClamped,
Exponent, ExponentClamped,
Default = InverseClamped
};
struct WetBuffer {
bool mInUse;
al::FlexArray<FloatBufferLine, 16> mBuffer;
WetBuffer(size_t count) : mBuffer{count} { }
DEF_FAM_NEWDEL(WetBuffer, mBuffer)
};
using WetBufferPtr = std::unique_ptr<WetBuffer>;
struct ContextProps {
float DopplerFactor;
float DopplerVelocity;
float SpeedOfSound;
bool SourceDistanceModel;
DistanceModel mDistanceModel;
std::atomic<ContextProps*> next;
DEF_NEWDEL(ContextProps)
};
struct ListenerProps {
std::array<float,3> Position;
std::array<float,3> Velocity;
std::array<float,3> OrientAt;
std::array<float,3> OrientUp;
float Gain;
float MetersPerUnit;
std::atomic<ListenerProps*> next;
DEF_NEWDEL(ListenerProps)
};
struct ContextParams {
/* Pointer to the most recent property values that are awaiting an update. */
std::atomic<ContextProps*> ContextUpdate{nullptr};
std::atomic<ListenerProps*> ListenerUpdate{nullptr};
alu::Matrix Matrix{alu::Matrix::Identity()};
alu::Vector Velocity{};
float Gain{1.0f};
float MetersPerUnit{1.0f};
float DopplerFactor{1.0f};
float SpeedOfSound{343.3f}; /* in units per sec! */
bool SourceDistanceModel{false};
DistanceModel mDistanceModel{};
};
struct SourceSubList {
uint64_t FreeMask{~0_u64};
ALsource *Sources{nullptr}; /* 64 */
SourceSubList() noexcept = default;
SourceSubList(const SourceSubList&) = delete;
SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
{ rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
~SourceSubList();
SourceSubList& operator=(const SourceSubList&) = delete;
SourceSubList& operator=(SourceSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
};
struct EffectSlotSubList {
uint64_t FreeMask{~0_u64};
ALeffectslot *EffectSlots{nullptr}; /* 64 */
EffectSlotSubList() noexcept = default;
EffectSlotSubList(const EffectSlotSubList&) = delete;
EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
: FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
{ rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
~EffectSlotSubList();
EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
};
struct ALCcontext : public al::intrusive_ref<ALCcontext> {
const al::intrusive_ptr<ALCdevice> mDevice;
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
* indicates if updates are currently happening).
*/
RefCount mUpdateCount{0u};
std::atomic<bool> mHoldUpdates{false};
float mGainBoost{1.0f};
/* Linked lists of unused property containers, free to use for future
* updates.
*/
std::atomic<ContextProps*> mFreeContextProps{nullptr};
std::atomic<ListenerProps*> mFreeListenerProps{nullptr};
std::atomic<VoicePropsItem*> mFreeVoiceProps{nullptr};
std::atomic<EffectSlotProps*> mFreeEffectslotProps{nullptr};
/* The voice change tail is the beginning of the "free" elements, up to and
* *excluding* the current. If tail==current, there's no free elements and
* new ones need to be allocated. The current voice change is the element
* last processed, and any after are pending.
*/
VoiceChange *mVoiceChangeTail{};
std::atomic<VoiceChange*> mCurrentVoiceChange{};
void allocVoiceChanges(size_t addcount);
ContextParams mParams;
using VoiceArray = al::FlexArray<Voice*>;
std::atomic<VoiceArray*> mVoices{};
std::atomic<size_t> mActiveVoiceCount{};
void allocVoices(size_t addcount);
al::span<Voice*> getVoicesSpan() const noexcept
{
return {mVoices.load(std::memory_order_relaxed)->data(),
mActiveVoiceCount.load(std::memory_order_relaxed)};
}
al::span<Voice*> getVoicesSpanAcquired() const noexcept
{
return {mVoices.load(std::memory_order_acquire)->data(),
mActiveVoiceCount.load(std::memory_order_acquire)};
}
using EffectSlotArray = al::FlexArray<EffectSlot*>;
std::atomic<EffectSlotArray*> mActiveAuxSlots{nullptr};
std::thread mEventThread;
al::semaphore mEventSem;
std::unique_ptr<RingBuffer> mAsyncEvents;
std::atomic<uint> mEnabledEvts{0u};
/* Asynchronous voice change actions are processed as a linked list of
* VoiceChange objects by the mixer, which is atomically appended to.
* However, to avoid allocating each object individually, they're allocated
* in clusters that are stored in a vector for easy automatic cleanup.
*/
using VoiceChangeCluster = std::unique_ptr<VoiceChange[]>;
al::vector<VoiceChangeCluster> mVoiceChangeClusters;
using VoiceCluster = std::unique_ptr<Voice[]>;
al::vector<VoiceCluster> mVoiceClusters;
/* Wet buffers used by effect slots. */
al::vector<WetBufferPtr> mWetBuffers;
std::atomic_flag mPropsClean;
std::atomic<bool> mDeferUpdates{false};
std::mutex mPropLock;
std::atomic<ALenum> mLastError{AL_NO_ERROR};
DistanceModel mDistanceModel{DistanceModel::Default};
bool mSourceDistanceModel{false};
float mDopplerFactor{1.0f};
float mDopplerVelocity{1.0f};
float mSpeedOfSound{SpeedOfSoundMetersPerSec};
std::mutex mEventCbLock;
ALEVENTPROCSOFT mEventCb{};
void *mEventParam{nullptr};
ALlistener mListener{};
al::vector<SourceSubList> mSourceList;
ALuint mNumSources{0};
std::mutex mSourceLock;
al::vector<EffectSlotSubList> mEffectSlotList;
ALuint mNumEffectSlots{0u};
std::mutex mEffectSlotLock;
/* Default effect slot */
std::unique_ptr<ALeffectslot> mDefaultSlot;
const char *mExtensionList{nullptr};
ALCcontext(al::intrusive_ptr<ALCdevice> device);
ALCcontext(const ALCcontext&) = delete;
ALCcontext& operator=(const ALCcontext&) = delete;
~ALCcontext();
void init();
/**
* Removes the context from its device and removes it from being current on
* the running thread or globally. Returns true if other contexts still
* exist on the device.
*/
bool deinit();
/**
* Defers/suspends updates for the given context's listener and sources.
* This does *NOT* stop mixing, but rather prevents certain property
* changes from taking effect.
*/
void deferUpdates() noexcept { mDeferUpdates.exchange(true, std::memory_order_acq_rel); }
/** Resumes update processing after being deferred. */
void processUpdates();
[[gnu::format(printf,3,4)]] void setError(ALenum errorCode, const char *msg, ...);
DEF_NEWDEL(ALCcontext)
};
#define SETERR_RETURN(ctx, err, retval, ...) do { \
(ctx)->setError((err), __VA_ARGS__); \
return retval; \
} while(0)
using ContextRef = al::intrusive_ptr<ALCcontext>;
ContextRef GetContextRef(void);
void UpdateContextProps(ALCcontext *context);
extern bool TrapALError;
#endif /* ALCONTEXT_H */

View file

@ -1,250 +0,0 @@
#include "config.h"
#include "oboe.h"
#include <cassert>
#include <cstring>
#include "alu.h"
#include "core/logging.h"
#include "oboe/Oboe.h"
namespace {
constexpr char device_name[] = "Oboe Default";
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
OboePlayback(ALCdevice *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
};
oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames)
{
assert(numFrames > 0);
const int32_t numChannels{oboeStream->getChannelCount()};
if UNLIKELY(numChannels > 2 && mDevice->FmtChans == DevFmtStereo)
{
/* If the device is only mixing stereo but there's more than two
* output channels, there are unused channels that need to be silenced.
*/
if(mStream->getFormat() == oboe::AudioFormat::Float)
memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(float));
else
memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(int16_t));
}
mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
static_cast<uint32_t>(numChannels));
return oboe::DataCallbackResult::Continue;
}
void OboePlayback::open(const char *name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
/* Open a basic output stream, just to ensure it can work. */
oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->openManagedStream(mStream)};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mDevice->DeviceName = name;
}
bool OboePlayback::reset()
{
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
/* Don't let Oboe convert. We should be able to handle anything it gives
* back.
*/
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
builder.setChannelConversionAllowed(false);
builder.setFormatConversionAllowed(false);
builder.setCallback(this);
if(mDevice->Flags.test(FrequencyRequest))
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
if(mDevice->Flags.test(ChannelsRequest))
{
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
: (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
: oboe::ChannelCount::Unspecified);
}
if(mDevice->Flags.test(SampleTypeRequest))
{
oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
switch(mDevice->FmtType)
{
case DevFmtByte:
case DevFmtUByte:
case DevFmtShort:
case DevFmtUShort:
format = oboe::AudioFormat::I16;
break;
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
format = oboe::AudioFormat::Float;
break;
}
builder.setFormat(format);
}
oboe::Result result{builder.openManagedStream(mStream)};
/* If the format failed, try asking for the defaults. */
while(result == oboe::Result::ErrorInvalidFormat)
{
if(builder.getFormat() != oboe::AudioFormat::Unspecified)
builder.setFormat(oboe::AudioFormat::Unspecified);
else if(builder.getSampleRate() != oboe::kUnspecified)
builder.setSampleRate(oboe::kUnspecified);
else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
builder.setChannelCount(oboe::ChannelCount::Unspecified);
else
break;
result = builder.openManagedStream(mStream);
}
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
switch(mStream->getChannelCount())
{
case oboe::ChannelCount::Mono:
mDevice->FmtChans = DevFmtMono;
break;
case oboe::ChannelCount::Stereo:
mDevice->FmtChans = DevFmtStereo;
break;
/* Other potential configurations. Could be wrong, but better than failing.
* Assume WFX channel order.
*/
case 4:
mDevice->FmtChans = DevFmtQuad;
break;
case 6:
mDevice->FmtChans = DevFmtX51Rear;
break;
case 7:
mDevice->FmtChans = DevFmtX61;
break;
case 8:
mDevice->FmtChans = DevFmtX71;
break;
default:
if(mStream->getChannelCount() < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled channel count: %d", mStream->getChannelCount()};
/* Assume first two channels are front left/right. We can do a stereo
* mix and keep the other channels silent.
*/
mDevice->FmtChans = DevFmtStereo;
break;
}
setDefaultWFXChannelOrder();
switch(mStream->getFormat())
{
case oboe::AudioFormat::I16:
mDevice->FmtType = DevFmtShort;
break;
case oboe::AudioFormat::Float:
mDevice->FmtType = DevFmtFloat;
break;
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
}
mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
/* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
* indicating variable updates, but OpenAL should have a reasonable minimum update size set.
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size.
*/
mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true;
}
void OboePlayback::start()
{
const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
oboe::convertToText(result)};
}
void OboePlayback::stop()
{
oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
}
} // namespace
bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string OboeBackendFactory::probe(BackendType type)
{
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
return std::string{device_name, sizeof(device_name)};
case BackendType::Capture:
break;
}
return std::string{};
}
BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OboePlayback{device}};
return nullptr;
}
BackendFactory &OboeBackendFactory::getFactory()
{
static OboeBackendFactory factory{};
return factory;
}

View file

@ -1,9 +0,0 @@
#ifndef AL_COMPAT_H
#define AL_COMPAT_H
#include <string>
struct PathNamePair { std::string path, fname; };
const PathNamePair &GetProcBinary(void);
#endif /* AL_COMPAT_H */

View file

@ -1,793 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "voice.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cassert>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include <new>
#include <utility>
#include "alcmain.h"
#include "albyte.h"
#include "alconfig.h"
#include "alcontext.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alspan.h"
#include "alstring.h"
#include "alu.h"
#include "async_event.h"
#include "buffer_storage.h"
#include "core/cpu_caps.h"
#include "core/devformat.h"
#include "core/filters/biquad.h"
#include "core/filters/nfc.h"
#include "core/filters/splitter.h"
#include "core/fmt_traits.h"
#include "core/logging.h"
#include "core/mixer/defs.h"
#include "core/mixer/hrtfdefs.h"
#include "hrtf.h"
#include "inprogext.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include "voice_change.h"
struct CTag;
#ifdef HAVE_SSE
struct SSETag;
#endif
#ifdef HAVE_NEON
struct NEONTag;
#endif
struct CopyTag;
Resampler ResamplerDefault{Resampler::Linear};
MixerFunc MixSamples{Mix_<CTag>};
namespace {
using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize,
const MixHrtfFilter *hrtfparams, const size_t BufferSize);
using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples,
const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams,
const size_t BufferSize);
HrtfMixerFunc MixHrtfSamples{MixHrtf_<CTag>};
HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_<CTag>};
inline MixerFunc SelectMixer()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Mix_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Mix_<SSETag>;
#endif
return Mix_<CTag>;
}
inline HrtfMixerFunc SelectHrtfMixer()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixHrtf_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixHrtf_<SSETag>;
#endif
return MixHrtf_<CTag>;
}
inline HrtfMixerBlendFunc SelectHrtfBlendMixer()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixHrtfBlend_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixHrtfBlend_<SSETag>;
#endif
return MixHrtfBlend_<CTag>;
}
} // namespace
void aluInitMixer()
{
if(auto resopt = ConfigValueStr(nullptr, nullptr, "resampler"))
{
struct ResamplerEntry {
const char name[16];
const Resampler resampler;
};
constexpr ResamplerEntry ResamplerList[]{
{ "none", Resampler::Point },
{ "point", Resampler::Point },
{ "linear", Resampler::Linear },
{ "cubic", Resampler::Cubic },
{ "bsinc12", Resampler::BSinc12 },
{ "fast_bsinc12", Resampler::FastBSinc12 },
{ "bsinc24", Resampler::BSinc24 },
{ "fast_bsinc24", Resampler::FastBSinc24 },
};
const char *str{resopt->c_str()};
if(al::strcasecmp(str, "bsinc") == 0)
{
WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str);
str = "bsinc12";
}
else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0)
{
WARN("Resampler option \"%s\" is deprecated, using cubic\n", str);
str = "cubic";
}
auto iter = std::find_if(std::begin(ResamplerList), std::end(ResamplerList),
[str](const ResamplerEntry &entry) -> bool
{ return al::strcasecmp(str, entry.name) == 0; });
if(iter == std::end(ResamplerList))
ERR("Invalid resampler: %s\n", str);
else
ResamplerDefault = iter->resampler;
}
MixSamples = SelectMixer();
MixHrtfBlendSamples = SelectHrtfBlendMixer();
MixHrtfSamples = SelectHrtfMixer();
}
namespace {
void SendSourceStoppedEvent(ALCcontext *context, uint id)
{
RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len < 1) return;
AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
evt->u.srcstate.id = id;
evt->u.srcstate.state = VChangeState::Stop;
ring->writeAdvance(1);
}
const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst,
const al::span<const float> src, int type)
{
switch(type)
{
case AF_None:
lpfilter.clear();
hpfilter.clear();
break;
case AF_LowPass:
lpfilter.process(src, dst);
hpfilter.clear();
return dst;
case AF_HighPass:
lpfilter.clear();
hpfilter.process(src, dst);
return dst;
case AF_BandPass:
DualBiquad{lpfilter, hpfilter}.process(src, dst);
return dst;
}
return src.data();
}
void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
const size_t samples) noexcept
{
#define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
switch(srctype)
{
HANDLE_FMT(FmtUByte);
HANDLE_FMT(FmtShort);
HANDLE_FMT(FmtFloat);
HANDLE_FMT(FmtDouble);
HANDLE_FMT(FmtMulaw);
HANDLE_FMT(FmtAlaw);
}
#undef HANDLE_FMT
}
float *LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *&bufferLoopItem,
const size_t numChannels, const FmtType sampleType, const size_t sampleSize, const size_t chan,
size_t dataPosInt, al::span<float> srcBuffer)
{
const uint LoopStart{buffer->mLoopStart};
const uint LoopEnd{buffer->mLoopEnd};
ASSUME(LoopEnd > LoopStart);
/* If current pos is beyond the loop range, do not loop */
if(!bufferLoopItem || dataPosInt >= LoopEnd)
{
bufferLoopItem = nullptr;
/* Load what's left to play from the buffer */
const size_t DataRem{minz(srcBuffer.size(), buffer->mSampleLen-dataPosInt)};
const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize};
LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem);
srcBuffer = srcBuffer.subspan(DataRem);
}
else
{
/* Load what's left of this loop iteration */
const size_t DataRem{minz(srcBuffer.size(), LoopEnd-dataPosInt)};
const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize};
LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem);
srcBuffer = srcBuffer.subspan(DataRem);
/* Load any repeats of the loop we can to fill the buffer. */
const auto LoopSize = static_cast<size_t>(LoopEnd - LoopStart);
while(!srcBuffer.empty())
{
const size_t DataSize{minz(srcBuffer.size(), LoopSize)};
Data = buffer->mSamples + (LoopStart*numChannels + chan)*sampleSize;
LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataSize);
srcBuffer = srcBuffer.subspan(DataSize);
}
}
return srcBuffer.begin();
}
float *LoadBufferCallback(VoiceBufferItem *buffer, const size_t numChannels,
const FmtType sampleType, const size_t sampleSize, const size_t chan,
size_t numCallbackSamples, al::span<float> srcBuffer)
{
/* Load what's left to play from the buffer */
const size_t DataRem{minz(srcBuffer.size(), numCallbackSamples)};
const al::byte *Data{buffer->mSamples + chan*sampleSize};
LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem);
srcBuffer = srcBuffer.subspan(DataRem);
return srcBuffer.begin();
}
float *LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
const size_t numChannels, const FmtType sampleType, const size_t sampleSize, const size_t chan,
size_t dataPosInt, al::span<float> srcBuffer)
{
/* Crawl the buffer queue to fill in the temp buffer */
while(buffer && !srcBuffer.empty())
{
if(dataPosInt >= buffer->mSampleLen)
{
dataPosInt -= buffer->mSampleLen;
buffer = buffer->mNext.load(std::memory_order_acquire);
if(!buffer) buffer = bufferLoopItem;
continue;
}
const size_t DataSize{minz(srcBuffer.size(), buffer->mSampleLen-dataPosInt)};
const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize};
LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataSize);
srcBuffer = srcBuffer.subspan(DataSize);
if(srcBuffer.empty()) break;
dataPosInt = 0;
buffer = buffer->mNext.load(std::memory_order_acquire);
if(!buffer) buffer = bufferLoopItem;
}
return srcBuffer.begin();
}
void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms,
const float TargetGain, const uint Counter, uint OutPos, const uint IrSize,
ALCdevice *Device)
{
auto &HrtfSamples = Device->HrtfSourceData;
/* Source HRTF mixing needs to include the direct delay so it remains
* aligned with the direct mix's HRTF filtering.
*/
float2 *AccumSamples{Device->HrtfAccumData + HrtfDirectDelay};
/* Copy the HRTF history and new input samples into a temp buffer. */
auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(),
std::begin(HrtfSamples));
std::copy_n(samples, DstBufferSize, src_iter);
/* Copy the last used samples back into the history buffer for later. */
std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(),
parms.Hrtf.History.begin());
/* If fading and this is the first mixing pass, fade between the IRs. */
uint fademix{0u};
if(Counter && OutPos == 0)
{
fademix = minu(DstBufferSize, Counter);
float gain{TargetGain};
/* The new coefficients need to fade in completely since they're
* replacing the old ones. To keep the gain fading consistent,
* interpolate between the old and new target gains given how much of
* the fade time this mix handles.
*/
if(Counter > fademix)
{
const float a{static_cast<float>(fademix) / static_cast<float>(Counter)};
gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
}
MixHrtfFilter hrtfparams;
hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
hrtfparams.Delay = parms.Hrtf.Target.Delay;
hrtfparams.Gain = 0.0f;
hrtfparams.GainStep = gain / static_cast<float>(fademix);
MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams,
fademix);
/* Update the old parameters with the result. */
parms.Hrtf.Old = parms.Hrtf.Target;
parms.Hrtf.Old.Gain = gain;
OutPos += fademix;
}
if(fademix < DstBufferSize)
{
const uint todo{DstBufferSize - fademix};
float gain{TargetGain};
/* Interpolate the target gain if the gain fading lasts longer than
* this mix.
*/
if(Counter > DstBufferSize)
{
const float a{static_cast<float>(todo) / static_cast<float>(Counter-fademix)};
gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
}
MixHrtfFilter hrtfparams;
hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
hrtfparams.Delay = parms.Hrtf.Target.Delay;
hrtfparams.Gain = parms.Hrtf.Old.Gain;
hrtfparams.GainStep = (gain - parms.Hrtf.Old.Gain) / static_cast<float>(todo);
MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo);
/* Store the now-current gain for next time. */
parms.Hrtf.Old.Gain = gain;
}
}
void DoNfcMix(const al::span<const float> samples, FloatBufferLine *OutBuffer, DirectParams &parms,
const float *TargetGains, const uint Counter, const uint OutPos, ALCdevice *Device)
{
using FilterProc = void (NfcFilter::*)(const al::span<const float>, float*);
static constexpr FilterProc NfcProcess[MaxAmbiOrder+1]{
nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3};
float *CurrentGains{parms.Gains.Current.data()};
MixSamples(samples, {OutBuffer, 1u}, CurrentGains, TargetGains, Counter, OutPos);
++OutBuffer;
++CurrentGains;
++TargetGains;
const al::span<float> nfcsamples{Device->NfcSampleData, samples.size()};
size_t order{1};
while(const size_t chancount{Device->NumChannelsPerOrder[order]})
{
(parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples.data());
MixSamples(nfcsamples, {OutBuffer, chancount}, CurrentGains, TargetGains, Counter, OutPos);
OutBuffer += chancount;
CurrentGains += chancount;
TargetGains += chancount;
if(++order == MaxAmbiOrder+1)
break;
}
}
} // namespace
void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
{
static constexpr std::array<float,MAX_OUTPUT_CHANNELS> SilentTarget{};
ASSUME(SamplesToDo > 0);
/* Get voice info */
uint DataPosInt{mPosition.load(std::memory_order_relaxed)};
uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)};
VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)};
VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)};
const FmtType SampleType{mFmtType};
const uint SampleSize{mSampleSize};
const uint increment{mStep};
if UNLIKELY(increment < 1)
{
/* If the voice is supposed to be stopping but can't be mixed, just
* stop it before bailing.
*/
if(vstate == Stopping)
mPlayState.store(Stopped, std::memory_order_release);
return;
}
ASSUME(SampleSize > 0);
const size_t FrameSize{mChans.size() * SampleSize};
ASSUME(FrameSize > 0);
ALCdevice *Device{Context->mDevice.get()};
const uint NumSends{Device->NumAuxSends};
const uint IrSize{Device->mIrSize};
ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ?
Resample_<CopyTag,CTag> : mResampler};
uint Counter{(mFlags&VoiceIsFading) ? SamplesToDo : 0};
if(!Counter)
{
/* No fading, just overwrite the old/current params. */
for(auto &chandata : mChans)
{
{
DirectParams &parms = chandata.mDryParams;
if(!(mFlags&VoiceHasHrtf))
parms.Gains.Current = parms.Gains.Target;
else
parms.Hrtf.Old = parms.Hrtf.Target;
}
for(uint send{0};send < NumSends;++send)
{
if(mSend[send].Buffer.empty())
continue;
SendParams &parms = chandata.mWetParams[send];
parms.Gains.Current = parms.Gains.Target;
}
}
}
float fadeCoeff{1.0f}, fadeGain{1.0f};
if UNLIKELY(vstate == Stopping)
{
/* Calculate the multiplier for fading the resampled signal by -60dB
* over 1ms.
*/
fadeCoeff = std::pow(0.001f, 1000.0f/static_cast<float>(Device->Frequency));
}
uint buffers_done{0u};
uint OutPos{0u};
do {
/* Figure out how many buffer samples will be needed */
uint DstBufferSize{SamplesToDo - OutPos};
uint SrcBufferSize;
if(increment <= MixerFracOne)
{
/* Calculate the last written dst sample pos. */
uint64_t DataSize64{DstBufferSize - 1};
/* Calculate the last read src sample pos. */
DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
/* +1 to get the src sample count, include padding. */
DataSize64 += 1 + MaxResamplerPadding;
/* Result is guaranteed to be <= BufferLineSize+MaxResamplerPadding
* since we won't use more src samples than dst samples+padding.
*/
SrcBufferSize = static_cast<uint>(DataSize64);
}
else
{
uint64_t DataSize64{DstBufferSize};
/* Calculate the end src sample pos, include padding. */
DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
DataSize64 += MaxResamplerPadding;
if(DataSize64 <= BufferLineSize + MaxResamplerPadding)
SrcBufferSize = static_cast<uint>(DataSize64);
else
{
/* If the source size got saturated, we can't fill the desired
* dst size. Figure out how many samples we can actually mix.
*/
SrcBufferSize = BufferLineSize + MaxResamplerPadding;
DataSize64 = SrcBufferSize - MaxResamplerPadding;
DataSize64 = ((DataSize64<<MixerFracBits) - DataPosFrac) / increment;
if(DataSize64 < DstBufferSize)
{
/* Some mixers require being 16-byte aligned, so also limit
* to a multiple of 4 samples to maintain alignment.
*/
DstBufferSize = static_cast<uint>(DataSize64) & ~3u;
}
}
}
if((mFlags&(VoiceIsCallback|VoiceCallbackStopped)) == VoiceIsCallback && BufferListItem)
{
/* Exclude resampler pre-padding from the needed size. */
const uint toLoad{SrcBufferSize - (MaxResamplerPadding>>1)};
if(toLoad > mNumCallbackSamples)
{
const size_t byteOffset{mNumCallbackSamples*FrameSize};
const size_t needBytes{toLoad*FrameSize - byteOffset};
const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData,
&BufferListItem->mSamples[byteOffset], static_cast<int>(needBytes))};
if(gotBytes < 1)
mFlags |= VoiceCallbackStopped;
else if(static_cast<uint>(gotBytes) < needBytes)
{
mFlags |= VoiceCallbackStopped;
mNumCallbackSamples += static_cast<uint>(static_cast<uint>(gotBytes) /
FrameSize);
}
else
mNumCallbackSamples = toLoad;
}
}
const float fadeVal{fadeGain};
const size_t num_chans{mChans.size()};
size_t chan_idx{0};
ASSUME(DstBufferSize > 0);
for(auto &chandata : mChans)
{
const al::span<float> SrcData{Device->SourceData, SrcBufferSize};
/* Load the previous samples into the source data first, then load
* what we can from the buffer queue.
*/
auto srciter = std::copy_n(chandata.mPrevSamples.begin(), MaxResamplerPadding>>1,
SrcData.begin());
if UNLIKELY(!BufferListItem)
srciter = std::copy(chandata.mPrevSamples.begin()+(MaxResamplerPadding>>1),
chandata.mPrevSamples.end(), srciter);
else if((mFlags&VoiceIsStatic))
srciter = LoadBufferStatic(BufferListItem, BufferLoopItem, num_chans, SampleType,
SampleSize, chan_idx, DataPosInt, {srciter, SrcData.end()});
else if((mFlags&VoiceIsCallback))
srciter = LoadBufferCallback(BufferListItem, num_chans, SampleType, SampleSize,
chan_idx, mNumCallbackSamples, {srciter, SrcData.end()});
else
srciter = LoadBufferQueue(BufferListItem, BufferLoopItem, num_chans, SampleType,
SampleSize, chan_idx, DataPosInt, {srciter, SrcData.end()});
if UNLIKELY(srciter != SrcData.end())
{
/* If the source buffer wasn't filled, copy the last sample for
* the remaining buffer. Ideally it should have ended with
* silence, but if not the gain fading should help avoid clicks
* from sudden amplitude changes.
*/
const float sample{*(srciter-1)};
std::fill(srciter, SrcData.end(), sample);
}
/* Store the last source samples used for next time. */
std::copy_n(&SrcData[(increment*DstBufferSize + DataPosFrac)>>MixerFracBits],
chandata.mPrevSamples.size(), chandata.mPrevSamples.begin());
/* Resample, then apply ambisonic upsampling as needed. */
float *ResampledData{Resample(&mResampleState, &SrcData[MaxResamplerPadding>>1],
DataPosFrac, increment, {Device->ResampledData, DstBufferSize})};
if((mFlags&VoiceIsAmbisonic))
chandata.mAmbiSplitter.processHfScale({ResampledData, DstBufferSize},
chandata.mAmbiScale);
if UNLIKELY(vstate == Stopping)
{
fadeGain = fadeVal;
for(float &sample : al::span<float>{ResampledData, DstBufferSize})
{
fadeGain *= fadeCoeff;
sample *= fadeGain;
}
}
/* Now filter and mix to the appropriate outputs. */
float (&FilterBuf)[BufferLineSize] = Device->FilteredData;
{
DirectParams &parms = chandata.mDryParams;
const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf,
{ResampledData, DstBufferSize}, mDirect.FilterType)};
if((mFlags&VoiceHasHrtf))
{
const float TargetGain{UNLIKELY(vstate == Stopping) ? 0.0f :
parms.Hrtf.Target.Gain};
DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos, IrSize,
Device);
}
else if((mFlags&VoiceHasNfc))
{
const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data()
: parms.Gains.Target.data()};
DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms, TargetGains,
Counter, OutPos, Device);
}
else
{
const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data()
: parms.Gains.Target.data()};
MixSamples({samples, DstBufferSize}, mDirect.Buffer,
parms.Gains.Current.data(), TargetGains, Counter, OutPos);
}
}
for(uint send{0};send < NumSends;++send)
{
if(mSend[send].Buffer.empty())
continue;
SendParams &parms = chandata.mWetParams[send];
const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf,
{ResampledData, DstBufferSize}, mSend[send].FilterType)};
const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data()
: parms.Gains.Target.data()};
MixSamples({samples, DstBufferSize}, mSend[send].Buffer,
parms.Gains.Current.data(), TargetGains, Counter, OutPos);
}
++chan_idx;
}
/* Update positions */
DataPosFrac += increment*DstBufferSize;
const uint SrcSamplesDone{DataPosFrac>>MixerFracBits};
DataPosInt += SrcSamplesDone;
DataPosFrac &= MixerFracMask;
OutPos += DstBufferSize;
Counter = maxu(DstBufferSize, Counter) - DstBufferSize;
if UNLIKELY(!BufferListItem)
{
/* Do nothing extra when there's no buffers. */
}
else if((mFlags&VoiceIsStatic))
{
if(BufferLoopItem)
{
/* Handle looping static source */
const uint LoopStart{BufferListItem->mLoopStart};
const uint LoopEnd{BufferListItem->mLoopEnd};
if(DataPosInt >= LoopEnd)
{
assert(LoopEnd > LoopStart);
DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart;
}
}
else
{
/* Handle non-looping static source */
if(DataPosInt >= BufferListItem->mSampleLen)
{
BufferListItem = nullptr;
break;
}
}
}
else if((mFlags&VoiceIsCallback))
{
if(SrcSamplesDone < mNumCallbackSamples)
{
const size_t byteOffset{SrcSamplesDone*FrameSize};
const size_t byteEnd{mNumCallbackSamples*FrameSize};
al::byte *data{BufferListItem->mSamples};
std::copy(data+byteOffset, data+byteEnd, data);
mNumCallbackSamples -= SrcSamplesDone;
}
else
{
BufferListItem = nullptr;
mNumCallbackSamples = 0;
}
}
else
{
/* Handle streaming source */
do {
if(BufferListItem->mSampleLen > DataPosInt)
break;
DataPosInt -= BufferListItem->mSampleLen;
++buffers_done;
BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed);
if(!BufferListItem) BufferListItem = BufferLoopItem;
} while(BufferListItem);
}
} while(OutPos < SamplesToDo);
mFlags |= VoiceIsFading;
/* Don't update positions and buffers if we were stopping. */
if UNLIKELY(vstate == Stopping)
{
mPlayState.store(Stopped, std::memory_order_release);
return;
}
/* Capture the source ID in case it's reset for stopping. */
const uint SourceID{mSourceID.load(std::memory_order_relaxed)};
/* Update voice info */
mPosition.store(DataPosInt, std::memory_order_relaxed);
mPositionFrac.store(DataPosFrac, std::memory_order_relaxed);
mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed);
if(!BufferListItem)
{
mLoopBuffer.store(nullptr, std::memory_order_relaxed);
mSourceID.store(0u, std::memory_order_relaxed);
}
std::atomic_thread_fence(std::memory_order_release);
/* Send any events now, after the position/buffer info was updated. */
const uint enabledevt{Context->mEnabledEvts.load(std::memory_order_acquire)};
if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted))
{
RingBuffer *ring{Context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len > 0)
{
AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_BufferCompleted}};
evt->u.bufcomp.id = SourceID;
evt->u.bufcomp.count = buffers_done;
ring->writeAdvance(1);
}
}
if(!BufferListItem)
{
/* If the voice just ended, set it to Stopping so the next render
* ensures any residual noise fades to 0 amplitude.
*/
mPlayState.store(Stopping, std::memory_order_release);
if((enabledevt&EventType_SourceStateChange))
SendSourceStoppedEvent(Context, SourceID);
}
}

View file

@ -1,6 +1,7 @@
Portions of this software are licensed under the BSD 3-Clause license. Portions of this software are licensed under the BSD 3-Clause license.
Copyright (c) 2015, Archontis Politis Copyright (c) 2015, Archontis Politis
Copyright (c) 2019, Anis A. Hireche
Copyright (c) 2019, Christopher Robinson Copyright (c) 2019, Christopher Robinson
All rights reserved. All rights reserved.

View file

@ -2,21 +2,24 @@
cmake_minimum_required(VERSION 3.0.2) cmake_minimum_required(VERSION 3.0.2)
# The workaround for try_compile failing with code signing if(APPLE)
# since cmake-3.18.2, not required # The workaround for try_compile failing with code signing
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES # since cmake-3.18.2, not required
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED" set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED") ${CMAKE_TRY_COMPILE_PLATFORM_VARIABLES}
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO) "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED"
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO) "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
endif()
# Fix compile failure with armv7 deployment target >= 11.0, xcode clang will
# report:
# error: invalid iOS deployment version '--target=armv7-apple-ios13.6',
# iOS 10 is the maximum deployment target for 32-bit targets
# If CMAKE_OSX_DEPLOYMENT_TARGET is not defined, cmake will choose latest
# deployment target
if(CMAKE_SYSTEM_NAME STREQUAL "iOS") if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
# Fix compile failure with armv7 deployment target >= 11.0, xcode clang
# will report:
# error: invalid iOS deployment version '--target=armv7-apple-ios13.6',
# iOS 10 is the maximum deployment target for 32-bit targets
# If CMAKE_OSX_DEPLOYMENT_TARGET is not defined, cmake will choose latest
# deployment target
if("${CMAKE_OSX_ARCHITECTURES}" MATCHES ".*armv7.*") if("${CMAKE_OSX_ARCHITECTURES}" MATCHES ".*armv7.*")
if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
OR NOT CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "11.0") OR NOT CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "11.0")
@ -69,8 +72,11 @@ include(CheckCXXCompilerFlag)
include(CheckCSourceCompiles) include(CheckCSourceCompiles)
include(CheckCXXSourceCompiles) include(CheckCXXSourceCompiles)
include(CheckStructHasMember) include(CheckStructHasMember)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs) include(GNUInstallDirs)
find_package(PkgConfig)
option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON)
@ -89,6 +95,8 @@ option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)
option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON) option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON)
option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON) option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON)
option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32})
if(DEFINED SHARE_INSTALL_DIR) if(DEFINED SHARE_INSTALL_DIR)
message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead") message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead")
set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}") set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}")
@ -115,7 +123,7 @@ set(LINKER_FLAGS )
set(EXTRA_LIBS ) set(EXTRA_LIBS )
if(WIN32) if(WIN32)
set(CPP_DEFS ${CPP_DEFS} _WIN32) set(CPP_DEFS ${CPP_DEFS} _WIN32 NOMINMAX)
if(MINGW) if(MINGW)
set(CPP_DEFS ${CPP_DEFS} __USE_MINGW_ANSI_STDIO) set(CPP_DEFS ${CPP_DEFS} __USE_MINGW_ANSI_STDIO)
endif() endif()
@ -142,7 +150,7 @@ if(NOT LIBTYPE)
endif() endif()
set(LIB_MAJOR_VERSION "1") set(LIB_MAJOR_VERSION "1")
set(LIB_MINOR_VERSION "21") set(LIB_MINOR_VERSION "22")
set(LIB_REVISION "0") set(LIB_REVISION "0")
set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}")
set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0)
@ -196,20 +204,22 @@ else()
endif() endif()
unset(OLD_REQUIRED_LIBRARIES) unset(OLD_REQUIRED_LIBRARIES)
# Include liblog for Android logging if(ANDROID)
check_library_exists(log __android_log_print "" HAVE_LIBLOG) # Include liblog for Android logging
if(HAVE_LIBLOG) check_library_exists(log __android_log_print "" HAVE_LIBLOG)
set(EXTRA_LIBS log ${EXTRA_LIBS}) if(HAVE_LIBLOG)
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log) set(EXTRA_LIBS log ${EXTRA_LIBS})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log)
endif()
endif() endif()
if(MSVC) if(MSVC)
set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS NOMINMAX) set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS)
check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH) check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH)
if(HAVE_PERMISSIVE_SWITCH) if(HAVE_PERMISSIVE_SWITCH)
set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:/permissive->) set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:/permissive->)
endif() endif()
set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4268 /wd4324 /wd5030) set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030)
# Remove /W3, which is added by default, since we set /W4. Some build # Remove /W3, which is added by default, since we set /W4. Some build
# generators with MSVC complain about both /W3 and /W4 being specified. # generators with MSVC complain about both /W3 and /W4 being specified.
foreach(flag_var CMAKE_C_FLAGS CMAKE_CXX_FLAGS) foreach(flag_var CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
@ -345,17 +355,17 @@ endif()
set(SSE2_SWITCH "") set(SSE2_SWITCH "")
set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
if(NOT MSVC) if(NOT MSVC)
set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
# Yes GCC, really don't accept command line options you don't support # Yes GCC, really don't accept command line options you don't support
set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Werror") set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Werror")
check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH)
if(HAVE_MSSE2_SWITCH)
set(SSE2_SWITCH "-msse2")
endif()
set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS})
unset(OLD_REQUIRED_FLAGS)
endif() endif()
check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH)
if(HAVE_MSSE2_SWITCH)
set(SSE2_SWITCH "-msse2")
endif()
set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS})
unset(OLD_REQUIRED_FLAGS)
check_include_file(xmmintrin.h HAVE_XMMINTRIN_H) check_include_file(xmmintrin.h HAVE_XMMINTRIN_H)
check_include_file(emmintrin.h HAVE_EMMINTRIN_H) check_include_file(emmintrin.h HAVE_EMMINTRIN_H)
@ -369,20 +379,25 @@ set(HAVE_SSE3 0)
set(HAVE_SSE4_1 0) set(HAVE_SSE4_1 0)
set(HAVE_NEON 0) set(HAVE_NEON 0)
# Check for SSE+SSE2 support # Check for SSE support
option(ALSOFT_REQUIRE_SSE "Require SSE support" OFF) option(ALSOFT_REQUIRE_SSE "Require SSE support" OFF)
option(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF) if(HAVE_XMMINTRIN_H)
if(HAVE_XMMINTRIN_H AND HAVE_EMMINTRIN_H)
option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON) option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON)
option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON) if(ALSOFT_CPUEXT_SSE)
if(ALSOFT_CPUEXT_SSE AND ALSOFT_CPUEXT_SSE2)
set(HAVE_SSE 1) set(HAVE_SSE 1)
set(HAVE_SSE2 1)
endif() endif()
endif() endif()
if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE) if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE)
message(FATAL_ERROR "Failed to enabled required SSE CPU extensions") message(FATAL_ERROR "Failed to enabled required SSE CPU extensions")
endif() endif()
option(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF)
if(HAVE_EMMINTRIN_H)
option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON)
if(HAVE_SSE AND ALSOFT_CPUEXT_SSE2)
set(HAVE_SSE2 1)
endif()
endif()
if(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2) if(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2)
message(FATAL_ERROR "Failed to enable required SSE2 CPU extensions") message(FATAL_ERROR "Failed to enable required SSE2 CPU extensions")
endif() endif()
@ -435,7 +450,14 @@ set(FPMATH_SET "0")
if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2)
option(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE) option(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE)
if(ALSOFT_ENABLE_SSE2_CODEGEN) if(ALSOFT_ENABLE_SSE2_CODEGEN)
if(SSE2_SWITCH OR NOT MSVC) if(MSVC)
check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2)
if(HAVE_ARCH_SSE2)
set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2")
set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS})
set(FPMATH_SET 2)
endif()
elseif(SSE2_SWITCH)
check_c_compiler_flag("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2) check_c_compiler_flag("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2)
if(HAVE_MFPMATH_SSE_2) if(HAVE_MFPMATH_SSE_2)
set(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse) set(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse)
@ -447,13 +469,6 @@ if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2)
# OSs don't guarantee this on 32-bit, so externally-callable # OSs don't guarantee this on 32-bit, so externally-callable
# functions need to ensure an aligned stack. # functions need to ensure an aligned stack.
set(EXPORT_DECL "${EXPORT_DECL} __attribute__((force_align_arg_pointer))") set(EXPORT_DECL "${EXPORT_DECL} __attribute__((force_align_arg_pointer))")
elseif(MSVC)
check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2)
if(HAVE_ARCH_SSE2)
set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2")
set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS})
set(FPMATH_SET 2)
endif()
endif() endif()
endif() endif()
endif() endif()
@ -551,51 +566,11 @@ if(NOT WIN32)
check_symbol_exists(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP) check_symbol_exists(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP)
if(NOT HAVE_PTHREAD_SETNAME_NP) if(NOT HAVE_PTHREAD_SETNAME_NP)
check_symbol_exists(pthread_set_name_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SET_NAME_NP) check_symbol_exists(pthread_set_name_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SET_NAME_NP)
else()
check_c_source_compiles("
#include <pthread.h>
#include <pthread_np.h>
int main()
{
pthread_setname_np(\"testname\");
return 0;
}"
PTHREAD_SETNAME_NP_ONE_PARAM
)
check_c_source_compiles("
#include <pthread.h>
#include <pthread_np.h>
int main()
{
pthread_setname_np(pthread_self(), \"%s\", \"testname\");
return 0;
}"
PTHREAD_SETNAME_NP_THREE_PARAMS
)
endif() endif()
else() else()
check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP) check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP)
if(NOT HAVE_PTHREAD_SETNAME_NP) if(NOT HAVE_PTHREAD_SETNAME_NP)
check_symbol_exists(pthread_set_name_np pthread.h HAVE_PTHREAD_SET_NAME_NP) check_symbol_exists(pthread_set_name_np pthread.h HAVE_PTHREAD_SET_NAME_NP)
else()
check_c_source_compiles("
#include <pthread.h>
int main()
{
pthread_setname_np(\"testname\");
return 0;
}"
PTHREAD_SETNAME_NP_ONE_PARAM
)
check_c_source_compiles("
#include <pthread.h>
int main()
{
pthread_setname_np(pthread_self(), \"%s\", \"testname\");
return 0;
}"
PTHREAD_SETNAME_NP_THREE_PARAMS
)
endif() endif()
endif() endif()
endif() endif()
@ -615,17 +590,19 @@ set(COMMON_OBJS
common/alfstream.h common/alfstream.h
common/almalloc.cpp common/almalloc.cpp
common/almalloc.h common/almalloc.h
common/alnumbers.h
common/alnumeric.h common/alnumeric.h
common/aloptional.h common/aloptional.h
common/alspan.h common/alspan.h
common/alstring.cpp common/alstring.cpp
common/alstring.h common/alstring.h
common/atomic.h common/atomic.h
common/comptr.h
common/dynload.cpp common/dynload.cpp
common/dynload.h common/dynload.h
common/intrusive_ptr.h common/intrusive_ptr.h
common/math_defs.h
common/opthelpers.h common/opthelpers.h
common/phase_shifter.h
common/polyphase_resampler.cpp common/polyphase_resampler.cpp
common/polyphase_resampler.h common/polyphase_resampler.h
common/pragmadefs.h common/pragmadefs.h
@ -642,17 +619,32 @@ set(COMMON_OBJS
set(CORE_OBJS set(CORE_OBJS
core/ambdec.cpp core/ambdec.cpp
core/ambdec.h core/ambdec.h
core/ambidefs.cpp
core/ambidefs.h core/ambidefs.h
core/async_event.h
core/bformatdec.cpp
core/bformatdec.h
core/bs2b.cpp core/bs2b.cpp
core/bs2b.h core/bs2b.h
core/bsinc_defs.h core/bsinc_defs.h
core/bsinc_tables.cpp core/bsinc_tables.cpp
core/bsinc_tables.h core/bsinc_tables.h
core/bufferline.h core/bufferline.h
core/buffer_storage.cpp
core/buffer_storage.h
core/context.cpp
core/context.h
core/converter.cpp
core/converter.h
core/cpu_caps.cpp core/cpu_caps.cpp
core/cpu_caps.h core/cpu_caps.h
core/devformat.cpp core/devformat.cpp
core/devformat.h core/devformat.h
core/device.cpp
core/device.h
core/effects/base.h
core/effectslot.cpp
core/effectslot.h
core/except.cpp core/except.cpp
core/except.h core/except.h
core/filters/biquad.h core/filters/biquad.h
@ -665,12 +657,57 @@ set(CORE_OBJS
core/fmt_traits.h core/fmt_traits.h
core/fpu_ctrl.cpp core/fpu_ctrl.cpp
core/fpu_ctrl.h core/fpu_ctrl.h
core/front_stablizer.h
core/helpers.cpp
core/helpers.h
core/hrtf.cpp
core/hrtf.h
core/logging.cpp core/logging.cpp
core/logging.h core/logging.h
core/mastering.cpp core/mastering.cpp
core/mastering.h core/mastering.h
core/mixer.cpp
core/mixer.h
core/resampler_limits.h
core/uhjfilter.cpp core/uhjfilter.cpp
core/uhjfilter.h core/uhjfilter.h
core/uiddefs.cpp
core/voice.cpp
core/voice.h
core/voice_change.h)
set(HAVE_RTKIT 0)
option(ALSOFT_REQUIRE_RTKIT "Require RTKit/D-Bus support" FALSE)
find_package(DBus1 QUIET)
if(DBus1_FOUND)
option(ALSOFT_RTKIT "Enable RTKit support" ON)
if(ALSOFT_RTKIT)
set(HAVE_RTKIT 1)
set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h core/rtkit.cpp core/rtkit.h)
if(WIN32 OR HAVE_DLFCN_H)
set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS})
set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS})
else()
set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES})
endif()
endif()
else()
set(MISSING_VARS "")
if(NOT DBus1_INCLUDE_DIRS)
set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS")
endif()
if(NOT DBus1_LIBRARIES)
set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES")
endif()
message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})")
unset(MISSING_VARS)
endif()
if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT)
message(FATAL_ERROR "Failed to enabled required RTKit support")
endif()
# Default mixers, always available
set(CORE_OBJS ${CORE_OBJS}
core/mixer/defs.h core/mixer/defs.h
core/mixer/hrtfbase.h core/mixer/hrtfbase.h
core/mixer/hrtfdefs.h core/mixer/hrtfdefs.h
@ -691,6 +728,7 @@ set(OPENAL_OBJS
al/effects/dedicated.cpp al/effects/dedicated.cpp
al/effects/distortion.cpp al/effects/distortion.cpp
al/effects/echo.cpp al/effects/echo.cpp
al/effects/effects.cpp
al/effects/effects.h al/effects/effects.h
al/effects/equalizer.cpp al/effects/equalizer.cpp
al/effects/fshifter.cpp al/effects/fshifter.cpp
@ -714,22 +752,14 @@ set(OPENAL_OBJS
# ALC and related routines # ALC and related routines
set(ALC_OBJS set(ALC_OBJS
alc/alc.cpp alc/alc.cpp
alc/alcmain.h
alc/alu.cpp alc/alu.cpp
alc/alu.h alc/alu.h
alc/alconfig.cpp alc/alconfig.cpp
alc/alconfig.h alc/alconfig.h
alc/alcontext.h alc/context.cpp
alc/async_event.h alc/context.h
alc/bformatdec.cpp alc/device.cpp
alc/bformatdec.h alc/device.h
alc/buffer_storage.cpp
alc/buffer_storage.h
alc/compat.h
alc/converter.cpp
alc/converter.h
alc/effectslot.cpp
alc/effectslot.h
alc/effects/base.h alc/effects/base.h
alc/effects/autowah.cpp alc/effects/autowah.cpp
alc/effects/chorus.cpp alc/effects/chorus.cpp
@ -745,22 +775,42 @@ set(ALC_OBJS
alc/effects/pshifter.cpp alc/effects/pshifter.cpp
alc/effects/reverb.cpp alc/effects/reverb.cpp
alc/effects/vmorpher.cpp alc/effects/vmorpher.cpp
alc/front_stablizer.h
alc/helpers.cpp
alc/hrtf.cpp
alc/hrtf.h
alc/inprogext.h alc/inprogext.h
alc/panning.cpp alc/panning.cpp)
alc/uiddefs.cpp
alc/voice.cpp if (ALSOFT_EAX)
alc/voice.h set(OPENAL_OBJS
alc/voice_change.h) ${OPENAL_OBJS}
al/eax_api.cpp
al/eax_api.h
al/eax_eax_call.cpp
al/eax_eax_call.h
al/eax_effect.cpp
al/eax_effect.h
al/eax_exception.cpp
al/eax_exception.h
al/eax_fx_slot_index.cpp
al/eax_fx_slot_index.h
al/eax_fx_slots.cpp
al/eax_fx_slots.h
al/eax_globals.cpp
al/eax_globals.h
al/eax_utils.cpp
al/eax_utils.h
al/eax_x_ram.cpp
al/eax_x_ram.h
)
endif ()
# Include SIMD mixers # Include SIMD mixers
set(CPU_EXTS "Default") set(CPU_EXTS "Default")
if(HAVE_SSE)
set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse.cpp)
set(CPU_EXTS "${CPU_EXTS}, SSE")
endif()
if(HAVE_SSE2) if(HAVE_SSE2)
set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse.cpp core/mixer/mixer_sse2.cpp) set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse2.cpp)
set(CPU_EXTS "${CPU_EXTS}, SSE, SSE2") set(CPU_EXTS "${CPU_EXTS}, SSE2")
endif() endif()
if(HAVE_SSE3) if(HAVE_SSE3)
set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse3.cpp) set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse3.cpp)
@ -778,6 +828,7 @@ endif()
set(HAVE_ALSA 0) set(HAVE_ALSA 0)
set(HAVE_OSS 0) set(HAVE_OSS 0)
set(HAVE_PIPEWIRE 0)
set(HAVE_SOLARIS 0) set(HAVE_SOLARIS 0)
set(HAVE_SNDIO 0) set(HAVE_SNDIO 0)
set(HAVE_DSOUND 0) set(HAVE_DSOUND 0)
@ -849,6 +900,25 @@ if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS)
message(FATAL_ERROR "Failed to enabled required OSS backend") message(FATAL_ERROR "Failed to enabled required OSS backend")
endif() endif()
# Check PipeWire backend
option(ALSOFT_REQUIRE_PIPEWIRE "Require PipeWire backend" OFF)
if(PkgConfig_FOUND)
pkg_check_modules(PIPEWIRE libpipewire-0.3)
if(PIPEWIRE_FOUND)
option(ALSOFT_BACKEND_PIPEWIRE "Enable PipeWire backend" ON)
if(ALSOFT_BACKEND_PIPEWIRE)
set(HAVE_PIPEWIRE 1)
set(BACKENDS "${BACKENDS} PipeWire${IS_LINKED},")
set(ALC_OBJS ${ALC_OBJS} alc/backends/pipewire.cpp alc/backends/pipewire.h)
add_backend_libs(${PIPEWIRE_LIBRARIES})
set(INC_PATHS ${INC_PATHS} ${PIPEWIRE_INCLUDE_DIRS})
endif()
endif()
endif()
if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE)
message(FATAL_ERROR "Failed to enabled required PipeWire backend")
endif()
# Check Solaris backend # Check Solaris backend
option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF)
find_package(AudioIO) find_package(AudioIO)
@ -1148,10 +1218,16 @@ if(ALSOFT_EMBED_HRTF_DATA)
endif() endif()
if(ALSOFT_UTILS AND NOT ALSOFT_NO_CONFIG_UTIL) if(ALSOFT_UTILS)
find_package(Qt5Widgets) find_package(MySOFA)
if(NOT ALSOFT_NO_CONFIG_UTIL)
find_package(Qt5Widgets QUIET)
if(NOT Qt5Widgets_FOUND)
message(STATUS "Could NOT find Qt5Widgets")
endif()
endif()
endif() endif()
if(ALSOFT_EXAMPLES) if(ALSOFT_UTILS OR ALSOFT_EXAMPLES)
find_package(SndFile) find_package(SndFile)
find_package(SDL2) find_package(SDL2)
if(SDL2_FOUND) if(SDL2_FOUND)
@ -1212,6 +1288,7 @@ if(LIBTYPE STREQUAL "STATIC")
add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS}) add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS})
target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC) target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC)
target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
if(WIN32) if(WIN32)
# This option is for static linking OpenAL Soft into another project # This option is for static linking OpenAL Soft into another project
# that already defines the IDs. It is up to that project to ensure all # that already defines the IDs. It is up to that project to ensure all
@ -1232,13 +1309,14 @@ else()
router/al.cpp router/al.cpp
) )
target_compile_definitions(OpenAL target_compile_definitions(OpenAL
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS}) PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}"
"AL_API=${EXPORT_DECL}" ${CPP_DEFS})
target_compile_options(OpenAL PRIVATE ${C_FLAGS}) target_compile_options(OpenAL PRIVATE ${C_FLAGS})
target_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS}) target_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS})
target_include_directories(OpenAL target_include_directories(OpenAL
PUBLIC PUBLIC
$<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include> $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE PRIVATE
${OpenAL_SOURCE_DIR}/common ${OpenAL_SOURCE_DIR}/common
${OpenAL_BINARY_DIR} ${OpenAL_BINARY_DIR}
@ -1303,12 +1381,14 @@ endif()
target_include_directories(${IMPL_TARGET} target_include_directories(${IMPL_TARGET}
PUBLIC PUBLIC
$<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include> $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include> INTERFACE
$<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include/AL>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/AL>
PRIVATE PRIVATE
${INC_PATHS} ${INC_PATHS}
${OpenAL_BINARY_DIR} ${OpenAL_BINARY_DIR}
${OpenAL_SOURCE_DIR} ${OpenAL_SOURCE_DIR}
${OpenAL_SOURCE_DIR}/alc
${OpenAL_SOURCE_DIR}/common ${OpenAL_SOURCE_DIR}/common
) )
@ -1317,7 +1397,8 @@ set_target_properties(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME}
SOVERSION ${LIB_MAJOR_VERSION} SOVERSION ${LIB_MAJOR_VERSION}
) )
target_compile_definitions(${IMPL_TARGET} target_compile_definitions(${IMPL_TARGET}
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS}) PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}"
${CPP_DEFS})
target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS}) target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS})
if(TARGET build_version) if(TARGET build_version)
@ -1351,25 +1432,35 @@ if(HAS_ROUTER)
message(STATUS "Building DLL router") message(STATUS "Building DLL router")
endif() endif()
message(STATUS " message(STATUS "")
Building OpenAL with support for the following backends: message(STATUS "Building OpenAL with support for the following backends:")
${BACKENDS} message(STATUS " ${BACKENDS}")
message(STATUS "")
Building with support for CPU extensions: message(STATUS "Building with support for CPU extensions:")
${CPU_EXTS} message(STATUS " ${CPU_EXTS}")
") message(STATUS "")
if(FPMATH_SET) if(FPMATH_SET)
message(STATUS "Building with SSE${FPMATH_SET} codegen message(STATUS "Building with SSE${FPMATH_SET} codegen")
") message(STATUS "")
endif()
if(ALSOFT_EAX)
message(STATUS "Building with legacy EAX extension support")
message(STATUS "")
endif() endif()
if(ALSOFT_EMBED_HRTF_DATA) if(ALSOFT_EMBED_HRTF_DATA)
message(STATUS "Embedding HRTF datasets message(STATUS "Embedding HRTF datasets")
") message(STATUS "")
endif() endif()
# An alias for sub-project builds
add_library(OpenAL::OpenAL ALIAS OpenAL)
# Install main library # Install main library
if(ALSOFT_INSTALL) if(ALSOFT_INSTALL)
configure_package_config_file(OpenALConfig.cmake.in OpenALConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL)
install(TARGETS OpenAL EXPORT OpenAL install(TARGETS OpenAL EXPORT OpenAL
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
@ -1378,15 +1469,17 @@ if(ALSOFT_INSTALL)
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL) INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL)
export(TARGETS OpenAL export(TARGETS OpenAL
NAMESPACE OpenAL:: NAMESPACE OpenAL::
FILE OpenALConfig.cmake) FILE OpenALTargets.cmake)
install(EXPORT OpenAL install(EXPORT OpenAL
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL
NAMESPACE OpenAL:: NAMESPACE OpenAL::
FILE OpenALConfig.cmake) FILE OpenALTargets.cmake)
install(DIRECTORY include/AL install(DIRECTORY include/AL
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${OpenAL_BINARY_DIR}/openal.pc" install(FILES "${OpenAL_BINARY_DIR}/openal.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(FILES "${OpenAL_BINARY_DIR}/OpenALConfig.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL")
if(TARGET soft_oal) if(TARGET soft_oal)
install(TARGETS soft_oal install(TARGETS soft_oal
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
@ -1429,7 +1522,24 @@ if(ALSOFT_UTILS)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info)
endif() endif()
find_package(MySOFA) if(SNDFILE_FOUND)
add_executable(uhjdecoder utils/uhjdecoder.cpp)
target_compile_definitions(uhjdecoder PRIVATE ${CPP_DEFS})
target_include_directories(uhjdecoder
PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_options(uhjdecoder PRIVATE ${C_FLAGS})
target_link_libraries(uhjdecoder PUBLIC common
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG})
add_executable(uhjencoder utils/uhjencoder.cpp)
target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS})
target_include_directories(uhjencoder
PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
target_compile_options(uhjencoder PRIVATE ${C_FLAGS})
target_link_libraries(uhjencoder PUBLIC common
PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG})
endif()
if(MYSOFA_FOUND) if(MYSOFA_FOUND)
set(SOFA_SUPPORT_SRCS set(SOFA_SUPPORT_SRCS
utils/sofa-support.cpp utils/sofa-support.cpp

View file

@ -1,3 +1,57 @@
openal-soft-1.22.0:
Implemented the ALC_SOFT_reopen_device extension. This allows for moving
devices to different outputs without losing object state.
Implemented the ALC_SOFT_output_mode extension.
Implemented the AL_SOFT_callback_buffer extension.
Implemented the AL_SOFT_UHJ extension. This supports native UHJ buffer
formats and Super Stereo processing.
Implemented the legacy EAX extensions. Enabled by default only on Windows.
Improved sound positioning stability when a source is near the listener.
Improved the default 5.1 output decoder.
Improved the high frequency response for the HRTF second-order ambisonic
decoder.
Improved SoundIO capture behavior.
Fixed UHJ output on NEON-capable CPUs.
Fixed redundant effect updates when setting an effect property to the
current value.
Fixed WASAPI capture using really low sample rates, and sources with very
high pitch shifts when using a bsinc resampler.
Added a PipeWire backend.
Added enumeration for the JACK and CoreAudio backends.
Added optional support for RTKit to get real-time priority. Only used as a
backup when pthread_setschedparam fails.
Added an option for JACK playback to render directly in the real-time
processing callback. For lower playback latency, on by default.
Added an option for custom JACK devices.
Added utilities to encode and decode UHJ audio files. Files are decoded to
the .amb format, and are encoded from libsndfile-compatible formats.
Added an in-progress extension to hold sources in a playing state when a
device disconnects. Allows devices to be reset or reopened and have sources
resume from where they left off.
Lowered the priority of the JACK backend. To avoid it getting picked when
PipeWire is providing JACK compatibility, since the JACK backend is less
robust with auto-configuration.
openal-soft-1.21.1: openal-soft-1.21.1:
Improved alext.h's detection of standard types. Improved alext.h's detection of standard types.
@ -5,6 +59,8 @@ openal-soft-1.21.1:
Improved slightly the local source position when the listener and source Improved slightly the local source position when the listener and source
are near each other. are near each other.
Improved click/pop prevention for sounds that stop prematurely.
Fixed compilation for Windows ARM targets with MSVC. Fixed compilation for Windows ARM targets with MSVC.
Fixed ARM NEON detection on Windows. Fixed ARM NEON detection on Windows.
@ -19,6 +75,8 @@ openal-soft-1.21.1:
Fixed missing source stop events when stopping a paused source. Fixed missing source stop events when stopping a paused source.
Added capture support to the experimental Oboe backend.
openal-soft-1.21.0: openal-soft-1.21.0:
Updated library codebase to C++14. Updated library codebase to C++14.

View file

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.1)
include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake")
set(OPENAL_FOUND ON)
set(OPENAL_INCLUDE_DIR $<TARGET_PROPERTY:OpenAL::OpenAL,INTERFACE_INCLUDE_DIRECTORIES>)
set(OPENAL_LIBRARY $<LINK_ONLY:OpenAL::OpenAL>)
set(OPENAL_DEFINITIONS $<TARGET_PROPERTY:OpenAL::OpenAL,INTERFACE_COMPILE_DEFINITIONS>)
set(OPENAL_VERSION_STRING @PACKAGE_VERSION@)

View file

@ -7,5 +7,10 @@
# cmake .. -DANDROID_STL=c++_shared \ # cmake .. -DANDROID_STL=c++_shared \
# -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake # -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake
# #
# Certain NDK versions may also need to use the lld linker to avoid errors
# about missing liblog.so and libOpenSLES.so. That can be done by:
# cmake .. -DANDROID_LD=lld \
# -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake
#
MESSAGE(FATAL_ERROR "Use the toolchain provided by the Android NDK") MESSAGE(FATAL_ERROR "Use the toolchain provided by the Android NDK")

File diff suppressed because it is too large Load diff

View file

@ -8,14 +8,22 @@
#include "AL/alc.h" #include "AL/alc.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alcmain.h" #include "alc/device.h"
#include "alc/effects/base.h"
#include "almalloc.h" #include "almalloc.h"
#include "atomic.h" #include "atomic.h"
#include "effectslot.h" #include "core/effectslot.h"
#include "effects/base.h"
#include "intrusive_ptr.h" #include "intrusive_ptr.h"
#include "vector.h" #include "vector.h"
#ifdef ALSOFT_EAX
#include <memory>
#include "eax_eax_call.h"
#include "eax_effect.h"
#include "eax_fx_slot_index.h"
#endif // ALSOFT_EAX
struct ALbuffer; struct ALbuffer;
struct ALeffect; struct ALeffect;
struct WetBuffer; struct WetBuffer;
@ -40,7 +48,7 @@ struct ALeffectslot {
al::intrusive_ptr<EffectState> State; al::intrusive_ptr<EffectState> State;
} Effect; } Effect;
std::atomic_flag PropsClean; bool mPropsDirty{true};
SlotState mState{SlotState::Initial}; SlotState mState{SlotState::Initial};
@ -56,13 +64,214 @@ struct ALeffectslot {
ALeffectslot& operator=(const ALeffectslot&) = delete; ALeffectslot& operator=(const ALeffectslot&) = delete;
~ALeffectslot(); ~ALeffectslot();
ALenum initEffect(ALeffect *effect, ALCcontext *context); ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context);
void updateProps(ALCcontext *context); void updateProps(ALCcontext *context);
/* This can be new'd for the context's default effect slot. */ /* This can be new'd for the context's default effect slot. */
DEF_NEWDEL(ALeffectslot) DEF_NEWDEL(ALeffectslot)
#ifdef ALSOFT_EAX
public:
void eax_initialize(
ALCcontext& al_context,
EaxFxSlotIndexValue index);
const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept;
// [[nodiscard]]
bool eax_dispatch(const EaxEaxCall& eax_call)
{ return eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); }
void eax_unlock_legacy() noexcept;
void eax_commit() { eax_apply_deferred(); }
private:
ALCcontext* eax_al_context_{};
EaxFxSlotIndexValue eax_fx_slot_index_{};
EAX50FXSLOTPROPERTIES eax_eax_fx_slot_{};
EaxEffectUPtr eax_effect_{};
bool eax_is_locked_{};
[[noreturn]]
static void eax_fail(
const char* message);
GUID eax_get_eax_default_effect_guid() const noexcept;
long eax_get_eax_default_lock() const noexcept;
void eax_set_eax_fx_slot_defaults();
void eax_initialize_eax();
void eax_initialize_lock();
void eax_initialize_effects();
void eax_get_fx_slot_all(
const EaxEaxCall& eax_call) const;
void eax_get_fx_slot(
const EaxEaxCall& eax_call) const;
// [[nodiscard]]
bool eax_get(
const EaxEaxCall& eax_call);
void eax_set_fx_slot_effect(
ALenum effect_type);
void eax_set_fx_slot_effect();
void eax_set_efx_effect_slot_gain();
void eax_set_fx_slot_volume();
void eax_set_effect_slot_send_auto();
void eax_set_fx_slot_flags();
void eax_ensure_is_unlocked() const;
void eax_validate_fx_slot_effect(
const GUID& eax_effect_id);
void eax_validate_fx_slot_volume(
long eax_volume);
void eax_validate_fx_slot_lock(
long eax_lock);
void eax_validate_fx_slot_flags(
unsigned long eax_flags,
int eax_version);
void eax_validate_fx_slot_occlusion(
long eax_occlusion);
void eax_validate_fx_slot_occlusion_lf_ratio(
float eax_occlusion_lf_ratio);
void eax_validate_fx_slot_all(
const EAX40FXSLOTPROPERTIES& fx_slot,
int eax_version);
void eax_validate_fx_slot_all(
const EAX50FXSLOTPROPERTIES& fx_slot,
int eax_version);
void eax_set_fx_slot_effect(
const GUID& eax_effect_id);
void eax_set_fx_slot_volume(
long eax_volume);
void eax_set_fx_slot_lock(
long eax_lock);
void eax_set_fx_slot_flags(
unsigned long eax_flags);
// [[nodiscard]]
bool eax_set_fx_slot_occlusion(
long eax_occlusion);
// [[nodiscard]]
bool eax_set_fx_slot_occlusion_lf_ratio(
float eax_occlusion_lf_ratio);
void eax_set_fx_slot_all(
const EAX40FXSLOTPROPERTIES& eax_fx_slot);
// [[nodiscard]]
bool eax_set_fx_slot_all(
const EAX50FXSLOTPROPERTIES& eax_fx_slot);
void eax_set_fx_slot_effect(
const EaxEaxCall& eax_call);
void eax_set_fx_slot_volume(
const EaxEaxCall& eax_call);
void eax_set_fx_slot_lock(
const EaxEaxCall& eax_call);
void eax_set_fx_slot_flags(
const EaxEaxCall& eax_call);
// [[nodiscard]]
bool eax_set_fx_slot_occlusion(
const EaxEaxCall& eax_call);
// [[nodiscard]]
bool eax_set_fx_slot_occlusion_lf_ratio(
const EaxEaxCall& eax_call);
// [[nodiscard]]
bool eax_set_fx_slot_all(
const EaxEaxCall& eax_call);
bool eax_set_fx_slot(
const EaxEaxCall& eax_call);
void eax_apply_deferred();
// [[nodiscard]]
bool eax_set(
const EaxEaxCall& eax_call);
void eax_dispatch_effect(
const EaxEaxCall& eax_call);
// `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)`
void eax_set_effect_slot_effect(EaxEffect &effect);
// `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)`
void eax_set_effect_slot_send_auto(bool is_send_auto);
// `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)`
void eax_set_effect_slot_gain(ALfloat gain);
public:
class EaxDeleter {
public:
void operator()(ALeffectslot *effect_slot);
}; // EaxAlEffectSlotDeleter
#endif // ALSOFT_EAX
}; };
void UpdateAllEffectSlotProps(ALCcontext *context); void UpdateAllEffectSlotProps(ALCcontext *context);
#ifdef ALSOFT_EAX
using EaxAlEffectSlotUPtr = std::unique_ptr<ALeffectslot, ALeffectslot::EaxDeleter>;
EaxAlEffectSlotUPtr eax_create_al_effect_slot(
ALCcontext& context);
void eax_delete_al_effect_slot(
ALCcontext& context,
ALeffectslot& effect_slot);
#endif // ALSOFT_EAX
#endif #endif

View file

@ -35,6 +35,7 @@
#include <mutex> #include <mutex>
#include <new> #include <new>
#include <numeric> #include <numeric>
#include <stdexcept>
#include <utility> #include <utility>
#include "AL/al.h" #include "AL/al.h"
@ -43,16 +44,23 @@
#include "albit.h" #include "albit.h"
#include "albyte.h" #include "albyte.h"
#include "alcmain.h" #include "alc/context.h"
#include "alcontext.h" #include "alc/device.h"
#include "alc/inprogext.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "aloptional.h" #include "aloptional.h"
#include "atomic.h" #include "atomic.h"
#include "core/except.h" #include "core/except.h"
#include "inprogext.h" #include "core/logging.h"
#include "core/voice.h"
#include "opthelpers.h" #include "opthelpers.h"
#ifdef ALSOFT_EAX
#include "eax_globals.h"
#include "eax_x_ram.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -110,10 +118,10 @@ void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t
for(size_t c{0};c < numchans;c++) for(size_t c{0};c < numchans;c++)
{ {
sample[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8); sample[c] = src[0] | (src[1]<<8);
sample[c] = (sample[c]^0x8000) - 32768; sample[c] = (sample[c]^0x8000) - 32768;
src += 2; src += 2;
index[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8); index[c] = src[0] | (src[1]<<8);
index[c] = clampi((index[c]^0x8000) - 32768, 0, 88); index[c] = clampi((index[c]^0x8000) - 32768, 0, 88);
src += 2; src += 2;
@ -126,8 +134,8 @@ void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t
{ {
for(size_t c{0};c < numchans;c++) for(size_t c{0};c < numchans;c++)
{ {
code[c] = al::to_integer<ALuint>(src[0]) | (al::to_integer<ALuint>(src[1])<< 8) | code[c] = ALuint{src[0]} | (ALuint{src[1]}<< 8) | (ALuint{src[2]}<<16)
(al::to_integer<ALuint>(src[2])<<16) | (al::to_integer<ALuint>(src[3])<<24); | (ALuint{src[3]}<<24);
src += 4; src += 4;
} }
} }
@ -156,25 +164,23 @@ void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size
for(size_t c{0};c < numchans;c++) for(size_t c{0};c < numchans;c++)
{ {
blockpred[c] = std::min<ALubyte>(al::to_integer<ALubyte>(src[0]), 6); blockpred[c] = std::min<ALubyte>(src[0], 6);
++src; ++src;
} }
for(size_t c{0};c < numchans;c++) for(size_t c{0};c < numchans;c++)
{ {
delta[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8); delta[c] = src[0] | (src[1]<<8);
delta[c] = (delta[c]^0x8000) - 32768; delta[c] = (delta[c]^0x8000) - 32768;
src += 2; src += 2;
} }
for(size_t c{0};c < numchans;c++) for(size_t c{0};c < numchans;c++)
{ {
samples[c][0] = static_cast<ALshort>(al::to_integer<int>(src[0]) | samples[c][0] = static_cast<ALshort>(src[0] | (src[1]<<8));
(al::to_integer<int>(src[1])<<8));
src += 2; src += 2;
} }
for(size_t c{0};c < numchans;c++) for(size_t c{0};c < numchans;c++)
{ {
samples[c][1] = static_cast<ALshort>(al::to_integer<int>(src[0]) | samples[c][1] = static_cast<ALshort>(src[0] | (src[1]<<8));
(al::to_integer<int>(src[1])<<8));
src += 2; src += 2;
} }
@ -198,13 +204,13 @@ void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size
int pred{(samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] + int pred{(samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] +
samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256}; samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256};
pred += (al::to_integer<int>(nibble^0x08) - 0x08) * delta[c]; pred += ((nibble^0x08) - 0x08) * delta[c];
pred = clampi(pred, -32768, 32767); pred = clampi(pred, -32768, 32767);
samples[c][1] = samples[c][0]; samples[c][1] = samples[c][0];
samples[c][0] = static_cast<int16_t>(pred); samples[c][0] = static_cast<int16_t>(pred);
delta[c] = (MSADPCMAdaption[al::to_integer<ALubyte>(nibble)] * delta[c]) / 256; delta[c] = (MSADPCMAdaption[nibble] * delta[c]) / 256;
delta[c] = maxi(16, delta[c]); delta[c] = maxi(16, delta[c]);
*(dst++) = static_cast<int16_t>(pred); *(dst++) = static_cast<int16_t>(pred);
@ -271,6 +277,9 @@ ALuint ChannelsFromUserFmt(UserFmtChannels chans, ALuint ambiorder) noexcept
case UserFmtX71: return 8; case UserFmtX71: return 8;
case UserFmtBFormat2D: return (ambiorder*2) + 1; case UserFmtBFormat2D: return (ambiorder*2) + 1;
case UserFmtBFormat3D: return (ambiorder+1) * (ambiorder+1); case UserFmtBFormat3D: return (ambiorder+1) * (ambiorder+1);
case UserFmtUHJ2: return 2;
case UserFmtUHJ3: return 3;
case UserFmtUHJ4: return 4;
} }
return 0; return 0;
} }
@ -310,11 +319,82 @@ ALenum EnumFromAmbiScaling(AmbiScaling scale)
{ {
case AmbiScaling::FuMa: return AL_FUMA_SOFT; case AmbiScaling::FuMa: return AL_FUMA_SOFT;
case AmbiScaling::SN3D: return AL_SN3D_SOFT; case AmbiScaling::SN3D: return AL_SN3D_SOFT;
case AmbiScaling::N3D: return AL_SN3D_SOFT; case AmbiScaling::N3D: return AL_N3D_SOFT;
case AmbiScaling::UHJ: break;
} }
throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))}; throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))};
} }
al::optional<FmtChannels> FmtFromUserFmt(UserFmtChannels chans)
{
switch(chans)
{
case UserFmtMono: return al::make_optional(FmtMono);
case UserFmtStereo: return al::make_optional(FmtStereo);
case UserFmtRear: return al::make_optional(FmtRear);
case UserFmtQuad: return al::make_optional(FmtQuad);
case UserFmtX51: return al::make_optional(FmtX51);
case UserFmtX61: return al::make_optional(FmtX61);
case UserFmtX71: return al::make_optional(FmtX71);
case UserFmtBFormat2D: return al::make_optional(FmtBFormat2D);
case UserFmtBFormat3D: return al::make_optional(FmtBFormat3D);
case UserFmtUHJ2: return al::make_optional(FmtUHJ2);
case UserFmtUHJ3: return al::make_optional(FmtUHJ3);
case UserFmtUHJ4: return al::make_optional(FmtUHJ4);
}
return al::nullopt;
}
al::optional<FmtType> FmtFromUserFmt(UserFmtType type)
{
switch(type)
{
case UserFmtUByte: return al::make_optional(FmtUByte);
case UserFmtShort: return al::make_optional(FmtShort);
case UserFmtFloat: return al::make_optional(FmtFloat);
case UserFmtDouble: return al::make_optional(FmtDouble);
case UserFmtMulaw: return al::make_optional(FmtMulaw);
case UserFmtAlaw: return al::make_optional(FmtAlaw);
/* ADPCM not handled here. */
case UserFmtIMA4: break;
case UserFmtMSADPCM: break;
}
return al::nullopt;
}
#ifdef ALSOFT_EAX
bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer,
const ALuint newsize) noexcept
{
ALuint freemem{device.eax_x_ram_free_size};
/* If the buffer is currently in "hardware", add its memory to the free
* pool since it'll be "replaced".
*/
if(buffer.eax_x_ram_is_hardware)
freemem += buffer.OriginalSize;
return freemem >= newsize;
}
void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept
{
if(buffer.eax_x_ram_is_hardware)
return;
if(device.eax_x_ram_free_size >= buffer.OriginalSize)
{
device.eax_x_ram_free_size -= buffer.OriginalSize;
buffer.eax_x_ram_is_hardware = true;
}
}
void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer)
{
if(al_buffer.eax_x_ram_is_hardware)
al_device.eax_x_ram_free_size += al_buffer.OriginalSize;
al_buffer.eax_x_ram_is_hardware = false;
}
#endif // ALSOFT_EAX
constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT | constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT |
AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)}; AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)};
@ -352,13 +432,12 @@ ALbuffer *AllocBuffer(ALCdevice *device)
{ {
auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(),
[](const BufferSubList &entry) noexcept -> bool [](const BufferSubList &entry) noexcept -> bool
{ return entry.FreeMask != 0; } { return entry.FreeMask != 0; });
);
auto lidx = static_cast<ALuint>(std::distance(device->BufferList.begin(), sublist)); auto lidx = static_cast<ALuint>(std::distance(device->BufferList.begin(), sublist));
auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask)); auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
ASSUME(slidx < 64);
ALbuffer *buffer{::new (sublist->Buffers + slidx) ALbuffer{}}; ALbuffer *buffer{al::construct_at(sublist->Buffers + slidx)};
/* Add 1 to avoid buffer ID 0. */ /* Add 1 to avoid buffer ID 0. */
buffer->id = ((lidx<<6) | slidx) + 1; buffer->id = ((lidx<<6) | slidx) + 1;
@ -370,6 +449,10 @@ ALbuffer *AllocBuffer(ALCdevice *device)
void FreeBuffer(ALCdevice *device, ALbuffer *buffer) void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
{ {
#ifdef ALSOFT_EAX
eax_x_ram_clear(*device, *buffer);
#endif // ALSOFT_EAX
const ALuint id{buffer->id - 1}; const ALuint id{buffer->id - 1};
const size_t lidx{id >> 6}; const size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f}; const ALuint slidx{id & 0x3f};
@ -453,46 +536,26 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
ALBuf->id); ALBuf->id);
/* Currently no channel configurations need to be converted. */ /* Currently no channel configurations need to be converted. */
FmtChannels DstChannels{FmtMono}; auto DstChannels = FmtFromUserFmt(SrcChannels);
switch(SrcChannels) if UNLIKELY(!DstChannels)
{
case UserFmtMono: DstChannels = FmtMono; break;
case UserFmtStereo: DstChannels = FmtStereo; break;
case UserFmtRear: DstChannels = FmtRear; break;
case UserFmtQuad: DstChannels = FmtQuad; break;
case UserFmtX51: DstChannels = FmtX51; break;
case UserFmtX61: DstChannels = FmtX61; break;
case UserFmtX71: DstChannels = FmtX71; break;
case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break;
case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break;
}
if UNLIKELY(static_cast<long>(SrcChannels) != static_cast<long>(DstChannels))
SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format");
/* IMA4 and MSADPCM convert to 16-bit short. */ /* IMA4 and MSADPCM convert to 16-bit short.
FmtType DstType{FmtUByte}; *
switch(SrcType) * TODO: Currently we can only map samples when they're not converted. To
{
case UserFmtUByte: DstType = FmtUByte; break;
case UserFmtShort: DstType = FmtShort; break;
case UserFmtFloat: DstType = FmtFloat; break;
case UserFmtDouble: DstType = FmtDouble; break;
case UserFmtAlaw: DstType = FmtAlaw; break;
case UserFmtMulaw: DstType = FmtMulaw; break;
case UserFmtIMA4: DstType = FmtShort; break;
case UserFmtMSADPCM: DstType = FmtShort; break;
}
/* TODO: Currently we can only map samples when they're not converted. To
* allow it would need some kind of double-buffering to hold onto a copy of * allow it would need some kind of double-buffering to hold onto a copy of
* the original data. * the original data.
*/ */
if((access&MAP_READ_WRITE_FLAGS)) if((access&MAP_READ_WRITE_FLAGS))
{ {
if UNLIKELY(static_cast<long>(SrcType) != static_cast<long>(DstType)) if UNLIKELY(SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM)
SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped", SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped",
NameFromUserFmtType(SrcType)); NameFromUserFmtType(SrcType));
} }
auto DstType = (SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM)
? al::make_optional(FmtShort) : FmtFromUserFmt(SrcType);
if UNLIKELY(!DstType)
SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format");
const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint unpackalign{ALBuf->UnpackAlign};
const ALuint align{SanitizeAlignment(SrcType, unpackalign)}; const ALuint align{SanitizeAlignment(SrcType, unpackalign)};
@ -500,13 +563,13 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %u for %s samples", SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %u for %s samples",
unpackalign, NameFromUserFmtType(SrcType)); unpackalign, NameFromUserFmtType(SrcType));
const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ? const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder :
ALBuf->UnpackAmbiOrder : 0}; (IsUHJ(*DstChannels) ? 1 : 0)};
if((access&AL_PRESERVE_DATA_BIT_SOFT)) if((access&AL_PRESERVE_DATA_BIT_SOFT))
{ {
/* Can only preserve data with the same format and alignment. */ /* Can only preserve data with the same format and alignment. */
if UNLIKELY(ALBuf->mChannels != DstChannels || ALBuf->OriginalType != SrcType) if UNLIKELY(ALBuf->mChannels != *DstChannels || ALBuf->OriginalType != SrcType)
SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format"); SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format");
if UNLIKELY(ALBuf->OriginalAlign != align) if UNLIKELY(ALBuf->OriginalAlign != align)
SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment"); SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment");
@ -534,13 +597,23 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
/* Convert the sample frames to the number of bytes needed for internal /* Convert the sample frames to the number of bytes needed for internal
* storage. * storage.
*/ */
ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)}; ALuint NumChannels{ChannelsFromFmt(*DstChannels, ambiorder)};
ALuint FrameSize{NumChannels * BytesFromFmt(DstType)}; ALuint FrameSize{NumChannels * BytesFromFmt(*DstType)};
if UNLIKELY(frames > std::numeric_limits<size_t>::max()/FrameSize) if UNLIKELY(frames > std::numeric_limits<size_t>::max()/FrameSize)
SETERR_RETURN(context, AL_OUT_OF_MEMORY,, SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
"Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize); "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize);
size_t newsize{static_cast<size_t>(frames) * FrameSize}; size_t newsize{static_cast<size_t>(frames) * FrameSize};
#ifdef ALSOFT_EAX
if(ALBuf->eax_x_ram_mode == AL_STORAGE_HARDWARE)
{
ALCdevice &device = *context->mALDevice;
if(!eax_x_ram_check_availability(device, *ALBuf, size))
SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
"Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size);
}
#endif
/* Round up to the next 16-byte multiple. This could reallocate only when /* Round up to the next 16-byte multiple. This could reallocate only when
* increasing or the new size is less than half the current, but then the * increasing or the new size is less than half the current, but then the
* buffer's AL_SIZE would not be very reliable for accounting buffer memory * buffer's AL_SIZE would not be very reliable for accounting buffer memory
@ -561,7 +634,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
if(SrcType == UserFmtIMA4) if(SrcType == UserFmtIMA4)
{ {
assert(DstType == FmtShort); assert(*DstType == FmtShort);
if(SrcData != nullptr && !ALBuf->mData.empty()) if(SrcData != nullptr && !ALBuf->mData.empty())
Convert_int16_ima4(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData, Convert_int16_ima4(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData,
NumChannels, frames, align); NumChannels, frames, align);
@ -569,7 +642,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
} }
else if(SrcType == UserFmtMSADPCM) else if(SrcType == UserFmtMSADPCM)
{ {
assert(DstType == FmtShort); assert(*DstType == FmtShort);
if(SrcData != nullptr && !ALBuf->mData.empty()) if(SrcData != nullptr && !ALBuf->mData.empty())
Convert_int16_msadpcm(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData, Convert_int16_msadpcm(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData,
NumChannels, frames, align); NumChannels, frames, align);
@ -577,7 +650,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
} }
else else
{ {
assert(static_cast<long>(SrcType) == static_cast<long>(DstType)); assert(DstType.has_value());
if(SrcData != nullptr && !ALBuf->mData.empty()) if(SrcData != nullptr && !ALBuf->mData.empty())
std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin()); std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin());
ALBuf->OriginalAlign = 1; ALBuf->OriginalAlign = 1;
@ -588,8 +661,8 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
ALBuf->Access = access; ALBuf->Access = access;
ALBuf->mSampleRate = static_cast<ALuint>(freq); ALBuf->mSampleRate = static_cast<ALuint>(freq);
ALBuf->mChannels = DstChannels; ALBuf->mChannels = *DstChannels;
ALBuf->mType = DstType; ALBuf->mType = *DstType;
ALBuf->mAmbiOrder = ambiorder; ALBuf->mAmbiOrder = ambiorder;
ALBuf->mCallback = nullptr; ALBuf->mCallback = nullptr;
@ -598,11 +671,16 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
ALBuf->mSampleLen = frames; ALBuf->mSampleLen = frames;
ALBuf->mLoopStart = 0; ALBuf->mLoopStart = 0;
ALBuf->mLoopEnd = ALBuf->mSampleLen; ALBuf->mLoopEnd = ALBuf->mSampleLen;
#ifdef ALSOFT_EAX
if(eax_g_is_enabled && ALBuf->eax_x_ram_mode != AL_STORAGE_ACCESSIBLE)
eax_x_ram_apply(*context->mALDevice, *ALBuf);
#endif
} }
/** Prepares the buffer to use the specified callback, using the specified format. */ /** Prepares the buffer to use the specified callback, using the specified format. */
void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
UserFmtChannels SrcChannels, UserFmtType SrcType, LPALBUFFERCALLBACKTYPESOFT callback, UserFmtChannels SrcChannels, UserFmtType SrcType, ALBUFFERCALLBACKTYPESOFT callback,
void *userptr) void *userptr)
{ {
if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0)
@ -610,43 +688,25 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
ALBuf->id); ALBuf->id);
/* Currently no channel configurations need to be converted. */ /* Currently no channel configurations need to be converted. */
FmtChannels DstChannels{FmtMono}; auto DstChannels = FmtFromUserFmt(SrcChannels);
switch(SrcChannels) if UNLIKELY(!DstChannels)
{
case UserFmtMono: DstChannels = FmtMono; break;
case UserFmtStereo: DstChannels = FmtStereo; break;
case UserFmtRear: DstChannels = FmtRear; break;
case UserFmtQuad: DstChannels = FmtQuad; break;
case UserFmtX51: DstChannels = FmtX51; break;
case UserFmtX61: DstChannels = FmtX61; break;
case UserFmtX71: DstChannels = FmtX71; break;
case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break;
case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break;
}
if UNLIKELY(static_cast<long>(SrcChannels) != static_cast<long>(DstChannels))
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format"); SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format");
/* IMA4 and MSADPCM convert to 16-bit short. Not supported with callbacks. */ /* IMA4 and MSADPCM convert to 16-bit short. Not supported with callbacks. */
FmtType DstType{FmtUByte}; auto DstType = FmtFromUserFmt(SrcType);
switch(SrcType) if UNLIKELY(!DstType)
{
case UserFmtUByte: DstType = FmtUByte; break;
case UserFmtShort: DstType = FmtShort; break;
case UserFmtFloat: DstType = FmtFloat; break;
case UserFmtDouble: DstType = FmtDouble; break;
case UserFmtAlaw: DstType = FmtAlaw; break;
case UserFmtMulaw: DstType = FmtMulaw; break;
case UserFmtIMA4: DstType = FmtShort; break;
case UserFmtMSADPCM: DstType = FmtShort; break;
}
if UNLIKELY(static_cast<long>(SrcType) != static_cast<long>(DstType))
SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format"); SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format");
const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ? const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder :
ALBuf->UnpackAmbiOrder : 0}; (IsUHJ(*DstChannels) ? 1 : 0)};
al::vector<al::byte,16>(FrameSizeFromFmt(DstChannels, DstType, ambiorder) * static constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad};
size_t{BufferLineSize + (MaxResamplerPadding>>1)}).swap(ALBuf->mData); al::vector<al::byte,16>(FrameSizeFromFmt(*DstChannels, *DstType, ambiorder) *
size_t{line_size}).swap(ALBuf->mData);
#ifdef ALSOFT_EAX
eax_x_ram_clear(*context->mALDevice, *ALBuf);
#endif
ALBuf->mCallback = callback; ALBuf->mCallback = callback;
ALBuf->mUserData = userptr; ALBuf->mUserData = userptr;
@ -657,8 +717,8 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
ALBuf->Access = 0; ALBuf->Access = 0;
ALBuf->mSampleRate = static_cast<ALuint>(freq); ALBuf->mSampleRate = static_cast<ALuint>(freq);
ALBuf->mChannels = DstChannels; ALBuf->mChannels = *DstChannels;
ALBuf->mType = DstType; ALBuf->mType = *DstType;
ALBuf->mAmbiOrder = ambiorder; ALBuf->mAmbiOrder = ambiorder;
ALBuf->mSampleLen = 0; ALBuf->mSampleLen = 0;
@ -675,7 +735,7 @@ al::optional<DecompResult> DecomposeUserFormat(ALenum format)
UserFmtChannels channels; UserFmtChannels channels;
UserFmtType type; UserFmtType type;
}; };
static const std::array<FormatMap,46> UserFmtList{{ static const std::array<FormatMap,55> UserFmtList{{
{ AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte }, { AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte },
{ AL_FORMAT_MONO16, UserFmtMono, UserFmtShort }, { AL_FORMAT_MONO16, UserFmtMono, UserFmtShort },
{ AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat }, { AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat },
@ -731,6 +791,18 @@ al::optional<DecompResult> DecomposeUserFormat(ALenum format)
{ AL_FORMAT_BFORMAT3D_16, UserFmtBFormat3D, UserFmtShort }, { AL_FORMAT_BFORMAT3D_16, UserFmtBFormat3D, UserFmtShort },
{ AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat }, { AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat },
{ AL_FORMAT_BFORMAT3D_MULAW, UserFmtBFormat3D, UserFmtMulaw }, { AL_FORMAT_BFORMAT3D_MULAW, UserFmtBFormat3D, UserFmtMulaw },
{ AL_FORMAT_UHJ2CHN8_SOFT, UserFmtUHJ2, UserFmtUByte },
{ AL_FORMAT_UHJ2CHN16_SOFT, UserFmtUHJ2, UserFmtShort },
{ AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, UserFmtUHJ2, UserFmtFloat },
{ AL_FORMAT_UHJ3CHN8_SOFT, UserFmtUHJ3, UserFmtUByte },
{ AL_FORMAT_UHJ3CHN16_SOFT, UserFmtUHJ3, UserFmtShort },
{ AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, UserFmtUHJ3, UserFmtFloat },
{ AL_FORMAT_UHJ4CHN8_SOFT, UserFmtUHJ4, UserFmtUByte },
{ AL_FORMAT_UHJ4CHN16_SOFT, UserFmtUHJ4, UserFmtShort },
{ AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, UserFmtUHJ4, UserFmtFloat },
}}; }};
for(const auto &fmt : UserFmtList) for(const auto &fmt : UserFmtList)
@ -754,7 +826,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Generating %d buffers", n); context->setError(AL_INVALID_VALUE, "Generating %d buffers", n);
if UNLIKELY(n <= 0) return; if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if(!EnsureBuffers(device, static_cast<ALuint>(n))) if(!EnsureBuffers(device, static_cast<ALuint>(n)))
{ {
@ -794,7 +866,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n); context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n);
if UNLIKELY(n <= 0) return; if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
/* First try to find any buffers that are invalid or in-use. */ /* First try to find any buffers that are invalid or in-use. */
@ -834,7 +906,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if LIKELY(context) if LIKELY(context)
{ {
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if(!buffer || LookupBuffer(device, buffer)) if(!buffer || LookupBuffer(device, buffer))
return AL_TRUE; return AL_TRUE;
@ -855,7 +927,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -877,8 +949,10 @@ START_API_FUNC
if UNLIKELY(!usrfmt) if UNLIKELY(!usrfmt)
context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
else else
{
LoadData(context.get(), albuf, freq, static_cast<ALuint>(size), usrfmt->channels, LoadData(context.get(), albuf, freq, static_cast<ALuint>(size), usrfmt->channels,
usrfmt->type, static_cast<const al::byte*>(data), flags); usrfmt->type, static_cast<const al::byte*>(data), flags);
}
} }
} }
END_API_FUNC END_API_FUNC
@ -889,7 +963,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return nullptr; if UNLIKELY(!context) return nullptr;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -942,7 +1016,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -965,7 +1039,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -997,7 +1071,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1127,7 +1201,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1147,7 +1221,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1166,7 +1240,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1188,7 +1262,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1250,7 +1324,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1283,7 +1357,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1321,7 +1395,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1343,7 +1417,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1371,7 +1445,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1393,7 +1467,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
if UNLIKELY(!albuf) if UNLIKELY(!albuf)
@ -1450,7 +1524,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@ -1488,7 +1562,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
if UNLIKELY(!albuf) if UNLIKELY(!albuf)
@ -1510,13 +1584,13 @@ END_API_FUNC
AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq,
LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags) ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr)
START_API_FUNC START_API_FUNC
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1526,8 +1600,6 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq);
else if UNLIKELY(callback == nullptr) else if UNLIKELY(callback == nullptr)
context->setError(AL_INVALID_VALUE, "NULL callback"); context->setError(AL_INVALID_VALUE, "NULL callback");
else if UNLIKELY(flags != 0)
context->setError(AL_INVALID_VALUE, "Invalid callback flags 0x%x", flags);
else else
{ {
auto usrfmt = DecomposeUserFormat(format); auto usrfmt = DecomposeUserFormat(format);
@ -1546,7 +1618,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer); ALbuffer *albuf = LookupBuffer(device, buffer);
if UNLIKELY(!albuf) if UNLIKELY(!albuf)
@ -1574,7 +1646,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@ -1602,7 +1674,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock}; std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr) if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@ -1630,3 +1702,161 @@ BufferSubList::~BufferSubList()
al_free(Buffers); al_free(Buffers);
Buffers = nullptr; Buffers = nullptr;
} }
#ifdef ALSOFT_EAX
FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint* buffers, ALint value)
START_API_FUNC
{
#define EAX_PREFIX "[EAXSetBufferMode] "
const auto context = ContextRef{GetContextRef()};
if(!context)
{
ERR(EAX_PREFIX "%s\n", "No current context.");
return ALC_FALSE;
}
if(!eax_g_is_enabled)
{
context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled.");
return ALC_FALSE;
}
switch(value)
{
case AL_STORAGE_AUTOMATIC:
case AL_STORAGE_HARDWARE:
case AL_STORAGE_ACCESSIBLE:
break;
default:
context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value);
return ALC_FALSE;
}
if(n == 0)
return ALC_TRUE;
if(n < 0)
{
context->setError(AL_INVALID_VALUE, EAX_PREFIX "Buffer count %d out of range", n);
return ALC_FALSE;
}
if(!buffers)
{
context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Null AL buffers");
return ALC_FALSE;
}
auto device = context->mALDevice.get();
std::lock_guard<std::mutex> device_lock{device->BufferLock};
size_t total_needed{0};
// Validate the buffers.
//
for(auto i = 0;i < n;++i)
{
const auto buffer = buffers[i];
if(buffer == AL_NONE)
continue;
const auto al_buffer = LookupBuffer(device, buffer);
if (!al_buffer)
{
ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer);
return ALC_FALSE;
}
/* TODO: Is the store location allowed to change for in-use buffers, or
* only when not set/queued on a source?
*/
if(value == AL_STORAGE_HARDWARE && !al_buffer->eax_x_ram_is_hardware)
{
/* FIXME: This doesn't account for duplicate buffers. When the same
* buffer ID is specified multiple times in the provided list, it
* counts each instance as more memory that needs to fit in X-RAM.
*/
if(unlikely(std::numeric_limits<size_t>::max()-al_buffer->OriginalSize < total_needed))
{
context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Buffer size overflow (%u + %zu)\n",
al_buffer->OriginalSize, total_needed);
return ALC_FALSE;
}
total_needed += al_buffer->OriginalSize;
}
}
if(total_needed > device->eax_x_ram_free_size)
{
context->setError(AL_INVALID_ENUM, EAX_PREFIX "Out of X-RAM memory (need: %zu, avail: %u)",
total_needed, device->eax_x_ram_free_size);
return ALC_FALSE;
}
// Update the mode.
//
for(auto i = 0;i < n;++i)
{
const auto buffer = buffers[i];
if(buffer == AL_NONE)
continue;
const auto al_buffer = LookupBuffer(device, buffer);
assert(al_buffer);
if(value != AL_STORAGE_ACCESSIBLE)
eax_x_ram_apply(*device, *al_buffer);
else
eax_x_ram_clear(*device, *al_buffer);
al_buffer->eax_x_ram_mode = value;
}
return AL_TRUE;
#undef EAX_PREFIX
}
END_API_FUNC
FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint* pReserved)
START_API_FUNC
{
#define EAX_PREFIX "[EAXGetBufferMode] "
const auto context = ContextRef{GetContextRef()};
if(!context)
{
ERR(EAX_PREFIX "%s\n", "No current context.");
return AL_NONE;
}
if(!eax_g_is_enabled)
{
context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled.");
return AL_NONE;
}
if(pReserved)
{
context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Non-null reserved parameter");
return AL_NONE;
}
auto device = context->mALDevice.get();
std::lock_guard<std::mutex> device_lock{device->BufferLock};
const auto al_buffer = LookupBuffer(device, buffer);
if(!al_buffer)
{
context->setError(AL_INVALID_NAME, EAX_PREFIX "Invalid buffer ID %u", buffer);
return AL_NONE;
}
return al_buffer->eax_x_ram_mode;
#undef EAX_PREFIX
}
END_API_FUNC
#endif // ALSOFT_EAX

View file

@ -6,12 +6,15 @@
#include "AL/al.h" #include "AL/al.h"
#include "albyte.h" #include "albyte.h"
#include "alc/inprogext.h"
#include "almalloc.h" #include "almalloc.h"
#include "atomic.h" #include "atomic.h"
#include "buffer_storage.h" #include "core/buffer_storage.h"
#include "inprogext.h"
#include "vector.h" #include "vector.h"
#ifdef ALSOFT_EAX
#include "eax_x_ram.h"
#endif // ALSOFT_EAX
/* User formats */ /* User formats */
enum UserFmtType : unsigned char { enum UserFmtType : unsigned char {
@ -35,6 +38,9 @@ enum UserFmtChannels : unsigned char {
UserFmtX71 = FmtX71, UserFmtX71 = FmtX71,
UserFmtBFormat2D = FmtBFormat2D, UserFmtBFormat2D = FmtBFormat2D,
UserFmtBFormat3D = FmtBFormat3D, UserFmtBFormat3D = FmtBFormat3D,
UserFmtUHJ2 = FmtUHJ2,
UserFmtUHJ3 = FmtUHJ3,
UserFmtUHJ4 = FmtUHJ4,
}; };
@ -65,6 +71,11 @@ struct ALbuffer : public BufferStorage {
ALuint id{0}; ALuint id{0};
DISABLE_ALLOC() DISABLE_ALLOC()
#ifdef ALSOFT_EAX
ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC};
bool eax_x_ram_is_hardware{};
#endif // ALSOFT_EAX
}; };
#endif #endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,324 @@
#include "config.h"
#include "al/eax_eax_call.h"
#include "al/eax_exception.h"
namespace {
constexpr auto deferred_flag = 0x80000000U;
class EaxEaxCallException :
public EaxException
{
public:
explicit EaxEaxCallException(
const char* message)
:
EaxException{"EAX_EAX_CALL", message}
{
}
}; // EaxEaxCallException
} // namespace
EaxEaxCall::EaxEaxCall(
bool is_get,
const GUID& property_set_guid,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size)
: is_get_{is_get}, version_{0}, property_set_id_{EaxEaxCallPropertySetId::none}
, property_id_{property_id & ~deferred_flag}, property_source_id_{property_source_id}
, property_buffer_{property_buffer}, property_size_{property_size}
{
if (false)
{
}
else if (property_set_guid == EAXPROPERTYID_EAX40_Context)
{
version_ = 4;
property_set_id_ = EaxEaxCallPropertySetId::context;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_Context)
{
version_ = 5;
property_set_id_ = EaxEaxCallPropertySetId::context;
}
else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties)
{
version_ = 2;
fx_slot_index_ = 0u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
property_id_ = convert_eax_v2_0_listener_property_id(property_id_);
}
else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties)
{
version_ = 3;
fx_slot_index_ = 0u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0)
{
version_ = 4;
fx_slot_index_ = 0u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0)
{
version_ = 5;
fx_slot_index_ = 0u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1)
{
version_ = 4;
fx_slot_index_ = 1u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1)
{
version_ = 5;
fx_slot_index_ = 1u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2)
{
version_ = 4;
fx_slot_index_ = 2u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2)
{
version_ = 5;
fx_slot_index_ = 2u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3)
{
version_ = 4;
fx_slot_index_ = 3u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3)
{
version_ = 5;
fx_slot_index_ = 3u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties)
{
version_ = 2;
property_set_id_ = EaxEaxCallPropertySetId::source;
property_id_ = convert_eax_v2_0_buffer_property_id(property_id_);
}
else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties)
{
version_ = 3;
property_set_id_ = EaxEaxCallPropertySetId::source;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_Source)
{
version_ = 4;
property_set_id_ = EaxEaxCallPropertySetId::source;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_Source)
{
version_ = 5;
property_set_id_ = EaxEaxCallPropertySetId::source;
}
else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties)
{
version_ = 1;
fx_slot_index_ = 0u;
property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
}
else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties)
{
version_ = 1;
property_set_id_ = EaxEaxCallPropertySetId::source;
}
else
{
fail("Unsupported property set id.");
}
if (version_ < 1 || version_ > 5)
{
fail("EAX version out of range.");
}
if(!(property_id&deferred_flag))
{
if(property_set_id_ != EaxEaxCallPropertySetId::fx_slot && property_id_ != 0)
{
if (!property_buffer)
{
fail("Null property buffer.");
}
if (property_size == 0)
{
fail("Empty property.");
}
}
}
if(property_set_id_ == EaxEaxCallPropertySetId::source && property_source_id_ == 0)
{
fail("Null AL source id.");
}
if (property_set_id_ == EaxEaxCallPropertySetId::fx_slot)
{
if (property_id_ < EAXFXSLOT_NONE)
{
property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
}
}
}
[[noreturn]]
void EaxEaxCall::fail(
const char* message)
{
throw EaxEaxCallException{message};
}
ALuint EaxEaxCall::convert_eax_v2_0_listener_property_id(
ALuint property_id)
{
switch (property_id)
{
case DSPROPERTY_EAX20LISTENER_NONE:
return EAXREVERB_NONE;
case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS:
return EAXREVERB_ALLPARAMETERS;
case DSPROPERTY_EAX20LISTENER_ROOM:
return EAXREVERB_ROOM;
case DSPROPERTY_EAX20LISTENER_ROOMHF:
return EAXREVERB_ROOMHF;
case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR:
return EAXREVERB_ROOMROLLOFFFACTOR;
case DSPROPERTY_EAX20LISTENER_DECAYTIME:
return EAXREVERB_DECAYTIME;
case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO:
return EAXREVERB_DECAYHFRATIO;
case DSPROPERTY_EAX20LISTENER_REFLECTIONS:
return EAXREVERB_REFLECTIONS;
case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY:
return EAXREVERB_REFLECTIONSDELAY;
case DSPROPERTY_EAX20LISTENER_REVERB:
return EAXREVERB_REVERB;
case DSPROPERTY_EAX20LISTENER_REVERBDELAY:
return EAXREVERB_REVERBDELAY;
case DSPROPERTY_EAX20LISTENER_ENVIRONMENT:
return EAXREVERB_ENVIRONMENT;
case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE:
return EAXREVERB_ENVIRONMENTSIZE;
case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION:
return EAXREVERB_ENVIRONMENTDIFFUSION;
case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF:
return EAXREVERB_AIRABSORPTIONHF;
case DSPROPERTY_EAX20LISTENER_FLAGS:
return EAXREVERB_FLAGS;
default:
fail("Unsupported EAX 2.0 listener property id.");
}
}
ALuint EaxEaxCall::convert_eax_v2_0_buffer_property_id(
ALuint property_id)
{
switch (property_id)
{
case DSPROPERTY_EAX20BUFFER_NONE:
return EAXSOURCE_NONE;
case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
return EAXSOURCE_ALLPARAMETERS;
case DSPROPERTY_EAX20BUFFER_DIRECT:
return EAXSOURCE_DIRECT;
case DSPROPERTY_EAX20BUFFER_DIRECTHF:
return EAXSOURCE_DIRECTHF;
case DSPROPERTY_EAX20BUFFER_ROOM:
return EAXSOURCE_ROOM;
case DSPROPERTY_EAX20BUFFER_ROOMHF:
return EAXSOURCE_ROOMHF;
case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
return EAXSOURCE_ROOMROLLOFFFACTOR;
case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
return EAXSOURCE_OBSTRUCTION;
case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
return EAXSOURCE_OBSTRUCTIONLFRATIO;
case DSPROPERTY_EAX20BUFFER_OCCLUSION:
return EAXSOURCE_OCCLUSION;
case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
return EAXSOURCE_OCCLUSIONLFRATIO;
case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
return EAXSOURCE_OCCLUSIONROOMRATIO;
case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
return EAXSOURCE_OUTSIDEVOLUMEHF;
case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
return EAXSOURCE_AIRABSORPTIONFACTOR;
case DSPROPERTY_EAX20BUFFER_FLAGS:
return EAXSOURCE_FLAGS;
default:
fail("Unsupported EAX 2.0 buffer property id.");
}
}
EaxEaxCall create_eax_call(
bool is_get,
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size)
{
if(!property_set_id)
throw EaxEaxCallException{"Null property set ID."};
return EaxEaxCall{
is_get,
*property_set_id,
property_id,
property_source_id,
property_buffer,
property_size
};
}

View file

@ -0,0 +1,117 @@
#ifndef EAX_EAX_CALL_INCLUDED
#define EAX_EAX_CALL_INCLUDED
#include "AL/al.h"
#include "alspan.h"
#include "eax_api.h"
#include "eax_fx_slot_index.h"
enum class EaxEaxCallPropertySetId
{
none,
context,
fx_slot,
source,
fx_slot_effect,
}; // EaxEaxCallPropertySetId
class EaxEaxCall
{
public:
EaxEaxCall(
bool is_get,
const GUID& property_set_guid,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size);
bool is_get() const noexcept { return is_get_; }
int get_version() const noexcept { return version_; }
EaxEaxCallPropertySetId get_property_set_id() const noexcept { return property_set_id_; }
ALuint get_property_id() const noexcept { return property_id_; }
ALuint get_property_al_name() const noexcept { return property_source_id_; }
EaxFxSlotIndex get_fx_slot_index() const noexcept { return fx_slot_index_; }
template<
typename TException,
typename TValue
>
TValue& get_value() const
{
if (property_size_ < static_cast<ALuint>(sizeof(TValue)))
{
throw TException{"Property buffer too small."};
}
return *static_cast<TValue*>(property_buffer_);
}
template<
typename TException,
typename TValue
>
al::span<TValue> get_values() const
{
if (property_size_ < static_cast<ALuint>(sizeof(TValue)))
{
throw TException{"Property buffer too small."};
}
const auto count = property_size_ / sizeof(TValue);
return al::span<TValue>{static_cast<TValue*>(property_buffer_), count};
}
template<
typename TException,
typename TValue
>
void set_value(
const TValue& value) const
{
get_value<TException, TValue>() = value;
}
private:
const bool is_get_;
int version_;
EaxFxSlotIndex fx_slot_index_;
EaxEaxCallPropertySetId property_set_id_;
ALuint property_id_;
const ALuint property_source_id_;
ALvoid*const property_buffer_;
const ALuint property_size_;
[[noreturn]]
static void fail(
const char* message);
static ALuint convert_eax_v2_0_listener_property_id(
ALuint property_id);
static ALuint convert_eax_v2_0_buffer_property_id(
ALuint property_id);
}; // EaxEaxCall
EaxEaxCall create_eax_call(
bool is_get,
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size);
#endif // !EAX_EAX_CALL_INCLUDED

View file

@ -0,0 +1,3 @@
#include "config.h"
#include "eax_effect.h"

View file

@ -0,0 +1,44 @@
#ifndef EAX_EFFECT_INCLUDED
#define EAX_EFFECT_INCLUDED
#include <memory>
#include "AL/al.h"
#include "core/effects/base.h"
#include "eax_eax_call.h"
class EaxEffect
{
public:
EaxEffect(ALenum type) : al_effect_type_{type} { }
virtual ~EaxEffect() = default;
const ALenum al_effect_type_;
EffectProps al_effect_props_{};
virtual void dispatch(const EaxEaxCall& eax_call) = 0;
// Returns "true" if any immediated property was changed.
// [[nodiscard]]
virtual bool apply_deferred() = 0;
}; // EaxEffect
using EaxEffectUPtr = std::unique_ptr<EaxEffect>;
EaxEffectUPtr eax_create_eax_null_effect();
EaxEffectUPtr eax_create_eax_chorus_effect();
EaxEffectUPtr eax_create_eax_distortion_effect();
EaxEffectUPtr eax_create_eax_echo_effect();
EaxEffectUPtr eax_create_eax_flanger_effect();
EaxEffectUPtr eax_create_eax_frequency_shifter_effect();
EaxEffectUPtr eax_create_eax_vocal_morpher_effect();
EaxEffectUPtr eax_create_eax_pitch_shifter_effect();
EaxEffectUPtr eax_create_eax_ring_modulator_effect();
EaxEffectUPtr eax_create_eax_auto_wah_effect();
EaxEffectUPtr eax_create_eax_compressor_effect();
EaxEffectUPtr eax_create_eax_equalizer_effect();
EaxEffectUPtr eax_create_eax_reverb_effect();
#endif // !EAX_EFFECT_INCLUDED

View file

@ -0,0 +1,63 @@
#include "config.h"
#include "eax_exception.h"
#include <cassert>
#include <string>
EaxException::EaxException(
const char* context,
const char* message)
:
std::runtime_error{make_message(context, message)}
{
}
std::string EaxException::make_message(
const char* context,
const char* message)
{
const auto context_size = (context ? std::string::traits_type::length(context) : 0);
const auto has_contex = (context_size > 0);
const auto message_size = (message ? std::string::traits_type::length(message) : 0);
const auto has_message = (message_size > 0);
if (!has_contex && !has_message)
{
return std::string{};
}
static constexpr char left_prefix[] = "[";
const auto left_prefix_size = std::string::traits_type::length(left_prefix);
static constexpr char right_prefix[] = "] ";
const auto right_prefix_size = std::string::traits_type::length(right_prefix);
const auto what_size =
(
has_contex ?
left_prefix_size + context_size + right_prefix_size :
0) +
message_size +
1;
auto what = std::string{};
what.reserve(what_size);
if (has_contex)
{
what.append(left_prefix, left_prefix_size);
what.append(context, context_size);
what.append(right_prefix, right_prefix_size);
}
if (has_message)
{
what.append(message, message_size);
}
return what;
}

View file

@ -0,0 +1,25 @@
#ifndef EAX_EXCEPTION_INCLUDED
#define EAX_EXCEPTION_INCLUDED
#include <stdexcept>
#include <string>
class EaxException :
public std::runtime_error
{
public:
EaxException(
const char* context,
const char* message);
private:
static std::string make_message(
const char* context,
const char* message);
}; // EaxException
#endif // !EAX_EXCEPTION_INCLUDED

View file

@ -0,0 +1,71 @@
#include "config.h"
#include "eax_fx_slot_index.h"
#include "eax_exception.h"
namespace
{
class EaxFxSlotIndexException :
public EaxException
{
public:
explicit EaxFxSlotIndexException(
const char* message)
:
EaxException{"EAX_FX_SLOT_INDEX", message}
{
}
}; // EaxFxSlotIndexException
} // namespace
void EaxFxSlotIndex::set(EaxFxSlotIndexValue index)
{
if(index >= EaxFxSlotIndexValue{EAX_MAX_FXSLOTS})
fail("Index out of range.");
emplace(index);
}
void EaxFxSlotIndex::set(const GUID &guid)
{
if (false)
{
}
else if (guid == EAX_NULL_GUID)
{
reset();
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot0 || guid == EAXPROPERTYID_EAX50_FXSlot0)
{
emplace(0u);
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot1 || guid == EAXPROPERTYID_EAX50_FXSlot1)
{
emplace(1u);
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot2 || guid == EAXPROPERTYID_EAX50_FXSlot2)
{
emplace(2u);
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot3 || guid == EAXPROPERTYID_EAX50_FXSlot3)
{
emplace(3u);
}
else
{
fail("Unsupported GUID.");
}
}
[[noreturn]]
void EaxFxSlotIndex::fail(const char* message)
{
throw EaxFxSlotIndexException{message};
}

View file

@ -0,0 +1,41 @@
#ifndef EAX_FX_SLOT_INDEX_INCLUDED
#define EAX_FX_SLOT_INDEX_INCLUDED
#include <cstddef>
#include "aloptional.h"
#include "eax_api.h"
using EaxFxSlotIndexValue = std::size_t;
class EaxFxSlotIndex : public al::optional<EaxFxSlotIndexValue>
{
public:
using al::optional<EaxFxSlotIndexValue>::optional;
EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; }
EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; }
void set(EaxFxSlotIndexValue index);
void set(const GUID& guid);
private:
[[noreturn]]
static void fail(const char *message);
}; // EaxFxSlotIndex
inline bool operator==(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept
{
if(lhs.has_value() != rhs.has_value())
return false;
if(lhs.has_value())
return *lhs == *rhs;
return true;
}
inline bool operator!=(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept
{ return !(lhs == rhs); }
#endif // !EAX_FX_SLOT_INDEX_INCLUDED

View file

@ -0,0 +1,84 @@
#include "config.h"
#include "eax_fx_slots.h"
#include <array>
#include "eax_exception.h"
#include "eax_api.h"
namespace
{
class EaxFxSlotsException :
public EaxException
{
public:
explicit EaxFxSlotsException(
const char* message)
:
EaxException{"EAX_FX_SLOTS", message}
{
}
}; // EaxFxSlotsException
} // namespace
void EaxFxSlots::initialize(
ALCcontext& al_context)
{
initialize_fx_slots(al_context);
}
void EaxFxSlots::uninitialize() noexcept
{
for (auto& fx_slot : fx_slots_)
{
fx_slot = nullptr;
}
}
const ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) const
{
if(!index.has_value())
fail("Empty index.");
return *fx_slots_[index.value()];
}
ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index)
{
if(!index.has_value())
fail("Empty index.");
return *fx_slots_[index.value()];
}
void EaxFxSlots::unlock_legacy() noexcept
{
fx_slots_[0]->eax_unlock_legacy();
fx_slots_[1]->eax_unlock_legacy();
}
[[noreturn]]
void EaxFxSlots::fail(
const char* message)
{
throw EaxFxSlotsException{message};
}
void EaxFxSlots::initialize_fx_slots(
ALCcontext& al_context)
{
auto fx_slot_index = EaxFxSlotIndexValue{};
for (auto& fx_slot : fx_slots_)
{
fx_slot = eax_create_al_effect_slot(al_context);
fx_slot->eax_initialize(al_context, fx_slot_index);
fx_slot_index += 1;
}
}

View file

@ -0,0 +1,54 @@
#ifndef EAX_FX_SLOTS_INCLUDED
#define EAX_FX_SLOTS_INCLUDED
#include <array>
#include "al/auxeffectslot.h"
#include "eax_api.h"
#include "eax_fx_slot_index.h"
class EaxFxSlots
{
public:
void initialize(
ALCcontext& al_context);
void uninitialize() noexcept;
void commit()
{
for(auto& fx_slot : fx_slots_)
fx_slot->eax_commit();
}
const ALeffectslot& get(
EaxFxSlotIndex index) const;
ALeffectslot& get(
EaxFxSlotIndex index);
void unlock_legacy() noexcept;
private:
using Items = std::array<EaxAlEffectSlotUPtr, EAX_MAX_FXSLOTS>;
Items fx_slots_{};
[[noreturn]]
static void fail(
const char* message);
void initialize_fx_slots(
ALCcontext& al_context);
}; // EaxFxSlots
#endif // !EAX_FX_SLOTS_INCLUDED

View file

@ -0,0 +1,21 @@
#include "config.h"
#include "eax_globals.h"
bool eax_g_is_enabled = true;
const char eax1_ext_name[] = "EAX";
const char eax2_ext_name[] = "EAX2.0";
const char eax3_ext_name[] = "EAX3.0";
const char eax4_ext_name[] = "EAX4.0";
const char eax5_ext_name[] = "EAX5.0";
const char eax_x_ram_ext_name[] = "EAX-RAM";
const char eax_eax_set_func_name[] = "EAXSet";
const char eax_eax_get_func_name[] = "EAXGet";
const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode";
const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode";

View file

@ -0,0 +1,22 @@
#ifndef EAX_GLOBALS_INCLUDED
#define EAX_GLOBALS_INCLUDED
extern bool eax_g_is_enabled;
extern const char eax1_ext_name[];
extern const char eax2_ext_name[];
extern const char eax3_ext_name[];
extern const char eax4_ext_name[];
extern const char eax5_ext_name[];
extern const char eax_x_ram_ext_name[];
extern const char eax_eax_set_func_name[];
extern const char eax_eax_get_func_name[];
extern const char eax_eax_set_buffer_mode_func_name[];
extern const char eax_eax_get_buffer_mode_func_name[];
#endif // !EAX_GLOBALS_INCLUDED

View file

@ -0,0 +1,36 @@
#include "config.h"
#include "eax_utils.h"
#include <cassert>
#include <exception>
#include "core/logging.h"
void eax_log_exception(
const char* message) noexcept
{
const auto exception_ptr = std::current_exception();
assert(exception_ptr);
if (message)
{
ERR("%s\n", message);
}
try
{
std::rethrow_exception(exception_ptr);
}
catch (const std::exception& ex)
{
const auto ex_message = ex.what();
ERR("%s\n", ex_message);
}
catch (...)
{
ERR("%s\n", "Generic exception.");
}
}

View file

@ -0,0 +1,132 @@
#ifndef EAX_UTILS_INCLUDED
#define EAX_UTILS_INCLUDED
#include <algorithm>
#include <cstdint>
#include <string>
#include <type_traits>
struct EaxAlLowPassParam
{
float gain;
float gain_hf;
}; // EaxAlLowPassParam
void eax_log_exception(
const char* message = nullptr) noexcept;
template<
typename TException,
typename TValue
>
void eax_validate_range(
const char* value_name,
const TValue& value,
const TValue& min_value,
const TValue& max_value)
{
if (value >= min_value && value <= max_value)
{
return;
}
const auto message =
std::string{value_name} +
" out of range (value: " +
std::to_string(value) + "; min: " +
std::to_string(min_value) + "; max: " +
std::to_string(max_value) + ").";
throw TException{message.c_str()};
}
namespace detail
{
template<
typename T
>
struct EaxIsBitFieldStruct
{
private:
using yes = std::true_type;
using no = std::false_type;
template<
typename U
>
static auto test(int) -> decltype(std::declval<typename U::EaxIsBitFieldStruct>(), yes{});
template<
typename
>
static no test(...);
public:
static constexpr auto value = std::is_same<decltype(test<T>(0)), yes>::value;
}; // EaxIsBitFieldStruct
template<
typename T,
typename TValue
>
inline bool eax_bit_fields_are_equal(
const T& lhs,
const T& rhs) noexcept
{
static_assert(sizeof(T) == sizeof(TValue), "Invalid type size.");
return reinterpret_cast<const TValue&>(lhs) == reinterpret_cast<const TValue&>(rhs);
}
} // namespace detail
template<
typename T,
std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
>
inline bool operator==(
const T& lhs,
const T& rhs) noexcept
{
using Value = std::conditional_t<
sizeof(T) == 1,
std::uint8_t,
std::conditional_t<
sizeof(T) == 2,
std::uint16_t,
std::conditional_t<
sizeof(T) == 4,
std::uint32_t,
void
>
>
>;
static_assert(!std::is_same<Value, void>::value, "Unsupported type.");
return detail::eax_bit_fields_are_equal<T, Value>(lhs, rhs);
}
template<
typename T,
std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
>
inline bool operator!=(
const T& lhs,
const T& rhs) noexcept
{
return !(lhs == rhs);
}
#endif // !EAX_UTILS_INCLUDED

View file

@ -0,0 +1,3 @@
#include "config.h"
#include "eax_x_ram.h"

View file

@ -0,0 +1,38 @@
#ifndef EAX_X_RAM_INCLUDED
#define EAX_X_RAM_INCLUDED
#include "AL/al.h"
constexpr auto eax_x_ram_min_size = ALsizei{};
constexpr auto eax_x_ram_max_size = ALsizei{64 * 1'024 * 1'024};
constexpr auto AL_EAX_RAM_SIZE = ALenum{0x202201};
constexpr auto AL_EAX_RAM_FREE = ALenum{0x202202};
constexpr auto AL_STORAGE_AUTOMATIC = ALenum{0x202203};
constexpr auto AL_STORAGE_HARDWARE = ALenum{0x202204};
constexpr auto AL_STORAGE_ACCESSIBLE = ALenum{0x202205};
constexpr auto AL_EAX_RAM_SIZE_NAME = "AL_EAX_RAM_SIZE";
constexpr auto AL_EAX_RAM_FREE_NAME = "AL_EAX_RAM_FREE";
constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC";
constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE";
constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE";
ALboolean AL_APIENTRY EAXSetBufferMode(
ALsizei n,
const ALuint* buffers,
ALint value);
ALenum AL_APIENTRY EAXGetBufferMode(
ALuint buffer,
ALint* pReserved);
#endif // !EAX_X_RAM_INCLUDED

View file

@ -39,17 +39,23 @@
#include "AL/efx.h" #include "AL/efx.h"
#include "albit.h" #include "albit.h"
#include "alcmain.h" #include "alc/context.h"
#include "alcontext.h" #include "alc/device.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alstring.h" #include "alstring.h"
#include "core/except.h" #include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "effects/base.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "vector.h" #include "vector.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "eax_exception.h"
#endif // ALSOFT_EAX
const EffectList gEffectList[16]{ const EffectList gEffectList[16]{
{ "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
@ -181,12 +187,12 @@ ALeffect *AllocEffect(ALCdevice *device)
{ {
auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
[](const EffectSubList &entry) noexcept -> bool [](const EffectSubList &entry) noexcept -> bool
{ return entry.FreeMask != 0; } { return entry.FreeMask != 0; });
);
auto lidx = static_cast<ALuint>(std::distance(device->EffectList.begin(), sublist)); auto lidx = static_cast<ALuint>(std::distance(device->EffectList.begin(), sublist));
auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask)); auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
ASSUME(slidx < 64);
ALeffect *effect{::new (sublist->Effects + slidx) ALeffect{}}; ALeffect *effect{al::construct_at(sublist->Effects + slidx)};
InitEffectParams(effect, AL_EFFECT_NULL); InitEffectParams(effect, AL_EFFECT_NULL);
/* Add 1 to avoid effect ID 0. */ /* Add 1 to avoid effect ID 0. */
@ -233,7 +239,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Generating %d effects", n); context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
if UNLIKELY(n <= 0) return; if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
if(!EnsureEffects(device, static_cast<ALuint>(n))) if(!EnsureEffects(device, static_cast<ALuint>(n)))
{ {
@ -273,7 +279,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n); context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
if UNLIKELY(n <= 0) return; if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
/* First try to find any effects that are invalid. */ /* First try to find any effects that are invalid. */
@ -304,7 +310,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if LIKELY(context) if LIKELY(context)
{ {
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
if(!effect || LookupEffect(device, effect)) if(!effect || LookupEffect(device, effect))
return AL_TRUE; return AL_TRUE;
@ -319,7 +325,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
@ -369,7 +375,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
@ -392,7 +398,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
@ -415,7 +421,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)}; ALeffect *aleffect{LookupEffect(device, effect)};
@ -438,7 +444,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
@ -470,7 +476,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
@ -493,7 +499,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
@ -516,7 +522,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)}; const ALeffect *aleffect{LookupEffect(device, effect)};
@ -744,3 +750,16 @@ void LoadReverbPreset(const char *name, ALeffect *effect)
WARN("Reverb preset '%s' not found\n", name); WARN("Reverb preset '%s' not found\n", name);
} }
bool IsValidEffectType(ALenum type) noexcept
{
if(type == AL_EFFECT_NULL)
return true;
for(const auto &effect_item : gEffectList)
{
if(type == effect_item.val && !DisabledEffects[effect_item.type])
return true;
}
return false;
}

View file

@ -57,4 +57,6 @@ void InitEffect(ALeffect *effect);
void LoadReverbPreset(const char *name, ALeffect *effect); void LoadReverbPreset(const char *name, ALeffect *effect);
bool IsValidEffectType(ALenum type) noexcept;
#endif #endif

View file

@ -8,9 +8,17 @@
#include "AL/efx.h" #include "AL/efx.h"
#include "effects/base.h" #include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
void Autowah_setParamf(EffectProps *props, ALenum param, float val) void Autowah_setParamf(EffectProps *props, ALenum param, float val)
@ -107,3 +115,434 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Autowah); DEFINE_ALEFFECT_VTABLE(Autowah);
const EffectProps AutowahEffectProps{genDefaultProps()}; const EffectProps AutowahEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxAutoWahEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxAutoWahEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxAutoWahEffectDirtyFlagsValue flAttackTime : 1;
EaxAutoWahEffectDirtyFlagsValue flReleaseTime : 1;
EaxAutoWahEffectDirtyFlagsValue lResonance : 1;
EaxAutoWahEffectDirtyFlagsValue lPeakLevel : 1;
}; // EaxAutoWahEffectDirtyFlags
class EaxAutoWahEffect final :
public EaxEffect
{
public:
EaxAutoWahEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXAUTOWAHPROPERTIES eax_{};
EAXAUTOWAHPROPERTIES eax_d_{};
EaxAutoWahEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_attack_time();
void set_efx_release_time();
void set_efx_resonance();
void set_efx_peak_gain();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_attack_time(
float flAttackTime);
void validate_release_time(
float flReleaseTime);
void validate_resonance(
long lResonance);
void validate_peak_level(
long lPeakLevel);
void validate_all(
const EAXAUTOWAHPROPERTIES& eax_all);
void defer_attack_time(
float flAttackTime);
void defer_release_time(
float flReleaseTime);
void defer_resonance(
long lResonance);
void defer_peak_level(
long lPeakLevel);
void defer_all(
const EAXAUTOWAHPROPERTIES& eax_all);
void defer_attack_time(
const EaxEaxCall& eax_call);
void defer_release_time(
const EaxEaxCall& eax_call);
void defer_resonance(
const EaxEaxCall& eax_call);
void defer_peak_level(
const EaxEaxCall& eax_call);
void defer_all(
const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxAutoWahEffect
class EaxAutoWahEffectException :
public EaxException
{
public:
explicit EaxAutoWahEffectException(
const char* message)
:
EaxException{"EAX_AUTO_WAH_EFFECT", message}
{
}
}; // EaxAutoWahEffectException
EaxAutoWahEffect::EaxAutoWahEffect()
: EaxEffect{AL_EFFECT_AUTOWAH}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxAutoWahEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxAutoWahEffect::set_eax_defaults()
{
eax_.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
eax_.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
eax_.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
eax_.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
eax_d_ = eax_;
}
void EaxAutoWahEffect::set_efx_attack_time()
{
const auto attack_time = clamp(
eax_.flAttackTime,
AL_AUTOWAH_MIN_ATTACK_TIME,
AL_AUTOWAH_MAX_ATTACK_TIME);
al_effect_props_.Autowah.AttackTime = attack_time;
}
void EaxAutoWahEffect::set_efx_release_time()
{
const auto release_time = clamp(
eax_.flReleaseTime,
AL_AUTOWAH_MIN_RELEASE_TIME,
AL_AUTOWAH_MAX_RELEASE_TIME);
al_effect_props_.Autowah.ReleaseTime = release_time;
}
void EaxAutoWahEffect::set_efx_resonance()
{
const auto resonance = clamp(
level_mb_to_gain(static_cast<float>(eax_.lResonance)),
AL_AUTOWAH_MIN_RESONANCE,
AL_AUTOWAH_MAX_RESONANCE);
al_effect_props_.Autowah.Resonance = resonance;
}
void EaxAutoWahEffect::set_efx_peak_gain()
{
const auto peak_gain = clamp(
level_mb_to_gain(static_cast<float>(eax_.lPeakLevel)),
AL_AUTOWAH_MIN_PEAK_GAIN,
AL_AUTOWAH_MAX_PEAK_GAIN);
al_effect_props_.Autowah.PeakGain = peak_gain;
}
void EaxAutoWahEffect::set_efx_defaults()
{
set_efx_attack_time();
set_efx_release_time();
set_efx_resonance();
set_efx_peak_gain();
}
void EaxAutoWahEffect::get(const EaxEaxCall& eax_call)
{
switch (eax_call.get_property_id())
{
case EAXAUTOWAH_NONE:
break;
case EAXAUTOWAH_ALLPARAMETERS:
eax_call.set_value<EaxAutoWahEffectException>(eax_);
break;
case EAXAUTOWAH_ATTACKTIME:
eax_call.set_value<EaxAutoWahEffectException>(eax_.flAttackTime);
break;
case EAXAUTOWAH_RELEASETIME:
eax_call.set_value<EaxAutoWahEffectException>(eax_.flReleaseTime);
break;
case EAXAUTOWAH_RESONANCE:
eax_call.set_value<EaxAutoWahEffectException>(eax_.lResonance);
break;
case EAXAUTOWAH_PEAKLEVEL:
eax_call.set_value<EaxAutoWahEffectException>(eax_.lPeakLevel);
break;
default:
throw EaxAutoWahEffectException{"Unsupported property id."};
}
}
void EaxAutoWahEffect::validate_attack_time(
float flAttackTime)
{
eax_validate_range<EaxAutoWahEffectException>(
"Attack Time",
flAttackTime,
EAXAUTOWAH_MINATTACKTIME,
EAXAUTOWAH_MAXATTACKTIME);
}
void EaxAutoWahEffect::validate_release_time(
float flReleaseTime)
{
eax_validate_range<EaxAutoWahEffectException>(
"Release Time",
flReleaseTime,
EAXAUTOWAH_MINRELEASETIME,
EAXAUTOWAH_MAXRELEASETIME);
}
void EaxAutoWahEffect::validate_resonance(
long lResonance)
{
eax_validate_range<EaxAutoWahEffectException>(
"Resonance",
lResonance,
EAXAUTOWAH_MINRESONANCE,
EAXAUTOWAH_MAXRESONANCE);
}
void EaxAutoWahEffect::validate_peak_level(
long lPeakLevel)
{
eax_validate_range<EaxAutoWahEffectException>(
"Peak Level",
lPeakLevel,
EAXAUTOWAH_MINPEAKLEVEL,
EAXAUTOWAH_MAXPEAKLEVEL);
}
void EaxAutoWahEffect::validate_all(
const EAXAUTOWAHPROPERTIES& eax_all)
{
validate_attack_time(eax_all.flAttackTime);
validate_release_time(eax_all.flReleaseTime);
validate_resonance(eax_all.lResonance);
validate_peak_level(eax_all.lPeakLevel);
}
void EaxAutoWahEffect::defer_attack_time(
float flAttackTime)
{
eax_d_.flAttackTime = flAttackTime;
eax_dirty_flags_.flAttackTime = (eax_.flAttackTime != eax_d_.flAttackTime);
}
void EaxAutoWahEffect::defer_release_time(
float flReleaseTime)
{
eax_d_.flReleaseTime = flReleaseTime;
eax_dirty_flags_.flReleaseTime = (eax_.flReleaseTime != eax_d_.flReleaseTime);
}
void EaxAutoWahEffect::defer_resonance(
long lResonance)
{
eax_d_.lResonance = lResonance;
eax_dirty_flags_.lResonance = (eax_.lResonance != eax_d_.lResonance);
}
void EaxAutoWahEffect::defer_peak_level(
long lPeakLevel)
{
eax_d_.lPeakLevel = lPeakLevel;
eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel);
}
void EaxAutoWahEffect::defer_all(
const EAXAUTOWAHPROPERTIES& eax_all)
{
validate_all(eax_all);
defer_attack_time(eax_all.flAttackTime);
defer_release_time(eax_all.flReleaseTime);
defer_resonance(eax_all.lResonance);
defer_peak_level(eax_all.lPeakLevel);
}
void EaxAutoWahEffect::defer_attack_time(
const EaxEaxCall& eax_call)
{
const auto& attack_time =
eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flAttackTime)>();
validate_attack_time(attack_time);
defer_attack_time(attack_time);
}
void EaxAutoWahEffect::defer_release_time(
const EaxEaxCall& eax_call)
{
const auto& release_time =
eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flReleaseTime)>();
validate_release_time(release_time);
defer_release_time(release_time);
}
void EaxAutoWahEffect::defer_resonance(
const EaxEaxCall& eax_call)
{
const auto& resonance =
eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lResonance)>();
validate_resonance(resonance);
defer_resonance(resonance);
}
void EaxAutoWahEffect::defer_peak_level(
const EaxEaxCall& eax_call)
{
const auto& peak_level =
eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lPeakLevel)>();
validate_peak_level(peak_level);
defer_peak_level(peak_level);
}
void EaxAutoWahEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<EaxAutoWahEffectException, const EAXAUTOWAHPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxAutoWahEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxAutoWahEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.flAttackTime)
{
set_efx_attack_time();
}
if (eax_dirty_flags_.flReleaseTime)
{
set_efx_release_time();
}
if (eax_dirty_flags_.lResonance)
{
set_efx_resonance();
}
if (eax_dirty_flags_.lPeakLevel)
{
set_efx_peak_gain();
}
eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{};
return true;
}
void EaxAutoWahEffect::set(const EaxEaxCall& eax_call)
{
switch (eax_call.get_property_id())
{
case EAXAUTOWAH_NONE:
break;
case EAXAUTOWAH_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXAUTOWAH_ATTACKTIME:
defer_attack_time(eax_call);
break;
case EAXAUTOWAH_RELEASETIME:
defer_release_time(eax_call);
break;
case EAXAUTOWAH_RESONANCE:
defer_resonance(eax_call);
break;
case EAXAUTOWAH_PEAKLEVEL:
defer_peak_level(eax_call);
break;
default:
throw EaxAutoWahEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_auto_wah_effect()
{
return std::make_unique<::EaxAutoWahEffect>();
}
#endif // ALSOFT_EAX

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,15 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -70,3 +77,220 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Compressor); DEFINE_ALEFFECT_VTABLE(Compressor);
const EffectProps CompressorEffectProps{genDefaultProps()}; const EffectProps CompressorEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxCompressorEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxCompressorEffectDirtyFlagsValue ulOnOff : 1;
}; // EaxCompressorEffectDirtyFlags
class EaxCompressorEffect final :
public EaxEffect
{
public:
EaxCompressorEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXAGCCOMPRESSORPROPERTIES eax_{};
EAXAGCCOMPRESSORPROPERTIES eax_d_{};
EaxCompressorEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_on_off();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_on_off(unsigned long ulOnOff);
void validate_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all);
void defer_on_off(unsigned long ulOnOff);
void defer_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all);
void defer_on_off(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxCompressorEffect
class EaxCompressorEffectException :
public EaxException
{
public:
explicit EaxCompressorEffectException(
const char* message)
:
EaxException{"EAX_COMPRESSOR_EFFECT", message}
{
}
}; // EaxCompressorEffectException
EaxCompressorEffect::EaxCompressorEffect()
: EaxEffect{AL_EFFECT_COMPRESSOR}
{
set_eax_defaults();
set_efx_defaults();
}
// [[nodiscard]]
void EaxCompressorEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxCompressorEffect::set_eax_defaults()
{
eax_.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF;
eax_d_ = eax_;
}
void EaxCompressorEffect::set_efx_on_off()
{
const auto on_off = clamp(
static_cast<ALint>(eax_.ulOnOff),
AL_COMPRESSOR_MIN_ONOFF,
AL_COMPRESSOR_MAX_ONOFF);
al_effect_props_.Compressor.OnOff = (on_off != AL_FALSE);
}
void EaxCompressorEffect::set_efx_defaults()
{
set_efx_on_off();
}
void EaxCompressorEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXAGCCOMPRESSOR_NONE:
break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS:
eax_call.set_value<EaxCompressorEffectException>(eax_);
break;
case EAXAGCCOMPRESSOR_ONOFF:
eax_call.set_value<EaxCompressorEffectException>(eax_.ulOnOff);
break;
default:
throw EaxCompressorEffectException{"Unsupported property id."};
}
}
void EaxCompressorEffect::validate_on_off(
unsigned long ulOnOff)
{
eax_validate_range<EaxCompressorEffectException>(
"On-Off",
ulOnOff,
EAXAGCCOMPRESSOR_MINONOFF,
EAXAGCCOMPRESSOR_MAXONOFF);
}
void EaxCompressorEffect::validate_all(
const EAXAGCCOMPRESSORPROPERTIES& eax_all)
{
validate_on_off(eax_all.ulOnOff);
}
void EaxCompressorEffect::defer_on_off(
unsigned long ulOnOff)
{
eax_d_.ulOnOff = ulOnOff;
eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff);
}
void EaxCompressorEffect::defer_all(
const EAXAGCCOMPRESSORPROPERTIES& eax_all)
{
defer_on_off(eax_all.ulOnOff);
}
void EaxCompressorEffect::defer_on_off(
const EaxEaxCall& eax_call)
{
const auto& on_off =
eax_call.get_value<EaxCompressorEffectException, const decltype(EAXAGCCOMPRESSORPROPERTIES::ulOnOff)>();
validate_on_off(on_off);
defer_on_off(on_off);
}
void EaxCompressorEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<EaxCompressorEffectException, const EAXAGCCOMPRESSORPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxCompressorEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.ulOnOff)
{
set_efx_on_off();
}
eax_dirty_flags_ = EaxCompressorEffectDirtyFlags{};
return true;
}
void EaxCompressorEffect::set(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXAGCCOMPRESSOR_NONE:
break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXAGCCOMPRESSOR_ONOFF:
defer_on_off(eax_call);
break;
default:
throw EaxCompressorEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_compressor_effect()
{
return std::make_unique<EaxCompressorEffect>();
}
#endif // ALSOFT_EAX

View file

@ -2,10 +2,10 @@
#include "config.h" #include "config.h"
#include "AL/al.h" #include "AL/al.h"
#include "inprogext.h" #include "alc/inprogext.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
namespace { namespace {

View file

@ -4,10 +4,10 @@
#include <cmath> #include <cmath>
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/alext.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
namespace { namespace {

View file

@ -4,8 +4,15 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -112,3 +119,453 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Distortion); DEFINE_ALEFFECT_VTABLE(Distortion);
const EffectProps DistortionEffectProps{genDefaultProps()}; const EffectProps DistortionEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxDistortionEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxDistortionEffectDirtyFlagsValue flEdge : 1;
EaxDistortionEffectDirtyFlagsValue lGain : 1;
EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1;
EaxDistortionEffectDirtyFlagsValue flEQCenter : 1;
EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1;
}; // EaxDistortionEffectDirtyFlags
class EaxDistortionEffect final :
public EaxEffect
{
public:
EaxDistortionEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXDISTORTIONPROPERTIES eax_{};
EAXDISTORTIONPROPERTIES eax_d_{};
EaxDistortionEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_edge();
void set_efx_gain();
void set_efx_lowpass_cutoff();
void set_efx_eq_center();
void set_efx_eq_bandwidth();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_edge(float flEdge);
void validate_gain(long lGain);
void validate_lowpass_cutoff(float flLowPassCutOff);
void validate_eq_center(float flEQCenter);
void validate_eq_bandwidth(float flEQBandwidth);
void validate_all(const EAXDISTORTIONPROPERTIES& eax_all);
void defer_edge(float flEdge);
void defer_gain(long lGain);
void defer_low_pass_cutoff(float flLowPassCutOff);
void defer_eq_center(float flEQCenter);
void defer_eq_bandwidth(float flEQBandwidth);
void defer_all(const EAXDISTORTIONPROPERTIES& eax_all);
void defer_edge(const EaxEaxCall& eax_call);
void defer_gain(const EaxEaxCall& eax_call);
void defer_low_pass_cutoff(const EaxEaxCall& eax_call);
void defer_eq_center(const EaxEaxCall& eax_call);
void defer_eq_bandwidth(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxDistortionEffect
class EaxDistortionEffectException :
public EaxException
{
public:
explicit EaxDistortionEffectException(
const char* message)
:
EaxException{"EAX_DISTORTION_EFFECT", message}
{
}
}; // EaxDistortionEffectException
EaxDistortionEffect::EaxDistortionEffect()
: EaxEffect{AL_EFFECT_DISTORTION}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxDistortionEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxDistortionEffect::set_eax_defaults()
{
eax_.flEdge = EAXDISTORTION_DEFAULTEDGE;
eax_.lGain = EAXDISTORTION_DEFAULTGAIN;
eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
eax_d_ = eax_;
}
void EaxDistortionEffect::set_efx_edge()
{
const auto edge = clamp(
eax_.flEdge,
AL_DISTORTION_MIN_EDGE,
AL_DISTORTION_MAX_EDGE);
al_effect_props_.Distortion.Edge = edge;
}
void EaxDistortionEffect::set_efx_gain()
{
const auto gain = clamp(
level_mb_to_gain(static_cast<float>(eax_.lGain)),
AL_DISTORTION_MIN_GAIN,
AL_DISTORTION_MAX_GAIN);
al_effect_props_.Distortion.Gain = gain;
}
void EaxDistortionEffect::set_efx_lowpass_cutoff()
{
const auto lowpass_cutoff = clamp(
eax_.flLowPassCutOff,
AL_DISTORTION_MIN_LOWPASS_CUTOFF,
AL_DISTORTION_MAX_LOWPASS_CUTOFF);
al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff;
}
void EaxDistortionEffect::set_efx_eq_center()
{
const auto eq_center = clamp(
eax_.flEQCenter,
AL_DISTORTION_MIN_EQCENTER,
AL_DISTORTION_MAX_EQCENTER);
al_effect_props_.Distortion.EQCenter = eq_center;
}
void EaxDistortionEffect::set_efx_eq_bandwidth()
{
const auto eq_bandwidth = clamp(
eax_.flEdge,
AL_DISTORTION_MIN_EQBANDWIDTH,
AL_DISTORTION_MAX_EQBANDWIDTH);
al_effect_props_.Distortion.EQBandwidth = eq_bandwidth;
}
void EaxDistortionEffect::set_efx_defaults()
{
set_efx_edge();
set_efx_gain();
set_efx_lowpass_cutoff();
set_efx_eq_center();
set_efx_eq_bandwidth();
}
void EaxDistortionEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXDISTORTION_NONE:
break;
case EAXDISTORTION_ALLPARAMETERS:
eax_call.set_value<EaxDistortionEffectException>(eax_);
break;
case EAXDISTORTION_EDGE:
eax_call.set_value<EaxDistortionEffectException>(eax_.flEdge);
break;
case EAXDISTORTION_GAIN:
eax_call.set_value<EaxDistortionEffectException>(eax_.lGain);
break;
case EAXDISTORTION_LOWPASSCUTOFF:
eax_call.set_value<EaxDistortionEffectException>(eax_.flLowPassCutOff);
break;
case EAXDISTORTION_EQCENTER:
eax_call.set_value<EaxDistortionEffectException>(eax_.flEQCenter);
break;
case EAXDISTORTION_EQBANDWIDTH:
eax_call.set_value<EaxDistortionEffectException>(eax_.flEQBandwidth);
break;
default:
throw EaxDistortionEffectException{"Unsupported property id."};
}
}
void EaxDistortionEffect::validate_edge(
float flEdge)
{
eax_validate_range<EaxDistortionEffectException>(
"Edge",
flEdge,
EAXDISTORTION_MINEDGE,
EAXDISTORTION_MAXEDGE);
}
void EaxDistortionEffect::validate_gain(
long lGain)
{
eax_validate_range<EaxDistortionEffectException>(
"Gain",
lGain,
EAXDISTORTION_MINGAIN,
EAXDISTORTION_MAXGAIN);
}
void EaxDistortionEffect::validate_lowpass_cutoff(
float flLowPassCutOff)
{
eax_validate_range<EaxDistortionEffectException>(
"Low-pass Cut-off",
flLowPassCutOff,
EAXDISTORTION_MINLOWPASSCUTOFF,
EAXDISTORTION_MAXLOWPASSCUTOFF);
}
void EaxDistortionEffect::validate_eq_center(
float flEQCenter)
{
eax_validate_range<EaxDistortionEffectException>(
"EQ Center",
flEQCenter,
EAXDISTORTION_MINEQCENTER,
EAXDISTORTION_MAXEQCENTER);
}
void EaxDistortionEffect::validate_eq_bandwidth(
float flEQBandwidth)
{
eax_validate_range<EaxDistortionEffectException>(
"EQ Bandwidth",
flEQBandwidth,
EAXDISTORTION_MINEQBANDWIDTH,
EAXDISTORTION_MAXEQBANDWIDTH);
}
void EaxDistortionEffect::validate_all(
const EAXDISTORTIONPROPERTIES& eax_all)
{
validate_edge(eax_all.flEdge);
validate_gain(eax_all.lGain);
validate_lowpass_cutoff(eax_all.flLowPassCutOff);
validate_eq_center(eax_all.flEQCenter);
validate_eq_bandwidth(eax_all.flEQBandwidth);
}
void EaxDistortionEffect::defer_edge(
float flEdge)
{
eax_d_.flEdge = flEdge;
eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge);
}
void EaxDistortionEffect::defer_gain(
long lGain)
{
eax_d_.lGain = lGain;
eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain);
}
void EaxDistortionEffect::defer_low_pass_cutoff(
float flLowPassCutOff)
{
eax_d_.flLowPassCutOff = flLowPassCutOff;
eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff);
}
void EaxDistortionEffect::defer_eq_center(
float flEQCenter)
{
eax_d_.flEQCenter = flEQCenter;
eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter);
}
void EaxDistortionEffect::defer_eq_bandwidth(
float flEQBandwidth)
{
eax_d_.flEQBandwidth = flEQBandwidth;
eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth);
}
void EaxDistortionEffect::defer_all(
const EAXDISTORTIONPROPERTIES& eax_all)
{
defer_edge(eax_all.flEdge);
defer_gain(eax_all.lGain);
defer_low_pass_cutoff(eax_all.flLowPassCutOff);
defer_eq_center(eax_all.flEQCenter);
defer_eq_bandwidth(eax_all.flEQBandwidth);
}
void EaxDistortionEffect::defer_edge(
const EaxEaxCall& eax_call)
{
const auto& edge =
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEdge)>();
validate_edge(edge);
defer_edge(edge);
}
void EaxDistortionEffect::defer_gain(
const EaxEaxCall& eax_call)
{
const auto& gain =
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::lGain)>();
validate_gain(gain);
defer_gain(gain);
}
void EaxDistortionEffect::defer_low_pass_cutoff(
const EaxEaxCall& eax_call)
{
const auto& lowpass_cutoff =
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flLowPassCutOff)>();
validate_lowpass_cutoff(lowpass_cutoff);
defer_low_pass_cutoff(lowpass_cutoff);
}
void EaxDistortionEffect::defer_eq_center(
const EaxEaxCall& eax_call)
{
const auto& eq_center =
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQCenter)>();
validate_eq_center(eq_center);
defer_eq_center(eq_center);
}
void EaxDistortionEffect::defer_eq_bandwidth(
const EaxEaxCall& eax_call)
{
const auto& eq_bandwidth =
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQBandwidth)>();
validate_eq_bandwidth(eq_bandwidth);
defer_eq_bandwidth(eq_bandwidth);
}
void EaxDistortionEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<EaxDistortionEffectException, const EAXDISTORTIONPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxDistortionEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.flEdge)
{
set_efx_edge();
}
if (eax_dirty_flags_.lGain)
{
set_efx_gain();
}
if (eax_dirty_flags_.flLowPassCutOff)
{
set_efx_lowpass_cutoff();
}
if (eax_dirty_flags_.flEQCenter)
{
set_efx_eq_center();
}
if (eax_dirty_flags_.flEQBandwidth)
{
set_efx_eq_bandwidth();
}
eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{};
return true;
}
void EaxDistortionEffect::set(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXDISTORTION_NONE:
break;
case EAXDISTORTION_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXDISTORTION_EDGE:
defer_edge(eax_call);
break;
case EAXDISTORTION_GAIN:
defer_gain(eax_call);
break;
case EAXDISTORTION_LOWPASSCUTOFF:
defer_low_pass_cutoff(eax_call);
break;
case EAXDISTORTION_EQCENTER:
defer_eq_center(eax_call);
break;
case EAXDISTORTION_EQBANDWIDTH:
defer_eq_bandwidth(eax_call);
break;
default:
throw EaxDistortionEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_distortion_effect()
{
return std::make_unique<EaxDistortionEffect>();
}
#endif // ALSOFT_EAX

View file

@ -4,8 +4,15 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -109,3 +116,454 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Echo); DEFINE_ALEFFECT_VTABLE(Echo);
const EffectProps EchoEffectProps{genDefaultProps()}; const EffectProps EchoEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxEchoEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxEchoEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxEchoEffectDirtyFlagsValue flDelay : 1;
EaxEchoEffectDirtyFlagsValue flLRDelay : 1;
EaxEchoEffectDirtyFlagsValue flDamping : 1;
EaxEchoEffectDirtyFlagsValue flFeedback : 1;
EaxEchoEffectDirtyFlagsValue flSpread : 1;
}; // EaxEchoEffectDirtyFlags
class EaxEchoEffect final :
public EaxEffect
{
public:
EaxEchoEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXECHOPROPERTIES eax_{};
EAXECHOPROPERTIES eax_d_{};
EaxEchoEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_delay();
void set_efx_lr_delay();
void set_efx_damping();
void set_efx_feedback();
void set_efx_spread();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_delay(float flDelay);
void validate_lr_delay(float flLRDelay);
void validate_damping(float flDamping);
void validate_feedback(float flFeedback);
void validate_spread(float flSpread);
void validate_all(const EAXECHOPROPERTIES& all);
void defer_delay(float flDelay);
void defer_lr_delay(float flLRDelay);
void defer_damping(float flDamping);
void defer_feedback(float flFeedback);
void defer_spread(float flSpread);
void defer_all(const EAXECHOPROPERTIES& all);
void defer_delay(const EaxEaxCall& eax_call);
void defer_lr_delay(const EaxEaxCall& eax_call);
void defer_damping(const EaxEaxCall& eax_call);
void defer_feedback(const EaxEaxCall& eax_call);
void defer_spread(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxEchoEffect
class EaxEchoEffectException :
public EaxException
{
public:
explicit EaxEchoEffectException(
const char* message)
:
EaxException{"EAX_ECHO_EFFECT", message}
{
}
}; // EaxEchoEffectException
EaxEchoEffect::EaxEchoEffect()
: EaxEffect{AL_EFFECT_ECHO}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxEchoEffect::dispatch(
const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxEchoEffect::set_eax_defaults()
{
eax_.flDelay = EAXECHO_DEFAULTDELAY;
eax_.flLRDelay = EAXECHO_DEFAULTLRDELAY;
eax_.flDamping = EAXECHO_DEFAULTDAMPING;
eax_.flFeedback = EAXECHO_DEFAULTFEEDBACK;
eax_.flSpread = EAXECHO_DEFAULTSPREAD;
eax_d_ = eax_;
}
void EaxEchoEffect::set_efx_delay()
{
const auto delay = clamp(
eax_.flDelay,
AL_ECHO_MIN_DELAY,
AL_ECHO_MAX_DELAY);
al_effect_props_.Echo.Delay = delay;
}
void EaxEchoEffect::set_efx_lr_delay()
{
const auto lr_delay = clamp(
eax_.flLRDelay,
AL_ECHO_MIN_LRDELAY,
AL_ECHO_MAX_LRDELAY);
al_effect_props_.Echo.LRDelay = lr_delay;
}
void EaxEchoEffect::set_efx_damping()
{
const auto damping = clamp(
eax_.flDamping,
AL_ECHO_MIN_DAMPING,
AL_ECHO_MAX_DAMPING);
al_effect_props_.Echo.Damping = damping;
}
void EaxEchoEffect::set_efx_feedback()
{
const auto feedback = clamp(
eax_.flFeedback,
AL_ECHO_MIN_FEEDBACK,
AL_ECHO_MAX_FEEDBACK);
al_effect_props_.Echo.Feedback = feedback;
}
void EaxEchoEffect::set_efx_spread()
{
const auto spread = clamp(
eax_.flSpread,
AL_ECHO_MIN_SPREAD,
AL_ECHO_MAX_SPREAD);
al_effect_props_.Echo.Spread = spread;
}
void EaxEchoEffect::set_efx_defaults()
{
set_efx_delay();
set_efx_lr_delay();
set_efx_damping();
set_efx_feedback();
set_efx_spread();
}
void EaxEchoEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXECHO_NONE:
break;
case EAXECHO_ALLPARAMETERS:
eax_call.set_value<EaxEchoEffectException>(eax_);
break;
case EAXECHO_DELAY:
eax_call.set_value<EaxEchoEffectException>(eax_.flDelay);
break;
case EAXECHO_LRDELAY:
eax_call.set_value<EaxEchoEffectException>(eax_.flLRDelay);
break;
case EAXECHO_DAMPING:
eax_call.set_value<EaxEchoEffectException>(eax_.flDamping);
break;
case EAXECHO_FEEDBACK:
eax_call.set_value<EaxEchoEffectException>(eax_.flFeedback);
break;
case EAXECHO_SPREAD:
eax_call.set_value<EaxEchoEffectException>(eax_.flSpread);
break;
default:
throw EaxEchoEffectException{"Unsupported property id."};
}
}
void EaxEchoEffect::validate_delay(
float flDelay)
{
eax_validate_range<EaxEchoEffectException>(
"Delay",
flDelay,
EAXECHO_MINDELAY,
EAXECHO_MAXDELAY);
}
void EaxEchoEffect::validate_lr_delay(
float flLRDelay)
{
eax_validate_range<EaxEchoEffectException>(
"LR Delay",
flLRDelay,
EAXECHO_MINLRDELAY,
EAXECHO_MAXLRDELAY);
}
void EaxEchoEffect::validate_damping(
float flDamping)
{
eax_validate_range<EaxEchoEffectException>(
"Damping",
flDamping,
EAXECHO_MINDAMPING,
EAXECHO_MAXDAMPING);
}
void EaxEchoEffect::validate_feedback(
float flFeedback)
{
eax_validate_range<EaxEchoEffectException>(
"Feedback",
flFeedback,
EAXECHO_MINFEEDBACK,
EAXECHO_MAXFEEDBACK);
}
void EaxEchoEffect::validate_spread(
float flSpread)
{
eax_validate_range<EaxEchoEffectException>(
"Spread",
flSpread,
EAXECHO_MINSPREAD,
EAXECHO_MAXSPREAD);
}
void EaxEchoEffect::validate_all(
const EAXECHOPROPERTIES& all)
{
validate_delay(all.flDelay);
validate_lr_delay(all.flLRDelay);
validate_damping(all.flDamping);
validate_feedback(all.flFeedback);
validate_spread(all.flSpread);
}
void EaxEchoEffect::defer_delay(
float flDelay)
{
eax_d_.flDelay = flDelay;
eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
}
void EaxEchoEffect::defer_lr_delay(
float flLRDelay)
{
eax_d_.flLRDelay = flLRDelay;
eax_dirty_flags_.flLRDelay = (eax_.flLRDelay != eax_d_.flLRDelay);
}
void EaxEchoEffect::defer_damping(
float flDamping)
{
eax_d_.flDamping = flDamping;
eax_dirty_flags_.flDamping = (eax_.flDamping != eax_d_.flDamping);
}
void EaxEchoEffect::defer_feedback(
float flFeedback)
{
eax_d_.flFeedback = flFeedback;
eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
}
void EaxEchoEffect::defer_spread(
float flSpread)
{
eax_d_.flSpread = flSpread;
eax_dirty_flags_.flSpread = (eax_.flSpread != eax_d_.flSpread);
}
void EaxEchoEffect::defer_all(
const EAXECHOPROPERTIES& all)
{
defer_delay(all.flDelay);
defer_lr_delay(all.flLRDelay);
defer_damping(all.flDamping);
defer_feedback(all.flFeedback);
defer_spread(all.flSpread);
}
void EaxEchoEffect::defer_delay(
const EaxEaxCall& eax_call)
{
const auto& delay =
eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDelay)>();
validate_delay(delay);
defer_delay(delay);
}
void EaxEchoEffect::defer_lr_delay(
const EaxEaxCall& eax_call)
{
const auto& lr_delay =
eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flLRDelay)>();
validate_lr_delay(lr_delay);
defer_lr_delay(lr_delay);
}
void EaxEchoEffect::defer_damping(
const EaxEaxCall& eax_call)
{
const auto& damping =
eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDamping)>();
validate_damping(damping);
defer_damping(damping);
}
void EaxEchoEffect::defer_feedback(
const EaxEaxCall& eax_call)
{
const auto& feedback =
eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flFeedback)>();
validate_feedback(feedback);
defer_feedback(feedback);
}
void EaxEchoEffect::defer_spread(
const EaxEaxCall& eax_call)
{
const auto& spread =
eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flSpread)>();
validate_spread(spread);
defer_spread(spread);
}
void EaxEchoEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<EaxEchoEffectException, const EAXECHOPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxEchoEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxEchoEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.flDelay)
{
set_efx_delay();
}
if (eax_dirty_flags_.flLRDelay)
{
set_efx_lr_delay();
}
if (eax_dirty_flags_.flDamping)
{
set_efx_damping();
}
if (eax_dirty_flags_.flFeedback)
{
set_efx_feedback();
}
if (eax_dirty_flags_.flSpread)
{
set_efx_spread();
}
eax_dirty_flags_ = EaxEchoEffectDirtyFlags{};
return true;
}
void EaxEchoEffect::set(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXECHO_NONE:
break;
case EAXECHO_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXECHO_DELAY:
defer_delay(eax_call);
break;
case EAXECHO_LRDELAY:
defer_lr_delay(eax_call);
break;
case EAXECHO_DAMPING:
defer_damping(eax_call);
break;
case EAXECHO_FEEDBACK:
defer_feedback(eax_call);
break;
case EAXECHO_SPREAD:
defer_spread(eax_call);
break;
default:
throw EaxEchoEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_echo_effect()
{
return std::make_unique<EaxEchoEffect>();
}
#endif // ALSOFT_EAX

View file

@ -0,0 +1,66 @@
#include "config.h"
#ifdef ALSOFT_EAX
#include "effects.h"
#include <cassert>
#include "AL/efx.h"
EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type)
{
#define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] "
switch (al_effect_type)
{
case AL_EFFECT_NULL:
return eax_create_eax_null_effect();
case AL_EFFECT_CHORUS:
return eax_create_eax_chorus_effect();
case AL_EFFECT_DISTORTION:
return eax_create_eax_distortion_effect();
case AL_EFFECT_ECHO:
return eax_create_eax_echo_effect();
case AL_EFFECT_FLANGER:
return eax_create_eax_flanger_effect();
case AL_EFFECT_FREQUENCY_SHIFTER:
return eax_create_eax_frequency_shifter_effect();
case AL_EFFECT_VOCAL_MORPHER:
return eax_create_eax_vocal_morpher_effect();
case AL_EFFECT_PITCH_SHIFTER:
return eax_create_eax_pitch_shifter_effect();
case AL_EFFECT_RING_MODULATOR:
return eax_create_eax_ring_modulator_effect();
case AL_EFFECT_AUTOWAH:
return eax_create_eax_auto_wah_effect();
case AL_EFFECT_COMPRESSOR:
return eax_create_eax_compressor_effect();
case AL_EFFECT_EQUALIZER:
return eax_create_eax_equalizer_effect();
case AL_EFFECT_EAXREVERB:
return eax_create_eax_reverb_effect();
default:
assert(false && "Unsupported AL effect type.");
return nullptr;
}
#undef EAX_PREFIX
}
#endif // ALSOFT_EAX

View file

@ -5,6 +5,10 @@
#include "core/except.h" #include "core/except.h"
#ifdef ALSOFT_EAX
#include "al/eax_effect.h"
#endif // ALSOFT_EAX
union EffectProps; union EffectProps;
@ -12,7 +16,11 @@ class effect_exception final : public al::base_exception {
ALenum mErrorCode; ALenum mErrorCode;
public: public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]] [[gnu::format(printf, 3, 4)]]
#endif
effect_exception(ALenum code, const char *msg, ...); effect_exception(ALenum code, const char *msg, ...);
ALenum errorCode() const noexcept { return mErrorCode; } ALenum errorCode() const noexcept { return mErrorCode; }
@ -76,4 +84,9 @@ extern const EffectVtable VmorpherEffectVtable;
extern const EffectVtable DedicatedEffectVtable; extern const EffectVtable DedicatedEffectVtable;
extern const EffectVtable ConvolutionEffectVtable; extern const EffectVtable ConvolutionEffectVtable;
#ifdef ALSOFT_EAX
EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type);
#endif // ALSOFT_EAX
#endif /* AL_EFFECTS_EFFECTS_H */ #endif /* AL_EFFECTS_EFFECTS_H */

View file

@ -4,8 +4,15 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -167,3 +174,748 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Equalizer); DEFINE_ALEFFECT_VTABLE(Equalizer);
const EffectProps EqualizerEffectProps{genDefaultProps()}; const EffectProps EqualizerEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxEqualizerEffectDirtyFlagsValue = std::uint_least16_t;
struct EaxEqualizerEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxEqualizerEffectDirtyFlagsValue lLowGain : 1;
EaxEqualizerEffectDirtyFlagsValue flLowCutOff : 1;
EaxEqualizerEffectDirtyFlagsValue lMid1Gain : 1;
EaxEqualizerEffectDirtyFlagsValue flMid1Center : 1;
EaxEqualizerEffectDirtyFlagsValue flMid1Width : 1;
EaxEqualizerEffectDirtyFlagsValue lMid2Gain : 1;
EaxEqualizerEffectDirtyFlagsValue flMid2Center : 1;
EaxEqualizerEffectDirtyFlagsValue flMid2Width : 1;
EaxEqualizerEffectDirtyFlagsValue lHighGain : 1;
EaxEqualizerEffectDirtyFlagsValue flHighCutOff : 1;
}; // EaxEqualizerEffectDirtyFlags
class EaxEqualizerEffect final :
public EaxEffect
{
public:
EaxEqualizerEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXEQUALIZERPROPERTIES eax_{};
EAXEQUALIZERPROPERTIES eax_d_{};
EaxEqualizerEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_low_gain();
void set_efx_low_cutoff();
void set_efx_mid1_gain();
void set_efx_mid1_center();
void set_efx_mid1_width();
void set_efx_mid2_gain();
void set_efx_mid2_center();
void set_efx_mid2_width();
void set_efx_high_gain();
void set_efx_high_cutoff();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_low_gain(long lLowGain);
void validate_low_cutoff(float flLowCutOff);
void validate_mid1_gain(long lMid1Gain);
void validate_mid1_center(float flMid1Center);
void validate_mid1_width(float flMid1Width);
void validate_mid2_gain(long lMid2Gain);
void validate_mid2_center(float flMid2Center);
void validate_mid2_width(float flMid2Width);
void validate_high_gain(long lHighGain);
void validate_high_cutoff(float flHighCutOff);
void validate_all(const EAXEQUALIZERPROPERTIES& all);
void defer_low_gain(long lLowGain);
void defer_low_cutoff(float flLowCutOff);
void defer_mid1_gain(long lMid1Gain);
void defer_mid1_center(float flMid1Center);
void defer_mid1_width(float flMid1Width);
void defer_mid2_gain(long lMid2Gain);
void defer_mid2_center(float flMid2Center);
void defer_mid2_width(float flMid2Width);
void defer_high_gain(long lHighGain);
void defer_high_cutoff(float flHighCutOff);
void defer_all(const EAXEQUALIZERPROPERTIES& all);
void defer_low_gain(const EaxEaxCall& eax_call);
void defer_low_cutoff(const EaxEaxCall& eax_call);
void defer_mid1_gain(const EaxEaxCall& eax_call);
void defer_mid1_center(const EaxEaxCall& eax_call);
void defer_mid1_width(const EaxEaxCall& eax_call);
void defer_mid2_gain(const EaxEaxCall& eax_call);
void defer_mid2_center(const EaxEaxCall& eax_call);
void defer_mid2_width(const EaxEaxCall& eax_call);
void defer_high_gain(const EaxEaxCall& eax_call);
void defer_high_cutoff(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxEqualizerEffect
class EaxEqualizerEffectException :
public EaxException
{
public:
explicit EaxEqualizerEffectException(
const char* message)
:
EaxException{"EAX_EQUALIZER_EFFECT", message}
{
}
}; // EaxEqualizerEffectException
EaxEqualizerEffect::EaxEqualizerEffect()
: EaxEffect{AL_EFFECT_EQUALIZER}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxEqualizerEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxEqualizerEffect::set_eax_defaults()
{
eax_.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
eax_.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
eax_.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
eax_.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
eax_.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
eax_.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
eax_.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
eax_.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
eax_.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
eax_.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
eax_d_ = eax_;
}
void EaxEqualizerEffect::set_efx_low_gain()
{
const auto low_gain = clamp(
level_mb_to_gain(static_cast<float>(eax_.lLowGain)),
AL_EQUALIZER_MIN_LOW_GAIN,
AL_EQUALIZER_MAX_LOW_GAIN);
al_effect_props_.Equalizer.LowGain = low_gain;
}
void EaxEqualizerEffect::set_efx_low_cutoff()
{
const auto low_cutoff = clamp(
eax_.flLowCutOff,
AL_EQUALIZER_MIN_LOW_CUTOFF,
AL_EQUALIZER_MAX_LOW_CUTOFF);
al_effect_props_.Equalizer.LowCutoff = low_cutoff;
}
void EaxEqualizerEffect::set_efx_mid1_gain()
{
const auto mid1_gain = clamp(
level_mb_to_gain(static_cast<float>(eax_.lMid1Gain)),
AL_EQUALIZER_MIN_MID1_GAIN,
AL_EQUALIZER_MAX_MID1_GAIN);
al_effect_props_.Equalizer.Mid1Gain = mid1_gain;
}
void EaxEqualizerEffect::set_efx_mid1_center()
{
const auto mid1_center = clamp(
eax_.flMid1Center,
AL_EQUALIZER_MIN_MID1_CENTER,
AL_EQUALIZER_MAX_MID1_CENTER);
al_effect_props_.Equalizer.Mid1Center = mid1_center;
}
void EaxEqualizerEffect::set_efx_mid1_width()
{
const auto mid1_width = clamp(
eax_.flMid1Width,
AL_EQUALIZER_MIN_MID1_WIDTH,
AL_EQUALIZER_MAX_MID1_WIDTH);
al_effect_props_.Equalizer.Mid1Width = mid1_width;
}
void EaxEqualizerEffect::set_efx_mid2_gain()
{
const auto mid2_gain = clamp(
level_mb_to_gain(static_cast<float>(eax_.lMid2Gain)),
AL_EQUALIZER_MIN_MID2_GAIN,
AL_EQUALIZER_MAX_MID2_GAIN);
al_effect_props_.Equalizer.Mid2Gain = mid2_gain;
}
void EaxEqualizerEffect::set_efx_mid2_center()
{
const auto mid2_center = clamp(
eax_.flMid2Center,
AL_EQUALIZER_MIN_MID2_CENTER,
AL_EQUALIZER_MAX_MID2_CENTER);
al_effect_props_.Equalizer.Mid2Center = mid2_center;
}
void EaxEqualizerEffect::set_efx_mid2_width()
{
const auto mid2_width = clamp(
eax_.flMid2Width,
AL_EQUALIZER_MIN_MID2_WIDTH,
AL_EQUALIZER_MAX_MID2_WIDTH);
al_effect_props_.Equalizer.Mid2Width = mid2_width;
}
void EaxEqualizerEffect::set_efx_high_gain()
{
const auto high_gain = clamp(
level_mb_to_gain(static_cast<float>(eax_.lHighGain)),
AL_EQUALIZER_MIN_HIGH_GAIN,
AL_EQUALIZER_MAX_HIGH_GAIN);
al_effect_props_.Equalizer.HighGain = high_gain;
}
void EaxEqualizerEffect::set_efx_high_cutoff()
{
const auto high_cutoff = clamp(
eax_.flHighCutOff,
AL_EQUALIZER_MIN_HIGH_CUTOFF,
AL_EQUALIZER_MAX_HIGH_CUTOFF);
al_effect_props_.Equalizer.HighCutoff = high_cutoff;
}
void EaxEqualizerEffect::set_efx_defaults()
{
set_efx_low_gain();
set_efx_low_cutoff();
set_efx_mid1_gain();
set_efx_mid1_center();
set_efx_mid1_width();
set_efx_mid2_gain();
set_efx_mid2_center();
set_efx_mid2_width();
set_efx_high_gain();
set_efx_high_cutoff();
}
void EaxEqualizerEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXEQUALIZER_NONE:
break;
case EAXEQUALIZER_ALLPARAMETERS:
eax_call.set_value<EaxEqualizerEffectException>(eax_);
break;
case EAXEQUALIZER_LOWGAIN:
eax_call.set_value<EaxEqualizerEffectException>(eax_.lLowGain);
break;
case EAXEQUALIZER_LOWCUTOFF:
eax_call.set_value<EaxEqualizerEffectException>(eax_.flLowCutOff);
break;
case EAXEQUALIZER_MID1GAIN:
eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid1Gain);
break;
case EAXEQUALIZER_MID1CENTER:
eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Center);
break;
case EAXEQUALIZER_MID1WIDTH:
eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Width);
break;
case EAXEQUALIZER_MID2GAIN:
eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid2Gain);
break;
case EAXEQUALIZER_MID2CENTER:
eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Center);
break;
case EAXEQUALIZER_MID2WIDTH:
eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Width);
break;
case EAXEQUALIZER_HIGHGAIN:
eax_call.set_value<EaxEqualizerEffectException>(eax_.lHighGain);
break;
case EAXEQUALIZER_HIGHCUTOFF:
eax_call.set_value<EaxEqualizerEffectException>(eax_.flHighCutOff);
break;
default:
throw EaxEqualizerEffectException{"Unsupported property id."};
}
}
void EaxEqualizerEffect::validate_low_gain(
long lLowGain)
{
eax_validate_range<EaxEqualizerEffectException>(
"Low Gain",
lLowGain,
EAXEQUALIZER_MINLOWGAIN,
EAXEQUALIZER_MAXLOWGAIN);
}
void EaxEqualizerEffect::validate_low_cutoff(
float flLowCutOff)
{
eax_validate_range<EaxEqualizerEffectException>(
"Low Cutoff",
flLowCutOff,
EAXEQUALIZER_MINLOWCUTOFF,
EAXEQUALIZER_MAXLOWCUTOFF);
}
void EaxEqualizerEffect::validate_mid1_gain(
long lMid1Gain)
{
eax_validate_range<EaxEqualizerEffectException>(
"Mid1 Gain",
lMid1Gain,
EAXEQUALIZER_MINMID1GAIN,
EAXEQUALIZER_MAXMID1GAIN);
}
void EaxEqualizerEffect::validate_mid1_center(
float flMid1Center)
{
eax_validate_range<EaxEqualizerEffectException>(
"Mid1 Center",
flMid1Center,
EAXEQUALIZER_MINMID1CENTER,
EAXEQUALIZER_MAXMID1CENTER);
}
void EaxEqualizerEffect::validate_mid1_width(
float flMid1Width)
{
eax_validate_range<EaxEqualizerEffectException>(
"Mid1 Width",
flMid1Width,
EAXEQUALIZER_MINMID1WIDTH,
EAXEQUALIZER_MAXMID1WIDTH);
}
void EaxEqualizerEffect::validate_mid2_gain(
long lMid2Gain)
{
eax_validate_range<EaxEqualizerEffectException>(
"Mid2 Gain",
lMid2Gain,
EAXEQUALIZER_MINMID2GAIN,
EAXEQUALIZER_MAXMID2GAIN);
}
void EaxEqualizerEffect::validate_mid2_center(
float flMid2Center)
{
eax_validate_range<EaxEqualizerEffectException>(
"Mid2 Center",
flMid2Center,
EAXEQUALIZER_MINMID2CENTER,
EAXEQUALIZER_MAXMID2CENTER);
}
void EaxEqualizerEffect::validate_mid2_width(
float flMid2Width)
{
eax_validate_range<EaxEqualizerEffectException>(
"Mid2 Width",
flMid2Width,
EAXEQUALIZER_MINMID2WIDTH,
EAXEQUALIZER_MAXMID2WIDTH);
}
void EaxEqualizerEffect::validate_high_gain(
long lHighGain)
{
eax_validate_range<EaxEqualizerEffectException>(
"High Gain",
lHighGain,
EAXEQUALIZER_MINHIGHGAIN,
EAXEQUALIZER_MAXHIGHGAIN);
}
void EaxEqualizerEffect::validate_high_cutoff(
float flHighCutOff)
{
eax_validate_range<EaxEqualizerEffectException>(
"High Cutoff",
flHighCutOff,
EAXEQUALIZER_MINHIGHCUTOFF,
EAXEQUALIZER_MAXHIGHCUTOFF);
}
void EaxEqualizerEffect::validate_all(
const EAXEQUALIZERPROPERTIES& all)
{
validate_low_gain(all.lLowGain);
validate_low_cutoff(all.flLowCutOff);
validate_mid1_gain(all.lMid1Gain);
validate_mid1_center(all.flMid1Center);
validate_mid1_width(all.flMid1Width);
validate_mid2_gain(all.lMid2Gain);
validate_mid2_center(all.flMid2Center);
validate_mid2_width(all.flMid2Width);
validate_high_gain(all.lHighGain);
validate_high_cutoff(all.flHighCutOff);
}
void EaxEqualizerEffect::defer_low_gain(
long lLowGain)
{
eax_d_.lLowGain = lLowGain;
eax_dirty_flags_.lLowGain = (eax_.lLowGain != eax_d_.lLowGain);
}
void EaxEqualizerEffect::defer_low_cutoff(
float flLowCutOff)
{
eax_d_.flLowCutOff = flLowCutOff;
eax_dirty_flags_.flLowCutOff = (eax_.flLowCutOff != eax_d_.flLowCutOff);
}
void EaxEqualizerEffect::defer_mid1_gain(
long lMid1Gain)
{
eax_d_.lMid1Gain = lMid1Gain;
eax_dirty_flags_.lMid1Gain = (eax_.lMid1Gain != eax_d_.lMid1Gain);
}
void EaxEqualizerEffect::defer_mid1_center(
float flMid1Center)
{
eax_d_.flMid1Center = flMid1Center;
eax_dirty_flags_.flMid1Center = (eax_.flMid1Center != eax_d_.flMid1Center);
}
void EaxEqualizerEffect::defer_mid1_width(
float flMid1Width)
{
eax_d_.flMid1Width = flMid1Width;
eax_dirty_flags_.flMid1Width = (eax_.flMid1Width != eax_d_.flMid1Width);
}
void EaxEqualizerEffect::defer_mid2_gain(
long lMid2Gain)
{
eax_d_.lMid2Gain = lMid2Gain;
eax_dirty_flags_.lMid2Gain = (eax_.lMid2Gain != eax_d_.lMid2Gain);
}
void EaxEqualizerEffect::defer_mid2_center(
float flMid2Center)
{
eax_d_.flMid2Center = flMid2Center;
eax_dirty_flags_.flMid2Center = (eax_.flMid2Center != eax_d_.flMid2Center);
}
void EaxEqualizerEffect::defer_mid2_width(
float flMid2Width)
{
eax_d_.flMid2Width = flMid2Width;
eax_dirty_flags_.flMid2Width = (eax_.flMid2Width != eax_d_.flMid2Width);
}
void EaxEqualizerEffect::defer_high_gain(
long lHighGain)
{
eax_d_.lHighGain = lHighGain;
eax_dirty_flags_.lHighGain = (eax_.lHighGain != eax_d_.lHighGain);
}
void EaxEqualizerEffect::defer_high_cutoff(
float flHighCutOff)
{
eax_d_.flHighCutOff = flHighCutOff;
eax_dirty_flags_.flHighCutOff = (eax_.flHighCutOff != eax_d_.flHighCutOff);
}
void EaxEqualizerEffect::defer_all(
const EAXEQUALIZERPROPERTIES& all)
{
defer_low_gain(all.lLowGain);
defer_low_cutoff(all.flLowCutOff);
defer_mid1_gain(all.lMid1Gain);
defer_mid1_center(all.flMid1Center);
defer_mid1_width(all.flMid1Width);
defer_mid2_gain(all.lMid2Gain);
defer_mid2_center(all.flMid2Center);
defer_mid2_width(all.flMid2Width);
defer_high_gain(all.lHighGain);
defer_high_cutoff(all.flHighCutOff);
}
void EaxEqualizerEffect::defer_low_gain(
const EaxEaxCall& eax_call)
{
const auto& low_gain =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lLowGain)>();
validate_low_gain(low_gain);
defer_low_gain(low_gain);
}
void EaxEqualizerEffect::defer_low_cutoff(
const EaxEaxCall& eax_call)
{
const auto& low_cutoff =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flLowCutOff)>();
validate_low_cutoff(low_cutoff);
defer_low_cutoff(low_cutoff);
}
void EaxEqualizerEffect::defer_mid1_gain(
const EaxEaxCall& eax_call)
{
const auto& mid1_gain =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid1Gain)>();
validate_mid1_gain(mid1_gain);
defer_mid1_gain(mid1_gain);
}
void EaxEqualizerEffect::defer_mid1_center(
const EaxEaxCall& eax_call)
{
const auto& mid1_center =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Center)>();
validate_mid1_center(mid1_center);
defer_mid1_center(mid1_center);
}
void EaxEqualizerEffect::defer_mid1_width(
const EaxEaxCall& eax_call)
{
const auto& mid1_width =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Width)>();
validate_mid1_width(mid1_width);
defer_mid1_width(mid1_width);
}
void EaxEqualizerEffect::defer_mid2_gain(
const EaxEaxCall& eax_call)
{
const auto& mid2_gain =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid2Gain)>();
validate_mid2_gain(mid2_gain);
defer_mid2_gain(mid2_gain);
}
void EaxEqualizerEffect::defer_mid2_center(
const EaxEaxCall& eax_call)
{
const auto& mid2_center =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Center)>();
validate_mid2_center(mid2_center);
defer_mid2_center(mid2_center);
}
void EaxEqualizerEffect::defer_mid2_width(
const EaxEaxCall& eax_call)
{
const auto& mid2_width =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Width)>();
validate_mid2_width(mid2_width);
defer_mid2_width(mid2_width);
}
void EaxEqualizerEffect::defer_high_gain(
const EaxEaxCall& eax_call)
{
const auto& high_gain =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lHighGain)>();
validate_high_gain(high_gain);
defer_high_gain(high_gain);
}
void EaxEqualizerEffect::defer_high_cutoff(
const EaxEaxCall& eax_call)
{
const auto& high_cutoff =
eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flHighCutOff)>();
validate_high_cutoff(high_cutoff);
defer_high_cutoff(high_cutoff);
}
void EaxEqualizerEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<EaxEqualizerEffectException, const EAXEQUALIZERPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxEqualizerEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxEqualizerEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.lLowGain)
{
set_efx_low_gain();
}
if (eax_dirty_flags_.flLowCutOff)
{
set_efx_low_cutoff();
}
if (eax_dirty_flags_.lMid1Gain)
{
set_efx_mid1_gain();
}
if (eax_dirty_flags_.flMid1Center)
{
set_efx_mid1_center();
}
if (eax_dirty_flags_.flMid1Width)
{
set_efx_mid1_width();
}
if (eax_dirty_flags_.lMid2Gain)
{
set_efx_mid2_gain();
}
if (eax_dirty_flags_.flMid2Center)
{
set_efx_mid2_center();
}
if (eax_dirty_flags_.flMid2Width)
{
set_efx_mid2_width();
}
if (eax_dirty_flags_.lHighGain)
{
set_efx_high_gain();
}
if (eax_dirty_flags_.flHighCutOff)
{
set_efx_high_cutoff();
}
eax_dirty_flags_ = EaxEqualizerEffectDirtyFlags{};
return true;
}
void EaxEqualizerEffect::set(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXEQUALIZER_NONE:
break;
case EAXEQUALIZER_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXEQUALIZER_LOWGAIN:
defer_low_gain(eax_call);
break;
case EAXEQUALIZER_LOWCUTOFF:
defer_low_cutoff(eax_call);
break;
case EAXEQUALIZER_MID1GAIN:
defer_mid1_gain(eax_call);
break;
case EAXEQUALIZER_MID1CENTER:
defer_mid1_center(eax_call);
break;
case EAXEQUALIZER_MID1WIDTH:
defer_mid1_width(eax_call);
break;
case EAXEQUALIZER_MID2GAIN:
defer_mid2_gain(eax_call);
break;
case EAXEQUALIZER_MID2CENTER:
defer_mid2_center(eax_call);
break;
case EAXEQUALIZER_MID2WIDTH:
defer_mid2_width(eax_call);
break;
case EAXEQUALIZER_HIGHGAIN:
defer_high_gain(eax_call);
break;
case EAXEQUALIZER_HIGHCUTOFF:
defer_high_cutoff(eax_call);
break;
default:
throw EaxEqualizerEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_equalizer_effect()
{
return std::make_unique<EaxEqualizerEffect>();
}
#endif // ALSOFT_EAX

View file

@ -1,12 +1,23 @@
#include "config.h" #include "config.h"
#include <stdexcept>
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h" #include "aloptional.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -126,3 +137,343 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Fshifter); DEFINE_ALEFFECT_VTABLE(Fshifter);
const EffectProps FshifterEffectProps{genDefaultProps()}; const EffectProps FshifterEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxFrequencyShifterEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxFrequencyShifterEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxFrequencyShifterEffectDirtyFlagsValue flFrequency : 1;
EaxFrequencyShifterEffectDirtyFlagsValue ulLeftDirection : 1;
EaxFrequencyShifterEffectDirtyFlagsValue ulRightDirection : 1;
}; // EaxFrequencyShifterEffectDirtyFlags
class EaxFrequencyShifterEffect final :
public EaxEffect
{
public:
EaxFrequencyShifterEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXFREQUENCYSHIFTERPROPERTIES eax_{};
EAXFREQUENCYSHIFTERPROPERTIES eax_d_{};
EaxFrequencyShifterEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_frequency();
void set_efx_left_direction();
void set_efx_right_direction();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_frequency(float flFrequency);
void validate_left_direction(unsigned long ulLeftDirection);
void validate_right_direction(unsigned long ulRightDirection);
void validate_all(const EAXFREQUENCYSHIFTERPROPERTIES& all);
void defer_frequency(float flFrequency);
void defer_left_direction(unsigned long ulLeftDirection);
void defer_right_direction(unsigned long ulRightDirection);
void defer_all(const EAXFREQUENCYSHIFTERPROPERTIES& all);
void defer_frequency(const EaxEaxCall& eax_call);
void defer_left_direction(const EaxEaxCall& eax_call);
void defer_right_direction(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxFrequencyShifterEffect
class EaxFrequencyShifterEffectException :
public EaxException
{
public:
explicit EaxFrequencyShifterEffectException(
const char* message)
:
EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message}
{
}
}; // EaxFrequencyShifterEffectException
EaxFrequencyShifterEffect::EaxFrequencyShifterEffect()
: EaxEffect{AL_EFFECT_FREQUENCY_SHIFTER}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxFrequencyShifterEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxFrequencyShifterEffect::set_eax_defaults()
{
eax_.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY;
eax_.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION;
eax_.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION;
eax_d_ = eax_;
}
void EaxFrequencyShifterEffect::set_efx_frequency()
{
const auto frequency = clamp(
eax_.flFrequency,
AL_FREQUENCY_SHIFTER_MIN_FREQUENCY,
AL_FREQUENCY_SHIFTER_MAX_FREQUENCY);
al_effect_props_.Fshifter.Frequency = frequency;
}
void EaxFrequencyShifterEffect::set_efx_left_direction()
{
const auto left_direction = clamp(
static_cast<ALint>(eax_.ulLeftDirection),
AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION,
AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION);
const auto efx_left_direction = DirectionFromEmum(left_direction);
assert(efx_left_direction.has_value());
al_effect_props_.Fshifter.LeftDirection = *efx_left_direction;
}
void EaxFrequencyShifterEffect::set_efx_right_direction()
{
const auto right_direction = clamp(
static_cast<ALint>(eax_.ulRightDirection),
AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION,
AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION);
const auto efx_right_direction = DirectionFromEmum(right_direction);
assert(efx_right_direction.has_value());
al_effect_props_.Fshifter.RightDirection = *efx_right_direction;
}
void EaxFrequencyShifterEffect::set_efx_defaults()
{
set_efx_frequency();
set_efx_left_direction();
set_efx_right_direction();
}
void EaxFrequencyShifterEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXFREQUENCYSHIFTER_NONE:
break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS:
eax_call.set_value<EaxFrequencyShifterEffectException>(eax_);
break;
case EAXFREQUENCYSHIFTER_FREQUENCY:
eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.flFrequency);
break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION:
eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulLeftDirection);
break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION:
eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulRightDirection);
break;
default:
throw EaxFrequencyShifterEffectException{"Unsupported property id."};
}
}
void EaxFrequencyShifterEffect::validate_frequency(
float flFrequency)
{
eax_validate_range<EaxFrequencyShifterEffectException>(
"Frequency",
flFrequency,
EAXFREQUENCYSHIFTER_MINFREQUENCY,
EAXFREQUENCYSHIFTER_MAXFREQUENCY);
}
void EaxFrequencyShifterEffect::validate_left_direction(
unsigned long ulLeftDirection)
{
eax_validate_range<EaxFrequencyShifterEffectException>(
"Left Direction",
ulLeftDirection,
EAXFREQUENCYSHIFTER_MINLEFTDIRECTION,
EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION);
}
void EaxFrequencyShifterEffect::validate_right_direction(
unsigned long ulRightDirection)
{
eax_validate_range<EaxFrequencyShifterEffectException>(
"Right Direction",
ulRightDirection,
EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION,
EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION);
}
void EaxFrequencyShifterEffect::validate_all(
const EAXFREQUENCYSHIFTERPROPERTIES& all)
{
validate_frequency(all.flFrequency);
validate_left_direction(all.ulLeftDirection);
validate_right_direction(all.ulRightDirection);
}
void EaxFrequencyShifterEffect::defer_frequency(
float flFrequency)
{
eax_d_.flFrequency = flFrequency;
eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency);
}
void EaxFrequencyShifterEffect::defer_left_direction(
unsigned long ulLeftDirection)
{
eax_d_.ulLeftDirection = ulLeftDirection;
eax_dirty_flags_.ulLeftDirection = (eax_.ulLeftDirection != eax_d_.ulLeftDirection);
}
void EaxFrequencyShifterEffect::defer_right_direction(
unsigned long ulRightDirection)
{
eax_d_.ulRightDirection = ulRightDirection;
eax_dirty_flags_.ulRightDirection = (eax_.ulRightDirection != eax_d_.ulRightDirection);
}
void EaxFrequencyShifterEffect::defer_all(
const EAXFREQUENCYSHIFTERPROPERTIES& all)
{
defer_frequency(all.flFrequency);
defer_left_direction(all.ulLeftDirection);
defer_right_direction(all.ulRightDirection);
}
void EaxFrequencyShifterEffect::defer_frequency(
const EaxEaxCall& eax_call)
{
const auto& frequency =
eax_call.get_value<
EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::flFrequency)>();
validate_frequency(frequency);
defer_frequency(frequency);
}
void EaxFrequencyShifterEffect::defer_left_direction(
const EaxEaxCall& eax_call)
{
const auto& left_direction =
eax_call.get_value<
EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulLeftDirection)>();
validate_left_direction(left_direction);
defer_left_direction(left_direction);
}
void EaxFrequencyShifterEffect::defer_right_direction(
const EaxEaxCall& eax_call)
{
const auto& right_direction =
eax_call.get_value<
EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulRightDirection)>();
validate_right_direction(right_direction);
defer_right_direction(right_direction);
}
void EaxFrequencyShifterEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<
EaxFrequencyShifterEffectException, const EAXFREQUENCYSHIFTERPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxFrequencyShifterEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxFrequencyShifterEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.flFrequency)
{
set_efx_frequency();
}
if (eax_dirty_flags_.ulLeftDirection)
{
set_efx_left_direction();
}
if (eax_dirty_flags_.ulRightDirection)
{
set_efx_right_direction();
}
eax_dirty_flags_ = EaxFrequencyShifterEffectDirtyFlags{};
return true;
}
void EaxFrequencyShifterEffect::set(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXFREQUENCYSHIFTER_NONE:
break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXFREQUENCYSHIFTER_FREQUENCY:
defer_frequency(eax_call);
break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION:
defer_left_direction(eax_call);
break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION:
defer_right_direction(eax_call);
break;
default:
throw EaxFrequencyShifterEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_frequency_shifter_effect()
{
return std::make_unique<EaxFrequencyShifterEffect>();
}
#endif // ALSOFT_EAX

View file

@ -1,12 +1,23 @@
#include "config.h" #include "config.h"
#include <stdexcept>
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h" #include "aloptional.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -132,3 +143,340 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Modulator); DEFINE_ALEFFECT_VTABLE(Modulator);
const EffectProps ModulatorEffectProps{genDefaultProps()}; const EffectProps ModulatorEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxRingModulatorEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxRingModulatorEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxRingModulatorEffectDirtyFlagsValue flFrequency : 1;
EaxRingModulatorEffectDirtyFlagsValue flHighPassCutOff : 1;
EaxRingModulatorEffectDirtyFlagsValue ulWaveform : 1;
}; // EaxPitchShifterEffectDirtyFlags
class EaxRingModulatorEffect final :
public EaxEffect
{
public:
EaxRingModulatorEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXRINGMODULATORPROPERTIES eax_{};
EAXRINGMODULATORPROPERTIES eax_d_{};
EaxRingModulatorEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_frequency();
void set_efx_high_pass_cutoff();
void set_efx_waveform();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_frequency(float flFrequency);
void validate_high_pass_cutoff(float flHighPassCutOff);
void validate_waveform(unsigned long ulWaveform);
void validate_all(const EAXRINGMODULATORPROPERTIES& all);
void defer_frequency(float flFrequency);
void defer_high_pass_cutoff(float flHighPassCutOff);
void defer_waveform(unsigned long ulWaveform);
void defer_all(const EAXRINGMODULATORPROPERTIES& all);
void defer_frequency(const EaxEaxCall& eax_call);
void defer_high_pass_cutoff(const EaxEaxCall& eax_call);
void defer_waveform(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxRingModulatorEffect
class EaxRingModulatorEffectException :
public EaxException
{
public:
explicit EaxRingModulatorEffectException(
const char* message)
:
EaxException{"EAX_RING_MODULATOR_EFFECT", message}
{
}
}; // EaxRingModulatorEffectException
EaxRingModulatorEffect::EaxRingModulatorEffect()
: EaxEffect{AL_EFFECT_RING_MODULATOR}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxRingModulatorEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxRingModulatorEffect::set_eax_defaults()
{
eax_.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY;
eax_.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF;
eax_.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM;
eax_d_ = eax_;
}
void EaxRingModulatorEffect::set_efx_frequency()
{
const auto frequency = clamp(
eax_.flFrequency,
AL_RING_MODULATOR_MIN_FREQUENCY,
AL_RING_MODULATOR_MAX_FREQUENCY);
al_effect_props_.Modulator.Frequency = frequency;
}
void EaxRingModulatorEffect::set_efx_high_pass_cutoff()
{
const auto high_pass_cutoff = clamp(
eax_.flHighPassCutOff,
AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF,
AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF);
al_effect_props_.Modulator.HighPassCutoff = high_pass_cutoff;
}
void EaxRingModulatorEffect::set_efx_waveform()
{
const auto waveform = clamp(
static_cast<ALint>(eax_.ulWaveform),
AL_RING_MODULATOR_MIN_WAVEFORM,
AL_RING_MODULATOR_MAX_WAVEFORM);
const auto efx_waveform = WaveformFromEmum(waveform);
assert(efx_waveform.has_value());
al_effect_props_.Modulator.Waveform = *efx_waveform;
}
void EaxRingModulatorEffect::set_efx_defaults()
{
set_efx_frequency();
set_efx_high_pass_cutoff();
set_efx_waveform();
}
void EaxRingModulatorEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXRINGMODULATOR_NONE:
break;
case EAXRINGMODULATOR_ALLPARAMETERS:
eax_call.set_value<EaxRingModulatorEffectException>(eax_);
break;
case EAXRINGMODULATOR_FREQUENCY:
eax_call.set_value<EaxRingModulatorEffectException>(eax_.flFrequency);
break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF:
eax_call.set_value<EaxRingModulatorEffectException>(eax_.flHighPassCutOff);
break;
case EAXRINGMODULATOR_WAVEFORM:
eax_call.set_value<EaxRingModulatorEffectException>(eax_.ulWaveform);
break;
default:
throw EaxRingModulatorEffectException{"Unsupported property id."};
}
}
void EaxRingModulatorEffect::validate_frequency(
float flFrequency)
{
eax_validate_range<EaxRingModulatorEffectException>(
"Frequency",
flFrequency,
EAXRINGMODULATOR_MINFREQUENCY,
EAXRINGMODULATOR_MAXFREQUENCY);
}
void EaxRingModulatorEffect::validate_high_pass_cutoff(
float flHighPassCutOff)
{
eax_validate_range<EaxRingModulatorEffectException>(
"High-Pass Cutoff",
flHighPassCutOff,
EAXRINGMODULATOR_MINHIGHPASSCUTOFF,
EAXRINGMODULATOR_MAXHIGHPASSCUTOFF);
}
void EaxRingModulatorEffect::validate_waveform(
unsigned long ulWaveform)
{
eax_validate_range<EaxRingModulatorEffectException>(
"Waveform",
ulWaveform,
EAXRINGMODULATOR_MINWAVEFORM,
EAXRINGMODULATOR_MAXWAVEFORM);
}
void EaxRingModulatorEffect::validate_all(
const EAXRINGMODULATORPROPERTIES& all)
{
validate_frequency(all.flFrequency);
validate_high_pass_cutoff(all.flHighPassCutOff);
validate_waveform(all.ulWaveform);
}
void EaxRingModulatorEffect::defer_frequency(
float flFrequency)
{
eax_d_.flFrequency = flFrequency;
eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency);
}
void EaxRingModulatorEffect::defer_high_pass_cutoff(
float flHighPassCutOff)
{
eax_d_.flHighPassCutOff = flHighPassCutOff;
eax_dirty_flags_.flHighPassCutOff = (eax_.flHighPassCutOff != eax_d_.flHighPassCutOff);
}
void EaxRingModulatorEffect::defer_waveform(
unsigned long ulWaveform)
{
eax_d_.ulWaveform = ulWaveform;
eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
}
void EaxRingModulatorEffect::defer_all(
const EAXRINGMODULATORPROPERTIES& all)
{
defer_frequency(all.flFrequency);
defer_high_pass_cutoff(all.flHighPassCutOff);
defer_waveform(all.ulWaveform);
}
void EaxRingModulatorEffect::defer_frequency(
const EaxEaxCall& eax_call)
{
const auto& frequency =
eax_call.get_value<
EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flFrequency)>();
validate_frequency(frequency);
defer_frequency(frequency);
}
void EaxRingModulatorEffect::defer_high_pass_cutoff(
const EaxEaxCall& eax_call)
{
const auto& high_pass_cutoff =
eax_call.get_value<
EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flHighPassCutOff)>();
validate_high_pass_cutoff(high_pass_cutoff);
defer_high_pass_cutoff(high_pass_cutoff);
}
void EaxRingModulatorEffect::defer_waveform(
const EaxEaxCall& eax_call)
{
const auto& waveform =
eax_call.get_value<
EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::ulWaveform)>();
validate_waveform(waveform);
defer_waveform(waveform);
}
void EaxRingModulatorEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<EaxRingModulatorEffectException, const EAXRINGMODULATORPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxRingModulatorEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxRingModulatorEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.flFrequency)
{
set_efx_frequency();
}
if (eax_dirty_flags_.flHighPassCutOff)
{
set_efx_high_pass_cutoff();
}
if (eax_dirty_flags_.ulWaveform)
{
set_efx_waveform();
}
eax_dirty_flags_ = EaxRingModulatorEffectDirtyFlags{};
return true;
}
void EaxRingModulatorEffect::set(const EaxEaxCall& eax_call)
{
switch (eax_call.get_property_id())
{
case EAXRINGMODULATOR_NONE:
break;
case EAXRINGMODULATOR_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXRINGMODULATOR_FREQUENCY:
defer_frequency(eax_call);
break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF:
defer_high_pass_cutoff(eax_call);
break;
case EAXRINGMODULATOR_WAVEFORM:
defer_waveform(eax_call);
break;
default:
throw EaxRingModulatorEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_ring_modulator_effect()
{
return std::make_unique<EaxRingModulatorEffect>();
}
#endif // ALSOFT_EAX

View file

@ -4,8 +4,12 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include "al/eax_exception.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -91,3 +95,58 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Null); DEFINE_ALEFFECT_VTABLE(Null);
const EffectProps NullEffectProps{genDefaultProps()}; const EffectProps NullEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
class EaxNullEffect final :
public EaxEffect
{
public:
EaxNullEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
}; // EaxNullEffect
class EaxNullEffectException :
public EaxException
{
public:
explicit EaxNullEffectException(
const char* message)
:
EaxException{"EAX_NULL_EFFECT", message}
{
}
}; // EaxNullEffectException
EaxNullEffect::EaxNullEffect()
: EaxEffect{AL_EFFECT_NULL}
{
}
void EaxNullEffect::dispatch(const EaxEaxCall& eax_call)
{
if(eax_call.get_property_id() != 0)
throw EaxNullEffectException{"Unsupported property id."};
}
bool EaxNullEffect::apply_deferred()
{
return false;
}
} // namespace
EaxEffectUPtr eax_create_eax_null_effect()
{
return std::make_unique<EaxNullEffect>();
}
#endif // ALSOFT_EAX

View file

@ -4,8 +4,15 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -82,3 +89,276 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Pshifter); DEFINE_ALEFFECT_VTABLE(Pshifter);
const EffectProps PshifterEffectProps{genDefaultProps()}; const EffectProps PshifterEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxPitchShifterEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxPitchShifterEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxPitchShifterEffectDirtyFlagsValue lCoarseTune : 1;
EaxPitchShifterEffectDirtyFlagsValue lFineTune : 1;
}; // EaxPitchShifterEffectDirtyFlags
class EaxPitchShifterEffect final :
public EaxEffect
{
public:
EaxPitchShifterEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXPITCHSHIFTERPROPERTIES eax_{};
EAXPITCHSHIFTERPROPERTIES eax_d_{};
EaxPitchShifterEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_coarse_tune();
void set_efx_fine_tune();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_coarse_tune(long lCoarseTune);
void validate_fine_tune(long lFineTune);
void validate_all(const EAXPITCHSHIFTERPROPERTIES& all);
void defer_coarse_tune(long lCoarseTune);
void defer_fine_tune(long lFineTune);
void defer_all(const EAXPITCHSHIFTERPROPERTIES& all);
void defer_coarse_tune(const EaxEaxCall& eax_call);
void defer_fine_tune(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxPitchShifterEffect
class EaxPitchShifterEffectException :
public EaxException
{
public:
explicit EaxPitchShifterEffectException(
const char* message)
:
EaxException{"EAX_PITCH_SHIFTER_EFFECT", message}
{
}
}; // EaxPitchShifterEffectException
EaxPitchShifterEffect::EaxPitchShifterEffect()
: EaxEffect{AL_EFFECT_PITCH_SHIFTER}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxPitchShifterEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxPitchShifterEffect::set_eax_defaults()
{
eax_.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE;
eax_.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE;
eax_d_ = eax_;
}
void EaxPitchShifterEffect::set_efx_coarse_tune()
{
const auto coarse_tune = clamp(
static_cast<ALint>(eax_.lCoarseTune),
AL_PITCH_SHIFTER_MIN_COARSE_TUNE,
AL_PITCH_SHIFTER_MAX_COARSE_TUNE);
al_effect_props_.Pshifter.CoarseTune = coarse_tune;
}
void EaxPitchShifterEffect::set_efx_fine_tune()
{
const auto fine_tune = clamp(
static_cast<ALint>(eax_.lFineTune),
AL_PITCH_SHIFTER_MIN_FINE_TUNE,
AL_PITCH_SHIFTER_MAX_FINE_TUNE);
al_effect_props_.Pshifter.FineTune = fine_tune;
}
void EaxPitchShifterEffect::set_efx_defaults()
{
set_efx_coarse_tune();
set_efx_fine_tune();
}
void EaxPitchShifterEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXPITCHSHIFTER_NONE:
break;
case EAXPITCHSHIFTER_ALLPARAMETERS:
eax_call.set_value<EaxPitchShifterEffectException>(eax_);
break;
case EAXPITCHSHIFTER_COARSETUNE:
eax_call.set_value<EaxPitchShifterEffectException>(eax_.lCoarseTune);
break;
case EAXPITCHSHIFTER_FINETUNE:
eax_call.set_value<EaxPitchShifterEffectException>(eax_.lFineTune);
break;
default:
throw EaxPitchShifterEffectException{"Unsupported property id."};
}
}
void EaxPitchShifterEffect::validate_coarse_tune(
long lCoarseTune)
{
eax_validate_range<EaxPitchShifterEffectException>(
"Coarse Tune",
lCoarseTune,
EAXPITCHSHIFTER_MINCOARSETUNE,
EAXPITCHSHIFTER_MAXCOARSETUNE);
}
void EaxPitchShifterEffect::validate_fine_tune(
long lFineTune)
{
eax_validate_range<EaxPitchShifterEffectException>(
"Fine Tune",
lFineTune,
EAXPITCHSHIFTER_MINFINETUNE,
EAXPITCHSHIFTER_MAXFINETUNE);
}
void EaxPitchShifterEffect::validate_all(
const EAXPITCHSHIFTERPROPERTIES& all)
{
validate_coarse_tune(all.lCoarseTune);
validate_fine_tune(all.lFineTune);
}
void EaxPitchShifterEffect::defer_coarse_tune(
long lCoarseTune)
{
eax_d_.lCoarseTune = lCoarseTune;
eax_dirty_flags_.lCoarseTune = (eax_.lCoarseTune != eax_d_.lCoarseTune);
}
void EaxPitchShifterEffect::defer_fine_tune(
long lFineTune)
{
eax_d_.lFineTune = lFineTune;
eax_dirty_flags_.lFineTune = (eax_.lFineTune != eax_d_.lFineTune);
}
void EaxPitchShifterEffect::defer_all(
const EAXPITCHSHIFTERPROPERTIES& all)
{
defer_coarse_tune(all.lCoarseTune);
defer_fine_tune(all.lFineTune);
}
void EaxPitchShifterEffect::defer_coarse_tune(
const EaxEaxCall& eax_call)
{
const auto& coarse_tune =
eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lCoarseTune)>();
validate_coarse_tune(coarse_tune);
defer_coarse_tune(coarse_tune);
}
void EaxPitchShifterEffect::defer_fine_tune(
const EaxEaxCall& eax_call)
{
const auto& fine_tune =
eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lFineTune)>();
validate_fine_tune(fine_tune);
defer_fine_tune(fine_tune);
}
void EaxPitchShifterEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all =
eax_call.get_value<EaxPitchShifterEffectException, const EAXPITCHSHIFTERPROPERTIES>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxPitchShifterEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxPitchShifterEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.lCoarseTune)
{
set_efx_coarse_tune();
}
if (eax_dirty_flags_.lFineTune)
{
set_efx_fine_tune();
}
eax_dirty_flags_ = EaxPitchShifterEffectDirtyFlags{};
return true;
}
void EaxPitchShifterEffect::set(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXPITCHSHIFTER_NONE:
break;
case EAXPITCHSHIFTER_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXPITCHSHIFTER_COARSETUNE:
defer_coarse_tune(eax_call);
break;
case EAXPITCHSHIFTER_FINETUNE:
defer_fine_tune(eax_call);
break;
default:
throw EaxPitchShifterEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_pitch_shifter_effect()
{
return std::make_unique<EaxPitchShifterEffect>();
}
#endif // ALSOFT_EAX

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,23 @@
#include "config.h" #include "config.h"
#include <stdexcept>
#include "AL/al.h" #include "AL/al.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h" #include "aloptional.h"
#include "effects.h" #include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -245,3 +256,531 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Vmorpher); DEFINE_ALEFFECT_VTABLE(Vmorpher);
const EffectProps VmorpherEffectProps{genDefaultProps()}; const EffectProps VmorpherEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t;
struct EaxVocalMorpherEffectDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1;
EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1;
EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1;
EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1;
EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1;
EaxVocalMorpherEffectDirtyFlagsValue flRate : 1;
}; // EaxPitchShifterEffectDirtyFlags
class EaxVocalMorpherEffect final :
public EaxEffect
{
public:
EaxVocalMorpherEffect();
void dispatch(const EaxEaxCall& eax_call) override;
// [[nodiscard]]
bool apply_deferred() override;
private:
EAXVOCALMORPHERPROPERTIES eax_{};
EAXVOCALMORPHERPROPERTIES eax_d_{};
EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{};
void set_eax_defaults();
void set_efx_phoneme_a();
void set_efx_phoneme_a_coarse_tuning();
void set_efx_phoneme_b();
void set_efx_phoneme_b_coarse_tuning();
void set_efx_waveform();
void set_efx_rate();
void set_efx_defaults();
void get(const EaxEaxCall& eax_call);
void validate_phoneme_a(unsigned long ulPhonemeA);
void validate_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning);
void validate_phoneme_b(unsigned long ulPhonemeB);
void validate_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning);
void validate_waveform(unsigned long ulWaveform);
void validate_rate(float flRate);
void validate_all(const EAXVOCALMORPHERPROPERTIES& all);
void defer_phoneme_a(unsigned long ulPhonemeA);
void defer_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning);
void defer_phoneme_b(unsigned long ulPhonemeB);
void defer_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning);
void defer_waveform(unsigned long ulWaveform);
void defer_rate(float flRate);
void defer_all(const EAXVOCALMORPHERPROPERTIES& all);
void defer_phoneme_a(const EaxEaxCall& eax_call);
void defer_phoneme_a_coarse_tuning(const EaxEaxCall& eax_call);
void defer_phoneme_b(const EaxEaxCall& eax_call);
void defer_phoneme_b_coarse_tuning(const EaxEaxCall& eax_call);
void defer_waveform(const EaxEaxCall& eax_call);
void defer_rate(const EaxEaxCall& eax_call);
void defer_all(const EaxEaxCall& eax_call);
void set(const EaxEaxCall& eax_call);
}; // EaxVocalMorpherEffect
class EaxVocalMorpherEffectException :
public EaxException
{
public:
explicit EaxVocalMorpherEffectException(
const char* message)
:
EaxException{"EAX_VOCAL_MORPHER_EFFECT", message}
{
}
}; // EaxVocalMorpherEffectException
EaxVocalMorpherEffect::EaxVocalMorpherEffect()
: EaxEffect{AL_EFFECT_VOCAL_MORPHER}
{
set_eax_defaults();
set_efx_defaults();
}
void EaxVocalMorpherEffect::dispatch(const EaxEaxCall& eax_call)
{
eax_call.is_get() ? get(eax_call) : set(eax_call);
}
void EaxVocalMorpherEffect::set_eax_defaults()
{
eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE;
eax_d_ = eax_;
}
void EaxVocalMorpherEffect::set_efx_phoneme_a()
{
const auto phoneme_a = clamp(
static_cast<ALint>(eax_.ulPhonemeA),
AL_VOCAL_MORPHER_MIN_PHONEMEA,
AL_VOCAL_MORPHER_MAX_PHONEMEA);
const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a);
assert(efx_phoneme_a.has_value());
al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a;
}
void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning()
{
const auto phoneme_a_coarse_tuning = clamp(
static_cast<ALint>(eax_.lPhonemeACoarseTuning),
AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING,
AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING);
al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning;
}
void EaxVocalMorpherEffect::set_efx_phoneme_b()
{
const auto phoneme_b = clamp(
static_cast<ALint>(eax_.ulPhonemeB),
AL_VOCAL_MORPHER_MIN_PHONEMEB,
AL_VOCAL_MORPHER_MAX_PHONEMEB);
const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b);
assert(efx_phoneme_b.has_value());
al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b;
}
void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning()
{
const auto phoneme_b_coarse_tuning = clamp(
static_cast<ALint>(eax_.lPhonemeBCoarseTuning),
AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING,
AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING);
al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning;
}
void EaxVocalMorpherEffect::set_efx_waveform()
{
const auto waveform = clamp(
static_cast<ALint>(eax_.ulWaveform),
AL_VOCAL_MORPHER_MIN_WAVEFORM,
AL_VOCAL_MORPHER_MAX_WAVEFORM);
const auto wfx_waveform = WaveformFromEmum(waveform);
assert(wfx_waveform.has_value());
al_effect_props_.Vmorpher.Waveform = *wfx_waveform;
}
void EaxVocalMorpherEffect::set_efx_rate()
{
const auto rate = clamp(
eax_.flRate,
AL_VOCAL_MORPHER_MIN_RATE,
AL_VOCAL_MORPHER_MAX_RATE);
al_effect_props_.Vmorpher.Rate = rate;
}
void EaxVocalMorpherEffect::set_efx_defaults()
{
set_efx_phoneme_a();
set_efx_phoneme_a_coarse_tuning();
set_efx_phoneme_b();
set_efx_phoneme_b_coarse_tuning();
set_efx_waveform();
set_efx_rate();
}
void EaxVocalMorpherEffect::get(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXVOCALMORPHER_NONE:
break;
case EAXVOCALMORPHER_ALLPARAMETERS:
eax_call.set_value<EaxVocalMorpherEffectException>(eax_);
break;
case EAXVOCALMORPHER_PHONEMEA:
eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeA);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeACoarseTuning);
break;
case EAXVOCALMORPHER_PHONEMEB:
eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeB);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeBCoarseTuning);
break;
case EAXVOCALMORPHER_WAVEFORM:
eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulWaveform);
break;
case EAXVOCALMORPHER_RATE:
eax_call.set_value<EaxVocalMorpherEffectException>(eax_.flRate);
break;
default:
throw EaxVocalMorpherEffectException{"Unsupported property id."};
}
}
void EaxVocalMorpherEffect::validate_phoneme_a(
unsigned long ulPhonemeA)
{
eax_validate_range<EaxVocalMorpherEffectException>(
"Phoneme A",
ulPhonemeA,
EAXVOCALMORPHER_MINPHONEMEA,
EAXVOCALMORPHER_MAXPHONEMEA);
}
void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning(
long lPhonemeACoarseTuning)
{
eax_validate_range<EaxVocalMorpherEffectException>(
"Phoneme A Coarse Tuning",
lPhonemeACoarseTuning,
EAXVOCALMORPHER_MINPHONEMEACOARSETUNING,
EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING);
}
void EaxVocalMorpherEffect::validate_phoneme_b(
unsigned long ulPhonemeB)
{
eax_validate_range<EaxVocalMorpherEffectException>(
"Phoneme B",
ulPhonemeB,
EAXVOCALMORPHER_MINPHONEMEB,
EAXVOCALMORPHER_MAXPHONEMEB);
}
void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning(
long lPhonemeBCoarseTuning)
{
eax_validate_range<EaxVocalMorpherEffectException>(
"Phoneme B Coarse Tuning",
lPhonemeBCoarseTuning,
EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING,
EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING);
}
void EaxVocalMorpherEffect::validate_waveform(
unsigned long ulWaveform)
{
eax_validate_range<EaxVocalMorpherEffectException>(
"Waveform",
ulWaveform,
EAXVOCALMORPHER_MINWAVEFORM,
EAXVOCALMORPHER_MAXWAVEFORM);
}
void EaxVocalMorpherEffect::validate_rate(
float flRate)
{
eax_validate_range<EaxVocalMorpherEffectException>(
"Rate",
flRate,
EAXVOCALMORPHER_MINRATE,
EAXVOCALMORPHER_MAXRATE);
}
void EaxVocalMorpherEffect::validate_all(
const EAXVOCALMORPHERPROPERTIES& all)
{
validate_phoneme_a(all.ulPhonemeA);
validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
validate_phoneme_b(all.ulPhonemeB);
validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
validate_waveform(all.ulWaveform);
validate_rate(all.flRate);
}
void EaxVocalMorpherEffect::defer_phoneme_a(
unsigned long ulPhonemeA)
{
eax_d_.ulPhonemeA = ulPhonemeA;
eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA);
}
void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
long lPhonemeACoarseTuning)
{
eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning;
eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning);
}
void EaxVocalMorpherEffect::defer_phoneme_b(
unsigned long ulPhonemeB)
{
eax_d_.ulPhonemeB = ulPhonemeB;
eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB);
}
void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
long lPhonemeBCoarseTuning)
{
eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning;
eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning);
}
void EaxVocalMorpherEffect::defer_waveform(
unsigned long ulWaveform)
{
eax_d_.ulWaveform = ulWaveform;
eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
}
void EaxVocalMorpherEffect::defer_rate(
float flRate)
{
eax_d_.flRate = flRate;
eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
}
void EaxVocalMorpherEffect::defer_all(
const EAXVOCALMORPHERPROPERTIES& all)
{
defer_phoneme_a(all.ulPhonemeA);
defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
defer_phoneme_b(all.ulPhonemeB);
defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
defer_waveform(all.ulWaveform);
defer_rate(all.flRate);
}
void EaxVocalMorpherEffect::defer_phoneme_a(
const EaxEaxCall& eax_call)
{
const auto& phoneme_a = eax_call.get_value<EaxVocalMorpherEffectException,
const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeA)>();
validate_phoneme_a(phoneme_a);
defer_phoneme_a(phoneme_a);
}
void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
const EaxEaxCall& eax_call)
{
const auto& phoneme_a_coarse_tuning = eax_call.get_value<
EaxVocalMorpherEffectException,
const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning)
>();
validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
}
void EaxVocalMorpherEffect::defer_phoneme_b(
const EaxEaxCall& eax_call)
{
const auto& phoneme_b = eax_call.get_value<
EaxVocalMorpherEffectException,
const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB)
>();
validate_phoneme_b(phoneme_b);
defer_phoneme_b(phoneme_b);
}
void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
const EaxEaxCall& eax_call)
{
const auto& phoneme_b_coarse_tuning = eax_call.get_value<
EaxVocalMorpherEffectException,
const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning)
>();
validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
}
void EaxVocalMorpherEffect::defer_waveform(
const EaxEaxCall& eax_call)
{
const auto& waveform = eax_call.get_value<
EaxVocalMorpherEffectException,
const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform)
>();
validate_waveform(waveform);
defer_waveform(waveform);
}
void EaxVocalMorpherEffect::defer_rate(
const EaxEaxCall& eax_call)
{
const auto& rate = eax_call.get_value<
EaxVocalMorpherEffectException,
const decltype(EAXVOCALMORPHERPROPERTIES::flRate)
>();
validate_rate(rate);
defer_rate(rate);
}
void EaxVocalMorpherEffect::defer_all(
const EaxEaxCall& eax_call)
{
const auto& all = eax_call.get_value<
EaxVocalMorpherEffectException,
const EAXVOCALMORPHERPROPERTIES
>();
validate_all(all);
defer_all(all);
}
// [[nodiscard]]
bool EaxVocalMorpherEffect::apply_deferred()
{
if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{})
{
return false;
}
eax_ = eax_d_;
if (eax_dirty_flags_.ulPhonemeA)
{
set_efx_phoneme_a();
}
if (eax_dirty_flags_.lPhonemeACoarseTuning)
{
set_efx_phoneme_a_coarse_tuning();
}
if (eax_dirty_flags_.ulPhonemeB)
{
set_efx_phoneme_b();
}
if (eax_dirty_flags_.lPhonemeBCoarseTuning)
{
set_efx_phoneme_b_coarse_tuning();
}
if (eax_dirty_flags_.ulWaveform)
{
set_efx_waveform();
}
if (eax_dirty_flags_.flRate)
{
set_efx_rate();
}
eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{};
return true;
}
void EaxVocalMorpherEffect::set(const EaxEaxCall& eax_call)
{
switch(eax_call.get_property_id())
{
case EAXVOCALMORPHER_NONE:
break;
case EAXVOCALMORPHER_ALLPARAMETERS:
defer_all(eax_call);
break;
case EAXVOCALMORPHER_PHONEMEA:
defer_phoneme_a(eax_call);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
defer_phoneme_a_coarse_tuning(eax_call);
break;
case EAXVOCALMORPHER_PHONEMEB:
defer_phoneme_b(eax_call);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
defer_phoneme_b_coarse_tuning(eax_call);
break;
case EAXVOCALMORPHER_WAVEFORM:
defer_waveform(eax_call);
break;
case EAXVOCALMORPHER_RATE:
defer_rate(eax_call);
break;
default:
throw EaxVocalMorpherEffectException{"Unsupported property id."};
}
}
} // namespace
EaxEffectUPtr eax_create_eax_vocal_morpher_effect()
{
return std::make_unique<EaxVocalMorpherEffect>();
}
#endif // ALSOFT_EAX

View file

@ -35,7 +35,7 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "alcontext.h" #include "alc/context.h"
#include "almalloc.h" #include "almalloc.h"
#include "core/except.h" #include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
@ -85,9 +85,9 @@ AL_API ALenum AL_APIENTRY alGetError(void)
START_API_FUNC START_API_FUNC
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) if(unlikely(!context))
{ {
constexpr ALenum deferror{AL_INVALID_OPERATION}; static constexpr ALenum deferror{AL_INVALID_OPERATION};
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
if(TrapALError) if(TrapALError)
{ {

View file

@ -18,24 +18,24 @@
#include "AL/alc.h" #include "AL/alc.h"
#include "albyte.h" #include "albyte.h"
#include "alcontext.h" #include "alc/context.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h"
#include "almalloc.h" #include "almalloc.h"
#include "async_event.h" #include "core/async_event.h"
#include "core/except.h" #include "core/except.h"
#include "core/logging.h" #include "core/logging.h"
#include "effects/base.h" #include "core/voice_change.h"
#include "inprogext.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "threads.h" #include "threads.h"
#include "voice_change.h"
static int EventThread(ALCcontext *context) static int EventThread(ALCcontext *context)
{ {
RingBuffer *ring{context->mAsyncEvents.get()}; RingBuffer *ring{context->mAsyncEvents.get()};
bool quitnow{false}; bool quitnow{false};
while LIKELY(!quitnow) while(likely(!quitnow))
{ {
auto evt_data = ring->getReadVector().first; auto evt_data = ring->getReadVector().first;
if(evt_data.len == 0) if(evt_data.len == 0)
@ -54,10 +54,10 @@ static int EventThread(ALCcontext *context)
al::destroy_at(evt_ptr); al::destroy_at(evt_ptr);
ring->readAdvance(1); ring->readAdvance(1);
quitnow = evt.EnumType == EventType_KillThread; quitnow = evt.EnumType == AsyncEvent::KillThread;
if UNLIKELY(quitnow) break; if(unlikely(quitnow)) break;
if(evt.EnumType == EventType_ReleaseEffectState) if(evt.EnumType == AsyncEvent::ReleaseEffectState)
{ {
evt.u.mEffectState->release(); evt.u.mEffectState->release();
continue; continue;
@ -66,41 +66,38 @@ static int EventThread(ALCcontext *context)
uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)}; uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)};
if(!context->mEventCb) continue; if(!context->mEventCb) continue;
if(evt.EnumType == EventType_SourceStateChange) if(evt.EnumType == AsyncEvent::SourceStateChange)
{ {
if(!(enabledevts&EventType_SourceStateChange)) if(!(enabledevts&AsyncEvent::SourceStateChange))
continue; continue;
ALuint state{}; ALuint state{};
std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)};
msg += " state has changed to "; msg += " state has changed to ";
switch(evt.u.srcstate.state) switch(evt.u.srcstate.state)
{ {
case VChangeState::Reset: case AsyncEvent::SrcState::Reset:
msg += "AL_INITIAL"; msg += "AL_INITIAL";
state = AL_INITIAL; state = AL_INITIAL;
break; break;
case VChangeState::Stop: case AsyncEvent::SrcState::Stop:
msg += "AL_STOPPED"; msg += "AL_STOPPED";
state = AL_STOPPED; state = AL_STOPPED;
break; break;
case VChangeState::Play: case AsyncEvent::SrcState::Play:
msg += "AL_PLAYING"; msg += "AL_PLAYING";
state = AL_PLAYING; state = AL_PLAYING;
break; break;
case VChangeState::Pause: case AsyncEvent::SrcState::Pause:
msg += "AL_PAUSED"; msg += "AL_PAUSED";
state = AL_PAUSED; state = AL_PAUSED;
break; break;
/* Shouldn't happen */
case VChangeState::Restart:
break;
} }
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id,
state, static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam); state, static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
} }
else if(evt.EnumType == EventType_BufferCompleted) else if(evt.EnumType == AsyncEvent::BufferCompleted)
{ {
if(!(enabledevts&EventType_BufferCompleted)) if(!(enabledevts&AsyncEvent::BufferCompleted))
continue; continue;
std::string msg{std::to_string(evt.u.bufcomp.count)}; std::string msg{std::to_string(evt.u.bufcomp.count)};
if(evt.u.bufcomp.count == 1) msg += " buffer completed"; if(evt.u.bufcomp.count == 1) msg += " buffer completed";
@ -109,9 +106,9 @@ static int EventThread(ALCcontext *context)
evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(), evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(),
context->mEventParam); context->mEventParam);
} }
else if(evt.EnumType == EventType_Disconnected) else if(evt.EnumType == AsyncEvent::Disconnected)
{ {
if(!(enabledevts&EventType_Disconnected)) if(!(enabledevts&AsyncEvent::Disconnected))
continue; continue;
context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0,
static_cast<ALsizei>(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg, static_cast<ALsizei>(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg,
@ -146,7 +143,7 @@ void StopEventThrd(ALCcontext *ctx)
evt_data = ring->getWriteVector().first; evt_data = ring->getWriteVector().first;
} while(evt_data.len == 0); } while(evt_data.len == 0);
} }
::new(evt_data.buf) AsyncEvent{EventType_KillThread}; al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), AsyncEvent::KillThread);
ring->writeAdvance(1); ring->writeAdvance(1);
ctx->mEventSem.post(); ctx->mEventSem.post();
@ -158,7 +155,7 @@ AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, A
START_API_FUNC START_API_FUNC
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if(unlikely(!context)) return;
if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count); if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
if(count <= 0) return; if(count <= 0) return;
@ -170,11 +167,11 @@ START_API_FUNC
[&flags](ALenum type) noexcept -> bool [&flags](ALenum type) noexcept -> bool
{ {
if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
flags |= EventType_BufferCompleted; flags |= AsyncEvent::BufferCompleted;
else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
flags |= EventType_SourceStateChange; flags |= AsyncEvent::SourceStateChange;
else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
flags |= EventType_Disconnected; flags |= AsyncEvent::Disconnected;
else else
return false; return false;
return true; return true;
@ -204,7 +201,7 @@ START_API_FUNC
/* Wait to ensure the event handler sees the changed flags before /* Wait to ensure the event handler sees the changed flags before
* returning. * returning.
*/ */
std::lock_guard<std::mutex>{context->mEventCbLock}; std::lock_guard<std::mutex> _{context->mEventCbLock};
} }
} }
END_API_FUNC END_API_FUNC
@ -213,7 +210,7 @@ AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *user
START_API_FUNC START_API_FUNC
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if(unlikely(!context)) return;
std::lock_guard<std::mutex> _{context->mPropLock}; std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEventCbLock}; std::lock_guard<std::mutex> __{context->mEventCbLock};

View file

@ -27,7 +27,7 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "alcontext.h" #include "alc/context.h"
#include "alstring.h" #include "alstring.h"
#include "core/except.h" #include "core/except.h"
#include "opthelpers.h" #include "opthelpers.h"
@ -37,7 +37,7 @@ AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
START_API_FUNC START_API_FUNC
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return AL_FALSE; if(unlikely(!context)) return AL_FALSE;
if(!extName) if(!extName)
SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer"); SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");

View file

@ -37,8 +37,8 @@
#include "AL/efx.h" #include "AL/efx.h"
#include "albit.h" #include "albit.h"
#include "alcmain.h" #include "alc/context.h"
#include "alcontext.h" #include "alc/device.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "core/except.h" #include "core/except.h"
@ -52,7 +52,11 @@ class filter_exception final : public al::base_exception {
ALenum mErrorCode; ALenum mErrorCode;
public: public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]] [[gnu::format(printf, 3, 4)]]
#endif
filter_exception(ALenum code, const char *msg, ...) : mErrorCode{code} filter_exception(ALenum code, const char *msg, ...) : mErrorCode{code}
{ {
std::va_list args; std::va_list args;
@ -63,8 +67,6 @@ public:
ALenum errorCode() const noexcept { return mErrorCode; } ALenum errorCode() const noexcept { return mErrorCode; }
}; };
#define FILTER_MIN_GAIN 0.0f
#define FILTER_MAX_GAIN 4.0f /* +12dB */
#define DEFINE_ALFILTER_VTABLE(T) \ #define DEFINE_ALFILTER_VTABLE(T) \
const ALfilter::Vtable T##_vtable = { \ const ALfilter::Vtable T##_vtable = { \
@ -84,7 +86,7 @@ void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val)
switch(param) switch(param)
{ {
case AL_LOWPASS_GAIN: case AL_LOWPASS_GAIN:
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN))
throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val};
filter->Gain = val; filter->Gain = val;
break; break;
@ -143,7 +145,7 @@ void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val)
switch(param) switch(param)
{ {
case AL_HIGHPASS_GAIN: case AL_HIGHPASS_GAIN:
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN))
throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val};
filter->Gain = val; filter->Gain = val;
break; break;
@ -202,7 +204,7 @@ void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val)
switch(param) switch(param)
{ {
case AL_BANDPASS_GAIN: case AL_BANDPASS_GAIN:
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN)) if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN))
throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val};
filter->Gain = val; filter->Gain = val;
break; break;
@ -351,12 +353,12 @@ ALfilter *AllocFilter(ALCdevice *device)
{ {
auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
[](const FilterSubList &entry) noexcept -> bool [](const FilterSubList &entry) noexcept -> bool
{ return entry.FreeMask != 0; } { return entry.FreeMask != 0; });
);
auto lidx = static_cast<ALuint>(std::distance(device->FilterList.begin(), sublist)); auto lidx = static_cast<ALuint>(std::distance(device->FilterList.begin(), sublist));
auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask)); auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
ASSUME(slidx < 64);
ALfilter *filter{::new(sublist->Filters + slidx) ALfilter{}}; ALfilter *filter{al::construct_at(sublist->Filters + slidx)};
InitFilterParams(filter, AL_FILTER_NULL); InitFilterParams(filter, AL_FILTER_NULL);
/* Add 1 to avoid filter ID 0. */ /* Add 1 to avoid filter ID 0. */
@ -404,8 +406,8 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Generating %d filters", n); context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
if UNLIKELY(n <= 0) return; if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock}; std::lock_guard<std::mutex> _{device->FilterLock};
if(!EnsureFilters(device, static_cast<ALuint>(n))) if(!EnsureFilters(device, static_cast<ALuint>(n)))
{ {
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s"); context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s");
@ -444,7 +446,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Deleting %d filters", n); context->setError(AL_INVALID_VALUE, "Deleting %d filters", n);
if UNLIKELY(n <= 0) return; if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
/* First try to find any filters that are invalid. */ /* First try to find any filters that are invalid. */
@ -475,7 +477,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if LIKELY(context) if LIKELY(context)
{ {
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
if(!filter || LookupFilter(device, filter)) if(!filter || LookupFilter(device, filter))
return AL_TRUE; return AL_TRUE;
@ -491,7 +493,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
@ -532,7 +534,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
@ -555,7 +557,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
@ -578,7 +580,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)}; ALfilter *alfilt{LookupFilter(device, filter)};
@ -601,7 +603,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};
@ -636,7 +638,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};
@ -659,7 +661,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};
@ -682,7 +684,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()}; ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock}; std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)}; const ALfilter *alfilt{LookupFilter(device, filter)};

View file

@ -1,13 +1,13 @@
#ifndef AL_FILTER_H #ifndef AL_FILTER_H
#define AL_FILTER_H #define AL_FILTER_H
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "AL/alext.h" #include "AL/alext.h"
#include "almalloc.h" #include "almalloc.h"
#define LOWPASSFREQREF 5000.0f #define LOWPASSFREQREF 5000.0f
#define HIGHPASSFREQREF 250.0f #define HIGHPASSFREQREF 250.0f

View file

@ -29,20 +29,53 @@
#include "AL/alc.h" #include "AL/alc.h"
#include "AL/efx.h" #include "AL/efx.h"
#include "alcontext.h" #include "alc/context.h"
#include "almalloc.h" #include "almalloc.h"
#include "atomic.h" #include "atomic.h"
#include "core/except.h" #include "core/except.h"
#include "opthelpers.h" #include "opthelpers.h"
#define DO_UPDATEPROPS() do { \ namespace {
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
UpdateListenerProps(context.get()); \
else \
listener.PropsClean.clear(std::memory_order_release); \
} while(0)
inline void UpdateProps(ALCcontext *context)
{
if(!context->mDeferUpdates)
{
UpdateContextProps(context);
return;
}
context->mPropsDirty = true;
}
#ifdef ALSOFT_EAX
inline void CommitAndUpdateProps(ALCcontext *context)
{
if(!context->mDeferUpdates)
{
if(context->has_eax())
{
context->mHoldUpdates.store(true, std::memory_order_release);
while((context->mUpdateCount.load(std::memory_order_acquire)&1) != 0) {
/* busy-wait */
}
context->eax_commit_and_update_sources();
}
UpdateContextProps(context);
context->mHoldUpdates.store(false, std::memory_order_release);
return;
}
context->mPropsDirty = true;
}
#else
inline void CommitAndUpdateProps(ALCcontext *context)
{ UpdateProps(context); }
#endif
} // namespace
AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value)
START_API_FUNC START_API_FUNC
@ -58,14 +91,14 @@ START_API_FUNC
if(!(value >= 0.0f && std::isfinite(value))) if(!(value >= 0.0f && std::isfinite(value)))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range"); SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range");
listener.Gain = value; listener.Gain = value;
DO_UPDATEPROPS(); UpdateProps(context.get());
break; break;
case AL_METERS_PER_UNIT: case AL_METERS_PER_UNIT:
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range"); SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range");
listener.mMetersPerUnit = value; listener.mMetersPerUnit = value;
DO_UPDATEPROPS(); UpdateProps(context.get());
break; break;
default: default:
@ -90,7 +123,7 @@ START_API_FUNC
listener.Position[0] = value1; listener.Position[0] = value1;
listener.Position[1] = value2; listener.Position[1] = value2;
listener.Position[2] = value3; listener.Position[2] = value3;
DO_UPDATEPROPS(); CommitAndUpdateProps(context.get());
break; break;
case AL_VELOCITY: case AL_VELOCITY:
@ -99,7 +132,7 @@ START_API_FUNC
listener.Velocity[0] = value1; listener.Velocity[0] = value1;
listener.Velocity[1] = value2; listener.Velocity[1] = value2;
listener.Velocity[2] = value3; listener.Velocity[2] = value3;
DO_UPDATEPROPS(); CommitAndUpdateProps(context.get());
break; break;
default: default:
@ -146,7 +179,7 @@ START_API_FUNC
listener.OrientUp[0] = values[3]; listener.OrientUp[0] = values[3];
listener.OrientUp[1] = values[4]; listener.OrientUp[1] = values[4];
listener.OrientUp[2] = values[5]; listener.OrientUp[2] = values[5];
DO_UPDATEPROPS(); CommitAndUpdateProps(context.get());
break; break;
default: default:
@ -414,39 +447,3 @@ START_API_FUNC
} }
} }
END_API_FUNC END_API_FUNC
void UpdateListenerProps(ALCcontext *context)
{
/* Get an unused proprty container, or allocate a new one as needed. */
ListenerProps *props{context->mFreeListenerProps.load(std::memory_order_acquire)};
if(!props)
props = new ListenerProps{};
else
{
ListenerProps *next;
do {
next = props->next.load(std::memory_order_relaxed);
} while(context->mFreeListenerProps.compare_exchange_weak(props, next,
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
}
/* Copy in current property values. */
ALlistener &listener = context->mListener;
props->Position = listener.Position;
props->Velocity = listener.Velocity;
props->OrientAt = listener.OrientAt;
props->OrientUp = listener.OrientUp;
props->Gain = listener.Gain;
props->MetersPerUnit = listener.mMetersPerUnit;
/* Set the new container for updating internal parameters. */
props = context->mParams.ListenerUpdate.exchange(props, std::memory_order_acq_rel);
if(props)
{
/* If there was an unused update container, put it back in the
* freelist.
*/
AtomicReplaceHead(context->mFreeListenerProps, props);
}
}

View file

@ -2,7 +2,6 @@
#define AL_LISTENER_H #define AL_LISTENER_H
#include <array> #include <array>
#include <atomic>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
@ -19,13 +18,7 @@ struct ALlistener {
float Gain{1.0f}; float Gain{1.0f};
float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
std::atomic_flag PropsClean;
ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); }
DISABLE_ALLOC() DISABLE_ALLOC()
}; };
void UpdateListenerProps(ALCcontext *context);
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -11,19 +11,31 @@
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "alcontext.h" #include "alc/alu.h"
#include "alc/context.h"
#include "alc/inprogext.h"
#include "aldeque.h" #include "aldeque.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alu.h" #include "atomic.h"
#include "math_defs.h" #include "core/voice.h"
#include "vector.h" #include "vector.h"
#include "voice.h"
#ifdef ALSOFT_EAX
#include "eax_eax_call.h"
#include "eax_fx_slot_index.h"
#include "eax_utils.h"
#endif // ALSOFT_EAX
struct ALbuffer; struct ALbuffer;
struct ALeffectslot; struct ALeffectslot;
enum class SourceStereo : bool {
Normal = AL_NORMAL_SOFT,
Enhanced = AL_SUPER_STEREO_SOFT
};
#define DEFAULT_SENDS 2 #define DEFAULT_SENDS 2
#define INVALID_VOICE_IDX static_cast<ALuint>(-1) #define INVALID_VOICE_IDX static_cast<ALuint>(-1)
@ -35,6 +47,70 @@ struct ALbufferQueueItem : public VoiceBufferItem {
}; };
#ifdef ALSOFT_EAX
using EaxSourceSourceFilterDirtyFlagsValue = std::uint_least16_t;
struct EaxSourceSourceFilterDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxSourceSourceFilterDirtyFlagsValue lDirect : 1;
EaxSourceSourceFilterDirtyFlagsValue lDirectHF : 1;
EaxSourceSourceFilterDirtyFlagsValue lRoom : 1;
EaxSourceSourceFilterDirtyFlagsValue lRoomHF : 1;
EaxSourceSourceFilterDirtyFlagsValue lObstruction : 1;
EaxSourceSourceFilterDirtyFlagsValue flObstructionLFRatio : 1;
EaxSourceSourceFilterDirtyFlagsValue lOcclusion : 1;
EaxSourceSourceFilterDirtyFlagsValue flOcclusionLFRatio : 1;
EaxSourceSourceFilterDirtyFlagsValue flOcclusionRoomRatio : 1;
EaxSourceSourceFilterDirtyFlagsValue flOcclusionDirectRatio : 1;
EaxSourceSourceFilterDirtyFlagsValue lExclusion : 1;
EaxSourceSourceFilterDirtyFlagsValue flExclusionLFRatio : 1;
}; // EaxSourceSourceFilterDirtyFlags
using EaxSourceSourceMiscDirtyFlagsValue = std::uint_least8_t;
struct EaxSourceSourceMiscDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxSourceSourceMiscDirtyFlagsValue lOutsideVolumeHF : 1;
EaxSourceSourceMiscDirtyFlagsValue flDopplerFactor : 1;
EaxSourceSourceMiscDirtyFlagsValue flRolloffFactor : 1;
EaxSourceSourceMiscDirtyFlagsValue flRoomRolloffFactor : 1;
EaxSourceSourceMiscDirtyFlagsValue flAirAbsorptionFactor : 1;
EaxSourceSourceMiscDirtyFlagsValue ulFlags : 1;
EaxSourceSourceMiscDirtyFlagsValue flMacroFXFactor : 1;
EaxSourceSourceMiscDirtyFlagsValue speaker_levels : 1;
}; // EaxSourceSourceMiscDirtyFlags
using EaxSourceSendDirtyFlagsValue = std::uint_least8_t;
struct EaxSourceSendDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxSourceSendDirtyFlagsValue lSend : 1;
EaxSourceSendDirtyFlagsValue lSendHF : 1;
EaxSourceSendDirtyFlagsValue lOcclusion : 1;
EaxSourceSendDirtyFlagsValue flOcclusionLFRatio : 1;
EaxSourceSendDirtyFlagsValue flOcclusionRoomRatio : 1;
EaxSourceSendDirtyFlagsValue flOcclusionDirectRatio : 1;
EaxSourceSendDirtyFlagsValue lExclusion : 1;
EaxSourceSendDirtyFlagsValue flExclusionLFRatio : 1;
}; // EaxSourceSendDirtyFlags
struct EaxSourceSendsDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxSourceSendDirtyFlags sends[EAX_MAX_FXSLOTS];
}; // EaxSourceSendsDirtyFlags
#endif // ALSOFT_EAX
struct ALsource { struct ALsource {
/** Source properties. */ /** Source properties. */
float Pitch{1.0f}; float Pitch{1.0f};
@ -47,6 +123,11 @@ struct ALsource {
float RefDistance{1.0f}; float RefDistance{1.0f};
float MaxDistance{std::numeric_limits<float>::max()}; float MaxDistance{std::numeric_limits<float>::max()};
float RolloffFactor{1.0f}; float RolloffFactor{1.0f};
#ifdef ALSOFT_EAX
// For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to
// AL_ROLLOFF_FACTOR
float RolloffFactor2{0.0f};
#endif
std::array<float,3> Position{{0.0f, 0.0f, 0.0f}}; std::array<float,3> Position{{0.0f, 0.0f, 0.0f}};
std::array<float,3> Velocity{{0.0f, 0.0f, 0.0f}}; std::array<float,3> Velocity{{0.0f, 0.0f, 0.0f}};
std::array<float,3> Direction{{0.0f, 0.0f, 0.0f}}; std::array<float,3> Direction{{0.0f, 0.0f, 0.0f}};
@ -58,6 +139,7 @@ struct ALsource {
Resampler mResampler{ResamplerDefault}; Resampler mResampler{ResamplerDefault};
DirectMode DirectChannels{DirectMode::Off}; DirectMode DirectChannels{DirectMode::Off};
SpatializeMode mSpatialize{SpatializeMode::Auto}; SpatializeMode mSpatialize{SpatializeMode::Auto};
SourceStereo mStereoMode{SourceStereo::Normal};
bool DryGainHFAuto{true}; bool DryGainHFAuto{true};
bool WetGainAuto{true}; bool WetGainAuto{true};
@ -71,9 +153,10 @@ struct ALsource {
/* NOTE: Stereo pan angles are specified in radians, counter-clockwise /* NOTE: Stereo pan angles are specified in radians, counter-clockwise
* rather than clockwise. * rather than clockwise.
*/ */
std::array<float,2> StereoPan{{Deg2Rad( 30.0f), Deg2Rad(-30.0f)}}; std::array<float,2> StereoPan{{al::numbers::pi_v<float>/6.0f, -al::numbers::pi_v<float>/6.0f}};
float Radius{0.0f}; float Radius{0.0f};
float EnhWidth{0.593f};
/** Direct filter and auxiliary send info. */ /** Direct filter and auxiliary send info. */
struct { struct {
@ -109,7 +192,7 @@ struct ALsource {
/** Source Buffer Queue head. */ /** Source Buffer Queue head. */
al::deque<ALbufferQueueItem> mQueue; al::deque<ALbufferQueueItem> mQueue;
std::atomic_flag PropsClean; bool mPropsDirty{true};
/* Index into the context's Voices array. Lazily updated, only checked and /* Index into the context's Voices array. Lazily updated, only checked and
* reset when looking up the voice. * reset when looking up the voice.
@ -127,6 +210,598 @@ struct ALsource {
ALsource& operator=(const ALsource&) = delete; ALsource& operator=(const ALsource&) = delete;
DISABLE_ALLOC() DISABLE_ALLOC()
#ifdef ALSOFT_EAX
public:
void eax_initialize(ALCcontext *context) noexcept;
void eax_dispatch(const EaxEaxCall& eax_call)
{ eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); }
void eax_update_filters();
void eax_update(
EaxContextSharedDirtyFlags dirty_flags);
void eax_commit() { eax_apply_deferred(); }
void eax_commit_and_update();
bool eax_is_initialized() const noexcept { return eax_al_context_; }
static ALsource* eax_lookup_source(
ALCcontext& al_context,
ALuint source_id) noexcept;
private:
static constexpr auto eax_max_speakers = 9;
using EaxActiveFxSlots = std::array<bool, EAX_MAX_FXSLOTS>;
using EaxSpeakerLevels = std::array<long, eax_max_speakers>;
struct Eax
{
using Sends = std::array<EAXSOURCEALLSENDPROPERTIES, EAX_MAX_FXSLOTS>;
EAX50ACTIVEFXSLOTS active_fx_slots{};
EAX50SOURCEPROPERTIES source{};
Sends sends{};
EaxSpeakerLevels speaker_levels{};
}; // Eax
bool eax_uses_primary_id_{};
bool eax_has_active_fx_slots_{};
bool eax_are_active_fx_slots_dirty_{};
ALCcontext* eax_al_context_{};
EAXBUFFER_REVERBPROPERTIES eax1_{};
Eax eax_{};
Eax eax_d_{};
EaxActiveFxSlots eax_active_fx_slots_{};
EaxSourceSendsDirtyFlags eax_sends_dirty_flags_{};
EaxSourceSourceFilterDirtyFlags eax_source_dirty_filter_flags_{};
EaxSourceSourceMiscDirtyFlags eax_source_dirty_misc_flags_{};
[[noreturn]]
static void eax_fail(
const char* message);
void eax_set_source_defaults() noexcept;
void eax_set_active_fx_slots_defaults() noexcept;
void eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept;
void eax_set_sends_defaults() noexcept;
void eax_set_speaker_levels_defaults() noexcept;
void eax_set_defaults() noexcept;
static float eax_calculate_dst_occlusion_mb(
long src_occlusion_mb,
float path_ratio,
float lf_ratio) noexcept;
EaxAlLowPassParam eax_create_direct_filter_param() const noexcept;
EaxAlLowPassParam eax_create_room_filter_param(
const ALeffectslot& fx_slot,
const EAXSOURCEALLSENDPROPERTIES& send) const noexcept;
void eax_set_fx_slots();
void eax_initialize_fx_slots();
void eax_update_direct_filter_internal();
void eax_update_room_filters_internal();
void eax_update_filters_internal();
void eax_update_primary_fx_slot_id();
void eax_defer_active_fx_slots(
const EaxEaxCall& eax_call);
static const char* eax_get_exclusion_name() noexcept;
static const char* eax_get_exclusion_lf_ratio_name() noexcept;
static const char* eax_get_occlusion_name() noexcept;
static const char* eax_get_occlusion_lf_ratio_name() noexcept;
static const char* eax_get_occlusion_direct_ratio_name() noexcept;
static const char* eax_get_occlusion_room_ratio_name() noexcept;
static void eax1_validate_reverb_mix(float reverb_mix);
static void eax_validate_send_receiving_fx_slot_guid(
const GUID& guidReceivingFXSlotID);
static void eax_validate_send_send(
long lSend);
static void eax_validate_send_send_hf(
long lSendHF);
static void eax_validate_send_occlusion(
long lOcclusion);
static void eax_validate_send_occlusion_lf_ratio(
float flOcclusionLFRatio);
static void eax_validate_send_occlusion_room_ratio(
float flOcclusionRoomRatio);
static void eax_validate_send_occlusion_direct_ratio(
float flOcclusionDirectRatio);
static void eax_validate_send_exclusion(
long lExclusion);
static void eax_validate_send_exclusion_lf_ratio(
float flExclusionLFRatio);
static void eax_validate_send(
const EAXSOURCESENDPROPERTIES& all);
static void eax_validate_send_exclusion_all(
const EAXSOURCEEXCLUSIONSENDPROPERTIES& all);
static void eax_validate_send_occlusion_all(
const EAXSOURCEOCCLUSIONSENDPROPERTIES& all);
static void eax_validate_send_all(
const EAXSOURCEALLSENDPROPERTIES& all);
static EaxFxSlotIndexValue eax_get_send_index(
const GUID& send_guid);
void eax_defer_send_send(
long lSend,
EaxFxSlotIndexValue index);
void eax_defer_send_send_hf(
long lSendHF,
EaxFxSlotIndexValue index);
void eax_defer_send_occlusion(
long lOcclusion,
EaxFxSlotIndexValue index);
void eax_defer_send_occlusion_lf_ratio(
float flOcclusionLFRatio,
EaxFxSlotIndexValue index);
void eax_defer_send_occlusion_room_ratio(
float flOcclusionRoomRatio,
EaxFxSlotIndexValue index);
void eax_defer_send_occlusion_direct_ratio(
float flOcclusionDirectRatio,
EaxFxSlotIndexValue index);
void eax_defer_send_exclusion(
long lExclusion,
EaxFxSlotIndexValue index);
void eax_defer_send_exclusion_lf_ratio(
float flExclusionLFRatio,
EaxFxSlotIndexValue index);
void eax_defer_send(
const EAXSOURCESENDPROPERTIES& all,
EaxFxSlotIndexValue index);
void eax_defer_send_exclusion_all(
const EAXSOURCEEXCLUSIONSENDPROPERTIES& all,
EaxFxSlotIndexValue index);
void eax_defer_send_occlusion_all(
const EAXSOURCEOCCLUSIONSENDPROPERTIES& all,
EaxFxSlotIndexValue index);
void eax_defer_send_all(
const EAXSOURCEALLSENDPROPERTIES& all,
EaxFxSlotIndexValue index);
void eax_defer_send(
const EaxEaxCall& eax_call);
void eax_defer_send_exclusion_all(
const EaxEaxCall& eax_call);
void eax_defer_send_occlusion_all(
const EaxEaxCall& eax_call);
void eax_defer_send_all(
const EaxEaxCall& eax_call);
static void eax_validate_source_direct(
long direct);
static void eax_validate_source_direct_hf(
long direct_hf);
static void eax_validate_source_room(
long room);
static void eax_validate_source_room_hf(
long room_hf);
static void eax_validate_source_obstruction(
long obstruction);
static void eax_validate_source_obstruction_lf_ratio(
float obstruction_lf_ratio);
static void eax_validate_source_occlusion(
long occlusion);
static void eax_validate_source_occlusion_lf_ratio(
float occlusion_lf_ratio);
static void eax_validate_source_occlusion_room_ratio(
float occlusion_room_ratio);
static void eax_validate_source_occlusion_direct_ratio(
float occlusion_direct_ratio);
static void eax_validate_source_exclusion(
long exclusion);
static void eax_validate_source_exclusion_lf_ratio(
float exclusion_lf_ratio);
static void eax_validate_source_outside_volume_hf(
long outside_volume_hf);
static void eax_validate_source_doppler_factor(
float doppler_factor);
static void eax_validate_source_rolloff_factor(
float rolloff_factor);
static void eax_validate_source_room_rolloff_factor(
float room_rolloff_factor);
static void eax_validate_source_air_absorption_factor(
float air_absorption_factor);
static void eax_validate_source_flags(
unsigned long flags,
int eax_version);
static void eax_validate_source_macro_fx_factor(
float macro_fx_factor);
static void eax_validate_source_2d_all(
const EAXSOURCE2DPROPERTIES& all,
int eax_version);
static void eax_validate_source_obstruction_all(
const EAXOBSTRUCTIONPROPERTIES& all);
static void eax_validate_source_exclusion_all(
const EAXEXCLUSIONPROPERTIES& all);
static void eax_validate_source_occlusion_all(
const EAXOCCLUSIONPROPERTIES& all);
static void eax_validate_source_all(
const EAX20BUFFERPROPERTIES& all,
int eax_version);
static void eax_validate_source_all(
const EAX30SOURCEPROPERTIES& all,
int eax_version);
static void eax_validate_source_all(
const EAX50SOURCEPROPERTIES& all,
int eax_version);
static void eax_validate_source_speaker_id(
long speaker_id);
static void eax_validate_source_speaker_level(
long speaker_level);
static void eax_validate_source_speaker_level_all(
const EAXSPEAKERLEVELPROPERTIES& all);
void eax_defer_source_direct(
long lDirect);
void eax_defer_source_direct_hf(
long lDirectHF);
void eax_defer_source_room(
long lRoom);
void eax_defer_source_room_hf(
long lRoomHF);
void eax_defer_source_obstruction(
long lObstruction);
void eax_defer_source_obstruction_lf_ratio(
float flObstructionLFRatio);
void eax_defer_source_occlusion(
long lOcclusion);
void eax_defer_source_occlusion_lf_ratio(
float flOcclusionLFRatio);
void eax_defer_source_occlusion_room_ratio(
float flOcclusionRoomRatio);
void eax_defer_source_occlusion_direct_ratio(
float flOcclusionDirectRatio);
void eax_defer_source_exclusion(
long lExclusion);
void eax_defer_source_exclusion_lf_ratio(
float flExclusionLFRatio);
void eax_defer_source_outside_volume_hf(
long lOutsideVolumeHF);
void eax_defer_source_doppler_factor(
float flDopplerFactor);
void eax_defer_source_rolloff_factor(
float flRolloffFactor);
void eax_defer_source_room_rolloff_factor(
float flRoomRolloffFactor);
void eax_defer_source_air_absorption_factor(
float flAirAbsorptionFactor);
void eax_defer_source_flags(
unsigned long ulFlags);
void eax_defer_source_macro_fx_factor(
float flMacroFXFactor);
void eax_defer_source_2d_all(
const EAXSOURCE2DPROPERTIES& all);
void eax_defer_source_obstruction_all(
const EAXOBSTRUCTIONPROPERTIES& all);
void eax_defer_source_exclusion_all(
const EAXEXCLUSIONPROPERTIES& all);
void eax_defer_source_occlusion_all(
const EAXOCCLUSIONPROPERTIES& all);
void eax_defer_source_all(
const EAX20BUFFERPROPERTIES& all);
void eax_defer_source_all(
const EAX30SOURCEPROPERTIES& all);
void eax_defer_source_all(
const EAX50SOURCEPROPERTIES& all);
void eax_defer_source_speaker_level_all(
const EAXSPEAKERLEVELPROPERTIES& all);
void eax_defer_source_direct(
const EaxEaxCall& eax_call);
void eax_defer_source_direct_hf(
const EaxEaxCall& eax_call);
void eax_defer_source_room(
const EaxEaxCall& eax_call);
void eax_defer_source_room_hf(
const EaxEaxCall& eax_call);
void eax_defer_source_obstruction(
const EaxEaxCall& eax_call);
void eax_defer_source_obstruction_lf_ratio(
const EaxEaxCall& eax_call);
void eax_defer_source_occlusion(
const EaxEaxCall& eax_call);
void eax_defer_source_occlusion_lf_ratio(
const EaxEaxCall& eax_call);
void eax_defer_source_occlusion_room_ratio(
const EaxEaxCall& eax_call);
void eax_defer_source_occlusion_direct_ratio(
const EaxEaxCall& eax_call);
void eax_defer_source_exclusion(
const EaxEaxCall& eax_call);
void eax_defer_source_exclusion_lf_ratio(
const EaxEaxCall& eax_call);
void eax_defer_source_outside_volume_hf(
const EaxEaxCall& eax_call);
void eax_defer_source_doppler_factor(
const EaxEaxCall& eax_call);
void eax_defer_source_rolloff_factor(
const EaxEaxCall& eax_call);
void eax_defer_source_room_rolloff_factor(
const EaxEaxCall& eax_call);
void eax_defer_source_air_absorption_factor(
const EaxEaxCall& eax_call);
void eax_defer_source_flags(
const EaxEaxCall& eax_call);
void eax_defer_source_macro_fx_factor(
const EaxEaxCall& eax_call);
void eax_defer_source_2d_all(
const EaxEaxCall& eax_call);
void eax_defer_source_obstruction_all(
const EaxEaxCall& eax_call);
void eax_defer_source_exclusion_all(
const EaxEaxCall& eax_call);
void eax_defer_source_occlusion_all(
const EaxEaxCall& eax_call);
void eax_defer_source_all(
const EaxEaxCall& eax_call);
void eax_defer_source_speaker_level_all(
const EaxEaxCall& eax_call);
void eax_set_outside_volume_hf();
void eax_set_doppler_factor();
void eax_set_rolloff_factor();
void eax_set_room_rolloff_factor();
void eax_set_air_absorption_factor();
void eax_set_direct_hf_auto_flag();
void eax_set_room_auto_flag();
void eax_set_room_hf_auto_flag();
void eax_set_flags();
void eax_set_macro_fx_factor();
void eax_set_speaker_levels();
void eax1_set_efx();
void eax1_set_reverb_mix(const EaxEaxCall& eax_call);
void eax1_set(const EaxEaxCall& eax_call);
void eax_apply_deferred();
void eax_set(
const EaxEaxCall& eax_call);
static const GUID& eax_get_send_fx_slot_guid(
int eax_version,
EaxFxSlotIndexValue fx_slot_index);
static void eax_copy_send(
const EAXSOURCEALLSENDPROPERTIES& src_send,
EAXSOURCESENDPROPERTIES& dst_send);
static void eax_copy_send(
const EAXSOURCEALLSENDPROPERTIES& src_send,
EAXSOURCEALLSENDPROPERTIES& dst_send);
static void eax_copy_send(
const EAXSOURCEALLSENDPROPERTIES& src_send,
EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send);
static void eax_copy_send(
const EAXSOURCEALLSENDPROPERTIES& src_send,
EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send);
template<
typename TException,
typename TSrcSend
>
void eax_api_get_send_properties(
const EaxEaxCall& eax_call) const
{
const auto eax_version = eax_call.get_version();
const auto dst_sends = eax_call.get_values<TException, TSrcSend>();
const auto send_count = dst_sends.size();
for (auto fx_slot_index = EaxFxSlotIndexValue{}; fx_slot_index < send_count; ++fx_slot_index)
{
auto& dst_send = dst_sends[fx_slot_index];
const auto& src_send = eax_.sends[fx_slot_index];
eax_copy_send(src_send, dst_send);
dst_send.guidReceivingFXSlotID = eax_get_send_fx_slot_guid(eax_version, fx_slot_index);
}
}
void eax1_get(const EaxEaxCall& eax_call);
void eax_api_get_source_all_v2(
const EaxEaxCall& eax_call);
void eax_api_get_source_all_v3(
const EaxEaxCall& eax_call);
void eax_api_get_source_all_v5(
const EaxEaxCall& eax_call);
void eax_api_get_source_all(
const EaxEaxCall& eax_call);
void eax_api_get_source_all_obstruction(
const EaxEaxCall& eax_call);
void eax_api_get_source_all_occlusion(
const EaxEaxCall& eax_call);
void eax_api_get_source_all_exclusion(
const EaxEaxCall& eax_call);
void eax_api_get_source_active_fx_slot_id(
const EaxEaxCall& eax_call);
void eax_api_get_source_all_2d(
const EaxEaxCall& eax_call);
void eax_api_get_source_speaker_level_all(
const EaxEaxCall& eax_call);
void eax_get(
const EaxEaxCall& eax_call);
// `alSource3i(source, AL_AUXILIARY_SEND_FILTER, ...)`
void eax_set_al_source_send(ALeffectslot *slot, size_t sendidx,
const EaxAlLowPassParam &filter);
#endif // ALSOFT_EAX
}; };
void UpdateAllSourceProps(ALCcontext *context); void UpdateAllSourceProps(ALCcontext *context);

View file

@ -24,26 +24,34 @@
#include <atomic> #include <atomic>
#include <cmath> #include <cmath>
#include <cstdlib>
#include <cstring>
#include <mutex> #include <mutex>
#include <stdexcept>
#include <string>
#include "AL/al.h" #include "AL/al.h"
#include "AL/alc.h" #include "AL/alc.h"
#include "AL/alext.h" #include "AL/alext.h"
#include "alcontext.h" #include "alc/alu.h"
#include "almalloc.h" #include "alc/context.h"
#include "alc/inprogext.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "alspan.h" #include "aloptional.h"
#include "alu.h"
#include "atomic.h" #include "atomic.h"
#include "core/context.h"
#include "core/except.h" #include "core/except.h"
#include "event.h" #include "core/mixer/defs.h"
#include "inprogext.h" #include "core/voice.h"
#include "intrusive_ptr.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "strutils.h" #include "strutils.h"
#include "voice.h"
#ifdef ALSOFT_EAX
#include "alc/device.h"
#include "eax_globals.h"
#include "eax_x_ram.h"
#endif // ALSOFT_EAX
namespace { namespace {
@ -129,7 +137,7 @@ ALenum ALenumFromDistanceModel(DistanceModel model)
/* WARNING: Non-standard export! Not part of any extension, or exposed in the /* WARNING: Non-standard export! Not part of any extension, or exposed in the
* alcFunctions list. * alcFunctions list.
*/ */
extern "C" AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) AL_API const ALchar* AL_APIENTRY alsoft_get_version(void)
START_API_FUNC START_API_FUNC
{ {
static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION"); static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION");
@ -139,10 +147,10 @@ START_API_FUNC
END_API_FUNC END_API_FUNC
#define DO_UPDATEPROPS() do { \ #define DO_UPDATEPROPS() do { \
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \ if(!context->mDeferUpdates) \
UpdateContextProps(context.get()); \ UpdateContextProps(context.get()); \
else \ else \
context->mPropsClean.clear(std::memory_order_release); \ context->mPropsDirty = true; \
} while(0) } while(0)
@ -152,12 +160,18 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(capability) switch(capability)
{ {
case AL_SOURCE_DISTANCE_MODEL: case AL_SOURCE_DISTANCE_MODEL:
context->mSourceDistanceModel = true; {
DO_UPDATEPROPS(); std::lock_guard<std::mutex> _{context->mPropLock};
context->mSourceDistanceModel = true;
DO_UPDATEPROPS();
}
break;
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported");
break; break;
default: default:
@ -172,12 +186,18 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(capability) switch(capability)
{ {
case AL_SOURCE_DISTANCE_MODEL: case AL_SOURCE_DISTANCE_MODEL:
context->mSourceDistanceModel = false; {
DO_UPDATEPROPS(); std::lock_guard<std::mutex> _{context->mPropLock};
context->mSourceDistanceModel = false;
DO_UPDATEPROPS();
}
break;
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
context->mStopVoicesOnDisconnect = false;
break; break;
default: default:
@ -200,6 +220,10 @@ START_API_FUNC
value = context->mSourceDistanceModel ? AL_TRUE : AL_FALSE; value = context->mSourceDistanceModel ? AL_TRUE : AL_FALSE;
break; break;
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
value = context->mStopVoicesOnDisconnect ? AL_TRUE : AL_FALSE;
break;
default: default:
context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability);
} }
@ -239,7 +263,7 @@ START_API_FUNC
break; break;
case AL_DEFERRED_UPDATES_SOFT: case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire)) if(context->mDeferUpdates)
value = AL_TRUE; value = AL_TRUE;
break; break;
@ -292,7 +316,7 @@ START_API_FUNC
break; break;
case AL_DEFERRED_UPDATES_SOFT: case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire)) if(context->mDeferUpdates)
value = static_cast<ALdouble>(AL_TRUE); value = static_cast<ALdouble>(AL_TRUE);
break; break;
@ -343,7 +367,7 @@ START_API_FUNC
break; break;
case AL_DEFERRED_UPDATES_SOFT: case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire)) if(context->mDeferUpdates)
value = static_cast<ALfloat>(AL_TRUE); value = static_cast<ALfloat>(AL_TRUE);
break; break;
@ -394,7 +418,7 @@ START_API_FUNC
break; break;
case AL_DEFERRED_UPDATES_SOFT: case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire)) if(context->mDeferUpdates)
value = AL_TRUE; value = AL_TRUE;
break; break;
@ -410,6 +434,41 @@ START_API_FUNC
value = static_cast<int>(ResamplerDefault); value = static_cast<int>(ResamplerDefault);
break; break;
#ifdef ALSOFT_EAX
#define EAX_ERROR "[alGetInteger] EAX not enabled."
case AL_EAX_RAM_SIZE:
if (eax_g_is_enabled)
{
value = eax_x_ram_max_size;
}
else
{
context->setError(AL_INVALID_VALUE, EAX_ERROR);
}
break;
case AL_EAX_RAM_FREE:
if (eax_g_is_enabled)
{
auto device = context->mALDevice.get();
std::lock_guard<std::mutex> device_lock{device->BufferLock};
value = static_cast<ALint>(device->eax_x_ram_free_size);
}
else
{
context->setError(AL_INVALID_VALUE, EAX_ERROR);
}
break;
#undef EAX_ERROR
#endif // ALSOFT_EAX
default: default:
context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname); context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
} }
@ -418,7 +477,7 @@ START_API_FUNC
} }
END_API_FUNC END_API_FUNC
extern "C" AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
START_API_FUNC START_API_FUNC
{ {
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
@ -445,7 +504,7 @@ START_API_FUNC
break; break;
case AL_DEFERRED_UPDATES_SOFT: case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire)) if(context->mDeferUpdates)
value = AL_TRUE; value = AL_TRUE;
break; break;
@ -627,7 +686,7 @@ START_API_FUNC
} }
END_API_FUNC END_API_FUNC
extern "C" AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values)
START_API_FUNC START_API_FUNC
{ {
if(values) if(values)
@ -819,6 +878,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
context->deferUpdates(); context->deferUpdates();
} }
END_API_FUNC END_API_FUNC
@ -829,6 +889,7 @@ START_API_FUNC
ContextRef context{GetContextRef()}; ContextRef context{GetContextRef()};
if UNLIKELY(!context) return; if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
context->processUpdates(); context->processUpdates();
} }
END_API_FUNC END_API_FUNC
@ -874,6 +935,14 @@ void UpdateContextProps(ALCcontext *context)
} }
/* Copy in current property values. */ /* Copy in current property values. */
ALlistener &listener = context->mListener;
props->Position = listener.Position;
props->Velocity = listener.Velocity;
props->OrientAt = listener.OrientAt;
props->OrientUp = listener.OrientUp;
props->Gain = listener.Gain;
props->MetersPerUnit = listener.mMetersPerUnit;
props->DopplerFactor = context->mDopplerFactor; props->DopplerFactor = context->mDopplerFactor;
props->DopplerVelocity = context->mDopplerVelocity; props->DopplerVelocity = context->mDopplerVelocity;
props->SpeedOfSound = context->mSpeedOfSound; props->SpeedOfSound = context->mSpeedOfSound;

View file

@ -40,7 +40,7 @@
#include "alfstream.h" #include "alfstream.h"
#include "alstring.h" #include "alstring.h"
#include "compat.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "strutils.h" #include "strutils.h"
#include "vector.h" #include "vector.h"
@ -277,6 +277,52 @@ void LoadConfigFromFile(std::istream &f)
ConfOpts.shrink_to_fit(); ConfOpts.shrink_to_fit();
} }
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
{
if(!keyName)
return nullptr;
std::string key;
if(blockName && al::strcasecmp(blockName, "general") != 0)
{
key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/';
key += keyName;
}
else
{
if(devName)
{
key = devName;
key += '/';
}
key += keyName;
}
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool
{ return entry.key == key; });
if(iter != ConfOpts.cend())
{
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
if(!iter->value.empty())
return iter->value.c_str();
return nullptr;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
return nullptr;
}
return GetConfigValue(nullptr, blockName, keyName);
}
} // namespace } // namespace
@ -437,106 +483,46 @@ void ReadALConfig()
} }
#endif #endif
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
{
if(!keyName)
return def;
std::string key;
if(blockName && al::strcasecmp(blockName, "general") != 0)
{
key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/';
key += keyName;
}
else
{
if(devName)
{
key = devName;
key += '/';
}
key += keyName;
}
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool
{ return entry.key == key; }
);
if(iter != ConfOpts.cend())
{
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
if(!iter->value.empty())
return iter->value.c_str();
return def;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
return def;
}
return GetConfigValue(nullptr, blockName, keyName, def);
}
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
return val[0] != 0;
}
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName) al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
{ {
const char *val = GetConfigValue(devName, blockName, keyName, ""); if(const char *val{GetConfigValue(devName, blockName, keyName)})
if(!val[0]) return al::nullopt; return al::make_optional<std::string>(val);
return al::nullopt;
return al::make_optional<std::string>(val);
} }
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName) al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
{ {
const char *val = GetConfigValue(devName, blockName, keyName, ""); if(const char *val{GetConfigValue(devName, blockName, keyName)})
if(!val[0]) return al::nullopt; return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
return al::nullopt;
return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
} }
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName) al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
{ {
const char *val = GetConfigValue(devName, blockName, keyName, ""); if(const char *val{GetConfigValue(devName, blockName, keyName)})
if(!val[0]) return al::nullopt; return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
return al::nullopt;
return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
} }
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName) al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
{ {
const char *val = GetConfigValue(devName, blockName, keyName, ""); if(const char *val{GetConfigValue(devName, blockName, keyName)})
if(!val[0]) return al::nullopt; return al::make_optional(std::strtof(val, nullptr));
return al::nullopt;
return al::make_optional(std::strtof(val, nullptr));
} }
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName) al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
{ {
const char *val = GetConfigValue(devName, blockName, keyName, ""); if(const char *val{GetConfigValue(devName, blockName, keyName)})
if(!val[0]) return al::nullopt; return al::make_optional(al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0);
return al::make_optional( return al::nullopt;
al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
} }
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def) bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
{ {
const char *val = GetConfigValue(devName, blockName, keyName, ""); if(const char *val{GetConfigValue(devName, blockName, keyName)})
return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
if(!val[0]) return def != 0; || al::strcasecmp(val, "true") == 0 || atoi(val) != 0);
return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 || return def;
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
} }

View file

@ -7,9 +7,7 @@
void ReadALConfig(); void ReadALConfig();
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName); bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def);
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def);
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def);
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName); al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName); al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);

View file

@ -0,0 +1,38 @@
#ifndef ALU_H
#define ALU_H
#include <bitset>
#include "aloptional.h"
struct ALCcontext;
struct ALCdevice;
struct EffectSlot;
enum class StereoEncoding : unsigned char;
constexpr float GainMixMax{1000.0f}; /* +60dB */
enum CompatFlags : uint8_t {
ReverseX,
ReverseY,
ReverseZ,
Count
};
using CompatFlagBitset = std::bitset<CompatFlags::Count>;
void aluInit(CompatFlagBitset flags);
/* aluInitRenderer
*
* Set up the appropriate panning method and mixing method given the device
* properties.
*/
void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode);
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);
#endif

View file

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "backends/alsa.h" #include "alsa.h"
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
@ -36,12 +36,12 @@
#include <utility> #include <utility>
#include "albyte.h" #include "albyte.h"
#include "alcmain.h" #include "alc/alconfig.h"
#include "alconfig.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "aloptional.h" #include "aloptional.h"
#include "alu.h" #include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -68,35 +68,37 @@ constexpr char alsaDevice[] = "ALSA Default";
MAGIC(snd_pcm_hw_params_free); \ MAGIC(snd_pcm_hw_params_free); \
MAGIC(snd_pcm_hw_params_any); \ MAGIC(snd_pcm_hw_params_any); \
MAGIC(snd_pcm_hw_params_current); \ MAGIC(snd_pcm_hw_params_current); \
MAGIC(snd_pcm_hw_params_get_access); \
MAGIC(snd_pcm_hw_params_get_buffer_size); \
MAGIC(snd_pcm_hw_params_get_buffer_time_min); \
MAGIC(snd_pcm_hw_params_get_buffer_time_max); \
MAGIC(snd_pcm_hw_params_get_channels); \
MAGIC(snd_pcm_hw_params_get_period_size); \
MAGIC(snd_pcm_hw_params_get_period_time_max); \
MAGIC(snd_pcm_hw_params_get_period_time_min); \
MAGIC(snd_pcm_hw_params_get_periods); \
MAGIC(snd_pcm_hw_params_set_access); \ MAGIC(snd_pcm_hw_params_set_access); \
MAGIC(snd_pcm_hw_params_set_format); \ MAGIC(snd_pcm_hw_params_set_buffer_size_min); \
MAGIC(snd_pcm_hw_params_set_buffer_size_near); \
MAGIC(snd_pcm_hw_params_set_buffer_time_near); \
MAGIC(snd_pcm_hw_params_set_channels); \ MAGIC(snd_pcm_hw_params_set_channels); \
MAGIC(snd_pcm_hw_params_set_channels_near); \
MAGIC(snd_pcm_hw_params_set_format); \
MAGIC(snd_pcm_hw_params_set_period_time_near); \
MAGIC(snd_pcm_hw_params_set_period_size_near); \
MAGIC(snd_pcm_hw_params_set_periods_near); \ MAGIC(snd_pcm_hw_params_set_periods_near); \
MAGIC(snd_pcm_hw_params_set_rate_near); \ MAGIC(snd_pcm_hw_params_set_rate_near); \
MAGIC(snd_pcm_hw_params_set_rate); \ MAGIC(snd_pcm_hw_params_set_rate); \
MAGIC(snd_pcm_hw_params_set_rate_resample); \ MAGIC(snd_pcm_hw_params_set_rate_resample); \
MAGIC(snd_pcm_hw_params_set_buffer_time_near); \
MAGIC(snd_pcm_hw_params_set_period_time_near); \
MAGIC(snd_pcm_hw_params_set_buffer_size_near); \
MAGIC(snd_pcm_hw_params_set_period_size_near); \
MAGIC(snd_pcm_hw_params_set_buffer_size_min); \
MAGIC(snd_pcm_hw_params_get_buffer_time_min); \
MAGIC(snd_pcm_hw_params_get_buffer_time_max); \
MAGIC(snd_pcm_hw_params_get_period_time_min); \
MAGIC(snd_pcm_hw_params_get_period_time_max); \
MAGIC(snd_pcm_hw_params_get_buffer_size); \
MAGIC(snd_pcm_hw_params_get_period_size); \
MAGIC(snd_pcm_hw_params_get_access); \
MAGIC(snd_pcm_hw_params_get_periods); \
MAGIC(snd_pcm_hw_params_test_format); \ MAGIC(snd_pcm_hw_params_test_format); \
MAGIC(snd_pcm_hw_params_test_channels); \ MAGIC(snd_pcm_hw_params_test_channels); \
MAGIC(snd_pcm_hw_params); \ MAGIC(snd_pcm_hw_params); \
MAGIC(snd_pcm_sw_params_malloc); \ MAGIC(snd_pcm_sw_params); \
MAGIC(snd_pcm_sw_params_current); \ MAGIC(snd_pcm_sw_params_current); \
MAGIC(snd_pcm_sw_params_free); \
MAGIC(snd_pcm_sw_params_malloc); \
MAGIC(snd_pcm_sw_params_set_avail_min); \ MAGIC(snd_pcm_sw_params_set_avail_min); \
MAGIC(snd_pcm_sw_params_set_stop_threshold); \ MAGIC(snd_pcm_sw_params_set_stop_threshold); \
MAGIC(snd_pcm_sw_params); \
MAGIC(snd_pcm_sw_params_free); \
MAGIC(snd_pcm_prepare); \ MAGIC(snd_pcm_prepare); \
MAGIC(snd_pcm_start); \ MAGIC(snd_pcm_start); \
MAGIC(snd_pcm_resume); \ MAGIC(snd_pcm_resume); \
@ -105,7 +107,6 @@ constexpr char alsaDevice[] = "ALSA Default";
MAGIC(snd_pcm_delay); \ MAGIC(snd_pcm_delay); \
MAGIC(snd_pcm_state); \ MAGIC(snd_pcm_state); \
MAGIC(snd_pcm_avail_update); \ MAGIC(snd_pcm_avail_update); \
MAGIC(snd_pcm_areas_silence); \
MAGIC(snd_pcm_mmap_begin); \ MAGIC(snd_pcm_mmap_begin); \
MAGIC(snd_pcm_mmap_commit); \ MAGIC(snd_pcm_mmap_commit); \
MAGIC(snd_pcm_readi); \ MAGIC(snd_pcm_readi); \
@ -150,6 +151,7 @@ ALSA_FUNCS(MAKE_FUNC);
#define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access #define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access
#define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format #define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format
#define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels #define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels
#define snd_pcm_hw_params_set_channels_near psnd_pcm_hw_params_set_channels_near
#define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near #define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near
#define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near #define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near
#define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate #define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate
@ -167,6 +169,7 @@ ALSA_FUNCS(MAKE_FUNC);
#define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size #define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size
#define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access #define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access
#define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods #define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods
#define snd_pcm_hw_params_get_channels psnd_pcm_hw_params_get_channels
#define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format #define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format
#define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels #define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels
#define snd_pcm_hw_params psnd_pcm_hw_params #define snd_pcm_hw_params psnd_pcm_hw_params
@ -184,7 +187,6 @@ ALSA_FUNCS(MAKE_FUNC);
#define snd_pcm_delay psnd_pcm_delay #define snd_pcm_delay psnd_pcm_delay
#define snd_pcm_state psnd_pcm_state #define snd_pcm_state psnd_pcm_state
#define snd_pcm_avail_update psnd_pcm_avail_update #define snd_pcm_avail_update psnd_pcm_avail_update
#define snd_pcm_areas_silence psnd_pcm_areas_silence
#define snd_pcm_mmap_begin psnd_pcm_mmap_begin #define snd_pcm_mmap_begin psnd_pcm_mmap_begin
#define snd_pcm_mmap_commit psnd_pcm_mmap_commit #define snd_pcm_mmap_commit psnd_pcm_mmap_commit
#define snd_pcm_readi psnd_pcm_readi #define snd_pcm_readi psnd_pcm_readi
@ -260,29 +262,36 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
snd_pcm_info_t *pcminfo; snd_pcm_info_t *pcminfo;
snd_pcm_info_malloc(&pcminfo); snd_pcm_info_malloc(&pcminfo);
devlist.emplace_back(DevMap{alsaDevice, auto defname = ConfigValueStr(nullptr, "alsa",
GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture", (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
"default")}); devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"});
const char *customdevs{GetConfigValue(nullptr, "alsa", if(auto customdevs = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures", "")}; (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
while(const char *curdev{customdevs})
{ {
if(!curdev[0]) break; size_t nextpos{customdevs->find_first_not_of(';')};
customdevs = strchr(curdev, ';'); size_t curpos;
const char *sep{strchr(curdev, '=')}; while((curpos=nextpos) < customdevs->length())
if(!sep)
{ {
std::string spec{customdevs ? std::string(curdev, customdevs++) : std::string(curdev)}; nextpos = customdevs->find_first_of(';', curpos+1);
ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
continue;
}
const char *oldsep{sep++}; size_t seppos{customdevs->find_first_of('=', curpos)};
devlist.emplace_back(DevMap{std::string(curdev, oldsep), if(seppos == curpos || seppos >= nextpos)
customdevs ? std::string(sep, customdevs++) : std::string(sep)}); {
const auto &entry = devlist.back(); std::string spec{customdevs->substr(curpos, nextpos-curpos)};
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
}
else
{
devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos),
customdevs->substr(seppos+1, nextpos-seppos-1)});
const auto &entry = devlist.back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
if(nextpos < customdevs->length())
nextpos = customdevs->find_first_not_of(';', nextpos+1);
}
} }
const std::string main_prefix{ const std::string main_prefix{
@ -407,7 +416,7 @@ int verify_state(snd_pcm_t *handle)
struct AlsaPlayback final : public BackendBase { struct AlsaPlayback final : public BackendBase {
AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { } AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaPlayback() override; ~AlsaPlayback() override;
int mixerProc(); int mixerProc();
@ -424,6 +433,7 @@ struct AlsaPlayback final : public BackendBase {
std::mutex mMutex; std::mutex mMutex;
uint mFrameStep{};
al::vector<al::byte> mBuffer; al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true}; std::atomic<bool> mKillNow{true};
@ -445,7 +455,6 @@ int AlsaPlayback::mixerProc()
SetRTPriority(); SetRTPriority();
althrd_setname(MIXER_THREAD_NAME); althrd_setname(MIXER_THREAD_NAME);
const size_t samplebits{mDevice->bytesFromFmt() * 8};
const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
while(!mKillNow.load(std::memory_order_acquire)) while(!mKillNow.load(std::memory_order_acquire))
@ -507,7 +516,7 @@ int AlsaPlayback::mixerProc()
} }
char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)}; char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
mDevice->renderSamples(WritePtr, static_cast<uint>(frames), areas->step/samplebits); mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep);
snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)};
if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames) if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames)
@ -529,7 +538,6 @@ int AlsaPlayback::mixerNoMMapProc()
SetRTPriority(); SetRTPriority();
althrd_setname(MIXER_THREAD_NAME); althrd_setname(MIXER_THREAD_NAME);
const size_t frame_step{mDevice->channelsFromFmt()};
const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
while(!mKillNow.load(std::memory_order_acquire)) while(!mKillNow.load(std::memory_order_acquire))
@ -575,7 +583,7 @@ int AlsaPlayback::mixerNoMMapProc()
al::byte *WritePtr{mBuffer.data()}; al::byte *WritePtr{mBuffer.data()};
avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size())); avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
std::lock_guard<std::mutex> _{mMutex}; std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(WritePtr, static_cast<uint>(avail), frame_step); mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
while(avail > 0) while(avail > 0)
{ {
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
@ -615,16 +623,15 @@ int AlsaPlayback::mixerNoMMapProc()
void AlsaPlayback::open(const char *name) void AlsaPlayback::open(const char *name)
{ {
const char *driver{}; al::optional<std::string> driveropt;
const char *driver{"default"};
if(name) if(name)
{ {
if(PlaybackDevices.empty()) if(PlaybackDevices.empty())
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[name](const DevMap &entry) -> bool [name](const DevMap &entry) -> bool { return entry.name == name; });
{ return entry.name == name; }
);
if(iter == PlaybackDevices.cend()) if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name}; "Device name \"%s\" not found", name};
@ -633,14 +640,19 @@ void AlsaPlayback::open(const char *name)
else else
{ {
name = alsaDevice; name = alsaDevice;
driver = GetConfigValue(nullptr, "alsa", "device", "default"); if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")})
driver = driveropt->c_str();
} }
TRACE("Opening device \"%s\"\n", driver); TRACE("Opening device \"%s\"\n", driver);
int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
snd_pcm_t *pcmHandle{};
int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
if(err < 0) if(err < 0)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver}; "Could not open ALSA device \"%s\"", driver};
if(mPcmHandle)
snd_pcm_close(mPcmHandle);
mPcmHandle = pcmHandle;
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
snd_config_update_free_global(); snd_config_update_free_global();
@ -723,37 +735,25 @@ bool AlsaPlayback::reset()
} }
} }
CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format)); CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format));
/* test and set channels (implicitly sets frame bits) */ /* set channels (implicitly sets frame bits) */
if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0) if(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0)
{ {
static const DevFmtChannels channellist[] = { uint numchans{2u};
DevFmtStereo, CHECK(snd_pcm_hw_params_set_channels_near(mPcmHandle, hp.get(), &numchans));
DevFmtQuad, if(numchans < 1)
DevFmtX51, throw al::backend_exception{al::backend_error::DeviceError, "Got 0 device channels"};
DevFmtX71, if(numchans == 1) mDevice->FmtChans = DevFmtMono;
DevFmtMono, else mDevice->FmtChans = DevFmtStereo;
};
for(const auto &chan : channellist)
{
if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), ChannelsFromDevFmt(chan, 0)) >= 0)
{
mDevice->FmtChans = chan;
mDevice->mAmbiOrder = 0;
break;
}
}
} }
CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()));
/* set rate (implicitly constrains period/buffer parameters) */ /* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0)
|| !mDevice->Flags.test(FrequencyRequest)) || !mDevice->Flags.test(FrequencyRequest))
{ {
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
ERR("Failed to disable ALSA resampler\n"); WARN("Failed to disable ALSA resampler\n");
} }
else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0) else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0)
ERR("Failed to enable ALSA resampler\n"); WARN("Failed to enable ALSA resampler\n");
CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
/* set period time (implicitly constrains period/buffer parameters) */ /* set period time (implicitly constrains period/buffer parameters) */
if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0) if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0)
@ -772,6 +772,7 @@ bool AlsaPlayback::reset()
CHECK(snd_pcm_hw_params_get_access(hp.get(), &access)); CHECK(snd_pcm_hw_params_get_access(hp.get(), &access));
CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr)); CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr));
CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames)); CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames));
CHECK(snd_pcm_hw_params_get_channels(hp.get(), &mFrameStep));
hp = nullptr; hp = nullptr;
SwParamsPtr sp{CreateSwParams()}; SwParamsPtr sp{CreateSwParams()};
@ -809,8 +810,8 @@ void AlsaPlayback::start()
int (AlsaPlayback::*thread_func)(){}; int (AlsaPlayback::*thread_func)(){};
if(access == SND_PCM_ACCESS_RW_INTERLEAVED) if(access == SND_PCM_ACCESS_RW_INTERLEAVED)
{ {
mBuffer.resize( auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize);
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize))); mBuffer.resize(static_cast<size_t>(datalen));
thread_func = &AlsaPlayback::mixerNoMMapProc; thread_func = &AlsaPlayback::mixerNoMMapProc;
} }
else else
@ -863,7 +864,7 @@ ClockLatency AlsaPlayback::getClockLatency()
struct AlsaCapture final : public BackendBase { struct AlsaCapture final : public BackendBase {
AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { } AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaCapture() override; ~AlsaCapture() override;
void open(const char *name) override; void open(const char *name) override;
@ -895,16 +896,15 @@ AlsaCapture::~AlsaCapture()
void AlsaCapture::open(const char *name) void AlsaCapture::open(const char *name)
{ {
const char *driver{}; al::optional<std::string> driveropt;
const char *driver{"default"};
if(name) if(name)
{ {
if(CaptureDevices.empty()) if(CaptureDevices.empty())
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[name](const DevMap &entry) -> bool [name](const DevMap &entry) -> bool { return entry.name == name; });
{ return entry.name == name; }
);
if(iter == CaptureDevices.cend()) if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name}; "Device name \"%s\" not found", name};
@ -913,7 +913,8 @@ void AlsaCapture::open(const char *name)
else else
{ {
name = alsaDevice; name = alsaDevice;
driver = GetConfigValue(nullptr, "alsa", "capture", "default"); if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")})
driver = driveropt->c_str();
} }
TRACE("Opening device \"%s\"\n", driver); TRACE("Opening device \"%s\"\n", driver);
@ -1252,7 +1253,7 @@ std::string AlsaBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr AlsaBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new AlsaPlayback{device}}; return BackendPtr{new AlsaPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_ALSA_H #ifndef BACKENDS_ALSA_H
#define BACKENDS_ALSA_H #define BACKENDS_ALSA_H
#include "backends/base.h" #include "base.h"
struct AlsaBackendFactory final : public BackendFactory { struct AlsaBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -3,21 +3,22 @@
#include "base.h" #include "base.h"
#include <algorithm>
#include <array>
#include <atomic> #include <atomic>
#include <thread>
#ifdef _WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#include <mmreg.h> #include <mmreg.h>
#endif
#include "albit.h" #include "albit.h"
#include "alcmain.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "atomic.h"
#include "core/logging.h" #include "core/logging.h"
#include "aloptional.h"
#endif
#include "atomic.h"
#include "core/devformat.h"
bool BackendBase::reset() bool BackendBase::reset()
@ -78,14 +79,6 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[SideLeft] = 4; mDevice->RealOut.ChannelIndex[SideLeft] = 4;
mDevice->RealOut.ChannelIndex[SideRight] = 5; mDevice->RealOut.ChannelIndex[SideRight] = 5;
break; break;
case DevFmtX51Rear:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackLeft] = 4;
mDevice->RealOut.ChannelIndex[BackRight] = 5;
break;
case DevFmtX61: case DevFmtX61:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontRight] = 1;
@ -116,11 +109,11 @@ void BackendBase::setDefaultChannelOrder()
switch(mDevice->FmtChans) switch(mDevice->FmtChans)
{ {
case DevFmtX51Rear: case DevFmtX51:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2; mDevice->RealOut.ChannelIndex[SideLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3; mDevice->RealOut.ChannelIndex[SideRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4; mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5; mDevice->RealOut.ChannelIndex[LFE] = 5;
return; return;
@ -139,7 +132,6 @@ void BackendBase::setDefaultChannelOrder()
case DevFmtMono: case DevFmtMono:
case DevFmtStereo: case DevFmtStereo:
case DevFmtQuad: case DevFmtQuad:
case DevFmtX51:
case DevFmtX61: case DevFmtX61:
case DevFmtAmbi3D: case DevFmtAmbi3D:
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
@ -150,6 +142,13 @@ void BackendBase::setDefaultChannelOrder()
#ifdef _WIN32 #ifdef _WIN32
void BackendBase::setChannelOrderFromWFXMask(uint chanmask) void BackendBase::setChannelOrderFromWFXMask(uint chanmask)
{ {
static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT};
static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT};
/* Swap a 5.1 mask using the back channels for one with the sides. */
if(chanmask == x51rear) chanmask = x51;
auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel> auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel>
{ {
switch(chanbit) switch(chanbit)

View file

@ -2,12 +2,13 @@
#define ALC_BACKENDS_BASE_H #define ALC_BACKENDS_BASE_H
#include <chrono> #include <chrono>
#include <cstdarg>
#include <memory> #include <memory>
#include <mutex> #include <ratio>
#include <string> #include <string>
#include "albyte.h" #include "albyte.h"
#include "alcmain.h" #include "core/device.h"
#include "core/except.h" #include "core/except.h"
@ -30,9 +31,9 @@ struct BackendBase {
virtual ClockLatency getClockLatency(); virtual ClockLatency getClockLatency();
ALCdevice *const mDevice; DeviceBase *const mDevice;
BackendBase(ALCdevice *device) noexcept : mDevice{device} { } BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default; virtual ~BackendBase() = default;
protected: protected:
@ -57,7 +58,7 @@ enum class BackendType {
/* Helper to get the current clock time from the device's ClockBase, and /* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate. * SamplesDone converted from the sample rate.
*/ */
inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device) inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
{ {
using std::chrono::seconds; using std::chrono::seconds;
using std::chrono::nanoseconds; using std::chrono::nanoseconds;
@ -69,9 +70,8 @@ inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device)
/* Helper to get the device latency from the backend, including any fixed /* Helper to get the device latency from the backend, including any fixed
* latency from post-processing. * latency from post-processing.
*/ */
inline ClockLatency GetClockLatency(ALCdevice *device) inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
{ {
BackendBase *backend{device->Backend.get()};
ClockLatency ret{backend->getClockLatency()}; ClockLatency ret{backend->getClockLatency()};
ret.Latency += device->FixedLatency; ret.Latency += device->FixedLatency;
return ret; return ret;
@ -85,7 +85,7 @@ struct BackendFactory {
virtual std::string probe(BackendType type) = 0; virtual std::string probe(BackendType type) = 0;
virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0; virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected: protected:
virtual ~BackendFactory() = default; virtual ~BackendFactory() = default;
@ -103,7 +103,11 @@ class backend_exception final : public base_exception {
backend_error mErrorCode; backend_error mErrorCode;
public: public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]] [[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{ {
std::va_list args; std::va_list args;

View file

@ -20,34 +20,239 @@
#include "config.h" #include "config.h"
#include "backends/coreaudio.h" #include "coreaudio.h"
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <cmath> #include <cmath>
#include <memory>
#include <string>
#include "alcmain.h" #include "alnumeric.h"
#include "alu.h" #include "core/converter.h"
#include "ringbuffer.h" #include "core/device.h"
#include "converter.h"
#include "core/logging.h" #include "core/logging.h"
#include "backends/base.h" #include "ringbuffer.h"
#include <unistd.h>
#include <AudioUnit/AudioUnit.h> #include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h> #include <AudioToolbox/AudioToolbox.h>
namespace { namespace {
static const char ca_device[] = "CoreAudio Default"; #if TARGET_OS_IOS || TARGET_OS_TV
#define CAN_ENUMERATE 0
#else
#define CAN_ENUMERATE 1
#endif
#if CAN_ENUMERATE
struct DeviceEntry {
AudioDeviceID mId;
std::string mName;
};
std::vector<DeviceEntry> PlaybackList;
std::vector<DeviceEntry> CaptureList;
OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
{
const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
propData);
}
OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
{
const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
}
OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
UInt32 elem, UInt32 dataSize, void *propData)
{
static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
kAudioDevicePropertyScopeInput};
const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
}
OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
bool isCapture, UInt32 elem, UInt32 *outSize)
{
static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
kAudioDevicePropertyScopeInput};
const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
}
std::string GetDeviceName(AudioDeviceID devId)
{
std::string devname;
CFStringRef nameRef;
/* Try to get the device name as a CFString, for Unicode name support. */
OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
sizeof(nameRef), &nameRef)};
if(err == noErr)
{
const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
kCFStringEncodingUTF8)};
devname.resize(static_cast<size_t>(propSize)+1, '\0');
CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
CFRelease(nameRef);
}
else
{
/* If that failed, just get the C string. Hopefully there's nothing bad
* with this.
*/
UInt32 propSize{};
if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
return devname;
devname.resize(propSize+1, '\0');
if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
{
devname.clear();
return devname;
}
}
/* Clear extraneous nul chars that may have been written with the name
* string, and return it.
*/
while(!devname.back())
devname.pop_back();
return devname;
}
UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
{
UInt32 propSize{};
auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
&propSize);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
return 0;
}
auto buflist_data = std::make_unique<char[]>(propSize);
auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
buflist);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
return 0;
}
UInt32 numChannels{0};
for(size_t i{0};i < buflist->mNumberBuffers;++i)
numChannels += buflist->mBuffers[i].mNumberChannels;
return numChannels;
}
void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
{
UInt32 propSize{};
if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
{
ERR("Failed to get device list size: %u\n", err);
return;
}
auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
{
ERR("Failed to get device list: %u\n", err);
return;
}
std::vector<DeviceEntry> newdevs;
newdevs.reserve(devIds.size());
AudioDeviceID defaultId{kAudioDeviceUnknown};
GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
if(defaultId != kAudioDeviceUnknown)
{
newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
const auto &entry = newdevs.back();
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
}
for(const AudioDeviceID devId : devIds)
{
if(devId == kAudioDeviceUnknown)
continue;
auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
{ return entry.mId == devId; };
auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
if(match != newdevs.cend()) continue;
auto numChannels = GetDeviceChannelCount(devId, isCapture);
if(numChannels > 0)
{
newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
const auto &entry = newdevs.back();
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
}
}
if(newdevs.size() > 1)
{
/* Rename entries that have matching names, by appending '#2', '#3',
* etc, as needed.
*/
for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
{
auto check_match = [curitem](const DeviceEntry &entry) -> bool
{ return entry.mName == curitem->mName; };
if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
{
std::string name{curitem->mName};
size_t count{1};
auto check_name = [&name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
do {
name = curitem->mName;
name += " #";
name += std::to_string(++count);
} while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
curitem->mName = std::move(name);
}
}
}
newdevs.shrink_to_fit();
newdevs.swap(list);
}
#else
static constexpr char ca_device[] = "CoreAudio Default";
#endif
struct CoreAudioPlayback final : public BackendBase { struct CoreAudioPlayback final : public BackendBase {
CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioPlayback() override; ~CoreAudioPlayback() override;
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
@ -96,19 +301,41 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
void CoreAudioPlayback::open(const char *name) void CoreAudioPlayback::open(const char *name)
{ {
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
&audioDevice);
else
{
if(PlaybackList.empty())
EnumerateDevices(PlaybackList, false);
auto find_name = [name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
if(devmatch == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
audioDevice = devmatch->mId;
}
#else
if(!name) if(!name)
name = ca_device; name = ca_device;
else if(strcmp(name, ca_device) != 0) else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name}; name};
#endif
/* open the default output unit */ /* open the default output unit */
AudioComponentDescription desc{}; AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output; desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IOS #if CAN_ENUMERATE
desc.componentSubType = kAudioUnitSubType_RemoteIO; desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else #else
desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif #endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0; desc.componentFlags = 0;
@ -118,18 +345,50 @@ void CoreAudioPlayback::open(const char *name)
if(comp == nullptr) if(comp == nullptr)
throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; AudioUnit audioUnit{};
OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err}; "Could not create component instance: %u", err};
/* init and start the default audio unit... */ #if CAN_ENUMERATE
err = AudioUnitInitialize(mAudioUnit); if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
#endif
err = AudioUnitInitialize(audioUnit);
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err}; "Could not initialize audio unit: %u", err};
/* WARNING: I don't know if "valid" audio unit values are guaranteed to be
* non-0. If not, this logic is broken.
*/
if(mAudioUnit)
{
AudioUnitUninitialize(mAudioUnit);
AudioComponentInstanceDispose(mAudioUnit);
}
mAudioUnit = audioUnit;
#if CAN_ENUMERATE
if(name)
mDevice->DeviceName = name;
else
{
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name";
}
#else
mDevice->DeviceName = name; mDevice->DeviceName = name;
#endif
} }
bool CoreAudioPlayback::reset() bool CoreAudioPlayback::reset()
@ -140,10 +399,10 @@ bool CoreAudioPlayback::reset()
/* retrieve default output unit's properties (output side) */ /* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{}; AudioStreamBasicDescription streamFormat{};
auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription)); UInt32 size{sizeof(streamFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
0, &streamFormat, &size); 0, &streamFormat, &size);
if(err != noErr || size != sizeof(AudioStreamBasicDescription)) if(err != noErr || size != sizeof(streamFormat))
{ {
ERR("AudioUnitGetProperty failed\n"); ERR("AudioUnitGetProperty failed\n");
return false; return false;
@ -159,15 +418,9 @@ bool CoreAudioPlayback::reset()
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
#endif #endif
/* set default output unit's input side to match output side */ /* Use the sample rate from the output unit's current parameters, but reset
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, * everything else.
0, &streamFormat, size); */
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
if(mDevice->Frequency != streamFormat.mSampleRate) if(mDevice->Frequency != streamFormat.mSampleRate)
{ {
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} * mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
@ -176,82 +429,54 @@ bool CoreAudioPlayback::reset()
} }
/* FIXME: How to tell what channels are what in the output device, and how /* FIXME: How to tell what channels are what in the output device, and how
* to specify what we're giving? eg, 6.0 vs 5.1 */ * to specify what we're giving? e.g. 6.0 vs 5.1
switch(streamFormat.mChannelsPerFrame) */
{ streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
case 1:
mDevice->FmtChans = DevFmtMono;
break;
case 2:
mDevice->FmtChans = DevFmtStereo;
break;
case 4:
mDevice->FmtChans = DevFmtQuad;
break;
case 6:
mDevice->FmtChans = DevFmtX51;
break;
case 7:
mDevice->FmtChans = DevFmtX61;
break;
case 8:
mDevice->FmtChans = DevFmtX71;
break;
default:
ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
mDevice->FmtChans = DevFmtStereo;
streamFormat.mChannelsPerFrame = 2;
break;
}
setDefaultWFXChannelOrder();
/* use channel count and sample rate from the default output unit's current
* parameters, but reset everything else */
streamFormat.mFramesPerPacket = 1; streamFormat.mFramesPerPacket = 1;
streamFormat.mFormatFlags = 0; streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
streamFormat.mFormatID = kAudioFormatLinearPCM;
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
case DevFmtUByte: case DevFmtUByte:
mDevice->FmtType = DevFmtByte; mDevice->FmtType = DevFmtByte;
/* fall-through */ /* fall-through */
case DevFmtByte: case DevFmtByte:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 8; streamFormat.mBitsPerChannel = 8;
break; break;
case DevFmtUShort: case DevFmtUShort:
mDevice->FmtType = DevFmtShort; mDevice->FmtType = DevFmtShort;
/* fall-through */ /* fall-through */
case DevFmtShort: case DevFmtShort:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 16; streamFormat.mBitsPerChannel = 16;
break; break;
case DevFmtUInt: case DevFmtUInt:
mDevice->FmtType = DevFmtInt; mDevice->FmtType = DevFmtInt;
/* fall-through */ /* fall-through */
case DevFmtInt: case DevFmtInt:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 32; streamFormat.mBitsPerChannel = 32;
break; break;
case DevFmtFloat: case DevFmtFloat:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
streamFormat.mBitsPerChannel = 32; streamFormat.mBitsPerChannel = 32;
break; break;
} }
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame * streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
streamFormat.mBitsPerChannel / 8; streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
kLinearPCMFormatFlagIsPacked;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &streamFormat, sizeof(AudioStreamBasicDescription)); 0, &streamFormat, sizeof(streamFormat));
if(err != noErr) if(err != noErr)
{ {
ERR("AudioUnitSetProperty failed\n"); ERR("AudioUnitSetProperty failed\n");
return false; return false;
} }
setDefaultWFXChannelOrder();
/* setup callback */ /* setup callback */
mFrameSize = mDevice->frameSizeFromFmt(); mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{}; AURenderCallbackStruct input{};
@ -294,7 +519,7 @@ void CoreAudioPlayback::stop()
struct CoreAudioCapture final : public BackendBase { struct CoreAudioCapture final : public BackendBase {
CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { } CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioCapture() override; ~CoreAudioCapture() override;
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
@ -383,45 +608,64 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
void CoreAudioCapture::open(const char *name) void CoreAudioCapture::open(const char *name)
{ {
AudioStreamBasicDescription requestedFormat; // The application requested format #if CAN_ENUMERATE
AudioStreamBasicDescription hardwareFormat; // The hardware format AudioDeviceID audioDevice{kAudioDeviceUnknown};
AudioStreamBasicDescription outputFormat; // The AudioUnit output format if(!name)
AURenderCallbackStruct input; GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
AudioComponentDescription desc; &audioDevice);
UInt32 propertySize; else
UInt32 enableIO; {
AudioComponent comp; if(CaptureList.empty())
OSStatus err; EnumerateDevices(CaptureList, true);
auto find_name = [name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
if(devmatch == CaptureList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
audioDevice = devmatch->mId;
}
#else
if(!name) if(!name)
name = ca_device; name = ca_device;
else if(strcmp(name, ca_device) != 0) else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name}; name};
#endif
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output; desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IOS #if CAN_ENUMERATE
desc.componentSubType = kAudioUnitSubType_RemoteIO; desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else #else
desc.componentSubType = kAudioUnitSubType_HALOutput; desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif #endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0; desc.componentFlags = 0;
desc.componentFlagsMask = 0; desc.componentFlagsMask = 0;
// Search for component with given description // Search for component with given description
comp = AudioComponentFindNext(NULL, &desc); AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == NULL) if(comp == NULL)
throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
// Open the component // Open the component
err = AudioComponentInstanceNew(comp, &mAudioUnit); OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr) if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err}; "Could not create component instance: %u", err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
#endif
// Turn off AudioUnit output // Turn off AudioUnit output
enableIO = 0; UInt32 enableIO{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO)); kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
if(err != noErr) if(err != noErr)
@ -436,35 +680,8 @@ void CoreAudioCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: %u", err}; "Could not enable audio unit input property: %u", err};
#if !TARGET_OS_IOS
{
// Get the default input device
AudioDeviceID inputDevice = kAudioDeviceUnknown;
propertySize = sizeof(AudioDeviceID);
AudioObjectPropertyAddress propertyAddress{};
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
&propertySize, &inputDevice);
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not get input device: %u", err};
if(inputDevice == kAudioDeviceUnknown)
throw al::backend_exception{al::backend_error::NoDevice, "Unknown input device"};
// Track the input device
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not set input device: %u", err};
}
#endif
// set capture callback // set capture callback
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC; input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this; input.inputProcRefCon = this;
@ -489,14 +706,16 @@ void CoreAudioCapture::open(const char *name)
"Could not initialize audio unit: %u", err}; "Could not initialize audio unit: %u", err};
// Get the hardware format // Get the hardware format
propertySize = sizeof(AudioStreamBasicDescription); AudioStreamBasicDescription hardwareFormat{};
UInt32 propertySize{sizeof(hardwareFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1, &hardwareFormat, &propertySize); 1, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription)) if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err}; "Could not get input format: %u", err};
// Set up the requested format description // Set up the requested format description
AudioStreamBasicDescription requestedFormat{};
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
case DevFmtByte: case DevFmtByte:
@ -509,7 +728,8 @@ void CoreAudioCapture::open(const char *name)
break; break;
case DevFmtShort: case DevFmtShort:
requestedFormat.mBitsPerChannel = 16; requestedFormat.mBitsPerChannel = 16;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break; break;
case DevFmtUShort: case DevFmtUShort:
requestedFormat.mBitsPerChannel = 16; requestedFormat.mBitsPerChannel = 16;
@ -517,7 +737,8 @@ void CoreAudioCapture::open(const char *name)
break; break;
case DevFmtInt: case DevFmtInt:
requestedFormat.mBitsPerChannel = 32; requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break; break;
case DevFmtUInt: case DevFmtUInt:
requestedFormat.mBitsPerChannel = 32; requestedFormat.mBitsPerChannel = 32;
@ -525,7 +746,8 @@ void CoreAudioCapture::open(const char *name)
break; break;
case DevFmtFloat: case DevFmtFloat:
requestedFormat.mBitsPerChannel = 32; requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked;
break; break;
} }
@ -540,7 +762,6 @@ void CoreAudioCapture::open(const char *name)
case DevFmtQuad: case DevFmtQuad:
case DevFmtX51: case DevFmtX51:
case DevFmtX51Rear:
case DevFmtX61: case DevFmtX61:
case DevFmtX71: case DevFmtX71:
case DevFmtAmbi3D: case DevFmtAmbi3D:
@ -561,7 +782,7 @@ void CoreAudioCapture::open(const char *name)
// Use intermediate format for sample rate conversion (outputFormat) // Use intermediate format for sample rate conversion (outputFormat)
// Set sample rate to the same as hardware for resampling later // Set sample rate to the same as hardware for resampling later
outputFormat = requestedFormat; AudioStreamBasicDescription outputFormat{requestedFormat};
outputFormat.mSampleRate = hardwareFormat.mSampleRate; outputFormat.mSampleRate = hardwareFormat.mSampleRate;
// The output format should be the requested format, but using the hardware sample rate // The output format should be the requested format, but using the hardware sample rate
@ -600,7 +821,23 @@ void CoreAudioCapture::open(const char *name)
mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate), mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24); mDevice->Frequency, Resampler::FastBSinc24);
#if CAN_ENUMERATE
if(name)
mDevice->DeviceName = name;
else
{
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name";
}
#else
mDevice->DeviceName = name; mDevice->DeviceName = name;
#endif
} }
@ -665,6 +902,26 @@ bool CoreAudioBackendFactory::querySupport(BackendType type)
std::string CoreAudioBackendFactory::probe(BackendType type) std::string CoreAudioBackendFactory::probe(BackendType type)
{ {
std::string outnames; std::string outnames;
#if CAN_ENUMERATE
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
switch(type)
{
case BackendType::Playback:
EnumerateDevices(PlaybackList, false);
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
EnumerateDevices(CaptureList, true);
std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
break;
}
#else
switch(type) switch(type)
{ {
case BackendType::Playback: case BackendType::Playback:
@ -673,10 +930,11 @@ std::string CoreAudioBackendFactory::probe(BackendType type)
outnames.append(ca_device, sizeof(ca_device)); outnames.append(ca_device, sizeof(ca_device));
break; break;
} }
#endif
return outnames; return outnames;
} }
BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new CoreAudioPlayback{device}}; return BackendPtr{new CoreAudioPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_COREAUDIO_H #ifndef BACKENDS_COREAUDIO_H
#define BACKENDS_COREAUDIO_H #define BACKENDS_COREAUDIO_H
#include "backends/base.h" #include "base.h"
struct CoreAudioBackendFactory final : public BackendFactory { struct CoreAudioBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "backends/dsound.h" #include "dsound.h"
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
@ -44,9 +44,10 @@
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include "alcmain.h" #include "alnumeric.h"
#include "alu.h" #include "comptr.h"
#include "compat.h" #include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -169,21 +170,21 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
struct DSoundPlayback final : public BackendBase { struct DSoundPlayback final : public BackendBase {
DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { } DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundPlayback() override; ~DSoundPlayback() override;
int mixerProc(); int mixerProc();
void open(const ALCchar *name) override; void open(const char *name) override;
bool reset() override; bool reset() override;
void start() override; void start() override;
void stop() override; void stop() override;
IDirectSound *mDS{nullptr}; ComPtr<IDirectSound> mDS;
IDirectSoundBuffer *mPrimaryBuffer{nullptr}; ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
IDirectSoundBuffer *mBuffer{nullptr}; ComPtr<IDirectSoundBuffer> mBuffer;
IDirectSoundNotify *mNotifies{nullptr}; ComPtr<IDirectSoundNotify> mNotifies;
HANDLE mNotifyEvent{nullptr}; HANDLE mNotifyEvent{nullptr};
std::atomic<bool> mKillNow{true}; std::atomic<bool> mKillNow{true};
std::thread mThread; std::thread mThread;
@ -193,19 +194,11 @@ struct DSoundPlayback final : public BackendBase {
DSoundPlayback::~DSoundPlayback() DSoundPlayback::~DSoundPlayback()
{ {
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr; mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr; mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr; mPrimaryBuffer = nullptr;
if(mDS)
mDS->Release();
mDS = nullptr; mDS = nullptr;
if(mNotifyEvent) if(mNotifyEvent)
CloseHandle(mNotifyEvent); CloseHandle(mNotifyEvent);
mNotifyEvent = nullptr; mNotifyEvent = nullptr;
@ -234,8 +227,8 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
bool Playing{false}; bool Playing{false};
DWORD LastCursor{0u}; DWORD LastCursor{0u};
mBuffer->GetCurrentPosition(&LastCursor, nullptr); mBuffer->GetCurrentPosition(&LastCursor, nullptr);
while(!mKillNow.load(std::memory_order_acquire) && while(!mKillNow.load(std::memory_order_acquire)
mDevice->Connected.load(std::memory_order_acquire)) && mDevice->Connected.load(std::memory_order_acquire))
{ {
// Get current play cursor // Get current play cursor
DWORD PlayCursor; DWORD PlayCursor;
@ -344,31 +337,34 @@ void DSoundPlayback::open(const char *name)
} }
hr = DS_OK; hr = DS_OK;
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(!mNotifyEvent)
if(!mNotifyEvent) hr = E_FAIL; {
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(!mNotifyEvent) hr = E_FAIL;
}
//DirectSound Init code //DirectSound Init code
ComPtr<IDirectSound> ds;
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
hr = DirectSoundCreate(guid, &mDS, nullptr); hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr)) if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr}; hr};
mNotifies = nullptr;
mBuffer = nullptr;
mPrimaryBuffer = nullptr;
mDS = std::move(ds);
mDevice->DeviceName = name; mDevice->DeviceName = name;
} }
bool DSoundPlayback::reset() bool DSoundPlayback::reset()
{ {
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr; mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr; mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr; mPrimaryBuffer = nullptr;
switch(mDevice->FmtType) switch(mDevice->FmtType)
@ -406,17 +402,14 @@ bool DSoundPlayback::reset()
mDevice->FmtChans = DevFmtStereo; mDevice->FmtChans = DevFmtStereo;
else if(speakers == DSSPEAKER_QUAD) else if(speakers == DSSPEAKER_QUAD)
mDevice->FmtChans = DevFmtQuad; mDevice->FmtChans = DevFmtQuad;
else if(speakers == DSSPEAKER_5POINT1_SURROUND) else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51; mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51Rear;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71; mDevice->FmtChans = DevFmtX71;
else else
ERR("Unknown system speaker config: 0x%lx\n", speakers); ERR("Unknown system speaker config: 0x%lx\n", speakers);
} }
mDevice->IsHeadphones = mDevice->FmtChans == DevFmtStereo mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
&& speakers == DSSPEAKER_HEADPHONE;
switch(mDevice->FmtChans) switch(mDevice->FmtChans)
{ {
@ -426,7 +419,6 @@ bool DSoundPlayback::reset()
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break; case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break; case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break; case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
case DevFmtX51Rear: OutputType.dwChannelMask = X5DOT1REAR; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
} }
@ -454,8 +446,6 @@ retry_open:
else else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr; mPrimaryBuffer = nullptr;
} }
else else
@ -465,7 +455,7 @@ retry_open:
DSBUFFERDESC DSBDescription{}; DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr); hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
} }
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format); hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
@ -485,7 +475,7 @@ retry_open:
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format; DSBDescription.lpwfxFormat = &OutputType.Format;
hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr); hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{ {
mDevice->FmtType = DevFmtShort; mDevice->FmtType = DevFmtShort;
@ -499,7 +489,7 @@ retry_open:
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
{ {
mNotifies = static_cast<IDirectSoundNotify*>(ptr); mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES); assert(num_updates <= MAX_UPDATES);
@ -517,14 +507,8 @@ retry_open:
if(FAILED(hr)) if(FAILED(hr))
{ {
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr; mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr; mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr; mPrimaryBuffer = nullptr;
return false; return false;
} }
@ -558,7 +542,7 @@ void DSoundPlayback::stop()
struct DSoundCapture final : public BackendBase { struct DSoundCapture final : public BackendBase {
DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { } DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override; ~DSoundCapture() override;
void open(const char *name) override; void open(const char *name) override;
@ -567,8 +551,8 @@ struct DSoundCapture final : public BackendBase {
void captureSamples(al::byte *buffer, uint samples) override; void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override; uint availableSamples() override;
IDirectSoundCapture *mDSC{nullptr}; ComPtr<IDirectSoundCapture> mDSC;
IDirectSoundCaptureBuffer *mDSCbuffer{nullptr}; ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
DWORD mBufferBytes{0u}; DWORD mBufferBytes{0u};
DWORD mCursor{0u}; DWORD mCursor{0u};
@ -582,12 +566,8 @@ DSoundCapture::~DSoundCapture()
if(mDSCbuffer) if(mDSCbuffer)
{ {
mDSCbuffer->Stop(); mDSCbuffer->Stop();
mDSCbuffer->Release();
mDSCbuffer = nullptr; mDSCbuffer = nullptr;
} }
if(mDSC)
mDSC->Release();
mDSC = nullptr; mDSC = nullptr;
} }
@ -653,7 +633,6 @@ void DSoundCapture::open(const char *name)
case DevFmtStereo: InputType.dwChannelMask = STEREO; break; case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
case DevFmtQuad: InputType.dwChannelMask = QUAD; break; case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
case DevFmtX51: InputType.dwChannelMask = X5DOT1; break; case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
case DevFmtX51Rear: InputType.dwChannelMask = X5DOT1REAR; break;
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtAmbi3D: case DevFmtAmbi3D:
@ -693,20 +672,16 @@ void DSoundCapture::open(const char *name)
DSCBDescription.lpwfxFormat = &InputType.Format; DSCBDescription.lpwfxFormat = &InputType.Format;
//DirectSoundCapture Init code //DirectSoundCapture Init code
hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr); hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr); mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
if(SUCCEEDED(hr)) if(SUCCEEDED(hr))
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
if(FAILED(hr)) if(FAILED(hr))
{ {
mRing = nullptr; mRing = nullptr;
if(mDSCbuffer)
mDSCbuffer->Release();
mDSCbuffer = nullptr; mDSCbuffer = nullptr;
if(mDSC)
mDSC->Release();
mDSC = nullptr; mDSC = nullptr;
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
@ -858,7 +833,7 @@ std::string DSoundBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new DSoundPlayback{device}}; return BackendPtr{new DSoundPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_DSOUND_H #ifndef BACKENDS_DSOUND_H
#define BACKENDS_DSOUND_H #define BACKENDS_DSOUND_H
#include "backends/base.h" #include "base.h"
struct DSoundBackendFactory final : public BackendFactory { struct DSoundBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "backends/jack.h" #include "jack.h"
#include <cstdlib> #include <cstdlib>
#include <cstdio> #include <cstdio>
@ -31,9 +31,10 @@
#include <thread> #include <thread>
#include <functional> #include <functional>
#include "alcmain.h" #include "alc/alconfig.h"
#include "alu.h" #include "alnumeric.h"
#include "alconfig.h" #include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -45,9 +46,6 @@
namespace { namespace {
constexpr char jackDevice[] = "JACK Default";
#ifdef HAVE_DYNLOAD #ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \ #define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \ MAGIC(jack_client_open); \
@ -101,6 +99,8 @@ decltype(jack_error_callback) * pjack_error_callback;
#endif #endif
constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
jack_options_t ClientOptions = JackNullOption; jack_options_t ClientOptions = JackNullOption;
bool jack_load() bool jack_load()
@ -152,6 +152,11 @@ bool jack_load()
} }
struct JackDeleter {
void operator()(void *ptr) { jack_free(ptr); }
};
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
struct DeviceEntry { struct DeviceEntry {
std::string mName; std::string mName;
std::string mPattern; std::string mPattern;
@ -160,53 +165,125 @@ struct DeviceEntry {
al::vector<DeviceEntry> PlaybackList; al::vector<DeviceEntry> PlaybackList;
void EnumerateDevices(al::vector<DeviceEntry> &list) void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
{ {
al::vector<DeviceEntry>{}.swap(list); std::remove_reference_t<decltype(list)>{}.swap(list);
list.emplace_back(DeviceEntry{jackDevice, ""}); if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
std::string customList{ConfigValueStr(nullptr, "jack", "custom-devices").value_or("")};
size_t strpos{0};
while(strpos < customList.size())
{ {
size_t nextpos{customList.find(';', strpos)}; for(size_t i{0};ports[i];++i)
size_t seppos{customList.find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{ {
const std::string entry{customList.substr(strpos, nextpos-strpos)}; const char *sep{std::strchr(ports[i], ':')};
ERR("Invalid device entry: \"%s\"\n", entry.c_str()); if(!sep || ports[i] == sep) continue;
const al::span<const char> portdev{ports[i], sep};
auto check_name = [portdev](const DeviceEntry &entry) -> bool
{
const size_t len{portdev.size()};
return entry.mName.length() == len
&& entry.mName.compare(0, len, portdev.data(), len) == 0;
};
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(DeviceEntry{name, name+":"});
const auto &entry = list.back();
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
/* There are ports but couldn't get device names from them. Add a
* generic entry.
*/
if(ports[0] && list.empty())
{
WARN("No device names found in available ports, adding a generic name.\n");
list.emplace_back(DeviceEntry{"JACK", ""});
}
}
if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
{
for(size_t strpos{0};strpos < listopt->size();)
{
size_t nextpos{listopt->find(';', strpos)};
size_t seppos{listopt->find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{
const std::string entry{listopt->substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
continue;
}
const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
const al::span<const char> pattern{listopt->data()+(seppos+1),
std::min(nextpos, listopt->size())-(seppos+1)};
/* Check if this custom pattern already exists in the list. */
auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
{
const size_t len{pattern.size()};
return entry.mPattern.length() == len
&& entry.mPattern.compare(0, len, pattern.data(), len) == 0;
};
auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
if(itemmatch != list.end())
{
/* If so, replace the name with this custom one. */
itemmatch->mName.assign(name.data(), name.size());
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
itemmatch->mPattern.c_str());
}
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(DeviceEntry{std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()}});
const auto &entry = list.back();
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
if(nextpos != std::string::npos) ++nextpos; if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos; strpos = nextpos;
continue;
} }
}
size_t count{1}; if(list.size() > 1)
std::string name{customList.substr(strpos, seppos-strpos)}; {
auto check_name = [&name](const DeviceEntry &entry) -> bool /* Rename entries that have matching names, by appending '#2', '#3',
{ return entry.mName == name; }; * etc, as needed.
while(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) */
for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
{ {
name = customList.substr(strpos, seppos-strpos); auto check_match = [curitem](const DeviceEntry &entry) -> bool
name += " #"; { return entry.mName == curitem->mName; };
name += std::to_string(++count); if(std::find_if(list.begin(), curitem, check_match) != curitem)
{
std::string name{curitem->mName};
size_t count{1};
auto check_name = [&name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
do {
name = curitem->mName;
name += " #";
name += std::to_string(++count);
} while(std::find_if(list.begin(), curitem, check_name) != curitem);
curitem->mName = std::move(name);
}
} }
++seppos;
list.emplace_back(DeviceEntry{std::move(name), customList.substr(seppos, nextpos-seppos)});
const auto &entry = list.back();
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
} }
} }
struct JackPlayback final : public BackendBase { struct JackPlayback final : public BackendBase {
JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { } JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~JackPlayback() override; ~JackPlayback() override;
int processRt(jack_nframes_t numframes) noexcept;
static int processRtC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->processRt(numframes); }
int process(jack_nframes_t numframes) noexcept; int process(jack_nframes_t numframes) noexcept;
static int processC(jack_nframes_t numframes, void *arg) noexcept static int processC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->process(numframes); } { return static_cast<JackPlayback*>(arg)->process(numframes); }
@ -227,6 +304,7 @@ struct JackPlayback final : public BackendBase {
std::mutex mMutex; std::mutex mMutex;
std::atomic<bool> mPlaying{false}; std::atomic<bool> mPlaying{false};
bool mRTMixing{false};
RingBufferPtr mRing; RingBufferPtr mRing;
al::semaphore mSem; al::semaphore mSem;
@ -241,16 +319,40 @@ JackPlayback::~JackPlayback()
if(!mClient) if(!mClient)
return; return;
std::for_each(mPort.begin(), mPort.end(), auto unregister_port = [this](jack_port_t *port) -> void
[this](jack_port_t *port) -> void { if(port) jack_port_unregister(mClient, port); };
{ if(port) jack_port_unregister(mClient, port); } std::for_each(mPort.begin(), mPort.end(), unregister_port);
);
mPort.fill(nullptr); mPort.fill(nullptr);
jack_client_close(mClient); jack_client_close(mClient);
mClient = nullptr; mClient = nullptr;
} }
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
size_t numchans{0};
for(auto port : mPort)
{
if(!port || numchans == mDevice->RealOut.Buffer.size())
break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
if LIKELY(mPlaying.load(std::memory_order_acquire))
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
else
{
auto clear_buf = [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
return 0;
}
int JackPlayback::process(jack_nframes_t numframes) noexcept int JackPlayback::process(jack_nframes_t numframes) noexcept
{ {
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out; std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
@ -352,49 +454,56 @@ int JackPlayback::mixerProc()
void JackPlayback::open(const char *name) void JackPlayback::open(const char *name)
{ {
mPortPattern.clear(); if(!mClient)
if(!name)
name = jackDevice;
else if(strcmp(name, jackDevice) != 0)
{ {
if(PlaybackList.empty()) const PathNamePair &binname = GetProcBinary();
EnumerateDevices(PlaybackList); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to open client connection: 0x%02x", status};
if((status&JackServerStarted))
TRACE("JACK server started\n");
if((status&JackNameNotUnique))
{
client_name = jack_get_client_name(mClient);
TRACE("Client name not unique, got '%s' instead\n", client_name);
}
}
if(PlaybackList.empty())
EnumerateDevices(mClient, PlaybackList);
if(!name && !PlaybackList.empty())
{
name = PlaybackList[0].mName.c_str();
mPortPattern = PlaybackList[0].mPattern;
}
else
{
auto check_name = [name](const DeviceEntry &entry) -> bool auto check_name = [name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; }; { return entry.mName == name; };
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend()) if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice, throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name}; "Device name \"%s\" not found", name?name:""};
mPortPattern = iter->mPattern; mPortPattern = iter->mPattern;
} }
const char *client_name{"alsoft"}; mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
jack_status_t status; jack_set_process_callback(mClient,
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to open client connection: 0x%02x", status};
if((status&JackServerStarted))
TRACE("JACK server started\n");
if((status&JackNameNotUnique))
{
client_name = jack_get_client_name(mClient);
TRACE("Client name not unique, got '%s' instead\n", client_name);
}
jack_set_process_callback(mClient, &JackPlayback::processC, this);
mDevice->DeviceName = name; mDevice->DeviceName = name;
} }
bool JackPlayback::reset() bool JackPlayback::reset()
{ {
std::for_each(mPort.begin(), mPort.end(), auto unregister_port = [this](jack_port_t *port) -> void
[this](jack_port_t *port) -> void { if(port) jack_port_unregister(mClient, port); };
{ if(port) jack_port_unregister(mClient, port); }); std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr); mPort.fill(nullptr);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
@ -402,28 +511,39 @@ bool JackPlayback::reset()
*/ */
mDevice->Frequency = jack_get_sample_rate(mClient); mDevice->Frequency = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient); mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2; if(mRTMixing)
{
const char *devname{mDevice->DeviceName.c_str()}; /* Assume only two periods when directly mixing. Should try to query
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; * the total port latency when connected.
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); */
mDevice->BufferSize = bufsize + mDevice->UpdateSize; mDevice->BufferSize = mDevice->UpdateSize * 2;
}
else
{
const char *devname{mDevice->DeviceName.c_str()};
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
}
/* Force 32-bit float output. */ /* Force 32-bit float output. */
mDevice->FmtType = DevFmtFloat; mDevice->FmtType = DevFmtFloat;
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt(); auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = std::find_if_not(mPort.begin(), ports_end, auto bad_port = mPort.begin();
[this](jack_port_t *&port) -> bool while(bad_port != ports_end)
{ {
std::string name{"channel_" + std::to_string(&port - &mPort[0] + 1)}; std::string name{"channel_" + std::to_string(++port_num)};
port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
JackPortIsOutput, 0); JackPortIsOutput | JackPortIsTerminal, 0);
return port != nullptr; if(!*bad_port) break;
}); ++bad_port;
}
if(bad_port != ports_end) if(bad_port != ports_end)
{ {
ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); ERR("Failed to register enough JACK ports for %s output\n",
DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false; if(bad_port == mPort.begin()) return false;
if(bad_port == mPort.begin()+1) if(bad_port == mPort.begin()+1)
@ -453,29 +573,25 @@ void JackPlayback::start()
const char *devname{mDevice->DeviceName.c_str()}; const char *devname{mDevice->DeviceName.c_str()};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{ {
const char **ports{jack_get_ports(mClient, mPortPattern.c_str(), nullptr, JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
JackPortIsPhysical|JackPortIsInput)}; JackPortIsInput)};
if(ports == nullptr) if(!pnames)
{ {
jack_deactivate(mClient); jack_deactivate(mClient);
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
"No physical playback ports found"};
} }
auto connect_port = [this](const jack_port_t *port, const char *pname) -> bool
for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
{ {
if(!port) return false; if(!pnames[i])
if(!pname)
{ {
ERR("No physical playback port for \"%s\"\n", jack_port_name(port)); ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
return false; break;
} }
if(jack_connect(mClient, jack_port_name(port), pname)) if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port), ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
pname); pnames[i]);
return true; }
};
std::mismatch(mPort.begin(), mPort.end(), ports, connect_port);
jack_free(ports);
} }
/* Reconfigure buffer metrics in case the server changed it since the reset /* Reconfigure buffer metrics in case the server changed it since the reset
@ -486,36 +602,45 @@ void JackPlayback::start()
mDevice->UpdateSize = jack_get_buffer_size(mClient); mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2; mDevice->BufferSize = mDevice->UpdateSize * 2;
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mRing = nullptr; mRing = nullptr;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); if(mRTMixing)
try {
mPlaying.store(true, std::memory_order_release); mPlaying.store(true, std::memory_order_release);
mKillNow.store(false, std::memory_order_release); else
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; {
} uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
catch(std::exception& e) { bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
jack_deactivate(mClient); mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mPlaying.store(false, std::memory_order_release);
throw al::backend_exception{al::backend_error::DeviceError, mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
"Failed to start mixing thread: %s", e.what()};
try {
mPlaying.store(true, std::memory_order_release);
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
}
catch(std::exception& e) {
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
} }
} }
void JackPlayback::stop() void JackPlayback::stop()
{ {
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) if(mPlaying.load(std::memory_order_acquire))
return; {
mKillNow.store(true, std::memory_order_release);
if(mThread.joinable())
{
mSem.post();
mThread.join();
}
mSem.post(); jack_deactivate(mClient);
mThread.join(); mPlaying.store(false, std::memory_order_release);
}
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
} }
@ -525,7 +650,7 @@ ClockLatency JackPlayback::getClockLatency()
std::lock_guard<std::mutex> _{mMutex}; std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice); ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing->readSpace()}; ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency; ret.Latency /= mDevice->Frequency;
return ret; return ret;
@ -547,10 +672,13 @@ bool JackBackendFactory::init()
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer); ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler); jack_set_error_function(jack_msg_handler);
jack_status_t status; jack_status_t status;
jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)}; jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb); jack_set_error_function(old_error_cb);
if(!client) if(!client)
{ {
@ -575,10 +703,20 @@ std::string JackBackendFactory::probe(BackendType type)
/* Includes null char. */ /* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1); outnames.append(entry.mName.c_str(), entry.mName.length()+1);
}; };
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
switch(type) switch(type)
{ {
case BackendType::Playback: case BackendType::Playback:
EnumerateDevices(PlaybackList); if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
{
EnumerateDevices(client, PlaybackList);
jack_client_close(client);
}
else
WARN("jack_client_open() failed, 0x%02x\n", status);
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break; break;
case BackendType::Capture: case BackendType::Capture:
@ -587,7 +725,7 @@ std::string JackBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new JackPlayback{device}}; return BackendPtr{new JackPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_JACK_H #ifndef BACKENDS_JACK_H
#define BACKENDS_JACK_H #define BACKENDS_JACK_H
#include "backends/base.h" #include "base.h"
struct JackBackendFactory final : public BackendFactory { struct JackBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,18 +20,17 @@
#include "config.h" #include "config.h"
#include "backends/loopback.h" #include "loopback.h"
#include "alcmain.h" #include "core/device.h"
#include "alu.h"
namespace { namespace {
struct LoopbackBackend final : public BackendBase { struct LoopbackBackend final : public BackendBase {
LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { } LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
void open(const ALCchar *name) override; void open(const char *name) override;
bool reset() override; bool reset() override;
void start() override; void start() override;
void stop() override; void stop() override;
@ -40,7 +39,7 @@ struct LoopbackBackend final : public BackendBase {
}; };
void LoopbackBackend::open(const ALCchar *name) void LoopbackBackend::open(const char *name)
{ {
mDevice->DeviceName = name; mDevice->DeviceName = name;
} }
@ -69,7 +68,7 @@ bool LoopbackBackendFactory::querySupport(BackendType)
std::string LoopbackBackendFactory::probe(BackendType) std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; } { return std::string{}; }
BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType) BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; } { return BackendPtr{new LoopbackBackend{device}}; }
BackendFactory &LoopbackBackendFactory::getFactory() BackendFactory &LoopbackBackendFactory::getFactory()

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_LOOPBACK_H #ifndef BACKENDS_LOOPBACK_H
#define BACKENDS_LOOPBACK_H #define BACKENDS_LOOPBACK_H
#include "backends/base.h" #include "base.h"
struct LoopbackBackendFactory final : public BackendFactory { struct LoopbackBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "backends/null.h" #include "null.h"
#include <exception> #include <exception>
#include <atomic> #include <atomic>
@ -30,9 +30,9 @@
#include <functional> #include <functional>
#include <thread> #include <thread>
#include "alcmain.h" #include "core/device.h"
#include "almalloc.h" #include "almalloc.h"
#include "alu.h" #include "core/helpers.h"
#include "threads.h" #include "threads.h"
@ -46,7 +46,7 @@ constexpr char nullDevice[] = "No Output";
struct NullBackend final : public BackendBase { struct NullBackend final : public BackendBase {
NullBackend(ALCdevice *device) noexcept : BackendBase{device} { } NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
int mixerProc(); int mixerProc();
@ -165,7 +165,7 @@ std::string NullBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new NullBackend{device}}; return BackendPtr{new NullBackend{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_NULL_H #ifndef BACKENDS_NULL_H
#define BACKENDS_NULL_H #define BACKENDS_NULL_H
#include "backends/base.h" #include "base.h"
struct NullBackendFactory final : public BackendFactory { struct NullBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -0,0 +1,384 @@
#include "config.h"
#include "oboe.h"
#include <cassert>
#include <cstring>
#include <stdint.h>
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include "oboe/Oboe.h"
namespace {
constexpr char device_name[] = "Oboe Default";
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
OboePlayback(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
};
oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames)
{
assert(numFrames > 0);
const int32_t numChannels{oboeStream->getChannelCount()};
mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
static_cast<uint32_t>(numChannels));
return oboe::DataCallbackResult::Continue;
}
void OboePlayback::open(const char *name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
/* Open a basic output stream, just to ensure it can work. */
oboe::ManagedStream stream;
oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->openManagedStream(stream)};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mDevice->DeviceName = name;
}
bool OboePlayback::reset()
{
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
/* Don't let Oboe convert. We should be able to handle anything it gives
* back.
*/
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
builder.setChannelConversionAllowed(false);
builder.setFormatConversionAllowed(false);
builder.setCallback(this);
if(mDevice->Flags.test(FrequencyRequest))
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
if(mDevice->Flags.test(ChannelsRequest))
{
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
: (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
: oboe::ChannelCount::Unspecified);
}
if(mDevice->Flags.test(SampleTypeRequest))
{
oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
switch(mDevice->FmtType)
{
case DevFmtByte:
case DevFmtUByte:
case DevFmtShort:
case DevFmtUShort:
format = oboe::AudioFormat::I16;
break;
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
format = oboe::AudioFormat::Float;
break;
}
builder.setFormat(format);
}
oboe::Result result{builder.openManagedStream(mStream)};
/* If the format failed, try asking for the defaults. */
while(result == oboe::Result::ErrorInvalidFormat)
{
if(builder.getFormat() != oboe::AudioFormat::Unspecified)
builder.setFormat(oboe::AudioFormat::Unspecified);
else if(builder.getSampleRate() != oboe::kUnspecified)
builder.setSampleRate(oboe::kUnspecified);
else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
builder.setChannelCount(oboe::ChannelCount::Unspecified);
else
break;
result = builder.openManagedStream(mStream);
}
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
mStream->getBufferCapacityInFrames()));
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
{
if(mStream->getChannelCount() >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mStream->getChannelCount() == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled channel count: %d", mStream->getChannelCount()};
}
setDefaultWFXChannelOrder();
switch(mStream->getFormat())
{
case oboe::AudioFormat::I16:
mDevice->FmtType = DevFmtShort;
break;
case oboe::AudioFormat::Float:
mDevice->FmtType = DevFmtFloat;
break;
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
}
mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
/* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
* indicating variable updates, but OpenAL should have a reasonable minimum update size set.
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size.
*/
mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true;
}
void OboePlayback::start()
{
const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
oboe::convertToText(result)};
}
void OboePlayback::stop()
{
oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
}
struct OboeCapture final : public BackendBase {
OboeCapture(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
std::vector<al::byte> mSamples;
uint mLastAvail{0u};
void open(const char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
};
void OboeCapture::open(const char *name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
->setChannelConversionAllowed(true)
->setFormatConversionAllowed(true)
->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize))
->setSampleRate(static_cast<int32_t>(mDevice->Frequency));
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
switch(mDevice->FmtChans)
{
case DevFmtMono:
builder.setChannelCount(oboe::ChannelCount::Mono);
break;
case DevFmtStereo:
builder.setChannelCount(oboe::ChannelCount::Stereo);
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
/* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
* use a temp buffer and convert.
*/
switch(mDevice->FmtType)
{
case DevFmtShort:
builder.setFormat(oboe::AudioFormat::I16);
break;
case DevFmtFloat:
builder.setFormat(oboe::AudioFormat::Float);
break;
case DevFmtByte:
case DevFmtUByte:
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
oboe::Result result{builder.openManagedStream(mStream)};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
if(static_cast<int32_t>(mDevice->BufferSize) > mStream->getBufferCapacityInFrames())
throw al::backend_exception{al::backend_error::DeviceError,
"Buffer size too large (%u > %d)", mDevice->BufferSize,
mStream->getBufferCapacityInFrames()};
auto buffer_result = mStream->setBufferSizeInFrames(static_cast<int32_t>(mDevice->BufferSize));
if(!buffer_result)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set buffer size: %s", oboe::convertToText(buffer_result.error())};
else if(buffer_result.value() < static_cast<int32_t>(mDevice->BufferSize))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set large enough buffer size (%u > %d)", mDevice->BufferSize,
buffer_result.value()};
mDevice->BufferSize = static_cast<uint>(buffer_result.value());
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
mDevice->DeviceName = name;
}
void OboeCapture::start()
{
const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
oboe::convertToText(result)};
}
void OboeCapture::stop()
{
/* Capture any unread samples before stopping. Oboe drops whatever's left
* in the stream.
*/
if(auto availres = mStream->getAvailableFrames())
{
const auto avail = std::max(static_cast<uint>(availres.value()), mLastAvail);
const size_t frame_size{static_cast<uint32_t>(mStream->getBytesPerFrame())};
const size_t pos{mSamples.size()};
mSamples.resize(pos + avail*frame_size);
auto result = mStream->read(&mSamples[pos], availres.value(), 0);
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
if(got < avail)
std::fill_n(&mSamples[pos + got*frame_size], (avail-got)*frame_size, al::byte{});
mLastAvail = 0;
}
const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
}
uint OboeCapture::availableSamples()
{
/* Keep track of the max available frame count, to ensure it doesn't go
* backwards.
*/
if(auto result = mStream->getAvailableFrames())
mLastAvail = std::max(static_cast<uint>(result.value()), mLastAvail);
const auto frame_size = static_cast<uint32_t>(mStream->getBytesPerFrame());
return static_cast<uint>(mSamples.size()/frame_size) + mLastAvail;
}
void OboeCapture::captureSamples(al::byte *buffer, uint samples)
{
const auto frame_size = static_cast<uint>(mStream->getBytesPerFrame());
if(const size_t storelen{mSamples.size()})
{
const auto instore = static_cast<uint>(storelen / frame_size);
const uint tocopy{std::min(samples, instore) * frame_size};
std::copy_n(mSamples.begin(), tocopy, buffer);
mSamples.erase(mSamples.begin(), mSamples.begin() + tocopy);
buffer += tocopy;
samples -= tocopy/frame_size;
if(!samples) return;
}
auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0);
uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
if(got < samples)
std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{});
mLastAvail = std::max(mLastAvail, samples) - samples;
}
} // namespace
bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string OboeBackendFactory::probe(BackendType type)
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
return std::string{device_name, sizeof(device_name)};
}
return std::string{};
}
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OboePlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new OboeCapture{device}};
return BackendPtr{};
}
BackendFactory &OboeBackendFactory::getFactory()
{
static OboeBackendFactory factory{};
return factory;
}

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_OBOE_H #ifndef BACKENDS_OBOE_H
#define BACKENDS_OBOE_H #define BACKENDS_OBOE_H
#include "backends/base.h" #include "base.h"
struct OboeBackendFactory final : public BackendFactory { struct OboeBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -21,7 +21,7 @@
#include "config.h" #include "config.h"
#include "backends/opensl.h" #include "opensl.h"
#include <stdlib.h> #include <stdlib.h>
#include <jni.h> #include <jni.h>
@ -33,9 +33,9 @@
#include <functional> #include <functional>
#include "albit.h" #include "albit.h"
#include "alcmain.h" #include "alnumeric.h"
#include "alu.h" #include "core/device.h"
#include "compat.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "opthelpers.h" #include "opthelpers.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -68,9 +68,6 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
SL_SPEAKER_SIDE_RIGHT; SL_SPEAKER_SIDE_RIGHT;
case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
SL_SPEAKER_BACK_RIGHT;
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
@ -105,7 +102,7 @@ constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept
constexpr SLuint32 GetByteOrderEndianness() noexcept constexpr SLuint32 GetByteOrderEndianness() noexcept
{ {
if_constexpr(al::endian::native == al::endian::little) if(al::endian::native == al::endian::little)
return SL_BYTEORDER_LITTLEENDIAN; return SL_BYTEORDER_LITTLEENDIAN;
return SL_BYTEORDER_BIGENDIAN; return SL_BYTEORDER_BIGENDIAN;
} }
@ -151,7 +148,7 @@ const char *res_str(SLresult result) noexcept
struct OpenSLPlayback final : public BackendBase { struct OpenSLPlayback final : public BackendBase {
OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { } OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLPlayback() override; ~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept; void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@ -316,6 +313,9 @@ void OpenSLPlayback::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name}; name};
/* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return;
// create engine // create engine
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine"); PRINTERR(result, "slCreateEngine");
@ -629,7 +629,7 @@ ClockLatency OpenSLPlayback::getClockLatency()
struct OpenSLCapture final : public BackendBase { struct OpenSLCapture final : public BackendBase {
OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { } OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLCapture() override; ~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept; void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@ -810,8 +810,11 @@ void OpenSLCapture::open(const char* name)
if(SL_RESULT_SUCCESS == result) if(SL_RESULT_SUCCESS == result)
{ {
const uint chunk_size{mDevice->UpdateSize * mFrameSize}; const uint chunk_size{mDevice->UpdateSize * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
auto data = mRing->getWriteVector(); auto data = mRing->getWriteVector();
std::fill_n(data.first.buf, data.first.len*chunk_size, silence);
std::fill_n(data.second.buf, data.second.len*chunk_size, silence);
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
{ {
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
@ -875,6 +878,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
{ {
const uint update_size{mDevice->UpdateSize}; const uint update_size{mDevice->UpdateSize};
const uint chunk_size{update_size * mFrameSize}; const uint chunk_size{update_size * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
/* Read the desired samples from the ring buffer then advance its read /* Read the desired samples from the ring buffer then advance its read
* pointer. * pointer.
@ -922,15 +926,20 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
{ {
SLresult result{SL_RESULT_SUCCESS}; SLresult result{SL_RESULT_SUCCESS};
auto wdata = mRing->getWriteVector(); auto wdata = mRing->getWriteVector();
std::fill_n(wdata.first.buf, wdata.first.len*chunk_size, silence);
for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++) for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++)
{ {
result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size); result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue"); PRINTERR(result, "bufferQueue->Enqueue");
} }
for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++) if(wdata.second.len > 0)
{ {
result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size); std::fill_n(wdata.second.buf, wdata.second.len*chunk_size, silence);
PRINTERR(result, "bufferQueue->Enqueue"); for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
}
} }
} }
} }
@ -959,7 +968,7 @@ std::string OSLBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new OpenSLPlayback{device}}; return BackendPtr{new OpenSLPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_OSL_H #ifndef BACKENDS_OSL_H
#define BACKENDS_OSL_H #define BACKENDS_OSL_H
#include "backends/base.h" #include "base.h"
struct OSLBackendFactory final : public BackendFactory { struct OSLBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "backends/oss.h" #include "oss.h"
#include <fcntl.h> #include <fcntl.h>
#include <poll.h> #include <poll.h>
@ -41,13 +41,13 @@
#include <thread> #include <thread>
#include <utility> #include <utility>
#include "alcmain.h"
#include "alconfig.h"
#include "albyte.h" #include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h" #include "almalloc.h"
#include "alnumeric.h" #include "alnumeric.h"
#include "aloptional.h" #include "aloptional.h"
#include "alu.h" #include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "threads.h" #include "threads.h"
@ -226,7 +226,7 @@ uint log2i(uint x)
struct OSSPlayback final : public BackendBase { struct OSSPlayback final : public BackendBase {
OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { } OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OSSPlayback() override; ~OSSPlayback() override;
int mixerProc(); int mixerProc();
@ -249,7 +249,7 @@ struct OSSPlayback final : public BackendBase {
OSSPlayback::~OSSPlayback() OSSPlayback::~OSSPlayback()
{ {
if(mFd != -1) if(mFd != -1)
close(mFd); ::close(mFd);
mFd = -1; mFd = -1;
} }
@ -328,11 +328,15 @@ void OSSPlayback::open(const char *name)
devname = iter->device_name.c_str(); devname = iter->device_name.c_str();
} }
mFd = ::open(devname, O_WRONLY); int fd{::open(devname, O_WRONLY)};
if(mFd == -1) if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)}; strerror(errno)};
if(mFd != -1)
::close(mFd);
mFd = fd;
mDevice->DeviceName = name; mDevice->DeviceName = name;
} }
@ -438,7 +442,7 @@ void OSSPlayback::stop()
struct OSScapture final : public BackendBase { struct OSScapture final : public BackendBase {
OSScapture(ALCdevice *device) noexcept : BackendBase{device} { } OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OSScapture() override; ~OSScapture() override;
int recordProc(); int recordProc();
@ -676,7 +680,7 @@ std::string OSSBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new OSSPlayback{device}}; return BackendPtr{new OSSPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_OSS_H #ifndef BACKENDS_OSS_H
#define BACKENDS_OSS_H #define BACKENDS_OSS_H
#include "backends/base.h" #include "base.h"
struct OSSBackendFactory final : public BackendFactory { struct OSSBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
#ifndef BACKENDS_PIPEWIRE_H
#define BACKENDS_PIPEWIRE_H
#include <string>
#include "base.h"
struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PIPEWIRE_H */

View file

@ -20,15 +20,15 @@
#include "config.h" #include "config.h"
#include "backends/portaudio.h" #include "portaudio.h"
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include "alcmain.h" #include "alc/alconfig.h"
#include "alu.h" #include "alnumeric.h"
#include "alconfig.h" #include "core/device.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "ringbuffer.h" #include "ringbuffer.h"
@ -72,7 +72,7 @@ MAKE_FUNC(Pa_GetStreamInfo);
struct PortPlayback final : public BackendBase { struct PortPlayback final : public BackendBase {
PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { } PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PortPlayback() override; ~PortPlayback() override;
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
@ -123,53 +123,58 @@ void PortPlayback::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name}; name};
mUpdateSize = mDevice->UpdateSize; PaStreamParameters params{};
auto devidopt = ConfigValueInt(nullptr, "port", "device"); auto devidopt = ConfigValueInt(nullptr, "port", "device");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt; if(devidopt && *devidopt >= 0) params.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice(); else params.device = Pa_GetDefaultOutputDevice();
mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency); params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
mParams.hostApiSpecificStreamInfo = nullptr; params.hostApiSpecificStreamInfo = nullptr;
mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
case DevFmtByte: case DevFmtByte:
mParams.sampleFormat = paInt8; params.sampleFormat = paInt8;
break; break;
case DevFmtUByte: case DevFmtUByte:
mParams.sampleFormat = paUInt8; params.sampleFormat = paUInt8;
break; break;
case DevFmtUShort: case DevFmtUShort:
/* fall-through */ /* fall-through */
case DevFmtShort: case DevFmtShort:
mParams.sampleFormat = paInt16; params.sampleFormat = paInt16;
break; break;
case DevFmtUInt: case DevFmtUInt:
/* fall-through */ /* fall-through */
case DevFmtInt: case DevFmtInt:
mParams.sampleFormat = paInt32; params.sampleFormat = paInt32;
break; break;
case DevFmtFloat: case DevFmtFloat:
mParams.sampleFormat = paFloat32; params.sampleFormat = paFloat32;
break; break;
} }
retry_open: retry_open:
PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize, PaStream *stream{};
PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)}; paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError) if(err != paNoError)
{ {
if(mParams.sampleFormat == paFloat32) if(params.sampleFormat == paFloat32)
{ {
mParams.sampleFormat = paInt16; params.sampleFormat = paInt16;
goto retry_open; goto retry_open;
} }
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)}; Pa_GetErrorText(err)};
} }
Pa_CloseStream(mStream);
mStream = stream;
mParams = params;
mUpdateSize = mDevice->UpdateSize;
mDevice->DeviceName = name; mDevice->DeviceName = name;
} }
@ -195,7 +200,7 @@ bool PortPlayback::reset()
return false; return false;
} }
if(mParams.channelCount == 2) if(mParams.channelCount >= 2)
mDevice->FmtChans = DevFmtStereo; mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1) else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono; mDevice->FmtChans = DevFmtMono;
@ -226,7 +231,7 @@ void PortPlayback::stop()
struct PortCapture final : public BackendBase { struct PortCapture final : public BackendBase {
PortCapture(ALCdevice *device) noexcept : BackendBase{device} { } PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PortCapture() override; ~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
@ -426,7 +431,7 @@ std::string PortBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new PortPlayback{device}}; return BackendPtr{new PortPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_PORTAUDIO_H #ifndef BACKENDS_PORTAUDIO_H
#define BACKENDS_PORTAUDIO_H #define BACKENDS_PORTAUDIO_H
#include "backends/base.h" #include "base.h"
struct PortBackendFactory final : public BackendFactory { struct PortBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -21,33 +21,49 @@
#include "config.h" #include "config.h"
#include "backends/pulseaudio.h" #include "pulseaudio.h"
#include <poll.h>
#include <cstring>
#include <array>
#include <string>
#include <vector>
#include <atomic>
#include <thread>
#include <algorithm> #include <algorithm>
#include <functional> #include <array>
#include <atomic>
#include <bitset>
#include <chrono>
#include <condition_variable> #include <condition_variable>
#include <cstring>
#include <functional>
#include <limits>
#include <mutex>
#include <new>
#include <poll.h>
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <sys/types.h>
#include <thread>
#include <utility>
#include "alcmain.h" #include "albyte.h"
#include "alu.h" #include "alc/alconfig.h"
#include "alconfig.h" #include "almalloc.h"
#include "compat.h" #include "alnumeric.h"
#include "aloptional.h"
#include "alspan.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "dynload.h" #include "dynload.h"
#include "opthelpers.h"
#include "strutils.h" #include "strutils.h"
#include "vector.h"
#include <pulse/pulseaudio.h> #include <pulse/pulseaudio.h>
namespace { namespace {
using uint = unsigned int;
#ifdef HAVE_DYNLOAD #ifdef HAVE_DYNLOAD
#define PULSE_FUNCS(MAGIC) \ #define PULSE_FUNCS(MAGIC) \
MAGIC(pa_mainloop_new); \ MAGIC(pa_mainloop_new); \
@ -220,78 +236,6 @@ constexpr pa_channel_map MonoChanMap{
} }
}; };
al::optional<Channel> ChannelFromPulse(pa_channel_position_t chan)
{
switch(chan)
{
case PA_CHANNEL_POSITION_INVALID: break;
case PA_CHANNEL_POSITION_MONO: return al::make_optional(FrontCenter);
case PA_CHANNEL_POSITION_FRONT_LEFT: return al::make_optional(FrontLeft);
case PA_CHANNEL_POSITION_FRONT_RIGHT: return al::make_optional(FrontRight);
case PA_CHANNEL_POSITION_FRONT_CENTER: return al::make_optional(FrontCenter);
case PA_CHANNEL_POSITION_REAR_CENTER: return al::make_optional(BackCenter);
case PA_CHANNEL_POSITION_REAR_LEFT: return al::make_optional(BackLeft);
case PA_CHANNEL_POSITION_REAR_RIGHT: return al::make_optional(BackRight);
case PA_CHANNEL_POSITION_LFE: return al::make_optional(LFE);
case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break;
case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break;
case PA_CHANNEL_POSITION_SIDE_LEFT: return al::make_optional(SideLeft);
case PA_CHANNEL_POSITION_SIDE_RIGHT: return al::make_optional(SideRight);
case PA_CHANNEL_POSITION_AUX0: break;
case PA_CHANNEL_POSITION_AUX1: break;
case PA_CHANNEL_POSITION_AUX2: break;
case PA_CHANNEL_POSITION_AUX3: break;
case PA_CHANNEL_POSITION_AUX4: break;
case PA_CHANNEL_POSITION_AUX5: break;
case PA_CHANNEL_POSITION_AUX6: break;
case PA_CHANNEL_POSITION_AUX7: break;
case PA_CHANNEL_POSITION_AUX8: break;
case PA_CHANNEL_POSITION_AUX9: break;
case PA_CHANNEL_POSITION_AUX10: break;
case PA_CHANNEL_POSITION_AUX11: break;
case PA_CHANNEL_POSITION_AUX12: break;
case PA_CHANNEL_POSITION_AUX13: break;
case PA_CHANNEL_POSITION_AUX14: break;
case PA_CHANNEL_POSITION_AUX15: break;
case PA_CHANNEL_POSITION_AUX16: break;
case PA_CHANNEL_POSITION_AUX17: break;
case PA_CHANNEL_POSITION_AUX18: break;
case PA_CHANNEL_POSITION_AUX19: break;
case PA_CHANNEL_POSITION_AUX20: break;
case PA_CHANNEL_POSITION_AUX21: break;
case PA_CHANNEL_POSITION_AUX22: break;
case PA_CHANNEL_POSITION_AUX23: break;
case PA_CHANNEL_POSITION_AUX24: break;
case PA_CHANNEL_POSITION_AUX25: break;
case PA_CHANNEL_POSITION_AUX26: break;
case PA_CHANNEL_POSITION_AUX27: break;
case PA_CHANNEL_POSITION_AUX28: break;
case PA_CHANNEL_POSITION_AUX29: break;
case PA_CHANNEL_POSITION_AUX30: break;
case PA_CHANNEL_POSITION_AUX31: break;
case PA_CHANNEL_POSITION_TOP_CENTER: return al::make_optional(TopCenter);
case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return al::make_optional(TopBackLeft);
case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return al::make_optional(TopBackRight);
case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return al::make_optional(TopBackCenter);
case PA_CHANNEL_POSITION_MAX: break;
}
WARN("Unexpected channel enum %d (%s)\n", chan, pa_channel_position_to_string(chan));
return al::nullopt;
}
void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap)
{
device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
for(uint i{0};i < chanmap.channels;++i)
{
if(auto label = ChannelFromPulse(chanmap.map[i]))
device->RealOut.ChannelIndex[*label] = i;
}
}
/* *grumble* Don't use enums for bitflags. */ /* *grumble* Don't use enums for bitflags. */
constexpr inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) constexpr inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs)
@ -497,19 +441,13 @@ public:
pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock) pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock)
{ {
const char *name{"OpenAL Soft"};
const PathNamePair &binname = GetProcBinary();
if(!binname.fname.empty())
name = binname.fname.c_str();
if(!mMainloop) if(!mMainloop)
{ {
mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_proc), this}; mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_proc), this};
mCondVar.wait(plock, [this]() noexcept { return mMainloop; }); mCondVar.wait(plock, [this]() noexcept { return mMainloop; });
} }
pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), name)}; pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), nullptr)};
if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, if(!context) throw al::backend_exception{al::backend_error::OutOfMemory,
"pa_context_new() failed"}; "pa_context_new() failed"};
@ -661,7 +599,7 @@ PulseMainloop gGlobalMainloop;
struct PulsePlayback final : public BackendBase { struct PulsePlayback final : public BackendBase {
PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { } PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PulsePlayback() override; ~PulsePlayback() override;
void bufferAttrCallback(pa_stream *stream) noexcept; void bufferAttrCallback(pa_stream *stream) noexcept;
@ -698,6 +636,7 @@ struct PulsePlayback final : public BackendBase {
al::optional<std::string> mDeviceName{al::nullopt}; al::optional<std::string> mDeviceName{al::nullopt};
bool mIs51Rear{false};
pa_buffer_attr mAttr; pa_buffer_attr mAttr;
pa_sample_spec mSpec; pa_sample_spec mSpec;
@ -770,15 +709,16 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
struct ChannelMap { struct ChannelMap {
DevFmtChannels fmt; DevFmtChannels fmt;
pa_channel_map map; pa_channel_map map;
bool is_51rear;
}; };
static constexpr std::array<ChannelMap,7> chanmaps{{ static constexpr std::array<ChannelMap,7> chanmaps{{
{ DevFmtX71, X71ChanMap }, { DevFmtX71, X71ChanMap, false },
{ DevFmtX61, X61ChanMap }, { DevFmtX61, X61ChanMap, false },
{ DevFmtX51, X51ChanMap }, { DevFmtX51, X51ChanMap, false },
{ DevFmtX51Rear, X51RearChanMap }, { DevFmtX51, X51RearChanMap, true },
{ DevFmtQuad, QuadChanMap }, { DevFmtQuad, QuadChanMap, false },
{ DevFmtStereo, StereoChanMap }, { DevFmtStereo, StereoChanMap, false },
{ DevFmtMono, MonoChanMap } { DevFmtMono, MonoChanMap, false }
}}; }};
if(eol) if(eol)
@ -795,9 +735,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
{ {
if(!mDevice->Flags.test(ChannelsRequest)) if(!mDevice->Flags.test(ChannelsRequest))
mDevice->FmtChans = chaniter->fmt; mDevice->FmtChans = chaniter->fmt;
mIs51Rear = chaniter->is_51rear;
} }
else else
{ {
mIs51Rear = false;
char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{}; char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{};
pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map);
WARN("Failed to find format for channel map:\n %s\n", chanmap_str); WARN("Failed to find format for channel map:\n %s\n", chanmap_str);
@ -805,8 +747,8 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
if(info->active_port) if(info->active_port)
TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description); TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description);
mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo mDevice->Flags.set(DirectEar, (info->active_port
&& info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0); && strcmp(info->active_port->name, "analog-output-headphones") == 0));
} }
void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
@ -846,7 +788,8 @@ void PulsePlayback::open(const char *name)
} }
auto plock = mMainloop.getUniqueLock(); auto plock = mMainloop.getUniqueLock();
mContext = mMainloop.connectContext(plock); if(!mContext)
mContext = mMainloop.connectContext(plock);
pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
PA_STREAM_FIX_CHANNELS}; PA_STREAM_FIX_CHANNELS};
@ -864,8 +807,18 @@ void PulsePlayback::open(const char *name)
if(defname) pulse_name = defname->c_str(); if(defname) pulse_name = defname->c_str();
} }
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr, pa_stream *stream{mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec,
BackendType::Playback); nullptr, BackendType::Playback)};
if(mStream)
{
pa_stream_set_state_callback(mStream, nullptr, nullptr);
pa_stream_set_moved_callback(mStream, nullptr, nullptr);
pa_stream_set_write_callback(mStream, nullptr, nullptr);
pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr);
pa_stream_disconnect(mStream);
pa_stream_unref(mStream);
}
mStream = stream;
pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this);
mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream))); mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
@ -934,10 +887,7 @@ bool PulsePlayback::reset()
chanmap = QuadChanMap; chanmap = QuadChanMap;
break; break;
case DevFmtX51: case DevFmtX51:
chanmap = X51ChanMap; chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap);
break;
case DevFmtX51Rear:
chanmap = X51RearChanMap;
break; break;
case DevFmtX61: case DevFmtX61:
chanmap = X61ChanMap; chanmap = X61ChanMap;
@ -946,7 +896,7 @@ bool PulsePlayback::reset()
chanmap = X71ChanMap; chanmap = X71ChanMap;
break; break;
} }
SetChannelOrderFromMap(mDevice, chanmap); setDefaultWFXChannelOrder();
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
@ -1029,34 +979,34 @@ void PulsePlayback::start()
{ {
auto plock = mMainloop.getUniqueLock(); auto plock = mMainloop.getUniqueLock();
pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this); /* Write some (silent) samples to fill the buffer before we start feeding
pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, * it newly mixed samples.
&mMainloop)}; */
if(size_t todo{pa_stream_writable_size(mStream)})
/* Write some (silent) samples to fill the prebuf amount if needed. */
if(size_t prebuf{mAttr.prebuf})
{ {
prebuf = minz(prebuf, pa_stream_writable_size(mStream)); void *buf{pa_xmalloc(todo)};
void *buf{pa_xmalloc(prebuf)};
switch(mSpec.format) switch(mSpec.format)
{ {
case PA_SAMPLE_U8: case PA_SAMPLE_U8:
std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x80); std::fill_n(static_cast<uint8_t*>(buf), todo, 0x80);
break; break;
case PA_SAMPLE_ALAW: case PA_SAMPLE_ALAW:
std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0xD5); std::fill_n(static_cast<uint8_t*>(buf), todo, 0xD5);
break; break;
case PA_SAMPLE_ULAW: case PA_SAMPLE_ULAW:
std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x7f); std::fill_n(static_cast<uint8_t*>(buf), todo, 0x7f);
break; break;
default: default:
std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x00); std::fill_n(static_cast<uint8_t*>(buf), todo, 0x00);
break; break;
} }
pa_stream_write(mStream, buf, prebuf, pa_xfree, 0, PA_SEEK_RELATIVE); pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE);
} }
pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this);
pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
&mMainloop)};
mMainloop.waitForOperation(op, plock); mMainloop.waitForOperation(op, plock);
} }
@ -1103,7 +1053,7 @@ ClockLatency PulsePlayback::getClockLatency()
struct PulseCapture final : public BackendBase { struct PulseCapture final : public BackendBase {
PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { } PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PulseCapture() override; ~PulseCapture() override;
void streamStateCallback(pa_stream *stream) noexcept; void streamStateCallback(pa_stream *stream) noexcept;
@ -1217,9 +1167,6 @@ void PulseCapture::open(const char *name)
case DevFmtX51: case DevFmtX51:
chanmap = X51ChanMap; chanmap = X51ChanMap;
break; break;
case DevFmtX51Rear:
chanmap = X51RearChanMap;
break;
case DevFmtX61: case DevFmtX61:
chanmap = X61ChanMap; chanmap = X61ChanMap;
break; break;
@ -1230,7 +1177,7 @@ void PulseCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)}; DevFmtChannelsString(mDevice->FmtChans)};
} }
SetChannelOrderFromMap(mDevice, chanmap); setDefaultWFXChannelOrder();
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
@ -1505,7 +1452,7 @@ std::string PulseBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr PulseBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new PulsePlayback{device}}; return BackendPtr{new PulsePlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_PULSEAUDIO_H #ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H
#include "backends/base.h" #include "base.h"
class PulseBackendFactory final : public BackendFactory { class PulseBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,16 +20,16 @@
#include "config.h" #include "config.h"
#include "backends/sdl2.h" #include "sdl2.h"
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <string> #include <string>
#include "alcmain.h"
#include "almalloc.h" #include "almalloc.h"
#include "alu.h" #include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h" #include "core/logging.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -46,7 +46,7 @@ namespace {
constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
struct Sdl2Backend final : public BackendBase { struct Sdl2Backend final : public BackendBase {
Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { } Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override; ~Sdl2Backend() override;
void audioCallback(Uint8 *stream, int len) noexcept; void audioCallback(Uint8 *stream, int len) noexcept;
@ -99,59 +99,64 @@ void Sdl2Backend::open(const char *name)
case DevFmtFloat: want.format = AUDIO_F32; break; case DevFmtFloat: want.format = AUDIO_F32; break;
} }
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
want.samples = static_cast<Uint16>(mDevice->UpdateSize); want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
want.callback = &Sdl2Backend::audioCallbackC; want.callback = &Sdl2Backend::audioCallbackC;
want.userdata = this; want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list. * necessarily the first in the list.
*/ */
SDL_AudioDeviceID devid;
if(!name || strcmp(name, defaultDeviceName) == 0) if(!name || strcmp(name, defaultDeviceName) == 0)
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
SDL_AUDIO_ALLOW_ANY_CHANGE);
else else
{ {
const size_t prefix_len = strlen(DEVNAME_PREFIX); const size_t prefix_len = strlen(DEVNAME_PREFIX);
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE); SDL_AUDIO_ALLOW_ANY_CHANGE);
else else
mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
SDL_AUDIO_ALLOW_ANY_CHANGE);
} }
if(mDeviceID == 0) if(!devid)
throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
mDevice->Frequency = static_cast<uint>(have.freq); DevFmtChannels devchans{};
if(have.channels >= 2)
if(have.channels == 1) devchans = DevFmtStereo;
mDevice->FmtChans = DevFmtMono; else if(have.channels == 1)
else if(have.channels == 2) devchans = DevFmtMono;
mDevice->FmtChans = DevFmtStereo;
else else
{
SDL_CloseAudioDevice(devid);
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL channel count: %d", int{have.channels}}; "Unhandled SDL channel count: %d", int{have.channels}};
}
DevFmtType devtype{};
switch(have.format) switch(have.format)
{ {
case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; case AUDIO_U8: devtype = DevFmtUByte; break;
case AUDIO_S8: mDevice->FmtType = DevFmtByte; break; case AUDIO_S8: devtype = DevFmtByte; break;
case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break; case AUDIO_U16SYS: devtype = DevFmtUShort; break;
case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break; case AUDIO_S16SYS: devtype = DevFmtShort; break;
case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; case AUDIO_S32SYS: devtype = DevFmtInt; break;
case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; case AUDIO_F32SYS: devtype = DevFmtFloat; break;
default: default:
SDL_CloseAudioDevice(devid);
throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x", throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x",
have.format}; have.format};
} }
mDevice->UpdateSize = have.samples;
mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */
mFrameSize = mDevice->frameSizeFromFmt(); if(mDeviceID)
mFrequency = mDevice->Frequency; SDL_CloseAudioDevice(mDeviceID);
mFmtChans = mDevice->FmtChans; mDeviceID = devid;
mFmtType = mDevice->FmtType;
mUpdateSize = mDevice->UpdateSize; mFrameSize = BytesFromDevFmt(devtype) * have.channels;
mFrequency = static_cast<uint>(have.freq);
mFmtChans = devchans;
mFmtType = devtype;
mUpdateSize = have.samples;
mDevice->DeviceName = name ? name : defaultDeviceName; mDevice->DeviceName = name ? name : defaultDeviceName;
} }
@ -162,7 +167,7 @@ bool Sdl2Backend::reset()
mDevice->FmtChans = mFmtChans; mDevice->FmtChans = mFmtChans;
mDevice->FmtType = mFmtType; mDevice->FmtType = mFmtType;
mDevice->UpdateSize = mUpdateSize; mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2; mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */
setDefaultWFXChannelOrder(); setDefaultWFXChannelOrder();
return true; return true;
} }
@ -208,7 +213,7 @@ std::string SDL2BackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new Sdl2Backend{device}}; return BackendPtr{new Sdl2Backend{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_SDL2_H #ifndef BACKENDS_SDL2_H
#define BACKENDS_SDL2_H #define BACKENDS_SDL2_H
#include "backends/base.h" #include "base.h"
struct SDL2BackendFactory final : public BackendFactory { struct SDL2BackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,8 +20,9 @@
#include "config.h" #include "config.h"
#include "backends/sndio.h" #include "sndio.h"
#include <poll.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -29,8 +30,9 @@
#include <thread> #include <thread>
#include <functional> #include <functional>
#include "alcmain.h" #include "alnumeric.h"
#include "alu.h" #include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "threads.h" #include "threads.h"
@ -43,9 +45,14 @@ namespace {
static const char sndio_device[] = "SndIO Default"; static const char sndio_device[] = "SndIO Default";
struct SioPar : public sio_par {
SioPar() { sio_initpar(this); }
void clear() { sio_initpar(this); }
};
struct SndioPlayback final : public BackendBase { struct SndioPlayback final : public BackendBase {
SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { } SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioPlayback() override; ~SndioPlayback() override;
int mixerProc(); int mixerProc();
@ -56,6 +63,7 @@ struct SndioPlayback final : public BackendBase {
void stop() override; void stop() override;
sio_hdl *mSndHandle{nullptr}; sio_hdl *mSndHandle{nullptr};
uint mFrameStep{};
al::vector<al::byte> mBuffer; al::vector<al::byte> mBuffer;
@ -74,16 +82,8 @@ SndioPlayback::~SndioPlayback()
int SndioPlayback::mixerProc() int SndioPlayback::mixerProc()
{ {
sio_par par; const size_t frameStep{mFrameStep};
sio_initpar(&par); const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
if(!sio_getpar(mSndHandle, &par))
{
mDevice->handleDisconnect("Failed to get device parameters");
return 1;
}
const size_t frameStep{par.pchan};
const size_t frameSize{frameStep * par.bps};
SetRTPriority(); SetRTPriority();
althrd_setname(MIXER_THREAD_NAME); althrd_setname(MIXER_THREAD_NAME);
@ -91,22 +91,20 @@ int SndioPlayback::mixerProc()
while(!mKillNow.load(std::memory_order_acquire) while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire)) && mDevice->Connected.load(std::memory_order_acquire))
{ {
al::byte *WritePtr{mBuffer.data()}; al::span<al::byte> buffer{mBuffer};
size_t len{mBuffer.size()};
mDevice->renderSamples(WritePtr, static_cast<uint>(len/frameSize), frameStep); mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
while(len > 0 && !mKillNow.load(std::memory_order_acquire)) frameStep);
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{ {
size_t wrote{sio_write(mSndHandle, WritePtr, len)}; size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
if(wrote == 0) if(wrote == 0)
{ {
ERR("sio_write failed\n"); ERR("sio_write failed\n");
mDevice->handleDisconnect("Failed to write playback samples"); mDevice->handleDisconnect("Failed to write playback samples");
break; break;
} }
buffer = buffer.subspan(wrote);
len -= wrote;
WritePtr += wrote;
} }
} }
@ -122,34 +120,24 @@ void SndioPlayback::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name}; name};
mSndHandle = sio_open(nullptr, SIO_PLAY, 0); sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(mSndHandle == nullptr) if(!sndHandle)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
if(mSndHandle)
sio_close(mSndHandle);
mSndHandle = sndHandle;
mDevice->DeviceName = name; mDevice->DeviceName = name;
} }
bool SndioPlayback::reset() bool SndioPlayback::reset()
{ {
sio_par par; SioPar par;
sio_initpar(&par);
par.rate = mDevice->Frequency; auto tryfmt = mDevice->FmtType;
switch(mDevice->FmtChans) retry_params:
{ switch(tryfmt)
case DevFmtMono : par.pchan = 1; break;
case DevFmtQuad : par.pchan = 4; break;
case DevFmtX51Rear: // fall-through - "Similar to 5.1, except using rear channels instead of sides"
case DevFmtX51 : par.pchan = 6; break;
case DevFmtX61 : par.pchan = 7; break;
case DevFmtX71 : par.pchan = 8; break;
// fall back to stereo for Ambi3D
case DevFmtAmbi3D : // fall-through
case DevFmtStereo : par.pchan = 2; break;
}
switch(mDevice->FmtType)
{ {
case DevFmtByte: case DevFmtByte:
par.bits = 8; par.bits = 8;
@ -159,7 +147,6 @@ bool SndioPlayback::reset()
par.bits = 8; par.bits = 8;
par.sig = 0; par.sig = 0;
break; break;
case DevFmtFloat:
case DevFmtShort: case DevFmtShort:
par.bits = 16; par.bits = 16;
par.sig = 1; par.sig = 1;
@ -168,6 +155,7 @@ bool SndioPlayback::reset()
par.bits = 16; par.bits = 16;
par.sig = 0; par.sig = 0;
break; break;
case DevFmtFloat:
case DevFmtInt: case DevFmtInt:
par.bits = 32; par.bits = 32;
par.sig = 1; par.sig = 1;
@ -177,70 +165,64 @@ bool SndioPlayback::reset()
par.sig = 0; par.sig = 0;
break; break;
} }
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE; par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize; par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) try {
{ if(!sio_setpar(mSndHandle, &par))
ERR("Failed to set device parameters\n"); throw al::backend_exception{al::backend_error::DeviceError,
return false; "Failed to set device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
goto retry_params;
} }
if(par.bits != par.bps*8) if(par.bps == 1)
{ mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte;
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); else if(par.bps == 2)
return false; mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort;
} else if(par.bps == 4)
if(par.le != SIO_LE_NATIVE) mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
{
ERR("Non-native-endian samples not supported (got %s-endian)\n",
par.le ? "little" : "big");
return false;
}
mDevice->Frequency = par.rate;
if(par.pchan < 2)
{
if(mDevice->FmtChans != DevFmtMono)
{
WARN("Got %u channel for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans));
mDevice->FmtChans = DevFmtMono;
}
}
else if((par.pchan == 2 && mDevice->FmtChans != DevFmtStereo)
|| par.pchan == 3
|| (par.pchan == 4 && mDevice->FmtChans != DevFmtQuad)
|| par.pchan == 5
|| (par.pchan == 6 && mDevice->FmtChans != DevFmtX51 && mDevice->FmtChans != DevFmtX51Rear)
|| (par.pchan == 7 && mDevice->FmtChans != DevFmtX61)
|| (par.pchan == 8 && mDevice->FmtChans != DevFmtX71)
|| par.pchan > 8)
{
WARN("Got %u channels for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans));
mDevice->FmtChans = DevFmtStereo;
}
if(par.bits == 8 && par.sig == 1)
mDevice->FmtType = DevFmtByte;
else if(par.bits == 8 && par.sig == 0)
mDevice->FmtType = DevFmtUByte;
else if(par.bits == 16 && par.sig == 1)
mDevice->FmtType = DevFmtShort;
else if(par.bits == 16 && par.sig == 0)
mDevice->FmtType = DevFmtUShort;
else if(par.bits == 32 && par.sig == 1)
mDevice->FmtType = DevFmtInt;
else if(par.bits == 32 && par.sig == 0)
mDevice->FmtType = DevFmtUInt;
else else
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8};
mFrameStep = par.pchan;
if(par.pchan != mDevice->channelsFromFmt())
{ {
ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits); WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
return false; DevFmtChannelsString(mDevice->FmtChans));
if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
else mDevice->FmtChans = DevFmtStereo;
} }
mDevice->Frequency = par.rate;
setDefaultChannelOrder(); setDefaultChannelOrder();
@ -287,8 +269,13 @@ void SndioPlayback::stop()
} }
/* TODO: This could be improved by avoiding the ring buffer and record thread,
* counting the available samples with the sio_onmove callback and reading
* directly from the device. However, this depends on reasonable support for
* capture buffer sizes apps may request.
*/
struct SndioCapture final : public BackendBase { struct SndioCapture final : public BackendBase {
SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { } SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioCapture() override; ~SndioCapture() override;
int recordProc(); int recordProc();
@ -323,40 +310,65 @@ int SndioCapture::recordProc()
const uint frameSize{mDevice->frameSizeFromFmt()}; const uint frameSize{mDevice->frameSizeFromFmt()};
int nfds_pre{sio_nfds(mSndHandle)};
if(nfds_pre <= 0)
{
mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre);
return 1;
}
auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
while(!mKillNow.load(std::memory_order_acquire) while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire)) && mDevice->Connected.load(std::memory_order_acquire))
{ {
auto data = mRing->getWriteVector(); /* Wait until there's some samples to read. */
size_t todo{data.first.len + data.second.len}; const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
if(todo == 0) if(nfds <= 0)
{ {
static char junk[4096]; mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
sio_read(mSndHandle, junk, break;
minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize); }
int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
if(pollres < 0)
{
if(errno == EINTR) continue;
mDevice->handleDisconnect("Poll error: %s", strerror(errno));
break;
}
if(pollres == 0)
continue; continue;
}
size_t total{0u}; const int revents{sio_revents(mSndHandle, fds.get())};
data.first.len *= frameSize; if((revents&POLLHUP))
data.second.len *= frameSize;
todo = minz(todo, mDevice->UpdateSize) * frameSize;
while(total < todo)
{ {
if(!data.first.len) mDevice->handleDisconnect("Got POLLHUP from poll events");
data.first = data.second; break;
}
size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))}; if(!(revents&POLLIN))
if(!got) continue;
{
mDevice->handleDisconnect("Failed to read capture samples"); auto data = mRing->getWriteVector();
break; al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
} while(!buffer.empty())
{
data.first.buf += got; size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
data.first.len -= got; if(got == 0) break;
total += got;
mRing->writeAdvance(got / frameSize);
buffer = buffer.subspan(got);
if(buffer.empty())
{
data = mRing->getWriteVector();
buffer = {data.first.buf, data.first.len*frameSize};
}
}
if(buffer.empty())
{
/* Got samples to read, but no place to store it. Drop it. */
static char junk[4096];
sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
} }
mRing->writeAdvance(total / frameSize);
} }
return 0; return 0;
@ -371,76 +383,80 @@ void SndioCapture::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name}; name};
mSndHandle = sio_open(nullptr, SIO_REC, 0); mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr) if(mSndHandle == nullptr)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
sio_par par; SioPar par;
sio_initpar(&par);
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
case DevFmtByte: case DevFmtByte:
par.bps = 1; par.bits = 8;
par.sig = 1; par.sig = 1;
break; break;
case DevFmtUByte: case DevFmtUByte:
par.bps = 1; par.bits = 8;
par.sig = 0; par.sig = 0;
break; break;
case DevFmtShort: case DevFmtShort:
par.bps = 2; par.bits = 16;
par.sig = 1; par.sig = 1;
break; break;
case DevFmtUShort: case DevFmtUShort:
par.bps = 2; par.bits = 16;
par.sig = 0; par.sig = 0;
break; break;
case DevFmtInt: case DevFmtInt:
par.bps = 4; par.bits = 32;
par.sig = 1; par.sig = 1;
break; break;
case DevFmtUInt: case DevFmtUInt:
par.bps = 4; par.bits = 32;
par.sig = 0; par.sig = 0;
break; break;
case DevFmtFloat: case DevFmtFloat:
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
} }
par.bits = par.bps * 8; par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE; par.le = SIO_LE_NATIVE;
par.msb = SIO_LE_NATIVE ? 0 : 1; par.msb = 1;
par.rchan = mDevice->channelsFromFmt(); par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency; par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10); par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz, mDevice->Frequency/40); par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.appbufsz;
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device praameters"}; "Failed to set device praameters"};
if(par.bits != par.bps*8) if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8}; "Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
|| (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) {
|| (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0)
|| (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) || (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0)
|| (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) || (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0)
|| (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) || (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0)
|| mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate) || (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0)
|| (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
};
if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
|| mDevice->Frequency != par.rate)
throw al::backend_exception{al::backend_error::DeviceError, throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead", "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate}; mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false); mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round;
setDefaultChannelOrder(); setDefaultChannelOrder();
@ -507,7 +523,7 @@ std::string SndIOBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new SndioPlayback{device}}; return BackendPtr{new SndioPlayback{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_SNDIO_H #ifndef BACKENDS_SNDIO_H
#define BACKENDS_SNDIO_H #define BACKENDS_SNDIO_H
#include "backends/base.h" #include "base.h"
struct SndIOBackendFactory final : public BackendFactory { struct SndIOBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

View file

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "backends/solaris.h" #include "solaris.h"
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/types.h> #include <sys/types.h>
@ -34,15 +34,15 @@
#include <errno.h> #include <errno.h>
#include <poll.h> #include <poll.h>
#include <math.h> #include <math.h>
#include <string.h>
#include <thread> #include <thread>
#include <functional> #include <functional>
#include "alcmain.h"
#include "albyte.h" #include "albyte.h"
#include "alu.h" #include "alc/alconfig.h"
#include "alconfig.h" #include "core/device.h"
#include "compat.h" #include "core/helpers.h"
#include "core/logging.h" #include "core/logging.h"
#include "threads.h" #include "threads.h"
#include "vector.h" #include "vector.h"
@ -58,7 +58,7 @@ std::string solaris_driver{"/dev/audio"};
struct SolarisBackend final : public BackendBase { struct SolarisBackend final : public BackendBase {
SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { } SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~SolarisBackend() override; ~SolarisBackend() override;
int mixerProc(); int mixerProc();
@ -70,6 +70,7 @@ struct SolarisBackend final : public BackendBase {
int mFd{-1}; int mFd{-1};
uint mFrameStep{};
al::vector<al::byte> mBuffer; al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true}; std::atomic<bool> mKillNow{true};
@ -147,11 +148,15 @@ void SolarisBackend::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name}; name};
mFd = ::open(solaris_driver.c_str(), O_WRONLY); int fd{::open(solaris_driver.c_str(), O_WRONLY)};
if(mFd == -1) if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
solaris_driver.c_str(), strerror(errno)}; solaris_driver.c_str(), strerror(errno)};
if(mFd != -1)
::close(mFd);
mFd = fd;
mDevice->DeviceName = name; mDevice->DeviceName = name;
} }
@ -161,12 +166,7 @@ bool SolarisBackend::reset()
AUDIO_INITINFO(&info); AUDIO_INITINFO(&info);
info.play.sample_rate = mDevice->Frequency; info.play.sample_rate = mDevice->Frequency;
info.play.channels = mDevice->channelsFromFmt();
if(mDevice->FmtChans != DevFmtMono)
mDevice->FmtChans = DevFmtStereo;
uint numChannels{mDevice->channelsFromFmt()};
info.play.channels = numChannels;
switch(mDevice->FmtType) switch(mDevice->FmtType)
{ {
case DevFmtByte: case DevFmtByte:
@ -188,9 +188,7 @@ bool SolarisBackend::reset()
info.play.encoding = AUDIO_ENCODING_LINEAR; info.play.encoding = AUDIO_ENCODING_LINEAR;
break; break;
} }
info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
uint frameSize{numChannels * mDevice->bytesFromFmt()};
info.play.buffer_size = mDevice->BufferSize * frameSize;
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
{ {
@ -200,28 +198,39 @@ bool SolarisBackend::reset()
if(mDevice->channelsFromFmt() != info.play.channels) if(mDevice->channelsFromFmt() != info.play.channels)
{ {
ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans), if(info.play.channels >= 2)
info.play.channels); mDevice->FmtChans = DevFmtStereo;
return false; else if(info.play.channels == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Got %u device channels", info.play.channels};
} }
if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) || if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) || mDevice->FmtType = DevFmtUByte;
(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) || else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR)
(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt))) mDevice->FmtType = DevFmtByte;
else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtShort;
else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtInt;
else
{ {
ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType), ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding);
info.play.precision, info.play.encoding);
return false; return false;
} }
uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
mFrameStep = info.play.channels;
mDevice->Frequency = info.play.sample_rate; mDevice->Frequency = info.play.sample_rate;
mDevice->BufferSize = info.play.buffer_size / frameSize; mDevice->BufferSize = info.play.buffer_size / frame_size;
/* How to get the actual period size/count? */
mDevice->UpdateSize = mDevice->BufferSize / 2; mDevice->UpdateSize = mDevice->BufferSize / 2;
setDefaultChannelOrder(); setDefaultChannelOrder();
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
return true; return true;
@ -286,7 +295,7 @@ std::string SolarisBackendFactory::probe(BackendType type)
return outnames; return outnames;
} }
BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type) BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
{ {
if(type == BackendType::Playback) if(type == BackendType::Playback)
return BackendPtr{new SolarisBackend{device}}; return BackendPtr{new SolarisBackend{device}};

View file

@ -1,7 +1,7 @@
#ifndef BACKENDS_SOLARIS_H #ifndef BACKENDS_SOLARIS_H
#define BACKENDS_SOLARIS_H #define BACKENDS_SOLARIS_H
#include "backends/base.h" #include "base.h"
struct SolarisBackendFactory final : public BackendFactory { struct SolarisBackendFactory final : public BackendFactory {
public: public:
@ -11,7 +11,7 @@ public:
std::string probe(BackendType type) override; std::string probe(BackendType type) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory(); static BackendFactory &getFactory();
}; };

Some files were not shown because too many files have changed in this diff Show more