Merge branch 'cmake_adjustments' of github.com:Ragora/Torque3D into cmake_adjustments

This commit is contained in:
Robert MacGregor 2022-05-30 19:06:24 -04:00
commit 790cb17435
236 changed files with 30902 additions and 7540 deletions

View file

@ -121,7 +121,11 @@ setupVersionNumbers()
if(NOT TORQUE_TEMPLATE)
set(TORQUE_TEMPLATE "BaseGame" CACHE STRING "the template to use")
endif()
installTemplate(${TORQUE_TEMPLATE})
if (NOT TORQUE_INSTALLED_TEMPLATE)
installTemplate(${TORQUE_TEMPLATE})
set(TORQUE_INSTALLED_TEMPLATE TRUE CACHE BOOL "Whether or not the game template is installed. This may be reset to false (or removed) to force a reinstall.")
endif(NOT TORQUE_INSTALLED_TEMPLATE)
# Generate torqueConfig.h in our temp directory
CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/Tools/CMake/torqueConfig.h.in" "${CMAKE_BINARY_DIR}/temp/torqueConfig.h")

View file

@ -1,8 +1,5 @@
# 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
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)
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}")
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(ljpeg ${CMAKE_BINARY_DIR}/temp/ljpeg EXCLUDE_FROM_ALL)

View file

@ -1,3 +1,7 @@
file(GLOB CONVEX_DECOMP_SOURCES "*.cpp")
add_library(convexDecomp STATIC ${CONVEX_DECOMP_SOURCES})
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 \
libasound2-dev \
libjack-dev \
qtbase5-dev
qtbase5-dev \
libdbus-1-dev
fi
- >
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.
Copyright (c) 2015, Archontis Politis
Copyright (c) 2019, Anis A. Hireche
Copyright (c) 2019, Christopher Robinson
All rights reserved.

View file

@ -2,21 +2,24 @@
cmake_minimum_required(VERSION 3.0.2)
# The workaround for try_compile failing with code signing
# since cmake-3.18.2, not required
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED"
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
if(APPLE)
# The workaround for try_compile failing with code signing
# since cmake-3.18.2, not required
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
${CMAKE_TRY_COMPILE_PLATFORM_VARIABLES}
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED"
"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")
# 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(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
OR NOT CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "11.0")
@ -69,8 +72,11 @@ include(CheckCXXCompilerFlag)
include(CheckCSourceCompiles)
include(CheckCXXSourceCompiles)
include(CheckStructHasMember)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
find_package(PkgConfig)
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_UPDATE_BUILD_VERSION "Update git build version info" ON)
option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32})
if(DEFINED SHARE_INSTALL_DIR)
message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead")
set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}")
@ -115,7 +123,7 @@ set(LINKER_FLAGS )
set(EXTRA_LIBS )
if(WIN32)
set(CPP_DEFS ${CPP_DEFS} _WIN32)
set(CPP_DEFS ${CPP_DEFS} _WIN32 NOMINMAX)
if(MINGW)
set(CPP_DEFS ${CPP_DEFS} __USE_MINGW_ANSI_STDIO)
endif()
@ -142,7 +150,7 @@ if(NOT LIBTYPE)
endif()
set(LIB_MAJOR_VERSION "1")
set(LIB_MINOR_VERSION "21")
set(LIB_MINOR_VERSION "22")
set(LIB_REVISION "0")
set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}")
set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0)
@ -196,20 +204,22 @@ else()
endif()
unset(OLD_REQUIRED_LIBRARIES)
# Include liblog for Android logging
check_library_exists(log __android_log_print "" HAVE_LIBLOG)
if(HAVE_LIBLOG)
set(EXTRA_LIBS log ${EXTRA_LIBS})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log)
if(ANDROID)
# Include liblog for Android logging
check_library_exists(log __android_log_print "" HAVE_LIBLOG)
if(HAVE_LIBLOG)
set(EXTRA_LIBS log ${EXTRA_LIBS})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log)
endif()
endif()
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)
if(HAVE_PERMISSIVE_SWITCH)
set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:/permissive->)
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
# generators with MSVC complain about both /W3 and /W4 being specified.
foreach(flag_var CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
@ -345,17 +355,17 @@ endif()
set(SSE2_SWITCH "")
set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
if(NOT MSVC)
set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
# Yes GCC, really don't accept command line options you don't support
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()
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(emmintrin.h HAVE_EMMINTRIN_H)
@ -369,20 +379,25 @@ set(HAVE_SSE3 0)
set(HAVE_SSE4_1 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_SSE2 "Require SSE2 support" OFF)
if(HAVE_XMMINTRIN_H AND HAVE_EMMINTRIN_H)
if(HAVE_XMMINTRIN_H)
option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON)
option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON)
if(ALSOFT_CPUEXT_SSE AND ALSOFT_CPUEXT_SSE2)
if(ALSOFT_CPUEXT_SSE)
set(HAVE_SSE 1)
set(HAVE_SSE2 1)
endif()
endif()
if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE)
message(FATAL_ERROR "Failed to enabled required SSE CPU extensions")
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)
message(FATAL_ERROR "Failed to enable required SSE2 CPU extensions")
endif()
@ -435,7 +450,14 @@ set(FPMATH_SET "0")
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)
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)
if(HAVE_MFPMATH_SSE_2)
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
# functions need to ensure an aligned stack.
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()
@ -551,51 +566,11 @@ if(NOT WIN32)
check_symbol_exists(pthread_setname_np "pthread.h;pthread_np.h" 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)
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()
else()
check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP)
if(NOT HAVE_PTHREAD_SETNAME_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()
@ -615,17 +590,19 @@ set(COMMON_OBJS
common/alfstream.h
common/almalloc.cpp
common/almalloc.h
common/alnumbers.h
common/alnumeric.h
common/aloptional.h
common/alspan.h
common/alstring.cpp
common/alstring.h
common/atomic.h
common/comptr.h
common/dynload.cpp
common/dynload.h
common/intrusive_ptr.h
common/math_defs.h
common/opthelpers.h
common/phase_shifter.h
common/polyphase_resampler.cpp
common/polyphase_resampler.h
common/pragmadefs.h
@ -642,17 +619,32 @@ set(COMMON_OBJS
set(CORE_OBJS
core/ambdec.cpp
core/ambdec.h
core/ambidefs.cpp
core/ambidefs.h
core/async_event.h
core/bformatdec.cpp
core/bformatdec.h
core/bs2b.cpp
core/bs2b.h
core/bsinc_defs.h
core/bsinc_tables.cpp
core/bsinc_tables.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.h
core/devformat.cpp
core/devformat.h
core/device.cpp
core/device.h
core/effects/base.h
core/effectslot.cpp
core/effectslot.h
core/except.cpp
core/except.h
core/filters/biquad.h
@ -665,12 +657,57 @@ set(CORE_OBJS
core/fmt_traits.h
core/fpu_ctrl.cpp
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.h
core/mastering.cpp
core/mastering.h
core/mixer.cpp
core/mixer.h
core/resampler_limits.h
core/uhjfilter.cpp
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/hrtfbase.h
core/mixer/hrtfdefs.h
@ -691,6 +728,7 @@ set(OPENAL_OBJS
al/effects/dedicated.cpp
al/effects/distortion.cpp
al/effects/echo.cpp
al/effects/effects.cpp
al/effects/effects.h
al/effects/equalizer.cpp
al/effects/fshifter.cpp
@ -714,22 +752,14 @@ set(OPENAL_OBJS
# ALC and related routines
set(ALC_OBJS
alc/alc.cpp
alc/alcmain.h
alc/alu.cpp
alc/alu.h
alc/alconfig.cpp
alc/alconfig.h
alc/alcontext.h
alc/async_event.h
alc/bformatdec.cpp
alc/bformatdec.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/context.cpp
alc/context.h
alc/device.cpp
alc/device.h
alc/effects/base.h
alc/effects/autowah.cpp
alc/effects/chorus.cpp
@ -745,22 +775,42 @@ set(ALC_OBJS
alc/effects/pshifter.cpp
alc/effects/reverb.cpp
alc/effects/vmorpher.cpp
alc/front_stablizer.h
alc/helpers.cpp
alc/hrtf.cpp
alc/hrtf.h
alc/inprogext.h
alc/panning.cpp
alc/uiddefs.cpp
alc/voice.cpp
alc/voice.h
alc/voice_change.h)
alc/panning.cpp)
if (ALSOFT_EAX)
set(OPENAL_OBJS
${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
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)
set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse.cpp core/mixer/mixer_sse2.cpp)
set(CPU_EXTS "${CPU_EXTS}, SSE, SSE2")
set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse2.cpp)
set(CPU_EXTS "${CPU_EXTS}, SSE2")
endif()
if(HAVE_SSE3)
set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse3.cpp)
@ -778,6 +828,7 @@ endif()
set(HAVE_ALSA 0)
set(HAVE_OSS 0)
set(HAVE_PIPEWIRE 0)
set(HAVE_SOLARIS 0)
set(HAVE_SNDIO 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")
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
option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF)
find_package(AudioIO)
@ -1148,10 +1218,16 @@ if(ALSOFT_EMBED_HRTF_DATA)
endif()
if(ALSOFT_UTILS AND NOT ALSOFT_NO_CONFIG_UTIL)
find_package(Qt5Widgets)
if(ALSOFT_UTILS)
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()
if(ALSOFT_EXAMPLES)
if(ALSOFT_UTILS OR ALSOFT_EXAMPLES)
find_package(SndFile)
find_package(SDL2)
if(SDL2_FOUND)
@ -1212,6 +1288,7 @@ if(LIBTYPE STREQUAL "STATIC")
add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS})
target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC)
target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
if(WIN32)
# 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
@ -1232,13 +1309,14 @@ else()
router/al.cpp
)
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_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS})
target_include_directories(OpenAL
PUBLIC
$<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE
${OpenAL_SOURCE_DIR}/common
${OpenAL_BINARY_DIR}
@ -1303,12 +1381,14 @@ endif()
target_include_directories(${IMPL_TARGET}
PUBLIC
$<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
${INC_PATHS}
${OpenAL_BINARY_DIR}
${OpenAL_SOURCE_DIR}
${OpenAL_SOURCE_DIR}/alc
${OpenAL_SOURCE_DIR}/common
)
@ -1317,7 +1397,8 @@ set_target_properties(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME}
SOVERSION ${LIB_MAJOR_VERSION}
)
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})
if(TARGET build_version)
@ -1351,25 +1432,35 @@ if(HAS_ROUTER)
message(STATUS "Building DLL router")
endif()
message(STATUS "
Building OpenAL with support for the following backends:
${BACKENDS}
Building with support for CPU extensions:
${CPU_EXTS}
")
message(STATUS "")
message(STATUS "Building OpenAL with support for the following backends:")
message(STATUS " ${BACKENDS}")
message(STATUS "")
message(STATUS "Building with support for CPU extensions:")
message(STATUS " ${CPU_EXTS}")
message(STATUS "")
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()
if(ALSOFT_EMBED_HRTF_DATA)
message(STATUS "Embedding HRTF datasets
")
message(STATUS "Embedding HRTF datasets")
message(STATUS "")
endif()
# An alias for sub-project builds
add_library(OpenAL::OpenAL ALIAS OpenAL)
# Install main library
if(ALSOFT_INSTALL)
configure_package_config_file(OpenALConfig.cmake.in OpenALConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL)
install(TARGETS OpenAL EXPORT OpenAL
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
@ -1378,15 +1469,17 @@ if(ALSOFT_INSTALL)
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL)
export(TARGETS OpenAL
NAMESPACE OpenAL::
FILE OpenALConfig.cmake)
FILE OpenALTargets.cmake)
install(EXPORT OpenAL
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL
NAMESPACE OpenAL::
FILE OpenALConfig.cmake)
FILE OpenALTargets.cmake)
install(DIRECTORY include/AL
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${OpenAL_BINARY_DIR}/openal.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(FILES "${OpenAL_BINARY_DIR}/OpenALConfig.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL")
if(TARGET soft_oal)
install(TARGETS soft_oal
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
@ -1429,7 +1522,24 @@ if(ALSOFT_UTILS)
set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info)
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)
set(SOFA_SUPPORT_SRCS
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:
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
are near each other.
Improved click/pop prevention for sounds that stop prematurely.
Fixed compilation for Windows ARM targets with MSVC.
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.
Added capture support to the experimental Oboe backend.
openal-soft-1.21.0:
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 \
# -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")

File diff suppressed because it is too large Load diff

View file

@ -8,14 +8,22 @@
#include "AL/alc.h"
#include "AL/efx.h"
#include "alcmain.h"
#include "alc/device.h"
#include "alc/effects/base.h"
#include "almalloc.h"
#include "atomic.h"
#include "effectslot.h"
#include "effects/base.h"
#include "core/effectslot.h"
#include "intrusive_ptr.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 ALeffect;
struct WetBuffer;
@ -40,7 +48,7 @@ struct ALeffectslot {
al::intrusive_ptr<EffectState> State;
} Effect;
std::atomic_flag PropsClean;
bool mPropsDirty{true};
SlotState mState{SlotState::Initial};
@ -56,13 +64,214 @@ struct ALeffectslot {
ALeffectslot& operator=(const ALeffectslot&) = delete;
~ALeffectslot();
ALenum initEffect(ALeffect *effect, ALCcontext *context);
ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context);
void updateProps(ALCcontext *context);
/* This can be new'd for the context's default effect slot. */
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);
#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

View file

@ -35,6 +35,7 @@
#include <mutex>
#include <new>
#include <numeric>
#include <stdexcept>
#include <utility>
#include "AL/al.h"
@ -43,16 +44,23 @@
#include "albit.h"
#include "albyte.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alc/context.h"
#include "alc/device.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "atomic.h"
#include "core/except.h"
#include "inprogext.h"
#include "core/logging.h"
#include "core/voice.h"
#include "opthelpers.h"
#ifdef ALSOFT_EAX
#include "eax_globals.h"
#include "eax_x_ram.h"
#endif // ALSOFT_EAX
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++)
{
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;
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);
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++)
{
code[c] = al::to_integer<ALuint>(src[0]) | (al::to_integer<ALuint>(src[1])<< 8) |
(al::to_integer<ALuint>(src[2])<<16) | (al::to_integer<ALuint>(src[3])<<24);
code[c] = ALuint{src[0]} | (ALuint{src[1]}<< 8) | (ALuint{src[2]}<<16)
| (ALuint{src[3]}<<24);
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++)
{
blockpred[c] = std::min<ALubyte>(al::to_integer<ALubyte>(src[0]), 6);
blockpred[c] = std::min<ALubyte>(src[0], 6);
++src;
}
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;
src += 2;
}
for(size_t c{0};c < numchans;c++)
{
samples[c][0] = static_cast<ALshort>(al::to_integer<int>(src[0]) |
(al::to_integer<int>(src[1])<<8));
samples[c][0] = static_cast<ALshort>(src[0] | (src[1]<<8));
src += 2;
}
for(size_t c{0};c < numchans;c++)
{
samples[c][1] = static_cast<ALshort>(al::to_integer<int>(src[0]) |
(al::to_integer<int>(src[1])<<8));
samples[c][1] = static_cast<ALshort>(src[0] | (src[1]<<8));
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] +
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);
samples[c][1] = samples[c][0];
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]);
*(dst++) = static_cast<int16_t>(pred);
@ -271,6 +277,9 @@ ALuint ChannelsFromUserFmt(UserFmtChannels chans, ALuint ambiorder) noexcept
case UserFmtX71: return 8;
case UserFmtBFormat2D: return (ambiorder*2) + 1;
case UserFmtBFormat3D: return (ambiorder+1) * (ambiorder+1);
case UserFmtUHJ2: return 2;
case UserFmtUHJ3: return 3;
case UserFmtUHJ4: return 4;
}
return 0;
}
@ -310,11 +319,82 @@ ALenum EnumFromAmbiScaling(AmbiScaling scale)
{
case AmbiScaling::FuMa: return AL_FUMA_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))};
}
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 |
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(),
[](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 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. */
buffer->id = ((lidx<<6) | slidx) + 1;
@ -370,6 +449,10 @@ ALbuffer *AllocBuffer(ALCdevice *device)
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 size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f};
@ -453,46 +536,26 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
ALBuf->id);
/* Currently no channel configurations need to be converted. */
FmtChannels DstChannels{FmtMono};
switch(SrcChannels)
{
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))
auto DstChannels = FmtFromUserFmt(SrcChannels);
if UNLIKELY(!DstChannels)
SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format");
/* IMA4 and MSADPCM convert to 16-bit short. */
FmtType DstType{FmtUByte};
switch(SrcType)
{
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
/* IMA4 and MSADPCM convert to 16-bit short.
*
* 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
* the original data.
*/
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",
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 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",
unpackalign, NameFromUserFmtType(SrcType));
const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ?
ALBuf->UnpackAmbiOrder : 0};
const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder :
(IsUHJ(*DstChannels) ? 1 : 0)};
if((access&AL_PRESERVE_DATA_BIT_SOFT))
{
/* 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");
if UNLIKELY(ALBuf->OriginalAlign != align)
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
* storage.
*/
ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)};
ALuint FrameSize{NumChannels * BytesFromFmt(DstType)};
ALuint NumChannels{ChannelsFromFmt(*DstChannels, ambiorder)};
ALuint FrameSize{NumChannels * BytesFromFmt(*DstType)};
if UNLIKELY(frames > std::numeric_limits<size_t>::max()/FrameSize)
SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
"Buffer size overflow, %d frames x %d bytes per frame", 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
* 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
@ -561,7 +634,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
if(SrcType == UserFmtIMA4)
{
assert(DstType == FmtShort);
assert(*DstType == FmtShort);
if(SrcData != nullptr && !ALBuf->mData.empty())
Convert_int16_ima4(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData,
NumChannels, frames, align);
@ -569,7 +642,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
}
else if(SrcType == UserFmtMSADPCM)
{
assert(DstType == FmtShort);
assert(*DstType == FmtShort);
if(SrcData != nullptr && !ALBuf->mData.empty())
Convert_int16_msadpcm(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData,
NumChannels, frames, align);
@ -577,7 +650,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
}
else
{
assert(static_cast<long>(SrcType) == static_cast<long>(DstType));
assert(DstType.has_value());
if(SrcData != nullptr && !ALBuf->mData.empty())
std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin());
ALBuf->OriginalAlign = 1;
@ -588,8 +661,8 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
ALBuf->Access = access;
ALBuf->mSampleRate = static_cast<ALuint>(freq);
ALBuf->mChannels = DstChannels;
ALBuf->mType = DstType;
ALBuf->mChannels = *DstChannels;
ALBuf->mType = *DstType;
ALBuf->mAmbiOrder = ambiorder;
ALBuf->mCallback = nullptr;
@ -598,11 +671,16 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
ALBuf->mSampleLen = frames;
ALBuf->mLoopStart = 0;
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. */
void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
UserFmtChannels SrcChannels, UserFmtType SrcType, LPALBUFFERCALLBACKTYPESOFT callback,
UserFmtChannels SrcChannels, UserFmtType SrcType, ALBUFFERCALLBACKTYPESOFT callback,
void *userptr)
{
if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0)
@ -610,43 +688,25 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
ALBuf->id);
/* Currently no channel configurations need to be converted. */
FmtChannels DstChannels{FmtMono};
switch(SrcChannels)
{
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))
auto DstChannels = FmtFromUserFmt(SrcChannels);
if UNLIKELY(!DstChannels)
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format");
/* IMA4 and MSADPCM convert to 16-bit short. Not supported with callbacks. */
FmtType DstType{FmtUByte};
switch(SrcType)
{
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))
auto DstType = FmtFromUserFmt(SrcType);
if UNLIKELY(!DstType)
SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format");
const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ?
ALBuf->UnpackAmbiOrder : 0};
const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder :
(IsUHJ(*DstChannels) ? 1 : 0)};
al::vector<al::byte,16>(FrameSizeFromFmt(DstChannels, DstType, ambiorder) *
size_t{BufferLineSize + (MaxResamplerPadding>>1)}).swap(ALBuf->mData);
static constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad};
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->mUserData = userptr;
@ -657,8 +717,8 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
ALBuf->Access = 0;
ALBuf->mSampleRate = static_cast<ALuint>(freq);
ALBuf->mChannels = DstChannels;
ALBuf->mType = DstType;
ALBuf->mChannels = *DstChannels;
ALBuf->mType = *DstType;
ALBuf->mAmbiOrder = ambiorder;
ALBuf->mSampleLen = 0;
@ -675,7 +735,7 @@ al::optional<DecompResult> DecomposeUserFormat(ALenum format)
UserFmtChannels channels;
UserFmtType type;
};
static const std::array<FormatMap,46> UserFmtList{{
static const std::array<FormatMap,55> UserFmtList{{
{ AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte },
{ AL_FORMAT_MONO16, UserFmtMono, UserFmtShort },
{ 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_FLOAT32, UserFmtBFormat3D, UserFmtFloat },
{ 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)
@ -754,7 +826,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Generating %d buffers", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if(!EnsureBuffers(device, static_cast<ALuint>(n)))
{
@ -794,7 +866,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
/* First try to find any buffers that are invalid or in-use. */
@ -834,7 +906,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if LIKELY(context)
{
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if(!buffer || LookupBuffer(device, buffer))
return AL_TRUE;
@ -855,7 +927,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -877,8 +949,10 @@ START_API_FUNC
if UNLIKELY(!usrfmt)
context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
else
{
LoadData(context.get(), albuf, freq, static_cast<ALuint>(size), usrfmt->channels,
usrfmt->type, static_cast<const al::byte*>(data), flags);
}
}
}
END_API_FUNC
@ -889,7 +963,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return nullptr;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -942,7 +1016,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -965,7 +1039,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -997,7 +1071,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1127,7 +1201,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1147,7 +1221,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1166,7 +1240,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1188,7 +1262,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1250,7 +1324,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1283,7 +1357,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1321,7 +1395,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1343,7 +1417,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1371,7 +1445,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@ -1393,7 +1467,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
if UNLIKELY(!albuf)
@ -1450,7 +1524,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@ -1488,7 +1562,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
if UNLIKELY(!albuf)
@ -1510,13 +1584,13 @@ END_API_FUNC
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
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
@ -1526,8 +1600,6 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq);
else if UNLIKELY(callback == nullptr)
context->setError(AL_INVALID_VALUE, "NULL callback");
else if UNLIKELY(flags != 0)
context->setError(AL_INVALID_VALUE, "Invalid callback flags 0x%x", flags);
else
{
auto usrfmt = DecomposeUserFormat(format);
@ -1546,7 +1618,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
ALbuffer *albuf = LookupBuffer(device, buffer);
if UNLIKELY(!albuf)
@ -1574,7 +1646,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@ -1602,7 +1674,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->BufferLock};
if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@ -1630,3 +1702,161 @@ BufferSubList::~BufferSubList()
al_free(Buffers);
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 "albyte.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "atomic.h"
#include "buffer_storage.h"
#include "inprogext.h"
#include "core/buffer_storage.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "eax_x_ram.h"
#endif // ALSOFT_EAX
/* User formats */
enum UserFmtType : unsigned char {
@ -35,6 +38,9 @@ enum UserFmtChannels : unsigned char {
UserFmtX71 = FmtX71,
UserFmtBFormat2D = FmtBFormat2D,
UserFmtBFormat3D = FmtBFormat3D,
UserFmtUHJ2 = FmtUHJ2,
UserFmtUHJ3 = FmtUHJ3,
UserFmtUHJ4 = FmtUHJ4,
};
@ -65,6 +71,11 @@ struct ALbuffer : public BufferStorage {
ALuint id{0};
DISABLE_ALLOC()
#ifdef ALSOFT_EAX
ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC};
bool eax_x_ram_is_hardware{};
#endif // ALSOFT_EAX
};
#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 "albit.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alc/context.h"
#include "alc/device.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alstring.h"
#include "core/except.h"
#include "core/logging.h"
#include "effects/base.h"
#include "opthelpers.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "eax_exception.h"
#endif // ALSOFT_EAX
const EffectList gEffectList[16]{
{ "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(),
[](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 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);
/* Add 1 to avoid effect ID 0. */
@ -233,7 +239,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!EnsureEffects(device, static_cast<ALuint>(n)))
{
@ -273,7 +279,7 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
/* First try to find any effects that are invalid. */
@ -304,7 +310,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if LIKELY(context)
{
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!effect || LookupEffect(device, effect))
return AL_TRUE;
@ -319,7 +325,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
@ -369,7 +375,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
@ -392,7 +398,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
@ -415,7 +421,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
@ -438,7 +444,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
@ -470,7 +476,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
@ -493,7 +499,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
@ -516,7 +522,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
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);
}
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);
bool IsValidEffectType(ALenum type) noexcept;
#endif

View file

@ -8,9 +8,17 @@
#include "AL/efx.h"
#include "effects/base.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax_exception.h"
#include "al/eax_utils.h"
#endif // ALSOFT_EAX
namespace {
void Autowah_setParamf(EffectProps *props, ALenum param, float val)
@ -107,3 +115,434 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Autowah);
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/efx.h"
#include "alc/effects/base.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 {
@ -70,3 +77,220 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Compressor);
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 "AL/al.h"
#include "inprogext.h"
#include "alc/inprogext.h"
#include "alc/effects/base.h"
#include "effects.h"
#include "effects/base.h"
namespace {

View file

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

View file

@ -4,8 +4,15 @@
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.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 {
@ -112,3 +119,453 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Distortion);
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/efx.h"
#include "alc/effects/base.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 {
@ -109,3 +116,454 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Echo);
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"
#ifdef ALSOFT_EAX
#include "al/eax_effect.h"
#endif // ALSOFT_EAX
union EffectProps;
@ -12,7 +16,11 @@ class effect_exception final : public al::base_exception {
ALenum mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
effect_exception(ALenum code, const char *msg, ...);
ALenum errorCode() const noexcept { return mErrorCode; }
@ -76,4 +84,9 @@ extern const EffectVtable VmorpherEffectVtable;
extern const EffectVtable DedicatedEffectVtable;
extern const EffectVtable ConvolutionEffectVtable;
#ifdef ALSOFT_EAX
EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type);
#endif // ALSOFT_EAX
#endif /* AL_EFFECTS_EFFECTS_H */

View file

@ -4,8 +4,15 @@
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.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 {
@ -167,3 +174,748 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Equalizer);
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 <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.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 {
@ -126,3 +137,343 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Fshifter);
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 <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.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 {
@ -132,3 +143,340 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Modulator);
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/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#include "effects/base.h"
#ifdef ALSOFT_EAX
#include "al/eax_exception.h"
#endif // ALSOFT_EAX
namespace {
@ -91,3 +95,58 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Null);
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/efx.h"
#include "alc/effects/base.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 {
@ -82,3 +89,276 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Pshifter);
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 <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.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 {
@ -245,3 +256,531 @@ EffectProps genDefaultProps() noexcept
DEFINE_ALEFFECT_VTABLE(Vmorpher);
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/alc.h"
#include "alcontext.h"
#include "alc/context.h"
#include "almalloc.h"
#include "core/except.h"
#include "core/logging.h"
@ -85,9 +85,9 @@ AL_API ALenum AL_APIENTRY alGetError(void)
START_API_FUNC
{
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);
if(TrapALError)
{

View file

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

View file

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

View file

@ -37,8 +37,8 @@
#include "AL/efx.h"
#include "albit.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alc/context.h"
#include "alc/device.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/except.h"
@ -52,7 +52,11 @@ class filter_exception final : public al::base_exception {
ALenum mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
filter_exception(ALenum code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
@ -63,8 +67,6 @@ public:
ALenum errorCode() const noexcept { return mErrorCode; }
};
#define FILTER_MIN_GAIN 0.0f
#define FILTER_MAX_GAIN 4.0f /* +12dB */
#define DEFINE_ALFILTER_VTABLE(T) \
const ALfilter::Vtable T##_vtable = { \
@ -84,7 +86,7 @@ void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val)
switch(param)
{
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};
filter->Gain = val;
break;
@ -143,7 +145,7 @@ void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val)
switch(param)
{
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};
filter->Gain = val;
break;
@ -202,7 +204,7 @@ void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val)
switch(param)
{
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};
filter->Gain = val;
break;
@ -351,12 +353,12 @@ ALfilter *AllocFilter(ALCdevice *device)
{
auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
[](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 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);
/* Add 1 to avoid filter ID 0. */
@ -404,8 +406,8 @@ START_API_FUNC
context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
if(!EnsureFilters(device, static_cast<ALuint>(n)))
{
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);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
/* First try to find any filters that are invalid. */
@ -475,7 +477,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if LIKELY(context)
{
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
if(!filter || LookupFilter(device, filter))
return AL_TRUE;
@ -491,7 +493,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
@ -532,7 +534,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
@ -555,7 +557,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
@ -578,7 +580,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
@ -601,7 +603,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};
@ -636,7 +638,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};
@ -659,7 +661,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};
@ -682,7 +684,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};

View file

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

View file

@ -29,20 +29,53 @@
#include "AL/alc.h"
#include "AL/efx.h"
#include "alcontext.h"
#include "alc/context.h"
#include "almalloc.h"
#include "atomic.h"
#include "core/except.h"
#include "opthelpers.h"
#define DO_UPDATEPROPS() do { \
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
UpdateListenerProps(context.get()); \
else \
listener.PropsClean.clear(std::memory_order_release); \
} while(0)
namespace {
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)
START_API_FUNC
@ -58,14 +91,14 @@ START_API_FUNC
if(!(value >= 0.0f && std::isfinite(value)))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range");
listener.Gain = value;
DO_UPDATEPROPS();
UpdateProps(context.get());
break;
case AL_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");
listener.mMetersPerUnit = value;
DO_UPDATEPROPS();
UpdateProps(context.get());
break;
default:
@ -90,7 +123,7 @@ START_API_FUNC
listener.Position[0] = value1;
listener.Position[1] = value2;
listener.Position[2] = value3;
DO_UPDATEPROPS();
CommitAndUpdateProps(context.get());
break;
case AL_VELOCITY:
@ -99,7 +132,7 @@ START_API_FUNC
listener.Velocity[0] = value1;
listener.Velocity[1] = value2;
listener.Velocity[2] = value3;
DO_UPDATEPROPS();
CommitAndUpdateProps(context.get());
break;
default:
@ -146,7 +179,7 @@ START_API_FUNC
listener.OrientUp[0] = values[3];
listener.OrientUp[1] = values[4];
listener.OrientUp[2] = values[5];
DO_UPDATEPROPS();
CommitAndUpdateProps(context.get());
break;
default:
@ -414,39 +447,3 @@ START_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
#include <array>
#include <atomic>
#include "AL/al.h"
#include "AL/alc.h"
@ -19,13 +18,7 @@ struct ALlistener {
float Gain{1.0f};
float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
std::atomic_flag PropsClean;
ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); }
DISABLE_ALLOC()
};
void UpdateListenerProps(ALCcontext *context);
#endif

File diff suppressed because it is too large Load diff

View file

@ -11,19 +11,31 @@
#include "AL/al.h"
#include "AL/alc.h"
#include "alcontext.h"
#include "alc/alu.h"
#include "alc/context.h"
#include "alc/inprogext.h"
#include "aldeque.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alu.h"
#include "math_defs.h"
#include "atomic.h"
#include "core/voice.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 ALeffectslot;
enum class SourceStereo : bool {
Normal = AL_NORMAL_SOFT,
Enhanced = AL_SUPER_STEREO_SOFT
};
#define DEFAULT_SENDS 2
#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 {
/** Source properties. */
float Pitch{1.0f};
@ -47,6 +123,11 @@ struct ALsource {
float RefDistance{1.0f};
float MaxDistance{std::numeric_limits<float>::max()};
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> Velocity{{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};
DirectMode DirectChannels{DirectMode::Off};
SpatializeMode mSpatialize{SpatializeMode::Auto};
SourceStereo mStereoMode{SourceStereo::Normal};
bool DryGainHFAuto{true};
bool WetGainAuto{true};
@ -71,9 +153,10 @@ struct ALsource {
/* NOTE: Stereo pan angles are specified in radians, counter-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 EnhWidth{0.593f};
/** Direct filter and auxiliary send info. */
struct {
@ -109,7 +192,7 @@ struct ALsource {
/** Source Buffer Queue head. */
al::deque<ALbufferQueueItem> mQueue;
std::atomic_flag PropsClean;
bool mPropsDirty{true};
/* Index into the context's Voices array. Lazily updated, only checked and
* reset when looking up the voice.
@ -127,6 +210,598 @@ struct ALsource {
ALsource& operator=(const ALsource&) = delete;
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);

View file

@ -24,26 +24,34 @@
#include <atomic>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <string>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alcontext.h"
#include "almalloc.h"
#include "alc/alu.h"
#include "alc/context.h"
#include "alc/inprogext.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alu.h"
#include "aloptional.h"
#include "atomic.h"
#include "core/context.h"
#include "core/except.h"
#include "event.h"
#include "inprogext.h"
#include "core/mixer/defs.h"
#include "core/voice.h"
#include "intrusive_ptr.h"
#include "opthelpers.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 {
@ -129,7 +137,7 @@ ALenum ALenumFromDistanceModel(DistanceModel model)
/* WARNING: Non-standard export! Not part of any extension, or exposed in the
* 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
{
static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION");
@ -139,10 +147,10 @@ START_API_FUNC
END_API_FUNC
#define DO_UPDATEPROPS() do { \
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
if(!context->mDeferUpdates) \
UpdateContextProps(context.get()); \
else \
context->mPropsClean.clear(std::memory_order_release); \
context->mPropsDirty = true; \
} while(0)
@ -152,12 +160,18 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(capability)
{
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;
default:
@ -172,12 +186,18 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(capability)
{
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;
default:
@ -200,6 +220,10 @@ START_API_FUNC
value = context->mSourceDistanceModel ? AL_TRUE : AL_FALSE;
break;
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
value = context->mStopVoicesOnDisconnect ? AL_TRUE : AL_FALSE;
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability);
}
@ -239,7 +263,7 @@ START_API_FUNC
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
if(context->mDeferUpdates)
value = AL_TRUE;
break;
@ -292,7 +316,7 @@ START_API_FUNC
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
if(context->mDeferUpdates)
value = static_cast<ALdouble>(AL_TRUE);
break;
@ -343,7 +367,7 @@ START_API_FUNC
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
if(context->mDeferUpdates)
value = static_cast<ALfloat>(AL_TRUE);
break;
@ -394,7 +418,7 @@ START_API_FUNC
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
if(context->mDeferUpdates)
value = AL_TRUE;
break;
@ -410,6 +434,41 @@ START_API_FUNC
value = static_cast<int>(ResamplerDefault);
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:
context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
}
@ -418,7 +477,7 @@ START_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
{
ContextRef context{GetContextRef()};
@ -445,7 +504,7 @@ START_API_FUNC
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
if(context->mDeferUpdates)
value = AL_TRUE;
break;
@ -627,7 +686,7 @@ START_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
{
if(values)
@ -819,6 +878,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
context->deferUpdates();
}
END_API_FUNC
@ -829,6 +889,7 @@ START_API_FUNC
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
context->processUpdates();
}
END_API_FUNC
@ -874,6 +935,14 @@ void UpdateContextProps(ALCcontext *context)
}
/* 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->DopplerVelocity = context->mDopplerVelocity;
props->SpeedOfSound = context->mSpeedOfSound;

View file

@ -40,7 +40,7 @@
#include "alfstream.h"
#include "alstring.h"
#include "compat.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "strutils.h"
#include "vector.h"
@ -277,6 +277,52 @@ void LoadConfigFromFile(std::istream &f)
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
@ -437,106 +483,46 @@ void ReadALConfig()
}
#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)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional<std::string>(val);
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional<std::string>(val);
return al::nullopt;
}
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
{
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)));
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
return al::nullopt;
}
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
{
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)));
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
return al::nullopt;
}
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional(std::strtof(val, nullptr));
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(std::strtof(val, nullptr));
return al::nullopt;
}
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional(
al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0);
return al::nullopt;
}
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(!val[0]) return def != 0;
return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0);
return def;
}

View file

@ -7,9 +7,7 @@
void ReadALConfig();
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName);
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);
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def);
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);

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 "backends/alsa.h"
#include "alsa.h"
#include <algorithm>
#include <atomic>
@ -36,12 +36,12 @@
#include <utility>
#include "albyte.h"
#include "alcmain.h"
#include "alconfig.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alu.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
@ -68,35 +68,37 @@ constexpr char alsaDevice[] = "ALSA Default";
MAGIC(snd_pcm_hw_params_free); \
MAGIC(snd_pcm_hw_params_any); \
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_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_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_rate_near); \
MAGIC(snd_pcm_hw_params_set_rate); \
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_channels); \
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_free); \
MAGIC(snd_pcm_sw_params_malloc); \
MAGIC(snd_pcm_sw_params_set_avail_min); \
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_start); \
MAGIC(snd_pcm_resume); \
@ -105,7 +107,6 @@ constexpr char alsaDevice[] = "ALSA Default";
MAGIC(snd_pcm_delay); \
MAGIC(snd_pcm_state); \
MAGIC(snd_pcm_avail_update); \
MAGIC(snd_pcm_areas_silence); \
MAGIC(snd_pcm_mmap_begin); \
MAGIC(snd_pcm_mmap_commit); \
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_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_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_rate_near psnd_pcm_hw_params_set_rate_near
#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_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_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_channels psnd_pcm_hw_params_test_channels
#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_state psnd_pcm_state
#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_commit psnd_pcm_mmap_commit
#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_malloc(&pcminfo);
devlist.emplace_back(DevMap{alsaDevice,
GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture",
"default")});
auto defname = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"});
const char *customdevs{GetConfigValue(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures", "")};
while(const char *curdev{customdevs})
if(auto customdevs = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
{
if(!curdev[0]) break;
customdevs = strchr(curdev, ';');
const char *sep{strchr(curdev, '=')};
if(!sep)
size_t nextpos{customdevs->find_first_not_of(';')};
size_t curpos;
while((curpos=nextpos) < customdevs->length())
{
std::string spec{customdevs ? std::string(curdev, customdevs++) : std::string(curdev)};
ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
continue;
}
nextpos = customdevs->find_first_of(';', curpos+1);
const char *oldsep{sep++};
devlist.emplace_back(DevMap{std::string(curdev, oldsep),
customdevs ? std::string(sep, customdevs++) : std::string(sep)});
const auto &entry = devlist.back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
size_t seppos{customdevs->find_first_of('=', curpos)};
if(seppos == curpos || seppos >= nextpos)
{
std::string spec{customdevs->substr(curpos, nextpos-curpos)};
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{
@ -407,7 +416,7 @@ int verify_state(snd_pcm_t *handle)
struct AlsaPlayback final : public BackendBase {
AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaPlayback() override;
int mixerProc();
@ -424,6 +433,7 @@ struct AlsaPlayback final : public BackendBase {
std::mutex mMutex;
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
@ -445,7 +455,6 @@ int AlsaPlayback::mixerProc()
SetRTPriority();
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 buffer_size{mDevice->BufferSize};
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)};
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)};
if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames)
@ -529,7 +538,6 @@ int AlsaPlayback::mixerNoMMapProc()
SetRTPriority();
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 buffer_size{mDevice->BufferSize};
while(!mKillNow.load(std::memory_order_acquire))
@ -575,7 +583,7 @@ int AlsaPlayback::mixerNoMMapProc()
al::byte *WritePtr{mBuffer.data()};
avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
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)
{
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
@ -615,16 +623,15 @@ int AlsaPlayback::mixerNoMMapProc()
void AlsaPlayback::open(const char *name)
{
const char *driver{};
al::optional<std::string> driveropt;
const char *driver{"default"};
if(name)
{
if(PlaybackDevices.empty())
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
@ -633,14 +640,19 @@ void AlsaPlayback::open(const char *name)
else
{
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);
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)
throw al::backend_exception{al::backend_error::NoDevice,
"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. */
snd_config_update_free_global();
@ -723,37 +735,25 @@ bool AlsaPlayback::reset()
}
}
CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format));
/* test and set channels (implicitly sets frame bits) */
if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0)
/* set channels (implicitly sets frame bits) */
if(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0)
{
static const DevFmtChannels channellist[] = {
DevFmtStereo,
DevFmtQuad,
DevFmtX51,
DevFmtX71,
DevFmtMono,
};
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;
}
}
uint numchans{2u};
CHECK(snd_pcm_hw_params_set_channels_near(mPcmHandle, hp.get(), &numchans));
if(numchans < 1)
throw al::backend_exception{al::backend_error::DeviceError, "Got 0 device channels"};
if(numchans == 1) mDevice->FmtChans = DevFmtMono;
else mDevice->FmtChans = DevFmtStereo;
}
CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()));
/* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0)
|| !mDevice->Flags.test(FrequencyRequest))
{
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)
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));
/* set period time (implicitly constrains period/buffer parameters) */
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_period_size(hp.get(), &periodSizeInFrames, nullptr));
CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames));
CHECK(snd_pcm_hw_params_get_channels(hp.get(), &mFrameStep));
hp = nullptr;
SwParamsPtr sp{CreateSwParams()};
@ -809,8 +810,8 @@ void AlsaPlayback::start()
int (AlsaPlayback::*thread_func)(){};
if(access == SND_PCM_ACCESS_RW_INTERLEAVED)
{
mBuffer.resize(
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize)));
auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize);
mBuffer.resize(static_cast<size_t>(datalen));
thread_func = &AlsaPlayback::mixerNoMMapProc;
}
else
@ -863,7 +864,7 @@ ClockLatency AlsaPlayback::getClockLatency()
struct AlsaCapture final : public BackendBase {
AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { }
AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaCapture() override;
void open(const char *name) override;
@ -895,16 +896,15 @@ AlsaCapture::~AlsaCapture()
void AlsaCapture::open(const char *name)
{
const char *driver{};
al::optional<std::string> driveropt;
const char *driver{"default"};
if(name)
{
if(CaptureDevices.empty())
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
@ -913,7 +913,8 @@ void AlsaCapture::open(const char *name)
else
{
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);
@ -1252,7 +1253,7 @@ std::string AlsaBackendFactory::probe(BackendType type)
return outnames;
}
BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr AlsaBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new AlsaPlayback{device}};

View file

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

View file

@ -3,21 +3,22 @@
#include "base.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <thread>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#endif
#include "albit.h"
#include "alcmain.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "atomic.h"
#include "core/logging.h"
#include "aloptional.h"
#endif
#include "atomic.h"
#include "core/devformat.h"
bool BackendBase::reset()
@ -78,14 +79,6 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[SideLeft] = 4;
mDevice->RealOut.ChannelIndex[SideRight] = 5;
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:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
@ -116,11 +109,11 @@ void BackendBase::setDefaultChannelOrder()
switch(mDevice->FmtChans)
{
case DevFmtX51Rear:
case DevFmtX51:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[SideLeft] = 2;
mDevice->RealOut.ChannelIndex[SideRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
return;
@ -139,7 +132,6 @@ void BackendBase::setDefaultChannelOrder()
case DevFmtMono:
case DevFmtStereo:
case DevFmtQuad:
case DevFmtX51:
case DevFmtX61:
case DevFmtAmbi3D:
setDefaultWFXChannelOrder();
@ -150,6 +142,13 @@ void BackendBase::setDefaultChannelOrder()
#ifdef _WIN32
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>
{
switch(chanbit)

View file

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

View file

@ -20,34 +20,239 @@
#include "config.h"
#include "backends/coreaudio.h"
#include "coreaudio.h"
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cmath>
#include <memory>
#include <string>
#include "alcmain.h"
#include "alu.h"
#include "ringbuffer.h"
#include "converter.h"
#include "alnumeric.h"
#include "core/converter.h"
#include "core/device.h"
#include "core/logging.h"
#include "backends/base.h"
#include "ringbuffer.h"
#include <unistd.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
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 {
CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioPlayback() override;
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
@ -96,19 +301,41 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
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)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
#endif
/* open the default output unit */
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IOS
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#if CAN_ENUMERATE
desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
@ -118,18 +345,50 @@ void CoreAudioPlayback::open(const char *name)
if(comp == nullptr)
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)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
/* init and start the default audio unit... */
err = AudioUnitInitialize(mAudioUnit);
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
#endif
err = AudioUnitInitialize(audioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"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;
#endif
}
bool CoreAudioPlayback::reset()
@ -140,10 +399,10 @@ bool CoreAudioPlayback::reset()
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
UInt32 size{sizeof(streamFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
0, &streamFormat, &size);
if(err != noErr || size != sizeof(AudioStreamBasicDescription))
if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
return false;
@ -159,15 +418,9 @@ bool CoreAudioPlayback::reset()
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
#endif
/* set default output unit's input side to match output side */
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &streamFormat, size);
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
/* Use the sample rate from the output unit's current parameters, but reset
* everything else.
*/
if(mDevice->Frequency != streamFormat.mSampleRate)
{
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
* to specify what we're giving? eg, 6.0 vs 5.1 */
switch(streamFormat.mChannelsPerFrame)
{
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();
* to specify what we're giving? e.g. 6.0 vs 5.1
*/
streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
/* use channel count and sample rate from the default output unit's current
* parameters, but reset everything else */
streamFormat.mFramesPerPacket = 1;
streamFormat.mFormatFlags = 0;
streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
streamFormat.mFormatID = kAudioFormatLinearPCM;
switch(mDevice->FmtType)
{
case DevFmtUByte:
mDevice->FmtType = DevFmtByte;
/* fall-through */
case DevFmtByte:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 8;
break;
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 16;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
/* fall-through */
case DevFmtInt:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 32;
break;
case DevFmtFloat:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
streamFormat.mBitsPerChannel = 32;
break;
}
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
streamFormat.mBitsPerChannel / 8;
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
kLinearPCMFormatFlagIsPacked;
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &streamFormat, sizeof(AudioStreamBasicDescription));
0, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
setDefaultWFXChannelOrder();
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
@ -294,7 +519,7 @@ void CoreAudioPlayback::stop()
struct CoreAudioCapture final : public BackendBase {
CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~CoreAudioCapture() override;
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
@ -383,45 +608,64 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
void CoreAudioCapture::open(const char *name)
{
AudioStreamBasicDescription requestedFormat; // The application requested format
AudioStreamBasicDescription hardwareFormat; // The hardware format
AudioStreamBasicDescription outputFormat; // The AudioUnit output format
AURenderCallbackStruct input;
AudioComponentDescription desc;
UInt32 propertySize;
UInt32 enableIO;
AudioComponent comp;
OSStatus err;
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
&audioDevice);
else
{
if(CaptureList.empty())
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)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
#endif
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IOS
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#if CAN_ENUMERATE
desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_HALOutput;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
// Search for component with given description
comp = AudioComponentFindNext(NULL, &desc);
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == NULL)
throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
// Open the component
err = AudioComponentInstanceNew(comp, &mAudioUnit);
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"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
enableIO = 0;
UInt32 enableIO{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
if(err != noErr)
@ -436,35 +680,8 @@ void CoreAudioCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError,
"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
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this;
@ -489,14 +706,16 @@ void CoreAudioCapture::open(const char *name)
"Could not initialize audio unit: %u", err};
// Get the hardware format
propertySize = sizeof(AudioStreamBasicDescription);
AudioStreamBasicDescription hardwareFormat{};
UInt32 propertySize{sizeof(hardwareFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err};
// Set up the requested format description
AudioStreamBasicDescription requestedFormat{};
switch(mDevice->FmtType)
{
case DevFmtByte:
@ -509,7 +728,8 @@ void CoreAudioCapture::open(const char *name)
break;
case DevFmtShort:
requestedFormat.mBitsPerChannel = 16;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtUShort:
requestedFormat.mBitsPerChannel = 16;
@ -517,7 +737,8 @@ void CoreAudioCapture::open(const char *name)
break;
case DevFmtInt:
requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtUInt:
requestedFormat.mBitsPerChannel = 32;
@ -525,7 +746,8 @@ void CoreAudioCapture::open(const char *name)
break;
case DevFmtFloat:
requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked;
break;
}
@ -540,7 +762,6 @@ void CoreAudioCapture::open(const char *name)
case DevFmtQuad:
case DevFmtX51:
case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
case DevFmtAmbi3D:
@ -561,7 +782,7 @@ void CoreAudioCapture::open(const char *name)
// Use intermediate format for sample rate conversion (outputFormat)
// Set sample rate to the same as hardware for resampling later
outputFormat = requestedFormat;
AudioStreamBasicDescription outputFormat{requestedFormat};
outputFormat.mSampleRate = hardwareFormat.mSampleRate;
// 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),
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;
#endif
}
@ -665,6 +902,26 @@ bool CoreAudioBackendFactory::querySupport(BackendType type)
std::string CoreAudioBackendFactory::probe(BackendType type)
{
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)
{
case BackendType::Playback:
@ -673,10 +930,11 @@ std::string CoreAudioBackendFactory::probe(BackendType type)
outnames.append(ca_device, sizeof(ca_device));
break;
}
#endif
return outnames;
}
BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new CoreAudioPlayback{device}};

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@
#include "config.h"
#include "backends/jack.h"
#include "jack.h"
#include <cstdlib>
#include <cstdio>
@ -31,9 +31,10 @@
#include <thread>
#include <functional>
#include "alcmain.h"
#include "alu.h"
#include "alconfig.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
@ -45,9 +46,6 @@
namespace {
constexpr char jackDevice[] = "JACK Default";
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
@ -101,6 +99,8 @@ decltype(jack_error_callback) * pjack_error_callback;
#endif
constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
jack_options_t ClientOptions = JackNullOption;
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 {
std::string mName;
std::string mPattern;
@ -160,53 +165,125 @@ struct DeviceEntry {
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, ""});
std::string customList{ConfigValueStr(nullptr, "jack", "custom-devices").value_or("")};
size_t strpos{0};
while(strpos < customList.size())
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
{
size_t nextpos{customList.find(';', strpos)};
size_t seppos{customList.find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
for(size_t i{0};ports[i];++i)
{
const std::string entry{customList.substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
const char *sep{std::strchr(ports[i], ':')};
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;
strpos = nextpos;
continue;
}
}
size_t count{1};
std::string name{customList.substr(strpos, seppos-strpos)};
auto check_name = [&name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
while(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
if(list.size() > 1)
{
/* Rename entries that have matching names, by appending '#2', '#3',
* etc, as needed.
*/
for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
{
name = customList.substr(strpos, seppos-strpos);
name += " #";
name += std::to_string(++count);
auto check_match = [curitem](const DeviceEntry &entry) -> bool
{ return entry.mName == curitem->mName; };
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 {
JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~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;
static int processC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
@ -227,6 +304,7 @@ struct JackPlayback final : public BackendBase {
std::mutex mMutex;
std::atomic<bool> mPlaying{false};
bool mRTMixing{false};
RingBufferPtr mRing;
al::semaphore mSem;
@ -241,16 +319,40 @@ JackPlayback::~JackPlayback()
if(!mClient)
return;
std::for_each(mPort.begin(), mPort.end(),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); }
);
auto unregister_port = [this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); };
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
jack_client_close(mClient);
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
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
@ -352,49 +454,56 @@ int JackPlayback::mixerProc()
void JackPlayback::open(const char *name)
{
mPortPattern.clear();
if(!name)
name = jackDevice;
else if(strcmp(name, jackDevice) != 0)
if(!mClient)
{
if(PlaybackList.empty())
EnumerateDevices(PlaybackList);
const PathNamePair &binname = GetProcBinary();
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
{ return entry.mName == name; };
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%s\" not found", name?name:""};
mPortPattern = iter->mPattern;
}
const char *client_name{"alsoft"};
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);
}
jack_set_process_callback(mClient, &JackPlayback::processC, this);
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
bool JackPlayback::reset()
{
std::for_each(mPort.begin(), mPort.end(),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); });
auto unregister_port = [this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); };
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
/* 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->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2;
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;
if(mRTMixing)
{
/* Assume only two periods when directly mixing. Should try to query
* the total port latency when connected.
*/
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. */
mDevice->FmtType = DevFmtFloat;
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = std::find_if_not(mPort.begin(), ports_end,
[this](jack_port_t *&port) -> bool
{
std::string name{"channel_" + std::to_string(&port - &mPort[0] + 1)};
port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
return port != nullptr;
});
auto bad_port = mPort.begin();
while(bad_port != ports_end)
{
std::string name{"channel_" + std::to_string(++port_num)};
*bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
JackPortIsOutput | JackPortIsTerminal, 0);
if(!*bad_port) break;
++bad_port;
}
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()+1)
@ -453,29 +573,25 @@ void JackPlayback::start()
const char *devname{mDevice->DeviceName.c_str()};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{
const char **ports{jack_get_ports(mClient, mPortPattern.c_str(), nullptr,
JackPortIsPhysical|JackPortIsInput)};
if(ports == nullptr)
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
JackPortIsInput)};
if(!pnames)
{
jack_deactivate(mClient);
throw al::backend_exception{al::backend_error::DeviceError,
"No physical playback ports found"};
throw al::backend_exception{al::backend_error::DeviceError, "No 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(!pname)
if(!pnames[i])
{
ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
return false;
ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
break;
}
if(jack_connect(mClient, jack_port_name(port), pname))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
pname);
return true;
};
std::mismatch(mPort.begin(), mPort.end(), ports, connect_port);
jack_free(ports);
if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
pnames[i]);
}
}
/* 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->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 = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
try {
if(mRTMixing)
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()};
else
{
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
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()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
if(mPlaying.load(std::memory_order_acquire))
{
mKillNow.store(true, std::memory_order_release);
if(mThread.joinable())
{
mSem.post();
mThread.join();
}
mSem.post();
mThread.join();
jack_deactivate(mClient);
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};
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;
return ret;
@ -547,10 +672,13 @@ bool JackBackendFactory::init()
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
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};
jack_set_error_function(jack_msg_handler);
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);
if(!client)
{
@ -575,10 +703,20 @@ std::string JackBackendFactory::probe(BackendType type)
/* Includes null char. */
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)
{
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);
break;
case BackendType::Capture:
@ -587,7 +725,7 @@ std::string JackBackendFactory::probe(BackendType type)
return outnames;
}
BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new JackPlayback{device}};

View file

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

View file

@ -20,18 +20,17 @@
#include "config.h"
#include "backends/loopback.h"
#include "loopback.h"
#include "alcmain.h"
#include "alu.h"
#include "core/device.h"
namespace {
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;
void start() 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;
}
@ -69,7 +68,7 @@ bool LoopbackBackendFactory::querySupport(BackendType)
std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; }
BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType)
BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }
BackendFactory &LoopbackBackendFactory::getFactory()

View file

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

View file

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

View file

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

View file

@ -21,7 +21,7 @@
#include "config.h"
#include "backends/opensl.h"
#include "opensl.h"
#include <stdlib.h>
#include <jni.h>
@ -33,9 +33,9 @@
#include <functional>
#include "albit.h"
#include "alcmain.h"
#include "alu.h"
#include "compat.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.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 |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
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 |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
@ -105,7 +102,7 @@ constexpr SLuint32 GetTypeRepresentation(DevFmtType type) 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_BIGENDIAN;
}
@ -151,7 +148,7 @@ const char *res_str(SLresult result) noexcept
struct OpenSLPlayback final : public BackendBase {
OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLPlayback() override;
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",
name};
/* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return;
// create engine
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
@ -629,7 +629,7 @@ ClockLatency OpenSLPlayback::getClockLatency()
struct OpenSLCapture final : public BackendBase {
OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@ -810,8 +810,11 @@ void OpenSLCapture::open(const char* name)
if(SL_RESULT_SUCCESS == result)
{
const uint chunk_size{mDevice->UpdateSize * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
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++)
{
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 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
* pointer.
@ -922,15 +926,20 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
{
SLresult result{SL_RESULT_SUCCESS};
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++)
{
result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size);
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);
PRINTERR(result, "bufferQueue->Enqueue");
std::fill_n(wdata.second.buf, wdata.second.len*chunk_size, silence);
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;
}
BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OpenSLPlayback{device}};

View file

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

View file

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

View file

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

View file

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

View file

@ -21,33 +21,49 @@
#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 <functional>
#include <array>
#include <atomic>
#include <bitset>
#include <chrono>
#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 "alu.h"
#include "alconfig.h"
#include "compat.h"
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.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 "dynload.h"
#include "opthelpers.h"
#include "strutils.h"
#include "vector.h"
#include <pulse/pulseaudio.h>
namespace {
using uint = unsigned int;
#ifdef HAVE_DYNLOAD
#define PULSE_FUNCS(MAGIC) \
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. */
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)
{
const char *name{"OpenAL Soft"};
const PathNamePair &binname = GetProcBinary();
if(!binname.fname.empty())
name = binname.fname.c_str();
if(!mMainloop)
{
mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_proc), this};
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,
"pa_context_new() failed"};
@ -661,7 +599,7 @@ PulseMainloop gGlobalMainloop;
struct PulsePlayback final : public BackendBase {
PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { }
PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~PulsePlayback() override;
void bufferAttrCallback(pa_stream *stream) noexcept;
@ -698,6 +636,7 @@ struct PulsePlayback final : public BackendBase {
al::optional<std::string> mDeviceName{al::nullopt};
bool mIs51Rear{false};
pa_buffer_attr mAttr;
pa_sample_spec mSpec;
@ -770,15 +709,16 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
struct ChannelMap {
DevFmtChannels fmt;
pa_channel_map map;
bool is_51rear;
};
static constexpr std::array<ChannelMap,7> chanmaps{{
{ DevFmtX71, X71ChanMap },
{ DevFmtX61, X61ChanMap },
{ DevFmtX51, X51ChanMap },
{ DevFmtX51Rear, X51RearChanMap },
{ DevFmtQuad, QuadChanMap },
{ DevFmtStereo, StereoChanMap },
{ DevFmtMono, MonoChanMap }
{ DevFmtX71, X71ChanMap, false },
{ DevFmtX61, X61ChanMap, false },
{ DevFmtX51, X51ChanMap, false },
{ DevFmtX51, X51RearChanMap, true },
{ DevFmtQuad, QuadChanMap, false },
{ DevFmtStereo, StereoChanMap, false },
{ DevFmtMono, MonoChanMap, false }
}};
if(eol)
@ -795,9 +735,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
{
if(!mDevice->Flags.test(ChannelsRequest))
mDevice->FmtChans = chaniter->fmt;
mIs51Rear = chaniter->is_51rear;
}
else
{
mIs51Rear = false;
char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{};
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);
@ -805,8 +747,8 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
if(info->active_port)
TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description);
mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo
&& info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0);
mDevice->Flags.set(DirectEar, (info->active_port
&& strcmp(info->active_port->name, "analog-output-headphones") == 0));
}
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();
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_FIX_CHANNELS};
@ -864,8 +807,18 @@ void PulsePlayback::open(const char *name)
if(defname) pulse_name = defname->c_str();
}
TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr,
BackendType::Playback);
pa_stream *stream{mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec,
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);
mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
@ -934,10 +887,7 @@ bool PulsePlayback::reset()
chanmap = QuadChanMap;
break;
case DevFmtX51:
chanmap = X51ChanMap;
break;
case DevFmtX51Rear:
chanmap = X51RearChanMap;
chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap);
break;
case DevFmtX61:
chanmap = X61ChanMap;
@ -946,7 +896,7 @@ bool PulsePlayback::reset()
chanmap = X71ChanMap;
break;
}
SetChannelOrderFromMap(mDevice, chanmap);
setDefaultWFXChannelOrder();
switch(mDevice->FmtType)
{
@ -1029,34 +979,34 @@ void PulsePlayback::start()
{
auto plock = mMainloop.getUniqueLock();
pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this);
pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
&mMainloop)};
/* Write some (silent) samples to fill the prebuf amount if needed. */
if(size_t prebuf{mAttr.prebuf})
/* Write some (silent) samples to fill the buffer before we start feeding
* it newly mixed samples.
*/
if(size_t todo{pa_stream_writable_size(mStream)})
{
prebuf = minz(prebuf, pa_stream_writable_size(mStream));
void *buf{pa_xmalloc(prebuf)};
void *buf{pa_xmalloc(todo)};
switch(mSpec.format)
{
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;
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;
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;
default:
std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x00);
std::fill_n(static_cast<uint8_t*>(buf), todo, 0x00);
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);
}
@ -1103,7 +1053,7 @@ ClockLatency PulsePlayback::getClockLatency()
struct PulseCapture final : public BackendBase {
PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { }
PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~PulseCapture() override;
void streamStateCallback(pa_stream *stream) noexcept;
@ -1217,9 +1167,6 @@ void PulseCapture::open(const char *name)
case DevFmtX51:
chanmap = X51ChanMap;
break;
case DevFmtX51Rear:
chanmap = X51RearChanMap;
break;
case DevFmtX61:
chanmap = X61ChanMap;
break;
@ -1230,7 +1177,7 @@ void PulseCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
SetChannelOrderFromMap(mDevice, chanmap);
setDefaultWFXChannelOrder();
switch(mDevice->FmtType)
{
@ -1505,7 +1452,7 @@ std::string PulseBackendFactory::probe(BackendType type)
return outnames;
}
BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr PulseBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new PulsePlayback{device}};

View file

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

View file

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

View file

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

View file

@ -20,8 +20,9 @@
#include "config.h"
#include "backends/sndio.h"
#include "sndio.h"
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -29,8 +30,9 @@
#include <thread>
#include <functional>
#include "alcmain.h"
#include "alu.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
@ -43,9 +45,14 @@ namespace {
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 {
SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioPlayback() override;
int mixerProc();
@ -56,6 +63,7 @@ struct SndioPlayback final : public BackendBase {
void stop() override;
sio_hdl *mSndHandle{nullptr};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
@ -74,16 +82,8 @@ SndioPlayback::~SndioPlayback()
int SndioPlayback::mixerProc()
{
sio_par par;
sio_initpar(&par);
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};
const size_t frameStep{mFrameStep};
const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
@ -91,22 +91,20 @@ int SndioPlayback::mixerProc()
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
al::byte *WritePtr{mBuffer.data()};
size_t len{mBuffer.size()};
al::span<al::byte> buffer{mBuffer};
mDevice->renderSamples(WritePtr, static_cast<uint>(len/frameSize), frameStep);
while(len > 0 && !mKillNow.load(std::memory_order_acquire))
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
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)
{
ERR("sio_write failed\n");
mDevice->handleDisconnect("Failed to write playback samples");
break;
}
len -= wrote;
WritePtr += wrote;
buffer = buffer.subspan(wrote);
}
}
@ -122,34 +120,24 @@ void SndioPlayback::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
if(mSndHandle == nullptr)
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(!sndHandle)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
if(mSndHandle)
sio_close(mSndHandle);
mSndHandle = sndHandle;
mDevice->DeviceName = name;
}
bool SndioPlayback::reset()
{
sio_par par;
sio_initpar(&par);
SioPar par;
par.rate = mDevice->Frequency;
switch(mDevice->FmtChans)
{
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)
auto tryfmt = mDevice->FmtType;
retry_params:
switch(tryfmt)
{
case DevFmtByte:
par.bits = 8;
@ -159,7 +147,6 @@ bool SndioPlayback::reset()
par.bits = 8;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtShort:
par.bits = 16;
par.sig = 1;
@ -168,6 +155,7 @@ bool SndioPlayback::reset()
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
@ -177,70 +165,64 @@ bool SndioPlayback::reset()
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
{
ERR("Failed to set device parameters\n");
return false;
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"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)
{
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
return false;
}
if(par.le != SIO_LE_NATIVE)
{
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;
if(par.bps == 1)
mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte;
else if(par.bps == 2)
mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort;
else if(par.bps == 4)
mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
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);
return false;
WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
DevFmtChannelsString(mDevice->FmtChans));
if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
else mDevice->FmtChans = DevFmtStereo;
}
mDevice->Frequency = par.rate;
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 {
SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioCapture() override;
int recordProc();
@ -323,40 +310,65 @@ int SndioCapture::recordProc()
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)
&& mDevice->Connected.load(std::memory_order_acquire))
{
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
if(todo == 0)
/* Wait until there's some samples to read. */
const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
if(nfds <= 0)
{
static char junk[4096];
sio_read(mSndHandle, junk,
minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
break;
}
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;
}
size_t total{0u};
data.first.len *= frameSize;
data.second.len *= frameSize;
todo = minz(todo, mDevice->UpdateSize) * frameSize;
while(total < todo)
const int revents{sio_revents(mSndHandle, fds.get())};
if((revents&POLLHUP))
{
if(!data.first.len)
data.first = data.second;
size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
if(!got)
{
mDevice->handleDisconnect("Failed to read capture samples");
break;
}
data.first.buf += got;
data.first.len -= got;
total += got;
mDevice->handleDisconnect("Got POLLHUP from poll events");
break;
}
if(!(revents&POLLIN))
continue;
auto data = mRing->getWriteVector();
al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
while(!buffer.empty())
{
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
if(got == 0) break;
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;
@ -371,76 +383,80 @@ void SndioCapture::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mSndHandle = sio_open(nullptr, SIO_REC, 0);
mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
sio_par par;
sio_initpar(&par);
SioPar par;
switch(mDevice->FmtType)
{
case DevFmtByte:
par.bps = 1;
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bps = 1;
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bps = 2;
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bps = 2;
par.bits = 16;
par.sig = 0;
break;
case DevFmtInt:
par.bps = 4;
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bps = 4;
par.bits = 32;
par.sig = 0;
break;
case DevFmtFloat:
throw al::backend_exception{al::backend_error::DeviceError,
"%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.msb = SIO_LE_NATIVE ? 0 : 1;
par.msb = 1;
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz, mDevice->Frequency/40);
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.appbufsz;
par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"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,
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0)
|| (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0)
|| (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0)
|| (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0)
|| (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0)
|| (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0))
|| mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate)
auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
{
return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0)
|| (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0)
|| (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0)
|| (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0)
|| (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,
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
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);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round;
setDefaultChannelOrder();
@ -507,7 +523,7 @@ std::string SndIOBackendFactory::probe(BackendType type)
return outnames;
}
BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type)
BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SndioPlayback{device}};

View file

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

View file

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

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