Initial commit

added libraries:
opus
flac
libsndfile

updated:
libvorbis
libogg
openal

- Everything works as expected for now. Bare in mind libsndfile needed the check for whether or not it could find the xiph libraries removed in order for this to work.
This commit is contained in:
marauder2k7 2024-03-21 17:33:47 +00:00
parent 05a083ca6f
commit a745fc3757
1954 changed files with 431332 additions and 21037 deletions

File diff suppressed because it is too large Load diff

View file

@ -140,7 +140,7 @@ void LoadConfigFromFile(std::istream &f)
if(buffer[0] == '[')
{
char *line{&buffer[0]};
auto line = const_cast<char*>(buffer.data());
char *section = line+1;
char *endsection;
@ -486,36 +486,36 @@ void ReadALConfig()
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional<std::string>(val);
return val;
return al::nullopt;
}
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
return 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)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
return 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)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::make_optional(std::strtof(val, nullptr));
return std::strtof(val, nullptr);
return al::nullopt;
}
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
{
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::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0;
return al::nullopt;
}

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@ enum CompatFlags : uint8_t {
};
using CompatFlagBitset = std::bitset<CompatFlags::Count>;
void aluInit(CompatFlagBitset flags);
void aluInit(CompatFlagBitset flags, const float nfcscale);
/* aluInitRenderer
*

View file

@ -132,7 +132,7 @@ constexpr char alsaDevice[] = "ALSA Default";
MAGIC(snd_card_next); \
MAGIC(snd_config_update_free_global)
static void *alsa_handle;
void *alsa_handle;
#define MAKE_FUNC(f) decltype(f) * p##f
ALSA_FUNCS(MAKE_FUNC);
#undef MAKE_FUNC
@ -241,6 +241,11 @@ SwParamsPtr CreateSwParams()
struct DevMap {
std::string name;
std::string device_name;
template<typename T, typename U>
DevMap(T&& name_, U&& devname)
: name{std::forward<T>(name_)}, device_name{std::forward<U>(devname)}
{ }
};
al::vector<DevMap> PlaybackDevices;
@ -264,7 +269,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
auto defname = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"});
devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default");
if(auto customdevs = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
@ -283,8 +288,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
}
else
{
devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos),
customdevs->substr(seppos+1, nextpos-seppos-1)});
devlist.emplace_back(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());
}
@ -325,7 +330,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
int dev{-1};
while(1)
while(true)
{
if(snd_ctl_pcm_next_device(handle, &dev) < 0)
ERR("snd_ctl_pcm_next_device failed\n");
@ -367,7 +372,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
device += ",DEV=";
device += std::to_string(dev);
devlist.emplace_back(DevMap{std::move(name), std::move(device)});
devlist.emplace_back(std::move(name), std::move(device));
const auto &entry = devlist.back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
@ -623,8 +628,7 @@ int AlsaPlayback::mixerNoMMapProc()
void AlsaPlayback::open(const char *name)
{
al::optional<std::string> driveropt;
const char *driver{"default"};
std::string driver{"default"};
if(name)
{
if(PlaybackDevices.empty())
@ -635,21 +639,21 @@ void AlsaPlayback::open(const char *name)
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
driver = iter->device_name.c_str();
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")})
driver = driveropt->c_str();
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device"))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver);
TRACE("Opening device \"%s\"\n", driver.c_str());
snd_pcm_t *pcmHandle{};
int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
int err{snd_pcm_open(&pcmHandle, driver.c_str(), 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};
"Could not open ALSA device \"%s\"", driver.c_str()};
if(mPcmHandle)
snd_pcm_close(mPcmHandle);
mPcmHandle = pcmHandle;
@ -688,7 +692,7 @@ bool AlsaPlayback::reset()
break;
}
bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)};
bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)};
uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
uint rate{mDevice->Frequency};
@ -746,7 +750,7 @@ bool AlsaPlayback::reset()
else mDevice->FmtChans = DevFmtStereo;
}
/* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0)
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false)
|| !mDevice->Flags.test(FrequencyRequest))
{
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
@ -896,8 +900,7 @@ AlsaCapture::~AlsaCapture()
void AlsaCapture::open(const char *name)
{
al::optional<std::string> driveropt;
const char *driver{"default"};
std::string driver{"default"};
if(name)
{
if(CaptureDevices.empty())
@ -908,20 +911,20 @@ void AlsaCapture::open(const char *name)
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
driver = iter->device_name.c_str();
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")})
driver = driveropt->c_str();
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture"))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver);
int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
TRACE("Opening device \"%s\"\n", driver.c_str());
int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
if(err < 0)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver};
"Could not open ALSA device \"%s\"", driver.c_str()};
/* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
snd_config_update_free_global();

View file

@ -21,6 +21,20 @@
#include "core/devformat.h"
namespace al {
backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
backend_exception::~backend_exception() = default;
} // namespace al
bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
@ -54,7 +68,7 @@ ClockLatency BackendBase::getClockLatency()
void BackendBase::setDefaultWFXChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
switch(mDevice->FmtChans)
{
@ -98,6 +112,30 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
break;
case DevFmtX714:
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;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[Aux0] = 4;
mDevice->RealOut.ChannelIndex[Aux1] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
break;
case DevFmtAmbi3D:
break;
}
@ -105,7 +143,7 @@ void BackendBase::setDefaultWFXChannelOrder()
void BackendBase::setDefaultChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
switch(mDevice->FmtChans)
{
@ -127,6 +165,30 @@ void BackendBase::setDefaultChannelOrder()
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
return;
case DevFmtX714:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[Aux0] = 2;
mDevice->RealOut.ChannelIndex[Aux1] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
return;
/* Same as WFX order */
case DevFmtMono:
@ -138,57 +200,3 @@ void BackendBase::setDefaultChannelOrder()
break;
}
}
#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)
{
case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft);
case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight);
case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter);
case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE);
case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft);
case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight);
case SPEAKER_FRONT_LEFT_OF_CENTER: break;
case SPEAKER_FRONT_RIGHT_OF_CENTER: break;
case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter);
case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft);
case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight);
case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter);
case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft);
case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter);
case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight);
}
WARN("Unhandled WFX channel bit 0x%lx\n", chanbit);
return al::nullopt;
};
const uint numchans{mDevice->channelsFromFmt()};
uint idx{0};
while(chanmask)
{
const int bit{al::countr_zero(chanmask)};
const uint mask{1u << bit};
chanmask &= ~mask;
if(auto label = get_channel(mask))
{
mDevice->RealOut.ChannelIndex[*label] = idx;
if(++idx == numchans) break;
}
}
}
#endif

View file

@ -41,11 +41,6 @@ protected:
void setDefaultChannelOrder();
/** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder();
#ifdef _WIN32
/** Sets the channel order given the WaveFormatEx mask. */
void setChannelOrderFromWFXMask(uint chanmask);
#endif
};
using BackendPtr = std::unique_ptr<BackendBase>;
@ -108,13 +103,9 @@ public:
#else
[[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
backend_exception(backend_error code, const char *msg, ...);
~backend_exception() override;
backend_error errorCode() const noexcept { return mErrorCode; }
};

View file

@ -51,6 +51,8 @@ namespace {
#define CAN_ENUMERATE 1
#endif
constexpr auto OutputElement = 0;
constexpr auto InputElement = 1;
#if CAN_ENUMERATE
struct DeviceEntry {
@ -354,7 +356,7 @@ void CoreAudioPlayback::open(const char *name)
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
err = AudioUnitInitialize(audioUnit);
@ -380,7 +382,7 @@ void CoreAudioPlayback::open(const char *name)
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, &propSize);
kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
@ -401,7 +403,7 @@ bool CoreAudioPlayback::reset()
AudioStreamBasicDescription streamFormat{};
UInt32 size{sizeof(streamFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
0, &streamFormat, &size);
OutputElement, &streamFormat, &size);
if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
@ -423,8 +425,8 @@ bool CoreAudioPlayback::reset()
*/
if(mDevice->Frequency != streamFormat.mSampleRate)
{
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
streamFormat.mSampleRate / mDevice->Frequency);
mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
mDevice->Frequency + 0.5);
mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
}
@ -468,7 +470,7 @@ bool CoreAudioPlayback::reset()
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &streamFormat, sizeof(streamFormat));
OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
@ -484,7 +486,7 @@ bool CoreAudioPlayback::reset()
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
@ -546,6 +548,8 @@ struct CoreAudioCapture final : public BackendBase {
SampleConverterPtr mConverter;
al::vector<char> mCaptureData;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
@ -559,49 +563,29 @@ CoreAudioCapture::~CoreAudioCapture()
}
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList*) noexcept
{
AudioUnitRenderActionFlags flags = 0;
union {
al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
auto rec_vec = mRing->getWriteVector();
inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
rec_vec.first.len+rec_vec.second.len));
audiobuf.list.mNumberBuffers = 1;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = mCaptureData.data();
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
// Fill the ringbuffer's two segments with data from the input device
if(rec_vec.first.len >= inNumberFrames)
{
audiobuf.list.mNumberBuffers = 1;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
}
else
{
const auto remaining = static_cast<uint>(inNumberFrames - rec_vec.first.len);
audiobuf.list.mNumberBuffers = 2;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
mFormat.mBytesPerFrame;
audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
}
OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
ERR("AudioUnitRender error: %d\n", err);
ERR("AudioUnitRender capture error: %d\n", err);
return err;
}
mRing->writeAdvance(inNumberFrames);
mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
@ -658,16 +642,10 @@ void CoreAudioCapture::open(const char *name)
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
UInt32 enableIO{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable audio unit output property: %u", err};
@ -675,18 +653,24 @@ void CoreAudioCapture::open(const char *name)
// Turn on AudioUnit input
enableIO = 1;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO));
kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: %u", err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
// set capture callback
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set capture callback: %u", err};
@ -694,7 +678,7 @@ void CoreAudioCapture::open(const char *name)
// Disable buffer allocation for capture
UInt32 flag{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output, 1, &flag, sizeof(flag));
kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable buffer allocation property: %u", err};
@ -709,7 +693,7 @@ void CoreAudioCapture::open(const char *name)
AudioStreamBasicDescription hardwareFormat{};
UInt32 propertySize{sizeof(hardwareFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1, &hardwareFormat, &propertySize);
InputElement, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err};
@ -764,6 +748,8 @@ void CoreAudioCapture::open(const char *name)
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
DevFmtChannelsString(mDevice->FmtChans)};
@ -788,7 +774,7 @@ void CoreAudioCapture::open(const char *name)
// The output format should be the requested format, but using the hardware sample rate
// This is because the AudioUnit will automatically scale other properties, except for sample rate
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
1, &outputFormat, sizeof(outputFormat));
InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set input format: %u", err};
@ -796,7 +782,7 @@ void CoreAudioCapture::open(const char *name)
/* Calculate the minimum AudioUnit output format frame count for the pre-
* conversion ring buffer. Ensure at least 100ms for the total buffer.
*/
double srateScale{double{outputFormat.mSampleRate} / mDevice->Frequency};
double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10);
FrameCount64 += MaxResamplerPadding;
@ -807,17 +793,19 @@ void CoreAudioCapture::open(const char *name)
UInt32 outputFrameCount{};
propertySize = sizeof(outputFrameCount);
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Global, 0, &outputFrameCount, &propertySize);
kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
if(err != noErr || propertySize != sizeof(outputFrameCount))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input frame count: %u", err};
mCaptureData.resize(outputFrameCount * mFrameSize);
outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
/* Set up sample converter if needed */
if(outputFormat.mSampleRate != mDevice->Frequency)
mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24);
@ -829,7 +817,7 @@ void CoreAudioCapture::open(const char *name)
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &audioDevice, &propSize);
kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);

View file

@ -115,6 +115,7 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac
#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
#define MAX_UPDATES 128
@ -389,52 +390,56 @@ bool DSoundPlayback::reset()
}
WAVEFORMATEXTENSIBLE OutputType{};
DWORD speakers;
DWORD speakers{};
HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
if(SUCCEEDED(hr))
{
speakers = DSSPEAKER_CONFIG(speakers);
if(!mDevice->Flags.test(ChannelsRequest))
{
if(speakers == DSSPEAKER_MONO)
mDevice->FmtChans = DevFmtMono;
else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
mDevice->FmtChans = DevFmtStereo;
else if(speakers == DSSPEAKER_QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71;
else
ERR("Unknown system speaker config: 0x%lx\n", speakers);
}
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get speaker config: 0x%08lx", hr};
switch(mDevice->FmtChans)
{
case DevFmtMono: OutputType.dwChannelMask = MONO; break;
case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
/*fall-through*/
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
}
speakers = DSSPEAKER_CONFIG(speakers);
if(!mDevice->Flags.test(ChannelsRequest))
{
if(speakers == DSSPEAKER_MONO)
mDevice->FmtChans = DevFmtMono;
else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
mDevice->FmtChans = DevFmtStereo;
else if(speakers == DSSPEAKER_QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71;
else
ERR("Unknown system speaker config: 0x%lx\n", speakers);
}
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
switch(mDevice->FmtChans)
{
case DevFmtMono: OutputType.dwChannelMask = MONO; break;
case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
/* fall-through */
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break;
}
retry_open:
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
}
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
@ -514,7 +519,7 @@ retry_open:
}
ResetEvent(mNotifyEvent);
setChannelOrderFromWFXMask(OutputType.dwChannelMask);
setDefaultWFXChannelOrder();
return true;
}
@ -635,6 +640,8 @@ void DSoundCapture::open(const char *name)
case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -689,7 +696,7 @@ void DSoundCapture::open(const char *name)
}
mBufferBytes = DSCBDescription.dwBufferBytes;
setChannelOrderFromWFXMask(InputType.dwChannelMask);
setDefaultWFXChannelOrder();
mDevice->DeviceName = name;
}

View file

@ -160,6 +160,11 @@ using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
struct DeviceEntry {
std::string mName;
std::string mPattern;
template<typename T, typename U>
DeviceEntry(T&& name, U&& pattern)
: mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
{ }
};
al::vector<DeviceEntry> PlaybackList;
@ -187,7 +192,7 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(DeviceEntry{name, name+":"});
list.emplace_back(name, name+":");
const auto &entry = list.back();
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
@ -197,7 +202,7 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
if(ports[0] && list.empty())
{
WARN("No device names found in available ports, adding a generic name.\n");
list.emplace_back(DeviceEntry{"JACK", ""});
list.emplace_back("JACK", "");
}
}
@ -238,8 +243,8 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(DeviceEntry{std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()}});
list.emplace_back(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());
}
@ -340,7 +345,7 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
if LIKELY(mPlaying.load(std::memory_order_acquire))
if(mPlaying.load(std::memory_order_acquire)) LIKELY
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
else
{
@ -364,7 +369,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
}
jack_nframes_t total{0};
if LIKELY(mPlaying.load(std::memory_order_acquire))
if(mPlaying.load(std::memory_order_acquire)) LIKELY
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
@ -492,7 +497,7 @@ void JackPlayback::open(const char *name)
mPortPattern = iter->mPattern;
}
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
@ -669,7 +674,7 @@ bool JackBackendFactory::init()
if(!jack_load())
return false;
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();

View file

@ -10,6 +10,7 @@
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "oboe/Oboe.h"
@ -80,7 +81,10 @@ bool OboePlayback::reset()
builder.setCallback(this);
if(mDevice->Flags.test(FrequencyRequest))
{
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
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
@ -103,6 +107,10 @@ bool OboePlayback::reset()
break;
case DevFmtInt:
case DevFmtUInt:
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
format = oboe::AudioFormat::I32;
break;
#endif
case DevFmtFloat:
format = oboe::AudioFormat::Float;
break;
@ -151,6 +159,12 @@ bool OboePlayback::reset()
case oboe::AudioFormat::Float:
mDevice->FmtType = DevFmtFloat;
break;
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
case oboe::AudioFormat::I32:
mDevice->FmtType = DevFmtInt;
break;
case oboe::AudioFormat::I24:
#endif
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
throw al::backend_exception{al::backend_error::DeviceError,
@ -188,13 +202,15 @@ void OboePlayback::stop()
}
struct OboeCapture final : public BackendBase {
struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
OboeCapture(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
std::vector<al::byte> mSamples;
uint mLastAvail{0u};
RingBufferPtr mRing{nullptr};
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void start() override;
@ -203,6 +219,14 @@ struct OboeCapture final : public BackendBase {
uint availableSamples() override;
};
oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
int32_t numFrames)
{
mRing->write(audioData, static_cast<uint32_t>(numFrames));
return oboe::DataCallbackResult::Continue;
}
void OboeCapture::open(const char *name)
{
if(!name)
@ -217,8 +241,8 @@ void OboeCapture::open(const char *name)
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
->setChannelConversionAllowed(true)
->setFormatConversionAllowed(true)
->setBufferCapacityInFrames(static_cast<int32_t>(mDevice->BufferSize))
->setSampleRate(static_cast<int32_t>(mDevice->Frequency));
->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
->setCallback(this);
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
@ -234,13 +258,15 @@ void OboeCapture::open(const char *name)
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
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.
* convert.
*/
switch(mDevice->FmtType)
{
@ -250,10 +276,14 @@ void OboeCapture::open(const char *name)
case DevFmtFloat:
builder.setFormat(oboe::AudioFormat::Float);
break;
case DevFmtInt:
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
builder.setFormat(oboe::AudioFormat::I32);
break;
#endif
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)};
@ -263,22 +293,13 @@ void OboeCapture::open(const char *name)
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()));
/* Ensure a minimum ringbuffer size of 100ms. */
mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
mDevice->DeviceName = name;
}
@ -292,23 +313,6 @@ void OboeCapture::start()
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",
@ -316,38 +320,10 @@ void OboeCapture::stop()
}
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;
}
{ return static_cast<uint>(mRing->readSpace()); }
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;
}
{ mRing->read(buffer, samples); }
} // namespace

View file

@ -71,9 +71,15 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
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;
case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
case DevFmtX71:
case DevFmtX3D71: 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 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
case DevFmtX714: 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 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT |
SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT |
SL_SPEAKER_TOP_BACK_RIGHT;
case DevFmtAmbi3D:
break;
}
@ -107,7 +113,7 @@ constexpr SLuint32 GetByteOrderEndianness() noexcept
return SL_BYTEORDER_BIGENDIAN;
}
const char *res_str(SLresult result) noexcept
constexpr const char *res_str(SLresult result) noexcept
{
switch(result)
{
@ -141,10 +147,11 @@ const char *res_str(SLresult result) noexcept
return "Unknown error code";
}
#define PRINTERR(x, s) do { \
if UNLIKELY((x) != SL_RESULT_SUCCESS) \
ERR("%s: %s\n", (s), res_str((x))); \
} while(0)
inline void PrintErr(SLresult res, const char *str)
{
if(res != SL_RESULT_SUCCESS) UNLIKELY
ERR("%s: %s\n", str, res_str(res));
}
struct OpenSLPlayback final : public BackendBase {
@ -228,11 +235,11 @@ int OpenSLPlayback::mixerProc()
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY");
}
const size_t frame_step{mDevice->channelsFromFmt()};
@ -248,11 +255,11 @@ int OpenSLPlayback::mixerProc()
SLuint32 state{0};
result = VCALL(player,GetPlayState)(&state);
PRINTERR(result, "player->GetPlayState");
PrintErr(result, "player->GetPlayState");
if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
PRINTERR(result, "player->SetPlayState");
PrintErr(result, "player->SetPlayState");
}
if(SL_RESULT_SUCCESS != result)
{
@ -289,7 +296,7 @@ int OpenSLPlayback::mixerProc()
}
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
PRINTERR(result, "bufferQueue->Enqueue");
PrintErr(result, "bufferQueue->Enqueue");
if(SL_RESULT_SUCCESS != result)
{
mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
@ -318,26 +325,26 @@ void OpenSLPlayback::open(const char *name)
// create engine
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
PrintErr(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "engine->Realize");
PrintErr(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
PRINTERR(result, "engine->GetInterface");
PrintErr(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
PRINTERR(result, "engine->CreateOutputMix");
PrintErr(result, "engine->CreateOutputMix");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "outputMix->Realize");
PrintErr(result, "outputMix->Realize");
}
if(SL_RESULT_SUCCESS != result)
@ -505,20 +512,20 @@ bool OpenSLPlayback::reset()
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
ids.data(), reqs.data());
PRINTERR(result, "engine->CreateAudioPlayer");
PrintErr(result, "engine->CreateAudioPlayer");
}
if(SL_RESULT_SUCCESS == result)
{
/* Set the stream type to "media" (games, music, etc), if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
sizeof(streamType));
PRINTERR(result, "config->SetConfiguration");
PrintErr(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
@ -527,7 +534,7 @@ bool OpenSLPlayback::reset()
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "bufferQueue->Realize");
PrintErr(result, "bufferQueue->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
@ -554,11 +561,11 @@ void OpenSLPlayback::start()
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "bufferQueue->GetInterface");
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS != result)
throw al::backend_exception{al::backend_error::DeviceError,
@ -584,25 +591,25 @@ void OpenSLPlayback::stop()
SLPlayItf player;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
PRINTERR(result, "bufferQueue->GetInterface");
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
PRINTERR(result, "player->SetPlayState");
PrintErr(result, "player->SetPlayState");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
PRINTERR(result, "bufferQueue->GetInterface");
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL0(bufferQueue,Clear)();
PRINTERR(result, "bufferQueue->Clear");
PrintErr(result, "bufferQueue->Clear");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
PRINTERR(result, "bufferQueue->RegisterCallback");
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
@ -611,7 +618,9 @@ void OpenSLPlayback::stop()
std::this_thread::yield();
result = VCALL(bufferQueue,GetState)(&state);
} while(SL_RESULT_SUCCESS == result && state.count > 0);
PRINTERR(result, "bufferQueue->GetState");
PrintErr(result, "bufferQueue->GetState");
mRing->reset();
}
}
@ -686,16 +695,16 @@ void OpenSLCapture::open(const char* name)
name};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
PrintErr(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "engine->Realize");
PrintErr(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
PRINTERR(result, "engine->GetInterface");
PrintErr(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
@ -770,7 +779,7 @@ void OpenSLCapture::open(const char* name)
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
ids.size(), ids.data(), reqs.data());
}
PRINTERR(result, "engine->CreateAudioRecorder");
PrintErr(result, "engine->CreateAudioRecorder");
}
}
if(SL_RESULT_SUCCESS == result)
@ -778,13 +787,13 @@ void OpenSLCapture::open(const char* name)
/* Set the record preset to "generic", if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
sizeof(preset));
PRINTERR(result, "config->SetConfiguration");
PrintErr(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
@ -793,19 +802,19 @@ void OpenSLCapture::open(const char* name)
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "recordObj->Realize");
PrintErr(result, "recordObj->Realize");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
PRINTERR(result, "recordObj->GetInterface");
PrintErr(result, "recordObj->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
@ -818,12 +827,12 @@ void OpenSLCapture::open(const char* name)
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);
PRINTERR(result, "bufferQueue->Enqueue");
PrintErr(result, "bufferQueue->Enqueue");
}
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
PrintErr(result, "bufferQueue->Enqueue");
}
}
@ -849,12 +858,12 @@ void OpenSLCapture::start()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
PRINTERR(result, "recordObj->GetInterface");
PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
PRINTERR(result, "record->SetRecordState");
PrintErr(result, "record->SetRecordState");
}
if(SL_RESULT_SUCCESS != result)
throw al::backend_exception{al::backend_error::DeviceError,
@ -865,12 +874,12 @@ void OpenSLCapture::stop()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
PRINTERR(result, "recordObj->GetInterface");
PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
PRINTERR(result, "record->SetRecordState");
PrintErr(result, "record->SetRecordState");
}
}
@ -878,7 +887,6 @@ 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.
@ -907,39 +915,52 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
i += rem;
}
mRing->readAdvance(adv_count);
SLAndroidSimpleBufferQueueItf bufferQueue{};
if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
if(mDevice->Connected.load(std::memory_order_acquire)) LIKELY
{
const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "recordObj->GetInterface");
if UNLIKELY(SL_RESULT_SUCCESS != result)
PrintErr(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS != result) UNLIKELY
{
mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
bufferQueue = nullptr;
}
}
if(!bufferQueue || adv_count == 0)
return;
if LIKELY(bufferQueue)
/* For each buffer chunk that was fully read, queue another writable buffer
* chunk to keep the OpenSL queue full. This is rather convulated, as a
* result of the ring buffer holding more elements than are writable at a
* given time. The end of the write vector increments when the read pointer
* advances, which will "expose" a previously unwritable element. So for
* every element that we've finished reading, we queue that many elements
* from the end of the write vector.
*/
mRing->readAdvance(adv_count);
SLresult result{SL_RESULT_SUCCESS};
auto wdata = mRing->getWriteVector();
if(adv_count > wdata.second.len) LIKELY
{
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++)
auto len1 = std::min(wdata.first.len, adv_count-wdata.second.len);
auto buf1 = wdata.first.buf + chunk_size*(wdata.first.len-len1);
for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size);
PrintErr(result, "bufferQueue->Enqueue");
}
if(wdata.second.len > 0)
}
if(wdata.second.len > 0)
{
auto len2 = std::min(wdata.second.len, adv_count);
auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2);
for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++)
{
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");
}
result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size);
PrintErr(result, "bufferQueue->Enqueue");
}
}
}

View file

@ -32,6 +32,7 @@
#include <memory>
#include <mutex>
#include <stdint.h>
#include <thread>
#include <type_traits>
#include <utility>
@ -50,8 +51,20 @@
#include "opthelpers.h"
#include "ringbuffer.h"
/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */
/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). GCC
* doesn't support ignoring -Weverything, so we have the list the individual
* warnings to ignore (and ignoring -Winline doesn't seem to work).
*/
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wpedantic\"")
_Pragma("GCC diagnostic ignored \"-Wconversion\"")
_Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"")
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"")
_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
_Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
_Pragma("GCC diagnostic ignored \"-Winline\"")
_Pragma("GCC diagnostic ignored \"-Wpragmas\"")
_Pragma("GCC diagnostic ignored \"-Weverything\"")
#include "pipewire/pipewire.h"
#include "pipewire/extensions/metadata.h"
@ -107,7 +120,13 @@ _Pragma("GCC diagnostic pop")
namespace {
/* Added in 0.3.33, but we currently only require 0.3.23. */
#ifndef PW_KEY_NODE_RATE
#define PW_KEY_NODE_RATE "node.rate"
#endif
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
using uint = unsigned int;
@ -115,12 +134,27 @@ constexpr char pwireDevice[] = "PipeWire Output";
constexpr char pwireInput[] = "PipeWire Input";
bool check_version(const char *version)
{
/* There doesn't seem to be a function to get the version as an integer, so
* instead we have to parse the string, which hopefully won't break in the
* future.
*/
int major{0}, minor{0}, revision{0};
int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)};
if(ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR)
|| (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)))
return true;
return false;
}
#ifdef HAVE_DYNLOAD
#define PWIRE_FUNCS(MAGIC) \
MAGIC(pw_context_connect) \
MAGIC(pw_context_destroy) \
MAGIC(pw_context_new) \
MAGIC(pw_core_disconnect) \
MAGIC(pw_get_library_version) \
MAGIC(pw_init) \
MAGIC(pw_properties_free) \
MAGIC(pw_properties_new) \
@ -134,7 +168,6 @@ constexpr char pwireInput[] = "PipeWire Input";
MAGIC(pw_stream_dequeue_buffer) \
MAGIC(pw_stream_destroy) \
MAGIC(pw_stream_get_state) \
MAGIC(pw_stream_get_time) \
MAGIC(pw_stream_new) \
MAGIC(pw_stream_queue_buffer) \
MAGIC(pw_stream_set_active) \
@ -146,11 +179,19 @@ constexpr char pwireInput[] = "PipeWire Input";
MAGIC(pw_thread_loop_lock) \
MAGIC(pw_thread_loop_wait) \
MAGIC(pw_thread_loop_signal) \
MAGIC(pw_thread_loop_unlock) \
MAGIC(pw_thread_loop_unlock)
#if PW_CHECK_VERSION(0,3,50)
#define PWIRE_FUNCS2(MAGIC) \
MAGIC(pw_stream_get_time_n)
#else
#define PWIRE_FUNCS2(MAGIC) \
MAGIC(pw_stream_get_time)
#endif
void *pwire_handle;
#define MAKE_FUNC(f) decltype(f) * p##f;
PWIRE_FUNCS(MAKE_FUNC)
PWIRE_FUNCS2(MAKE_FUNC)
#undef MAKE_FUNC
bool pwire_load()
@ -173,6 +214,7 @@ bool pwire_load()
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0);
PWIRE_FUNCS(LOAD_FUNC)
PWIRE_FUNCS2(LOAD_FUNC)
#undef LOAD_FUNC
if(!missing_funcs.empty())
@ -191,6 +233,7 @@ bool pwire_load()
#define pw_context_destroy ppw_context_destroy
#define pw_context_new ppw_context_new
#define pw_core_disconnect ppw_core_disconnect
#define pw_get_library_version ppw_get_library_version
#define pw_init ppw_init
#define pw_properties_free ppw_properties_free
#define pw_properties_new ppw_properties_new
@ -204,7 +247,6 @@ bool pwire_load()
#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
#define pw_stream_destroy ppw_stream_destroy
#define pw_stream_get_state ppw_stream_get_state
#define pw_stream_get_time ppw_stream_get_time
#define pw_stream_new ppw_stream_new
#define pw_stream_queue_buffer ppw_stream_queue_buffer
#define pw_stream_set_active ppw_stream_set_active
@ -217,6 +259,12 @@ bool pwire_load()
#define pw_thread_loop_stop ppw_thread_loop_stop
#define pw_thread_loop_unlock ppw_thread_loop_unlock
#define pw_thread_loop_wait ppw_thread_loop_wait
#if PW_CHECK_VERSION(0,3,50)
#define pw_stream_get_time_n ppw_stream_get_time_n
#else
inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/)
{ return ppw_stream_get_time(stream, ptime); }
#endif
#endif
#else
@ -319,7 +367,10 @@ using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
/* Enums for bitflags... again... *sigh* */
constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
{ return static_cast<pw_stream_flags>(lhs | std::underlying_type_t<pw_stream_flags>{rhs}); }
{ return static_cast<pw_stream_flags>(lhs | al::to_underlying(rhs)); }
constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept
{ lhs = lhs | rhs; return lhs; }
class ThreadMainloop {
pw_thread_loop *mLoop{};
@ -426,7 +477,7 @@ struct EventManager {
*/
void waitForInit()
{
if(unlikely(!mInitDone.load(std::memory_order_acquire)))
if(!mInitDone.load(std::memory_order_acquire)) UNLIKELY
{
MainloopUniqueLock plock{mLoop};
plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); });
@ -503,10 +554,12 @@ enum class NodeType : unsigned char {
};
constexpr auto InvalidChannelConfig = DevFmtChannels(255);
struct DeviceNode {
uint32_t mId{};
uint64_t mSerial{};
std::string mName;
std::string mDevName;
uint32_t mId{};
NodeType mType{};
bool mIsHeadphones{};
bool mIs51Rear{};
@ -560,7 +613,7 @@ DeviceNode *DeviceNode::Find(uint32_t id)
{ return n.mId == id; };
auto match = std::find_if(sList.begin(), sList.end(), match_id);
if(match != sList.end()) return std::addressof(*match);
if(match != sList.end()) return al::to_address(match);
return nullptr;
}
@ -598,6 +651,10 @@ const spa_audio_channel MonoMap[]{
}, X71Map[]{
SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
}, X714Map[]{
SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR,
SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR
};
/**
@ -640,8 +697,8 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
auto srates = get_pod_body<int32_t,3>(value);
/* [0] is the default, [1] is the min, and [2] is the max. */
TRACE("Device ID %u sample rate: %d (range: %d -> %d)\n", mId, srates[0], srates[1],
srates[2]);
TRACE("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0],
srates[1], srates[2]);
mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
return;
}
@ -662,7 +719,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
others += ", ";
others += std::to_string(srates[i]);
}
TRACE("Device ID %u sample rate: %d (%s)\n", mId, srates[0], others.c_str());
TRACE("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str());
/* Pick the first rate listed that's within the allowed range (default
* rate if possible).
*/
@ -686,7 +743,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
}
auto srates = get_pod_body<int32_t,1>(value);
TRACE("Device ID %u sample rate: %d\n", mId, srates[0]);
TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]);
mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
return;
}
@ -701,7 +758,9 @@ void DeviceNode::parsePositions(const spa_pod *value) noexcept
mIs51Rear = false;
if(MatchChannelMap(chanmap, X71Map))
if(MatchChannelMap(chanmap, X714Map))
mChannels = DevFmtX714;
else if(MatchChannelMap(chanmap, X71Map))
mChannels = DevFmtX71;
else if(MatchChannelMap(chanmap, X61Map))
mChannels = DevFmtX61;
@ -718,7 +777,7 @@ void DeviceNode::parsePositions(const spa_pod *value) noexcept
mChannels = DevFmtStereo;
else
mChannels = DevFmtMono;
TRACE("Device ID %u got %zu position%s for %s%s\n", mId, chanmap.size(),
TRACE("Device ID %" PRIu64 " got %zu position%s for %s%s\n", mSerial, chanmap.size(),
(chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
}
@ -734,8 +793,8 @@ void DeviceNode::parseChannelCount(const spa_pod *value) noexcept
mChannels = DevFmtStereo;
else if(*chancount >= 1)
mChannels = DevFmtMono;
TRACE("Device ID %u got %d channel%s for %s\n", mId, *chancount, (*chancount==1)?"":"s",
DevFmtChannelsString(mChannels));
TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *chancount,
(*chancount==1)?"":"s", DevFmtChannelsString(mChannels));
}
@ -743,6 +802,7 @@ constexpr char MonitorPrefix[]{"Monitor of "};
constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1;
constexpr char AudioSinkClass[]{"Audio/Sink"};
constexpr char AudioSourceClass[]{"Audio/Source"};
constexpr char AudioSourceVirtualClass[]{"Audio/Source/Virtual"};
constexpr char AudioDuplexClass[]{"Audio/Duplex"};
constexpr char StreamClass[]{"Stream/"};
@ -802,12 +862,13 @@ void NodeProxy::infoCallback(const pw_node_info *info)
{
/* Can this actually change? */
const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)};
if(unlikely(!media_class)) return;
if(!media_class) UNLIKELY return;
NodeType ntype{};
if(al::strcasecmp(media_class, AudioSinkClass) == 0)
ntype = NodeType::Sink;
else if(al::strcasecmp(media_class, AudioSourceClass) == 0)
else if(al::strcasecmp(media_class, AudioSourceClass) == 0
|| al::strcasecmp(media_class, AudioSourceVirtualClass) == 0)
ntype = NodeType::Source;
else if(al::strcasecmp(media_class, AudioDuplexClass) == 0)
ntype = NodeType::Duplex;
@ -823,12 +884,27 @@ void NodeProxy::infoCallback(const pw_node_info *info)
if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK);
if(!nodeName || !*nodeName) nodeName = devName;
uint64_t serial_id{info->id};
#ifdef PW_KEY_OBJECT_SERIAL
if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)})
{
char *serial_end{};
serial_id = std::strtoull(serial_str, &serial_end, 0);
if(*serial_end != '\0' || errno == ERANGE)
{
ERR("Unexpected object serial: %s\n", serial_str);
serial_id = info->id;
}
}
#endif
const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)",
form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
TRACE(" \"%s\" = ID %u\n", nodeName ? nodeName : "(nil)", info->id);
TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id);
DeviceNode &node = DeviceNode::Add(info->id);
node.mSerial = serial_id;
if(nodeName && *nodeName) node.mName = nodeName;
else node.mName = "PipeWire node #"+std::to_string(info->id);
node.mDevName = devName ? devName : "";
@ -843,7 +919,7 @@ void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_po
if(id == SPA_PARAM_EnumFormat)
{
DeviceNode *node{DeviceNode::Find(mId)};
if(unlikely(!node)) return;
if(!node) UNLIKELY return;
if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
node->parseSampleRate(&prop->value);
@ -1048,6 +1124,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t
/* Specifically, audio sinks and sources (and duplexes). */
const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0
|| al::strcasecmp(media_class, AudioSourceClass) == 0
|| al::strcasecmp(media_class, AudioSourceVirtualClass) == 0
|| al::strcasecmp(media_class, AudioDuplexClass) == 0};
if(!isGood)
{
@ -1181,6 +1258,8 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u
break;
case DevFmtX61: map = X61Map; break;
case DevFmtX71: map = X71Map; break;
case DevFmtX714: map = X714Map; break;
case DevFmtX3D71: map = X71Map; break;
case DevFmtAmbi3D:
info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
info.channels = device->channelsFromFmt();
@ -1215,7 +1294,7 @@ class PipeWirePlayback final : public BackendBase {
void stop() override;
ClockLatency getClockLatency() override;
uint32_t mTargetId{PwIdAny};
uint64_t mTargetId{PwIdAny};
nanoseconds mTimeBase{0};
ThreadMainloop mLoop;
PwContextPtr mContext;
@ -1265,34 +1344,44 @@ void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size)
void PipeWirePlayback::outputCallback()
{
pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
if(unlikely(!pw_buf)) return;
if(!pw_buf) UNLIKELY return;
const al::span<spa_data> datas{pw_buf->buffer->datas,
minu(mNumChannels, pw_buf->buffer->n_datas)};
#if PW_CHECK_VERSION(0,3,49)
/* In 0.3.49, pw_buffer::requested specifies the number of samples needed
* by the resampler/graph for this audio update.
*/
uint length{static_cast<uint>(pw_buf->requested)};
#else
/* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number
* of samples per update.
*/
uint length{mRateMatch ? mRateMatch->size : 0u};
#endif
/* If no length is specified, use the device's update size as a fallback. */
if(!length) UNLIKELY length = mDevice->UpdateSize;
/* For planar formats, each datas[] seems to contain one channel, so store
* the pointers in an array. Limit the render length in case the available
* buffer length in any one channel is smaller than we wanted (shouldn't
* be, but just in case).
*/
spa_data *datas{pw_buf->buffer->datas};
const size_t chancount{minu(mNumChannels, pw_buf->buffer->n_datas)};
/* TODO: How many samples should actually be written? 'maxsize' can be 16k
* samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains
* a 'size' field that apparently indicates how many samples should be
* written per update, but it's not obviously right.
*/
uint length{mRateMatch ? mRateMatch->size : mDevice->UpdateSize};
for(size_t i{0};i < chancount;++i)
float **chanptr_end{mChannelPtrs.get()};
for(const auto &data : datas)
{
length = minu(length, datas[i].maxsize/sizeof(float));
mChannelPtrs[i] = static_cast<float*>(datas[i].data);
length = minu(length, data.maxsize/sizeof(float));
*chanptr_end = static_cast<float*>(data.data);
++chanptr_end;
}
mDevice->renderSamples({mChannelPtrs.get(), chancount}, length);
mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length);
for(size_t i{0};i < chancount;++i)
for(const auto &data : datas)
{
datas[i].chunk->offset = 0;
datas[i].chunk->stride = sizeof(float);
datas[i].chunk->size = length * sizeof(float);
data.chunk->offset = 0;
data.chunk->stride = sizeof(float);
data.chunk->size = length * sizeof(float);
}
pw_buf->size = length;
pw_stream_queue_buffer(mStream.get(), pw_buf);
@ -1303,7 +1392,7 @@ void PipeWirePlayback::open(const char *name)
{
static std::atomic<uint> OpenCount{0};
uint32_t targetid{PwIdAny};
uint64_t targetid{PwIdAny};
std::string devname{};
gEventHandler.waitForInit();
if(!name)
@ -1328,7 +1417,7 @@ void PipeWirePlayback::open(const char *name)
"No PipeWire playback device found"};
}
targetid = match->mId;
targetid = match->mSerial;
devname = match->mName;
}
else
@ -1343,7 +1432,7 @@ void PipeWirePlayback::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
targetid = match->mId;
targetid = match->mSerial;
devname = match->mName;
}
@ -1408,7 +1497,7 @@ bool PipeWirePlayback::reset()
auto&& devlist = DeviceNode::GetList();
auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
{ return targetid == n.mId; };
{ return targetid == n.mSerial; };
auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
if(match != devlist.cend())
{
@ -1416,10 +1505,11 @@ bool PipeWirePlayback::reset()
{
/* Scale the update size if the sample rate changes. */
const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency};
const double numbufs{static_cast<double>(mDevice->BufferSize)/mDevice->UpdateSize};
mDevice->Frequency = match->mSampleRate;
mDevice->UpdateSize = static_cast<uint>(clampd(mDevice->UpdateSize*scale + 0.5,
64.0, 8192.0));
mDevice->BufferSize = mDevice->UpdateSize * 2;
mDevice->BufferSize = static_cast<uint>(numbufs*mDevice->UpdateSize + 0.5);
}
if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
mDevice->FmtChans = match->mChannels;
@ -1445,7 +1535,13 @@ bool PipeWirePlayback::reset()
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set PipeWire audio format parameters"};
pw_properties *props{pw_properties_new(
/* TODO: Which properties are actually needed here? Any others that could
* be useful?
*/
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname,
PW_KEY_NODE_DESCRIPTION, appname,
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Playback",
PW_KEY_MEDIA_ROLE, "Game",
@ -1455,16 +1551,14 @@ bool PipeWirePlayback::reset()
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire stream properties (errno: %d)", errno};
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
/* TODO: Which properties are actually needed here? Any others that could
* be useful?
*/
pw_properties_set(props, PW_KEY_NODE_NAME, appname);
pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname);
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
mDevice->Frequency);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
#ifdef PW_KEY_TARGET_OBJECT
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
#else
pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
#endif
MainloopUniqueLock plock{mLoop};
/* The stream takes overship of 'props', even in the case of failure. */
@ -1475,28 +1569,31 @@ bool PipeWirePlayback::reset()
static constexpr pw_stream_events streamEvents{CreateEvents()};
pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, mTargetId, Flags, &params, 1)})
pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS};
if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pipewire", "rt-mix", true))
flags |= PW_STREAM_FLAG_RT_PROCESS;
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, &params, 1)})
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream (res: %d)", res};
/* Wait for the stream to become paused (ready to start streaming). */
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream: \"%s\"", error};
return state == PW_STREAM_STATE_PAUSED;
});
/* TODO: Update mDevice->BufferSize with the total known buffering delay
* from the head of this playback stream to the tail of the device output.
/* TODO: Update mDevice->UpdateSize with the stream's quantum, and
* mDevice->BufferSize with the total known buffering delay from the head
* of this playback stream to the tail of the device output.
*
* This info is apparently not available until after the stream starts.
*/
mDevice->BufferSize = mDevice->UpdateSize * 2;
plock.unlock();
mNumChannels = mDevice->channelsFromFmt();
@ -1517,22 +1614,72 @@ void PipeWirePlayback::start()
/* Wait for the stream to start playing (would be nice to not, but we need
* the actual update size which is only available after starting).
*/
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
return state != PW_STREAM_STATE_PAUSED;
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
return state == PW_STREAM_STATE_STREAMING;
});
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
if(state == PW_STREAM_STATE_STREAMING && mRateMatch && mRateMatch->size)
{
mDevice->UpdateSize = mRateMatch->size;
mDevice->BufferSize = mDevice->UpdateSize * 2;
}
/* HACK: Try to work out the update size and total buffering size. There's
* no actual query for this, so we have to work it out from the stream time
* info, and assume it stays accurate with future updates. The stream time
* info may also not be available right away, so we have to wait until it
* is (up to about 2 seconds).
*/
int wait_count{100};
do {
pw_time ptime{};
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
{
ERR("Failed to get PipeWire stream time (res: %d)\n", res);
break;
}
/* The rate match size is the update size for each buffer. */
const uint updatesize{mRateMatch ? mRateMatch->size : 0u};
#if PW_CHECK_VERSION(0,3,50)
/* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer
* queue size.
*/
if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0)
{
const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers};
/* Ensure the delay is in sample frames. */
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
ptime.rate.num / ptime.rate.denom};
mDevice->UpdateSize = updatesize;
mDevice->BufferSize = static_cast<uint>(ptime.buffered + delay +
totalbuffers*updatesize);
break;
}
#else
/* Prior to 0.3.50, we can only measure the delay with the update size,
* assuming one buffer and no resample buffering.
*/
if(ptime.rate.denom > 0 && updatesize > 0)
{
/* Ensure the delay is in sample frames. */
const uint64_t delay{static_cast<uint64_t>(ptime.delay) * mDevice->Frequency *
ptime.rate.num / ptime.rate.denom};
mDevice->UpdateSize = updatesize;
mDevice->BufferSize = static_cast<uint>(delay + updatesize);
break;
}
#endif
if(!--wait_count)
break;
plock.unlock();
std::this_thread::sleep_for(milliseconds{20});
plock.lock();
} while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING);
}
void PipeWirePlayback::stop()
@ -1560,7 +1707,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
if(mStream)
{
MainloopLockGuard _{mLoop};
if(int res{pw_stream_get_time(mStream.get(), &ptime)})
if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))})
ERR("Failed to get PipeWire stream time (res: %d)\n", res);
}
@ -1582,7 +1729,7 @@ ClockLatency PipeWirePlayback::getClockLatency()
*/
nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
nanoseconds curtic{}, delay{};
if(unlikely(ptime.rate.denom < 1))
if(ptime.rate.denom < 1) UNLIKELY
{
/* If there's no stream rate, the stream hasn't had a chance to get
* going and return time info yet. Just use dummy values.
@ -1648,7 +1795,7 @@ class PipeWireCapture final : public BackendBase {
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
uint32_t mTargetId{PwIdAny};
uint64_t mTargetId{PwIdAny};
ThreadMainloop mLoop;
PwContextPtr mContext;
PwCorePtr mCore;
@ -1680,7 +1827,7 @@ void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, con
void PipeWireCapture::inputCallback()
{
pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
if(unlikely(!pw_buf)) return;
if(!pw_buf) UNLIKELY return;
spa_data *bufdata{pw_buf->buffer->datas};
const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)};
@ -1696,7 +1843,7 @@ void PipeWireCapture::open(const char *name)
{
static std::atomic<uint> OpenCount{0};
uint32_t targetid{PwIdAny};
uint64_t targetid{PwIdAny};
std::string devname{};
gEventHandler.waitForInit();
if(!name)
@ -1725,7 +1872,7 @@ void PipeWireCapture::open(const char *name)
"No PipeWire capture device found"};
}
targetid = match->mId;
targetid = match->mSerial;
if(match->mType != NodeType::Sink) devname = match->mName;
else devname = MonitorPrefix+match->mName;
}
@ -1748,7 +1895,7 @@ void PipeWireCapture::open(const char *name)
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
targetid = match->mId;
targetid = match->mSerial;
devname = name;
}
@ -1798,7 +1945,7 @@ void PipeWireCapture::open(const char *name)
auto&& devlist = DeviceNode::GetList();
auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
{ return targetid == n.mId; };
{ return targetid == n.mSerial; };
auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
if(match != devlist.cend())
is51rear = match->mIs51Rear;
@ -1814,7 +1961,11 @@ void PipeWireCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set PipeWire audio format parameters"};
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties *props{pw_properties_new(
PW_KEY_NODE_NAME, appname,
PW_KEY_NODE_DESCRIPTION, appname,
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE, "Game",
@ -1824,10 +1975,6 @@ void PipeWireCapture::open(const char *name)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create PipeWire stream properties (errno: %d)", errno};
auto&& binary = GetProcBinary();
const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
pw_properties_set(props, PW_KEY_NODE_NAME, appname);
pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname);
/* We don't actually care what the latency/update size is, as long as it's
* reasonable. Unfortunately, when unspecified PipeWire seems to default to
* around 40ms, which isn't great. So request 20ms instead.
@ -1835,6 +1982,11 @@ void PipeWireCapture::open(const char *name)
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50,
mDevice->Frequency);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
#ifdef PW_KEY_TARGET_OBJECT
pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId);
#else
pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId);
#endif
MainloopUniqueLock plock{mLoop};
mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
@ -1846,16 +1998,15 @@ void PipeWireCapture::open(const char *name)
constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, mTargetId, Flags, params, 1)})
if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params, 1)})
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream (res: %d)", res};
/* Wait for the stream to become paused (ready to start streaming). */
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"Error connecting PipeWire stream: \"%s\"", error};
@ -1878,17 +2029,15 @@ void PipeWireCapture::start()
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start PipeWire stream (res: %d)", res};
pw_stream_state state{};
const char *error{};
plock.wait([stream=mStream.get(),&state,&error]()
plock.wait([stream=mStream.get()]()
{
state = pw_stream_get_state(stream, &error);
return state != PW_STREAM_STATE_PAUSED;
const char *error{};
pw_stream_state state{pw_stream_get_state(stream, &error)};
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
return state == PW_STREAM_STATE_STREAMING;
});
if(state == PW_STREAM_STATE_ERROR)
throw al::backend_exception{al::backend_error::DeviceError,
"PipeWire stream error: %s", error ? error : "(unknown)"};
}
void PipeWireCapture::stop()
@ -1916,6 +2065,15 @@ bool PipeWireBackendFactory::init()
if(!pwire_load())
return false;
const char *version{pw_get_library_version()};
if(!check_version(version))
{
WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version,
pw_get_headers_version());
return false;
}
TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version());
pw_init(0, nullptr);
if(!gEventHandler.init())
return false;

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,10 @@
#include "core/device.h"
#include "core/logging.h"
#include <SDL2/SDL.h>
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
#include "SDL.h"
_Pragma("GCC diagnostic pop")
namespace {

View file

@ -22,13 +22,13 @@
#include "sndio.h"
#include <functional>
#include <inttypes.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#include <functional>
#include "alnumeric.h"
#include "core/device.h"
@ -98,9 +98,9 @@ int SndioPlayback::mixerProc()
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
if(wrote == 0)
if(wrote > buffer.size() || wrote == 0)
{
ERR("sio_write failed\n");
ERR("sio_write failed: 0x%" PRIx64 "\n", wrote);
mDevice->handleDisconnect("Failed to write playback samples");
break;
}
@ -353,7 +353,14 @@ int SndioCapture::recordProc()
while(!buffer.empty())
{
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
if(got == 0) break;
if(got == 0)
break;
if(got > buffer.size())
{
ERR("sio_read failed: 0x%" PRIx64 "\n", got);
mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got);
break;
}
mRing->writeAdvance(got / frameSize);
buffer = buffer.subspan(got);

View file

@ -57,6 +57,7 @@
#include <vector>
#include "albit.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "comptr.h"
#include "core/converter.h"
@ -86,6 +87,7 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x
namespace {
using std::chrono::nanoseconds;
using std::chrono::milliseconds;
using std::chrono::seconds;
@ -102,6 +104,7 @@ inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) no
#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
{
@ -119,6 +122,7 @@ constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
constexpr char DevNameHead[] = "OpenAL Soft on ";
constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1};
@ -422,7 +426,6 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format)
enum class MsgType {
OpenDevice,
ReopenDevice,
ResetDevice,
StartDevice,
StopDevice,
@ -436,7 +439,6 @@ enum class MsgType {
constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
"Open Device",
"Reopen Device",
"Reset Device",
"Start Device",
"Stop Device",
@ -465,9 +467,12 @@ struct WasapiProxy {
explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
};
static std::thread sThread;
static std::deque<Msg> mMsgQueue;
static std::mutex mMsgQueueLock;
static std::condition_variable mMsgQueueCond;
static std::mutex sThreadLock;
static size_t sInitCount;
std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr)
{
@ -503,42 +508,60 @@ struct WasapiProxy {
}
static int messageHandler(std::promise<HRESULT> *promise);
static HRESULT InitThread()
{
std::lock_guard<std::mutex> _{sThreadLock};
HRESULT res{S_OK};
if(!sThread.joinable())
{
std::promise<HRESULT> promise;
auto future = promise.get_future();
sThread = std::thread{&WasapiProxy::messageHandler, &promise};
res = future.get();
if(FAILED(res))
{
sThread.join();
return res;
}
}
++sInitCount;
return res;
}
static void DeinitThread()
{
std::lock_guard<std::mutex> _{sThreadLock};
if(!--sInitCount && sThread.joinable())
{
pushMessageStatic(MsgType::QuitThread);
sThread.join();
}
}
};
std::thread WasapiProxy::sThread;
std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue;
std::mutex WasapiProxy::mMsgQueueLock;
std::condition_variable WasapiProxy::mMsgQueueCond;
std::mutex WasapiProxy::sThreadLock;
size_t WasapiProxy::sInitCount{0};
int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
{
TRACE("Starting message thread\n");
HRESULT cohr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(cohr))
{
WARN("Failed to initialize COM: 0x%08lx\n", cohr);
promise->set_value(cohr);
return 0;
}
void *ptr{};
HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, &ptr)};
HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(hr))
{
WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
WARN("Failed to initialize COM: 0x%08lx\n", hr);
promise->set_value(hr);
CoUninitialize();
return 0;
}
static_cast<IMMDeviceEnumerator*>(ptr)->Release();
CoUninitialize();
TRACE("Message thread initialization complete\n");
promise->set_value(S_OK);
promise = nullptr;
TRACE("Starting message loop\n");
uint deviceCount{0};
while(Msg msg{popMessage()})
{
TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n",
@ -548,21 +571,6 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
switch(msg.mType)
{
case MsgType::OpenDevice:
hr = cohr = S_OK;
if(++deviceCount == 1)
hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if(SUCCEEDED(hr))
hr = msg.mProxy->openProxy(msg.mParam);
msg.mPromise.set_value(hr);
if(FAILED(hr))
{
if(--deviceCount == 0 && SUCCEEDED(cohr))
CoUninitialize();
}
continue;
case MsgType::ReopenDevice:
hr = msg.mProxy->openProxy(msg.mParam);
msg.mPromise.set_value(hr);
continue;
@ -585,36 +593,29 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
case MsgType::CloseDevice:
msg.mProxy->closeProxy();
msg.mPromise.set_value(S_OK);
if(--deviceCount == 0)
CoUninitialize();
continue;
case MsgType::EnumeratePlayback:
case MsgType::EnumerateCapture:
hr = cohr = S_OK;
if(++deviceCount == 1)
hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if(SUCCEEDED(hr))
{
void *ptr{};
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, &ptr);
if(FAILED(hr))
msg.mPromise.set_value(hr);
else
{
ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
if(FAILED(hr))
msg.mPromise.set_value(hr);
else
{
ComPtr<IMMDeviceEnumerator> devenum{static_cast<IMMDeviceEnumerator*>(ptr)};
if(msg.mType == MsgType::EnumeratePlayback)
probe_devices(enumerator.get(), eRender, PlaybackDevices);
else if(msg.mType == MsgType::EnumerateCapture)
probe_devices(enumerator.get(), eCapture, CaptureDevices);
msg.mPromise.set_value(S_OK);
if(msg.mType == MsgType::EnumeratePlayback)
probe_devices(devenum.get(), eRender, PlaybackDevices);
else if(msg.mType == MsgType::EnumerateCapture)
probe_devices(devenum.get(), eCapture, CaptureDevices);
msg.mPromise.set_value(S_OK);
}
continue;
}
if(--deviceCount == 0 && SUCCEEDED(cohr))
CoUninitialize();
continue;
case MsgType::QuitThread:
break;
}
@ -622,6 +623,7 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
msg.mPromise.set_value(E_FAIL);
}
TRACE("Message loop finished\n");
CoUninitialize();
return 0;
}
@ -652,7 +654,12 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy {
ComPtr<IAudioRenderClient> mRender{nullptr};
HANDLE mNotifyEvent{nullptr};
UINT32 mFrameStep{0u};
UINT32 mOrigBufferSize{}, mOrigUpdateSize{};
std::unique_ptr<char[]> mResampleBuffer{};
uint mBufferFilled{0};
SampleConverterPtr mResampler;
WAVEFORMATEXTENSIBLE mFormat{};
std::atomic<UINT32> mPadding{0u};
std::mutex mMutex;
@ -666,7 +673,10 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy {
WasapiPlayback::~WasapiPlayback()
{
if(SUCCEEDED(mOpenStatus))
{
pushMessage(MsgType::CloseDevice).wait();
DeinitThread();
}
mOpenStatus = E_FAIL;
if(mNotifyEvent != nullptr)
@ -688,8 +698,9 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const uint update_size{mDevice->UpdateSize};
const UINT32 buffer_len{mDevice->BufferSize};
const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u};
const uint update_size{mOrigUpdateSize};
const UINT32 buffer_len{mOrigBufferSize};
while(!mKillNow.load(std::memory_order_relaxed))
{
UINT32 written;
@ -715,9 +726,37 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
hr = mRender->GetBuffer(len, &buffer);
if(SUCCEEDED(hr))
{
if(mResampler)
{
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(buffer, len, mFrameStep);
for(UINT32 done{0};done < len;)
{
if(mBufferFilled == 0)
{
mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize,
mFormat.Format.nChannels);
mBufferFilled = mDevice->UpdateSize;
}
const void *src{mResampleBuffer.get()};
uint srclen{mBufferFilled};
uint got{mResampler->convert(&src, &srclen, buffer, len-done)};
buffer += got*frame_size;
done += got;
mPadding.store(written + done, std::memory_order_relaxed);
if(srclen)
{
const char *bsrc{static_cast<const char*>(src)};
std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get());
}
mBufferFilled = srclen;
}
}
else
{
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
mPadding.store(written + len, std::memory_order_relaxed);
}
hr = mRender->ReleaseBuffer(len, 0);
@ -738,44 +777,44 @@ FORCE_ALIGN int WasapiPlayback::mixerProc()
void WasapiPlayback::open(const char *name)
{
HRESULT hr{S_OK};
if(SUCCEEDED(mOpenStatus))
throw al::backend_exception{al::backend_error::DeviceError,
"Unexpected duplicate open call"};
if(!mNotifyEvent)
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
ERR("Failed to create notify events: %lu\n", GetLastError());
hr = E_FAIL;
}
}
if(SUCCEEDED(hr))
{
if(name)
{
if(PlaybackDevices.empty())
pushMessage(MsgType::EnumeratePlayback);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
if(SUCCEEDED(mOpenStatus))
hr = pushMessage(MsgType::ReopenDevice, name).get();
else
{
hr = pushMessage(MsgType::OpenDevice, name).get();
mOpenStatus = hr;
}
ERR("Failed to create notify events: %lu\n", GetLastError());
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create notify events"};
}
HRESULT hr{InitThread()};
if(FAILED(hr))
{
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to init COM thread: 0x%08lx", hr};
}
if(name)
{
if(PlaybackDevices.empty())
pushMessage(MsgType::EnumeratePlayback);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
if(FAILED(mOpenStatus))
{
DeinitThread();
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
mOpenStatus};
}
}
HRESULT WasapiPlayback::openProxy(const char *name)
@ -863,6 +902,7 @@ HRESULT WasapiPlayback::resetProxy()
ERR("Failed to get mix format: 0x%08lx\n", hr);
return hr;
}
TraceFormat("Device mix format", wfx);
WAVEFORMATEXTENSIBLE OutputType;
if(!MakeExtensible(&OutputType, wfx))
@ -875,29 +915,46 @@ HRESULT WasapiPlayback::resetProxy()
const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
bool isRear51{false};
if(!mDevice->Flags.test(FrequencyRequest))
mDevice->Frequency = OutputType.Format.nSamplesPerSec;
if(!mDevice->Flags.test(ChannelsRequest))
{
/* If not requesting a channel configuration, auto-select given what
* fits the mask's lsb (to ensure no gaps in the output channels). If
* there's no mask, we can only assume mono or stereo.
*/
const uint32_t chancount{OutputType.Format.nChannels};
const DWORD chanmask{OutputType.dwChannelMask};
if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
mDevice->FmtChans = DevFmtX71;
else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
mDevice->FmtChans = DevFmtX71;
else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
mDevice->FmtChans = DevFmtX61;
else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
|| (chanmask&X51RearMask) == X5DOT1REAR))
else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
mDevice->FmtChans = DevFmtX51;
else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
{
mDevice->FmtChans = DevFmtX51;
isRear51 = true;
}
else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
mDevice->FmtChans = DevFmtStereo;
else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
mDevice->FmtChans = DevFmtMono;
else
ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
}
else
{
const uint32_t chancount{OutputType.Format.nChannels};
const DWORD chanmask{OutputType.dwChannelMask};
isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
}
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
switch(mDevice->FmtChans)
@ -919,16 +976,21 @@ HRESULT WasapiPlayback::resetProxy()
break;
case DevFmtX51:
OutputType.Format.nChannels = 6;
OutputType.dwChannelMask = X5DOT1;
OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
break;
case DevFmtX61:
OutputType.Format.nChannels = 7;
OutputType.dwChannelMask = X6DOT1;
break;
case DevFmtX71:
case DevFmtX3D71:
OutputType.Format.nChannels = 8;
OutputType.dwChannelMask = X7DOT1;
break;
case DevFmtX714:
OutputType.Format.nChannels = 12;
OutputType.dwChannelMask = X7DOT1DOT4;
break;
}
switch(mDevice->FmtType)
{
@ -973,7 +1035,7 @@ HRESULT WasapiPlayback::resetProxy()
hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
if(FAILED(hr))
{
ERR("Failed to check format support: 0x%08lx\n", hr);
WARN("Failed to check format support: 0x%08lx\n", hr);
hr = mClient->GetMixFormat(&wfx);
}
if(FAILED(hr))
@ -993,7 +1055,11 @@ HRESULT WasapiPlayback::resetProxy()
CoTaskMemFree(wfx);
wfx = nullptr;
mDevice->Frequency = OutputType.Format.nSamplesPerSec;
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true))
mDevice->Frequency = OutputType.Format.nSamplesPerSec;
else
mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec);
const uint32_t chancount{OutputType.Format.nChannels};
const DWORD chanmask{OutputType.dwChannelMask};
/* Don't update the channel format if the requested format fits what's
@ -1002,34 +1068,43 @@ HRESULT WasapiPlayback::resetProxy()
bool chansok{false};
if(mDevice->Flags.test(ChannelsRequest))
{
/* When requesting a channel configuration, make sure it fits the
* mask's lsb (to ensure no gaps in the output channels). If
* there's no mask, assume the request fits with enough channels.
*/
switch(mDevice->FmtChans)
{
case DevFmtMono:
chansok = (chancount >= 1 && (chanmask&MonoMask) == MONO);
chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
break;
case DevFmtStereo:
chansok = (chancount >= 2 && (chanmask&StereoMask) == STEREO);
chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
break;
case DevFmtQuad:
chansok = (chancount >= 4 && (chanmask&QuadMask) == QUAD);
chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
break;
case DevFmtX51:
chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
|| (chanmask&X51RearMask) == X5DOT1REAR));
|| (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
break;
case DevFmtX61:
chansok = (chancount >= 7 && (chanmask&X61Mask) == X6DOT1);
chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
break;
case DevFmtX71:
chansok = (chancount >= 8 && (chanmask&X71Mask) == X7DOT1);
case DevFmtX3D71:
chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
break;
case DevFmtX714:
chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
case DevFmtAmbi3D:
break;
}
}
if(!chansok)
{
if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
mDevice->FmtChans = DevFmtX714;
else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
mDevice->FmtChans = DevFmtX71;
else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
mDevice->FmtChans = DevFmtX61;
@ -1038,9 +1113,9 @@ HRESULT WasapiPlayback::resetProxy()
mDevice->FmtChans = DevFmtX51;
else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
mDevice->FmtChans = DevFmtStereo;
else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
mDevice->FmtChans = DevFmtMono;
else
{
@ -1082,12 +1157,12 @@ HRESULT WasapiPlayback::resetProxy()
}
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
}
mFrameStep = OutputType.Format.nChannels;
mFormat = OutputType;
const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())};
mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
setChannelOrderFromWFXMask(OutputType.dwChannelMask);
setDefaultWFXChannelOrder();
hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buf_time.count(), 0, &OutputType.Format, nullptr);
@ -1111,8 +1186,31 @@ HRESULT WasapiPlayback::resetProxy()
/* Find the nearest multiple of the period size to the update size */
if(min_per < per_time)
min_per *= maxi64((per_time + min_per/2) / min_per, 1);
mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), buffer_len/2);
mDevice->BufferSize = buffer_len;
mOrigBufferSize = buffer_len;
mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2);
mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
mFormat.Format.nSamplesPerSec);
mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency),
mDevice->BufferSize/2);
mResampler = nullptr;
mResampleBuffer = nullptr;
mBufferFilled = 0;
if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
{
mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
Resampler::FastBSinc24);
mResampleBuffer = std::make_unique<char[]>(size_t{mDevice->UpdateSize} *
mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8);
TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
mDevice->UpdateSize);
}
hr = mClient->SetEventHandle(mNotifyEvent);
if(FAILED(hr))
@ -1189,8 +1287,14 @@ ClockLatency WasapiPlayback::getClockLatency()
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)};
ret.Latency /= mDevice->Frequency;
ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
ret.Latency /= mFormat.Format.nSamplesPerSec;
if(mResampler)
{
auto extra = mResampler->currentInputDelay();
ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
}
return ret;
}
@ -1234,7 +1338,10 @@ struct WasapiCapture final : public BackendBase, WasapiProxy {
WasapiCapture::~WasapiCapture()
{
if(SUCCEEDED(mOpenStatus))
{
pushMessage(MsgType::CloseDevice).wait();
DeinitThread();
}
mOpenStatus = E_FAIL;
if(mNotifyEvent != nullptr)
@ -1337,35 +1444,44 @@ FORCE_ALIGN int WasapiCapture::recordProc()
void WasapiCapture::open(const char *name)
{
HRESULT hr{S_OK};
if(SUCCEEDED(mOpenStatus))
throw al::backend_exception{al::backend_error::DeviceError,
"Unexpected duplicate open call"};
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(mNotifyEvent == nullptr)
{
ERR("Failed to create notify event: %lu\n", GetLastError());
hr = E_FAIL;
ERR("Failed to create notify events: %lu\n", GetLastError());
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to create notify events"};
}
if(SUCCEEDED(hr))
{
if(name)
{
if(CaptureDevices.empty())
pushMessage(MsgType::EnumerateCapture);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
hr = pushMessage(MsgType::OpenDevice, name).get();
}
mOpenStatus = hr;
HRESULT hr{InitThread()};
if(FAILED(hr))
{
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to init COM thread: 0x%08lx", hr};
}
if(name)
{
if(CaptureDevices.empty())
pushMessage(MsgType::EnumerateCapture);
if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
{
name += DevNameHeadLen;
if(*name == '\0')
name = nullptr;
}
}
mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
if(FAILED(mOpenStatus))
{
DeinitThread();
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
mOpenStatus};
}
hr = pushMessage(MsgType::ResetDevice).get();
if(FAILED(hr))
@ -1443,11 +1559,32 @@ HRESULT WasapiCapture::resetProxy()
}
mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
WAVEFORMATEX *wfx;
hr = mClient->GetMixFormat(&wfx);
if(FAILED(hr))
{
ERR("Failed to get capture format: 0x%08lx\n", hr);
return hr;
}
TraceFormat("Device capture format", wfx);
WAVEFORMATEXTENSIBLE InputType{};
if(!MakeExtensible(&InputType, wfx))
{
CoTaskMemFree(wfx);
return E_FAIL;
}
CoTaskMemFree(wfx);
wfx = nullptr;
const bool isRear51{InputType.Format.nChannels == 6
&& (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
// Make sure buffer is at least 100ms in size
ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
WAVEFORMATEXTENSIBLE InputType{};
InputType = {};
InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
switch(mDevice->FmtChans)
{
@ -1465,7 +1602,7 @@ HRESULT WasapiCapture::resetProxy()
break;
case DevFmtX51:
InputType.Format.nChannels = 6;
InputType.dwChannelMask = X5DOT1;
InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
break;
case DevFmtX61:
InputType.Format.nChannels = 7;
@ -1475,7 +1612,12 @@ HRESULT WasapiCapture::resetProxy()
InputType.Format.nChannels = 8;
InputType.dwChannelMask = X7DOT1;
break;
case DevFmtX714:
InputType.Format.nChannels = 12;
InputType.dwChannelMask = X7DOT1DOT4;
break;
case DevFmtX3D71:
case DevFmtAmbi3D:
return E_FAIL;
}
@ -1512,11 +1654,15 @@ HRESULT WasapiCapture::resetProxy()
InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
TraceFormat("Requesting capture format", &InputType.Format);
WAVEFORMATEX *wfx;
hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
if(FAILED(hr))
{
ERR("Failed to check format support: 0x%08lx\n", hr);
WARN("Failed to check capture format support: 0x%08lx\n", hr);
hr = mClient->GetMixFormat(&wfx);
}
if(FAILED(hr))
{
ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
return hr;
}
@ -1556,7 +1702,10 @@ HRESULT WasapiCapture::resetProxy()
case DevFmtX61:
return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
case DevFmtX71:
case DevFmtX3D71:
return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
case DevFmtX714:
return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
case DevFmtAmbi3D:
return (chanmask == 0 && chancount == device->channelsFromFmt());
}
@ -1632,8 +1781,9 @@ HRESULT WasapiCapture::resetProxy()
if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
{
mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(),
InputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24);
mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
Resampler::FastBSinc24);
if(!mSampleConv)
{
ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
@ -1757,11 +1907,31 @@ bool WasapiBackendFactory::init()
if(FAILED(InitResult)) try
{
std::promise<HRESULT> promise;
auto future = promise.get_future();
auto res = std::async(std::launch::async, []() -> HRESULT
{
HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
if(FAILED(hr))
{
WARN("Failed to initialize COM: 0x%08lx\n", hr);
return hr;
}
std::thread{&WasapiProxy::messageHandler, &promise}.detach();
InitResult = future.get();
void *ptr{};
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, &ptr);
if(FAILED(hr))
{
WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
CoUninitialize();
return hr;
}
static_cast<IMMDeviceEnumerator*>(ptr)->Release();
CoUninitialize();
return S_OK;
});
InitResult = res.get();
}
catch(...) {
}
@ -1774,7 +1944,17 @@ bool WasapiBackendFactory::querySupport(BackendType type)
std::string WasapiBackendFactory::probe(BackendType type)
{
struct ProxyControl {
HRESULT mResult{};
ProxyControl() { mResult = WasapiProxy::InitThread(); }
~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); }
};
ProxyControl proxy;
std::string outnames;
if(FAILED(proxy.mResult))
return outnames;
switch(type)
{
case BackendType::Playback:

View file

@ -234,7 +234,7 @@ bool WaveBackend::reset()
fseek(mFile, 0, SEEK_SET);
clearerr(mFile);
if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
if(GetConfigValueBool(nullptr, "wave", "bformat", false))
{
mDevice->FmtChans = DevFmtAmbi3D;
mDevice->mAmbiOrder = 1;
@ -265,6 +265,12 @@ bool WaveBackend::reset()
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtX714:
chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
| 0x8000 | 0x20000;
break;
/* NOTE: Same as 7.1. */
case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);

View file

@ -301,23 +301,16 @@ bool WinMMPlayback::reset()
return false;
}
uint chanmask{};
if(mFormat.nChannels >= 2)
{
mDevice->FmtChans = DevFmtStereo;
chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
}
else if(mFormat.nChannels == 1)
{
mDevice->FmtChans = DevFmtMono;
chanmask = SPEAKER_FRONT_CENTER;
}
else
{
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
return false;
}
setChannelOrderFromWFXMask(chanmask);
setDefaultWFXChannelOrder();
uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
@ -476,6 +469,8 @@ void WinMMCapture::open(const char *name)
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};

File diff suppressed because it is too large Load diff

View file

@ -20,41 +20,11 @@
#include "vector.h"
#ifdef ALSOFT_EAX
#include "al/eax_eax_call.h"
#include "al/eax_fx_slot_index.h"
#include "al/eax_fx_slots.h"
#include "al/eax_utils.h"
using EaxContextSharedDirtyFlagsValue = std::uint_least8_t;
struct EaxContextSharedDirtyFlags
{
using EaxIsBitFieldStruct = bool;
EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1;
}; // EaxContextSharedDirtyFlags
using ContextDirtyFlagsValue = std::uint_least8_t;
struct ContextDirtyFlags
{
using EaxIsBitFieldStruct = bool;
ContextDirtyFlagsValue guidPrimaryFXSlotID : 1;
ContextDirtyFlagsValue flDistanceFactor : 1;
ContextDirtyFlagsValue flAirAbsorptionHF : 1;
ContextDirtyFlagsValue flHFReference : 1;
ContextDirtyFlagsValue flMacroFXFactor : 1;
}; // ContextDirtyFlags
struct EaxAlIsExtensionPresentResult
{
ALboolean is_present;
bool is_return;
}; // EaxAlIsExtensionPresentResult
#include "al/eax/call.h"
#include "al/eax/exception.h"
#include "al/eax/fx_slot_index.h"
#include "al/eax/fx_slots.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
struct ALeffect;
@ -98,9 +68,6 @@ struct EffectSlotSubList {
struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
const al::intrusive_ptr<ALCdevice> mALDevice;
/* Wet buffers used by effect slots. */
al::vector<WetBufferPtr> mWetBuffers;
bool mPropsDirty{true};
bool mDeferUpdates{false};
@ -136,6 +103,8 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
const char *mExtensionList{nullptr};
std::string mExtensionListOverride{};
ALCcontext(al::intrusive_ptr<ALCdevice> device);
ALCcontext(const ALCcontext&) = delete;
@ -181,6 +150,7 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
void setError(ALenum errorCode, const char *msg, ...);
/* Process-wide current context */
static std::atomic<bool> sGlobalContextLock;
static std::atomic<ALCcontext*> sGlobalContext;
private:
@ -217,13 +187,10 @@ public:
#ifdef ALSOFT_EAX
public:
bool has_eax() const noexcept { return eax_is_initialized_; }
bool eax_is_capable() const noexcept;
void eax_uninitialize() noexcept;
bool hasEax() const noexcept { return mEaxIsInitialized; }
bool eaxIsCapable() const noexcept;
void eaxUninitialize() noexcept;
ALenum eax_eax_set(
const GUID* property_set_id,
@ -239,242 +206,311 @@ public:
ALvoid* property_value,
ALuint property_value_size);
void eaxSetLastError() noexcept;
void eax_update_filters();
EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept
{ return mEaxPrimaryFxSlotIndex; }
void eax_commit_and_update_sources();
const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const
{ return mEaxFxSlots.get(fx_slot_index); }
ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index)
{ return mEaxFxSlots.get(fx_slot_index); }
bool eaxNeedsCommit() const noexcept { return mEaxNeedsCommit; }
void eaxCommit();
void eax_set_last_error() noexcept;
EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept
{ return eax_previous_primary_fx_slot_index_; }
EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept
{ return eax_primary_fx_slot_index_; }
const ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) const
{ return eax_fx_slots_.get(fx_slot_index); }
ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index)
{ return eax_fx_slots_.get(fx_slot_index); }
void eax_commit_fx_slots()
{ eax_fx_slots_.commit(); }
void eaxCommitFxSlots()
{ mEaxFxSlots.commit(); }
private:
struct Eax
static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0;
static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1;
static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2;
static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3;
static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4;
using Eax4Props = EAX40CONTEXTPROPERTIES;
struct Eax4State {
Eax4Props i; // Immediate.
Eax4Props d; // Deferred.
};
using Eax5Props = EAX50CONTEXTPROPERTIES;
struct Eax5State {
Eax5Props i; // Immediate.
Eax5Props d; // Deferred.
};
class ContextException : public EaxException
{
EAX50CONTEXTPROPERTIES context{};
}; // Eax
public:
explicit ContextException(const char* message)
: EaxException{"EAX_CONTEXT", message}
{}
};
struct Eax4PrimaryFxSlotIdValidator {
void operator()(const GUID& guidPrimaryFXSlotID) const
{
if(guidPrimaryFXSlotID != EAX_NULL_GUID &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot3)
{
eax_fail_unknown_primary_fx_slot_id();
}
}
};
bool eax_is_initialized_{};
bool eax_is_tried_{};
bool eax_are_legacy_fx_slots_unlocked_{};
struct Eax4DistanceFactorValidator {
void operator()(float flDistanceFactor) const
{
eax_validate_range<ContextException>(
"Distance Factor",
flDistanceFactor,
EAXCONTEXT_MINDISTANCEFACTOR,
EAXCONTEXT_MAXDISTANCEFACTOR);
}
};
long eax_last_error_{};
unsigned long eax_speaker_config_{};
struct Eax4AirAbsorptionHfValidator {
void operator()(float flAirAbsorptionHF) const
{
eax_validate_range<ContextException>(
"Air Absorption HF",
flAirAbsorptionHF,
EAXCONTEXT_MINAIRABSORPTIONHF,
EAXCONTEXT_MAXAIRABSORPTIONHF);
}
};
EaxFxSlotIndex eax_previous_primary_fx_slot_index_{};
EaxFxSlotIndex eax_primary_fx_slot_index_{};
EaxFxSlots eax_fx_slots_{};
struct Eax4HfReferenceValidator {
void operator()(float flHFReference) const
{
eax_validate_range<ContextException>(
"HF Reference",
flHFReference,
EAXCONTEXT_MINHFREFERENCE,
EAXCONTEXT_MAXHFREFERENCE);
}
};
EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{};
struct Eax4AllValidator {
void operator()(const EAX40CONTEXTPROPERTIES& all) const
{
Eax4PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID);
Eax4DistanceFactorValidator{}(all.flDistanceFactor);
Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF);
Eax4HfReferenceValidator{}(all.flHFReference);
}
};
Eax eax_{};
Eax eax_d_{};
EAXSESSIONPROPERTIES eax_session_{};
struct Eax5PrimaryFxSlotIdValidator {
void operator()(const GUID& guidPrimaryFXSlotID) const
{
if(guidPrimaryFXSlotID != EAX_NULL_GUID &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 &&
guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot3)
{
eax_fail_unknown_primary_fx_slot_id();
}
}
};
ContextDirtyFlags eax_context_dirty_flags_{};
struct Eax5MacroFxFactorValidator {
void operator()(float flMacroFXFactor) const
{
eax_validate_range<ContextException>(
"Macro FX Factor",
flMacroFXFactor,
EAXCONTEXT_MINMACROFXFACTOR,
EAXCONTEXT_MAXMACROFXFACTOR);
}
};
std::string eax_extension_list_{};
struct Eax5AllValidator {
void operator()(const EAX50CONTEXTPROPERTIES& all) const
{
Eax5PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID);
Eax4DistanceFactorValidator{}(all.flDistanceFactor);
Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF);
Eax4HfReferenceValidator{}(all.flHFReference);
Eax5MacroFxFactorValidator{}(all.flMacroFXFactor);
}
};
struct Eax5EaxVersionValidator {
void operator()(unsigned long ulEAXVersion) const
{
eax_validate_range<ContextException>(
"EAX version",
ulEAXVersion,
EAXCONTEXT_MINEAXSESSION,
EAXCONTEXT_MAXEAXSESSION);
}
};
[[noreturn]]
static void eax_fail(
const char* message);
struct Eax5MaxActiveSendsValidator {
void operator()(unsigned long ulMaxActiveSends) const
{
eax_validate_range<ContextException>(
"Max Active Sends",
ulMaxActiveSends,
EAXCONTEXT_MINMAXACTIVESENDS,
EAXCONTEXT_MAXMAXACTIVESENDS);
}
};
struct Eax5SessionAllValidator {
void operator()(const EAXSESSIONPROPERTIES& all) const
{
Eax5EaxVersionValidator{}(all.ulEAXVersion);
Eax5MaxActiveSendsValidator{}(all.ulMaxActiveSends);
}
};
struct Eax5SpeakerConfigValidator {
void operator()(unsigned long ulSpeakerConfig) const
{
eax_validate_range<ContextException>(
"Speaker Config",
ulSpeakerConfig,
EAXCONTEXT_MINSPEAKERCONFIG,
EAXCONTEXT_MAXSPEAKERCONFIG);
}
};
bool mEaxIsInitialized{};
bool mEaxIsTried{};
long mEaxLastError{};
unsigned long mEaxSpeakerConfig{};
EaxFxSlotIndex mEaxPrimaryFxSlotIndex{};
EaxFxSlots mEaxFxSlots{};
int mEaxVersion{}; // Current EAX version.
bool mEaxNeedsCommit{};
EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version.
Eax5State mEax123{}; // EAX1/EAX2/EAX3 state.
Eax4State mEax4{}; // EAX4 state.
Eax5State mEax5{}; // EAX5 state.
Eax5Props mEax{}; // Current EAX state.
EAXSESSIONPROPERTIES mEaxSession{};
[[noreturn]] static void eax_fail(const char* message);
[[noreturn]] static void eax_fail_unknown_property_set_id();
[[noreturn]] static void eax_fail_unknown_primary_fx_slot_id();
[[noreturn]] static void eax_fail_unknown_property_id();
[[noreturn]] static void eax_fail_unknown_version();
// Gets a value from EAX call,
// validates it,
// and updates the current value.
template<typename TValidator, typename TProperty>
static void eax_set(const EaxCall& call, TProperty& property)
{
const auto& value = call.get_value<ContextException, const TProperty>();
TValidator{}(value);
property = value;
}
// Gets a new value from EAX call,
// validates it,
// updates the deferred value,
// updates a dirty flag.
template<
typename TValidator,
EaxDirtyFlags TDirtyBit,
typename TMemberResult,
typename TProps,
typename TState>
void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) noexcept
{
const auto& src = call.get_value<ContextException, const TMemberResult>();
TValidator{}(src);
const auto& dst_i = state.i.*member;
auto& dst_d = state.d.*member;
dst_d = src;
if(dst_i != dst_d)
mEaxDf |= TDirtyBit;
}
template<
EaxDirtyFlags TDirtyBit,
typename TMemberResult,
typename TProps,
typename TState>
void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df,
TMemberResult TProps::*member) noexcept
{
if((mEaxDf & TDirtyBit) != EaxDirtyFlags{})
{
dst_df |= TDirtyBit;
const auto& src_d = state.d.*member;
state.i.*member = src_d;
mEax.*member = src_d;
}
}
void eax_initialize_extensions();
void eax_initialize();
bool eax_has_no_default_effect_slot() const noexcept;
void eax_ensure_no_default_effect_slot() const;
bool eax_has_enough_aux_sends() const noexcept;
void eax_ensure_enough_aux_sends() const;
void eax_ensure_compatibility();
unsigned long eax_detect_speaker_configuration() const;
void eax_update_speaker_configuration();
void eax_set_last_error_defaults() noexcept;
void eax_session_set_defaults() noexcept;
static void eax4_context_set_defaults(Eax4Props& props) noexcept;
static void eax4_context_set_defaults(Eax4State& state) noexcept;
static void eax5_context_set_defaults(Eax5Props& props) noexcept;
static void eax5_context_set_defaults(Eax5State& state) noexcept;
void eax_context_set_defaults();
void eax_set_defaults();
void eax_set_session_defaults() noexcept;
void eax_dispatch_fx_slot(const EaxCall& call);
void eax_dispatch_source(const EaxCall& call);
void eax_set_context_defaults() noexcept;
void eax_get_misc(const EaxCall& call);
void eax4_get(const EaxCall& call, const Eax4Props& props);
void eax5_get(const EaxCall& call, const Eax5Props& props);
void eax_get(const EaxCall& call);
void eax_set_defaults() noexcept;
void eax_initialize_sources();
void eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept;
void eax_dispatch_fx_slot(
const EaxEaxCall& eax_call);
void eax_dispatch_source(
const EaxEaxCall& eax_call);
void eax_get_primary_fx_slot_id(
const EaxEaxCall& eax_call);
void eax_get_distance_factor(
const EaxEaxCall& eax_call);
void eax_get_air_absorption_hf(
const EaxEaxCall& eax_call);
void eax_get_hf_reference(
const EaxEaxCall& eax_call);
void eax_get_last_error(
const EaxEaxCall& eax_call);
void eax_get_speaker_config(
const EaxEaxCall& eax_call);
void eax_get_session(
const EaxEaxCall& eax_call);
void eax_get_macro_fx_factor(
const EaxEaxCall& eax_call);
void eax_get_context_all(
const EaxEaxCall& eax_call);
void eax_get(
const EaxEaxCall& eax_call);
void eax_set_primary_fx_slot_id();
void eax_set_distance_factor();
void eax_set_air_absorbtion_hf();
void eax_set_hf_reference();
void eax_set_macro_fx_factor();
void eax_set_context();
void eax_context_commit_primary_fx_slot_id();
void eax_context_commit_distance_factor();
void eax_context_commit_air_absorbtion_hf();
void eax_context_commit_hf_reference();
void eax_context_commit_macro_fx_factor();
void eax_initialize_fx_slots();
void eax_update_sources();
void eax_set_misc(const EaxCall& call);
void eax4_defer_all(const EaxCall& call, Eax4State& state);
void eax4_defer(const EaxCall& call, Eax4State& state);
void eax5_defer_all(const EaxCall& call, Eax5State& state);
void eax5_defer(const EaxCall& call, Eax5State& state);
void eax_set(const EaxCall& call);
void eax_validate_primary_fx_slot_id(
const GUID& primary_fx_slot_id);
void eax_validate_distance_factor(
float distance_factor);
void eax_validate_air_absorption_hf(
float air_absorption_hf);
void eax_validate_hf_reference(
float hf_reference);
void eax_validate_speaker_config(
unsigned long speaker_config);
void eax_validate_session_eax_version(
unsigned long eax_version);
void eax_validate_session_max_active_sends(
unsigned long max_active_sends);
void eax_validate_session(
const EAXSESSIONPROPERTIES& eax_session);
void eax_validate_macro_fx_factor(
float macro_fx_factor);
void eax_validate_context_all(
const EAX40CONTEXTPROPERTIES& context_all);
void eax_validate_context_all(
const EAX50CONTEXTPROPERTIES& context_all);
void eax_defer_primary_fx_slot_id(
const GUID& primary_fx_slot_id);
void eax_defer_distance_factor(
float distance_factor);
void eax_defer_air_absorption_hf(
float air_absorption_hf);
void eax_defer_hf_reference(
float hf_reference);
void eax_defer_macro_fx_factor(
float macro_fx_factor);
void eax_defer_context_all(
const EAX40CONTEXTPROPERTIES& context_all);
void eax_defer_context_all(
const EAX50CONTEXTPROPERTIES& context_all);
void eax_defer_context_all(
const EaxEaxCall& eax_call);
void eax_defer_primary_fx_slot_id(
const EaxEaxCall& eax_call);
void eax_defer_distance_factor(
const EaxEaxCall& eax_call);
void eax_defer_air_absorption_hf(
const EaxEaxCall& eax_call);
void eax_defer_hf_reference(
const EaxEaxCall& eax_call);
void eax_set_session(
const EaxEaxCall& eax_call);
void eax_defer_macro_fx_factor(
const EaxEaxCall& eax_call);
void eax_set(
const EaxEaxCall& eax_call);
void eax_apply_deferred();
void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df);
void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df);
void eax_context_commit();
#endif // ALSOFT_EAX
};
#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);

View file

@ -84,7 +84,10 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1
case DevFmtX51: return OutputMode1::X51;
case DevFmtX61: return OutputMode1::X61;
case DevFmtX71: return OutputMode1::X71;
case DevFmtAmbi3D: break;
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
break;
}
return OutputMode1::Any;
}

View file

@ -20,7 +20,7 @@
#include "vector.h"
#ifdef ALSOFT_EAX
#include "al/eax_x_ram.h"
#include "al/eax/x_ram.h"
#endif // ALSOFT_EAX
struct ALbuffer;
@ -141,7 +141,7 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
{ return GetConfigValueBool(DeviceName.c_str(), block, key, def); }
template<typename T>
al::optional<T> configValue(const char *block, const char *key) = delete;
inline al::optional<T> configValue(const char *block, const char *key) = delete;
DEF_NEWDEL(ALCdevice)
};

View file

@ -65,21 +65,23 @@ struct AutowahState final : public EffectState {
} mEnv[BufferLineSize];
struct {
uint mTargetChannel{InvalidChannelIndex};
/* Effect filters' history. */
struct {
float z1, z2;
} Filter;
} mFilter;
/* Effect gains for each output channel */
float CurrentGains[MAX_OUTPUT_CHANNELS];
float TargetGains[MAX_OUTPUT_CHANNELS];
float mCurrentGain;
float mTargetGain;
} mChans[MaxAmbiChannels];
/* Effects buffers */
alignas(16) float mBufferOut[BufferLineSize];
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -88,7 +90,7 @@ struct AutowahState final : public EffectState {
DEF_NEWDEL(AutowahState)
};
void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&)
void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
/* (Re-)initializing parameters and clear the buffers. */
@ -108,9 +110,10 @@ void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&)
for(auto &chan : mChans)
{
std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f);
chan.Filter.z1 = 0.0f;
chan.Filter.z2 = 0.0f;
chan.mTargetChannel = InvalidChannelIndex;
chan.mFilter.z1 = 0.0f;
chan.mFilter.z2 = 0.0f;
chan.mCurrentGain = 0.0f;
}
}
@ -131,9 +134,12 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
auto set_channel = [this](size_t idx, uint outchan, float outgain)
{
mChans[idx].mTargetChannel = outchan;
mChans[idx].mTargetGain = outgain;
};
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
}
void AutowahState::process(const size_t samplesToDo,
@ -165,17 +171,24 @@ void AutowahState::process(const size_t samplesToDo,
}
mEnvDelay = env_delay;
auto chandata = std::addressof(mChans[0]);
auto chandata = std::begin(mChans);
for(const auto &insamples : samplesIn)
{
const size_t outidx{chandata->mTargetChannel};
if(outidx == InvalidChannelIndex)
{
++chandata;
continue;
}
/* This effectively inlines BiquadFilter_setParams for a peaking
* filter and BiquadFilter_processC. The alpha and cosine components
* for the filter coefficients were previously calculated with the
* envelope. Because the filter changes for each sample, the
* coefficients are transient and don't need to be held.
*/
float z1{chandata->Filter.z1};
float z2{chandata->Filter.z2};
float z1{chandata->mFilter.z1};
float z2{chandata->mFilter.z2};
for(size_t i{0u};i < samplesToDo;i++)
{
@ -197,12 +210,12 @@ void AutowahState::process(const size_t samplesToDo,
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
mBufferOut[i] = output;
}
chandata->Filter.z1 = z1;
chandata->Filter.z2 = z2;
chandata->mFilter.z1 = z1;
chandata->mFilter.z2 = z2;
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
chandata->TargetGains, samplesToDo, 0);
MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo);
++chandata;
}
}

View file

@ -48,10 +48,8 @@ namespace {
using uint = unsigned int;
#define MAX_UPDATE_SAMPLES 256
struct ChorusState final : public EffectState {
al::vector<float,16> mSampleBuffer;
al::vector<float,16> mDelayBuffer;
uint mOffset{0};
uint mLfoOffset{0};
@ -59,10 +57,16 @@ struct ChorusState final : public EffectState {
float mLfoScale{0.0f};
uint mLfoDisp{0};
/* Gains for left and right sides */
/* Calculated delays to apply to the left and right outputs. */
uint mModDelays[2][BufferLineSize];
/* Temp storage for the modulated left and right outputs. */
alignas(16) float mBuffer[2][BufferLineSize];
/* Gains for left and right outputs. */
struct {
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
/* effect parameters */
@ -71,10 +75,10 @@ struct ChorusState final : public EffectState {
float mDepth{0.0f};
float mFeedback{0.0f};
void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
void calcTriangleDelays(const size_t todo);
void calcSinusoidDelays(const size_t todo);
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -83,16 +87,16 @@ struct ChorusState final : public EffectState {
DEF_NEWDEL(ChorusState)
};
void ChorusState::deviceUpdate(const DeviceBase *Device, const Buffer&)
void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{
constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)};
const auto frequency = static_cast<float>(Device->Frequency);
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
if(maxlen != mSampleBuffer.size())
al::vector<float,16>(maxlen).swap(mSampleBuffer);
if(maxlen != mDelayBuffer.size())
decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer);
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
std::fill(mDelayBuffer.begin(), mDelayBuffer.end(), 0.0f);
for(auto &e : mGains)
{
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
@ -120,8 +124,13 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
mFeedback = props->Chorus.Feedback;
/* Gains for left and right sides */
const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
static constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2);
static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f});
static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f});
static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2});
static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2});
auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw;
auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw;
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
@ -162,7 +171,7 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
}
void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
void ChorusState::calcTriangleDelays(const size_t todo)
{
const uint lfo_range{mLfoRange};
const float lfo_scale{mLfoScale};
@ -172,22 +181,38 @@ void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const si
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
uint offset{mLfoOffset};
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
offset = (offset+1)%lfo_range;
const float offset_norm{static_cast<float>(offset) * lfo_scale};
return static_cast<uint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
};
std::generate_n(delays[0], todo, gen_lfo);
uint offset{mLfoOffset};
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[0][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
std::generate_n(delays[1], todo, gen_lfo);
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[1][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
}
void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
void ChorusState::calcSinusoidDelays(const size_t todo)
{
const uint lfo_range{mLfoRange};
const float lfo_scale{mLfoScale};
@ -197,69 +222,81 @@ void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const si
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
uint offset{mLfoOffset};
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
offset = (offset+1)%lfo_range;
const float offset_norm{static_cast<float>(offset) * lfo_scale};
return static_cast<uint>(fastf2i(std::sin(offset_norm)*depth) + delay);
};
std::generate_n(delays[0], todo, gen_lfo);
uint offset{mLfoOffset};
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[0][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
std::generate_n(delays[1], todo, gen_lfo);
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[1][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
}
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const size_t bufmask{mSampleBuffer.size()-1};
const size_t bufmask{mDelayBuffer.size()-1};
const float feedback{mFeedback};
const uint avgdelay{(static_cast<uint>(mDelay) + (MixerFracOne>>1)) >> MixerFracBits};
float *RESTRICT delaybuf{mSampleBuffer.data()};
const uint avgdelay{(static_cast<uint>(mDelay) + MixerFracHalf) >> MixerFracBits};
float *RESTRICT delaybuf{mDelayBuffer.data()};
uint offset{mOffset};
for(size_t base{0u};base < samplesToDo;)
if(mWaveform == ChorusWaveform::Sinusoid)
calcSinusoidDelays(samplesToDo);
else /*if(mWaveform == ChorusWaveform::Triangle)*/
calcTriangleDelays(samplesToDo);
const uint *RESTRICT ldelays{mModDelays[0]};
const uint *RESTRICT rdelays{mModDelays[1]};
float *RESTRICT lbuffer{al::assume_aligned<16>(mBuffer[0])};
float *RESTRICT rbuffer{al::assume_aligned<16>(mBuffer[1])};
for(size_t i{0u};i < samplesToDo;++i)
{
const size_t todo{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
// Feed the buffer's input first (necessary for delays < 1).
delaybuf[offset&bufmask] = samplesIn[0][i];
uint moddelays[2][MAX_UPDATE_SAMPLES];
if(mWaveform == ChorusWaveform::Sinusoid)
getSinusoidDelays(moddelays, todo);
else /*if(mWaveform == ChorusWaveform::Triangle)*/
getTriangleDelays(moddelays, todo);
// Tap for the left output.
uint delay{offset - (ldelays[i]>>MixerFracBits)};
float mu{static_cast<float>(ldelays[i]&MixerFracMask) * (1.0f/MixerFracOne)};
lbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
alignas(16) float temps[2][MAX_UPDATE_SAMPLES];
for(size_t i{0u};i < todo;++i)
{
// Feed the buffer's input first (necessary for delays < 1).
delaybuf[offset&bufmask] = samplesIn[0][base+i];
// Tap for the right output.
delay = offset - (rdelays[i]>>MixerFracBits);
mu = static_cast<float>(rdelays[i]&MixerFracMask) * (1.0f/MixerFracOne);
rbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
// Tap for the left output.
uint delay{offset - (moddelays[0][i]>>MixerFracBits)};
float mu{static_cast<float>(moddelays[0][i]&MixerFracMask) * (1.0f/MixerFracOne)};
temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
// Tap for the right output.
delay = offset - (moddelays[1][i]>>MixerFracBits);
mu = static_cast<float>(moddelays[1][i]&MixerFracMask) * (1.0f/MixerFracOne);
temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
// Accumulate feedback from the average delay of the taps.
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
++offset;
}
for(size_t c{0};c < 2;++c)
MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo-base, base);
base += todo;
// Accumulate feedback from the average delay of the taps.
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
++offset;
}
MixSamples({lbuffer, samplesToDo}, samplesOut, mGains[0].Current, mGains[0].Target,
samplesToDo, 0);
MixSamples({rbuffer, samplesToDo}, samplesOut, mGains[1].Current, mGains[1].Target,
samplesToDo, 0);
mOffset = offset;
}

View file

@ -64,7 +64,10 @@ namespace {
struct CompressorState final : public EffectState {
/* Effect gains for each channel */
float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{};
struct {
uint mTarget{InvalidChannelIndex};
float mGain{1.0f};
} mChans[MaxAmbiChannels];
/* Effect parameters */
bool mEnabled{true};
@ -73,7 +76,7 @@ struct CompressorState final : public EffectState {
float mEnvFollower{1.0f};
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -82,7 +85,7 @@ struct CompressorState final : public EffectState {
DEF_NEWDEL(CompressorState)
};
void CompressorState::deviceUpdate(const DeviceBase *device, const Buffer&)
void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*)
{
/* Number of samples to do a full attack and release (non-integer sample
* counts are okay).
@ -103,9 +106,12 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot,
mEnabled = props->Compressor.OnOff;
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &gains, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); };
SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains);
auto set_channel = [this](size_t idx, uint outchan, float outgain)
{
mChans[idx].mTarget = outchan;
mChans[idx].mGain = outgain;
};
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
}
void CompressorState::process(const size_t samplesToDo,
@ -158,19 +164,22 @@ void CompressorState::process(const size_t samplesToDo,
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto changains = std::addressof(mGain[0]);
auto chan = std::cbegin(mChans);
for(const auto &input : samplesIn)
{
const float *outgains{*(changains++)};
for(FloatBufferLine &output : samplesOut)
const size_t outidx{chan->mTarget};
if(outidx != InvalidChannelIndex)
{
const float gain{*(outgains++)};
const float *RESTRICT src{input.data() + base};
float *RESTRICT dst{samplesOut[outidx].data() + base};
const float gain{chan->mGain};
if(!(std::fabs(gain) > GainSilenceThreshold))
continue;
for(size_t i{0u};i < td;i++)
output[base+i] += input[base+i] * gains[i] * gain;
{
for(size_t i{0u};i < td;i++)
dst[i] += src[i] * gains[i] * gain;
}
}
++chan;
}
base += td;

View file

@ -72,7 +72,7 @@ namespace {
*/
void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
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
@ -84,6 +84,11 @@ void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep
HANDLE_FMT(FmtDouble);
HANDLE_FMT(FmtMulaw);
HANDLE_FMT(FmtAlaw);
/* FIXME: Handle ADPCM decoding here. */
case FmtIMA4:
case FmtMSADPCM:
std::fill_n(dst, samples, 0.0f);
break;
}
#undef HANDLE_FMT
}
@ -124,7 +129,7 @@ constexpr float Deg2Rad(float x) noexcept
{ return static_cast<float>(al::numbers::pi / 180.0 * x); }
using complex_d = std::complex<double>;
using complex_f = std::complex<float>;
constexpr size_t ConvolveUpdateSize{256};
constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
@ -187,21 +192,21 @@ struct ConvolutionState final : public EffectState {
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
alignas(16) std::array<complex_d,ConvolveUpdateSize> mFftBuffer{};
alignas(16) std::array<complex_f,ConvolveUpdateSize> mFftBuffer{};
size_t mCurrentSegment{0};
size_t mNumConvolveSegs{0};
struct ChannelData {
alignas(16) FloatBufferLine mBuffer{};
float mHfScale{};
float mHfScale{}, mLfScale{};
BandSplitter mFilter{};
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
};
using ChannelDataArray = al::FlexArray<ChannelData>;
std::unique_ptr<ChannelDataArray> mChans;
std::unique_ptr<complex_d[]> mComplexData;
std::unique_ptr<complex_f[]> mComplexData;
ConvolutionState() = default;
@ -212,7 +217,7 @@ struct ConvolutionState final : public EffectState {
void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t)
{&ConvolutionState::NormalMix};
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -235,21 +240,24 @@ void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
for(auto &chan : *mChans)
{
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
chan.mFilter.processHfScale(src, chan.mHfScale);
chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale);
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
}
}
void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer)
void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorage *buffer)
{
using UhjDecoderType = UhjDecoder<512>;
static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding;
constexpr uint MaxConvolveAmbiOrder{1u};
mFifoPos = 0;
mInput.fill(0.0f);
decltype(mFilter){}.swap(mFilter);
decltype(mOutput){}.swap(mOutput);
mFftBuffer.fill(complex_d{});
mFftBuffer.fill(complex_f{});
mCurrentSegment = 0;
mNumConvolveSegs = 0;
@ -258,27 +266,31 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff
mComplexData = nullptr;
/* An empty buffer doesn't need a convolution filter. */
if(!buffer.storage || buffer.storage->mSampleLen < 1) return;
if(!buffer || buffer->mSampleLen < 1) return;
mChannels = buffer->mChannels;
mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder);
constexpr size_t m{ConvolveUpdateSize/2 + 1};
auto bytesPerSample = BytesFromFmt(buffer.storage->mType);
auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder);
auto numChannels = ChannelsFromFmt(buffer.storage->mChannels,
minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder));
const auto bytesPerSample = BytesFromFmt(buffer->mType);
const auto realChannels = buffer->channelsFromFmt();
const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder);
mChans = ChannelDataArray::Create(numChannels);
/* The impulse response needs to have the same sample rate as the input and
* output. The bsinc24 resampler is decent, but there is high-frequency
* attenation that some people may be able to pick up on. Since this is
* attenuation that some people may be able to pick up on. Since this is
* called very infrequently, go ahead and use the polyphase resampler.
*/
PPhaseResampler resampler;
if(device->Frequency != buffer.storage->mSampleRate)
resampler.init(buffer.storage->mSampleRate, device->Frequency);
if(device->Frequency != buffer->mSampleRate)
resampler.init(buffer->mSampleRate, device->Frequency);
const auto resampledCount = static_cast<uint>(
(uint64_t{buffer.storage->mSampleLen}*device->Frequency+(buffer.storage->mSampleRate-1)) /
buffer.storage->mSampleRate);
(uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) /
buffer->mSampleRate);
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
for(auto &e : *mChans)
@ -296,43 +308,61 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)};
mComplexData = std::make_unique<complex_d[]>(complex_length);
std::fill_n(mComplexData.get(), complex_length, complex_d{});
mComplexData = std::make_unique<complex_f[]>(complex_length);
std::fill_n(mComplexData.get(), complex_length, complex_f{});
mChannels = buffer.storage->mChannels;
mAmbiLayout = buffer.storage->mAmbiLayout;
mAmbiScaling = buffer.storage->mAmbiScaling;
mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder);
/* Load the samples from the buffer. */
const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)};
auto srcsamples = std::make_unique<float[]>(srclinelength * numChannels);
std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f);
for(size_t c{0};c < numChannels && c < realChannels;++c)
LoadSamples(srcsamples.get() + srclinelength*c, buffer->mData.data() + bytesPerSample*c,
realChannels, buffer->mType, buffer->mSampleLen);
auto srcsamples = std::make_unique<double[]>(maxz(buffer.storage->mSampleLen, resampledCount));
complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m;
if(IsUHJ(mChannels))
{
auto decoder = std::make_unique<UhjDecoderType>();
std::array<float*,4> samples{};
for(size_t c{0};c < numChannels;++c)
samples[c] = srcsamples.get() + srclinelength*c;
decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen);
}
auto ressamples = std::make_unique<double[]>(buffer->mSampleLen +
(resampler ? resampledCount : 0));
complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m;
for(size_t c{0};c < numChannels;++c)
{
/* Load the samples from the buffer, and resample to match the device. */
LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels,
buffer.storage->mType, buffer.storage->mSampleLen);
if(device->Frequency != buffer.storage->mSampleRate)
resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount,
srcsamples.get());
/* Resample to match the device. */
if(resampler)
{
std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen,
ressamples.get() + resampledCount);
resampler.process(buffer->mSampleLen, ressamples.get()+resampledCount,
resampledCount, ressamples.get());
}
else
std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, ressamples.get());
/* Store the first segment's samples in reverse in the time-domain, to
* apply as a FIR filter.
*/
const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)};
std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(),
std::transform(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(),
[](const double d) noexcept -> float { return static_cast<float>(d); });
auto fftbuffer = std::vector<std::complex<double>>(ConvolveUpdateSize);
size_t done{first_size};
for(size_t s{0};s < mNumConvolveSegs;++s)
{
const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)};
auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin());
auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin());
done += todo;
std::fill(iter, mFftBuffer.end(), complex_d{});
std::fill(iter, fftbuffer.end(), std::complex<double>{});
forward_fft(mFftBuffer);
filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter);
forward_fft(al::as_span(fftbuffer));
filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter);
}
}
}
@ -388,7 +418,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
};
if(mNumConvolveSegs < 1)
if(mNumConvolveSegs < 1) UNLIKELY
return;
mMix = &ConvolutionState::NormalMix;
@ -396,39 +426,36 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
for(auto &chan : *mChans)
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
const float gain{slot->Gain};
/* TODO: UHJ should be decoded to B-Format and processed that way, since
* there's no telling if it can ever do a direct-out mix (even if the
* device is outputing UHJ, the effect slot can feed another effect that's
* not UHJ).
*
* Not that UHJ should really ever be used for convolution, but it's a
* valid format regardless.
*/
if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut
&& target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX
&& target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX)
{
mOutTarget = target.RealOut->Buffer;
const uint lidx = target.RealOut->ChannelIndex[FrontLeft];
const uint ridx = target.RealOut->ChannelIndex[FrontRight];
(*mChans)[0].Target[lidx] = gain;
(*mChans)[1].Target[ridx] = gain;
}
else if(IsBFormat(mChannels))
if(IsAmbisonic(mChannels))
{
DeviceBase *device{context->mDevice};
if(device->mAmbiOrder > mAmbiOrder)
if(mChannels == FmtUHJ2 && !device->mUhjEncoder)
{
mMix = &ConvolutionState::UpsampleMix;
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
(*mChans)[0].mHfScale = 1.0f;
(*mChans)[0].mLfScale = DecoderBase::sWLFScale;
(*mChans)[1].mHfScale = 1.0f;
(*mChans)[1].mLfScale = DecoderBase::sXYLFScale;
(*mChans)[2].mHfScale = 1.0f;
(*mChans)[2].mLfScale = DecoderBase::sXYLFScale;
}
else if(device->mAmbiOrder > mAmbiOrder)
{
mMix = &ConvolutionState::UpsampleMix;
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder,
device->m2DMixing);
(*mChans)[0].mHfScale = scales[0];
(*mChans)[0].mLfScale = 1.0f;
for(size_t i{1};i < mChans->size();++i)
{
(*mChans)[i].mHfScale = scales[1];
(*mChans)[i].mLfScale = 1.0f;
}
}
mOutTarget = target.Main->Buffer;
auto&& scales = GetAmbiScales(mAmbiScaling);
const uint8_t *index_map{(mChannels == FmtBFormat2D) ?
const uint8_t *index_map{Is2DAmbisonic(mChannels) ?
GetAmbi2DLayout(mAmbiLayout).data() :
GetAmbiLayout(mAmbiLayout).data()};
@ -495,7 +522,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
void ConvolutionState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
if(mNumConvolveSegs < 1)
if(mNumConvolveSegs < 1) UNLIKELY
return;
constexpr size_t m{ConvolveUpdateSize/2 + 1};
@ -515,8 +542,7 @@ void ConvolutionState::process(const size_t samplesToDo,
for(size_t c{0};c < chans.size();++c)
{
auto buf_iter = chans[c].mBuffer.begin() + base;
apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos,
mFilter[c].data());
apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data());
auto fifo_iter = mOutput[c].begin() + mFifoPos;
std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
@ -536,20 +562,20 @@ void ConvolutionState::process(const size_t samplesToDo,
* frequency bins to the FFT history.
*/
auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin());
std::fill(fftiter, mFftBuffer.end(), complex_d{});
forward_fft(mFftBuffer);
std::fill(fftiter, mFftBuffer.end(), complex_f{});
forward_fft(al::as_span(mFftBuffer));
std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]);
const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
for(size_t c{0};c < chans.size();++c)
{
std::fill_n(mFftBuffer.begin(), m, complex_d{});
std::fill_n(mFftBuffer.begin(), m, complex_f{});
/* Convolve each input segment with its IR filter counterpart
* (aligned in time).
*/
const complex_d *RESTRICT input{&mComplexData[curseg*m]};
const complex_f *RESTRICT input{&mComplexData[curseg*m]};
for(size_t s{curseg};s < mNumConvolveSegs;++s)
{
for(size_t i{0};i < m;++i,++input,++filter)
@ -573,19 +599,17 @@ void ConvolutionState::process(const size_t samplesToDo,
* second-half samples (and this output's second half is
* subsequently saved for next time).
*/
inverse_fft(mFftBuffer);
inverse_fft(al::as_span(mFftBuffer));
/* The iFFT'd response is scaled up by the number of bins, so apply
* the inverse to normalize the output.
*/
for(size_t i{0};i < ConvolveUpdateSamples;++i)
mOutput[c][i] =
static_cast<float>(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) +
mOutput[c][ConvolveUpdateSamples+i];
(mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) *
(1.0f/float{ConvolveUpdateSize});
for(size_t i{0};i < ConvolveUpdateSamples;++i)
mOutput[c][ConvolveUpdateSamples+i] =
static_cast<float>(mFftBuffer[ConvolveUpdateSamples+i].real() *
(1.0/double{ConvolveUpdateSize}));
mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real();
}
/* Shift the input history. */

View file

@ -43,11 +43,15 @@ namespace {
using uint = unsigned int;
struct DedicatedState final : public EffectState {
/* The "dedicated" effect can output to the real output, so should have
* gains for all possible output channels and not just the main ambisonic
* buffer.
*/
float mCurrentGains[MAX_OUTPUT_CHANNELS];
float mTargetGains[MAX_OUTPUT_CHANNELS];
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -56,7 +60,7 @@ struct DedicatedState final : public EffectState {
DEF_NEWDEL(DedicatedState)
};
void DedicatedState::deviceUpdate(const DeviceBase*, const Buffer&)
void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
}
@ -70,9 +74,8 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
if(slot->EffectType == EffectSlotType::DedicatedLFE)
{
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
GetChannelIdxByName(*target.RealOut, LFE)};
if(idx != INVALID_CHANNEL_INDEX)
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
@ -82,16 +85,16 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
{
/* Dialog goes to the front-center speaker if it exists, otherwise it
* plays from the front-center location. */
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
GetChannelIdxByName(*target.RealOut, FrontCenter)};
if(idx != INVALID_CHANNEL_INDEX)
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
: InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
else
{
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);

View file

@ -45,7 +45,7 @@ namespace {
struct DistortionState final : public EffectState {
/* Effect gains for each channel */
float mGain[MAX_OUTPUT_CHANNELS]{};
float mGain[MaxAmbiChannels]{};
/* Effect parameters */
BiquadFilter mLowpass;
@ -53,10 +53,10 @@ struct DistortionState final : public EffectState {
float mAttenuation{};
float mEdgeCoeff{};
float mBuffer[2][BufferLineSize]{};
alignas(16) float mBuffer[2][BufferLineSize]{};
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -65,7 +65,7 @@ struct DistortionState final : public EffectState {
DEF_NEWDEL(DistortionState)
};
void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&)
void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
mLowpass.clear();
mBandpass.clear();
@ -95,7 +95,7 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);

View file

@ -60,8 +60,8 @@ struct EchoState final : public EffectState {
/* The panning gains for the two taps */
struct {
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
BiquadFilter mFilter;
@ -69,7 +69,7 @@ struct EchoState final : public EffectState {
alignas(16) float mTempBuffer[2][BufferLineSize];
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -78,7 +78,7 @@ struct EchoState final : public EffectState {
DEF_NEWDEL(EchoState)
};
void EchoState::deviceUpdate(const DeviceBase *Device, const Buffer&)
void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{
const auto frequency = static_cast<float>(Device->Frequency);

View file

@ -87,18 +87,20 @@ namespace {
struct EqualizerState final : public EffectState {
struct {
uint mTargetChannel{InvalidChannelIndex};
/* Effect parameters */
BiquadFilter filter[4];
BiquadFilter mFilter[4];
/* Effect gains for each channel */
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
float TargetGains[MAX_OUTPUT_CHANNELS]{};
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
FloatBufferLine mSampleBuffer{};
alignas(16) FloatBufferLine mSampleBuffer{};
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -107,12 +109,14 @@ struct EqualizerState final : public EffectState {
DEF_NEWDEL(EqualizerState)
};
void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&)
void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
for(auto &e : mChans)
{
std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear));
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
e.mTargetChannel = InvalidChannelIndex;
std::for_each(std::begin(e.mFilter), std::end(e.mFilter),
std::mem_fn(&BiquadFilter::clear));
e.mCurrentGain = 0.0f;
}
}
@ -131,48 +135,56 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
*/
gain = std::sqrt(props->Equalizer.LowGain);
f0norm = props->Equalizer.LowCutoff / frequency;
mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
gain = std::sqrt(props->Equalizer.Mid1Gain);
f0norm = props->Equalizer.Mid1Center / frequency;
mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid1Width);
gain = std::sqrt(props->Equalizer.Mid2Gain);
f0norm = props->Equalizer.Mid2Center / frequency;
mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid2Width);
gain = std::sqrt(props->Equalizer.HighGain);
f0norm = props->Equalizer.HighCutoff / frequency;
mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
/* Copy the filter coefficients for the other input channels. */
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
{
mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]);
mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]);
mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]);
mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]);
mChans[i].mFilter[0].copyParamsFrom(mChans[0].mFilter[0]);
mChans[i].mFilter[1].copyParamsFrom(mChans[0].mFilter[1]);
mChans[i].mFilter[2].copyParamsFrom(mChans[0].mFilter[2]);
mChans[i].mFilter[3].copyParamsFrom(mChans[0].mFilter[3]);
}
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
auto set_channel = [this](size_t idx, uint outchan, float outgain)
{
mChans[idx].mTargetChannel = outchan;
mChans[idx].mTargetGain = outgain;
};
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
}
void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const al::span<float> buffer{mSampleBuffer.data(), samplesToDo};
auto chan = std::addressof(mChans[0]);
auto chan = std::begin(mChans);
for(const auto &input : samplesIn)
{
const al::span<const float> inbuf{input.data(), samplesToDo};
DualBiquad{chan->filter[0], chan->filter[1]}.process(inbuf, buffer.begin());
DualBiquad{chan->filter[2], chan->filter[3]}.process(buffer, buffer.begin());
const size_t outidx{chan->mTargetChannel};
if(outidx != InvalidChannelIndex)
{
const al::span<const float> inbuf{input.data(), samplesToDo};
DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer.begin());
DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer.begin());
MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u);
MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain,
samplesToDo);
}
++chan;
}
}

View file

@ -48,53 +48,56 @@ namespace {
using uint = unsigned int;
using complex_d = std::complex<double>;
#define HIL_SIZE 1024
#define OVERSAMP (1<<2)
constexpr size_t HilSize{1024};
constexpr size_t HilHalfSize{HilSize >> 1};
constexpr size_t OversampleFactor{4};
#define HIL_STEP (HIL_SIZE / OVERSAMP)
#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
static_assert(HilSize%OversampleFactor == 0, "Factor must be a clean divisor of the size");
constexpr size_t HilStep{HilSize / OversampleFactor};
/* Define a Hann window, used to filter the HIL input and output. */
std::array<double,HIL_SIZE> InitHannWindow()
{
std::array<double,HIL_SIZE> ret;
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
for(size_t i{0};i < HIL_SIZE>>1;i++)
struct Windower {
alignas(16) std::array<double,HilSize> mData;
Windower()
{
constexpr double scale{al::numbers::pi / double{HIL_SIZE}};
const double val{std::sin(static_cast<double>(i+1) * scale)};
ret[i] = ret[HIL_SIZE-1-i] = val * val;
/* Create lookup table of the Hann window for the desired size. */
for(size_t i{0};i < HilHalfSize;i++)
{
constexpr double scale{al::numbers::pi / double{HilSize}};
const double val{std::sin((static_cast<double>(i)+0.5) * scale)};
mData[i] = mData[HilSize-1-i] = val * val;
}
}
return ret;
}
alignas(16) const std::array<double,HIL_SIZE> HannWindow = InitHannWindow();
};
const Windower gWindow{};
struct FshifterState final : public EffectState {
/* Effect parameters */
size_t mCount{};
size_t mPos{};
uint mPhaseStep[2]{};
uint mPhase[2]{};
double mSign[2]{};
std::array<uint,2> mPhaseStep{};
std::array<uint,2> mPhase{};
std::array<double,2> mSign{};
/* Effects buffers */
double mInFIFO[HIL_SIZE]{};
complex_d mOutFIFO[HIL_STEP]{};
complex_d mOutputAccum[HIL_SIZE]{};
complex_d mAnalytic[HIL_SIZE]{};
complex_d mOutdata[BufferLineSize]{};
std::array<double,HilSize> mInFIFO{};
std::array<complex_d,HilStep> mOutFIFO{};
std::array<complex_d,HilSize> mOutputAccum{};
std::array<complex_d,HilSize> mAnalytic{};
std::array<complex_d,BufferLineSize> mOutdata{};
alignas(16) float mBufferOut[BufferLineSize]{};
alignas(16) FloatBufferLine mBufferOut{};
/* Effect gains for each output channel */
struct {
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -103,19 +106,19 @@ struct FshifterState final : public EffectState {
DEF_NEWDEL(FshifterState)
};
void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
/* (Re-)initializing parameters and clear the buffers. */
mCount = 0;
mPos = FIFO_LATENCY;
mPos = HilSize - HilStep;
std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0u);
std::fill(std::begin(mPhase), std::end(mPhase), 0u);
std::fill(std::begin(mSign), std::end(mSign), 1.0);
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0);
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{});
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{});
std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{});
mPhaseStep.fill(0u);
mPhase.fill(0u);
mSign.fill(1.0);
mInFIFO.fill(0.0);
mOutFIFO.fill(complex_d{});
mOutputAccum.fill(complex_d{});
mAnalytic.fill(complex_d{});
for(auto &gain : mGains)
{
@ -160,8 +163,13 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
break;
}
const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
static constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2);
static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f});
static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f});
static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2});
static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2});
auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw;
auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw;
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target);
@ -172,7 +180,7 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
{
for(size_t base{0u};base < samplesToDo;)
{
size_t todo{minz(HIL_STEP-mCount, samplesToDo-base)};
size_t todo{minz(HilStep-mCount, samplesToDo-base)};
/* Fill FIFO buffer with samples data */
const size_t pos{mPos};
@ -185,33 +193,33 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
mCount = count;
/* Check whether FIFO buffer is filled */
if(mCount < HIL_STEP) break;
if(mCount < HilStep) break;
mCount = 0;
mPos = (mPos+HIL_STEP) & (HIL_SIZE-1);
mPos = (mPos+HilStep) & (HilSize-1);
/* Real signal windowing and store in Analytic buffer */
for(size_t src{mPos}, k{0u};src < HIL_SIZE;++src,++k)
mAnalytic[k] = mInFIFO[src]*HannWindow[k];
for(size_t src{0u}, k{HIL_SIZE-mPos};src < mPos;++src,++k)
mAnalytic[k] = mInFIFO[src]*HannWindow[k];
for(size_t src{mPos}, k{0u};src < HilSize;++src,++k)
mAnalytic[k] = mInFIFO[src]*gWindow.mData[k];
for(size_t src{0u}, k{HilSize-mPos};src < mPos;++src,++k)
mAnalytic[k] = mInFIFO[src]*gWindow.mData[k];
/* Processing signal by Discrete Hilbert Transform (analytical signal). */
complex_hilbert(mAnalytic);
/* Windowing and add to output accumulator */
for(size_t dst{mPos}, k{0u};dst < HIL_SIZE;++dst,++k)
mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
for(size_t dst{0u}, k{HIL_SIZE-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
for(size_t dst{mPos}, k{0u};dst < HilSize;++dst,++k)
mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k];
for(size_t dst{0u}, k{HilSize-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k];
/* Copy out the accumulated result, then clear for the next iteration. */
std::copy_n(mOutputAccum + mPos, HIL_STEP, mOutFIFO);
std::fill_n(mOutputAccum + mPos, HIL_STEP, complex_d{});
std::copy_n(mOutputAccum.cbegin() + mPos, HilStep, mOutFIFO.begin());
std::fill_n(mOutputAccum.begin() + mPos, HilStep, complex_d{});
}
/* Process frequency shifter using the analytic signal obtained. */
float *RESTRICT BufferOut{mBufferOut};
for(int c{0};c < 2;++c)
float *RESTRICT BufferOut{al::assume_aligned<16>(mBufferOut.data())};
for(size_t c{0};c < 2;++c)
{
const uint phase_step{mPhaseStep[c]};
uint phase_idx{mPhase[c]};

View file

@ -84,14 +84,16 @@ struct ModulatorState final : public EffectState {
uint mStep{1};
struct {
BiquadFilter Filter;
uint mTargetChannel{InvalidChannelIndex};
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
float TargetGains[MAX_OUTPUT_CHANNELS]{};
BiquadFilter mFilter;
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -100,12 +102,13 @@ struct ModulatorState final : public EffectState {
DEF_NEWDEL(ModulatorState)
};
void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&)
void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
for(auto &e : mChans)
{
e.Filter.clear();
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
e.mTargetChannel = InvalidChannelIndex;
e.mFilter.clear();
e.mCurrentGain = 0.0f;
}
}
@ -129,14 +132,17 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)};
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
/* Bandwidth value is constant in octaves. */
mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
mChans[i].mFilter.copyParamsFrom(mChans[0].mFilter);
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
auto set_channel = [this](size_t idx, uint outchan, float outgain)
{
mChans[idx].mTargetChannel = outchan;
mChans[idx].mTargetGain = outgain;
};
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
}
void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
@ -153,14 +159,18 @@ void ModulatorState::process(const size_t samplesToDo, const al::span<const Floa
auto chandata = std::begin(mChans);
for(const auto &input : samplesIn)
{
alignas(16) float temps[MAX_UPDATE_SAMPLES];
const size_t outidx{chandata->mTargetChannel};
if(outidx != InvalidChannelIndex)
{
alignas(16) float temps[MAX_UPDATE_SAMPLES];
chandata->Filter.process({&input[base], td}, temps);
for(size_t i{0u};i < td;i++)
temps[i] *= modsamples[i];
chandata->mFilter.process({&input[base], td}, temps);
for(size_t i{0u};i < td;i++)
temps[i] *= modsamples[i];
MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
samplesToDo-base, base);
MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
}
++chandata;
}

View file

@ -20,7 +20,7 @@ struct NullState final : public EffectState {
NullState();
~NullState() override;
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -44,7 +44,7 @@ NullState::~NullState() = default;
* format) have been changed. Will always be followed by a call to the update
* method, if successful.
*/
void NullState::deviceUpdate(const DeviceBase* /*device*/, const Buffer& /*buffer*/)
void NullState::deviceUpdate(const DeviceBase* /*device*/, const BufferStorage* /*buffer*/)
{
}

View file

@ -47,34 +47,36 @@ struct ContextBase;
namespace {
using uint = unsigned int;
using complex_d = std::complex<double>;
using complex_f = std::complex<float>;
#define STFT_SIZE 1024
#define STFT_HALF_SIZE (STFT_SIZE>>1)
#define OVERSAMP (1<<2)
constexpr size_t StftSize{1024};
constexpr size_t StftHalfSize{StftSize >> 1};
constexpr size_t OversampleFactor{8};
#define STFT_STEP (STFT_SIZE / OVERSAMP)
#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
static_assert(StftSize%OversampleFactor == 0, "Factor must be a clean divisor of the size");
constexpr size_t StftStep{StftSize / OversampleFactor};
/* Define a Hann window, used to filter the STFT input and output. */
std::array<double,STFT_SIZE> InitHannWindow()
{
std::array<double,STFT_SIZE> ret;
/* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */
for(size_t i{0};i < STFT_SIZE>>1;i++)
struct Windower {
alignas(16) std::array<float,StftSize> mData;
Windower()
{
constexpr double scale{al::numbers::pi / double{STFT_SIZE}};
const double val{std::sin(static_cast<double>(i+1) * scale)};
ret[i] = ret[STFT_SIZE-1-i] = val * val;
/* Create lookup table of the Hann window for the desired size. */
for(size_t i{0};i < StftHalfSize;i++)
{
constexpr double scale{al::numbers::pi / double{StftSize}};
const double val{std::sin((static_cast<double>(i)+0.5) * scale)};
mData[i] = mData[StftSize-1-i] = static_cast<float>(val * val);
}
}
return ret;
}
alignas(16) const std::array<double,STFT_SIZE> HannWindow = InitHannWindow();
};
const Windower gWindow{};
struct FrequencyBin {
double Amplitude;
double FreqBin;
float Magnitude;
float FreqBin;
};
@ -83,27 +85,27 @@ struct PshifterState final : public EffectState {
size_t mCount;
size_t mPos;
uint mPitchShiftI;
double mPitchShift;
float mPitchShift;
/* Effects buffers */
std::array<double,STFT_SIZE> mFIFO;
std::array<double,STFT_HALF_SIZE+1> mLastPhase;
std::array<double,STFT_HALF_SIZE+1> mSumPhase;
std::array<double,STFT_SIZE> mOutputAccum;
std::array<float,StftSize> mFIFO;
std::array<float,StftHalfSize+1> mLastPhase;
std::array<float,StftHalfSize+1> mSumPhase;
std::array<float,StftSize> mOutputAccum;
std::array<complex_d,STFT_SIZE> mFftBuffer;
std::array<complex_f,StftSize> mFftBuffer;
std::array<FrequencyBin,STFT_HALF_SIZE+1> mAnalysisBuffer;
std::array<FrequencyBin,STFT_HALF_SIZE+1> mSynthesisBuffer;
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer;
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer;
alignas(16) FloatBufferLine mBufferOut;
/* Effect gains for each output channel */
float mCurrentGains[MAX_OUTPUT_CHANNELS];
float mTargetGains[MAX_OUTPUT_CHANNELS];
float mCurrentGains[MaxAmbiChannels];
float mTargetGains[MaxAmbiChannels];
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -112,21 +114,21 @@ struct PshifterState final : public EffectState {
DEF_NEWDEL(PshifterState)
};
void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
/* (Re-)initializing parameters and clear the buffers. */
mCount = 0;
mPos = FIFO_LATENCY;
mPos = StftSize - StftStep;
mPitchShiftI = MixerFracOne;
mPitchShift = 1.0;
mPitchShift = 1.0f;
std::fill(mFIFO.begin(), mFIFO.end(), 0.0);
std::fill(mLastPhase.begin(), mLastPhase.end(), 0.0);
std::fill(mSumPhase.begin(), mSumPhase.end(), 0.0);
std::fill(mOutputAccum.begin(), mOutputAccum.end(), 0.0);
std::fill(mFftBuffer.begin(), mFftBuffer.end(), complex_d{});
std::fill(mAnalysisBuffer.begin(), mAnalysisBuffer.end(), FrequencyBin{});
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
mFIFO.fill(0.0f);
mLastPhase.fill(0.0f);
mSumPhase.fill(0.0f);
mOutputAccum.fill(0.0f);
mFftBuffer.fill(complex_f{});
mAnalysisBuffer.fill(FrequencyBin{});
mSynthesisBuffer.fill(FrequencyBin{});
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
@ -137,16 +139,17 @@ void PshifterState::update(const ContextBase*, const EffectSlot *slot,
{
const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune};
const float pitch{std::pow(2.0f, static_cast<float>(tune) / 1200.0f)};
mPitchShiftI = fastf2u(pitch*MixerFracOne);
mPitchShift = mPitchShiftI * double{1.0/MixerFracOne};
mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2);
mPitchShift = static_cast<float>(mPitchShiftI) * float{1.0f/MixerFracOne};
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains);
}
void PshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
void PshifterState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
/* Pitch shifter engine based on the work of Stephan Bernsee.
* http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
@ -155,103 +158,133 @@ void PshifterState::process(const size_t samplesToDo, const al::span<const Float
/* Cycle offset per update expected of each frequency bin (bin 0 is none,
* bin 1 is x1, bin 2 is x2, etc).
*/
constexpr double expected_cycles{al::numbers::pi*2.0 / OVERSAMP};
constexpr float expected_cycles{al::numbers::pi_v<float>*2.0f / OversampleFactor};
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(STFT_STEP-mCount, samplesToDo-base)};
const size_t todo{minz(StftStep-mCount, samplesToDo-base)};
/* Retrieve the output samples from the FIFO and fill in the new input
* samples.
*/
auto fifo_iter = mFIFO.begin()+mPos + mCount;
std::transform(fifo_iter, fifo_iter+todo, mBufferOut.begin()+base,
[](double d) noexcept -> float { return static_cast<float>(d); });
std::copy_n(fifo_iter, todo, mBufferOut.begin()+base);
std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter);
mCount += todo;
base += todo;
/* Check whether FIFO buffer is filled with new samples. */
if(mCount < STFT_STEP) break;
if(mCount < StftStep) break;
mCount = 0;
mPos = (mPos+STFT_STEP) & (mFIFO.size()-1);
mPos = (mPos+StftStep) & (mFIFO.size()-1);
/* Time-domain signal windowing, store in FftBuffer, and apply a
* forward FFT to get the frequency-domain signal.
*/
for(size_t src{mPos}, k{0u};src < STFT_SIZE;++src,++k)
mFftBuffer[k] = mFIFO[src] * HannWindow[k];
for(size_t src{0u}, k{STFT_SIZE-mPos};src < mPos;++src,++k)
mFftBuffer[k] = mFIFO[src] * HannWindow[k];
forward_fft(mFftBuffer);
for(size_t src{mPos}, k{0u};src < StftSize;++src,++k)
mFftBuffer[k] = mFIFO[src] * gWindow.mData[k];
for(size_t src{0u}, k{StftSize-mPos};src < mPos;++src,++k)
mFftBuffer[k] = mFIFO[src] * gWindow.mData[k];
forward_fft(al::as_span(mFftBuffer));
/* Analyze the obtained data. Since the real FFT is symmetric, only
* STFT_HALF_SIZE+1 samples are needed.
* StftHalfSize+1 samples are needed.
*/
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
for(size_t k{0u};k < StftHalfSize+1;k++)
{
const double amplitude{std::abs(mFftBuffer[k])};
const double phase{std::arg(mFftBuffer[k])};
const float magnitude{std::abs(mFftBuffer[k])};
const float phase{std::arg(mFftBuffer[k])};
/* Compute phase difference and subtract expected phase difference */
double tmp{(phase - mLastPhase[k]) - static_cast<double>(k)*expected_cycles};
/* Map delta phase into +/- Pi interval */
int qpd{double2int(tmp / al::numbers::pi)};
tmp -= al::numbers::pi * (qpd + (qpd%2));
/* Get deviation from bin frequency from the +/- Pi interval */
tmp /= expected_cycles;
/* Compute the k-th partials' true frequency and store the
* amplitude and frequency bin in the analysis buffer.
/* Compute the phase difference from the last update and subtract
* the expected phase difference for this bin.
*
* When oversampling, the expected per-update offset increments by
* 1/OversampleFactor for every frequency bin. So, the offset wraps
* every 'OversampleFactor' bin.
*/
mAnalysisBuffer[k].Amplitude = amplitude;
mAnalysisBuffer[k].FreqBin = static_cast<double>(k) + tmp;
/* Store the actual phase[k] for the next frame. */
const auto bin_offset = static_cast<float>(k % OversampleFactor);
float tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles};
/* Store the actual phase for the next update. */
mLastPhase[k] = phase;
/* Normalize from pi, and wrap the delta between -1 and +1. */
tmp *= al::numbers::inv_pi_v<float>;
int qpd{float2int(tmp)};
tmp -= static_cast<float>(qpd + (qpd%2));
/* Get deviation from bin frequency (-0.5 to +0.5), and account for
* oversampling.
*/
tmp *= 0.5f * OversampleFactor;
/* Compute the k-th partials' frequency bin target and store the
* magnitude and frequency bin in the analysis buffer. We don't
* need the "true frequency" since it's a linear relationship with
* the bin.
*/
mAnalysisBuffer[k].Magnitude = magnitude;
mAnalysisBuffer[k].FreqBin = static_cast<float>(k) + tmp;
}
/* Shift the frequency bins according to the pitch adjustment,
* accumulating the amplitudes of overlapping frequency bins.
* accumulating the magnitudes of overlapping frequency bins.
*/
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
const size_t bin_count{minz(STFT_HALF_SIZE+1,
(((STFT_HALF_SIZE+1)<<MixerFracBits) - (MixerFracOne>>1) - 1)/mPitchShiftI + 1)};
constexpr size_t bin_limit{((StftHalfSize+1)<<MixerFracBits) - MixerFracHalf - 1};
const size_t bin_count{minz(StftHalfSize+1, bin_limit/mPitchShiftI + 1)};
for(size_t k{0u};k < bin_count;k++)
{
const size_t j{(k*mPitchShiftI + (MixerFracOne>>1)) >> MixerFracBits};
mSynthesisBuffer[j].Amplitude += mAnalysisBuffer[k].Amplitude;
mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift;
const size_t j{(k*mPitchShiftI + MixerFracHalf) >> MixerFracBits};
/* If more than two bins end up together, use the target frequency
* bin for the one with the dominant magnitude. There might be a
* better way to handle this, but it's better than last-index-wins.
*/
if(mAnalysisBuffer[k].Magnitude > mSynthesisBuffer[j].Magnitude)
mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift;
mSynthesisBuffer[j].Magnitude += mAnalysisBuffer[k].Magnitude;
}
/* Reconstruct the frequency-domain signal from the adjusted frequency
* bins.
*/
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
for(size_t k{0u};k < StftHalfSize+1;k++)
{
/* Calculate actual delta phase and accumulate it to get bin phase */
mSumPhase[k] += mSynthesisBuffer[k].FreqBin * expected_cycles;
/* Calculate the actual delta phase for this bin's target frequency
* bin, and accumulate it to get the actual bin phase.
*/
float tmp{mSumPhase[k] + mSynthesisBuffer[k].FreqBin*expected_cycles};
mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Amplitude, mSumPhase[k]);
/* Wrap between -pi and +pi for the sum. If mSumPhase is left to
* grow indefinitely, it will lose precision and produce less exact
* phase over time.
*/
tmp *= al::numbers::inv_pi_v<float>;
int qpd{float2int(tmp)};
tmp -= static_cast<float>(qpd + (qpd%2));
mSumPhase[k] = tmp * al::numbers::pi_v<float>;
mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]);
}
for(size_t k{STFT_HALF_SIZE+1};k < STFT_SIZE;++k)
mFftBuffer[k] = std::conj(mFftBuffer[STFT_SIZE-k]);
for(size_t k{StftHalfSize+1};k < StftSize;++k)
mFftBuffer[k] = std::conj(mFftBuffer[StftSize-k]);
/* Apply an inverse FFT to get the time-domain siganl, and accumulate
/* Apply an inverse FFT to get the time-domain signal, and accumulate
* for the output with windowing.
*/
inverse_fft(mFftBuffer);
for(size_t dst{mPos}, k{0u};dst < STFT_SIZE;++dst,++k)
mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE);
for(size_t dst{0u}, k{STFT_SIZE-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE);
inverse_fft(al::as_span(mFftBuffer));
static constexpr float scale{3.0f / OversampleFactor / StftSize};
for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k)
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
/* Copy out the accumulated result, then clear for the next iteration. */
std::copy_n(mOutputAccum.begin() + mPos, STFT_STEP, mFIFO.begin() + mPos);
std::fill_n(mOutputAccum.begin() + mPos, STFT_STEP, 0.0);
std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos);
std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0f);
}
/* Now, mix the processed sound data to the output. */

File diff suppressed because it is too large Load diff

View file

@ -143,12 +143,14 @@ struct FormantFilter
struct VmorpherState final : public EffectState {
struct {
uint mTargetChannel{InvalidChannelIndex};
/* Effect parameters */
FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS];
FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS];
/* Effect gains for each channel */
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
float TargetGains[MAX_OUTPUT_CHANNELS]{};
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
@ -161,7 +163,7 @@ struct VmorpherState final : public EffectState {
alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{};
alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{};
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
@ -225,15 +227,16 @@ std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome p
}
void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&)
void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
for(auto &e : mChans)
{
std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]),
e.mTargetChannel = InvalidChannelIndex;
std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]),
std::mem_fn(&FormantFilter::clear));
std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]),
std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]),
std::mem_fn(&FormantFilter::clear));
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
e.mCurrentGain = 0.0f;
}
}
@ -265,14 +268,17 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
/* Copy the filter coefficients to the input channels. */
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX]));
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX]));
std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].mFormants[VOWEL_A_INDEX]));
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX]));
}
mOutTarget = target.Main->Buffer;
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
auto set_channel = [this](size_t idx, uint outchan, float outgain)
{
mChans[idx].mTargetChannel = outchan;
mChans[idx].mTargetGain = outgain;
};
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
}
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
@ -291,8 +297,15 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
auto chandata = std::begin(mChans);
for(const auto &input : samplesIn)
{
auto& vowelA = chandata->Formants[VOWEL_A_INDEX];
auto& vowelB = chandata->Formants[VOWEL_B_INDEX];
const size_t outidx{chandata->mTargetChannel};
if(outidx == InvalidChannelIndex)
{
++chandata;
continue;
}
auto& vowelA = chandata->mFormants[VOWEL_A_INDEX];
auto& vowelB = chandata->mFormants[VOWEL_B_INDEX];
/* Process first vowel. */
std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
@ -313,8 +326,8 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
/* Now, mix the processed sound data to the output. */
MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
samplesToDo-base, base);
MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
++chandata;
}

View file

@ -59,7 +59,7 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *
AL_API const ALchar* AL_APIENTRY alsoft_get_version(void);
/* Functions from abandoned extenions. Only here for binary compatibility. */
/* Functions from abandoned extensions. Only here for binary compatibility. */
AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb,
const ALuint *buffers);

View file

@ -22,6 +22,7 @@
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstdio>
@ -89,6 +90,23 @@ inline const char *GetLabelFromChannel(Channel channel)
case TopBackCenter: return "top-back-center";
case TopBackRight: return "top-back-right";
case Aux0: return "Aux0";
case Aux1: return "Aux1";
case Aux2: return "Aux2";
case Aux3: return "Aux3";
case Aux4: return "Aux4";
case Aux5: return "Aux5";
case Aux6: return "Aux6";
case Aux7: return "Aux7";
case Aux8: return "Aux8";
case Aux9: return "Aux9";
case Aux10: return "Aux10";
case Aux11: return "Aux11";
case Aux12: return "Aux12";
case Aux13: return "Aux13";
case Aux14: return "Aux14";
case Aux15: return "Aux15";
case MaxChannels: break;
}
return "(unknown)";
@ -98,13 +116,13 @@ inline const char *GetLabelFromChannel(Channel channel)
std::unique_ptr<FrontStablizer> CreateStablizer(const size_t outchans, const uint srate)
{
auto stablizer = FrontStablizer::Create(outchans);
for(auto &buf : stablizer->DelayBuf)
std::fill(buf.begin(), buf.end(), 0.0f);
/* Initialize band-splitting filter for the mid signal, with a crossover at
* 5khz (could be higher).
*/
stablizer->MidFilter.init(5000.0f / static_cast<float>(srate));
for(auto &filter : stablizer->ChannelFilters)
filter = stablizer->MidFilter;
return stablizer;
}
@ -202,6 +220,8 @@ struct DecoderConfig<DualBand, 0> {
mCoeffsLF = rhs.mCoeffsLF;
return *this;
}
explicit operator bool() const noexcept { return !mChannels.empty(); }
};
using DecoderView = DecoderConfig<DualBand, 0>;
@ -212,7 +232,7 @@ void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d
static const uint chans_per_order3d[MaxAmbiOrder+1]{ 1, 3, 5, 7 };
/* NFC is only used when AvgSpeakerDist is greater than 0. */
if(!device->getConfigValueBool("decoder", "nfc", 0) || !(ctrl_dist > 0.0f))
if(!device->getConfigValueBool("decoder", "nfc", false) || !(ctrl_dist > 0.0f))
return;
device->AvgSpeakerDist = clampf(ctrl_dist, 0.1f, 10.0f);
@ -232,7 +252,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
{
const float maxdist{std::accumulate(std::begin(dists), std::end(dists), 0.0f, maxf)};
if(!device->getConfigValueBool("decoder", "distance-comp", 1) || !(maxdist > 0.0f))
if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f))
return;
const auto distSampleScale = static_cast<float>(device->Frequency) / SpeedOfSoundMetersPerSec;
@ -243,7 +263,7 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
{
const Channel ch{channels[chidx]};
const uint idx{device->RealOut.ChannelIndex[ch]};
if(idx == INVALID_CHANNEL_INDEX)
if(idx == InvalidChannelIndex)
continue;
const float distance{dists[chidx]};
@ -255,11 +275,11 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
* will be in steps of about 7 millimeters.
*/
float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)};
if(delay > float{MAX_DELAY_LENGTH-1})
if(delay > float{DistanceComp::MaxDelay-1})
{
ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx,
GetLabelFromChannel(ch), delay, MAX_DELAY_LENGTH-1);
delay = float{MAX_DELAY_LENGTH-1};
GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1);
delay = float{DistanceComp::MaxDelay-1};
}
ChanDelay.resize(maxz(ChanDelay.size(), idx+1));
@ -312,12 +332,14 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
{
DecoderView ret{};
decoder.mOrder = (conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} :
decoder.mOrder = (conf->ChanMask > Ambi3OrderMask) ? uint8_t{4} :
(conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} :
(conf->ChanMask > Ambi1OrderMask) ? uint8_t{2} : uint8_t{1};
decoder.mIs3D = (conf->ChanMask&AmbiPeriphonicMask) != 0;
switch(conf->CoeffScale)
{
case AmbDecScale::Unset: ASSUME(false); break;
case AmbDecScale::N3D: decoder.mScaling = DevAmbiScaling::N3D; break;
case AmbDecScale::SN3D: decoder.mScaling = DevAmbiScaling::SN3D; break;
case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break;
@ -330,44 +352,10 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)),
std::begin(decoder.mOrderGainLF));
std::array<uint8_t,MaxAmbiChannels> idx_map{};
if(decoder.mIs3D)
{
uint flags{conf->ChanMask};
auto elem = idx_map.begin();
while(flags)
{
int acn{al::countr_zero(flags)};
flags &= ~(1u<<acn);
*elem = static_cast<uint8_t>(acn);
++elem;
}
}
else
{
uint flags{conf->ChanMask};
auto elem = idx_map.begin();
while(flags)
{
int acn{al::countr_zero(flags)};
flags &= ~(1u<<acn);
switch(acn)
{
case 0: *elem = 0; break;
case 1: *elem = 1; break;
case 3: *elem = 2; break;
case 4: *elem = 3; break;
case 8: *elem = 4; break;
case 9: *elem = 5; break;
case 15: *elem = 6; break;
default: return ret;
}
++elem;
}
}
const auto num_coeffs = static_cast<uint>(al::popcount(conf->ChanMask));
const auto num_coeffs = decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder)
: Ambi2DChannelsFromOrder(decoder.mOrder);
const auto idx_map = decoder.mIs3D ? AmbiIndex::FromACN().data()
: AmbiIndex::FromACN2D().data();
const auto hfmatrix = conf->HFMatrix;
const auto lfmatrix = conf->LFMatrix;
@ -388,6 +376,10 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
* RB = Back right
* CE = Front center
* CB = Back center
* LFT = Top front left
* RFT = Top front right
* LBT = Top back left
* RBT = Top back right
*
* Additionally, surround51 will acknowledge back speakers for side
* channels, to avoid issues with an ambdec expecting 5.1 to use the
@ -410,23 +402,38 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight;
else if(speaker.Name == "CB")
ch = BackCenter;
else if(speaker.Name == "LFT")
ch = TopFrontLeft;
else if(speaker.Name == "RFT")
ch = TopFrontRight;
else if(speaker.Name == "LBT")
ch = TopBackLeft;
else if(speaker.Name == "RBT")
ch = TopBackRight;
else
{
ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str());
continue;
int idx{};
char c{};
if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0
|| idx >= MaxChannels-Aux0)
{
ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str());
continue;
}
ch = static_cast<Channel>(Aux0+idx);
}
decoder.mChannels[chan_count] = ch;
for(size_t src{0};src < num_coeffs;++src)
for(size_t dst{0};dst < num_coeffs;++dst)
{
const size_t dst{idx_map[src]};
const size_t src{idx_map[dst]};
decoder.mCoeffs[chan_count][dst] = hfmatrix[chan_count][src];
}
if(conf->FreqBands > 1)
{
for(size_t src{0};src < num_coeffs;++src)
for(size_t dst{0};dst < num_coeffs;++dst)
{
const size_t dst{idx_map[src]};
const size_t src{idx_map[dst]};
decoder.mCoeffsLF[chan_count][dst] = lfmatrix[chan_count][src];
}
}
@ -466,21 +473,21 @@ constexpr DecoderConfig<SingleBand, 2> StereoConfig{
}}
};
constexpr DecoderConfig<DualBand, 4> QuadConfig{
2, false, {{BackLeft, FrontLeft, FrontRight, BackRight}},
1, false, {{BackLeft, FrontLeft, FrontRight, BackRight}},
DevAmbiScaling::N3D,
/*HF*/{{1.15470054e+0f, 1.00000000e+0f, 5.77350269e-1f}},
/*HF*/{{1.41421356e+0f, 1.00000000e+0f}},
{{
{{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}},
{{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}},
{{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}},
{{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}},
}},
/*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}},
/*LF*/{{1.00000000e+0f, 1.00000000e+0f}},
{{
{{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}},
{{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}},
{{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}},
{{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}},
{{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}},
}}
};
constexpr DecoderConfig<DualBand, 5> X51Config{
@ -516,32 +523,71 @@ constexpr DecoderConfig<SingleBand, 5> X61Config{
}}
};
constexpr DecoderConfig<DualBand, 6> X71Config{
3, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}},
2, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}},
DevAmbiScaling::N3D,
/*HF*/{{1.22474487e+0f, 1.13151672e+0f, 8.66025404e-1f, 4.68689571e-1f}},
/*HF*/{{1.41421356e+0f, 1.22474487e+0f, 7.07106781e-1f}},
{{
{{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, -7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, 7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}},
{{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}},
{{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}},
{{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}},
{{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}},
{{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}},
}},
/*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}},
/*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}},
{{
{{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, -7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, 7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}},
{{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}},
{{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}},
{{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}},
{{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}},
{{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}},
{{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}},
}}
};
constexpr DecoderConfig<DualBand, 6> X3D71Config{
1, true, {{Aux0, SideLeft, FrontLeft, FrontRight, SideRight, Aux1}},
DevAmbiScaling::N3D,
/*HF*/{{1.73205081e+0f, 1.00000000e+0f}},
{{
{{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}},
{{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}},
{{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}},
{{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}},
{{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}},
{{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}},
}},
/*LF*/{{1.00000000e+0f, 1.00000000e+0f}},
{{
{{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}},
{{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}},
{{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}},
{{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}},
{{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}},
{{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}},
}}
};
constexpr DecoderConfig<SingleBand, 10> X714Config{
1, true, {{FrontLeft, FrontRight, SideLeft, SideRight, BackLeft, BackRight, TopFrontLeft, TopFrontRight, TopBackLeft, TopBackRight }},
DevAmbiScaling::N3D,
{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}},
{{
{{1.27149251e-01f, 7.63047539e-02f, -3.64373750e-02f, 1.59700680e-01f}},
{{1.07005418e-01f, -7.67638760e-02f, -4.92129762e-02f, 1.29012797e-01f}},
{{1.26400196e-01f, 1.77494694e-01f, -3.71203389e-02f, 0.00000000e+00f}},
{{1.26396516e-01f, -1.77488059e-01f, -3.71297878e-02f, 0.00000000e+00f}},
{{1.06996956e-01f, 7.67615256e-02f, -4.92166307e-02f, -1.29001640e-01f}},
{{1.27145671e-01f, -7.63003471e-02f, -3.64353304e-02f, -1.59697510e-01f}},
{{8.80919747e-02f, 7.48940670e-02f, 9.08786244e-02f, 6.22527183e-02f}},
{{1.57880745e-01f, -7.28755272e-02f, 1.82364187e-01f, 8.74240284e-02f}},
{{1.57892225e-01f, 7.28944768e-02f, 1.82363474e-01f, -8.74301086e-02f}},
{{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}},
}}
};
void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false,
DecoderView decoder={})
{
if(!decoder.mOrder)
if(!decoder)
{
switch(device->FmtChans)
{
@ -551,6 +597,8 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
case DevFmtX51: decoder = X51Config; break;
case DevFmtX61: decoder = X61Config; break;
case DevFmtX71: decoder = X71Config; break;
case DevFmtX714: decoder = X714Config; break;
case DevFmtX3D71: decoder = X3D71Config; break;
case DevFmtAmbi3D:
auto&& acnmap = GetAmbiLayout(device->mAmbiLayout);
auto&& n3dscale = GetAmbiScales(device->mAmbiScale);
@ -561,59 +609,59 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
[&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f/n3dscale[acn], acn}; });
AllocChannels(device, count, 0);
device->m2DMixing = false;
float nfc_delay{device->configValue<float>("decoder", "nfc-ref-delay").value_or(0.0f)};
if(nfc_delay > 0.0f)
InitNearFieldCtrl(device, nfc_delay * SpeedOfSoundMetersPerSec, device->mAmbiOrder,
true);
float avg_dist{};
if(auto distopt = device->configValue<float>("decoder", "speaker-dist"))
avg_dist = *distopt;
else if(auto delayopt = device->configValue<float>("decoder", "nfc-ref-delay"))
{
WARN("nfc-ref-delay is deprecated, use speaker-dist instead\n");
avg_dist = *delayopt * SpeedOfSoundMetersPerSec;
}
InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true);
return;
}
}
const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) :
Ambi2DChannelsFromOrder(decoder.mOrder)};
const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()};
al::vector<ChannelDec> chancoeffs, chancoeffslf;
for(size_t i{0u};i < decoder.mChannels.size();++i)
{
const uint idx{GetChannelIdxByName(device->RealOut, decoder.mChannels[i])};
if(idx == INVALID_CHANNEL_INDEX)
const uint idx{device->channelIdxByName(decoder.mChannels[i])};
if(idx == InvalidChannelIndex)
{
ERR("Failed to find %s channel in device\n",
GetLabelFromChannel(decoder.mChannels[i]));
continue;
}
auto ordermap = decoder.mIs3D ? AmbiIndex::OrderFromChannel().data()
: AmbiIndex::OrderFrom2DChannel().data();
chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{});
al::span<float,MaxAmbiChannels> coeffs{chancoeffs[idx]};
size_t ambichan{0};
for(uint o{0};o < decoder.mOrder+1u;++o)
{
const float order_gain{decoder.mOrderGain[o]};
const size_t order_max{decoder.mIs3D ? AmbiChannelsFromOrder(o) :
Ambi2DChannelsFromOrder(o)};
for(;ambichan < order_max;++ambichan)
coeffs[ambichan] = decoder.mCoeffs[i][ambichan] * order_gain;
}
al::span<const float,MaxAmbiChannels> src{decoder.mCoeffs[i]};
al::span<float,MaxAmbiChannels> dst{chancoeffs[idx]};
for(size_t ambichan{0};ambichan < ambicount;++ambichan)
dst[ambichan] = src[ambichan] * decoder.mOrderGain[ordermap[ambichan]];
if(!dual_band)
continue;
chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{});
coeffs = chancoeffslf[idx];
ambichan = 0;
for(uint o{0};o < decoder.mOrder+1u;++o)
{
const float order_gain{decoder.mOrderGainLF[o]};
const size_t order_max{decoder.mIs3D ? AmbiChannelsFromOrder(o) :
Ambi2DChannelsFromOrder(o)};
for(;ambichan < order_max;++ambichan)
coeffs[ambichan] = decoder.mCoeffsLF[i][ambichan] * order_gain;
}
src = decoder.mCoeffsLF[i];
dst = chancoeffslf[idx];
for(size_t ambichan{0};ambichan < ambicount;++ambichan)
dst[ambichan] = src[ambichan] * decoder.mOrderGainLF[ordermap[ambichan]];
}
/* For non-DevFmtAmbi3D, set the ambisonic order. */
device->mAmbiOrder = decoder.mOrder;
device->m2DMixing = !decoder.mIs3D;
const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) :
Ambi2DChannelsFromOrder(decoder.mOrder)};
const al::span<const uint8_t> acnmap{decoder.mIs3D ? AmbiIndex::FromACN().data() :
AmbiIndex::FromACN2D().data(), ambicount};
auto&& coeffscale = GetAmbiScales(decoder.mScaling);
@ -649,6 +697,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
TRACE("Enabling %s-band %s-order%s ambisonic decoder\n",
!dual_band ? "single" : "dual",
(decoder.mOrder > 3) ? "fourth" :
(decoder.mOrder > 2) ? "third" :
(decoder.mOrder > 1) ? "second" : "first",
decoder.mIs3D ? " periphonic" : "");
@ -662,10 +711,13 @@ void InitHrtfPanning(ALCdevice *device)
constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/};
constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/};
constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/};
constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/};
constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/};
constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/};
constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/};
constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/};
constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/};
constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/};
constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/};
static const AngularPoint AmbiPoints1O[]{
{ EvRadians{ Deg_35}, AzRadians{-Deg_45} },
{ EvRadians{ Deg_35}, AzRadians{-Deg135} },
@ -676,20 +728,18 @@ void InitHrtfPanning(ALCdevice *device)
{ EvRadians{-Deg_35}, AzRadians{ Deg_45} },
{ EvRadians{-Deg_35}, AzRadians{ Deg135} },
}, AmbiPoints2O[]{
{ EvRadians{ 0.0f}, AzRadians{ 0.0f} },
{ EvRadians{ 0.0f}, AzRadians{ Deg180} },
{ EvRadians{ 0.0f}, AzRadians{-Deg_90} },
{ EvRadians{ 0.0f}, AzRadians{ Deg_90} },
{ EvRadians{ Deg_90}, AzRadians{ 0.0f} },
{ EvRadians{-Deg_90}, AzRadians{ 0.0f} },
{ EvRadians{ Deg_35}, AzRadians{-Deg_45} },
{ EvRadians{ Deg_35}, AzRadians{-Deg135} },
{ EvRadians{ Deg_35}, AzRadians{ Deg_45} },
{ EvRadians{ Deg_35}, AzRadians{ Deg135} },
{ EvRadians{-Deg_35}, AzRadians{-Deg_45} },
{ EvRadians{-Deg_35}, AzRadians{-Deg135} },
{ EvRadians{-Deg_35}, AzRadians{ Deg_45} },
{ EvRadians{-Deg_35}, AzRadians{ Deg135} },
{ EvRadians{-Deg_32}, AzRadians{ 0.0f} },
{ EvRadians{ 0.0f}, AzRadians{ Deg_58} },
{ EvRadians{ Deg_58}, AzRadians{ Deg_90} },
{ EvRadians{ Deg_32}, AzRadians{ 0.0f} },
{ EvRadians{ 0.0f}, AzRadians{ Deg122} },
{ EvRadians{-Deg_58}, AzRadians{-Deg_90} },
{ EvRadians{-Deg_32}, AzRadians{ Deg180} },
{ EvRadians{ 0.0f}, AzRadians{-Deg122} },
{ EvRadians{ Deg_58}, AzRadians{-Deg_90} },
{ EvRadians{ Deg_32}, AzRadians{ Deg180} },
{ EvRadians{ 0.0f}, AzRadians{-Deg_58} },
{ EvRadians{-Deg_58}, AzRadians{ Deg_90} },
}, AmbiPoints3O[]{
{ EvRadians{ Deg_69}, AzRadians{-Deg_90} },
{ EvRadians{ Deg_69}, AzRadians{ Deg_90} },
@ -722,20 +772,18 @@ void InitHrtfPanning(ALCdevice *device)
{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f },
{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f },
}, AmbiMatrix2O[][MaxAmbiChannels]{
{ 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, },
{ 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, },
{ 7.142857143e-02f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, },
{ 7.142857143e-02f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, },
{ 7.142857143e-02f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, },
{ 7.142857143e-02f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, },
{ 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, },
{ 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, },
{ 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, },
{ 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, },
{ 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, },
{ 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, },
{ 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, },
{ 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, },
{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, },
{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, },
{ 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, },
{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, },
{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, },
{ 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, },
{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, },
{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, },
{ 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, },
{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, },
{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, },
{ 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, },
}, AmbiMatrix3O[][MaxAmbiChannels]{
{ 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, },
{ 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, },
@ -761,13 +809,13 @@ void InitHrtfPanning(ALCdevice *device)
static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{
/*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f
}, AmbiOrderHFGain2O[MaxAmbiOrder+1]{
/*ENRGY 2.357022604e+00f, 1.825741858e+00f, 9.428090416e-01f*/
/*ENRGY*/ 1.825741858e+00f, 1.414213562e+00f, 7.302967433e-01f
/*AMP 1.000000000e+00f, 7.745966692e-01f, 4.000000000e-01f*/
/*RMS*/ 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f
/*RMS 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f*/
}, AmbiOrderHFGain3O[MaxAmbiOrder+1]{
/*ENRGY 1.865086714e+00f, 1.606093894e+00f, 1.142055301e+00f, 5.683795528e-01f*/
/*AMP 1.000000000e+00f, 8.611363116e-01f, 6.123336207e-01f, 3.047469850e-01f*/
/*RMS*/ 8.340921354e-01f, 7.182670250e-01f, 5.107426573e-01f, 2.541870634e-01f
/*AMP*/ 1.000000000e+00f, 8.611363116e-01f, 6.123336207e-01f, 3.047469850e-01f
/*RMS 8.340921354e-01f, 7.182670250e-01f, 5.107426573e-01f, 2.541870634e-01f*/
};
static_assert(al::size(AmbiPoints1O) == al::size(AmbiMatrix1O), "First-Order Ambisonic HRTF mismatch");
@ -832,11 +880,13 @@ void InitHrtfPanning(ALCdevice *device)
(device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "",
device->mHrtfName.c_str());
bool perHrirMin{false};
al::span<const AngularPoint> AmbiPoints{AmbiPoints1O};
const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O};
al::span<const float,MaxAmbiOrder+1> AmbiOrderHFGain{AmbiOrderHFGain1O};
if(ambi_order >= 3)
{
perHrirMin = true;
AmbiPoints = AmbiPoints3O;
AmbiMatrix = AmbiMatrix3O;
AmbiOrderHFGain = AmbiOrderHFGain3O;
@ -848,6 +898,7 @@ void InitHrtfPanning(ALCdevice *device)
AmbiOrderHFGain = AmbiOrderHFGain2O;
}
device->mAmbiOrder = ambi_order;
device->m2DMixing = false;
const size_t count{AmbiChannelsFromOrder(ambi_order)};
std::transform(AmbiIndex::FromACN().begin(), AmbiIndex::FromACN().begin()+count,
@ -858,11 +909,11 @@ void InitHrtfPanning(ALCdevice *device)
HrtfStore *Hrtf{device->mHrtf.get()};
auto hrtfstate = DirectHrtfState::Create(count);
hrtfstate->build(Hrtf, device->mIrSize, AmbiPoints, AmbiMatrix, device->mXOverFreq,
hrtfstate->build(Hrtf, device->mIrSize, perHrirMin, AmbiPoints, AmbiMatrix, device->mXOverFreq,
AmbiOrderHFGain);
device->mHrtfState = std::move(hrtfstate);
InitNearFieldCtrl(device, Hrtf->field[0].distance, ambi_order, true);
InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true);
}
void InitUhjPanning(ALCdevice *device)
@ -871,8 +922,9 @@ void InitUhjPanning(ALCdevice *device)
constexpr size_t count{Ambi2DChannelsFromOrder(1)};
device->mAmbiOrder = 1;
device->m2DMixing = true;
auto acnmap_begin = AmbiIndex::FromFuMa().begin();
auto acnmap_begin = AmbiIndex::FromFuMa2D().begin();
std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap),
[](const uint8_t &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; });
@ -891,6 +943,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
device->mIrSize = 0;
device->mHrtfName.clear();
device->mXOverFreq = 400.0f;
device->m2DMixing = false;
device->mRenderMode = RenderMode::Normal;
if(device->FmtChans != DevFmtStereo)
@ -906,6 +959,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
case DevFmtX51: layout = "surround51"; break;
case DevFmtX61: layout = "surround61"; break;
case DevFmtX71: layout = "surround71"; break;
case DevFmtX714: layout = "surround714"; break;
case DevFmtX3D71: layout = "surround3d71"; break;
/* Mono, Stereo, and Ambisonics output don't use custom decoders. */
case DevFmtMono:
case DevFmtStereo:
@ -915,7 +970,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
std::unique_ptr<DecoderConfig<DualBand,MAX_OUTPUT_CHANNELS>> decoder_store;
DecoderView decoder{};
float speakerdists[MaxChannels]{};
float speakerdists[MAX_OUTPUT_CHANNELS]{};
auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config)
{
AmbDecConf conf{};
@ -949,13 +1004,13 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
/* Enable the stablizer only for formats that have front-left, front-
* right, and front-center outputs.
*/
const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != INVALID_CHANNEL_INDEX
&& device->RealOut.ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX
&& device->RealOut.ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX
&& device->getConfigValueBool(nullptr, "front-stablizer", 0) != 0};
const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", 1) != 0};
const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != InvalidChannelIndex
&& device->RealOut.ChannelIndex[FrontLeft] != InvalidChannelIndex
&& device->RealOut.ChannelIndex[FrontRight] != InvalidChannelIndex
&& device->getConfigValueBool(nullptr, "front-stablizer", false) != 0};
const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true) != 0};
InitPanning(device, hqdec, stablize, decoder);
if(decoder.mOrder > 0)
if(decoder)
{
float accum_dist{0.0f}, spkr_count{0.0f};
for(auto dist : speakerdists)
@ -966,11 +1021,13 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
spkr_count += 1.0f;
}
}
const float avg_dist{(accum_dist > 0.0f && spkr_count > 0) ? accum_dist/spkr_count :
device->configValue<float>("decoder", "speaker-dist").value_or(1.0f)};
InitNearFieldCtrl(device, avg_dist, decoder.mOrder, decoder.mIs3D);
if(spkr_count > 0)
{
InitNearFieldCtrl(device, accum_dist / spkr_count, decoder.mOrder, decoder.mIs3D);
InitDistanceComp(device, decoder.mChannels, speakerdists);
}
}
if(auto *ambidec{device->AmbiDecoder.get()})
{
@ -1018,7 +1075,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
old_hrtf = nullptr;
HrtfStore *hrtf{device->mHrtf.get()};
device->mIrSize = hrtf->irSize;
device->mIrSize = hrtf->mIrSize;
if(auto hrtfsizeopt = device->configValue<uint>(nullptr, "hrtf-size"))
{
if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize)
@ -1035,7 +1092,20 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj)
{
device->mUhjEncoder = std::make_unique<UhjEncoder>();
switch(UhjEncodeQuality)
{
case UhjQualityType::IIR:
device->mUhjEncoder = std::make_unique<UhjEncoderIIR>();
break;
case UhjQualityType::FIR256:
device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength256>>();
break;
case UhjQualityType::FIR512:
device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLength512>>();
break;
}
assert(device->mUhjEncoder != nullptr);
TRACE("UHJ enabled\n");
InitUhjPanning(device);
device->PostProcess = &ALCdevice::ProcessUhj;
@ -1071,49 +1141,12 @@ void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context)
DeviceBase *device{context->mDevice};
const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)};
auto wetbuffer_iter = context->mWetBuffers.end();
if(slot->mWetBuffer)
{
/* If the effect slot already has a wet buffer attached, allocate a new
* one in its place.
*/
wetbuffer_iter = context->mWetBuffers.begin();
for(;wetbuffer_iter != context->mWetBuffers.end();++wetbuffer_iter)
{
if(wetbuffer_iter->get() == slot->mWetBuffer)
{
slot->mWetBuffer = nullptr;
slot->Wet.Buffer = {};
*wetbuffer_iter = WetBufferPtr{new(FamCount(count)) WetBuffer{count}};
break;
}
}
}
if(wetbuffer_iter == context->mWetBuffers.end())
{
/* Otherwise, search for an unused wet buffer. */
wetbuffer_iter = context->mWetBuffers.begin();
for(;wetbuffer_iter != context->mWetBuffers.end();++wetbuffer_iter)
{
if(!(*wetbuffer_iter)->mInUse)
break;
}
if(wetbuffer_iter == context->mWetBuffers.end())
{
/* Otherwise, allocate a new one to use. */
context->mWetBuffers.emplace_back(WetBufferPtr{new(FamCount(count)) WetBuffer{count}});
wetbuffer_iter = context->mWetBuffers.end()-1;
}
}
WetBuffer *wetbuffer{slot->mWetBuffer = wetbuffer_iter->get()};
wetbuffer->mInUse = true;
slot->mWetBuffer.resize(count);
auto acnmap_begin = AmbiIndex::FromACN().begin();
auto iter = std::transform(acnmap_begin, acnmap_begin + count, slot->Wet.AmbiMap.begin(),
[](const uint8_t &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f, acn}; });
std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{});
slot->Wet.Buffer = wetbuffer->mBuffer;
slot->Wet.Buffer = slot->mWetBuffer;
}