update openal

This commit is contained in:
AzaezelX 2024-06-30 14:35:57 -05:00
parent 62f3b93ff9
commit 6721a6b021
287 changed files with 33851 additions and 27325 deletions

File diff suppressed because it is too large Load diff

View file

@ -22,9 +22,6 @@
#include "alconfig.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
@ -34,25 +31,47 @@
#endif
#include <algorithm>
#include <cstdio>
#include <array>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <istream>
#include <limits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "alfstream.h"
#include "almalloc.h"
#include "alstring.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "strutils.h"
#include "vector.h"
#if defined(ALSOFT_UWP)
#include <winrt/Windows.Media.Core.h> // !!This is important!!
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
using namespace winrt;
#endif
namespace {
using namespace std::string_view_literals;
#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP)
struct CoTaskMemDeleter {
void operator()(void *mem) const { CoTaskMemFree(mem); }
};
#endif
struct ConfigEntry {
std::string key;
std::string value;
};
al::vector<ConfigEntry> ConfOpts;
std::vector<ConfigEntry> ConfOpts;
std::string &lstrip(std::string &line)
@ -72,57 +91,48 @@ bool readline(std::istream &f, std::string &output)
return std::getline(f, output) && !output.empty();
}
std::string expdup(const char *str)
std::string expdup(std::string_view str)
{
std::string output;
std::string envval;
while(*str != '\0')
while(!str.empty())
{
const char *addstr;
size_t addstrlen;
if(str[0] != '$')
if(auto nextpos = str.find('$'))
{
const char *next = std::strchr(str, '$');
addstr = str;
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
output += str.substr(0, nextpos);
if(nextpos == std::string_view::npos)
break;
str += addstrlen;
str.remove_prefix(nextpos);
}
else
str.remove_prefix(1);
if(str.empty())
{
str++;
if(*str == '$')
{
const char *next = std::strchr(str+1, '$');
addstr = str;
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
str += addstrlen;
}
else
{
const bool hasbraces{(*str == '{')};
if(hasbraces) str++;
const char *envstart = str;
while(std::isalnum(*str) || *str == '_')
++str;
if(hasbraces && *str != '}')
continue;
const std::string envname{envstart, str};
if(hasbraces) str++;
envval = al::getenv(envname.c_str()).value_or(std::string{});
addstr = envval.data();
addstrlen = envval.length();
}
output += '$';
break;
}
if(addstrlen == 0)
if(str.front() == '$')
{
output += '$';
str.remove_prefix(1);
continue;
}
output.append(addstr, addstrlen);
const bool hasbraces{str.front() == '{'};
if(hasbraces) str.remove_prefix(1);
size_t envend{0};
while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_'))
++envend;
if(hasbraces && (envend == str.size() || str[envend] != '}'))
continue;
const std::string envname{str.substr(0, envend)};
if(hasbraces) ++envend;
str.remove_prefix(envend);
if(auto envval = al::getenv(envname.c_str()))
output += *envval;
}
return output;
@ -140,44 +150,43 @@ void LoadConfigFromFile(std::istream &f)
if(buffer[0] == '[')
{
auto line = const_cast<char*>(buffer.data());
char *section = line+1;
char *endsection;
endsection = std::strchr(section, ']');
if(!endsection || section == endsection)
auto endpos = buffer.find(']', 1);
if(endpos == 1 || endpos == std::string::npos)
{
ERR(" config parse error: bad line \"%s\"\n", line);
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
continue;
}
if(endsection[1] != 0)
if(buffer[endpos+1] != '\0')
{
char *end = endsection+1;
while(std::isspace(*end))
++end;
if(*end != 0 && *end != '#')
size_t last{endpos+1};
while(last < buffer.size() && std::isspace(buffer[last]))
++last;
if(last < buffer.size() && buffer[last] != '#')
{
ERR(" config parse error: bad line \"%s\"\n", line);
ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
continue;
}
}
*endsection = 0;
auto section = std::string_view{buffer}.substr(1, endpos-1);
curSection.clear();
if(al::strcasecmp(section, "general") != 0)
if(al::case_compare(section, "general"sv) != 0)
{
do {
char *nextp = std::strchr(section, '%');
if(!nextp)
auto nextp = section.find('%');
if(nextp == std::string_view::npos)
{
curSection += section;
break;
}
curSection.append(section, nextp);
section = nextp;
curSection += section.substr(0, nextp);
section.remove_prefix(nextp);
if(((section[1] >= '0' && section[1] <= '9') ||
if(section.size() > 2 &&
((section[1] >= '0' && section[1] <= '9') ||
(section[1] >= 'a' && section[1] <= 'f') ||
(section[1] >= 'A' && section[1] <= 'F')) &&
((section[2] >= '0' && section[2] <= '9') ||
@ -198,19 +207,19 @@ void LoadConfigFromFile(std::istream &f)
else if(section[2] >= 'A' && section[2] <= 'F')
b |= (section[2]-'A'+0x0a);
curSection += static_cast<char>(b);
section += 3;
section.remove_prefix(3);
}
else if(section[1] == '%')
else if(section.size() > 1 && section[1] == '%')
{
curSection += '%';
section += 2;
section.remove_prefix(2);
}
else
{
curSection += '%';
section += 1;
section.remove_prefix(1);
}
} while(*section != 0);
} while(!section.empty());
}
continue;
@ -228,16 +237,17 @@ void LoadConfigFromFile(std::istream &f)
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
auto keyend = sep++;
while(keyend > 0 && std::isspace(buffer[keyend-1]))
--keyend;
if(!keyend)
auto keypart = std::string_view{buffer}.substr(0, sep++);
while(!keypart.empty() && std::isspace(keypart.back()))
keypart.remove_suffix(1);
if(keypart.empty())
{
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
while(sep < buffer.size() && std::isspace(buffer[sep]))
sep++;
auto valpart = std::string_view{buffer}.substr(sep);
while(!valpart.empty() && std::isspace(valpart.front()))
valpart.remove_prefix(1);
std::string fullKey;
if(!curSection.empty())
@ -245,20 +255,24 @@ void LoadConfigFromFile(std::istream &f)
fullKey += curSection;
fullKey += '/';
}
fullKey += buffer.substr(0u, keyend);
fullKey += keypart;
std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
if(value.size() > 1)
if(valpart.size() > size_t{std::numeric_limits<int>::max()})
{
if((value.front() == '"' && value.back() == '"')
|| (value.front() == '\'' && value.back() == '\''))
ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str());
continue;
}
if(valpart.size() > 1)
{
if((valpart.front() == '"' && valpart.back() == '"')
|| (valpart.front() == '\'' && valpart.back() == '\''))
{
value.pop_back();
value.erase(value.begin());
valpart.remove_prefix(1);
valpart.remove_suffix(1);
}
}
TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data());
/* Check if we already have this option set */
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
@ -266,61 +280,49 @@ void LoadConfigFromFile(std::istream &f)
auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
if(ent != ConfOpts.end())
{
if(!value.empty())
ent->value = expdup(value.c_str());
if(!valpart.empty())
ent->value = expdup(valpart);
else
ConfOpts.erase(ent);
}
else if(!value.empty())
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
else if(!valpart.empty())
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)});
}
ConfOpts.shrink_to_fit();
}
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
const char *GetConfigValue(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName)
{
if(!keyName)
if(keyName.empty())
return nullptr;
std::string key;
if(blockName && al::strcasecmp(blockName, "general") != 0)
if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
{
key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/';
key += keyName;
}
else
if(!devName.empty())
{
if(devName)
{
key = devName;
key += '/';
}
key += keyName;
key += devName;
key += '/';
}
key += keyName;
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool
{ return entry.key == key; });
[&key](const ConfigEntry &entry) -> bool { return entry.key == key; });
if(iter != ConfOpts.cend())
{
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str());
if(!iter->value.empty())
return iter->value.c_str();
return nullptr;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
if(devName.empty())
return nullptr;
}
return GetConfigValue(nullptr, blockName, keyName);
return GetConfigValue({}, blockName, keyName);
}
} // namespace
@ -329,33 +331,48 @@ const char *GetConfigValue(const char *devName, const char *blockName, const cha
#ifdef _WIN32
void ReadALConfig()
{
WCHAR buffer[MAX_PATH];
if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
{
std::string filepath{wstr_to_utf8(buffer)};
filepath += "\\alsoft.ini";
namespace fs = std::filesystem;
fs::path path;
TRACE("Loading config %s...\n", filepath.c_str());
al::ifstream f{filepath};
if(f.is_open())
LoadConfigFromFile(f);
#if !defined(_GAMING_XBOX)
{
#if !defined(ALSOFT_UWP)
std::unique_ptr<WCHAR,CoTaskMemDeleter> bufstore;
const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
nullptr, al::out_ptr(bufstore))};
if(SUCCEEDED(hr))
{
const std::wstring_view buffer{bufstore.get()};
#else
winrt::Windows::Storage::ApplicationDataContainer localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings();
auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path();
std::wstring_view buffer{bufstore};
{
#endif
path = fs::path{buffer};
path /= L"alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
}
#endif
std::string ppath{GetProcBinary().path};
if(!ppath.empty())
path = fs::u8path(GetProcBinary().path);
if(!path.empty())
{
ppath += "\\alsoft.ini";
TRACE("Loading config %s...\n", ppath.c_str());
al::ifstream f{ppath};
if(f.is_open())
path /= "alsoft.ini";
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto confpath = al::getenv(L"ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
al::ifstream f{*confpath};
if(f.is_open())
path = *confpath;
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
}
@ -364,13 +381,12 @@ void ReadALConfig()
void ReadALConfig()
{
const char *str{"/etc/openal/alsoft.conf"};
namespace fs = std::filesystem;
fs::path path{"/etc/openal/alsoft.conf"};
TRACE("Loading config %s...\n", str);
al::ifstream f{str};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
f.close();
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
/* Go through the list in reverse, since "the order of base directories
@ -378,48 +394,43 @@ void ReadALConfig()
* important". Ergo, we need to load the settings from the later dirs
* first so that the settings in the earlier dirs override them.
*/
std::string fname;
while(!confpaths.empty())
{
auto next = confpaths.find_last_of(':');
auto next = confpaths.rfind(':');
if(next < confpaths.length())
{
fname = confpaths.substr(next+1);
path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal();
confpaths.erase(next);
}
else
{
fname = confpaths;
path = fs::path{confpaths}.lexically_normal();
confpaths.clear();
}
if(fname.empty() || fname.front() != '/')
WARN("Ignoring XDG config dir: %s\n", fname.c_str());
if(!path.is_absolute())
WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str());
else
{
if(fname.back() != '/') fname += "/alsoft.conf";
else fname += "alsoft.conf";
path /= "alsoft.conf";
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
fname.clear();
}
#ifdef __APPLE__
CFBundleRef mainBundle = CFBundleGetMainBundle();
if(mainBundle)
{
unsigned char fileName[PATH_MAX];
CFURLRef configURL;
CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""),
nullptr)};
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
std::array<unsigned char,PATH_MAX> fileName{};
if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size()))
{
f = al::ifstream{reinterpret_cast<char*>(fileName)};
if(f.is_open())
if(std::ifstream f{reinterpret_cast<char*>(fileName.data())}; f.is_open())
LoadConfigFromFile(f);
}
}
@ -427,102 +438,100 @@ void ReadALConfig()
if(auto homedir = al::getenv("HOME"))
{
fname = *homedir;
if(fname.back() != '/') fname += "/.alsoftrc";
else fname += ".alsoftrc";
path = *homedir;
path /= ".alsoftrc";
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
{
fname = *configdir;
if(fname.back() != '/') fname += "/alsoft.conf";
else fname += "alsoft.conf";
path = *configdir;
path /= "alsoft.conf";
}
else
{
fname.clear();
path.clear();
if(auto homedir = al::getenv("HOME"))
{
fname = *homedir;
if(fname.back() != '/') fname += "/.config/alsoft.conf";
else fname += ".config/alsoft.conf";
path = *homedir;
path /= ".config/alsoft.conf";
}
}
if(!fname.empty())
if(!path.empty())
{
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
std::string ppath{GetProcBinary().path};
if(!ppath.empty())
path = GetProcBinary().path;
if(!path.empty())
{
if(ppath.back() != '/') ppath += "/alsoft.conf";
else ppath += "alsoft.conf";
path /= "alsoft.conf";
TRACE("Loading config %s...\n", ppath.c_str());
f = al::ifstream{ppath};
if(f.is_open())
TRACE("Loading config %s...\n", path.u8string().c_str());
if(std::ifstream f{path}; f.is_open())
LoadConfigFromFile(f);
}
if(auto confname = al::getenv("ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", confname->c_str());
f = al::ifstream{*confname};
if(f.is_open())
if(std::ifstream f{*confname}; f.is_open())
LoadConfigFromFile(f);
}
}
#endif
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
std::optional<std::string> ConfigValueStr(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return val;
return al::nullopt;
return std::nullopt;
}
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<int>(std::strtol(val, nullptr, 0));
return al::nullopt;
return std::nullopt;
}
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
return al::nullopt;
return std::nullopt;
}
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
std::optional<float> ConfigValueFloat(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return std::strtof(val, nullptr);
return al::nullopt;
return std::nullopt;
}
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
std::optional<bool> ConfigValueBool(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0;
return al::nullopt;
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
return std::nullopt;
}
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName, bool def)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0);
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
return def;
}

View file

@ -1,18 +1,25 @@
#ifndef ALCONFIG_H
#define ALCONFIG_H
#include <optional>
#include <string>
#include <string_view>
#include "aloptional.h"
void ReadALConfig();
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def);
bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName, bool def);
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
std::optional<std::string> ConfigValueStr(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName);
std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<float> ConfigValueFloat(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<bool> ConfigValueBool(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
#endif /* ALCONFIG_H */

File diff suppressed because it is too large Load diff

View file

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

View file

@ -31,29 +31,33 @@
#include <exception>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include "albyte.h"
#include "albit.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <alsa/asoundlib.h>
namespace {
constexpr char alsaDevice[] = "ALSA Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; }
#ifdef HAVE_DYNLOAD
@ -248,59 +252,97 @@ struct DevMap {
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
const char *prefix_name(snd_pcm_stream_t stream)
std::string_view prefix_name(snd_pcm_stream_t stream) noexcept
{
assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE);
return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix";
if(stream == SND_PCM_STREAM_PLAYBACK)
return "device-prefix"sv;
return "capture-prefix"sv;
}
al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
struct SndCtlCardInfo {
snd_ctl_card_info_t *mInfo{};
SndCtlCardInfo() { snd_ctl_card_info_malloc(&mInfo); }
~SndCtlCardInfo() { if(mInfo) snd_ctl_card_info_free(mInfo); }
SndCtlCardInfo(const SndCtlCardInfo&) = delete;
SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete;
[[nodiscard]]
operator snd_ctl_card_info_t*() const noexcept { return mInfo; }
};
struct SndPcmInfo {
snd_pcm_info_t *mInfo{};
SndPcmInfo() { snd_pcm_info_malloc(&mInfo); }
~SndPcmInfo() { if(mInfo) snd_pcm_info_free(mInfo); }
SndPcmInfo(const SndPcmInfo&) = delete;
SndPcmInfo& operator=(const SndPcmInfo&) = delete;
[[nodiscard]]
operator snd_pcm_info_t*() const noexcept { return mInfo; }
};
struct SndCtl {
snd_ctl_t *mHandle{};
SndCtl() = default;
~SndCtl() { if(mHandle) snd_ctl_close(mHandle); }
SndCtl(const SndCtl&) = delete;
SndCtl& operator=(const SndCtl&) = delete;
[[nodiscard]]
auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); }
[[nodiscard]]
operator snd_ctl_t*() const noexcept { return mHandle; }
};
std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
{
al::vector<DevMap> devlist;
std::vector<DevMap> devlist;
snd_ctl_card_info_t *info;
snd_ctl_card_info_malloc(&info);
snd_pcm_info_t *pcminfo;
snd_pcm_info_malloc(&pcminfo);
SndCtlCardInfo info;
SndPcmInfo pcminfo;
auto defname = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default");
auto defname = ConfigValueStr({}, "alsa"sv,
(stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv);
devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv);
if(auto customdevs = ConfigValueStr(nullptr, "alsa",
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
if(auto customdevs = ConfigValueStr({}, "alsa"sv,
(stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv))
{
size_t nextpos{customdevs->find_first_not_of(';')};
size_t curpos;
while((curpos=nextpos) < customdevs->length())
size_t curpos{customdevs->find_first_not_of(';')};
while(curpos < customdevs->length())
{
nextpos = customdevs->find_first_of(';', curpos+1);
size_t seppos{customdevs->find_first_of('=', curpos)};
size_t nextpos{customdevs->find(';', curpos+1)};
const size_t seppos{customdevs->find('=', curpos)};
if(seppos == curpos || seppos >= nextpos)
{
std::string spec{customdevs->substr(curpos, nextpos-curpos)};
const std::string spec{customdevs->substr(curpos, nextpos-curpos)};
ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
}
else
{
devlist.emplace_back(customdevs->substr(curpos, seppos-curpos),
customdevs->substr(seppos+1, nextpos-seppos-1));
const auto &entry = devlist.back();
const std::string_view strview{*customdevs};
const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos),
strview.substr(seppos+1, nextpos-seppos-1));
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
if(nextpos < customdevs->length())
nextpos = customdevs->find_first_not_of(';', nextpos+1);
curpos = nextpos;
}
}
const std::string main_prefix{
ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")};
const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream))
.value_or("plughw:")};
int card{-1};
int err{snd_card_next(&card)};
@ -308,16 +350,17 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
{
std::string name{"hw:" + std::to_string(card)};
snd_ctl_t *handle;
if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0)
SndCtl handle;
err = handle.open(name.c_str(), 0);
if(err < 0)
{
ERR("control open (hw:%d): %s\n", card, snd_strerror(err));
continue;
}
if((err=snd_ctl_card_info(handle, info)) < 0)
err = snd_ctl_card_info(handle, info);
if(err < 0)
{
ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err));
snd_ctl_close(handle);
continue;
}
@ -326,8 +369,7 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
name = prefix_name(stream);
name += '-';
name += cardid;
const std::string card_prefix{
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)};
int dev{-1};
while(true)
@ -339,7 +381,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, stream);
if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0)
err = snd_ctl_pcm_info(handle, pcminfo);
if(err < 0)
{
if(err != -ENOENT)
ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err));
@ -352,8 +395,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
name += cardid;
name += '-';
name += std::to_string(dev);
const std::string device_prefix{
ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)};
const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name)
.value_or(card_prefix)};
/* "CardName, PcmName (CARD=cardid,DEV=dev)" */
name = cardname;
@ -372,18 +415,13 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
device += ",DEV=";
device += std::to_string(dev);
devlist.emplace_back(std::move(name), std::move(device));
const auto &entry = devlist.back();
const auto &entry = devlist.emplace_back(std::move(name), std::move(device));
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
snd_ctl_close(handle);
}
if(err < 0)
ERR("snd_card_next failed: %s\n", snd_strerror(err));
snd_pcm_info_free(pcminfo);
snd_ctl_card_info_free(info);
return devlist;
}
@ -392,7 +430,6 @@ int verify_state(snd_pcm_t *handle)
{
snd_pcm_state_t state{snd_pcm_state(handle)};
int err;
switch(state)
{
case SND_PCM_STATE_OPEN:
@ -405,15 +442,27 @@ int verify_state(snd_pcm_t *handle)
break;
case SND_PCM_STATE_XRUN:
if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0)
if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0)
return err;
break;
case SND_PCM_STATE_SUSPENDED:
if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0)
if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0)
return err;
break;
case SND_PCM_STATE_DISCONNECTED:
return -ENODEV;
/* ALSA headers have made this enum public, leaving us in a bind: use
* the enum despite being private and internal to the libasound, or
* ignore when an enum value isn't handled. We can't rely on it being
* declared either, since older headers don't have it and it could be
* removed in the future. We can't even really rely on its value, since
* being private/internal means it's subject to change, but this is the
* best we can do.
*/
case 1024 /*SND_PCM_STATE_PRIVATE1*/:
assert(state != 1024);
}
return state;
@ -427,7 +476,7 @@ struct AlsaPlayback final : public BackendBase {
int mixerProc();
int mixerNoMMapProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -439,12 +488,10 @@ struct AlsaPlayback final : public BackendBase {
std::mutex mMutex;
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(AlsaPlayback)
};
AlsaPlayback::~AlsaPlayback()
@ -458,7 +505,7 @@ AlsaPlayback::~AlsaPlayback()
int AlsaPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@ -506,7 +553,7 @@ int AlsaPlayback::mixerProc()
avail -= avail%update_size;
// it is possible that contiguous areas are smaller, thus we use a loop
std::lock_guard<std::mutex> _{mMutex};
std::lock_guard<std::mutex> dlock{mMutex};
while(avail > 0)
{
snd_pcm_uframes_t frames{avail};
@ -520,6 +567,7 @@ int AlsaPlayback::mixerProc()
break;
}
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep);
@ -541,7 +589,7 @@ int AlsaPlayback::mixerProc()
int AlsaPlayback::mixerNoMMapProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@ -585,13 +633,13 @@ int AlsaPlayback::mixerNoMMapProc()
continue;
}
al::byte *WritePtr{mBuffer.data()};
auto WritePtr = mBuffer.begin();
avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
std::lock_guard<std::mutex> dlock{mMutex};
mDevice->renderSamples(al::to_address(WritePtr), static_cast<uint>(avail), mFrameStep);
while(avail > 0)
{
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, al::to_address(WritePtr),
static_cast<snd_pcm_uframes_t>(avail))};
switch(ret)
{
@ -626,10 +674,10 @@ int AlsaPlayback::mixerNoMMapProc()
}
void AlsaPlayback::open(const char *name)
void AlsaPlayback::open(std::string_view name)
{
std::string driver{"default"};
if(name)
if(!name.empty())
{
if(PlaybackDevices.empty())
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
@ -638,13 +686,13 @@ void AlsaPlayback::open(const char *name)
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device"))
name = GetDefaultName();
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv))
driver = std::move(driveropt).value();
}
TRACE("Opening device \"%s\"\n", driver.c_str());
@ -692,15 +740,14 @@ bool AlsaPlayback::reset()
break;
}
bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)};
bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, 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};
int err{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -715,17 +762,18 @@ bool AlsaPlayback::reset()
/* test and set format (implicitly sets sample bits) */
if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0)
{
static const struct {
struct FormatMap {
snd_pcm_format_t format;
DevFmtType fmttype;
} formatlist[] = {
{ SND_PCM_FORMAT_FLOAT, DevFmtFloat },
{ SND_PCM_FORMAT_S32, DevFmtInt },
{ SND_PCM_FORMAT_U32, DevFmtUInt },
{ SND_PCM_FORMAT_S16, DevFmtShort },
{ SND_PCM_FORMAT_U16, DevFmtUShort },
{ SND_PCM_FORMAT_S8, DevFmtByte },
{ SND_PCM_FORMAT_U8, DevFmtUByte },
};
static constexpr std::array formatlist{
FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat },
FormatMap{SND_PCM_FORMAT_S32, DevFmtInt },
FormatMap{SND_PCM_FORMAT_U32, DevFmtUInt },
FormatMap{SND_PCM_FORMAT_S16, DevFmtShort },
FormatMap{SND_PCM_FORMAT_U16, DevFmtUShort},
FormatMap{SND_PCM_FORMAT_S8, DevFmtByte },
FormatMap{SND_PCM_FORMAT_U8, DevFmtUByte },
};
for(const auto &fmt : formatlist)
@ -750,7 +798,7 @@ bool AlsaPlayback::reset()
else mDevice->FmtChans = DevFmtStereo;
}
/* set rate (implicitly constrains period/buffer parameters) */
if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false)
if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false)
|| !mDevice->Flags.test(FrequencyRequest))
{
if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
@ -760,10 +808,10 @@ bool AlsaPlayback::reset()
WARN("Failed to enable ALSA resampler\n");
CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
/* set period time (implicitly constrains period/buffer parameters) */
if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0)
if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0)
ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err));
/* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */
if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0)
if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0)
ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err));
/* install and prepare hardware configuration */
CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
@ -798,11 +846,10 @@ bool AlsaPlayback::reset()
void AlsaPlayback::start()
{
int err{};
snd_pcm_access_t access{};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -849,10 +896,9 @@ void AlsaPlayback::stop()
ClockLatency AlsaPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0)
@ -871,23 +917,21 @@ struct AlsaCapture final : public BackendBase {
AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~AlsaCapture() override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
ClockLatency getClockLatency() override;
snd_pcm_t *mPcmHandle{nullptr};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
bool mDoCapture{false};
RingBufferPtr mRing{nullptr};
snd_pcm_sframes_t mLastAvail{0};
DEF_NEWDEL(AlsaCapture)
};
AlsaCapture::~AlsaCapture()
@ -898,10 +942,10 @@ AlsaCapture::~AlsaCapture()
}
void AlsaCapture::open(const char *name)
void AlsaCapture::open(std::string_view name)
{
std::string driver{"default"};
if(name)
if(!name.empty())
{
if(CaptureDevices.empty())
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
@ -910,19 +954,18 @@ void AlsaCapture::open(const char *name)
[name](const DevMap &entry) -> bool { return entry.name == name; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
driver = iter->device_name;
}
else
{
name = alsaDevice;
if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture"))
name = GetDefaultName();
if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv))
driver = std::move(driveropt).value();
}
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)
if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not open ALSA device \"%s\"", driver.c_str()};
@ -955,13 +998,15 @@ void AlsaCapture::open(const char *name)
break;
}
snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)};
snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)};
snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize,
100u*mDevice->Frequency/1000u)};
snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize,
25u*mDevice->Frequency/1000u)};
bool needring{false};
HwParamsPtr hp{CreateHwParams()};
#define CHECK(x) do { \
if((err=(x)) < 0) \
if(int err{x}; err < 0) \
throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
snd_strerror(err)}; \
} while(0)
@ -999,13 +1044,11 @@ void AlsaCapture::open(const char *name)
void AlsaCapture::start()
{
int err{snd_pcm_prepare(mPcmHandle)};
if(err < 0)
if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s",
snd_strerror(err)};
err = snd_pcm_start(mPcmHandle);
if(err < 0)
if(int err{snd_pcm_start(mPcmHandle)}; err < 0)
throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s",
snd_strerror(err)};
@ -1024,25 +1067,27 @@ void AlsaCapture::stop()
/* The ring buffer implicitly captures when checking availability.
* Direct access needs to explicitly capture it into temp storage.
*/
auto temp = al::vector<al::byte>(
auto temp = std::vector<std::byte>(
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail)));
captureSamples(temp.data(), avail);
mBuffer = std::move(temp);
}
int err{snd_pcm_drop(mPcmHandle)};
if(err < 0)
if(int err{snd_pcm_drop(mPcmHandle)}; err < 0)
ERR("drop failed: %s\n", snd_strerror(err));
mDoCapture = false;
}
void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
void AlsaCapture::captureSamples(std::byte *buffer, uint samples)
{
if(mRing)
{
mRing->read(buffer, samples);
std::ignore = mRing->read(buffer, samples);
return;
}
const auto outspan = al::span{buffer,
static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, samples))};
auto outiter = outspan.begin();
mLastAvail -= samples;
while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0)
{
@ -1055,20 +1100,21 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
if(static_cast<snd_pcm_uframes_t>(amt) > samples) amt = samples;
amt = snd_pcm_frames_to_bytes(mPcmHandle, amt);
std::copy_n(mBuffer.begin(), amt, buffer);
std::copy_n(mBuffer.begin(), amt, outiter);
mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt);
amt = snd_pcm_bytes_to_frames(mPcmHandle, amt);
}
else if(mDoCapture)
amt = snd_pcm_readi(mPcmHandle, buffer, samples);
amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples);
if(amt < 0)
{
ERR("read error: %s\n", snd_strerror(static_cast<int>(amt)));
if(amt == -EAGAIN)
continue;
if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
if(amt >= 0)
{
amt = snd_pcm_start(mPcmHandle);
if(amt >= 0)
@ -1088,12 +1134,12 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
continue;
}
buffer = buffer + amt;
outiter += amt;
samples -= static_cast<uint>(amt);
}
if(samples > 0)
std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples),
al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
std::fill_n(outiter, snd_pcm_frames_to_bytes(mPcmHandle, samples),
std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
}
uint AlsaCapture::availableSamples()
@ -1105,7 +1151,8 @@ uint AlsaCapture::availableSamples()
{
ERR("avail update failed: %s\n", snd_strerror(static_cast<int>(avail)));
if((avail=snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1)) >= 0)
avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1);
if(avail >= 0)
{
if(mDoCapture)
avail = snd_pcm_start(mPcmHandle);
@ -1141,7 +1188,8 @@ uint AlsaCapture::availableSamples()
if(amt == -EAGAIN)
continue;
if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
if(amt >= 0)
{
if(mDoCapture)
amt = snd_pcm_start(mPcmHandle);
@ -1168,9 +1216,8 @@ uint AlsaCapture::availableSamples()
ClockLatency AlsaCapture::getClockLatency()
{
ClockLatency ret;
ret.ClockTime = GetDeviceClockTime(mDevice);
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
snd_pcm_sframes_t delay{};
int err{snd_pcm_delay(mPcmHandle, &delay)};
if(err < 0)
@ -1189,13 +1236,9 @@ ClockLatency AlsaCapture::getClockLatency()
bool AlsaBackendFactory::init()
{
bool error{false};
#ifdef HAVE_DYNLOAD
if(!alsa_handle)
{
std::string missing_funcs;
alsa_handle = LoadLib("libasound.so.2");
if(!alsa_handle)
{
@ -1203,52 +1246,47 @@ bool AlsaBackendFactory::init()
return false;
}
error = false;
std::string missing_funcs;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f)); \
if(p##f == nullptr) { \
error = true; \
missing_funcs += "\n" #f; \
} \
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0)
ALSA_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
if(error)
if(!missing_funcs.empty())
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(alsa_handle);
alsa_handle = nullptr;
return false;
}
}
#endif
return !error;
return true;
}
bool AlsaBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string AlsaBackendFactory::probe(BackendType type)
auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
switch(type)
{
case BackendType::Playback:
PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct AlsaBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_ALSA_H */

View file

@ -7,17 +7,6 @@
#include <array>
#include <atomic>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#include "albit.h"
#include "core/logging.h"
#include "aloptional.h"
#endif
#include "atomic.h"
#include "core/devformat.h"
@ -25,10 +14,12 @@ namespace al {
backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
/* NOLINTBEGIN(*-array-to-pointer-decay) */
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
}
backend_exception::~backend_exception() = default;
@ -38,7 +29,7 @@ backend_exception::~backend_exception() = default;
bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
void BackendBase::captureSamples(al::byte*, uint)
void BackendBase::captureSamples(std::byte*, uint)
{ }
uint BackendBase::availableSamples()
@ -46,27 +37,26 @@ uint BackendBase::availableSamples()
ClockLatency BackendBase::getClockLatency()
{
ClockLatency ret;
ClockLatency ret{};
uint refcount;
do {
refcount = mDevice->waitForMix();
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.ClockTime = mDevice->getClockTime();
std::atomic_thread_fence(std::memory_order_acquire);
} while(refcount != ReadRef(mDevice->MixCount));
} while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed));
/* NOTE: The device will generally have about all but one periods filled at
* any given time during playback. Without a more accurate measurement from
* the output, this is an okay approximation.
*/
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
std::chrono::seconds::zero());
ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
return ret;
}
void BackendBase::setDefaultWFXChannelOrder()
void BackendBase::setDefaultWFXChannelOrder() const
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -126,6 +116,24 @@ void BackendBase::setDefaultWFXChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
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;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
@ -141,7 +149,7 @@ void BackendBase::setDefaultWFXChannelOrder()
}
}
void BackendBase::setDefaultChannelOrder()
void BackendBase::setDefaultChannelOrder() const
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
@ -179,6 +187,24 @@ void BackendBase::setDefaultChannelOrder()
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX7144:
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;
mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12;
mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13;
mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14;
mDevice->RealOut.ChannelIndex[BottomBackRight] = 15;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;

View file

@ -3,13 +3,16 @@
#include <chrono>
#include <cstdarg>
#include <cstddef>
#include <memory>
#include <ratio>
#include <string>
#include <string_view>
#include <vector>
#include "albyte.h"
#include "core/device.h"
#include "core/except.h"
#include "alc/events.h"
using uint = unsigned int;
@ -20,27 +23,33 @@ struct ClockLatency {
};
struct BackendBase {
virtual void open(const char *name) = 0;
virtual void open(std::string_view name) = 0;
virtual bool reset();
virtual void start() = 0;
virtual void stop() = 0;
virtual void captureSamples(al::byte *buffer, uint samples);
virtual void captureSamples(std::byte *buffer, uint samples);
virtual uint availableSamples();
virtual ClockLatency getClockLatency();
DeviceBase *const mDevice;
BackendBase() = delete;
BackendBase(const BackendBase&) = delete;
BackendBase(BackendBase&&) = delete;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default;
void operator=(const BackendBase&) = delete;
void operator=(BackendBase&&) = delete;
protected:
/** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
void setDefaultChannelOrder();
void setDefaultChannelOrder() const;
/** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder();
void setDefaultWFXChannelOrder() const;
};
using BackendPtr = std::unique_ptr<BackendBase>;
@ -50,18 +59,6 @@ enum class BackendType {
};
/* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate.
*/
inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
return device->ClockBase + ns;
}
/* Helper to get the device latency from the backend, including any fixed
* latency from post-processing.
*/
@ -74,16 +71,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
struct BackendFactory {
virtual bool init() = 0;
virtual bool querySupport(BackendType type) = 0;
virtual std::string probe(BackendType type) = 0;
virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected:
BackendFactory() = default;
BackendFactory(const BackendFactory&) = delete;
BackendFactory(BackendFactory&&) = delete;
virtual ~BackendFactory() = default;
void operator=(const BackendFactory&) = delete;
void operator=(BackendFactory&&) = delete;
virtual auto init() -> bool = 0;
virtual auto querySupport(BackendType type) -> bool = 0;
virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport
{ return alc::EventSupport::NoSupport; }
virtual auto enumerate(BackendType type) -> std::vector<std::string> = 0;
virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0;
};
namespace al {
@ -98,15 +103,15 @@ class backend_exception final : public base_exception {
backend_error mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...);
~backend_exception() override;
backend_error errorCode() const noexcept { return mErrorCode; }
[[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; }
};
} // namespace al

View file

@ -22,18 +22,20 @@
#include "coreaudio.h"
#include <inttypes.h>
#include <cinttypes>
#include <cmath>
#include <memory>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <unistd.h>
#include <cmath>
#include <memory>
#include <string>
#include <vector>
#include <optional>
#include "alnumeric.h"
#include "alstring.h"
#include "core/converter.h"
#include "core/device.h"
#include "core/logging.h"
@ -42,18 +44,40 @@
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
#if TARGET_OS_IOS || TARGET_OS_TV
#define CAN_ENUMERATE 0
#else
#include <IOKit/audio/IOAudioTypes.h>
#define CAN_ENUMERATE 1
#endif
namespace {
constexpr auto OutputElement = 0;
constexpr auto InputElement = 1;
struct FourCCPrinter {
char mString[sizeof(UInt32) + 1]{};
constexpr FourCCPrinter(UInt32 code) noexcept
{
for(size_t i{0};i < sizeof(UInt32);++i)
{
const auto ch = static_cast<char>(code & 0xff);
/* If this breaks early it'll leave the first byte null, to get
* read as a 0-length string.
*/
if(ch <= 0x1f || ch >= 0x7f)
break;
mString[sizeof(UInt32)-1-i] = ch;
code >>= 8;
}
}
constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
constexpr const char *c_str() const noexcept { return mString; }
};
#if CAN_ENUMERATE
struct DeviceEntry {
AudioDeviceID mId;
@ -147,7 +171,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
&propSize);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return 0;
}
@ -158,7 +183,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
buflist);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return 0;
}
@ -182,7 +208,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
{
ERR("Failed to get device list: %u\n", err);
ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return;
}
@ -247,6 +273,48 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
newdevs.swap(list);
}
struct DeviceHelper {
DeviceHelper()
{
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr)
ERR("AudioObjectAddPropertyListener fail: %d", status);
}
~DeviceHelper()
{
AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
if (status != noErr)
ERR("AudioObjectRemovePropertyListener fail: %d", status);
}
static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/)
{
for(UInt32 i = 0; i < inNumberAddresses; ++i)
{
switch(inAddresses[i].mSelector)
{
case kAudioHardwarePropertyDefaultOutputDevice:
case kAudioHardwarePropertyDefaultSystemOutputDevice:
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
"Default playback device changed: "+std::to_string(inAddresses[i].mSelector));
break;
case kAudioHardwarePropertyDefaultInputDevice:
alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
"Default capture device changed: "+std::to_string(inAddresses[i].mSelector));
break;
}
}
return noErr;
}
};
static std::optional<DeviceHelper> sDeviceHelper;
#else
static constexpr char ca_device[] = "CoreAudio Default";
@ -260,15 +328,8 @@ struct CoreAudioPlayback final : public BackendBase {
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept;
static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -277,8 +338,6 @@ struct CoreAudioPlayback final : public BackendBase {
uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
DEF_NEWDEL(CoreAudioPlayback)
};
CoreAudioPlayback::~CoreAudioPlayback()
@ -301,11 +360,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
}
void CoreAudioPlayback::open(const char *name)
void CoreAudioPlayback::open(std::string_view name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
if(name.empty())
GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
&audioDevice);
else
@ -318,16 +377,16 @@ void CoreAudioPlayback::open(const char *name)
auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
if(devmatch == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
audioDevice = devmatch->mId;
}
#else
if(!name)
if(name.empty())
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
#endif
/* open the default output unit */
@ -351,7 +410,7 @@ void CoreAudioPlayback::open(const char *name)
OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
@ -362,7 +421,7 @@ void CoreAudioPlayback::open(const char *name)
err = AudioUnitInitialize(audioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
/* WARNING: I don't know if "valid" audio unit values are guaranteed to be
* non-0. If not, this logic is broken.
@ -375,7 +434,7 @@ void CoreAudioPlayback::open(const char *name)
mAudioUnit = audioUnit;
#if CAN_ENUMERATE
if(name)
if(!name.empty())
mDevice->DeviceName = name;
else
{
@ -388,6 +447,21 @@ void CoreAudioPlayback::open(const char *name)
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name";
}
if(audioDevice != kAudioDeviceUnknown)
{
UInt32 type{};
err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
kAudioObjectPropertyElementMaster, sizeof(type), &type);
if(err != noErr)
ERR("Failed to get audio device type: %u\n", err);
else
{
TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str());
mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
}
}
#else
mDevice->DeviceName = name;
#endif
@ -397,7 +471,7 @@ bool CoreAudioPlayback::reset()
{
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
if(err != noErr)
ERR("-- AudioUnitUninitialize failed.\n");
ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
@ -406,7 +480,8 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, &size);
if(err != noErr || size != sizeof(streamFormat))
{
ERR("AudioUnitGetProperty failed\n");
ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
err);
return false;
}
@ -473,7 +548,8 @@ bool CoreAudioPlayback::reset()
OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
err);
return false;
}
@ -482,14 +558,16 @@ bool CoreAudioPlayback::reset()
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
input.inputProc = CoreAudioPlayback::MixerProcC;
input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
{ return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
FourCCPrinter{err}.c_str(), err);
return false;
}
@ -497,7 +575,7 @@ bool CoreAudioPlayback::reset()
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
{
ERR("AudioUnitInitialize failed\n");
ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return false;
}
@ -509,14 +587,14 @@ void CoreAudioPlayback::start()
const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
}
void CoreAudioPlayback::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
}
@ -527,18 +605,11 @@ struct CoreAudioCapture final : public BackendBase {
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
AudioUnit mAudioUnit{0};
@ -548,11 +619,9 @@ struct CoreAudioCapture final : public BackendBase {
SampleConverterPtr mConverter;
al::vector<char> mCaptureData;
std::vector<char> mCaptureData;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
};
CoreAudioCapture::~CoreAudioCapture()
@ -568,7 +637,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
AudioBufferList*) noexcept
{
union {
al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
@ -581,20 +650,20 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
ERR("AudioUnitRender capture error: %d\n", err);
ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
return err;
}
mRing->write(mCaptureData.data(), inNumberFrames);
std::ignore = mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
void CoreAudioCapture::open(const char *name)
void CoreAudioCapture::open(std::string_view name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
if(name.empty())
GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
&audioDevice);
else
@ -607,16 +676,16 @@ void CoreAudioCapture::open(const char *name)
auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
if(devmatch == CaptureList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
audioDevice = devmatch->mId;
}
#else
if(!name)
if(name.empty())
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
else if(name != ca_device)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
#endif
AudioComponentDescription desc{};
@ -640,7 +709,7 @@ void CoreAudioCapture::open(const char *name)
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
"Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Turn off AudioUnit output
UInt32 enableIO{0};
@ -648,7 +717,8 @@ void CoreAudioCapture::open(const char *name)
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};
"Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
// Turn on AudioUnit input
enableIO = 1;
@ -656,7 +726,8 @@ void CoreAudioCapture::open(const char *name)
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};
"Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
@ -666,14 +737,15 @@ void CoreAudioCapture::open(const char *name)
// set capture callback
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
{ return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set capture callback: %u", err};
"Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Disable buffer allocation for capture
UInt32 flag{0};
@ -681,13 +753,14 @@ void CoreAudioCapture::open(const char *name)
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};
"Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(),
err};
// Initialize the device
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
"Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Get the hardware format
AudioStreamBasicDescription hardwareFormat{};
@ -696,7 +769,7 @@ void CoreAudioCapture::open(const char *name)
InputElement, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err};
"Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
// Set up the requested format description
AudioStreamBasicDescription requestedFormat{};
@ -749,6 +822,7 @@ void CoreAudioCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
@ -777,14 +851,14 @@ void CoreAudioCapture::open(const char *name)
InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set input format: %u", err};
"Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
/* Calculate the minimum AudioUnit output format frame count for the pre-
* conversion ring buffer. Ensure at least 100ms for the total buffer.
*/
double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10);
auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
FrameCount64 += MaxResamplerPadding;
if(FrameCount64 > std::numeric_limits<int32_t>::max())
throw al::backend_exception{al::backend_error::DeviceError,
@ -796,11 +870,11 @@ void CoreAudioCapture::open(const char *name)
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};
"Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
mCaptureData.resize(outputFrameCount * mFrameSize);
outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
outputFrameCount = static_cast<UInt32>(std::max(uint64_t{outputFrameCount}, FrameCount64));
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
/* Set up sample converter if needed */
@ -810,7 +884,7 @@ void CoreAudioCapture::open(const char *name)
mDevice->Frequency, Resampler::FastBSinc24);
#if CAN_ENUMERATE
if(name)
if(!name.empty())
mDevice->DeviceName = name;
else
{
@ -834,21 +908,21 @@ void CoreAudioCapture::start()
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
"AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
}
void CoreAudioCapture::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
}
void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
{
if(!mConverter)
{
mRing->read(buffer, samples);
std::ignore = mRing->read(buffer, samples);
return;
}
@ -882,28 +956,34 @@ BackendFactory &CoreAudioBackendFactory::getFactory()
return factory;
}
bool CoreAudioBackendFactory::init() { return true; }
bool CoreAudioBackendFactory::init()
{
#if CAN_ENUMERATE
sDeviceHelper.emplace();
#endif
return true;
}
bool CoreAudioBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string CoreAudioBackendFactory::probe(BackendType type)
auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
#if CAN_ENUMERATE
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
switch(type)
{
case BackendType::Playback:
EnumerateDevices(PlaybackList, false);
outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
EnumerateDevices(CaptureList, true);
outnames.reserve(CaptureList.size());
std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
break;
}
@ -914,8 +994,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(ca_device, sizeof(ca_device));
outnames.emplace_back(ca_device);
break;
}
#endif
@ -930,3 +1009,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp
return BackendPtr{new CoreAudioCapture{device}};
return nullptr;
}
alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
{
switch(eventType)
{
case alc::EventType::DefaultDeviceChanged:
return alc::EventSupport::FullSupport;
case alc::EventType::DeviceAdded:
case alc::EventType::DeviceRemoved:
case alc::EventType::Count:
break;
}
return alc::EventSupport::NoSupport;
}

View file

@ -5,15 +5,17 @@
struct CoreAudioBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_COREAUDIO_H */

View file

@ -25,10 +25,6 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <cguid.h>
#include <mmreg.h>
#ifndef _WAVEFORMATEXTENSIBLE_
@ -36,15 +32,21 @@
#include <ksmedia.h>
#endif
#include <algorithm>
#include <atomic>
#include <cassert>
#include <thread>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <memory.h>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "comptr.h"
#include "core/device.h"
#include "core/helpers.h"
@ -52,7 +54,6 @@
#include "dynload.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
/* MinGW-w64 needs this for some unknown reason now. */
using LPCWAVEFORMATEX = const WAVEFORMATEX*;
@ -129,10 +130,10 @@ struct DevMap {
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
bool checkName(const al::vector<DevMap> &list, const std::string &name)
bool checkName(const al::span<DevMap> list, const std::string &name)
{
auto match_name = [&name](const DevMap &entry) -> bool
{ return entry.name == name; };
@ -144,7 +145,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
if(!guid)
return TRUE;
auto& devices = *static_cast<al::vector<DevMap>*>(data);
auto& devices = *static_cast<std::vector<DevMap>*>(data);
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
int count{1};
@ -176,7 +177,7 @@ struct DSoundPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -189,8 +190,6 @@ struct DSoundPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(DSoundPlayback)
};
DSoundPlayback::~DSoundPlayback()
@ -209,7 +208,7 @@ DSoundPlayback::~DSoundPlayback()
FORCE_ALIGN int DSoundPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
DSBCAPS DSBCaps{};
DSBCaps.dwSize = sizeof(DSBCaps);
@ -299,24 +298,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
return 0;
}
void DSoundPlayback::open(const char *name)
void DSoundPlayback::open(std::string_view name)
{
HRESULT hr;
if(PlaybackDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !PlaybackDevices.empty())
if(name.empty() && !PlaybackDevices.empty())
{
name = PlaybackDevices[0].name.c_str();
name = PlaybackDevices[0].name;
guid = &PlaybackDevices[0].guid;
}
else
@ -332,7 +329,7 @@ void DSoundPlayback::open(const char *name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
}
guid = &iter->guid;
}
@ -347,7 +344,7 @@ void DSoundPlayback::open(const char *name)
//DirectSound Init code
ComPtr<IDirectSound> ds;
if(SUCCEEDED(hr))
hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr);
if(SUCCEEDED(hr))
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr))
@ -425,49 +422,53 @@ bool DSoundPlayback::reset()
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
case DevFmtX7144: mDevice->FmtChans = DevFmtX714;
/* fall-through */
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;
do {
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)
{
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
if(FAILED(hr))
break;
if(SUCCEEDED(hr))
{
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
if(num_updates > MAX_UPDATES)
num_updates = MAX_UPDATES;
@ -480,26 +481,21 @@ retry_open:
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format;
hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
goto retry_open;
}
}
hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr);
if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat)
break;
mDevice->FmtType = DevFmtShort;
} while(FAILED(hr));
if(SUCCEEDED(hr))
{
void *ptr;
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies));
if(SUCCEEDED(hr))
{
mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots{};
for(uint i{0};i < num_updates;++i)
{
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
@ -550,10 +546,10 @@ struct DSoundCapture final : public BackendBase {
DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
ComPtr<IDirectSoundCapture> mDSC;
@ -562,8 +558,6 @@ struct DSoundCapture final : public BackendBase {
DWORD mCursor{0u};
RingBufferPtr mRing;
DEF_NEWDEL(DSoundCapture)
};
DSoundCapture::~DSoundCapture()
@ -577,24 +571,22 @@ DSoundCapture::~DSoundCapture()
}
void DSoundCapture::open(const char *name)
void DSoundCapture::open(std::string_view name)
{
HRESULT hr;
if(CaptureDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !CaptureDevices.empty())
if(name.empty() && !CaptureDevices.empty())
{
name = CaptureDevices[0].name.c_str();
name = CaptureDevices[0].name;
guid = &CaptureDevices[0].guid;
}
else
@ -610,7 +602,7 @@ void DSoundCapture::open(const char *name)
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
}
guid = &iter->guid;
}
@ -641,6 +633,7 @@ void DSoundCapture::open(const char *name)
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
@ -657,6 +650,7 @@ void DSoundCapture::open(const char *name)
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
InputType.Format.nBlockAlign;
InputType.Format.cbSize = 0;
/* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
if(mDevice->FmtType == DevFmtFloat)
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
@ -669,8 +663,7 @@ void DSoundCapture::open(const char *name)
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
}
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
DSCBUFFERDESC DSCBDescription{};
DSCBDescription.dwSize = sizeof(DSCBDescription);
@ -679,9 +672,9 @@ void DSoundCapture::open(const char *name)
DSCBDescription.lpwfxFormat = &InputType.Format;
//DirectSoundCapture Init code
hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr);
if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr);
if(SUCCEEDED(hr))
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
@ -719,8 +712,8 @@ void DSoundCapture::stop()
}
}
void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void DSoundCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint DSoundCapture::availableSamples()
{
@ -743,9 +736,9 @@ uint DSoundCapture::availableSamples()
}
if(SUCCEEDED(hr))
{
mRing->write(ReadPtr1, ReadCnt1/FrameSize);
std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize);
if(ReadPtr2 != nullptr && ReadCnt2 > 0)
mRing->write(ReadPtr2, ReadCnt2/FrameSize);
std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize);
hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
mCursor = ReadCursor;
}
@ -802,40 +795,32 @@ bool DSoundBackendFactory::init()
bool DSoundBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string DSoundBackendFactory::probe(BackendType type)
auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames.append(entry.name.c_str(), entry.name.length()+1);
};
{ outnames.emplace_back(entry.name); };
/* Initialize COM to prevent name truncation */
HRESULT hr;
HRESULT hrcom{CoInitialize(nullptr)};
ComWrapper com{};
switch(type)
{
case BackendType::Playback:
PlaybackDevices.clear();
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
if(SUCCEEDED(hrcom))
CoUninitialize();
return outnames;
}

View file

@ -5,15 +5,15 @@
struct DSoundBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_DSOUND_H */

View file

@ -22,23 +22,26 @@
#include "jack.h"
#include <array>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <memory.h>
#include <array>
#include <mutex>
#include <thread>
#include <functional>
#include <vector>
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include <jack/jack.h>
#include <jack/ringbuffer.h>
@ -46,6 +49,8 @@
namespace {
using namespace std::string_view_literals;
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
@ -99,19 +104,13 @@ decltype(jack_error_callback) * pjack_error_callback;
#endif
constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
jack_options_t ClientOptions = JackNullOption;
bool jack_load()
{
bool error{false};
#ifdef HAVE_DYNLOAD
if(!jack_handle)
{
std::string missing_funcs;
#ifdef _WIN32
#define JACKLIB "libjack.dll"
#else
@ -124,13 +123,10 @@ bool jack_load()
return false;
}
error = false;
std::string missing_funcs;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
error = true; \
missing_funcs += "\n" #f; \
} \
if(p##f == nullptr) missing_funcs += "\n" #f; \
} while(0)
JACK_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
@ -139,61 +135,66 @@ bool jack_load()
LOAD_SYM(jack_error_callback);
#undef LOAD_SYM
if(error)
if(!missing_funcs.empty())
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(jack_handle);
jack_handle = nullptr;
return false;
}
}
#endif
return !error;
return true;
}
struct JackDeleter {
void operator()(void *ptr) { jack_free(ptr); }
};
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>; /* NOLINT(*-avoid-c-arrays) */
struct DeviceEntry {
std::string mName;
std::string mPattern;
DeviceEntry() = default;
DeviceEntry(const DeviceEntry&) = default;
DeviceEntry(DeviceEntry&&) = default;
template<typename T, typename U>
DeviceEntry(T&& name, U&& pattern)
: mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
{ }
~DeviceEntry();
DeviceEntry& operator=(const DeviceEntry&) = default;
DeviceEntry& operator=(DeviceEntry&&) = default;
};
DeviceEntry::~DeviceEntry() = default;
al::vector<DeviceEntry> PlaybackList;
std::vector<DeviceEntry> PlaybackList;
void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
{
std::remove_reference_t<decltype(list)>{}.swap(list);
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)})
{
for(size_t i{0};ports[i];++i)
{
const char *sep{std::strchr(ports[i], ':')};
if(!sep || ports[i] == sep) continue;
const std::string_view portname{ports[i]};
const size_t seppos{portname.find(':')};
if(seppos == 0 || seppos >= portname.size())
continue;
const al::span<const char> portdev{ports[i], sep};
const auto portdev = portname.substr(0, seppos);
auto check_name = [portdev](const DeviceEntry &entry) -> bool
{
const size_t len{portdev.size()};
return entry.mName.length() == len
&& entry.mName.compare(0, len, portdev.data(), len) == 0;
};
{ return entry.mName == portdev; };
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(name, name+":");
const auto &entry = list.back();
const auto &entry = list.emplace_back(portdev, std::string{portdev}+":");
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
/* There are ports but couldn't get device names from them. Add a
@ -202,11 +203,11 @@ 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("JACK", "");
list.emplace_back("JACK"sv, ""sv);
}
}
if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
if(auto listopt = ConfigValueStr({}, "jack", "custom-devices"))
{
for(size_t strpos{0};strpos < listopt->size();)
{
@ -214,38 +215,32 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
size_t seppos{listopt->find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{
const std::string entry{listopt->substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos);
ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data());
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
continue;
}
const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
const al::span<const char> pattern{listopt->data()+(seppos+1),
std::min(nextpos, listopt->size())-(seppos+1)};
const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos);
const auto pattern = std::string_view{*listopt}.substr(seppos+1,
std::min(nextpos, listopt->size())-(seppos+1));
/* Check if this custom pattern already exists in the list. */
auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
{
const size_t len{pattern.size()};
return entry.mPattern.length() == len
&& entry.mPattern.compare(0, len, pattern.data(), len) == 0;
};
{ return entry.mPattern == pattern; };
auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
if(itemmatch != list.end())
{
/* If so, replace the name with this custom one. */
itemmatch->mName.assign(name.data(), name.size());
itemmatch->mName = name;
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
itemmatch->mPattern.c_str());
}
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()});
const auto &entry = list.back();
const auto &entry = list.emplace_back(name, pattern);
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
@ -295,7 +290,7 @@ struct JackPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -304,7 +299,7 @@ struct JackPlayback final : public BackendBase {
std::string mPortPattern;
jack_client_t *mClient{nullptr};
std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
std::array<jack_port_t*,MaxOutputChannels> mPort{};
std::mutex mMutex;
@ -315,8 +310,6 @@ struct JackPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(JackPlayback)
};
JackPlayback::~JackPlayback()
@ -336,22 +329,22 @@ JackPlayback::~JackPlayback()
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
size_t numchans{0};
auto outptrs = std::array<jack_default_audio_sample_t*,MaxOutputChannels>{};
auto numchans = size_t{0};
for(auto port : mPort)
{
if(!port || numchans == mDevice->RealOut.Buffer.size())
break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
outptrs[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
const auto dst = al::span{outptrs}.first(numchans);
if(mPlaying.load(std::memory_order_acquire)) LIKELY
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
mDevice->renderSamples(dst, static_cast<uint>(numframes));
else
{
auto clear_buf = [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); });
}
return 0;
@ -360,53 +353,46 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
std::array<al::span<float>,MaxOutputChannels> out;
size_t numchans{0};
for(auto port : mPort)
{
if(!port) break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
out[numchans++] = {static_cast<float*>(jack_port_get_buffer(port, numframes)), numframes};
}
jack_nframes_t total{0};
size_t total{0};
if(mPlaying.load(std::memory_order_acquire)) LIKELY
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
auto write_first = [&data,numchans,todo](float *outbuf) -> float*
{
const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.first.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
total += todo;
const auto update_size = size_t{mDevice->UpdateSize};
todo = minu(numframes-total, static_cast<uint>(data.second.len));
if(todo > 0)
const auto outlen = size_t{numframes / update_size};
const auto len1 = size_t{std::min(data.first.len/update_size, outlen)};
const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)};
auto src = al::span{reinterpret_cast<float*>(data.first.buf), update_size*len1*numchans};
for(size_t i{0};i < len1;++i)
{
auto write_second = [&data,numchans,todo](float *outbuf) -> float*
for(size_t c{0};c < numchans;++c)
{
const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.second.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
total += todo;
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += update_size;
}
src = al::span{reinterpret_cast<float*>(data.second.buf), update_size*len2*numchans};
for(size_t i{0};i < len2;++i)
{
for(size_t c{0};c < numchans;++c)
{
const auto iter = std::copy_n(src.begin(), update_size, out[c].begin());
out[c] = {iter, out[c].end()};
src = src.subspan(update_size);
}
total += update_size;
}
mRing->readAdvance(total);
@ -415,8 +401,8 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
if(numframes > total)
{
const jack_nframes_t todo{numframes - total};
auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
auto clear_buf = [](const al::span<float> outbuf) -> void
{ std::fill(outbuf.begin(), outbuf.end(), 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
@ -426,45 +412,70 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
int JackPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const auto update_size = uint{mDevice->UpdateSize};
const auto num_channels = size_t{mDevice->channelsFromFmt()};
auto outptrs = std::vector<float*>(num_channels);
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
if(mRing->writeSpace() < update_size)
{
mSem.wait();
continue;
}
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
todo -= todo%mDevice->UpdateSize;
const auto len1 = size_t{data.first.len / update_size};
const auto len2 = size_t{data.second.len / update_size};
const auto len1 = static_cast<uint>(minz(data.first.len, todo));
const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(data.first.buf, len1, frame_step);
std::lock_guard<std::mutex> dlock{mMutex};
auto buffer = al::span{reinterpret_cast<float*>(data.first.buf),
data.first.len*num_channels};
auto bufiter = buffer.begin();
for(size_t i{0};i < len1;++i)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
if(len2 > 0)
mDevice->renderSamples(data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
{
buffer = al::span{reinterpret_cast<float*>(data.second.buf),
data.second.len*num_channels};
bufiter = buffer.begin();
for(size_t i{0};i < len2;++i)
{
std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size]
{
auto ret = al::to_address(bufiter);
bufiter += ptrdiff_t(update_size);
return ret;
});
mDevice->renderSamples(outptrs, update_size);
}
}
mRing->writeAdvance((len1+len2) * update_size);
}
return 0;
}
void JackPlayback::open(const char *name)
void JackPlayback::open(std::string_view name)
{
if(!mClient)
{
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
jack_status_t status{};
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError,
@ -481,9 +492,9 @@ void JackPlayback::open(const char *name)
if(PlaybackList.empty())
EnumerateDevices(mClient, PlaybackList);
if(!name && !PlaybackList.empty())
if(name.empty() && !PlaybackList.empty())
{
name = PlaybackList[0].mName.c_str();
name = PlaybackList[0].mName;
mPortPattern = PlaybackList[0].mPattern;
}
else
@ -493,14 +504,10 @@ void JackPlayback::open(const char *name)
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name?name:""};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
mPortPattern = iter->mPattern;
}
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
@ -511,6 +518,10 @@ bool JackPlayback::reset()
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
* ready for when requested.
*/
@ -525,9 +536,9 @@ bool JackPlayback::reset()
}
else
{
const char *devname{mDevice->DeviceName.c_str()};
const std::string_view devname{mDevice->DeviceName};
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
}
@ -535,27 +546,27 @@ bool JackPlayback::reset()
mDevice->FmtType = DevFmtFloat;
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = mPort.begin();
while(bad_port != ports_end)
auto ports = al::span{mPort}.first(mDevice->channelsFromFmt());
auto bad_port = ports.begin();
while(bad_port != ports.end())
{
std::string name{"channel_" + std::to_string(++port_num)};
*bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
*bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput | JackPortIsTerminal, 0);
if(!*bad_port) break;
++bad_port;
}
if(bad_port != ports_end)
if(bad_port != ports.end())
{
ERR("Failed to register enough JACK ports for %s output\n",
DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false;
if(bad_port == ports.begin()) return false;
if(bad_port == mPort.begin()+1)
if(bad_port == ports.begin()+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort.begin()+2;
const auto ports_end = ports.begin()+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
@ -575,10 +586,10 @@ void JackPlayback::start()
if(jack_activate(mClient))
throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
const char *devname{mDevice->DeviceName.c_str()};
const std::string_view devname{mDevice->DeviceName};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput)};
if(!pnames)
{
@ -586,7 +597,7 @@ void JackPlayback::start()
throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
}
for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
for(size_t i{0};i < std::size(mPort) && mPort[i];++i)
{
if(!pnames[i])
{
@ -613,7 +624,7 @@ void JackPlayback::start()
else
{
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
@ -651,10 +662,9 @@ void JackPlayback::stop()
ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ClockLatency ret{};
ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
@ -674,7 +684,7 @@ bool JackBackendFactory::init()
if(!jack_load())
return false;
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
if(!GetConfigValueBool({}, "jack", "spawn-server", false))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();
@ -682,7 +692,7 @@ bool JackBackendFactory::init()
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler);
jack_status_t status;
jack_status_t status{};
jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb);
if(!client)
@ -700,18 +710,15 @@ bool JackBackendFactory::init()
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string JackBackendFactory::probe(BackendType type)
auto JackBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
{ outnames.emplace_back(entry.mName); };
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
jack_status_t status{};
switch(type)
{
case BackendType::Playback:
@ -722,6 +729,7 @@ std::string JackBackendFactory::probe(BackendType type)
}
else
WARN("jack_client_open() failed, 0x%02x\n", status);
outnames.reserve(PlaybackList.size());
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:

View file

@ -5,15 +5,15 @@
struct JackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_JACK_H */

View file

@ -30,16 +30,14 @@ namespace {
struct LoopbackBackend final : public BackendBase {
LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
DEF_NEWDEL(LoopbackBackend)
};
void LoopbackBackend::open(const char *name)
void LoopbackBackend::open(std::string_view name)
{
mDevice->DeviceName = name;
}
@ -65,8 +63,8 @@ bool LoopbackBackendFactory::init()
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; }
auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector<std::string>
{ return {}; }
BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }

View file

@ -5,15 +5,15 @@
struct LoopbackBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_LOOPBACK_H */

View file

@ -30,10 +30,11 @@
#include <functional>
#include <thread>
#include "core/device.h"
#include "almalloc.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "threads.h"
namespace {
@ -41,8 +42,9 @@ namespace {
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
using namespace std::string_view_literals;
constexpr char nullDevice[] = "No Output";
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "No Output"sv; }
struct NullBackend final : public BackendBase {
@ -50,15 +52,13 @@ struct NullBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(NullBackend)
};
int NullBackend::mixerProc()
@ -66,7 +66,7 @@ int NullBackend::mixerProc()
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
int64_t done{0};
auto start = std::chrono::steady_clock::now();
@ -105,13 +105,13 @@ int NullBackend::mixerProc()
}
void NullBackend::open(const char *name)
void NullBackend::open(std::string_view name)
{
if(!name)
name = nullDevice;
else if(strcmp(name, nullDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
mDevice->DeviceName = name;
}
@ -150,19 +150,17 @@ bool NullBackendFactory::init()
bool NullBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
std::string NullBackendFactory::probe(BackendType type)
auto NullBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
outnames.append(nullDevice, sizeof(nullDevice));
break;
/* Include null char. */
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct NullBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_NULL_H */

View file

@ -4,10 +4,11 @@
#include "oboe.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <stdint.h>
#include "alnumeric.h"
#include "alstring.h"
#include "core/device.h"
#include "core/logging.h"
#include "ringbuffer.h"
@ -17,7 +18,9 @@
namespace {
constexpr char device_name[] = "Oboe Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; }
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
@ -28,7 +31,9 @@ struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -46,14 +51,20 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea
return oboe::DataCallbackResult::Continue;
}
void OboePlayback::open(const char *name)
void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(error == oboe::Result::ErrorDisconnected)
mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error));
TRACE("Error was %s", oboe::convertToText(error));
}
void OboePlayback::open(std::string_view name)
{
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* Open a basic output stream, just to ensure it can work. */
oboe::ManagedStream stream;
@ -72,6 +83,7 @@ bool OboePlayback::reset()
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
builder.setUsage(oboe::Usage::Game);
/* Don't let Oboe convert. We should be able to handle anything it gives
* back.
*/
@ -135,7 +147,7 @@ bool OboePlayback::reset()
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->BufferSize),
mStream->getBufferCapacityInFrames()));
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
@ -164,6 +176,9 @@ bool OboePlayback::reset()
mDevice->FmtType = DevFmtInt;
break;
case oboe::AudioFormat::I24:
#endif
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8)
case oboe::AudioFormat::IEC61937:
#endif
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
@ -177,9 +192,9 @@ bool OboePlayback::reset()
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size.
*/
mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
mDevice->UpdateSize = std::max(mDevice->Frequency/100u,
static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
mDevice->BufferSize = std::max(mDevice->UpdateSize*2u,
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true;
@ -197,8 +212,7 @@ void OboePlayback::stop()
{
oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
}
@ -212,28 +226,28 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
};
oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
int32_t numFrames)
{
mRing->write(audioData, static_cast<uint32_t>(numFrames));
std::ignore = mRing->write(audioData, static_cast<uint32_t>(numFrames));
return oboe::DataCallbackResult::Continue;
}
void OboeCapture::open(const char *name)
void OboeCapture::open(std::string_view name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
@ -259,6 +273,7 @@ void OboeCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -297,7 +312,7 @@ void OboeCapture::open(const char *name)
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),
mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u),
static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
mDevice->DeviceName = name;
@ -315,15 +330,14 @@ void OboeCapture::stop()
{
const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
}
uint OboeCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void OboeCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void OboeCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
} // namespace
@ -332,16 +346,15 @@ bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string OboeBackendFactory::probe(BackendType type)
auto OboeBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
return std::string{device_name, sizeof(device_name)};
return std::vector{std::string{GetDeviceName()}};
}
return std::string{};
return {};
}
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct OboeBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OBOE_H */

View file

@ -23,23 +23,26 @@
#include "opensl.h"
#include <stdlib.h>
#include <jni.h>
#include <new>
#include <array>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <new>
#include <thread>
#include <functional>
#include "albit.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
@ -48,15 +51,17 @@
namespace {
using namespace std::string_view_literals;
/* Helper macros */
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
constexpr char opensl_device[] = "OpenSL";
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "OpenSL"sv; }
[[nodiscard]]
constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
{
switch(chans)
@ -80,6 +85,7 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
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 DevFmtX7144:
case DevFmtAmbi3D:
break;
}
@ -159,12 +165,10 @@ struct OpenSLPlayback final : public BackendBase {
~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -189,8 +193,6 @@ struct OpenSLPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OpenSLPlayback)
};
OpenSLPlayback::~OpenSLPlayback()
@ -229,7 +231,7 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
int OpenSLPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
SLPlayItf player;
SLAndroidSimpleBufferQueueItf bufferQueue;
@ -312,13 +314,13 @@ int OpenSLPlayback::mixerProc()
}
void OpenSLPlayback::open(const char *name)
void OpenSLPlayback::open(std::string_view name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* There's only one device, so if it's already open, there's nothing to do. */
if(mEngineObj) return;
@ -375,74 +377,6 @@ bool OpenSLPlayback::reset()
mRing = nullptr;
#if 0
if(!mDevice->Flags.get<FrequencyRequest>())
{
/* FIXME: Disabled until I figure out how to get the Context needed for
* the getSystemService call.
*/
JNIEnv *env = Android_GetJNIEnv();
jobject jctx = Android_GetContext();
/* Get necessary stuff for using java.lang.Integer,
* android.content.Context, and android.media.AudioManager.
*/
jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
"parseInt", "(Ljava/lang/String;)I"
);
TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
"AUDIO_SERVICE", "Ljava/lang/String;"
);
jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
"getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
);
TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
ctx_cls, ctx_audsvc, ctx_getSysSvc);
jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
"PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
);
jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
);
TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
const char *strchars;
jstring strobj;
/* Now make the calls. */
//AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//int sampleRate = Integer.parseInt(srateStr);
sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
if(!sampleRate) sampleRate = device->Frequency;
else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
}
#endif
mDevice->FmtChans = DevFmtStereo;
mDevice->FmtType = DevFmtShort;
@ -564,7 +498,9 @@ void OpenSLPlayback::start()
PrintErr(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
result = VCALL(bufferQueue,RegisterCallback)(
[](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }, this);
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS != result)
@ -628,8 +564,8 @@ ClockLatency OpenSLPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> dlock{mMutex};
ret.ClockTime = mDevice->getClockTime();
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
@ -642,13 +578,11 @@ struct OpenSLCapture final : public BackendBase {
~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
/* engine interfaces */
@ -662,8 +596,6 @@ struct OpenSLCapture final : public BackendBase {
uint mSplOffset{0u};
uint mFrameSize{0};
DEF_NEWDEL(OpenSLCapture)
};
OpenSLCapture::~OpenSLCapture()
@ -686,13 +618,13 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
}
void OpenSLCapture::open(const char* name)
void OpenSLCapture::open(std::string_view name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PrintErr(result, "slCreateEngine");
@ -710,10 +642,10 @@ void OpenSLCapture::open(const char* name)
{
mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */
uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
mDevice->Frequency/100*5)};
uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u,
mDevice->Frequency/100u*5u)};
uint num_updates{(length+update_len-1) / update_len};
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
@ -813,13 +745,15 @@ void OpenSLCapture::open(const char* name)
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
result = VCALL(bufferQueue,RegisterCallback)(
[](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }, this);
PrintErr(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
const uint chunk_size{mDevice->UpdateSize * mFrameSize};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0};
auto data = mRing->getWriteVector();
std::fill_n(data.first.buf, data.first.len*chunk_size, silence);
@ -883,7 +817,7 @@ void OpenSLCapture::stop()
}
}
void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
{
const uint update_size{mDevice->UpdateSize};
const uint chunk_size{update_size * mFrameSize};
@ -895,7 +829,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
auto rdata = mRing->getReadVector();
for(uint i{0};i < samples;)
{
const uint rem{minu(samples - i, update_size - mSplOffset)};
const uint rem{std::min(samples - i, update_size - mSplOffset)};
std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
buffer + i*size_t{mFrameSize});
@ -932,7 +866,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
return;
/* 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
* chunk to keep the OpenSL queue full. This is rather convoluted, 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
@ -975,18 +909,15 @@ bool OSLBackendFactory::init() { return true; }
bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSLBackendFactory::probe(BackendType type)
auto OSLBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(opensl_device, sizeof(opensl_device));
break;
return std::vector{std::string{GetDeviceName()}};
}
return outnames;
return {};
}
BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct OSLBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSL_H */

View file

@ -31,27 +31,26 @@
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
#include <memory>
#include <new>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sys/soundcard.h>
@ -83,117 +82,148 @@
namespace {
constexpr char DefaultName[] = "OSS Default";
std::string DefaultPlayback{"/dev/dsp"};
std::string DefaultCapture{"/dev/dsp"};
using namespace std::string_literals;
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; }
std::string DefaultPlayback{"/dev/dsp"s};
std::string DefaultCapture{"/dev/dsp"s};
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;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
#ifdef ALC_OSS_COMPAT
#define DSP_CAP_OUTPUT 0x00020000
#define DSP_CAP_INPUT 0x00010000
void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
void ALCossListPopulate(std::vector<DevMap> &devlist, int type)
{
devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback);
}
#else
void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
class FileHandle {
int mFd{-1};
public:
FileHandle() = default;
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
~FileHandle() { if(mFd != -1) ::close(mFd); }
template<typename ...Args>
[[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool
{
close();
mFd = ::open(fname, std::forward<Args>(args)...);
return mFd != -1;
}
void close()
{
if(mFd != -1)
::close(mFd);
mFd = -1;
}
[[nodiscard]]
auto get() const noexcept -> int { return mFd; }
};
void ALCossListAppend(std::vector<DevMap> &list, std::string_view handle, std::string_view path)
{
#ifdef ALC_OSS_DEVNODE_TRUC
for(size_t i{0};i < path.size();++i)
{
if(path[i] == '.' && handle.size() + i >= path.size())
if(path[i] == '.' && handle.size() >= path.size() - i)
{
const size_t hoffset{handle.size() + i - path.size()};
if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
handle = handle.first(hoffset);
path = path.first(i);
handle = handle.substr(0, hoffset);
path = path.substr(0, i);
}
}
#endif
if(handle.empty())
handle = path;
std::string basename{handle.data(), handle.size()};
std::string devname{path.data(), path.size()};
auto match_devname = [&devname](const DevMap &entry) -> bool
{ return entry.device_name == devname; };
auto match_devname = [path](const DevMap &entry) -> bool
{ return entry.device_name == path; };
if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
return;
auto checkName = [&list](const std::string &name) -> bool
auto checkName = [&list](const std::string_view name) -> bool
{
auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
};
int count{1};
std::string newname{basename};
std::string newname{handle};
while(checkName(newname))
{
newname = basename;
newname = handle;
newname += " #";
newname += std::to_string(++count);
}
list.emplace_back(DevMap{std::move(newname), std::move(devname)});
const DevMap &entry = list.back();
const DevMap &entry = list.emplace_back(std::move(newname), path);
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
{
int fd{open("/dev/mixer", O_RDONLY)};
if(fd < 0)
oss_sysinfo si{};
FileHandle file;
if(!file.open("/dev/mixer", O_RDONLY))
{
TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str());
goto done;
}
oss_sysinfo si;
if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
{
TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str());
goto done;
}
for(int i{0};i < si.numaudios;i++)
{
oss_audioinfo ai;
oss_audioinfo ai{};
ai.dev = i;
if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1)
{
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i,
std::generic_category().message(errno).c_str());
continue;
}
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
continue;
al::span<const char> handle;
std::string_view handle;
if(ai.handle[0] != '\0')
handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
else
handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
ALCossListAppend(devlist, handle, devnode);
}
done:
if(fd >= 0)
close(fd);
fd = -1;
file.close();
const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
@ -201,7 +231,7 @@ done:
{ return entry.device_name == defdev; }
);
if(iter == devlist.cend())
devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev});
else
{
DevMap entry{std::move(*iter)};
@ -231,19 +261,17 @@ struct OSSPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
int mFd{-1};
al::vector<al::byte> mMixData;
std::vector<std::byte> mMixData;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSSPlayback)
};
OSSPlayback::~OSSPlayback()
@ -257,7 +285,7 @@ OSSPlayback::~OSSPlayback()
int OSSPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
@ -269,38 +297,38 @@ int OSSPlayback::mixerProc()
pollitem.fd = mFd;
pollitem.events = POLLOUT;
int pret{poll(&pollitem, 1, 1000)};
if(pret < 0)
if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str());
break;
}
else if(pret == 0)
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{
WARN("poll timeout\n");
continue;
}
al::byte *write_ptr{mMixData.data()};
size_t to_write{mMixData.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
al::span write_buf{mMixData};
mDevice->renderSamples(write_buf.data(), static_cast<uint>(write_buf.size()/frame_size),
frame_step);
while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("write failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str());
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
write_buf = write_buf.subspan(static_cast<size_t>(wrote));
}
}
@ -308,11 +336,11 @@ int OSSPlayback::mixerProc()
}
void OSSPlayback::open(const char *name)
void OSSPlayback::open(std::string_view name)
{
const char *devname{DefaultPlayback.c_str()};
if(!name)
name = DefaultName;
if(name.empty())
name = GetDefaultName();
else
{
if(PlaybackDevices.empty())
@ -324,14 +352,14 @@ void OSSPlayback::open(const char *name)
);
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
devname = iter->device_name.c_str();
}
int fd{::open(devname, O_WRONLY)};
if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
std::generic_category().message(errno).c_str()};
if(mFd != -1)
::close(mFd);
@ -367,15 +395,14 @@ bool OSSPlayback::reset()
uint ossSpeed{mDevice->Frequency};
uint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
const char *err;
#define CHECKERR(func) if((func) < 0) { \
err = #func; \
goto err; \
}
#define CHECKERR(func) if((func) < 0) \
throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \
std::generic_category().message(errno).c_str()};
/* Don't fail if SETFRAGMENT fails. We can handle just about anything
* that's reported back via GETOSPACE */
ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
@ -383,12 +410,6 @@ bool OSSPlayback::reset()
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
if(0)
{
err:
ERR("%s failed: %s\n", err, strerror(errno));
return false;
}
#undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels)
@ -413,7 +434,7 @@ bool OSSPlayback::reset()
setDefaultChannelOrder();
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt());
return true;
}
@ -437,7 +458,7 @@ void OSSPlayback::stop()
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
}
@ -447,10 +468,10 @@ struct OSScapture final : public BackendBase {
int recordProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
int mFd{-1};
@ -459,8 +480,6 @@ struct OSScapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSScapture)
};
OSScapture::~OSScapture()
@ -474,7 +493,7 @@ OSScapture::~OSScapture()
int OSScapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire))
@ -483,16 +502,16 @@ int OSScapture::recordProc()
pollitem.fd = mFd;
pollitem.events = POLLIN;
int sret{poll(&pollitem, 1, 1000)};
if(sret < 0)
if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("poll failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str());
break;
}
else if(sret == 0)
else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
{
WARN("poll timeout\n");
continue;
@ -504,8 +523,9 @@ int OSScapture::recordProc()
ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
if(amt < 0)
{
ERR("read failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
const auto errstr = std::generic_category().message(errno);
ERR("read failed: %s\n", errstr.c_str());
mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str());
break;
}
mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
@ -516,11 +536,11 @@ int OSScapture::recordProc()
}
void OSScapture::open(const char *name)
void OSScapture::open(std::string_view name)
{
const char *devname{DefaultCapture.c_str()};
if(!name)
name = DefaultName;
if(name.empty())
name = GetDefaultName();
else
{
if(CaptureDevices.empty())
@ -532,14 +552,14 @@ void OSScapture::open(const char *name)
);
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_RDONLY);
if(mFd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
std::generic_category().message(errno).c_str()};
int ossFormat{};
switch(mDevice->FmtType)
@ -566,13 +586,13 @@ void OSScapture::open(const char *name)
uint frameSize{numChannels * mDevice->bytesFromFmt()};
uint ossSpeed{mDevice->Frequency};
/* according to the OSS spec, 16 bytes are the minimum */
uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \
throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
strerror(errno)}; \
std::generic_category().message(errno).c_str()}; \
}
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
@ -617,11 +637,11 @@ void OSScapture::stop()
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
}
void OSScapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void OSScapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint OSScapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -637,9 +657,9 @@ BackendFactory &OSSBackendFactory::getFactory()
bool OSSBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
if(auto devopt = ConfigValueStr({}, "oss", "device"))
DefaultPlayback = std::move(*devopt);
if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
if(auto capopt = ConfigValueStr({}, "oss", "capture"))
DefaultCapture = std::move(*capopt);
return true;
@ -648,18 +668,13 @@ bool OSSBackendFactory::init()
bool OSSBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string OSSBackendFactory::probe(BackendType type)
auto OSSBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
{
/* Includes null char. */
outnames.append(entry.name.c_str(), entry.name.length()+1);
}
if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0)
outnames.emplace_back(entry.name);
};
switch(type)
@ -667,12 +682,14 @@ std::string OSSBackendFactory::probe(BackendType type)
case BackendType::Playback:
PlaybackDevices.clear();
ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct OSSBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_OSS_H */

File diff suppressed because it is too large Load diff

View file

@ -2,22 +2,26 @@
#define BACKENDS_PIPEWIRE_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h"
struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PIPEWIRE_H */

View file

@ -26,21 +26,20 @@
#include <cstdlib>
#include <cstring>
#include "albit.h"
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "alstring.h"
#include "core/device.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include <portaudio.h>
#include <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */
namespace {
constexpr char pa_device[] = "PortAudio Default";
#ifdef HAVE_DYNLOAD
void *pa_handle;
#define MAKE_FUNC(x) decltype(x) * p##x
@ -51,6 +50,8 @@ MAKE_FUNC(Pa_StartStream);
MAKE_FUNC(Pa_StopStream);
MAKE_FUNC(Pa_OpenStream);
MAKE_FUNC(Pa_CloseStream);
MAKE_FUNC(Pa_GetDeviceCount);
MAKE_FUNC(Pa_GetDeviceInfo);
MAKE_FUNC(Pa_GetDefaultOutputDevice);
MAKE_FUNC(Pa_GetDefaultInputDevice);
MAKE_FUNC(Pa_GetStreamInfo);
@ -64,12 +65,49 @@ MAKE_FUNC(Pa_GetStreamInfo);
#define Pa_StopStream pPa_StopStream
#define Pa_OpenStream pPa_OpenStream
#define Pa_CloseStream pPa_CloseStream
#define Pa_GetDeviceCount pPa_GetDeviceCount
#define Pa_GetDeviceInfo pPa_GetDeviceInfo
#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
#define Pa_GetStreamInfo pPa_GetStreamInfo
#endif
#endif
struct DeviceEntry {
std::string mName;
bool mIsPlayback{};
bool mIsCapture{};
};
std::vector<DeviceEntry> DeviceNames;
void EnumerateDevices()
{
const auto devcount = Pa_GetDeviceCount();
if(devcount < 0)
{
ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount));
return;
}
std::vector<DeviceEntry>(static_cast<uint>(devcount)).swap(DeviceNames);
PaDeviceIndex idx{0};
for(auto &entry : DeviceNames)
{
if(auto info = Pa_GetDeviceInfo(idx); info && info->name)
{
#ifdef _WIN32
entry.mName = "OpenAL Soft on "+std::string{info->name};
#else
entry.mName = info->name;
#endif
entry.mIsPlayback = (info->maxOutputChannels > 0);
entry.mIsCapture = (info->maxInputChannels > 0);
TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(),
info->maxOutputChannels, info->maxInputChannels);
}
++idx;
}
}
struct PortPlayback final : public BackendBase {
PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
@ -77,24 +115,17 @@ struct PortPlayback final : public BackendBase {
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams{};
DevFmtChannels mChannelConfig{};
uint mAmbiOrder{};
uint mUpdateSize{0u};
DEF_NEWDEL(PortPlayback)
};
PortPlayback::~PortPlayback()
@ -115,22 +146,38 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f
}
void PortPlayback::open(const char *name)
void PortPlayback::open(std::string_view name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(DeviceNames.empty())
EnumerateDevices();
int deviceid{-1};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "device"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultOutputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
PaStreamParameters params{};
auto devidopt = ConfigValueInt(nullptr, "port", "device");
if(devidopt && *devidopt >= 0) params.device = *devidopt;
else params.device = Pa_GetDefaultOutputDevice();
params.device = deviceid;
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
params.hostApiSpecificStreamInfo = nullptr;
params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
mChannelConfig = mDevice->FmtChans;
mAmbiOrder = mDevice->mAmbiOrder;
params.channelCount = static_cast<int>(mDevice->channelsFromFmt());
switch(mDevice->FmtType)
{
@ -155,19 +202,21 @@ void PortPlayback::open(const char *name)
break;
}
retry_open:
PaStream *stream{};
PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError)
static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
if(params.sampleFormat == paFloat32)
{
params.sampleFormat = paInt16;
goto retry_open;
}
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
PaStream *stream{};
while(PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency,
mDevice->UpdateSize, paNoFlag, writeCallback, this)})
{
if(params.sampleFormat != paFloat32)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
params.sampleFormat = paInt16;
}
Pa_CloseStream(mStream);
@ -182,7 +231,18 @@ bool PortPlayback::reset()
{
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate);
mDevice->FmtChans = mChannelConfig;
mDevice->mAmbiOrder = mAmbiOrder;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2;
if(streamInfo->outputLatency > 0.0f)
{
const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate};
TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency,
sampleLatency);
mDevice->BufferSize = static_cast<uint>(std::clamp(sampleLatency,
double(mDevice->BufferSize), double{std::numeric_limits<int>::max()}));
}
if(mParams.sampleFormat == paInt8)
mDevice->FmtType = DevFmtByte;
@ -200,15 +260,6 @@ bool PortPlayback::reset()
return false;
}
if(mParams.channelCount >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono;
else
{
ERR("Unexpected channel count: %u\n", mParams.channelCount);
return false;
}
setDefaultChannelOrder();
return true;
@ -216,16 +267,14 @@ bool PortPlayback::reset()
void PortPlayback::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err == paNoError)
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
Pa_GetErrorText(err)};
}
void PortPlayback::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
@ -235,27 +284,18 @@ struct PortCapture final : public BackendBase {
~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int readCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept;
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams;
PaStreamParameters mParams{};
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(PortCapture)
};
PortCapture::~PortCapture()
@ -268,30 +308,43 @@ PortCapture::~PortCapture()
int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept
{
mRing->write(inputBuffer, framesPerBuffer);
std::ignore = mRing->write(inputBuffer, framesPerBuffer);
return 0;
}
void PortCapture::open(const char *name)
void PortCapture::open(std::string_view name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(DeviceNames.empty())
EnumerateDevices();
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
uint frame_size{mDevice->frameSizeFromFmt()};
int deviceid{};
if(name.empty())
{
if(auto devidopt = ConfigValueInt({}, "port", "capture"))
deviceid = *devidopt;
if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
deviceid = Pa_GetDefaultInputDevice();
name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
}
else
{
auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
[name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; });
if(iter == DeviceNames.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%.*s\" not found", al::sizei(name), name.data()};
deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
}
const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
const uint frame_size{mDevice->frameSizeFromFmt()};
mRing = RingBuffer::Create(samples, frame_size, false);
auto devidopt = ConfigValueInt(nullptr, "port", "capture");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice();
mParams.device = deviceid;
mParams.suggestedLatency = 0.0f;
mParams.hostApiSpecificStreamInfo = nullptr;
@ -319,8 +372,15 @@ void PortCapture::open(const char *name)
}
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
};
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
paFramesPerBufferUnspecified, paNoFlag, readCallback, this)};
if(err != paNoError)
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
@ -331,16 +391,14 @@ void PortCapture::open(const char *name)
void PortCapture::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording: %s", Pa_GetErrorText(err)};
}
void PortCapture::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
@ -348,16 +406,14 @@ void PortCapture::stop()
uint PortCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void PortCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void PortCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
} // namespace
bool PortBackendFactory::init()
{
PaError err;
#ifdef HAVE_DYNLOAD
if(!pa_handle)
{
@ -391,12 +447,15 @@ bool PortBackendFactory::init()
LOAD_FUNC(Pa_StopStream);
LOAD_FUNC(Pa_OpenStream);
LOAD_FUNC(Pa_CloseStream);
LOAD_FUNC(Pa_GetDeviceCount);
LOAD_FUNC(Pa_GetDeviceInfo);
LOAD_FUNC(Pa_GetDefaultOutputDevice);
LOAD_FUNC(Pa_GetDefaultInputDevice);
LOAD_FUNC(Pa_GetStreamInfo);
#undef LOAD_FUNC
if((err=Pa_Initialize()) != paNoError)
const PaError err{Pa_Initialize()};
if(err != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
CloseLib(pa_handle);
@ -405,7 +464,8 @@ bool PortBackendFactory::init()
}
}
#else
if((err=Pa_Initialize()) != paNoError)
const PaError err{Pa_Initialize()};
if(err != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
return false;
@ -417,18 +477,52 @@ bool PortBackendFactory::init()
bool PortBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string PortBackendFactory::probe(BackendType type)
auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> devices;
EnumerateDevices();
int defaultid{-1};
switch(type)
{
case BackendType::Playback:
defaultid = Pa_GetDefaultOutputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mIsPlayback)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
case BackendType::Capture:
/* Includes null char. */
outnames.append(pa_device, sizeof(pa_device));
defaultid = Pa_GetDefaultInputDevice();
if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0
&& static_cast<uint>(*devidopt) < DeviceNames.size())
defaultid = *devidopt;
for(size_t i{0};i < DeviceNames.size();++i)
{
if(DeviceNames[i].mIsCapture)
{
if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
devices.emplace(devices.cbegin(), DeviceNames[i].mName);
else
devices.emplace_back(DeviceNames[i].mName);
}
}
break;
}
return outnames;
return devices;
}
BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct PortBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PORTAUDIO_H */

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,27 @@
#ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H
#include <string>
#include <vector>
#include "alc/events.h"
#include "base.h"
struct DeviceBase;
class PulseBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_PULSEAUDIO_H */

View file

@ -26,6 +26,7 @@
#include <cstdlib>
#include <cstring>
#include <string>
#include <string_view>
#include "almalloc.h"
#include "alnumeric.h"
@ -46,17 +47,17 @@ namespace {
#define DEVNAME_PREFIX ""
#endif
constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; }
constexpr auto getDefaultDeviceName() noexcept -> std::string_view
{ return DEVNAME_PREFIX "Default Device"; }
struct Sdl2Backend final : public BackendBase {
Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override;
void audioCallback(Uint8 *stream, int len) noexcept;
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -68,8 +69,6 @@ struct Sdl2Backend final : public BackendBase {
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
uint mUpdateSize{0u};
DEF_NEWDEL(Sdl2Backend)
};
Sdl2Backend::~Sdl2Backend()
@ -86,7 +85,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
}
void Sdl2Backend::open(const char *name)
void Sdl2Backend::open(std::string_view name)
{
SDL_AudioSpec want{}, have{};
@ -102,24 +101,39 @@ void Sdl2Backend::open(const char *name)
case DevFmtFloat: want.format = AUDIO_F32; break;
}
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
want.callback = &Sdl2Backend::audioCallbackC;
want.samples = static_cast<Uint16>(std::min(mDevice->UpdateSize, 8192u));
want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
{ return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list.
*/
const auto defaultDeviceName = getDefaultDeviceName();
SDL_AudioDeviceID devid;
if(!name || strcmp(name, defaultDeviceName) == 0)
if(name.empty() || name == defaultDeviceName)
{
name = defaultDeviceName;
devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
}
else
{
const size_t prefix_len = strlen(DEVNAME_PREFIX);
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
const auto namePrefix = getDevicePrefix();
if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix)
{
/* Copy the string_view to a string to ensure it's null terminated
* for this call.
*/
const std::string devname{name.substr(namePrefix.size())};
devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
else
devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
{
const std::string devname{name};
devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
}
if(!devid)
throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
@ -161,7 +175,7 @@ void Sdl2Backend::open(const char *name)
mFmtType = devtype;
mUpdateSize = have.samples;
mDevice->DeviceName = name ? name : defaultDeviceName;
mDevice->DeviceName = name;
}
bool Sdl2Backend::reset()
@ -195,23 +209,27 @@ bool SDL2BackendFactory::init()
bool SDL2BackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SDL2BackendFactory::probe(BackendType type)
auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
if(type != BackendType::Playback)
return outnames;
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
if(num_devices <= 0)
return outnames;
/* Includes null char. */
outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
outnames.reserve(static_cast<unsigned int>(num_devices));
outnames.emplace_back(getDefaultDeviceName());
for(int i{0};i < num_devices;++i)
{
std::string name{DEVNAME_PREFIX};
name += SDL_GetAudioDeviceName(i, SDL_FALSE);
if(!name.empty())
outnames.append(name.c_str(), name.length()+1);
std::string outname{getDevicePrefix()};
if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE))
outname += name;
else
outname += "Unknown Device Name #"+std::to_string(i);
outnames.emplace_back(std::move(outname));
}
return outnames;
}

View file

@ -5,15 +5,15 @@
struct SDL2BackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SDL2_H */

View file

@ -22,31 +22,35 @@
#include "sndio.h"
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <inttypes.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <system_error>
#include <thread>
#include <vector>
#include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sndio.h>
#include <sndio.h> /* NOLINT(*-duplicate-include) Not the same header. */
namespace {
static const char sndio_device[] = "SndIO Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "SndIO Default"sv; }
struct SioPar : public sio_par {
SioPar() { sio_initpar(this); }
SioPar() : sio_par{} { sio_initpar(this); }
void clear() { sio_initpar(this); }
};
@ -57,7 +61,7 @@ struct SndioPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -65,12 +69,10 @@ struct SndioPlayback final : public BackendBase {
sio_hdl *mSndHandle{nullptr};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioPlayback)
};
SndioPlayback::~SndioPlayback()
@ -86,12 +88,12 @@ int SndioPlayback::mixerProc()
const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
al::span<al::byte> buffer{mBuffer};
al::span<std::byte> buffer{mBuffer};
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
frameStep);
@ -112,13 +114,13 @@ int SndioPlayback::mixerProc()
}
void SndioPlayback::open(const char *name)
void SndioPlayback::open(std::string_view name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(!sndHandle)
@ -136,72 +138,75 @@ bool SndioPlayback::reset()
SioPar par;
auto tryfmt = mDevice->FmtType;
retry_params:
switch(tryfmt)
while(true)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
switch(tryfmt)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
goto retry_params;
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
break;
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
}
}
if(par.bps == 1)
@ -229,11 +234,11 @@ retry_params:
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round;
mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps);
if(par.sig == 1)
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
else if(par.bits == 8)
std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80));
else if(par.bits == 16)
std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
else if(par.bits == 32)
@ -280,10 +285,10 @@ struct SndioCapture final : public BackendBase {
int recordProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
sio_hdl *mSndHandle{nullptr};
@ -292,8 +297,6 @@ struct SndioCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioCapture)
};
SndioCapture::~SndioCapture()
@ -306,7 +309,7 @@ SndioCapture::~SndioCapture()
int SndioCapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
const uint frameSize{mDevice->frameSizeFromFmt()};
@ -317,29 +320,30 @@ int SndioCapture::recordProc()
return 1;
}
auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
auto fds = std::vector<pollfd>(static_cast<uint>(nfds_pre));
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
/* Wait until there's some samples to read. */
const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)};
if(nfds <= 0)
{
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
break;
}
int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
int pollres{::poll(fds.data(), fds.size(), 2000)};
if(pollres < 0)
{
if(errno == EINTR) continue;
mDevice->handleDisconnect("Poll error: %s", strerror(errno));
mDevice->handleDisconnect("Poll error: %s",
std::generic_category().message(errno).c_str());
break;
}
if(pollres == 0)
continue;
const int revents{sio_revents(mSndHandle, fds.get())};
const int revents{sio_revents(mSndHandle, fds.data())};
if((revents&POLLHUP))
{
mDevice->handleDisconnect("Got POLLHUP from poll events");
@ -349,7 +353,7 @@ int SndioCapture::recordProc()
continue;
auto data = mRing->getWriteVector();
al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
al::span<std::byte> buffer{data.first.buf, data.first.len*frameSize};
while(!buffer.empty())
{
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
@ -373,8 +377,8 @@ int SndioCapture::recordProc()
if(buffer.empty())
{
/* Got samples to read, but no place to store it. Drop it. */
static char junk[4096];
sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
static std::array<char,4096> junk;
sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize));
}
}
@ -382,13 +386,13 @@ int SndioCapture::recordProc()
}
void SndioCapture::open(const char *name)
void SndioCapture::open(std::string_view name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr)
@ -431,12 +435,12 @@ void SndioCapture::open(const char *name)
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u);
par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device praameters"};
"Failed to set device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
@ -461,7 +465,7 @@ void SndioCapture::open(const char *name)
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round;
@ -496,8 +500,8 @@ void SndioCapture::stop()
ERR("Error stopping device\n");
}
void SndioCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void SndioCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint SndioCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -516,18 +520,15 @@ bool SndIOBackendFactory::init()
bool SndIOBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
std::string SndIOBackendFactory::probe(BackendType type)
auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(sndio_device, sizeof(sndio_device));
break;
return std::vector{std::string{GetDefaultName()}};
}
return outnames;
return {};
}
BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct SndIOBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SNDIO_H */

View file

@ -35,24 +35,26 @@
#include <poll.h>
#include <math.h>
#include <string.h>
#include <vector>
#include <thread>
#include <functional>
#include "albyte.h"
#include "alc/alconfig.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "threads.h"
#include "vector.h"
#include <sys/audioio.h>
namespace {
constexpr char solaris_device[] = "Solaris Default";
using namespace std::string_view_literals;
[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; }
std::string solaris_driver{"/dev/audio"};
@ -63,7 +65,7 @@ struct SolarisBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -71,12 +73,10 @@ struct SolarisBackend final : public BackendBase {
int mFd{-1};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SolarisBackend)
};
SolarisBackend::~SolarisBackend()
@ -89,10 +89,10 @@ SolarisBackend::~SolarisBackend()
int SolarisBackend::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frame_step{mDevice->channelsFromFmt()};
const uint frame_size{mDevice->frameSizeFromFmt()};
const size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
@ -116,12 +116,12 @@ int SolarisBackend::mixerProc()
continue;
}
al::byte *write_ptr{mBuffer.data()};
size_t to_write{mBuffer.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
al::span<std::byte> buffer{mBuffer};
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size()/frame_size),
frame_step);
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
ssize_t wrote{write(mFd, buffer.data(), buffer.size())};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
@ -131,8 +131,7 @@ int SolarisBackend::mixerProc()
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
buffer = buffer.subspan(static_cast<size_t>(wrote));
}
}
@ -140,13 +139,13 @@ int SolarisBackend::mixerProc()
}
void SolarisBackend::open(const char *name)
void SolarisBackend::open(std::string_view name)
{
if(!name)
name = solaris_device;
else if(strcmp(name, solaris_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDefaultName();
else if(name != GetDefaultName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
int fd{::open(solaris_driver.c_str(), O_WRONLY)};
if(fd == -1)
@ -231,7 +230,7 @@ bool SolarisBackend::reset()
setDefaultChannelOrder();
mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
return true;
}
@ -268,7 +267,7 @@ BackendFactory &SolarisBackendFactory::getFactory()
bool SolarisBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
if(auto devopt = ConfigValueStr({}, "solaris", "device"))
solaris_driver = std::move(*devopt);
return true;
}
@ -276,23 +275,19 @@ bool SolarisBackendFactory::init()
bool SolarisBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string SolarisBackendFactory::probe(BackendType type)
auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
{
struct stat buf;
if(stat(solaris_driver.c_str(), &buf) == 0)
outnames.append(solaris_device, sizeof(solaris_device));
}
break;
if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0)
return std::vector{std::string{GetDefaultName()}};
break;
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct SolarisBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_SOLARIS_H */

File diff suppressed because it is too large Load diff

View file

@ -5,15 +5,17 @@
struct WasapiBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WASAPI_H */

View file

@ -31,24 +31,26 @@
#include <cstring>
#include <exception>
#include <functional>
#include <system_error>
#include <thread>
#include <vector>
#include "albit.h"
#include "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
namespace {
using namespace std::string_view_literals;
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
@ -56,38 +58,43 @@ using std::chrono::nanoseconds;
using ubyte = unsigned char;
using ushort = unsigned short;
constexpr char waveDevice[] = "Wave File Writer";
struct FileDeleter {
void operator()(gsl::owner<FILE*> f) { fclose(f); }
};
using FilePtr = std::unique_ptr<FILE,FileDeleter>;
constexpr ubyte SUBTYPE_PCM[]{
[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; }
constexpr std::array<ubyte,16> SUBTYPE_PCM{{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
constexpr ubyte SUBTYPE_FLOAT[]{
}};
constexpr std::array<ubyte,16> SUBTYPE_FLOAT{{
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
}};
constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_PCM{{
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
}};
constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_FLOAT{{
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
}};
void fwrite16le(ushort val, FILE *f)
{
ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
fwrite(data, 1, 2, f);
std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff)};
fwrite(data.data(), 1, data.size(), f);
}
void fwrite32le(uint val, FILE *f)
{
ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
fwrite(data, 1, 4, f);
std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff)};
fwrite(data.data(), 1, data.size(), f);
}
@ -97,34 +104,27 @@ struct WaveBackend final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
FILE *mFile{nullptr};
FilePtr mFile{nullptr};
long mDataStart{-1};
al::vector<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WaveBackend)
};
WaveBackend::~WaveBackend()
{
if(mFile)
fclose(mFile);
mFile = nullptr;
}
WaveBackend::~WaveBackend() = default;
int WaveBackend::mixerProc()
{
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
const size_t frameStep{mDevice->channelsFromFmt()};
const size_t frameSize{mDevice->frameSizeFromFmt()};
@ -155,13 +155,13 @@ int WaveBackend::mixerProc()
if(bytesize == 2)
{
const size_t len{mBuffer.size() & ~size_t{1}};
const size_t len{mBuffer.size() & ~1_uz};
for(size_t i{0};i < len;i+=2)
std::swap(mBuffer[i], mBuffer[i+1]);
}
else if(bytesize == 4)
{
const size_t len{mBuffer.size() & ~size_t{3}};
const size_t len{mBuffer.size() & ~3_uz};
for(size_t i{0};i < len;i+=4)
{
std::swap(mBuffer[i ], mBuffer[i+3]);
@ -170,8 +170,8 @@ int WaveBackend::mixerProc()
}
}
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
if(fs < mDevice->UpdateSize || ferror(mFile))
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())};
if(fs < mDevice->UpdateSize || ferror(mFile.get()))
{
ERR("Error writing to file\n");
mDevice->handleDisconnect("Failed to write playback samples");
@ -195,32 +195,32 @@ int WaveBackend::mixerProc()
return 0;
}
void WaveBackend::open(const char *name)
void WaveBackend::open(std::string_view name)
{
auto fname = ConfigValueStr(nullptr, "wave", "file");
auto fname = ConfigValueStr({}, "wave", "file");
if(!fname) throw al::backend_exception{al::backend_error::NoDevice,
"No wave output filename"};
if(!name)
name = waveDevice;
else if(strcmp(name, waveDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
if(name.empty())
name = GetDeviceName();
else if(name != GetDeviceName())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
/* There's only one "device", so if it's already open, we're done. */
if(mFile) return;
#ifdef _WIN32
{
std::wstring wname{utf8_to_wstr(fname->c_str())};
mFile = _wfopen(wname.c_str(), L"wb");
std::wstring wname{utf8_to_wstr(fname.value())};
mFile = FilePtr{_wfopen(wname.c_str(), L"wb")};
}
#else
mFile = fopen(fname->c_str(), "wb");
mFile = FilePtr{fopen(fname->c_str(), "wb")};
#endif
if(!mFile)
throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
fname->c_str(), strerror(errno)};
fname->c_str(), std::generic_category().message(errno).c_str()};
mDevice->DeviceName = name;
}
@ -229,12 +229,11 @@ bool WaveBackend::reset()
{
uint channels{0}, bytes{0}, chanmask{0};
bool isbformat{false};
size_t val;
fseek(mFile, 0, SEEK_SET);
clearerr(mFile);
fseek(mFile.get(), 0, SEEK_SET);
clearerr(mFile.get());
if(GetConfigValueBool(nullptr, "wave", "bformat", false))
if(GetConfigValueBool({}, "wave", "bformat", false))
{
mDevice->FmtChans = DevFmtAmbi3D;
mDevice->mAmbiOrder = 1;
@ -265,6 +264,9 @@ 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 DevFmtX7144:
mDevice->FmtChans = DevFmtX714;
[[fallthrough]];
case DevFmtX714:
chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
| 0x8000 | 0x20000;
@ -273,7 +275,7 @@ bool WaveBackend::reset()
case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u);
mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
mDevice->mAmbiScale = DevAmbiScaling::FuMa;
isbformat = true;
@ -283,49 +285,48 @@ bool WaveBackend::reset()
bytes = mDevice->bytesFromFmt();
channels = mDevice->channelsFromFmt();
rewind(mFile);
rewind(mFile.get());
fputs("RIFF", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
fputs("RIFF", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close
fputs("WAVE", mFile);
fputs("WAVE", mFile.get());
fputs("fmt ", mFile);
fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
fputs("fmt ", mFile.get());
fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE
// 16-bit val, format type id (extensible: 0xFFFE)
fwrite16le(0xFFFE, mFile);
fwrite16le(0xFFFE, mFile.get());
// 16-bit val, channel count
fwrite16le(static_cast<ushort>(channels), mFile);
fwrite16le(static_cast<ushort>(channels), mFile.get());
// 32-bit val, frequency
fwrite32le(mDevice->Frequency, mFile);
fwrite32le(mDevice->Frequency, mFile.get());
// 32-bit val, bytes per second
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
fwrite32le(mDevice->Frequency * channels * bytes, mFile.get());
// 16-bit val, frame size
fwrite16le(static_cast<ushort>(channels * bytes), mFile);
fwrite16le(static_cast<ushort>(channels * bytes), mFile.get());
// 16-bit val, bits per sample
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
// 16-bit val, extra byte count
fwrite16le(22, mFile);
fwrite16le(22, mFile.get());
// 16-bit val, valid bits per sample
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
// 32-bit val, channel mask
fwrite32le(chanmask, mFile);
fwrite32le(chanmask, mFile.get());
// 16 byte GUID, sub-type format
val = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
(void)val;
std::ignore = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) :
(isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get());
fputs("data", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
fputs("data", mFile.get());
fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close
if(ferror(mFile))
if(ferror(mFile.get()))
{
ERR("Error writing header: %s\n", strerror(errno));
ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str());
return false;
}
mDataStart = ftell(mFile);
mDataStart = ftell(mFile.get());
setDefaultWFXChannelOrder();
@ -337,7 +338,7 @@ bool WaveBackend::reset()
void WaveBackend::start()
{
if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0)
WARN("Failed to seek on output file\n");
try {
mKillNow.store(false, std::memory_order_release);
@ -357,14 +358,14 @@ void WaveBackend::stop()
if(mDataStart > 0)
{
long size{ftell(mFile)};
long size{ftell(mFile.get())};
if(size > 0)
{
long dataLen{size - mDataStart};
if(fseek(mFile, 4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
if(fseek(mFile.get(), 4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(size-8), mFile.get()); // 'WAVE' header len
if(fseek(mFile.get(), mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<uint>(dataLen), mFile.get()); // 'data' header len
}
}
}
@ -378,19 +379,16 @@ bool WaveBackendFactory::init()
bool WaveBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
std::string WaveBackendFactory::probe(BackendType type)
auto WaveBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
outnames.append(waveDevice, sizeof(waveDevice));
break;
return std::vector{std::string{GetDeviceName()}};
case BackendType::Capture:
break;
}
return outnames;
return {};
}
BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)

View file

@ -5,15 +5,15 @@
struct WaveBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WAVE_H */

View file

@ -22,8 +22,8 @@
#include "winmm.h"
#include <stdlib.h>
#include <stdio.h>
#include <cstdlib>
#include <cstdio>
#include <memory.h>
#include <windows.h>
@ -38,13 +38,15 @@
#include <algorithm>
#include <functional>
#include "alnumeric.h"
#include "alsem.h"
#include "alstring.h"
#include "althrd_setname.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
@ -55,13 +57,13 @@ namespace {
#define DEVNAME_HEAD "OpenAL Soft on "
al::vector<std::string> PlaybackDevices;
al::vector<std::string> CaptureDevices;
std::vector<std::string> PlaybackDevices;
std::vector<std::string> CaptureDevices;
bool checkName(const al::vector<std::string> &list, const std::string &name)
bool checkName(const std::vector<std::string> &list, const std::string &name)
{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
void ProbePlaybackDevices(void)
void ProbePlaybackDevices()
{
PlaybackDevices.clear();
@ -74,7 +76,7 @@ void ProbePlaybackDevices(void)
WAVEOUTCAPSW WaveCaps{};
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
int count{1};
std::string newname{basename};
@ -92,7 +94,7 @@ void ProbePlaybackDevices(void)
}
}
void ProbeCaptureDevices(void)
void ProbeCaptureDevices()
{
CaptureDevices.clear();
@ -105,7 +107,7 @@ void ProbeCaptureDevices(void)
WAVEINCAPSW WaveCaps{};
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))};
int count{1};
std::string newname{basename};
@ -134,7 +136,7 @@ struct WinMMPlayback final : public BackendBase {
int mixerProc();
void open(const char *name) override;
void open(std::string_view name) override;
bool reset() override;
void start() override;
void stop() override;
@ -143,6 +145,7 @@ struct WinMMPlayback final : public BackendBase {
al::semaphore mSem;
uint mIdx{0u};
std::array<WAVEHDR,4> mWaveBuffer{};
al::vector<char,16> mBuffer;
HWAVEOUT mOutHdl{nullptr};
@ -150,8 +153,6 @@ struct WinMMPlayback final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMPlayback)
};
WinMMPlayback::~WinMMPlayback()
@ -159,14 +160,11 @@ WinMMPlayback::~WinMMPlayback()
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMPlayback::waveOutProc
*
* Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
* Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is
* completed and returns to the application (for more data)
*/
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@ -179,7 +177,7 @@ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PT
FORCE_ALIGN int WinMMPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
althrd_setname(GetMixerThreadName());
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
@ -207,59 +205,55 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
}
void WinMMPlayback::open(const char *name)
void WinMMPlayback::open(std::string_view name)
{
if(PlaybackDevices.empty())
ProbePlaybackDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
auto iter = !name.empty() ?
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
PlaybackDevices.cbegin();
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
DevFmtType fmttype{mDevice->FmtType};
retry_open:
WAVEFORMATEX format{};
if(fmttype == DevFmtFloat)
{
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
HWAVEOUT outHandle{};
MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
{
do {
format = WAVEFORMATEX{};
if(fmttype == DevFmtFloat)
{
fmttype = DevFmtShort;
goto retry_open;
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
}
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
}
else
{
format.wFormatTag = WAVE_FORMAT_PCM;
if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
format.wBitsPerSample = 8;
else
format.wBitsPerSample = 16;
}
format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
format.nSamplesPerSec = mDevice->Frequency;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res == MMSYSERR_NOERROR) break;
if(fmttype != DevFmtFloat)
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u",
res};
fmttype = DevFmtShort;
} while(true);
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = outHandle;
mFormat = format;
mDevice->DeviceName = PlaybackDevices[DeviceID];
@ -312,11 +306,11 @@ bool WinMMPlayback::reset()
}
setDefaultWFXChannelOrder();
uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
al_free(mWaveBuffer[0].lpData);
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].lpData = mBuffer.data();
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();i++)
{
@ -369,16 +363,17 @@ struct WinMMCapture final : public BackendBase {
int captureProc();
void open(const char *name) override;
void open(std::string_view name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
void captureSamples(std::byte *buffer, uint samples) override;
uint availableSamples() override;
std::atomic<uint> mReadable{0u};
al::semaphore mSem;
uint mIdx{0};
std::array<WAVEHDR,4> mWaveBuffer{};
al::vector<char,16> mBuffer;
HWAVEIN mInHdl{nullptr};
@ -388,8 +383,6 @@ struct WinMMCapture final : public BackendBase {
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMCapture)
};
WinMMCapture::~WinMMCapture()
@ -398,14 +391,11 @@ WinMMCapture::~WinMMCapture()
if(mInHdl)
waveInClose(mInHdl);
mInHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMCapture::waveInProc
*
* Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
* Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is
* completed and returns to the application (with more data).
*/
void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@ -417,7 +407,7 @@ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR)
int WinMMCapture::captureProc()
{
althrd_setname(RECORD_THREAD_NAME);
althrd_setname(GetRecordThreadName());
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
@ -434,7 +424,8 @@ int WinMMCapture::captureProc()
WAVEHDR &waveHdr = mWaveBuffer[widx];
widx = (widx+1) % mWaveBuffer.size();
mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
std::ignore = mRing->write(waveHdr.lpData,
waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
mReadable.fetch_sub(1, std::memory_order_acq_rel);
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
@ -445,18 +436,18 @@ int WinMMCapture::captureProc()
}
void WinMMCapture::open(const char *name)
void WinMMCapture::open(std::string_view name)
{
if(CaptureDevices.empty())
ProbeCaptureDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
auto iter = !name.empty() ?
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
CaptureDevices.cbegin();
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
al::sizei(name), name.data()};
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
switch(mDevice->FmtChans)
@ -470,6 +461,7 @@ void WinMMCapture::open(const char *name)
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@ -513,14 +505,14 @@ void WinMMCapture::open(const char *name)
// Allocate circular memory buffer for the captured audio
// Make sure circular buffer is at least 100ms in size
uint CapturedDataSize{mDevice->BufferSize};
CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
const auto CapturedDataSize = std::max<size_t>(mDevice->BufferSize,
BufferSize*mWaveBuffer.size());
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
al_free(mWaveBuffer[0].lpData);
decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].lpData = mBuffer.data();
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();++i)
{
@ -571,8 +563,8 @@ void WinMMCapture::stop()
mIdx = 0;
}
void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
void WinMMCapture::captureSamples(std::byte *buffer, uint samples)
{ std::ignore = mRing->read(buffer, samples); }
uint WinMMCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
@ -586,26 +578,23 @@ bool WinMMBackendFactory::init()
bool WinMMBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string WinMMBackendFactory::probe(BackendType type)
auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
{
std::string outnames;
std::vector<std::string> outnames;
auto add_device = [&outnames](const std::string &dname) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
if(!dname.empty())
outnames.append(dname.c_str(), dname.length()+1);
};
{ if(!dname.empty()) outnames.emplace_back(dname); };
switch(type)
{
case BackendType::Playback:
ProbePlaybackDevices();
outnames.reserve(PlaybackDevices.size());
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
ProbeCaptureDevices();
outnames.reserve(CaptureDevices.size());
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}

View file

@ -5,15 +5,15 @@
struct WinMMBackendFactory final : public BackendFactory {
public:
bool init() override;
auto init() -> bool final;
bool querySupport(BackendType type) override;
auto querySupport(BackendType type) -> bool final;
std::string probe(BackendType type) override;
auto enumerate(BackendType type) -> std::vector<std::string> final;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static BackendFactory &getFactory();
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_WINMM_H */

View file

@ -4,21 +4,28 @@
#include "context.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstring>
#include <functional>
#include <limits>
#include <numeric>
#include <stddef.h>
#include <stdexcept>
#include <string_view>
#include <utility>
#include "AL/efx.h"
#include "al/auxeffectslot.h"
#include "al/debug.h"
#include "al/source.h"
#include "al/effect.h"
#include "al/event.h"
#include "al/listener.h"
#include "albit.h"
#include "alc/alu.h"
#include "alc/backends/base.h"
#include "alspan.h"
#include "core/async_event.h"
#include "core/device.h"
#include "core/effectslot.h"
@ -26,63 +33,68 @@
#include "core/voice.h"
#include "core/voice_change.h"
#include "device.h"
#include "flexarray.h"
#include "ringbuffer.h"
#include "vecmat.h"
#ifdef ALSOFT_EAX
#include <cstring>
#include "alstring.h"
#include "al/eax/globals.h"
#endif // ALSOFT_EAX
namespace {
using namespace std::placeholders;
using namespace std::string_view_literals;
using voidp = void*;
/* Default context extensions */
constexpr ALchar alExtList[] =
"AL_EXT_ALAW "
"AL_EXT_BFORMAT "
"AL_EXT_DOUBLE "
"AL_EXT_EXPONENT_DISTANCE "
"AL_EXT_FLOAT32 "
"AL_EXT_IMA4 "
"AL_EXT_LINEAR_DISTANCE "
"AL_EXT_MCFORMATS "
"AL_EXT_MULAW "
"AL_EXT_MULAW_BFORMAT "
"AL_EXT_MULAW_MCFORMATS "
"AL_EXT_OFFSET "
"AL_EXT_source_distance_model "
"AL_EXT_SOURCE_RADIUS "
"AL_EXT_STATIC_BUFFER "
"AL_EXT_STEREO_ANGLES "
"AL_LOKI_quadriphonic "
"AL_SOFT_bformat_ex "
"AL_SOFTX_bformat_hoa "
"AL_SOFT_block_alignment "
"AL_SOFT_buffer_length_query "
"AL_SOFT_callback_buffer "
"AL_SOFTX_convolution_reverb "
"AL_SOFT_deferred_updates "
"AL_SOFT_direct_channels "
"AL_SOFT_direct_channels_remix "
"AL_SOFT_effect_target "
"AL_SOFT_events "
"AL_SOFT_gain_clamp_ex "
"AL_SOFTX_hold_on_disconnect "
"AL_SOFT_loop_points "
"AL_SOFTX_map_buffer "
"AL_SOFT_MSADPCM "
"AL_SOFT_source_latency "
"AL_SOFT_source_length "
"AL_SOFT_source_resampler "
"AL_SOFT_source_spatialize "
"AL_SOFT_source_start_delay "
"AL_SOFT_UHJ "
"AL_SOFT_UHJ_ex";
std::vector<std::string_view> getContextExtensions() noexcept
{
return std::vector<std::string_view>{
"AL_EXT_ALAW"sv,
"AL_EXT_BFORMAT"sv,
"AL_EXT_debug"sv,
"AL_EXT_direct_context"sv,
"AL_EXT_DOUBLE"sv,
"AL_EXT_EXPONENT_DISTANCE"sv,
"AL_EXT_FLOAT32"sv,
"AL_EXT_IMA4"sv,
"AL_EXT_LINEAR_DISTANCE"sv,
"AL_EXT_MCFORMATS"sv,
"AL_EXT_MULAW"sv,
"AL_EXT_MULAW_BFORMAT"sv,
"AL_EXT_MULAW_MCFORMATS"sv,
"AL_EXT_OFFSET"sv,
"AL_EXT_source_distance_model"sv,
"AL_EXT_SOURCE_RADIUS"sv,
"AL_EXT_STATIC_BUFFER"sv,
"AL_EXT_STEREO_ANGLES"sv,
"AL_LOKI_quadriphonic"sv,
"AL_SOFT_bformat_ex"sv,
"AL_SOFTX_bformat_hoa"sv,
"AL_SOFT_block_alignment"sv,
"AL_SOFT_buffer_length_query"sv,
"AL_SOFT_callback_buffer"sv,
"AL_SOFTX_convolution_effect"sv,
"AL_SOFT_deferred_updates"sv,
"AL_SOFT_direct_channels"sv,
"AL_SOFT_direct_channels_remix"sv,
"AL_SOFT_effect_target"sv,
"AL_SOFT_events"sv,
"AL_SOFT_gain_clamp_ex"sv,
"AL_SOFTX_hold_on_disconnect"sv,
"AL_SOFT_loop_points"sv,
"AL_SOFTX_map_buffer"sv,
"AL_SOFT_MSADPCM"sv,
"AL_SOFT_source_latency"sv,
"AL_SOFT_source_length"sv,
"AL_SOFTX_source_panning"sv,
"AL_SOFT_source_resampler"sv,
"AL_SOFT_source_spatialize"sv,
"AL_SOFT_source_start_delay"sv,
"AL_SOFT_UHJ"sv,
"AL_SOFT_UHJ_ex"sv,
};
}
} // namespace
@ -90,10 +102,9 @@ constexpr ALchar alExtList[] =
std::atomic<bool> ALCcontext::sGlobalContextLock{false};
std::atomic<ALCcontext*> ALCcontext::sGlobalContext{nullptr};
thread_local ALCcontext *ALCcontext::sLocalContext{nullptr};
ALCcontext::ThreadCtx::~ThreadCtx()
{
if(ALCcontext *ctx{ALCcontext::sLocalContext})
if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)})
{
const bool result{ctx->releaseIfNoDelete()};
ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx},
@ -105,23 +116,18 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext;
ALeffect ALCcontext::sDefaultEffect;
#ifdef __MINGW32__
ALCcontext *ALCcontext::getThreadContext() noexcept
{ return sLocalContext; }
void ALCcontext::setThreadContext(ALCcontext *context) noexcept
{ sThreadContext.set(context); }
#endif
ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device)
: ContextBase{device.get()}, mALDevice{std::move(device)}
ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags)
: ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags}
{
mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{});
mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed);
}
ALCcontext::~ALCcontext()
{
TRACE("Freeing context %p\n", voidp{this});
size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u},
size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz,
[](size_t cur, const SourceSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
if(count > 0)
@ -134,7 +140,7 @@ ALCcontext::~ALCcontext()
#endif // ALSOFT_EAX
mDefaultSlot = nullptr;
count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u},
count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), 0_uz,
[](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
@ -151,16 +157,17 @@ void ALCcontext::init()
aluInitEffectPanning(mDefaultSlot->mSlot, this);
}
EffectSlotArray *auxslots;
std::unique_ptr<EffectSlotArray> auxslots;
if(!mDefaultSlot)
auxslots = EffectSlot::CreatePtrArray(0);
else
{
auxslots = EffectSlot::CreatePtrArray(1);
auxslots = EffectSlot::CreatePtrArray(2);
(*auxslots)[0] = mDefaultSlot->mSlot;
(*auxslots)[1] = mDefaultSlot->mSlot;
mDefaultSlot->mState = SlotState::Playing;
}
mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed);
allocVoiceChanges();
{
@ -170,26 +177,41 @@ void ALCcontext::init()
mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
}
mExtensionList = alExtList;
mExtensions = getContextExtensions();
if(sBufferSubDataCompat)
{
std::string extlist{mExtensionList};
const auto pos = extlist.find("AL_EXT_SOURCE_RADIUS ");
if(pos != std::string::npos)
extlist.replace(pos, 20, "AL_SOFT_buffer_sub_data");
else
extlist += " AL_SOFT_buffer_sub_data";
mExtensionListOverride = std::move(extlist);
mExtensionList = mExtensionListOverride.c_str();
auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv);
if(iter != mExtensions.end()) mExtensions.erase(iter);
/* TODO: Would be nice to sort this alphabetically. Needs case-
* insensitive searching.
*/
mExtensions.emplace_back("AL_SOFT_buffer_sub_data"sv);
}
#ifdef ALSOFT_EAX
eax_initialize_extensions();
#endif // ALSOFT_EAX
if(!mExtensions.empty())
{
const size_t len{std::accumulate(mExtensions.cbegin()+1, mExtensions.cend(),
mExtensions.front().length(),
[](size_t current, std::string_view ext) noexcept
{ return current + ext.length() + 1; })};
std::string extensions;
extensions.reserve(len);
extensions += mExtensions.front();
for(std::string_view ext : al::span{mExtensions}.subspan<1>())
{
extensions += ' ';
extensions += ext;
}
mExtensionsString = std::move(extensions);
}
mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f};
mParams.Matrix = alu::Matrix::Identity();
mParams.Velocity = alu::Vector{};
@ -202,7 +224,7 @@ void ALCcontext::init()
mParams.mDistanceModel = mDistanceModel;
mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false);
mAsyncEvents = RingBuffer::Create(1024, sizeof(AsyncEvent), false);
StartEventThrd(this);
@ -210,7 +232,7 @@ void ALCcontext::init()
mActiveVoiceCount.store(64, std::memory_order_relaxed);
}
bool ALCcontext::deinit()
void ALCcontext::deinit()
{
if(sLocalContext == this)
{
@ -230,18 +252,14 @@ bool ALCcontext::deinit()
dec_ref();
}
bool ret{};
bool stopPlayback{};
/* First make sure this context exists in the device's list. */
auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire);
if(auto toremove = static_cast<size_t>(std::count(oldarray->begin(), oldarray->end(), this)))
{
using ContextArray = al::FlexArray<ContextBase*>;
auto alloc_ctx_array = [](const size_t count) -> ContextArray*
{
if(count == 0) return &DeviceBase::sEmptyContextArray;
return ContextArray::Create(count).release();
};
auto *newarray = alloc_ctx_array(oldarray->size() - toremove);
const size_t newsize{oldarray->size() - toremove};
auto newarray = ContextArray::Create(newsize);
/* Copy the current/old context handles to the new array, excluding the
* given context.
@ -252,21 +270,21 @@ bool ALCcontext::deinit()
/* Store the new context array in the device. Wait for any current mix
* to finish before deleting the old array.
*/
mDevice->mContexts.store(newarray);
if(oldarray != &DeviceBase::sEmptyContextArray)
{
mDevice->waitForMix();
delete oldarray;
}
auto prevarray = mDevice->mContexts.exchange(std::move(newarray));
std::ignore = mDevice->waitForMix();
ret = !newarray->empty();
stopPlayback = (newsize == 0);
}
else
ret = !oldarray->empty();
stopPlayback = oldarray->empty();
StopEventThrd(this);
return ret;
if(stopPlayback && mALDevice->mDeviceState == DeviceState::Playing)
{
mALDevice->Backend->stop();
mALDevice->mDeviceState = DeviceState::Configured;
}
}
void ALCcontext::applyAllUpdates()
@ -295,6 +313,7 @@ void ALCcontext::applyAllUpdates()
mHoldUpdates.store(false, std::memory_order_release);
}
#ifdef ALSOFT_EAX
namespace {
@ -306,10 +325,10 @@ void ForEachSource(ALCcontext *context, F func)
uint64_t usemask{~sublist.FreeMask};
while(usemask)
{
const int idx{al::countr_zero(usemask)};
const auto idx = static_cast<uint>(al::countr_zero(usemask));
usemask &= ~(1_u64 << idx);
func(sublist.Sources[idx]);
func((*sublist.Sources)[idx]);
}
}
}
@ -447,43 +466,15 @@ void ALCcontext::eax_initialize_extensions()
if(!eax_g_is_enabled)
return;
const auto string_max_capacity =
std::strlen(mExtensionList) + 1 +
std::strlen(eax1_ext_name) + 1 +
std::strlen(eax2_ext_name) + 1 +
std::strlen(eax3_ext_name) + 1 +
std::strlen(eax4_ext_name) + 1 +
std::strlen(eax5_ext_name) + 1 +
std::strlen(eax_x_ram_ext_name) + 1;
std::string extlist;
extlist.reserve(string_max_capacity);
mExtensions.emplace(mExtensions.begin(), "EAX-RAM"sv);
if(eaxIsCapable())
{
extlist += eax1_ext_name;
extlist += ' ';
extlist += eax2_ext_name;
extlist += ' ';
extlist += eax3_ext_name;
extlist += ' ';
extlist += eax4_ext_name;
extlist += ' ';
extlist += eax5_ext_name;
extlist += ' ';
mExtensions.emplace(mExtensions.begin(), "EAX5.0"sv);
mExtensions.emplace(mExtensions.begin(), "EAX4.0"sv);
mExtensions.emplace(mExtensions.begin(), "EAX3.0"sv);
mExtensions.emplace(mExtensions.begin(), "EAX2.0"sv);
mExtensions.emplace(mExtensions.begin(), "EAX"sv);
}
extlist += eax_x_ram_ext_name;
extlist += ' ';
extlist += mExtensionList;
mExtensionListOverride = std::move(extlist);
mExtensionList = mExtensionListOverride.c_str();
}
void ALCcontext::eax_initialize()
@ -555,10 +546,11 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const
case DevFmtX51: return SPEAKERS_5;
case DevFmtX61: return SPEAKERS_6;
case DevFmtX71: return SPEAKERS_7;
/* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to
/* 7.1.4(.4) is compatible with 7.1. This could instead be HEADPHONES to
* suggest with-height surround sound (like HRTF).
*/
case DevFmtX714: return SPEAKERS_7;
case DevFmtX7144: return SPEAKERS_7;
/* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to
* suggest full-sphere surround sound (like HRTF).
*/
@ -582,7 +574,7 @@ void ALCcontext::eax_update_speaker_configuration()
void ALCcontext::eax_set_last_error_defaults() noexcept
{
mEaxLastError = EAX_OK;
mEaxLastError = EAXCONTEXT_DEFAULTLASTERROR;
}
void ALCcontext::eax_session_set_defaults() noexcept
@ -671,6 +663,7 @@ void ALCcontext::eax_get_misc(const EaxCall& call)
break;
case EAXCONTEXT_LASTERROR:
call.set_value<ContextException>(mEaxLastError);
mEaxLastError = EAX_OK;
break;
case EAXCONTEXT_SPEAKERCONFIG:
call.set_value<ContextException>(mEaxSpeakerConfig);
@ -1018,88 +1011,49 @@ void ALCcontext::eaxCommit()
eax_update_sources();
}
namespace {
class EaxSetException : public EaxException {
public:
explicit EaxSetException(const char* message)
: EaxException{"EAX_SET", message}
{}
};
[[noreturn]] void eax_fail_set(const char* message)
{
throw EaxSetException{message};
}
class EaxGetException : public EaxException {
public:
explicit EaxGetException(const char* message)
: EaxException{"EAX_GET", message}
{}
};
[[noreturn]] void eax_fail_get(const char* message)
{
throw EaxGetException{message};
}
} // namespace
FORCE_ALIGN ALenum AL_APIENTRY EAXSet(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size) noexcept
try
FORCE_ALIGN auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id,
ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
{
auto context = GetContextRef();
if(!context)
eax_fail_set("No current context.");
std::lock_guard<std::mutex> prop_lock{context->mPropLock};
return context->eax_eax_set(
property_set_id,
property_id,
property_source_id,
property_value,
property_value_size);
if(!context) UNLIKELY return AL_INVALID_OPERATION;
return EAXSetDirect(context.get(), property_set_id, property_id, source_id, value, value_size);
}
catch (...)
FORCE_ALIGN auto AL_APIENTRY EAXSetDirect(ALCcontext *context, const GUID *property_set_id,
ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
try
{
eax_log_exception(__func__);
std::lock_guard<std::mutex> prop_lock{context->mPropLock};
return context->eax_eax_set(property_set_id, property_id, source_id, value, value_size);
}
catch(...)
{
context->eaxSetLastError();
eax_log_exception(std::data(__func__));
return AL_INVALID_OPERATION;
}
FORCE_ALIGN ALenum AL_APIENTRY EAXGet(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size) noexcept
try
FORCE_ALIGN auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id,
ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
{
auto context = GetContextRef();
if(!context)
eax_fail_get("No current context.");
std::lock_guard<std::mutex> prop_lock{context->mPropLock};
return context->eax_eax_get(
property_set_id,
property_id,
property_source_id,
property_value,
property_value_size);
if(!context) UNLIKELY return AL_INVALID_OPERATION;
return EAXGetDirect(context.get(), property_set_id, property_id, source_id, value, value_size);
}
catch (...)
FORCE_ALIGN auto AL_APIENTRY EAXGetDirect(ALCcontext *context, const GUID *property_set_id,
ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
try
{
eax_log_exception(__func__);
std::lock_guard<std::mutex> prop_lock{context->mPropLock};
return context->eax_eax_get(property_set_id, property_id, source_id, value, value_size);
}
catch(...)
{
context->eaxSetLastError();
eax_log_exception(std::data(__func__));
return AL_INVALID_OPERATION;
}
#endif // ALSOFT_EAX

View file

@ -1,11 +1,17 @@
#ifndef ALC_CONTEXT_H
#define ALC_CONTEXT_H
#include <array>
#include <atomic>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <stdint.h>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#include "AL/al.h"
#include "AL/alc.h"
@ -14,10 +20,11 @@
#include "al/listener.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "althreads.h"
#include "atomic.h"
#include "core/context.h"
#include "inprogext.h"
#include "intrusive_ptr.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "al/eax/call.h"
@ -30,40 +37,40 @@
struct ALeffect;
struct ALeffectslot;
struct ALsource;
struct DebugGroup;
struct EffectSlotSubList;
struct SourceSubList;
enum class DebugSource : std::uint8_t;
enum class DebugType : std::uint8_t;
enum class DebugSeverity : std::uint8_t;
using uint = unsigned int;
struct SourceSubList {
uint64_t FreeMask{~0_u64};
ALsource *Sources{nullptr}; /* 64 */
enum ContextFlags {
DebugBit = 0, /* ALC_CONTEXT_DEBUG_BIT_EXT */
};
using ContextFlagBitset = std::bitset<sizeof(ALuint)*8>;
SourceSubList() noexcept = default;
SourceSubList(const SourceSubList&) = delete;
SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
{ rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
~SourceSubList();
SourceSubList& operator=(const SourceSubList&) = delete;
SourceSubList& operator=(SourceSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
struct DebugLogEntry {
const DebugSource mSource;
const DebugType mType;
const DebugSeverity mSeverity;
const uint mId;
std::string mMessage;
template<typename T>
DebugLogEntry(DebugSource source, DebugType type, uint id, DebugSeverity severity, T&& message)
: mSource{source}, mType{type}, mSeverity{severity}, mId{id}
, mMessage{std::forward<T>(message)}
{ }
DebugLogEntry(const DebugLogEntry&) = default;
DebugLogEntry(DebugLogEntry&&) = default;
};
struct EffectSlotSubList {
uint64_t FreeMask{~0_u64};
ALeffectslot *EffectSlots{nullptr}; /* 64 */
EffectSlotSubList() noexcept = default;
EffectSlotSubList(const EffectSlotSubList&) = delete;
EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
: FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
{ rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
~EffectSlotSubList();
EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
};
struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
const al::intrusive_ptr<ALCdevice> mALDevice;
@ -74,7 +81,10 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
std::mutex mPropLock;
std::atomic<ALenum> mLastError{AL_NO_ERROR};
al::tss<ALenum> mLastThreadError{AL_NO_ERROR};
const ContextFlagBitset mContextFlags;
std::atomic<bool> mDebugEnabled{false};
DistanceModel mDistanceModel{DistanceModel::Default};
bool mSourceDistanceModel{false};
@ -88,25 +98,32 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
ALEVENTPROCSOFT mEventCb{};
void *mEventParam{nullptr};
std::mutex mDebugCbLock;
ALDEBUGPROCEXT mDebugCb{};
void *mDebugParam{nullptr};
std::vector<DebugGroup> mDebugGroups;
std::deque<DebugLogEntry> mDebugLog;
ALlistener mListener{};
al::vector<SourceSubList> mSourceList;
std::vector<SourceSubList> mSourceList;
ALuint mNumSources{0};
std::mutex mSourceLock;
al::vector<EffectSlotSubList> mEffectSlotList;
std::vector<EffectSlotSubList> mEffectSlotList;
ALuint mNumEffectSlots{0u};
std::mutex mEffectSlotLock;
/* Default effect slot */
std::unique_ptr<ALeffectslot> mDefaultSlot;
const char *mExtensionList{nullptr};
std::vector<std::string_view> mExtensions;
std::string mExtensionsString{};
std::string mExtensionListOverride{};
std::unordered_map<ALuint,std::string> mSourceNames;
std::unordered_map<ALuint,std::string> mEffectSlotNames;
ALCcontext(al::intrusive_ptr<ALCdevice> device);
ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags);
ALCcontext(const ALCcontext&) = delete;
ALCcontext& operator=(const ALCcontext&) = delete;
~ALCcontext();
@ -114,10 +131,10 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
void init();
/**
* Removes the context from its device and removes it from being current on
* the running thread or globally. Returns true if other contexts still
* exist on the device.
* the running thread or globally. Stops device playback if this was the
* last context on its device.
*/
bool deinit();
void deinit();
/**
* Defers/suspends updates for the given context's listener and sources.
@ -142,20 +159,32 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
*/
void applyAllUpdates();
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
void setError(ALenum errorCode, const char *msg, ...);
void sendDebugMessage(std::unique_lock<std::mutex> &debuglock, DebugSource source,
DebugType type, ALuint id, DebugSeverity severity, std::string_view message);
void debugMessage(DebugSource source, DebugType type, ALuint id, DebugSeverity severity,
std::string_view message)
{
if(!mDebugEnabled.load(std::memory_order_relaxed)) LIKELY
return;
std::unique_lock<std::mutex> debuglock{mDebugCbLock};
sendDebugMessage(debuglock, source, type, id, severity, message);
}
/* Process-wide current context */
static std::atomic<bool> sGlobalContextLock;
static std::atomic<ALCcontext*> sGlobalContext;
private:
/* Thread-local current context. */
static thread_local ALCcontext *sLocalContext;
static inline thread_local ALCcontext *sLocalContext{};
/* Thread-local context handling. This handles attempting to release the
* context which may have been left current when the thread is destroyed.
@ -163,30 +192,25 @@ private:
class ThreadCtx {
public:
~ThreadCtx();
/* NOLINTBEGIN(readability-convert-member-functions-to-static)
* This should be non-static to invoke construction of the thread-local
* sThreadContext, so that it's destructor gets run at thread exit to
* clear sLocalContext (which isn't a member variable to make read
* access efficient).
*/
void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; }
/* NOLINTEND(readability-convert-member-functions-to-static) */
};
static thread_local ThreadCtx sThreadContext;
public:
/* HACK: MinGW generates bad code when accessing an extern thread_local
* object. Add a wrapper function for it that only accesses it where it's
* defined.
*/
#ifdef __MINGW32__
static ALCcontext *getThreadContext() noexcept;
static void setThreadContext(ALCcontext *context) noexcept;
#else
static ALCcontext *getThreadContext() noexcept { return sLocalContext; }
static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); }
#endif
/* Default effect that applies to sources that don't have an effect on send 0. */
static ALeffect sDefaultEffect;
DEF_NEWDEL(ALCcontext)
#ifdef ALSOFT_EAX
public:
bool hasEax() const noexcept { return mEaxIsInitialized; }
bool eaxIsCapable() const noexcept;
@ -430,7 +454,7 @@ private:
typename TMemberResult,
typename TProps,
typename TState>
void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) noexcept
void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member)
{
const auto& src = call.get_value<ContextException, const TMemberResult>();
TValidator{}(src);
@ -513,28 +537,20 @@ private:
using ContextRef = al::intrusive_ptr<ALCcontext>;
ContextRef GetContextRef(void);
ContextRef GetContextRef() noexcept;
void UpdateContextProps(ALCcontext *context);
extern bool TrapALError;
inline bool TrapALError{false};
#ifdef ALSOFT_EAX
ALenum AL_APIENTRY EAXSet(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size) noexcept;
auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id,
ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum;
ALenum AL_APIENTRY EAXGet(
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_value,
ALuint property_value_size) noexcept;
auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id,
ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum;
#endif // ALSOFT_EAX
#endif /* ALC_CONTEXT_H */

View file

@ -3,19 +3,22 @@
#include "device.h"
#include <algorithm>
#include <cstddef>
#include <numeric>
#include <stddef.h>
#include "al/buffer.h"
#include "al/effect.h"
#include "al/filter.h"
#include "albit.h"
#include "alconfig.h"
#include "alnumeric.h"
#include "atomic.h"
#include "backends/base.h"
#include "core/bformatdec.h"
#include "core/bs2b.h"
#include "core/front_stablizer.h"
#include "core/devformat.h"
#include "core/hrtf.h"
#include "core/logging.h"
#include "core/mastering.h"
#include "core/uhjfilter.h"
#include "flexarray.h"
namespace {
@ -34,19 +37,19 @@ ALCdevice::~ALCdevice()
Backend = nullptr;
size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u},
size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), 0_uz,
[](size_t cur, const BufferSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
if(count > 0)
WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s");
count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u},
count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz,
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s");
count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u},
count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz,
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t
{ return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
if(count > 0)
@ -55,8 +58,8 @@ ALCdevice::~ALCdevice()
void ALCdevice::enumerateHrtfs()
{
mHrtfList = EnumerateHrtf(configValue<std::string>(nullptr, "hrtf-paths"));
if(auto defhrtfopt = configValue<std::string>(nullptr, "default-hrtf"))
mHrtfList = EnumerateHrtf(configValue<std::string>({}, "hrtf-paths"));
if(auto defhrtfopt = configValue<std::string>({}, "default-hrtf"))
{
auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt);
if(iter == mHrtfList.end())
@ -85,6 +88,7 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1
case DevFmtX61: return OutputMode1::X61;
case DevFmtX71: return OutputMode1::X71;
case DevFmtX714:
case DevFmtX7144:
case DevFmtX3D71:
case DevFmtAmbi3D:
break;

View file

@ -4,79 +4,32 @@
#include <atomic>
#include <memory>
#include <mutex>
#include <stdint.h>
#include <optional>
#include <string>
#include <utility>
#include <unordered_map>
#include <string_view>
#include <vector>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/device.h"
#include "inprogext.h"
#include "intrusive_ptr.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "al/eax/x_ram.h"
#endif // ALSOFT_EAX
struct ALbuffer;
struct ALeffect;
struct ALfilter;
struct BackendBase;
struct BufferSubList;
struct EffectSubList;
struct FilterSubList;
using uint = unsigned int;
struct BufferSubList {
uint64_t FreeMask{~0_u64};
ALbuffer *Buffers{nullptr}; /* 64 */
BufferSubList() noexcept = default;
BufferSubList(const BufferSubList&) = delete;
BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers}
{ rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; }
~BufferSubList();
BufferSubList& operator=(const BufferSubList&) = delete;
BufferSubList& operator=(BufferSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; }
};
struct EffectSubList {
uint64_t FreeMask{~0_u64};
ALeffect *Effects{nullptr}; /* 64 */
EffectSubList() noexcept = default;
EffectSubList(const EffectSubList&) = delete;
EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects}
{ rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; }
~EffectSubList();
EffectSubList& operator=(const EffectSubList&) = delete;
EffectSubList& operator=(EffectSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; }
};
struct FilterSubList {
uint64_t FreeMask{~0_u64};
ALfilter *Filters{nullptr}; /* 64 */
FilterSubList() noexcept = default;
FilterSubList(const FilterSubList&) = delete;
FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters}
{ rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; }
~FilterSubList();
FilterSubList& operator=(const FilterSubList&) = delete;
FilterSubList& operator=(FilterSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; }
};
struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
/* This lock protects the device state (format, update size, etc) from
* being from being changed in multiple threads, or being accessed while
@ -94,7 +47,7 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
uint AuxiliaryEffectSlotMax{};
std::string mHrtfName;
al::vector<std::string> mHrtfList;
std::vector<std::string> mHrtfList;
ALCenum mHrtfStatus{ALC_FALSE};
enum class OutputMode1 : ALCenum {
@ -117,49 +70,54 @@ struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
// Map of Buffers for this device
std::mutex BufferLock;
al::vector<BufferSubList> BufferList;
std::vector<BufferSubList> BufferList;
// Map of Effects for this device
std::mutex EffectLock;
al::vector<EffectSubList> EffectList;
std::vector<EffectSubList> EffectList;
// Map of Filters for this device
std::mutex FilterLock;
al::vector<FilterSubList> FilterList;
std::vector<FilterSubList> FilterList;
#ifdef ALSOFT_EAX
ALuint eax_x_ram_free_size{eax_x_ram_max_size};
#endif // ALSOFT_EAX
std::unordered_map<ALuint,std::string> mBufferNames;
std::unordered_map<ALuint,std::string> mEffectNames;
std::unordered_map<ALuint,std::string> mFilterNames;
ALCdevice(DeviceType type);
~ALCdevice();
void enumerateHrtfs();
bool getConfigValueBool(const char *block, const char *key, bool def)
{ return GetConfigValueBool(DeviceName.c_str(), block, key, def); }
bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def)
{ return GetConfigValueBool(DeviceName, block, key, def); }
template<typename T>
inline al::optional<T> configValue(const char *block, const char *key) = delete;
DEF_NEWDEL(ALCdevice)
inline std::optional<T> configValue(const std::string_view block, const std::string_view key) = delete;
};
template<>
inline al::optional<std::string> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueStr(DeviceName.c_str(), block, key); }
inline std::optional<std::string> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueStr(DeviceName, block, key); }
template<>
inline al::optional<int> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueInt(DeviceName.c_str(), block, key); }
inline std::optional<int> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueInt(DeviceName, block, key); }
template<>
inline al::optional<uint> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueUInt(DeviceName.c_str(), block, key); }
inline std::optional<uint> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueUInt(DeviceName, block, key); }
template<>
inline al::optional<float> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueFloat(DeviceName.c_str(), block, key); }
inline std::optional<float> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueFloat(DeviceName, block, key); }
template<>
inline al::optional<bool> ALCdevice::configValue(const char *block, const char *key)
{ return ConfigValueBool(DeviceName.c_str(), block, key); }
inline std::optional<bool> ALCdevice::configValue(const std::string_view block, const std::string_view key)
{ return ConfigValueBool(DeviceName, block, key); }
/** Stores the latest ALC device error. */
void alcSetError(ALCdevice *device, ALCenum errorCode);
#endif

View file

@ -22,24 +22,24 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <utility>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
@ -50,35 +50,37 @@ constexpr float QFactor{5.0f};
struct AutowahState final : public EffectState {
/* Effect parameters */
float mAttackRate;
float mReleaseRate;
float mResonanceGain;
float mPeakGain;
float mFreqMinNorm;
float mBandwidthNorm;
float mEnvDelay;
float mAttackRate{};
float mReleaseRate{};
float mResonanceGain{};
float mPeakGain{};
float mFreqMinNorm{};
float mBandwidthNorm{};
float mEnvDelay{};
/* Filter components derived from the envelope. */
struct {
float cos_w0;
float alpha;
} mEnv[BufferLineSize];
struct FilterParam {
float cos_w0{};
float alpha{};
};
std::array<FilterParam,BufferLineSize> mEnv;
struct {
struct ChannelData {
uint mTargetChannel{InvalidChannelIndex};
/* Effect filters' history. */
struct {
float z1, z2;
} mFilter;
struct FilterHistory {
float z1{}, z2{};
};
FilterHistory mFilter;
/* Effect gains for each output channel */
float mCurrentGain;
float mTargetGain;
} mChans[MaxAmbiChannels];
float mCurrentGain{};
float mTargetGain{};
};
std::array<ChannelData,MaxAmbiChannels> mChans;
/* Effects buffers */
alignas(16) float mBufferOut[BufferLineSize];
alignas(16) FloatBufferLine mBufferOut{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -86,8 +88,6 @@ struct AutowahState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(AutowahState)
};
void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -118,18 +118,19 @@ void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*)
}
void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<AutowahProps>(*props_);
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)};
mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
mAttackRate = std::exp(-1.0f / (props.AttackTime*frequency));
mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency));
/* 0-20dB Resonance Peak gain */
mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale);
mResonanceGain = std::sqrt(std::log10(props.Resonance)*10.0f / 3.0f);
mPeakGain = 1.0f - std::log10(props.PeakGain / GainScale);
mFreqMinNorm = MinFreq / frequency;
mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
@ -155,23 +156,22 @@ void AutowahState::process(const size_t samplesToDo,
float env_delay{mEnvDelay};
for(size_t i{0u};i < samplesToDo;i++)
{
float w0, sample, a;
/* Envelope follower described on the book: Audio Effects, Theory,
* Implementation and Application.
*/
sample = peak_gain * std::fabs(samplesIn[0][i]);
a = (sample > env_delay) ? attack_rate : release_rate;
const float sample{peak_gain * std::fabs(samplesIn[0][i])};
const float a{(sample > env_delay) ? attack_rate : release_rate};
env_delay = lerpf(sample, env_delay, a);
/* Calculate the cos and alpha components for this sample's filter. */
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v<float>*2.0f);
const float w0{std::min(bandwidth*env_delay + freq_min, 0.46f) *
(al::numbers::pi_v<float>*2.0f)};
mEnv[i].cos_w0 = std::cos(w0);
mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor);
}
mEnvDelay = env_delay;
auto chandata = std::begin(mChans);
auto chandata = mChans.begin();
for(const auto &insamples : samplesIn)
{
const size_t outidx{chandata->mTargetChannel};
@ -194,18 +194,18 @@ void AutowahState::process(const size_t samplesToDo,
{
const float alpha{mEnv[i].alpha};
const float cos_w0{mEnv[i].cos_w0};
float input, output;
float a[3], b[3];
b[0] = 1.0f + alpha*res_gain;
b[1] = -2.0f * cos_w0;
b[2] = 1.0f - alpha*res_gain;
a[0] = 1.0f + alpha/res_gain;
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha/res_gain;
const std::array b{
1.0f + alpha*res_gain,
-2.0f * cos_w0,
1.0f - alpha*res_gain};
const std::array a{
1.0f + alpha/res_gain,
-2.0f * cos_w0,
1.0f - alpha/res_gain};
input = insamples[i];
output = input*(b[0]/a[0]) + z1;
const float input{insamples[i]};
const float output{input*(b[0]/a[0]) + z1};
z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
mBufferOut[i] = output;
@ -214,8 +214,8 @@ void AutowahState::process(const size_t samplesToDo,
chandata->mFilter.z2 = z2;
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut[outidx],
chandata->mCurrentGain, chandata->mTargetGain, samplesToDo);
++chandata;
}
}

View file

@ -4,23 +4,27 @@
#include "core/effects/base.h"
EffectStateFactory *NullStateFactory_getFactory(void);
EffectStateFactory *ReverbStateFactory_getFactory(void);
EffectStateFactory *StdReverbStateFactory_getFactory(void);
EffectStateFactory *AutowahStateFactory_getFactory(void);
EffectStateFactory *ChorusStateFactory_getFactory(void);
EffectStateFactory *CompressorStateFactory_getFactory(void);
EffectStateFactory *DistortionStateFactory_getFactory(void);
EffectStateFactory *EchoStateFactory_getFactory(void);
EffectStateFactory *EqualizerStateFactory_getFactory(void);
EffectStateFactory *FlangerStateFactory_getFactory(void);
EffectStateFactory *FshifterStateFactory_getFactory(void);
EffectStateFactory *ModulatorStateFactory_getFactory(void);
EffectStateFactory *PshifterStateFactory_getFactory(void);
EffectStateFactory* VmorpherStateFactory_getFactory(void);
/* This is a user config option for modifying the overall output of the reverb
* effect.
*/
inline float ReverbBoost{1.0f};
EffectStateFactory *DedicatedStateFactory_getFactory(void);
EffectStateFactory *ConvolutionStateFactory_getFactory(void);
EffectStateFactory *NullStateFactory_getFactory();
EffectStateFactory *ReverbStateFactory_getFactory();
EffectStateFactory *ChorusStateFactory_getFactory();
EffectStateFactory *AutowahStateFactory_getFactory();
EffectStateFactory *CompressorStateFactory_getFactory();
EffectStateFactory *DistortionStateFactory_getFactory();
EffectStateFactory *EchoStateFactory_getFactory();
EffectStateFactory *EqualizerStateFactory_getFactory();
EffectStateFactory *FshifterStateFactory_getFactory();
EffectStateFactory *ModulatorStateFactory_getFactory();
EffectStateFactory *PshifterStateFactory_getFactory();
EffectStateFactory* VmorpherStateFactory_getFactory();
EffectStateFactory *DedicatedStateFactory_getFactory();
EffectStateFactory *ConvolutionStateFactory_getFactory();
#endif /* EFFECTS_BASE_H */

View file

@ -22,34 +22,44 @@
#include <algorithm>
#include <array>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <limits>
#include <variant>
#include <vector>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/cubic_tables.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "core/resampler_limits.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
struct BufferStorage;
namespace {
using uint = unsigned int;
constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2);
constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f});
constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f});
constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2});
constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2});
struct ChorusState final : public EffectState {
al::vector<float,16> mDelayBuffer;
std::vector<float> mDelayBuffer;
uint mOffset{0};
uint mLfoOffset{0};
@ -58,16 +68,17 @@ struct ChorusState final : public EffectState {
uint mLfoDisp{0};
/* Calculated delays to apply to the left and right outputs. */
uint mModDelays[2][BufferLineSize];
std::array<std::array<uint,BufferLineSize>,2> mModDelays{};
/* Temp storage for the modulated left and right outputs. */
alignas(16) float mBuffer[2][BufferLineSize];
alignas(16) std::array<FloatBufferLine,2> mBuffer{};
/* Gains for left and right outputs. */
struct {
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
struct OutGains {
std::array<float,MaxAmbiChannels> Current{};
std::array<float,MaxAmbiChannels> Target{};
};
std::array<OutGains,2> mGains;
/* effect parameters */
ChorusWaveform mWaveform{};
@ -78,66 +89,70 @@ struct ChorusState final : public EffectState {
void calcTriangleDelays(const size_t todo);
void calcSinusoidDelays(const size_t todo);
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,
const al::span<FloatBufferLine> samplesOut) override;
void deviceUpdate(const DeviceBase *device, const float MaxDelay);
void update(const ContextBase *context, const EffectSlot *slot, const ChorusWaveform waveform,
const float delay, const float depth, const float feedback, const float rate,
int phase, const EffectTarget target);
DEF_NEWDEL(ChorusState)
void deviceUpdate(const DeviceBase *device, const BufferStorage*) final;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_,
const EffectTarget target) final;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) final;
};
void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{
constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)};
constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay);
const auto frequency = static_cast<float>(Device->Frequency);
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)};
if(maxlen != mDelayBuffer.size())
decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer);
std::fill(mDelayBuffer.begin(), mDelayBuffer.end(), 0.0f);
for(auto &e : mGains)
{
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
e.Current.fill(0.0f);
e.Target.fill(0.0f);
}
}
void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
const EffectProps *props, const EffectTarget target)
void ChorusState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props_, const EffectTarget target)
{
constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits};
static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits};
auto &props = std::get<ChorusProps>(*props_);
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
* delay and depth to allow enough padding for resampling.
*/
const DeviceBase *device{Context->mDevice};
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
mWaveform = props->Chorus.Waveform;
mWaveform = props.Waveform;
mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay);
mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
const auto stepscale = float{frequency * gCubicTable.sTableSteps};
mDelay = std::max(float2int(std::round(props.Delay * stepscale)), mindelay);
mDepth = std::min(static_cast<float>(mDelay) * props.Depth,
static_cast<float>(mDelay - mindelay));
mFeedback = props->Chorus.Feedback;
mFeedback = props.Feedback;
/* Gains for left and right sides */
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;
const bool ispairwise{device->mRenderMode == RenderMode::Pairwise};
const auto lcoeffs = (!ispairwise) ? al::span{lcoeffs_nrml} : al::span{lcoeffs_pw};
const auto rcoeffs = (!ispairwise) ? al::span{rcoeffs_nrml} : al::span{rcoeffs_pw};
/* Attenuate the outputs by -3dB, since we duplicate a single mono input to
* separate left/right outputs.
*/
const auto gain = slot->Gain * (1.0f/al::numbers::sqrt2_v<float>);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target);
ComputePanGains(target.Main, lcoeffs, gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs, gain, mGains[1].Target);
float rate{props->Chorus.Rate};
if(!(rate > 0.0f))
if(!(props.Rate > 0.0f))
{
mLfoOffset = 0;
mLfoRange = 1;
@ -149,7 +164,9 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
/* Calculate LFO coefficient (number of samples per cycle). Limit the
* max range to avoid overflow when calculating the displacement.
*/
uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))};
static constexpr int range_limit{std::numeric_limits<int>::max()/360 - 180};
const auto range = std::round(frequency / props.Rate);
const uint lfo_range{float2uint(std::min(range, float{range_limit}))};
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
mLfoRange = lfo_range;
@ -164,8 +181,8 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
}
/* Calculate lfo phase displacement */
int phase{props->Chorus.Phase};
if(phase < 0) phase = 360 + phase;
auto phase = props.Phase;
if(phase < 0) phase += 360;
mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360;
}
}
@ -178,9 +195,6 @@ void ChorusState::calcTriangleDelays(const size_t todo)
const float depth{mDepth};
const int delay{mDelay};
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
const float offset_norm{static_cast<float>(offset) * lfo_scale};
@ -188,25 +202,24 @@ void ChorusState::calcTriangleDelays(const size_t todo)
};
uint offset{mLfoOffset};
ASSUME(lfo_range > offset);
auto ldelays = mModDelays[0].begin();
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;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
auto rdelays = mModDelays[1].begin();
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;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
@ -219,9 +232,6 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
const float depth{mDepth};
const int delay{mDelay};
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
const float offset_norm{static_cast<float>(offset) * lfo_scale};
@ -229,25 +239,24 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
};
uint offset{mLfoOffset};
ASSUME(lfo_range > offset);
auto ldelays = mModDelays[0].begin();
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;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
auto rdelays = mModDelays[1].begin();
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;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
@ -255,10 +264,10 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const size_t bufmask{mDelayBuffer.size()-1};
const auto delaybuf = al::span{mDelayBuffer};
const size_t bufmask{delaybuf.size()-1};
const float feedback{mFeedback};
const uint avgdelay{(static_cast<uint>(mDelay) + MixerFracHalf) >> MixerFracBits};
float *RESTRICT delaybuf{mDelayBuffer.data()};
uint offset{mOffset};
if(mWaveform == ChorusWaveform::Sinusoid)
@ -266,35 +275,39 @@ void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBu
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])};
const auto ldelays = al::span{mModDelays[0]};
const auto rdelays = al::span{mModDelays[1]};
const auto lbuffer = al::span{mBuffer[0]};
const auto rbuffer = al::span{mBuffer[1]};
for(size_t i{0u};i < samplesToDo;++i)
{
// Feed the buffer's input first (necessary for delays < 1).
delaybuf[offset&bufmask] = samplesIn[0][i];
// 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);
size_t delay{offset - (ldelays[i] >> gCubicTable.sTableBits)};
size_t phase{ldelays[i] & gCubicTable.sTableMask};
lbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) +
delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) +
delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) +
delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase);
// 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);
delay = offset - (rdelays[i] >> gCubicTable.sTableBits);
phase = rdelays[i] & gCubicTable.sTableMask;
rbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) +
delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) +
delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) +
delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase);
// 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,
MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current, mGains[0].Target,
samplesToDo, 0);
MixSamples({rbuffer, samplesToDo}, samplesOut, mGains[1].Current, mGains[1].Target,
MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current, mGains[1].Target,
samplesToDo, 0);
mOffset = offset;
@ -306,15 +319,6 @@ struct ChorusStateFactory final : public EffectStateFactory {
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
};
/* Flanger is basically a chorus with a really short delay. They can both use
* the same processing functions, so piggyback flanger on the chorus functions.
*/
struct FlangerStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
};
} // namespace
EffectStateFactory *ChorusStateFactory_getFactory()
@ -322,9 +326,3 @@ EffectStateFactory *ChorusStateFactory_getFactory()
static ChorusStateFactory ChorusFactory{};
return &ChorusFactory;
}
EffectStateFactory *FlangerStateFactory_getFactory()
{
static FlangerStateFactory FlangerFactory{};
return &FlangerFactory;
}

View file

@ -32,48 +32,49 @@
#include "config.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <utility>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
struct BufferStorage;
struct ContextBase;
namespace {
#define AMP_ENVELOPE_MIN 0.5f
#define AMP_ENVELOPE_MAX 2.0f
constexpr float AmpEnvelopeMin{0.5f};
constexpr float AmpEnvelopeMax{2.0f};
#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */
#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
constexpr float AttackTime{0.1f}; /* 100ms to rise from min to max */
constexpr float ReleaseTime{0.2f}; /* 200ms to drop from max to min */
struct CompressorState final : public EffectState {
/* Effect gains for each channel */
struct {
struct TargetGain {
uint mTarget{InvalidChannelIndex};
float mGain{1.0f};
} mChans[MaxAmbiChannels];
};
std::array<TargetGain,MaxAmbiChannels> mChans;
/* Effect parameters */
bool mEnabled{true};
float mAttackMult{1.0f};
float mReleaseMult{1.0f};
float mEnvFollower{1.0f};
alignas(16) FloatBufferLine mGains{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -81,8 +82,6 @@ struct CompressorState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(CompressorState)
};
void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*)
@ -90,20 +89,20 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage
/* Number of samples to do a full attack and release (non-integer sample
* counts are okay).
*/
const float attackCount{static_cast<float>(device->Frequency) * ATTACK_TIME};
const float releaseCount{static_cast<float>(device->Frequency) * RELEASE_TIME};
const float attackCount{static_cast<float>(device->Frequency) * AttackTime};
const float releaseCount{static_cast<float>(device->Frequency) * ReleaseTime};
/* Calculate per-sample multipliers to attack and release at the desired
* rates.
*/
mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
mAttackMult = std::pow(AmpEnvelopeMax/AmpEnvelopeMin, 1.0f/attackCount);
mReleaseMult = std::pow(AmpEnvelopeMin/AmpEnvelopeMax, 1.0f/releaseCount);
}
void CompressorState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
mEnabled = props->Compressor.OnOff;
mEnabled = std::get<CompressorProps>(*props).OnOff;
mOutTarget = target.Main->Buffer;
auto set_channel = [this](size_t idx, uint outchan, float outgain)
@ -117,72 +116,62 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot,
void CompressorState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
/* Generate the per-sample gains from the signal envelope. */
float env{mEnvFollower};
if(mEnabled)
{
float gains[256];
const size_t td{minz(256, samplesToDo-base)};
/* Generate the per-sample gains from the signal envelope. */
float env{mEnvFollower};
if(mEnabled)
for(size_t i{0u};i < samplesToDo;++i)
{
for(size_t i{0u};i < td;++i)
{
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
AMP_ENVELOPE_MAX)};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
else if(amplitude < env)
env = maxf(env*mReleaseMult, amplitude);
/* Apply the reciprocal of the envelope to normalize the volume
* (compress the dynamic range).
*/
gains[i] = 1.0f / env;
}
}
else
{
/* Same as above, except the amplitude is forced to 1. This helps
* ensure smooth gain changes when the compressor is turned on and
* off.
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
for(size_t i{0u};i < td;++i)
{
const float amplitude{1.0f};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
else if(amplitude < env)
env = maxf(env*mReleaseMult, amplitude);
const float amplitude{std::clamp(std::fabs(samplesIn[0][i]), AmpEnvelopeMin,
AmpEnvelopeMax)};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
gains[i] = 1.0f / env;
}
/* Apply the reciprocal of the envelope to normalize the volume
* (compress the dynamic range).
*/
mGains[i] = 1.0f / env;
}
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto chan = std::cbegin(mChans);
for(const auto &input : samplesIn)
}
else
{
/* Same as above, except the amplitude is forced to 1. This helps
* ensure smooth gain changes when the compressor is turned on and off.
*/
for(size_t i{0u};i < samplesToDo;++i)
{
const size_t outidx{chan->mTarget};
if(outidx != InvalidChannelIndex)
{
const float *RESTRICT src{input.data() + base};
float *RESTRICT dst{samplesOut[outidx].data() + base};
const float gain{chan->mGain};
if(!(std::fabs(gain) > GainSilenceThreshold))
{
for(size_t i{0u};i < td;i++)
dst[i] += src[i] * gains[i] * gain;
}
}
++chan;
}
const float amplitude{1.0f};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
base += td;
mGains[i] = 1.0f / env;
}
}
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto chan = mChans.cbegin();
for(const auto &input : samplesIn)
{
const size_t outidx{chan->mTarget};
if(outidx != InvalidChannelIndex)
{
const auto dst = al::span{samplesOut[outidx]};
const float gain{chan->mGain};
if(!(std::fabs(gain) > GainSilenceThreshold))
{
for(size_t i{0u};i < samplesToDo;++i)
dst[i] += input[i] * mGains[i] * gain;
}
}
++chan;
}
}

View file

@ -3,13 +3,15 @@
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <stdint.h>
#include <utility>
#include <vector>
#include <variant>
#ifdef HAVE_SSE_INTRINSICS
#include <xmmintrin.h>
@ -17,7 +19,6 @@
#include <arm_neon.h>
#endif
#include "albyte.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
@ -30,56 +31,85 @@
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/splitter.h"
#include "core/fmt_traits.h"
#include "core/mixer.h"
#include "core/uhjfilter.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "pffft.h"
#include "polyphase_resampler.h"
#include "vecmat.h"
#include "vector.h"
namespace {
/* Convolution reverb is implemented using a segmented overlap-add method. The
* impulse response is broken up into multiple segments of 128 samples, and
* each segment has an FFT applied with a 256-sample buffer (the latter half
* left silent) to get its frequency-domain response. The resulting response
* has its positive/non-mirrored frequencies saved (129 bins) in each segment.
/* Convolution is implemented using a segmented overlap-add method. The impulse
* response is split into multiple segments of 128 samples, and each segment
* has an FFT applied with a 256-sample buffer (the latter half left silent) to
* get its frequency-domain response. The resulting response has its positive/
* non-mirrored frequencies saved (129 bins) in each segment. Note that since
* the 0- and half-frequency bins are real for a real signal, their imaginary
* components are always 0 and can be dropped, allowing their real components
* to be combined so only 128 complex values are stored for the 129 bins.
*
* Input samples are similarly broken up into 128-sample segments, with an FFT
* applied to each new incoming segment to get its 129 bins. A history of FFT'd
* input segments is maintained, equal to the length of the impulse response.
* Input samples are similarly broken up into 128-sample segments, with a 256-
* sample FFT applied to each new incoming segment to get its 129 bins. A
* history of FFT'd input segments is maintained, equal to the number of
* impulse response segments.
*
* To apply the reverberation, each impulse response segment is convolved with
* To apply the convolution, each impulse response segment is convolved with
* its paired input segment (using complex multiplies, far cheaper than FIRs),
* accumulating into a 256-bin FFT buffer. The input history is then shifted to
* align with later impulse response segments for next time.
* accumulating into a 129-bin FFT buffer. The input history is then shifted to
* align with later impulse response segments for the next input segment.
*
* An inverse FFT is then applied to the accumulated FFT buffer to get a 256-
* sample time-domain response for output, which is split in two halves. The
* first half is the 128-sample output, and the second half is a 128-sample
* (really, 127) delayed extension, which gets added to the output next time.
* Convolving two time-domain responses of lengths N and M results in a time-
* domain signal of length N+M-1, and this holds true regardless of the
* convolution being applied in the frequency domain, so these "overflow"
* samples need to be accounted for.
* Convolving two time-domain responses of length N results in a time-domain
* signal of length N*2 - 1, and this holds true regardless of the convolution
* being applied in the frequency domain, so these "overflow" samples need to
* be accounted for.
*
* To avoid a delay with gathering enough input samples to apply an FFT with,
* the first segment is applied directly in the time-domain as the samples come
* in. Once enough have been retrieved, the FFT is applied on the input and
* it's paired with the remaining (FFT'd) filter segments for processing.
* To avoid a delay with gathering enough input samples for the FFT, the first
* segment is applied directly in the time-domain as the samples come in. Once
* enough have been retrieved, the FFT is applied on the input and it's paired
* with the remaining (FFT'd) filter segments for processing.
*/
void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
const size_t samples) noexcept
template<FmtType SrcType>
inline void LoadSampleArray(const al::span<float> dst, const std::byte *src,
const std::size_t channel, const std::size_t srcstep) noexcept
{
#define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
using TypeTraits = al::FmtTypeTraits<SrcType>;
using SampleType = typename TypeTraits::Type;
const auto converter = TypeTraits{};
assert(channel < srcstep);
const auto srcspan = al::span{reinterpret_cast<const SampleType*>(src), dst.size()*srcstep};
auto ssrc = srcspan.cbegin();
std::generate(dst.begin(), dst.end(), [converter,channel,srcstep,&ssrc]
{
const auto ret = converter(ssrc[channel]);
ssrc += ptrdiff_t(srcstep);
return ret;
});
}
void LoadSamples(const al::span<float> dst, const std::byte *src, const size_t channel,
const size_t srcstep, const FmtType srctype) noexcept
{
#define HANDLE_FMT(T) case T: LoadSampleArray<T>(dst, src, channel, srcstep); break
switch(srctype)
{
HANDLE_FMT(FmtUByte);
HANDLE_FMT(FmtShort);
HANDLE_FMT(FmtInt);
HANDLE_FMT(FmtFloat);
HANDLE_FMT(FmtDouble);
HANDLE_FMT(FmtMulaw);
@ -87,47 +117,50 @@ void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep,
/* FIXME: Handle ADPCM decoding here. */
case FmtIMA4:
case FmtMSADPCM:
std::fill_n(dst, samples, 0.0f);
std::fill(dst.begin(), dst.end(), 0.0f);
break;
}
#undef HANDLE_FMT
}
inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept
{
switch(scaletype)
{
case AmbiScaling::FuMa: return AmbiScale::FromFuMa();
case AmbiScaling::SN3D: return AmbiScale::FromSN3D();
case AmbiScaling::UHJ: return AmbiScale::FromUHJ();
case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa};
case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D};
case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ};
case AmbiScaling::N3D: break;
}
return AmbiScale::FromN3D();
return al::span{AmbiScale::FromN3D};
}
inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept
{
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
return AmbiIndex::FromACN();
if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa};
return al::span{AmbiIndex::FromACN};
}
inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept
{
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
return AmbiIndex::FromACN2D();
if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D};
return al::span{AmbiIndex::FromACN2D};
}
struct ChanMap {
constexpr float sin30{0.5f};
constexpr float cos30{0.866025403785f};
constexpr float sin45{al::numbers::sqrt2_v<float>*0.5f};
constexpr float cos45{al::numbers::sqrt2_v<float>*0.5f};
constexpr float sin110{ 0.939692620786f};
constexpr float cos110{-0.342020143326f};
struct ChanPosMap {
Channel channel;
float angle;
float elevation;
std::array<float,3> pos;
};
constexpr float Deg2Rad(float x) noexcept
{ return static_cast<float>(al::numbers::pi / 180.0 * x); }
using complex_f = std::complex<float>;
@ -135,10 +168,11 @@ constexpr size_t ConvolveUpdateSize{256};
constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *RESTRICT filter)
void apply_fir(al::span<float> dst, const al::span<const float> input, const al::span<const float,ConvolveUpdateSamples> filter)
{
auto src = input.begin();
#ifdef HAVE_SSE_INTRINSICS
for(float &output : dst)
std::generate(dst.begin(), dst.end(), [&src,filter]
{
__m128 r4{_mm_setzero_ps()};
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
@ -148,39 +182,40 @@ void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *REST
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
}
++src;
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
output = _mm_cvtss_f32(r4);
++src;
}
return _mm_cvtss_f32(r4);
});
#elif defined(HAVE_NEON)
for(float &output : dst)
std::generate(dst.begin(), dst.end(), [&src,filter]
{
float32x4_t r4{vdupq_n_f32(0.0f)};
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j]));
r4 = vaddq_f32(r4, vrev64q_f32(r4));
output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
++src;
}
r4 = vaddq_f32(r4, vrev64q_f32(r4));
return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
});
#else
for(float &output : dst)
std::generate(dst.begin(), dst.end(), [&src,filter]
{
float ret{0.0f};
for(size_t j{0};j < ConvolveUpdateSamples;++j)
ret += src[j] * filter[j];
output = ret;
++src;
}
return ret;
});
#endif
}
struct ConvolutionState final : public EffectState {
FmtChannels mChannels{};
AmbiLayout mAmbiLayout{};
@ -188,11 +223,13 @@ struct ConvolutionState final : public EffectState {
uint mAmbiOrder{};
size_t mFifoPos{0};
std::array<float,ConvolveUpdateSamples*2> mInput{};
alignas(16) std::array<float,ConvolveUpdateSamples*2> mInput{};
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
alignas(16) std::array<complex_f,ConvolveUpdateSize> mFftBuffer{};
PFFFTSetup mFft{};
alignas(16) std::array<float,ConvolveUpdateSize> mFftBuffer{};
alignas(16) std::array<float,ConvolveUpdateSize> mFftWorkBuffer{};
size_t mCurrentSegment{0};
size_t mNumConvolveSegs{0};
@ -201,12 +238,11 @@ struct ConvolutionState final : public EffectState {
alignas(16) FloatBufferLine mBuffer{};
float mHfScale{}, mLfScale{};
BandSplitter mFilter{};
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
std::array<float,MaxOutputChannels> Current{};
std::array<float,MaxOutputChannels> Target{};
};
using ChannelDataArray = al::FlexArray<ChannelData>;
std::unique_ptr<ChannelDataArray> mChans;
std::unique_ptr<complex_f[]> mComplexData;
std::vector<ChannelData> mChans;
al::vector<float,16> mComplexData;
ConvolutionState() = default;
@ -222,24 +258,22 @@ struct ConvolutionState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ConvolutionState)
};
void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut,
const size_t samplesToDo)
{
for(auto &chan : *mChans)
MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target,
samplesToDo, 0);
for(auto &chan : mChans)
MixSamples(al::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current,
chan.Target, samplesToDo, 0);
}
void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
const size_t samplesToDo)
{
for(auto &chan : *mChans)
for(auto &chan : mChans)
{
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
const auto src = al::span{chan.mBuffer}.first(samplesToDo);
chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale);
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
}
@ -251,19 +285,23 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
using UhjDecoderType = UhjDecoder<512>;
static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding;
constexpr uint MaxConvolveAmbiOrder{1u};
static constexpr uint MaxConvolveAmbiOrder{1u};
if(!mFft)
mFft = PFFFTSetup{ConvolveUpdateSize, PFFFT_REAL};
mFifoPos = 0;
mInput.fill(0.0f);
decltype(mFilter){}.swap(mFilter);
decltype(mOutput){}.swap(mOutput);
mFftBuffer.fill(complex_f{});
mFftBuffer.fill(0.0f);
mFftWorkBuffer.fill(0.0f);
mCurrentSegment = 0;
mNumConvolveSegs = 0;
mChans = nullptr;
mComplexData = nullptr;
decltype(mChans){}.swap(mChans);
decltype(mComplexData){}.swap(mComplexData);
/* An empty buffer doesn't need a convolution filter. */
if(!buffer || buffer->mSampleLen < 1) return;
@ -271,14 +309,12 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
mChannels = buffer->mChannels;
mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder);
mAmbiOrder = std::min(buffer->mAmbiOrder, MaxConvolveAmbiOrder);
constexpr size_t m{ConvolveUpdateSize/2 + 1};
const auto bytesPerSample = BytesFromFmt(buffer->mType);
const auto realChannels = buffer->channelsFromFmt();
const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder);
mChans = ChannelDataArray::Create(numChannels);
mChans.resize(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
@ -293,7 +329,7 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
buffer->mSampleRate);
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
for(auto &e : *mChans)
for(auto &e : mChans)
e.mFilter = splitter;
mFilter.resize(numChannels, {});
@ -305,126 +341,150 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
* segment is allocated to simplify handling.
*/
mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples;
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
mNumConvolveSegs = std::max(mNumConvolveSegs, 2_uz) - 1_uz;
const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)};
mComplexData = std::make_unique<complex_f[]>(complex_length);
std::fill_n(mComplexData.get(), complex_length, complex_f{});
const size_t complex_length{mNumConvolveSegs * ConvolveUpdateSize * (numChannels+1)};
mComplexData.resize(complex_length, 0.0f);
/* 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);
auto srcsamples = std::vector<float>(srclinelength * numChannels);
std::fill(srcsamples.begin(), srcsamples.end(), 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);
LoadSamples(al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen),
buffer->mData.data(), c, realChannels, buffer->mType);
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;
samples[c] = al::to_address(srcsamples.begin() + ptrdiff_t(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;
auto ressamples = std::vector<double>(buffer->mSampleLen + (resampler ? resampledCount : 0));
auto ffttmp = al::vector<float,16>(ConvolveUpdateSize);
auto fftbuffer = std::vector<std::complex<double>>(ConvolveUpdateSize);
auto filteriter = mComplexData.begin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize);
for(size_t c{0};c < numChannels;++c)
{
auto bufsamples = al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen);
/* 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());
auto restmp = al::span{ressamples}.subspan(resampledCount, buffer->mSampleLen);
std::copy(bufsamples.cbegin(), bufsamples.cend(), restmp.begin());
resampler.process(restmp, al::span{ressamples}.first(resampledCount));
}
else
std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, ressamples.get());
std::copy(bufsamples.cbegin(), bufsamples.cend(), ressamples.begin());
/* 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(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(),
const size_t first_size{std::min(size_t{resampledCount}, ConvolveUpdateSamples)};
auto sampleseg = al::span{ressamples.cbegin(), first_size};
std::transform(sampleseg.cbegin(), sampleseg.cend(), 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)};
const size_t todo{std::min(resampledCount-done, ConvolveUpdateSamples)};
sampleseg = al::span{ressamples}.subspan(done, todo);
auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin());
/* Apply a double-precision forward FFT for more precise frequency
* measurements.
*/
auto iter = std::copy(sampleseg.cbegin(), sampleseg.cend(), fftbuffer.begin());
done += todo;
std::fill(iter, fftbuffer.end(), std::complex<double>{});
forward_fft(al::span{fftbuffer});
forward_fft(al::as_span(fftbuffer));
filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter);
/* Convert to, and pack in, a float buffer for PFFFT. Note that the
* first bin stores the real component of the half-frequency bin in
* the imaginary component. Also scale the FFT by its length so the
* iFFT'd output will be normalized.
*/
static constexpr float fftscale{1.0f / float{ConvolveUpdateSize}};
for(size_t i{0};i < ConvolveUpdateSamples;++i)
{
ffttmp[i*2 ] = static_cast<float>(fftbuffer[i].real()) * fftscale;
ffttmp[i*2 + 1] = static_cast<float>((i == 0) ?
fftbuffer[ConvolveUpdateSamples].real() : fftbuffer[i].imag()) * fftscale;
}
/* Reorder backward to make it suitable for pffft_zconvolve and the
* subsequent pffft_transform(..., PFFFT_BACKWARD).
*/
mFft.zreorder(ffttmp.data(), al::to_address(filteriter), PFFFT_BACKWARD);
filteriter += ConvolveUpdateSize;
}
}
}
void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps* /*props*/, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
/* NOTE: Stereo and Rear are slightly different from normal mixing (as
* defined in alu.cpp). These are 45 degrees from center, rather than the
* 30 degrees used there.
*
* TODO: LFE is not mixed to output. This will require each buffer channel
/* TODO: LFE is not mixed to output. This will require each buffer channel
* to have its own output target since the main mixing buffer won't have an
* LFE channel (due to being B-Format).
*/
static constexpr ChanMap MonoMap[1]{
{ FrontCenter, 0.0f, 0.0f }
}, StereoMap[2]{
{ FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }
}, RearMap[2]{
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
}, QuadMap[4]{
{ FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
}, X51Map[6]{
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
}, X61Map[7]{
{ FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
{ SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
}, X71Map[8]{
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
{ SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
static constexpr std::array MonoMap{
ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}}
};
static constexpr std::array StereoMap{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
};
static constexpr std::array RearMap{
ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}},
ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}},
};
static constexpr std::array QuadMap{
ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}},
ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}},
ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}},
ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}},
};
static constexpr std::array X51Map{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}},
ChanPosMap{LFE, {}},
ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}},
ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}},
};
static constexpr std::array X61Map{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}},
ChanPosMap{LFE, {}},
ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f} },
ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f} },
ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f} },
};
static constexpr std::array X71Map{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}},
ChanPosMap{LFE, {}},
ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}},
ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}},
ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}},
ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}},
};
if(mNumConvolveSegs < 1) UNLIKELY
return;
auto &props = std::get<ConvolutionProps>(*props_);
mMix = &ConvolutionState::NormalMix;
for(auto &chan : *mChans)
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
for(auto &chan : mChans)
std::fill(chan.Target.begin(), chan.Target.end(), 0.0f);
const float gain{slot->Gain};
if(IsAmbisonic(mChannels))
{
@ -432,49 +492,68 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
if(mChannels == FmtUHJ2 && !device->mUhjEncoder)
{
mMix = &ConvolutionState::UpsampleMix;
(*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;
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[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;
mChans[i].mHfScale = scales[1];
mChans[i].mLfScale = 1.0f;
}
}
mOutTarget = target.Main->Buffer;
auto&& scales = GetAmbiScales(mAmbiScaling);
const uint8_t *index_map{Is2DAmbisonic(mChannels) ?
GetAmbi2DLayout(mAmbiLayout).data() :
GetAmbiLayout(mAmbiLayout).data()};
alu::Vector N{props.OrientAt[0], props.OrientAt[1], props.OrientAt[2], 0.0f};
N.normalize();
alu::Vector V{props.OrientUp[0], props.OrientUp[1], props.OrientUp[2], 0.0f};
V.normalize();
/* Build and normalize right-vector */
alu::Vector U{N.cross_product(V)};
U.normalize();
const std::array mixmatrix{
std::array{1.0f, 0.0f, 0.0f, 0.0f},
std::array{0.0f, U[0], -U[1], U[2]},
std::array{0.0f, -V[0], V[1], -V[2]},
std::array{0.0f, -N[0], N[1], -N[2]},
};
const auto scales = GetAmbiScales(mAmbiScaling);
const auto index_map = Is2DAmbisonic(mChannels) ?
al::span{GetAmbi2DLayout(mAmbiLayout)}.subspan(0) :
al::span{GetAmbiLayout(mAmbiLayout)}.subspan(0);
std::array<float,MaxAmbiChannels> coeffs{};
for(size_t c{0u};c < mChans->size();++c)
for(size_t c{0u};c < mChans.size();++c)
{
const size_t acn{index_map[c]};
coeffs[acn] = scales[acn];
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target);
coeffs[acn] = 0.0f;
const float scale{scales[acn]};
std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(),
[scale](const float in) noexcept -> float { return in * scale; });
ComputePanGains(target.Main, coeffs, gain, mChans[c].Target);
}
}
else
{
DeviceBase *device{context->mDevice};
al::span<const ChanMap> chanmap{};
al::span<const ChanPosMap> chanmap{};
switch(mChannels)
{
case FmtMono: chanmap = MonoMap; break;
case FmtMonoDup: chanmap = MonoMap; break;
case FmtSuperStereo:
case FmtStereo: chanmap = StereoMap; break;
case FmtRear: chanmap = RearMap; break;
@ -493,28 +572,55 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
mOutTarget = target.Main->Buffer;
if(device->mRenderMode == RenderMode::Pairwise)
{
auto ScaleAzimuthFront = [](float azimuth, float scale) -> float
/* Scales the azimuth of the given vector by 3 if it's in front.
* Effectively scales +/-30 degrees to +/-90 degrees, leaving > +90
* and < -90 alone.
*/
auto ScaleAzimuthFront = [](std::array<float,3> pos) -> std::array<float,3>
{
constexpr float half_pi{al::numbers::pi_v<float>*0.5f};
const float abs_azi{std::fabs(azimuth)};
if(!(abs_azi >= half_pi))
return std::copysign(minf(abs_azi*scale, half_pi), azimuth);
return azimuth;
if(pos[2] < 0.0f)
{
/* Normalize the length of the x,z components for a 2D
* vector of the azimuth angle. Negate Z since {0,0,-1} is
* angle 0.
*/
const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])};
float x{pos[0] / len2d};
float z{-pos[2] / len2d};
/* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */
if(z > cos30)
{
/* Triple the angle represented by x,z. */
x = x*3.0f - x*x*x*4.0f;
z = z*z*z*4.0f - z*3.0f;
/* Scale the vector back to fit in 3D. */
pos[0] = x * len2d;
pos[2] = -z * len2d;
}
else
{
/* If azimuth >= 30 degrees, clamp to 90 degrees. */
pos[0] = std::copysign(len2d, pos[0]);
pos[2] = 0.0f;
}
}
return pos;
};
for(size_t i{0};i < chanmap.size();++i)
{
if(chanmap[i].channel == LFE) continue;
const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f),
chanmap[i].elevation, 0.0f);
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
const auto coeffs = CalcDirectionCoeffs(ScaleAzimuthFront(chanmap[i].pos), 0.0f);
ComputePanGains(target.Main, coeffs, gain, mChans[i].Target);
}
}
else for(size_t i{0};i < chanmap.size();++i)
{
if(chanmap[i].channel == LFE) continue;
const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f);
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
const auto coeffs = CalcDirectionCoeffs(chanmap[i].pos, 0.0f);
ComputePanGains(target.Main, coeffs, gain, mChans[i].Target);
}
}
}
@ -525,27 +631,26 @@ void ConvolutionState::process(const size_t samplesToDo,
if(mNumConvolveSegs < 1) UNLIKELY
return;
constexpr size_t m{ConvolveUpdateSize/2 + 1};
size_t curseg{mCurrentSegment};
auto &chans = *mChans;
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)};
const size_t todo{std::min(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)};
std::copy_n(samplesIn[0].begin() + base, todo,
mInput.begin()+ConvolveUpdateSamples+mFifoPos);
std::copy_n(samplesIn[0].begin() + ptrdiff_t(base), todo,
mInput.begin()+ptrdiff_t(ConvolveUpdateSamples+mFifoPos));
/* Apply the FIR for the newly retrieved input samples, and combine it
* with the inverse FFT'd output samples.
*/
for(size_t c{0};c < chans.size();++c)
for(size_t c{0};c < mChans.size();++c)
{
auto buf_iter = chans[c].mBuffer.begin() + base;
apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data());
auto outspan = al::span{mChans[c].mBuffer}.subspan(base, todo);
apply_fir(outspan, al::span{mInput}.subspan(1+mFifoPos), mFilter[c]);
auto fifo_iter = mOutput[c].begin() + mFifoPos;
std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
auto fifospan = al::span{mOutput[c]}.subspan(mFifoPos, todo);
std::transform(fifospan.cbegin(), fifospan.cend(), outspan.cbegin(), outspan.begin(),
std::plus{});
}
mFifoPos += todo;
@ -557,59 +662,51 @@ void ConvolutionState::process(const size_t samplesToDo,
/* Move the newest input to the front for the next iteration's history. */
std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin());
std::fill(mInput.begin()+ConvolveUpdateSamples, mInput.end(), 0.0f);
/* Calculate the frequency domain response and add the relevant
/* Calculate the frequency-domain response and add the relevant
* frequency bins to the FFT history.
*/
auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin());
std::fill(fftiter, mFftBuffer.end(), complex_f{});
forward_fft(al::as_span(mFftBuffer));
mFft.transform(mInput.data(), &mComplexData[curseg*ConvolveUpdateSize],
mFftWorkBuffer.data(), PFFFT_FORWARD);
std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]);
const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
for(size_t c{0};c < chans.size();++c)
auto filter = mComplexData.cbegin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize);
for(size_t c{0};c < mChans.size();++c)
{
std::fill_n(mFftBuffer.begin(), m, complex_f{});
/* Convolve each input segment with its IR filter counterpart
* (aligned in time).
*/
const complex_f *RESTRICT input{&mComplexData[curseg*m]};
mFftBuffer.fill(0.0f);
auto input = mComplexData.cbegin() + ptrdiff_t(curseg*ConvolveUpdateSize);
for(size_t s{curseg};s < mNumConvolveSegs;++s)
{
for(size_t i{0};i < m;++i,++input,++filter)
mFftBuffer[i] += *input * *filter;
mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
mFftBuffer.data());
input += ConvolveUpdateSize;
filter += ConvolveUpdateSize;
}
input = mComplexData.get();
input = mComplexData.cbegin();
for(size_t s{0};s < curseg;++s)
{
for(size_t i{0};i < m;++i,++input,++filter)
mFftBuffer[i] += *input * *filter;
mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
mFftBuffer.data());
input += ConvolveUpdateSize;
filter += ConvolveUpdateSize;
}
/* Reconstruct the mirrored/negative frequencies to do a proper
* inverse FFT.
*/
for(size_t i{m};i < ConvolveUpdateSize;++i)
mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]);
/* Apply iFFT to get the 256 (really 255) samples for output. The
* 128 output samples are combined with the last output's 127
* second-half samples (and this output's second half is
* subsequently saved for next time).
*/
inverse_fft(al::as_span(mFftBuffer));
mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(),
PFFFT_BACKWARD);
/* 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] =
(mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) *
(1.0f/float{ConvolveUpdateSize});
for(size_t i{0};i < ConvolveUpdateSamples;++i)
mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real();
/* The filter was attenuated, so the response is already scaled. */
std::transform(mFftBuffer.cbegin(), mFftBuffer.cbegin()+ConvolveUpdateSamples,
mOutput[c].cbegin()+ConvolveUpdateSamples, mOutput[c].begin(), std::plus{});
std::copy(mFftBuffer.cbegin()+ConvolveUpdateSamples, mFftBuffer.cend(),
mOutput[c].begin()+ConvolveUpdateSamples);
}
/* Shift the input history. */

View file

@ -23,18 +23,19 @@
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
struct ContextBase;
@ -47,45 +48,36 @@ struct DedicatedState final : public EffectState {
* gains for all possible output channels and not just the main ambisonic
* buffer.
*/
float mCurrentGains[MAX_OUTPUT_CHANNELS];
float mTargetGains[MAX_OUTPUT_CHANNELS];
std::array<float,MaxOutputChannels> mCurrentGains{};
std::array<float,MaxOutputChannels> mTargetGains{};
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 deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_,
const EffectTarget target) final;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DedicatedState)
const al::span<FloatBufferLine> samplesOut) final;
};
void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(mCurrentGains.begin(), mCurrentGains.end(), 0.0f);
}
void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f);
const float Gain{slot->Gain * props->Dedicated.Gain};
auto &props = std::get<DedicatedProps>(*props_);
const float Gain{slot->Gain * props.Gain};
if(slot->EffectType == EffectSlotType::DedicatedLFE)
{
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
}
else if(slot->EffectType == EffectSlotType::DedicatedDialog)
if(props.Target == DedicatedProps::Dialog)
{
/* Dialog goes to the front-center speaker if it exists, otherwise it
* plays from the front-center location. */
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
* plays from the front-center location.
*/
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
: InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
@ -94,17 +86,26 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
}
else
{
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);
ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
}
}
else if(props.Target == DedicatedProps::Lfe)
{
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
}
}
void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
MixSamples(al::span{samplesIn[0]}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains,
samplesToDo, 0);
}

View file

@ -22,30 +22,32 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
struct DistortionState final : public EffectState {
/* Effect gains for each channel */
float mGain[MaxAmbiChannels]{};
std::array<float,MaxAmbiChannels> mGain{};
/* Effect parameters */
BiquadFilter mLowpass;
@ -53,7 +55,7 @@ struct DistortionState final : public EffectState {
float mAttenuation{};
float mEdgeCoeff{};
alignas(16) float mBuffer[2][BufferLineSize]{};
alignas(16) std::array<FloatBufferLine,2> mBuffer{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -61,8 +63,6 @@ struct DistortionState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DistortionState)
};
void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -72,16 +72,16 @@ void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*)
}
void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<DistortionProps>(*props_);
const DeviceBase *device{context->mDevice};
/* Store waveshaper edge settings. */
const float edge{minf(std::sin(al::numbers::pi_v<float>*0.5f * props->Distortion.Edge),
0.99f)};
const float edge{std::min(std::sin(al::numbers::pi_v<float>*0.5f * props.Edge), 0.99f)};
mEdgeCoeff = 2.0f * edge / (1.0f-edge);
float cutoff{props->Distortion.LowpassCutoff};
float cutoff{props.LowpassCutoff};
/* Bandwidth value is constant in octaves. */
float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
/* Divide normalized frequency by the amount of oversampling done during
@ -90,15 +90,15 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
auto frequency = static_cast<float>(device->Frequency);
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
cutoff = props->Distortion.EQCenter;
cutoff = props.EQCenter;
/* Convert bandwidth in Hz to octaves. */
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
bandwidth = props.EQBandwidth / (cutoff * 0.67f);
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
ComputePanGains(target.Main, coeffs, slot->Gain*props.Gain, mGain);
}
void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
@ -111,7 +111,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
* bandpass filters using high frequencies, at which classic IIR
* filters became unstable.
*/
size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)};
size_t todo{std::min(BufferLineSize, (samplesToDo-base) * 4_uz)};
/* Fill oversample buffer using zero stuffing. Multiply the sample by
* the amount of oversampling to maintain the signal's power.
@ -124,7 +124,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
* (which is fortunately first step of distortion). So combine three
* operations into the one.
*/
mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
mLowpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]);
/* Second step, do distortion using waveshaper function to emulate
* signal processing during tube overdriving. Three steps of
@ -133,31 +133,39 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
*/
auto proc_sample = [fc](float smp) -> float
{
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)) * -1.0f;
smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp));
return smp;
};
std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
std::transform(mBuffer[1].begin(), mBuffer[1].begin()+todo, mBuffer[0].begin(),
proc_sample);
/* Third step, do bandpass filtering of distorted signal. */
mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
mBandpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]);
todo >>= 2;
const float *outgains{mGain};
for(FloatBufferLine &output : samplesOut)
auto outgains = mGain.cbegin();
auto proc_bufline = [this,base,todo,&outgains](FloatBufferSpan output)
{
/* Fourth step, final, do attenuation and perform decimation,
* storing only one sample out of four.
*/
const float gain{*(outgains++)};
if(!(std::fabs(gain) > GainSilenceThreshold))
continue;
return;
for(size_t i{0u};i < todo;i++)
output[base+i] += gain * mBuffer[1][i*4];
}
auto src = mBuffer[1].cbegin();
const auto dst = al::span{output}.subspan(base, todo);
auto dec_sample = [gain,&src](float sample) noexcept -> float
{
sample += *src * gain;
src += 4;
return sample;
};
std::transform(dst.begin(), dst.end(), dst.begin(), dec_sample);
};
std::for_each(samplesOut.begin(), samplesOut.end(), proc_bufline);
base += todo;
}

View file

@ -22,25 +22,26 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <tuple>
#include <variant>
#include <vector>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
struct BufferStorage;
namespace {
@ -49,33 +50,30 @@ using uint = unsigned int;
constexpr float LowpassFreqRef{5000.0f};
struct EchoState final : public EffectState {
al::vector<float,16> mSampleBuffer;
std::vector<float> mSampleBuffer;
// The echo is two tap. The delay is the number of samples from before the
// current offset
struct {
size_t delay{0u};
} mTap[2];
std::array<size_t,2> mDelayTap{};
size_t mOffset{0u};
/* The panning gains for the two taps */
struct {
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
struct OutGains {
std::array<float,MaxAmbiChannels> Current{};
std::array<float,MaxAmbiChannels> Target{};
};
std::array<OutGains,2> mGains;
BiquadFilter mFilter;
float mFeedGain{0.0f};
alignas(16) float mTempBuffer[2][BufferLineSize];
alignas(16) std::array<FloatBufferLine,2> mTempBuffer{};
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,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EchoState)
};
void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
@ -87,61 +85,62 @@ void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) +
float2uint(EchoMaxLRDelay*frequency + 0.5f))};
if(maxlen != mSampleBuffer.size())
al::vector<float,16>(maxlen).swap(mSampleBuffer);
decltype(mSampleBuffer)(maxlen).swap(mSampleBuffer);
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
for(auto &e : mGains)
{
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
std::fill(e.Current.begin(), e.Current.end(), 0.0f);
std::fill(e.Target.begin(), e.Target.end(), 0.0f);
}
}
void EchoState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<EchoProps>(*props_);
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u);
mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0];
const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
const float gainhf{std::max(1.0f - props.Damping, 0.0625f)}; /* Limit -24dB */
mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f);
mFeedGain = props->Echo.Feedback;
mFeedGain = props.Feedback;
/* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
const float angle{std::asin(props->Echo.Spread)};
/* Convert echo spread (where 0 = center, +/-1 = sides) to a 2D vector. */
const float x{props.Spread}; /* +x = left */
const float z{std::sqrt(1.0f - x*x)};
const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f);
const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f);
const auto coeffs0 = CalcAmbiCoeffs( x, 0.0f, z, 0.0f);
const auto coeffs1 = CalcAmbiCoeffs(-x, 0.0f, z, 0.0f);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target);
ComputePanGains(target.Main, coeffs0, slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs1, slot->Gain, mGains[1].Target);
}
void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const size_t mask{mSampleBuffer.size()-1};
float *RESTRICT delaybuf{mSampleBuffer.data()};
const auto delaybuf = al::span{mSampleBuffer};
const size_t mask{delaybuf.size()-1};
size_t offset{mOffset};
size_t tap1{offset - mTap[0].delay};
size_t tap2{offset - mTap[1].delay};
float z1, z2;
size_t tap1{offset - mDelayTap[0]};
size_t tap2{offset - mDelayTap[1]};
ASSUME(samplesToDo > 0);
const BiquadFilter filter{mFilter};
std::tie(z1, z2) = mFilter.getComponents();
auto [z1, z2] = mFilter.getComponents();
for(size_t i{0u};i < samplesToDo;)
{
offset &= mask;
tap1 &= mask;
tap2 &= mask;
size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
size_t td{std::min(mask+1 - std::max(offset, std::max(tap1, tap2)), samplesToDo-i)};
do {
/* Feed the delay buffer's input first. */
delaybuf[offset] = samplesIn[0][i];
@ -161,8 +160,8 @@ void EchoState::process(const size_t samplesToDo, const al::span<const FloatBuff
mOffset = offset;
for(size_t c{0};c < 2;c++)
MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo, 0);
MixSamples(al::span{mTempBuffer[c]}.first(samplesToDo), samplesOut, mGains[c].Current,
mGains[c].Target, samplesToDo, 0);
}

View file

@ -22,24 +22,24 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <utility>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
@ -86,16 +86,17 @@ namespace {
struct EqualizerState final : public EffectState {
struct {
struct OutParams {
uint mTargetChannel{InvalidChannelIndex};
/* Effect parameters */
BiquadFilter mFilter[4];
std::array<BiquadFilter,4> mFilter;
/* Effect gains for each channel */
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
};
std::array<OutParams,MaxAmbiChannels> mChans;
alignas(16) FloatBufferLine mSampleBuffer{};
@ -105,8 +106,6 @@ struct EqualizerState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EqualizerState)
};
void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -114,18 +113,17 @@ void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*)
for(auto &e : mChans)
{
e.mTargetChannel = InvalidChannelIndex;
std::for_each(std::begin(e.mFilter), std::end(e.mFilter),
std::mem_fn(&BiquadFilter::clear));
std::for_each(e.mFilter.begin(), e.mFilter.end(), std::mem_fn(&BiquadFilter::clear));
e.mCurrentGain = 0.0f;
}
}
void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<EqualizerProps>(*props_);
const DeviceBase *device{context->mDevice};
auto frequency = static_cast<float>(device->Frequency);
float gain, f0norm;
/* Calculate coefficients for the each type of filter. Note that the shelf
* and peaking filters' gain is for the centerpoint of the transition band,
@ -133,22 +131,22 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
* property gains need their dB halved (sqrt of linear gain) for the
* shelf/peak to reach the provided gain.
*/
gain = std::sqrt(props->Equalizer.LowGain);
f0norm = props->Equalizer.LowCutoff / frequency;
float gain{std::sqrt(props.LowGain)};
float f0norm{props.LowCutoff / frequency};
mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
gain = std::sqrt(props->Equalizer.Mid1Gain);
f0norm = props->Equalizer.Mid1Center / frequency;
gain = std::sqrt(props.Mid1Gain);
f0norm = props.Mid1Center / frequency;
mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid1Width);
props.Mid1Width);
gain = std::sqrt(props->Equalizer.Mid2Gain);
f0norm = props->Equalizer.Mid2Center / frequency;
gain = std::sqrt(props.Mid2Gain);
f0norm = props.Mid2Center / frequency;
mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid2Width);
props.Mid2Width);
gain = std::sqrt(props->Equalizer.HighGain);
f0norm = props->Equalizer.HighCutoff / frequency;
gain = std::sqrt(props.HighGain);
f0norm = props.HighCutoff / frequency;
mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
/* Copy the filter coefficients for the other input channels. */
@ -171,18 +169,17 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
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::begin(mChans);
const auto buffer = al::span{mSampleBuffer}.first(samplesToDo);
auto chan = mChans.begin();
for(const auto &input : samplesIn)
{
const size_t outidx{chan->mTargetChannel};
if(outidx != InvalidChannelIndex)
if(const size_t outidx{chan->mTargetChannel}; 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());
const auto inbuf = al::span{input}.first(samplesToDo);
DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer);
DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer);
MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain,
MixSamples(buffer, samplesOut[outidx], chan->mCurrentGain, chan->mTargetGain,
samplesToDo);
}
++chan;

View file

@ -25,23 +25,25 @@
#include <cmath>
#include <complex>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
struct BufferStorage;
namespace {
@ -57,7 +59,7 @@ constexpr size_t HilStep{HilSize / OversampleFactor};
/* Define a Hann window, used to filter the HIL input and output. */
struct Windower {
alignas(16) std::array<double,HilSize> mData;
alignas(16) std::array<double,HilSize> mData{};
Windower()
{
@ -91,10 +93,11 @@ struct FshifterState final : public EffectState {
alignas(16) FloatBufferLine mBufferOut{};
/* Effect gains for each output channel */
struct {
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
struct OutGains {
std::array<float,MaxAmbiChannels> Current{};
std::array<float,MaxAmbiChannels> Target{};
};
std::array<OutGains,2> mGains;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -102,8 +105,6 @@ struct FshifterState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(FshifterState)
};
void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -122,20 +123,21 @@ void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
for(auto &gain : mGains)
{
std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f);
std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f);
gain.Current.fill(0.0f);
gain.Target.fill(0.0f);
}
}
void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<FshifterProps>(*props_);
const DeviceBase *device{context->mDevice};
const float step{props->Fshifter.Frequency / static_cast<float>(device->Frequency)};
mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne);
const float step{props.Frequency / static_cast<float>(device->Frequency)};
mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne);
switch(props->Fshifter.LeftDirection)
switch(props.LeftDirection)
{
case FShifterDirection::Down:
mSign[0] = -1.0;
@ -149,7 +151,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
break;
}
switch(props->Fshifter.RightDirection)
switch(props.RightDirection)
{
case FShifterDirection::Down:
mSign[1] = -1.0;
@ -164,23 +166,23 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
}
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});
static constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f});
static constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f});
static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2});
static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ 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);
ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target);
ComputePanGains(target.Main, lcoeffs, slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs, slot->Gain, mGains[1].Target);
}
void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
{
size_t todo{minz(HilStep-mCount, samplesToDo-base)};
size_t todo{std::min(HilStep-mCount, samplesToDo-base)};
/* Fill FIFO buffer with samples data */
const size_t pos{mPos};
@ -218,25 +220,27 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
}
/* Process frequency shifter using the analytic signal obtained. */
float *RESTRICT BufferOut{al::assume_aligned<16>(mBufferOut.data())};
for(size_t c{0};c < 2;++c)
{
const double sign{mSign[c]};
const uint phase_step{mPhaseStep[c]};
uint phase_idx{mPhase[c]};
for(size_t k{0};k < samplesToDo;++k)
{
const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)};
BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
mOutdata[k].imag()*std::sin(phase)*mSign[c]);
std::transform(mOutdata.cbegin(), mOutdata.cbegin()+samplesToDo, mBufferOut.begin(),
[&phase_idx,phase_step,sign](const complex_d &in) -> float
{
const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)};
const auto out = static_cast<float>(in.real()*std::cos(phase) +
in.imag()*std::sin(phase)*sign);
phase_idx += phase_step;
phase_idx &= MixerFracMask;
}
phase_idx += phase_step;
phase_idx &= MixerFracMask;
return out;
});
mPhase[c] = phase_idx;
/* Now, mix the processed sound data to the output. */
MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
maxz(samplesToDo, 512), 0);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mGains[c].Current,
mGains[c].Target, std::max(samplesToDo, 512_uz), 0);
}
}

View file

@ -22,75 +22,73 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iterator>
#include <functional>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
struct BufferStorage;
namespace {
using uint = unsigned int;
#define MAX_UPDATE_SAMPLES 128
struct SinFunc {
static auto Get(uint index, float scale) noexcept(noexcept(std::sin(0.0f))) -> float
{ return std::sin(static_cast<float>(index) * scale); }
};
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
struct SawFunc {
static constexpr auto Get(uint index, float scale) noexcept -> float
{ return static_cast<float>(index)*scale - 1.0f; }
};
inline float Sin(uint index)
{
constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
return std::sin(static_cast<float>(index) * scale);
}
struct SquareFunc {
static constexpr auto Get(uint index, float scale) noexcept -> float
{ return float(static_cast<float>(index)*scale < 0.5f)*2.0f - 1.0f; }
};
inline float Saw(uint index)
{ return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; }
inline float Square(uint index)
{ return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); }
inline float One(uint) { return 1.0f; }
template<float (&func)(uint)>
void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo)
{
for(size_t i{0u};i < todo;i++)
{
index += step;
index &= WAVEFORM_FRACMASK;
dst[i] = func(index);
}
}
struct OneFunc {
static constexpr auto Get(uint, float) noexcept -> float
{ return 1.0f; }
};
struct ModulatorState final : public EffectState {
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
std::variant<OneFunc,SinFunc,SawFunc,SquareFunc> mSampleGen;
uint mIndex{0};
uint mStep{1};
uint mRange{1};
float mIndexScale{0.0f};
struct {
alignas(16) FloatBufferLine mModSamples{};
alignas(16) FloatBufferLine mBuffer{};
struct OutParams {
uint mTargetChannel{InvalidChannelIndex};
BiquadFilter mFilter;
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
};
std::array<OutParams,MaxAmbiChannels> mChans;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -98,8 +96,6 @@ struct ModulatorState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ModulatorState)
};
void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -113,24 +109,54 @@ void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*)
}
void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<ModulatorProps>(*props_);
const DeviceBase *device{context->mDevice};
const float step{props->Modulator.Frequency / static_cast<float>(device->Frequency)};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
/* The effective frequency will be adjusted to have a whole number of
* samples per cycle (at 48khz, that allows 8000, 6857.14, 6000, 5333.33,
* 4800, etc). We could do better by using fixed-point stepping over a sin
* function, with additive synthesis for the square and sawtooth waveforms,
* but that may need a more efficient sin function since it needs to do
* many iterations per sample.
*/
const float samplesPerCycle{props.Frequency > 0.0f
? static_cast<float>(device->Frequency)/props.Frequency + 0.5f
: 1.0f};
const uint range{static_cast<uint>(std::clamp(samplesPerCycle, 1.0f,
static_cast<float>(device->Frequency)))};
mIndex = static_cast<uint>(uint64_t{mIndex} * range / mRange);
mRange = range;
if(mStep == 0)
mGetSamples = Modulate<One>;
else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid)
mGetSamples = Modulate<Sin>;
else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth)
mGetSamples = Modulate<Saw>;
else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/
mGetSamples = Modulate<Square>;
if(mRange == 1)
{
mIndexScale = 0.0f;
mSampleGen.emplace<OneFunc>();
}
else if(props.Waveform == ModulatorWaveform::Sinusoid)
{
mIndexScale = al::numbers::pi_v<float>*2.0f / static_cast<float>(mRange);
mSampleGen.emplace<SinFunc>();
}
else if(props.Waveform == ModulatorWaveform::Sawtooth)
{
mIndexScale = 2.0f / static_cast<float>(mRange-1);
mSampleGen.emplace<SawFunc>();
}
else if(props.Waveform == ModulatorWaveform::Square)
{
/* For square wave, the range should be even (there should be an equal
* number of high and low samples). An odd number of samples per cycle
* would need a more complex value generator.
*/
mRange = (mRange+1) & ~1u;
mIndexScale = 1.0f / static_cast<float>(mRange-1);
mSampleGen.emplace<SquareFunc>();
}
float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)};
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
float f0norm{props.HighPassCutoff / static_cast<float>(device->Frequency)};
f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f);
/* Bandwidth value is constant in octaves. */
mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
@ -147,34 +173,41 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
ASSUME(samplesToDo > 0);
std::visit([this,samplesToDo](auto&& type)
{
alignas(16) float modsamples[MAX_UPDATE_SAMPLES];
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
const uint range{mRange};
const float scale{mIndexScale};
uint index{mIndex};
mGetSamples(modsamples, mIndex, mStep, td);
mIndex += static_cast<uint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
ASSUME(range > 1);
auto chandata = std::begin(mChans);
for(const auto &input : samplesIn)
for(size_t i{0};i < samplesToDo;)
{
const size_t outidx{chandata->mTargetChannel};
if(outidx != InvalidChannelIndex)
{
alignas(16) float temps[MAX_UPDATE_SAMPLES];
chandata->mFilter.process({&input[base], td}, temps);
for(size_t i{0u};i < td;i++)
temps[i] *= modsamples[i];
MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
}
++chandata;
size_t rem{std::min(samplesToDo-i, size_t{range-index})};
do {
mModSamples[i++] = type.Get(index++, scale);
} while(--rem);
if(index == range)
index = 0;
}
mIndex = index;
}, mSampleGen);
base += td;
auto chandata = mChans.begin();
for(const auto &input : samplesIn)
{
if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex)
{
chandata->mFilter.process(al::span{input}.first(samplesToDo), mBuffer);
std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(),
mBuffer.begin(), std::multiplies<>{});
MixSamples(al::span{mBuffer}.first(samplesToDo), samplesOut[outidx],
chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz));
}
++chandata;
}
}

View file

@ -1,14 +1,15 @@
#include "config.h"
#include <stddef.h>
#include <cstddef>
#include "almalloc.h"
#include "alspan.h"
#include "base.h"
#include "core/bufferline.h"
#include "core/effects/base.h"
#include "intrusive_ptr.h"
struct BufferStorage;
struct ContextBase;
struct DeviceBase;
struct EffectSlot;
@ -25,8 +26,6 @@ struct NullState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(NullState)
};
/* This constructs the effect state. It's called when the object is first

View file

@ -25,22 +25,23 @@
#include <cmath>
#include <complex>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
#include "pffft.h"
struct BufferStorage;
struct ContextBase;
@ -58,7 +59,7 @@ constexpr size_t StftStep{StftSize / OversampleFactor};
/* Define a Hann window, used to filter the STFT input and output. */
struct Windower {
alignas(16) std::array<float,StftSize> mData;
alignas(16) std::array<float,StftSize> mData{};
Windower()
{
@ -82,27 +83,29 @@ struct FrequencyBin {
struct PshifterState final : public EffectState {
/* Effect parameters */
size_t mCount;
size_t mPos;
uint mPitchShiftI;
float mPitchShift;
size_t mCount{};
size_t mPos{};
uint mPitchShiftI{};
float mPitchShift{};
/* Effects buffers */
std::array<float,StftSize> mFIFO;
std::array<float,StftHalfSize+1> mLastPhase;
std::array<float,StftHalfSize+1> mSumPhase;
std::array<float,StftSize> 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_f,StftSize> mFftBuffer;
PFFFTSetup mFft;
alignas(16) std::array<float,StftSize> mFftBuffer{};
alignas(16) std::array<float,StftSize> mFftWorkBuffer{};
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer;
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer;
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer{};
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer{};
alignas(16) FloatBufferLine mBufferOut;
alignas(16) FloatBufferLine mBufferOut{};
/* Effect gains for each output channel */
float mCurrentGains[MaxAmbiChannels];
float mTargetGains[MaxAmbiChannels];
std::array<float,MaxAmbiChannels> mCurrentGains{};
std::array<float,MaxAmbiChannels> mTargetGains{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -110,8 +113,6 @@ struct PshifterState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(PshifterState)
};
void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -126,26 +127,31 @@ void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
mLastPhase.fill(0.0f);
mSumPhase.fill(0.0f);
mOutputAccum.fill(0.0f);
mFftBuffer.fill(complex_f{});
mFftBuffer.fill(0.0f);
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);
mCurrentGains.fill(0.0f);
mTargetGains.fill(0.0f);
if(!mFft)
mFft = PFFFTSetup{StftSize, PFFFT_REAL};
}
void PshifterState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune};
auto &props = std::get<PshifterProps>(*props_);
const int tune{props.CoarseTune*100 + props.FineTune};
const float pitch{std::pow(2.0f, static_cast<float>(tune) / 1200.0f)};
mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2);
mPitchShiftI = std::clamp(fastf2u(pitch*MixerFracOne), uint{MixerFracHalf},
uint{MixerFracOne}*2u);
mPitchShift = static_cast<float>(mPitchShiftI) * float{1.0f/MixerFracOne};
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains);
ComputePanGains(target.Main, coeffs, slot->Gain, mTargetGains);
}
void PshifterState::process(const size_t samplesToDo,
@ -162,7 +168,7 @@ void PshifterState::process(const size_t samplesToDo,
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(StftStep-mCount, samplesToDo-base)};
const size_t todo{std::min(StftStep-mCount, samplesToDo-base)};
/* Retrieve the output samples from the FIFO and fill in the new input
* samples.
@ -186,15 +192,19 @@ void PshifterState::process(const size_t samplesToDo,
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));
mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(),
PFFFT_FORWARD);
/* Analyze the obtained data. Since the real FFT is symmetric, only
* StftHalfSize+1 samples are needed.
*/
for(size_t k{0u};k < StftHalfSize+1;k++)
for(size_t k{0u};k < StftHalfSize+1;++k)
{
const float magnitude{std::abs(mFftBuffer[k])};
const float phase{std::arg(mFftBuffer[k])};
const auto cplx = (k == 0) ? complex_f{mFftBuffer[0]} :
(k == StftHalfSize) ? complex_f{mFftBuffer[1]} :
complex_f{mFftBuffer[k*2], mFftBuffer[k*2 + 1]};
const float magnitude{std::abs(cplx)};
const float phase{std::arg(cplx)};
/* Compute the phase difference from the last update and subtract
* the expected phase difference for this bin.
@ -232,8 +242,8 @@ void PshifterState::process(const size_t samplesToDo,
*/
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
constexpr size_t bin_limit{((StftHalfSize+1)<<MixerFracBits) - MixerFracHalf - 1};
const size_t bin_count{minz(StftHalfSize+1, bin_limit/mPitchShiftI + 1)};
static constexpr size_t bin_limit{((StftHalfSize+1)<<MixerFracBits) - MixerFracHalf - 1};
const size_t bin_count{std::min(StftHalfSize+1, bin_limit/mPitchShiftI + 1)};
for(size_t k{0u};k < bin_count;k++)
{
const size_t j{(k*mPitchShiftI + MixerFracHalf) >> MixerFracBits};
@ -266,21 +276,29 @@ void PshifterState::process(const size_t samplesToDo,
tmp -= static_cast<float>(qpd + (qpd%2));
mSumPhase[k] = tmp * al::numbers::pi_v<float>;
mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]);
const complex_f cplx{std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k])};
if(k == 0)
mFftBuffer[0] = cplx.real();
else if(k == StftHalfSize)
mFftBuffer[1] = cplx.real();
else
{
mFftBuffer[k*2 + 0] = cplx.real();
mFftBuffer[k*2 + 1] = cplx.imag();
}
}
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 signal, and accumulate
* for the output with windowing.
*/
inverse_fft(al::as_span(mFftBuffer));
mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(),
PFFFT_BACKWARD);
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;
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale;
for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale;
/* Copy out the accumulated result, then clear for the next iteration. */
std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos);
@ -288,8 +306,8 @@ void PshifterState::process(const size_t samplesToDo,
}
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
maxz(samplesToDo, 512), 0);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains,
std::max(samplesToDo, 512_uz), 0);
}

File diff suppressed because it is too large Load diff

View file

@ -34,68 +34,69 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
using uint = unsigned int;
#define MAX_UPDATE_SAMPLES 256
#define NUM_FORMANTS 4
#define NUM_FILTERS 2
#define Q_FACTOR 5.0f
constexpr size_t MaxUpdateSamples{256};
constexpr size_t NumFormants{4};
constexpr float RcpQFactor{1.0f / 5.0f};
enum : size_t {
VowelAIndex,
VowelBIndex,
NumFilters
};
#define VOWEL_A_INDEX 0
#define VOWEL_B_INDEX 1
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
constexpr size_t WaveformFracBits{24};
constexpr size_t WaveformFracOne{1<<WaveformFracBits};
constexpr size_t WaveformFracMask{WaveformFracOne-1};
inline float Sin(uint index)
{
constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
constexpr float scale{al::numbers::pi_v<float>*2.0f / float{WaveformFracOne}};
return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
}
inline float Saw(uint index)
{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; }
{ return static_cast<float>(index) / float{WaveformFracOne}; }
inline float Triangle(uint index)
{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); }
{ return std::fabs(static_cast<float>(index)*(2.0f/WaveformFracOne) - 1.0f); }
inline float Half(uint) { return 0.5f; }
template<float (&func)(uint)>
void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo)
void Oscillate(const al::span<float> dst, uint index, const uint step)
{
for(size_t i{0u};i < todo;i++)
std::generate(dst.begin(), dst.end(), [&index,step]
{
index += step;
index &= WAVEFORM_FRACMASK;
dst[i] = func(index);
}
index &= WaveformFracMask;
return func(index);
});
}
struct FormantFilter
{
struct FormantFilter {
float mCoeff{0.0f};
float mGain{1.0f};
float mS1{0.0f};
@ -106,34 +107,38 @@ struct FormantFilter
: mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain}
{ }
inline void process(const float *samplesIn, float *samplesOut, const size_t numInput)
void process(const float *samplesIn, float *samplesOut, const size_t numInput) noexcept
{
/* A state variable filter from a topology-preserving transform.
* Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
*/
const float g{mCoeff};
const float gain{mGain};
const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))};
const float h{1.0f / (1.0f + (g*RcpQFactor) + (g*g))};
const float coeff{RcpQFactor + g};
float s1{mS1};
float s2{mS2};
for(size_t i{0u};i < numInput;i++)
{
const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h};
const float B{g*H + s1};
const float L{g*B + s2};
const auto input = al::span{samplesIn, numInput};
const auto output = al::span{samplesOut, numInput};
std::transform(input.cbegin(), input.cend(), output.cbegin(), output.begin(),
[g,gain,h,coeff,&s1,&s2](const float in, const float out) noexcept -> float
{
const float H{(in - coeff*s1 - s2)*h};
const float B{g*H + s1};
const float L{g*B + s2};
s1 = g*H + B;
s2 = g*B + L;
s1 = g*H + B;
s2 = g*B + L;
// Apply peak and accumulate samples.
samplesOut[i] += B * gain;
}
// Apply peak and accumulate samples.
return out + B*gain;
});
mS1 = s1;
mS2 = s2;
}
inline void clear()
void clear() noexcept
{
mS1 = 0.0f;
mS2 = 0.0f;
@ -142,26 +147,27 @@ struct FormantFilter
struct VmorpherState final : public EffectState {
struct {
struct OutParams {
uint mTargetChannel{InvalidChannelIndex};
/* Effect parameters */
FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS];
std::array<std::array<FormantFilter,NumFormants>,NumFilters> mFormants;
/* Effect gains for each channel */
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
};
std::array<OutParams,MaxAmbiChannels> mChans;
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
void (*mGetSamples)(const al::span<float> dst, uint index, const uint step){};
uint mIndex{0};
uint mStep{1};
/* Effects buffers */
alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{};
alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{};
alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{};
alignas(16) std::array<float,MaxUpdateSamples> mSampleBufferA{};
alignas(16) std::array<float,MaxUpdateSamples> mSampleBufferB{};
alignas(16) std::array<float,MaxUpdateSamples> mLfo{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
@ -169,14 +175,12 @@ struct VmorpherState final : public EffectState {
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
static std::array<FormantFilter,4> getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch);
DEF_NEWDEL(VmorpherState)
static std::array<FormantFilter,NumFormants> getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch) noexcept;
};
std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch)
std::array<FormantFilter,NumFormants> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch) noexcept
{
/* Using soprano formant set of values to
* better match mid-range frequency space.
@ -232,44 +236,43 @@ void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*)
for(auto &e : mChans)
{
e.mTargetChannel = InvalidChannelIndex;
std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]),
std::for_each(e.mFormants[VowelAIndex].begin(), e.mFormants[VowelAIndex].end(),
std::mem_fn(&FormantFilter::clear));
std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]),
std::for_each(e.mFormants[VowelBIndex].begin(), e.mFormants[VowelBIndex].end(),
std::mem_fn(&FormantFilter::clear));
e.mCurrentGain = 0.0f;
}
}
void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<VmorpherProps>(*props_);
const DeviceBase *device{context->mDevice};
const float frequency{static_cast<float>(device->Frequency)};
const float step{props->Vmorpher.Rate / frequency};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
const float step{props.Rate / frequency};
mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f));
if(mStep == 0)
mGetSamples = Oscillate<Half>;
else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid)
else if(props.Waveform == VMorpherWaveform::Sinusoid)
mGetSamples = Oscillate<Sin>;
else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle)
else if(props.Waveform == VMorpherWaveform::Triangle)
mGetSamples = Oscillate<Triangle>;
else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/
else /*if(props.Waveform == VMorpherWaveform::Sawtooth)*/
mGetSamples = Oscillate<Saw>;
const float pitchA{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)};
const float pitchB{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)};
const float pitchA{std::pow(2.0f, static_cast<float>(props.PhonemeACoarseTuning) / 12.0f)};
const float pitchB{std::pow(2.0f, static_cast<float>(props.PhonemeBCoarseTuning) / 12.0f)};
auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB);
auto vowelA = getFiltersByPhoneme(props.PhonemeA, frequency, pitchA);
auto vowelB = getFiltersByPhoneme(props.PhonemeB, frequency, pitchB);
/* 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].mFormants[VOWEL_A_INDEX]));
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX]));
std::copy(vowelA.begin(), vowelA.end(), mChans[i].mFormants[VowelAIndex].begin());
std::copy(vowelB.begin(), vowelB.end(), mChans[i].mFormants[VowelBIndex].begin());
}
mOutTarget = target.Main->Buffer;
@ -283,18 +286,20 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
alignas(16) std::array<float,MaxUpdateSamples> blended{};
/* Following the EFX specification for a conformant implementation which describes
* the effect as a pair of 4-band formant filters blended together using an LFO.
*/
for(size_t base{0u};base < samplesToDo;)
{
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
const size_t td{std::min(MaxUpdateSamples, samplesToDo-base)};
mGetSamples(mLfo, mIndex, mStep, td);
mGetSamples(al::span{mLfo}.first(td), mIndex, mStep);
mIndex += static_cast<uint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
mIndex &= WaveformFracMask;
auto chandata = std::begin(mChans);
auto chandata = mChans.begin();
for(const auto &input : samplesIn)
{
const size_t outidx{chandata->mTargetChannel};
@ -304,30 +309,29 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
continue;
}
auto& vowelA = chandata->mFormants[VOWEL_A_INDEX];
auto& vowelB = chandata->mFormants[VOWEL_B_INDEX];
const auto vowelA = al::span{chandata->mFormants[VowelAIndex]};
const auto vowelB = al::span{chandata->mFormants[VowelBIndex]};
/* Process first vowel. */
std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
vowelA[0].process(&input[base], mSampleBufferA, td);
vowelA[1].process(&input[base], mSampleBufferA, td);
vowelA[2].process(&input[base], mSampleBufferA, td);
vowelA[3].process(&input[base], mSampleBufferA, td);
std::fill_n(mSampleBufferA.begin(), td, 0.0f);
vowelA[0].process(&input[base], mSampleBufferA.data(), td);
vowelA[1].process(&input[base], mSampleBufferA.data(), td);
vowelA[2].process(&input[base], mSampleBufferA.data(), td);
vowelA[3].process(&input[base], mSampleBufferA.data(), td);
/* Process second vowel. */
std::fill_n(std::begin(mSampleBufferB), td, 0.0f);
vowelB[0].process(&input[base], mSampleBufferB, td);
vowelB[1].process(&input[base], mSampleBufferB, td);
vowelB[2].process(&input[base], mSampleBufferB, td);
vowelB[3].process(&input[base], mSampleBufferB, td);
std::fill_n(mSampleBufferB.begin(), td, 0.0f);
vowelB[0].process(&input[base], mSampleBufferB.data(), td);
vowelB[1].process(&input[base], mSampleBufferB.data(), td);
vowelB[2].process(&input[base], mSampleBufferB.data(), td);
vowelB[3].process(&input[base], mSampleBufferB.data(), td);
alignas(16) float blended[MAX_UPDATE_SAMPLES];
for(size_t i{0u};i < td;i++)
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
/* Now, mix the processed sound data to the output. */
MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base),
chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base);
++chandata;
}

View file

@ -0,0 +1,95 @@
#include "config.h"
#include "events.h"
#include "alspan.h"
#include "core/logging.h"
#include "device.h"
namespace {
ALCenum EnumFromEventType(const alc::EventType type)
{
switch(type)
{
case alc::EventType::DefaultDeviceChanged: return ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT;
case alc::EventType::DeviceAdded: return ALC_EVENT_TYPE_DEVICE_ADDED_SOFT;
case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT;
case alc::EventType::Count: break;
}
throw std::runtime_error{"Invalid EventType: "+std::to_string(al::to_underlying(type))};
}
} // namespace
namespace alc {
std::optional<alc::EventType> GetEventType(ALCenum type)
{
switch(type)
{
case ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: return alc::EventType::DefaultDeviceChanged;
case ALC_EVENT_TYPE_DEVICE_ADDED_SOFT: return alc::EventType::DeviceAdded;
case ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT: return alc::EventType::DeviceRemoved;
}
return std::nullopt;
}
void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept
{
auto eventlock = std::unique_lock{EventMutex};
if(EventCallback && EventsEnabled.test(al::to_underlying(eventType)))
EventCallback(EnumFromEventType(eventType), al::to_underlying(deviceType), device,
static_cast<ALCsizei>(message.length()), message.data(), EventUserPtr);
}
} // namespace alc
FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events,
ALCboolean enable) noexcept
{
if(enable != ALC_FALSE && enable != ALC_TRUE)
{
alcSetError(nullptr, ALC_INVALID_ENUM);
return ALC_FALSE;
}
if(count < 0)
{
alcSetError(nullptr, ALC_INVALID_VALUE);
return ALC_FALSE;
}
if(count == 0)
return ALC_TRUE;
if(!events)
{
alcSetError(nullptr, ALC_INVALID_VALUE);
return ALC_FALSE;
}
alc::EventBitSet eventSet{0};
for(ALCenum type : al::span{events, static_cast<ALCuint>(count)})
{
auto etype = alc::GetEventType(type);
if(!etype)
{
WARN("Invalid event type: 0x%04x\n", type);
alcSetError(nullptr, ALC_INVALID_ENUM);
return ALC_FALSE;
}
eventSet.set(al::to_underlying(*etype));
}
auto eventlock = std::unique_lock{alc::EventMutex};
if(enable) alc::EventsEnabled |= eventSet;
else alc::EventsEnabled &= ~eventSet;
return ALC_TRUE;
}
FORCE_ALIGN void ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) noexcept
{
auto eventlock = std::unique_lock{alc::EventMutex};
alc::EventCallback = callback;
alc::EventUserPtr = userParam;
}

View file

@ -0,0 +1,50 @@
#ifndef ALC_EVENTS_H
#define ALC_EVENTS_H
#include "inprogext.h"
#include "opthelpers.h"
#include <bitset>
#include <mutex>
#include <optional>
#include <string_view>
namespace alc {
enum class EventType : uint8_t {
DefaultDeviceChanged,
DeviceAdded,
DeviceRemoved,
Count
};
std::optional<alc::EventType> GetEventType(ALCenum type);
enum class EventSupport : ALCenum {
FullSupport = ALC_EVENT_SUPPORTED_SOFT,
NoSupport = ALC_EVENT_NOT_SUPPORTED_SOFT,
};
enum class DeviceType : ALCenum {
Playback = ALC_PLAYBACK_DEVICE_SOFT,
Capture = ALC_CAPTURE_DEVICE_SOFT,
};
using EventBitSet = std::bitset<al::to_underlying(EventType::Count)>;
inline EventBitSet EventsEnabled{0};
inline std::mutex EventMutex;
inline ALCEVENTPROCTYPESOFT EventCallback{};
inline void *EventUserPtr{};
void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept;
inline void Event(EventType eventType, DeviceType deviceType, std::string_view message) noexcept
{ Event(eventType, deviceType, nullptr, message); }
} // namespace alc
#endif /* ALC_EVENTS_H */

View file

@ -0,0 +1,917 @@
#ifndef ALC_EXPORT_LIST_H
#define ALC_EXPORT_LIST_H
#include "AL/alc.h"
#include "AL/al.h"
#include "AL/alext.h"
#include "inprogext.h"
#ifdef ALSOFT_EAX
#include "context.h"
#include "al/eax/x_ram.h"
#endif
struct FuncExport {
const char *funcName;
void *address;
};
#define DECL(x) FuncExport{#x, reinterpret_cast<void*>(x)}
/* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */
inline const FuncExport alcFunctions[]{
DECL(alcCreateContext),
DECL(alcMakeContextCurrent),
DECL(alcProcessContext),
DECL(alcSuspendContext),
DECL(alcDestroyContext),
DECL(alcGetCurrentContext),
DECL(alcGetContextsDevice),
DECL(alcOpenDevice),
DECL(alcCloseDevice),
DECL(alcGetError),
DECL(alcIsExtensionPresent),
DECL(alcGetProcAddress),
DECL(alcGetEnumValue),
DECL(alcGetString),
DECL(alcGetIntegerv),
DECL(alcCaptureOpenDevice),
DECL(alcCaptureCloseDevice),
DECL(alcCaptureStart),
DECL(alcCaptureStop),
DECL(alcCaptureSamples),
DECL(alcSetThreadContext),
DECL(alcGetThreadContext),
DECL(alcLoopbackOpenDeviceSOFT),
DECL(alcIsRenderFormatSupportedSOFT),
DECL(alcRenderSamplesSOFT),
DECL(alcDevicePauseSOFT),
DECL(alcDeviceResumeSOFT),
DECL(alcGetStringiSOFT),
DECL(alcResetDeviceSOFT),
DECL(alcGetInteger64vSOFT),
DECL(alcReopenDeviceSOFT),
DECL(alcEventIsSupportedSOFT),
DECL(alcEventControlSOFT),
DECL(alcEventCallbackSOFT),
DECL(alEnable),
DECL(alDisable),
DECL(alIsEnabled),
DECL(alGetString),
DECL(alGetBooleanv),
DECL(alGetIntegerv),
DECL(alGetFloatv),
DECL(alGetDoublev),
DECL(alGetBoolean),
DECL(alGetInteger),
DECL(alGetFloat),
DECL(alGetDouble),
DECL(alGetError),
DECL(alIsExtensionPresent),
DECL(alGetProcAddress),
DECL(alGetEnumValue),
DECL(alListenerf),
DECL(alListener3f),
DECL(alListenerfv),
DECL(alListeneri),
DECL(alListener3i),
DECL(alListeneriv),
DECL(alGetListenerf),
DECL(alGetListener3f),
DECL(alGetListenerfv),
DECL(alGetListeneri),
DECL(alGetListener3i),
DECL(alGetListeneriv),
DECL(alGenSources),
DECL(alDeleteSources),
DECL(alIsSource),
DECL(alSourcef),
DECL(alSource3f),
DECL(alSourcefv),
DECL(alSourcei),
DECL(alSource3i),
DECL(alSourceiv),
DECL(alGetSourcef),
DECL(alGetSource3f),
DECL(alGetSourcefv),
DECL(alGetSourcei),
DECL(alGetSource3i),
DECL(alGetSourceiv),
DECL(alSourcePlayv),
DECL(alSourceStopv),
DECL(alSourceRewindv),
DECL(alSourcePausev),
DECL(alSourcePlay),
DECL(alSourceStop),
DECL(alSourceRewind),
DECL(alSourcePause),
DECL(alSourceQueueBuffers),
DECL(alSourceUnqueueBuffers),
DECL(alGenBuffers),
DECL(alDeleteBuffers),
DECL(alIsBuffer),
DECL(alBufferData),
DECL(alBufferf),
DECL(alBuffer3f),
DECL(alBufferfv),
DECL(alBufferi),
DECL(alBuffer3i),
DECL(alBufferiv),
DECL(alGetBufferf),
DECL(alGetBuffer3f),
DECL(alGetBufferfv),
DECL(alGetBufferi),
DECL(alGetBuffer3i),
DECL(alGetBufferiv),
DECL(alDopplerFactor),
DECL(alDopplerVelocity),
DECL(alSpeedOfSound),
DECL(alDistanceModel),
DECL(alGenFilters),
DECL(alDeleteFilters),
DECL(alIsFilter),
DECL(alFilteri),
DECL(alFilteriv),
DECL(alFilterf),
DECL(alFilterfv),
DECL(alGetFilteri),
DECL(alGetFilteriv),
DECL(alGetFilterf),
DECL(alGetFilterfv),
DECL(alGenEffects),
DECL(alDeleteEffects),
DECL(alIsEffect),
DECL(alEffecti),
DECL(alEffectiv),
DECL(alEffectf),
DECL(alEffectfv),
DECL(alGetEffecti),
DECL(alGetEffectiv),
DECL(alGetEffectf),
DECL(alGetEffectfv),
DECL(alGenAuxiliaryEffectSlots),
DECL(alDeleteAuxiliaryEffectSlots),
DECL(alIsAuxiliaryEffectSlot),
DECL(alAuxiliaryEffectSloti),
DECL(alAuxiliaryEffectSlotiv),
DECL(alAuxiliaryEffectSlotf),
DECL(alAuxiliaryEffectSlotfv),
DECL(alGetAuxiliaryEffectSloti),
DECL(alGetAuxiliaryEffectSlotiv),
DECL(alGetAuxiliaryEffectSlotf),
DECL(alGetAuxiliaryEffectSlotfv),
DECL(alDeferUpdatesSOFT),
DECL(alProcessUpdatesSOFT),
DECL(alSourcedSOFT),
DECL(alSource3dSOFT),
DECL(alSourcedvSOFT),
DECL(alGetSourcedSOFT),
DECL(alGetSource3dSOFT),
DECL(alGetSourcedvSOFT),
DECL(alSourcei64SOFT),
DECL(alSource3i64SOFT),
DECL(alSourcei64vSOFT),
DECL(alGetSourcei64SOFT),
DECL(alGetSource3i64SOFT),
DECL(alGetSourcei64vSOFT),
DECL(alGetStringiSOFT),
DECL(alBufferStorageSOFT),
DECL(alMapBufferSOFT),
DECL(alUnmapBufferSOFT),
DECL(alFlushMappedBufferSOFT),
DECL(alEventControlSOFT),
DECL(alEventCallbackSOFT),
DECL(alGetPointerSOFT),
DECL(alGetPointervSOFT),
DECL(alBufferCallbackSOFT),
DECL(alGetBufferPtrSOFT),
DECL(alGetBuffer3PtrSOFT),
DECL(alGetBufferPtrvSOFT),
DECL(alSourcePlayAtTimeSOFT),
DECL(alSourcePlayAtTimevSOFT),
DECL(alBufferSubDataSOFT),
DECL(alBufferDataStatic),
DECL(alDebugMessageCallbackEXT),
DECL(alDebugMessageInsertEXT),
DECL(alDebugMessageControlEXT),
DECL(alPushDebugGroupEXT),
DECL(alPopDebugGroupEXT),
DECL(alGetDebugMessageLogEXT),
/* Direct Context functions */
DECL(alcGetProcAddress2),
DECL(alEnableDirect),
DECL(alDisableDirect),
DECL(alIsEnabledDirect),
DECL(alDopplerFactorDirect),
DECL(alSpeedOfSoundDirect),
DECL(alDistanceModelDirect),
DECL(alGetStringDirect),
DECL(alGetBooleanvDirect),
DECL(alGetIntegervDirect),
DECL(alGetFloatvDirect),
DECL(alGetDoublevDirect),
DECL(alGetBooleanDirect),
DECL(alGetIntegerDirect),
DECL(alGetFloatDirect),
DECL(alGetDoubleDirect),
DECL(alGetErrorDirect),
DECL(alIsExtensionPresentDirect),
DECL(alGetProcAddress),
DECL(alGetEnumValueDirect),
DECL(alListeneriDirect),
DECL(alListener3iDirect),
DECL(alListenerivDirect),
DECL(alListenerfDirect),
DECL(alListener3fDirect),
DECL(alListenerfvDirect),
DECL(alGetListeneriDirect),
DECL(alGetListener3iDirect),
DECL(alGetListenerivDirect),
DECL(alGetListenerfDirect),
DECL(alGetListener3fDirect),
DECL(alGetListenerfvDirect),
DECL(alGenBuffersDirect),
DECL(alDeleteBuffersDirect),
DECL(alIsBufferDirect),
DECL(alBufferDataDirect),
DECL(alBufferiDirect),
DECL(alBuffer3iDirect),
DECL(alBufferivDirect),
DECL(alBufferfDirect),
DECL(alBuffer3fDirect),
DECL(alBufferfvDirect),
DECL(alGetBufferiDirect),
DECL(alGetBuffer3iDirect),
DECL(alGetBufferivDirect),
DECL(alGetBufferfDirect),
DECL(alGetBuffer3fDirect),
DECL(alGetBufferfvDirect),
DECL(alGenSourcesDirect),
DECL(alDeleteSourcesDirect),
DECL(alIsSourceDirect),
DECL(alSourcePlayDirect),
DECL(alSourceStopDirect),
DECL(alSourcePauseDirect),
DECL(alSourceRewindDirect),
DECL(alSourcePlayvDirect),
DECL(alSourceStopvDirect),
DECL(alSourcePausevDirect),
DECL(alSourceRewindvDirect),
DECL(alSourceiDirect),
DECL(alSource3iDirect),
DECL(alSourceivDirect),
DECL(alSourcefDirect),
DECL(alSource3fDirect),
DECL(alSourcefvDirect),
DECL(alGetSourceiDirect),
DECL(alGetSource3iDirect),
DECL(alGetSourceivDirect),
DECL(alGetSourcefDirect),
DECL(alGetSource3fDirect),
DECL(alGetSourcefvDirect),
DECL(alSourceQueueBuffersDirect),
DECL(alSourceUnqueueBuffersDirect),
DECL(alGenFiltersDirect),
DECL(alDeleteFiltersDirect),
DECL(alIsFilterDirect),
DECL(alFilteriDirect),
DECL(alFilterivDirect),
DECL(alFilterfDirect),
DECL(alFilterfvDirect),
DECL(alGetFilteriDirect),
DECL(alGetFilterivDirect),
DECL(alGetFilterfDirect),
DECL(alGetFilterfvDirect),
DECL(alGenEffectsDirect),
DECL(alDeleteEffectsDirect),
DECL(alIsEffectDirect),
DECL(alEffectiDirect),
DECL(alEffectivDirect),
DECL(alEffectfDirect),
DECL(alEffectfvDirect),
DECL(alGetEffectiDirect),
DECL(alGetEffectivDirect),
DECL(alGetEffectfDirect),
DECL(alGetEffectfvDirect),
DECL(alGenAuxiliaryEffectSlotsDirect),
DECL(alDeleteAuxiliaryEffectSlotsDirect),
DECL(alIsAuxiliaryEffectSlotDirect),
DECL(alAuxiliaryEffectSlotiDirect),
DECL(alAuxiliaryEffectSlotivDirect),
DECL(alAuxiliaryEffectSlotfDirect),
DECL(alAuxiliaryEffectSlotfvDirect),
DECL(alGetAuxiliaryEffectSlotiDirect),
DECL(alGetAuxiliaryEffectSlotivDirect),
DECL(alGetAuxiliaryEffectSlotfDirect),
DECL(alGetAuxiliaryEffectSlotfvDirect),
DECL(alDeferUpdatesDirectSOFT),
DECL(alProcessUpdatesDirectSOFT),
DECL(alGetStringiDirectSOFT),
DECL(alBufferDataStaticDirect),
DECL(alBufferCallbackDirectSOFT),
DECL(alBufferSubDataDirectSOFT),
DECL(alBufferStorageDirectSOFT),
DECL(alMapBufferDirectSOFT),
DECL(alUnmapBufferDirectSOFT),
DECL(alFlushMappedBufferDirectSOFT),
DECL(alSourcei64DirectSOFT),
DECL(alSource3i64DirectSOFT),
DECL(alSourcei64vDirectSOFT),
DECL(alSourcedDirectSOFT),
DECL(alSource3dDirectSOFT),
DECL(alSourcedvDirectSOFT),
DECL(alGetSourcei64DirectSOFT),
DECL(alGetSource3i64DirectSOFT),
DECL(alGetSourcei64vDirectSOFT),
DECL(alGetSourcedDirectSOFT),
DECL(alGetSource3dDirectSOFT),
DECL(alGetSourcedvDirectSOFT),
DECL(alSourcePlayAtTimeDirectSOFT),
DECL(alSourcePlayAtTimevDirectSOFT),
DECL(alEventControlDirectSOFT),
DECL(alEventCallbackDirectSOFT),
DECL(alDebugMessageCallbackDirectEXT),
DECL(alDebugMessageInsertDirectEXT),
DECL(alDebugMessageControlDirectEXT),
DECL(alPushDebugGroupDirectEXT),
DECL(alPopDebugGroupDirectEXT),
DECL(alGetDebugMessageLogDirectEXT),
DECL(alObjectLabelEXT),
DECL(alObjectLabelDirectEXT),
DECL(alGetObjectLabelEXT),
DECL(alGetObjectLabelDirectEXT),
/* Extra functions */
DECL(alsoft_set_log_callback),
};
#ifdef ALSOFT_EAX
inline const std::array eaxFunctions{
DECL(EAXGet),
DECL(EAXSet),
DECL(EAXGetBufferMode),
DECL(EAXSetBufferMode),
DECL(EAXGetDirect),
DECL(EAXSetDirect),
DECL(EAXGetBufferModeDirect),
DECL(EAXSetBufferModeDirect),
};
#endif
#undef DECL
struct EnumExport {
const char *enumName;
int value;
};
#define DECL(x) EnumExport{#x, (x)}
/* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */
inline const EnumExport alcEnumerations[]{
DECL(ALC_INVALID),
DECL(ALC_FALSE),
DECL(ALC_TRUE),
DECL(ALC_MAJOR_VERSION),
DECL(ALC_MINOR_VERSION),
DECL(ALC_ATTRIBUTES_SIZE),
DECL(ALC_ALL_ATTRIBUTES),
DECL(ALC_DEFAULT_DEVICE_SPECIFIER),
DECL(ALC_DEVICE_SPECIFIER),
DECL(ALC_ALL_DEVICES_SPECIFIER),
DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER),
DECL(ALC_EXTENSIONS),
DECL(ALC_FREQUENCY),
DECL(ALC_REFRESH),
DECL(ALC_SYNC),
DECL(ALC_MONO_SOURCES),
DECL(ALC_STEREO_SOURCES),
DECL(ALC_CAPTURE_DEVICE_SPECIFIER),
DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER),
DECL(ALC_CAPTURE_SAMPLES),
DECL(ALC_CONNECTED),
DECL(ALC_EFX_MAJOR_VERSION),
DECL(ALC_EFX_MINOR_VERSION),
DECL(ALC_MAX_AUXILIARY_SENDS),
DECL(ALC_FORMAT_CHANNELS_SOFT),
DECL(ALC_FORMAT_TYPE_SOFT),
DECL(ALC_MONO_SOFT),
DECL(ALC_STEREO_SOFT),
DECL(ALC_QUAD_SOFT),
DECL(ALC_5POINT1_SOFT),
DECL(ALC_6POINT1_SOFT),
DECL(ALC_7POINT1_SOFT),
DECL(ALC_BFORMAT3D_SOFT),
DECL(ALC_BYTE_SOFT),
DECL(ALC_UNSIGNED_BYTE_SOFT),
DECL(ALC_SHORT_SOFT),
DECL(ALC_UNSIGNED_SHORT_SOFT),
DECL(ALC_INT_SOFT),
DECL(ALC_UNSIGNED_INT_SOFT),
DECL(ALC_FLOAT_SOFT),
DECL(ALC_HRTF_SOFT),
DECL(ALC_DONT_CARE_SOFT),
DECL(ALC_HRTF_STATUS_SOFT),
DECL(ALC_HRTF_DISABLED_SOFT),
DECL(ALC_HRTF_ENABLED_SOFT),
DECL(ALC_HRTF_DENIED_SOFT),
DECL(ALC_HRTF_REQUIRED_SOFT),
DECL(ALC_HRTF_HEADPHONES_DETECTED_SOFT),
DECL(ALC_HRTF_UNSUPPORTED_FORMAT_SOFT),
DECL(ALC_NUM_HRTF_SPECIFIERS_SOFT),
DECL(ALC_HRTF_SPECIFIER_SOFT),
DECL(ALC_HRTF_ID_SOFT),
DECL(ALC_AMBISONIC_LAYOUT_SOFT),
DECL(ALC_AMBISONIC_SCALING_SOFT),
DECL(ALC_AMBISONIC_ORDER_SOFT),
DECL(ALC_ACN_SOFT),
DECL(ALC_FUMA_SOFT),
DECL(ALC_N3D_SOFT),
DECL(ALC_SN3D_SOFT),
DECL(ALC_OUTPUT_LIMITER_SOFT),
DECL(ALC_DEVICE_CLOCK_SOFT),
DECL(ALC_DEVICE_LATENCY_SOFT),
DECL(ALC_DEVICE_CLOCK_LATENCY_SOFT),
DECL(AL_SAMPLE_OFFSET_CLOCK_SOFT),
DECL(AL_SEC_OFFSET_CLOCK_SOFT),
DECL(ALC_OUTPUT_MODE_SOFT),
DECL(ALC_ANY_SOFT),
DECL(ALC_STEREO_BASIC_SOFT),
DECL(ALC_STEREO_UHJ_SOFT),
DECL(ALC_STEREO_HRTF_SOFT),
DECL(ALC_SURROUND_5_1_SOFT),
DECL(ALC_SURROUND_6_1_SOFT),
DECL(ALC_SURROUND_7_1_SOFT),
DECL(ALC_NO_ERROR),
DECL(ALC_INVALID_DEVICE),
DECL(ALC_INVALID_CONTEXT),
DECL(ALC_INVALID_ENUM),
DECL(ALC_INVALID_VALUE),
DECL(ALC_OUT_OF_MEMORY),
DECL(ALC_CONTEXT_FLAGS_EXT),
DECL(ALC_CONTEXT_DEBUG_BIT_EXT),
DECL(ALC_PLAYBACK_DEVICE_SOFT),
DECL(ALC_CAPTURE_DEVICE_SOFT),
DECL(ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT),
DECL(ALC_EVENT_TYPE_DEVICE_ADDED_SOFT),
DECL(ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT),
DECL(AL_INVALID),
DECL(AL_NONE),
DECL(AL_FALSE),
DECL(AL_TRUE),
DECL(AL_SOURCE_RELATIVE),
DECL(AL_CONE_INNER_ANGLE),
DECL(AL_CONE_OUTER_ANGLE),
DECL(AL_PITCH),
DECL(AL_POSITION),
DECL(AL_DIRECTION),
DECL(AL_VELOCITY),
DECL(AL_LOOPING),
DECL(AL_BUFFER),
DECL(AL_GAIN),
DECL(AL_MIN_GAIN),
DECL(AL_MAX_GAIN),
DECL(AL_ORIENTATION),
DECL(AL_REFERENCE_DISTANCE),
DECL(AL_ROLLOFF_FACTOR),
DECL(AL_CONE_OUTER_GAIN),
DECL(AL_MAX_DISTANCE),
DECL(AL_SEC_OFFSET),
DECL(AL_SAMPLE_OFFSET),
DECL(AL_BYTE_OFFSET),
DECL(AL_SOURCE_TYPE),
DECL(AL_STATIC),
DECL(AL_STREAMING),
DECL(AL_UNDETERMINED),
DECL(AL_METERS_PER_UNIT),
DECL(AL_LOOP_POINTS_SOFT),
DECL(AL_DIRECT_CHANNELS_SOFT),
DECL(AL_DIRECT_FILTER),
DECL(AL_AUXILIARY_SEND_FILTER),
DECL(AL_AIR_ABSORPTION_FACTOR),
DECL(AL_ROOM_ROLLOFF_FACTOR),
DECL(AL_CONE_OUTER_GAINHF),
DECL(AL_DIRECT_FILTER_GAINHF_AUTO),
DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO),
DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO),
DECL(AL_SOURCE_STATE),
DECL(AL_INITIAL),
DECL(AL_PLAYING),
DECL(AL_PAUSED),
DECL(AL_STOPPED),
DECL(AL_BUFFERS_QUEUED),
DECL(AL_BUFFERS_PROCESSED),
DECL(AL_FORMAT_MONO8),
DECL(AL_FORMAT_MONO16),
DECL(AL_FORMAT_MONO_FLOAT32),
DECL(AL_FORMAT_MONO_DOUBLE_EXT),
DECL(AL_FORMAT_STEREO8),
DECL(AL_FORMAT_STEREO16),
DECL(AL_FORMAT_STEREO_FLOAT32),
DECL(AL_FORMAT_STEREO_DOUBLE_EXT),
DECL(AL_FORMAT_MONO_IMA4),
DECL(AL_FORMAT_STEREO_IMA4),
DECL(AL_FORMAT_MONO_MSADPCM_SOFT),
DECL(AL_FORMAT_STEREO_MSADPCM_SOFT),
DECL(AL_FORMAT_QUAD8_LOKI),
DECL(AL_FORMAT_QUAD16_LOKI),
DECL(AL_FORMAT_QUAD8),
DECL(AL_FORMAT_QUAD16),
DECL(AL_FORMAT_QUAD32),
DECL(AL_FORMAT_51CHN8),
DECL(AL_FORMAT_51CHN16),
DECL(AL_FORMAT_51CHN32),
DECL(AL_FORMAT_61CHN8),
DECL(AL_FORMAT_61CHN16),
DECL(AL_FORMAT_61CHN32),
DECL(AL_FORMAT_71CHN8),
DECL(AL_FORMAT_71CHN16),
DECL(AL_FORMAT_71CHN32),
DECL(AL_FORMAT_REAR8),
DECL(AL_FORMAT_REAR16),
DECL(AL_FORMAT_REAR32),
DECL(AL_FORMAT_MONO_MULAW),
DECL(AL_FORMAT_MONO_MULAW_EXT),
DECL(AL_FORMAT_STEREO_MULAW),
DECL(AL_FORMAT_STEREO_MULAW_EXT),
DECL(AL_FORMAT_QUAD_MULAW),
DECL(AL_FORMAT_51CHN_MULAW),
DECL(AL_FORMAT_61CHN_MULAW),
DECL(AL_FORMAT_71CHN_MULAW),
DECL(AL_FORMAT_REAR_MULAW),
DECL(AL_FORMAT_MONO_ALAW_EXT),
DECL(AL_FORMAT_STEREO_ALAW_EXT),
DECL(AL_FORMAT_BFORMAT2D_8),
DECL(AL_FORMAT_BFORMAT2D_16),
DECL(AL_FORMAT_BFORMAT2D_FLOAT32),
DECL(AL_FORMAT_BFORMAT2D_MULAW),
DECL(AL_FORMAT_BFORMAT3D_8),
DECL(AL_FORMAT_BFORMAT3D_16),
DECL(AL_FORMAT_BFORMAT3D_FLOAT32),
DECL(AL_FORMAT_BFORMAT3D_MULAW),
DECL(AL_FORMAT_UHJ2CHN8_SOFT),
DECL(AL_FORMAT_UHJ2CHN16_SOFT),
DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT),
DECL(AL_FORMAT_UHJ3CHN8_SOFT),
DECL(AL_FORMAT_UHJ3CHN16_SOFT),
DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT),
DECL(AL_FORMAT_UHJ4CHN8_SOFT),
DECL(AL_FORMAT_UHJ4CHN16_SOFT),
DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT),
DECL(AL_STEREO_MODE_SOFT),
DECL(AL_NORMAL_SOFT),
DECL(AL_SUPER_STEREO_SOFT),
DECL(AL_SUPER_STEREO_WIDTH_SOFT),
DECL(AL_FORMAT_UHJ2CHN_MULAW_SOFT),
DECL(AL_FORMAT_UHJ2CHN_ALAW_SOFT),
DECL(AL_FORMAT_UHJ2CHN_IMA4_SOFT),
DECL(AL_FORMAT_UHJ2CHN_MSADPCM_SOFT),
DECL(AL_FORMAT_UHJ3CHN_MULAW_SOFT),
DECL(AL_FORMAT_UHJ3CHN_ALAW_SOFT),
DECL(AL_FORMAT_UHJ4CHN_MULAW_SOFT),
DECL(AL_FORMAT_UHJ4CHN_ALAW_SOFT),
DECL(AL_FORMAT_MONO_I32),
DECL(AL_FORMAT_STEREO_I32),
DECL(AL_FORMAT_REAR_I32),
DECL(AL_FORMAT_QUAD_I32),
DECL(AL_FORMAT_51CHN_I32),
DECL(AL_FORMAT_61CHN_I32),
DECL(AL_FORMAT_71CHN_I32),
DECL(AL_FORMAT_UHJ2CHN_I32_SOFT),
DECL(AL_FORMAT_UHJ3CHN_I32_SOFT),
DECL(AL_FORMAT_UHJ4CHN_I32_SOFT),
DECL(AL_FORMAT_REAR_FLOAT32),
DECL(AL_FORMAT_QUAD_FLOAT32),
DECL(AL_FORMAT_51CHN_FLOAT32),
DECL(AL_FORMAT_61CHN_FLOAT32),
DECL(AL_FORMAT_71CHN_FLOAT32),
DECL(AL_FREQUENCY),
DECL(AL_BITS),
DECL(AL_CHANNELS),
DECL(AL_SIZE),
DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT),
DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT),
DECL(AL_SOURCE_RADIUS),
DECL(AL_SAMPLE_OFFSET_LATENCY_SOFT),
DECL(AL_SEC_OFFSET_LATENCY_SOFT),
DECL(AL_STEREO_ANGLES),
DECL(AL_UNUSED),
DECL(AL_PENDING),
DECL(AL_PROCESSED),
DECL(AL_NO_ERROR),
DECL(AL_INVALID_NAME),
DECL(AL_INVALID_ENUM),
DECL(AL_INVALID_VALUE),
DECL(AL_INVALID_OPERATION),
DECL(AL_OUT_OF_MEMORY),
DECL(AL_VENDOR),
DECL(AL_VERSION),
DECL(AL_RENDERER),
DECL(AL_EXTENSIONS),
DECL(AL_DOPPLER_FACTOR),
DECL(AL_DOPPLER_VELOCITY),
DECL(AL_DISTANCE_MODEL),
DECL(AL_SPEED_OF_SOUND),
DECL(AL_SOURCE_DISTANCE_MODEL),
DECL(AL_DEFERRED_UPDATES_SOFT),
DECL(AL_GAIN_LIMIT_SOFT),
DECL(AL_INVERSE_DISTANCE),
DECL(AL_INVERSE_DISTANCE_CLAMPED),
DECL(AL_LINEAR_DISTANCE),
DECL(AL_LINEAR_DISTANCE_CLAMPED),
DECL(AL_EXPONENT_DISTANCE),
DECL(AL_EXPONENT_DISTANCE_CLAMPED),
DECL(AL_FILTER_TYPE),
DECL(AL_FILTER_NULL),
DECL(AL_FILTER_LOWPASS),
DECL(AL_FILTER_HIGHPASS),
DECL(AL_FILTER_BANDPASS),
DECL(AL_LOWPASS_GAIN),
DECL(AL_LOWPASS_GAINHF),
DECL(AL_HIGHPASS_GAIN),
DECL(AL_HIGHPASS_GAINLF),
DECL(AL_BANDPASS_GAIN),
DECL(AL_BANDPASS_GAINHF),
DECL(AL_BANDPASS_GAINLF),
DECL(AL_EFFECT_TYPE),
DECL(AL_EFFECT_NULL),
DECL(AL_EFFECT_REVERB),
DECL(AL_EFFECT_EAXREVERB),
DECL(AL_EFFECT_CHORUS),
DECL(AL_EFFECT_DISTORTION),
DECL(AL_EFFECT_ECHO),
DECL(AL_EFFECT_FLANGER),
DECL(AL_EFFECT_PITCH_SHIFTER),
DECL(AL_EFFECT_FREQUENCY_SHIFTER),
DECL(AL_EFFECT_VOCAL_MORPHER),
DECL(AL_EFFECT_RING_MODULATOR),
DECL(AL_EFFECT_AUTOWAH),
DECL(AL_EFFECT_COMPRESSOR),
DECL(AL_EFFECT_EQUALIZER),
DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT),
DECL(AL_EFFECT_DEDICATED_DIALOGUE),
DECL(AL_EFFECTSLOT_EFFECT),
DECL(AL_EFFECTSLOT_GAIN),
DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO),
DECL(AL_EFFECTSLOT_NULL),
DECL(AL_EAXREVERB_DENSITY),
DECL(AL_EAXREVERB_DIFFUSION),
DECL(AL_EAXREVERB_GAIN),
DECL(AL_EAXREVERB_GAINHF),
DECL(AL_EAXREVERB_GAINLF),
DECL(AL_EAXREVERB_DECAY_TIME),
DECL(AL_EAXREVERB_DECAY_HFRATIO),
DECL(AL_EAXREVERB_DECAY_LFRATIO),
DECL(AL_EAXREVERB_REFLECTIONS_GAIN),
DECL(AL_EAXREVERB_REFLECTIONS_DELAY),
DECL(AL_EAXREVERB_REFLECTIONS_PAN),
DECL(AL_EAXREVERB_LATE_REVERB_GAIN),
DECL(AL_EAXREVERB_LATE_REVERB_DELAY),
DECL(AL_EAXREVERB_LATE_REVERB_PAN),
DECL(AL_EAXREVERB_ECHO_TIME),
DECL(AL_EAXREVERB_ECHO_DEPTH),
DECL(AL_EAXREVERB_MODULATION_TIME),
DECL(AL_EAXREVERB_MODULATION_DEPTH),
DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF),
DECL(AL_EAXREVERB_HFREFERENCE),
DECL(AL_EAXREVERB_LFREFERENCE),
DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR),
DECL(AL_EAXREVERB_DECAY_HFLIMIT),
DECL(AL_REVERB_DENSITY),
DECL(AL_REVERB_DIFFUSION),
DECL(AL_REVERB_GAIN),
DECL(AL_REVERB_GAINHF),
DECL(AL_REVERB_DECAY_TIME),
DECL(AL_REVERB_DECAY_HFRATIO),
DECL(AL_REVERB_REFLECTIONS_GAIN),
DECL(AL_REVERB_REFLECTIONS_DELAY),
DECL(AL_REVERB_LATE_REVERB_GAIN),
DECL(AL_REVERB_LATE_REVERB_DELAY),
DECL(AL_REVERB_AIR_ABSORPTION_GAINHF),
DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR),
DECL(AL_REVERB_DECAY_HFLIMIT),
DECL(AL_CHORUS_WAVEFORM),
DECL(AL_CHORUS_PHASE),
DECL(AL_CHORUS_RATE),
DECL(AL_CHORUS_DEPTH),
DECL(AL_CHORUS_FEEDBACK),
DECL(AL_CHORUS_DELAY),
DECL(AL_DISTORTION_EDGE),
DECL(AL_DISTORTION_GAIN),
DECL(AL_DISTORTION_LOWPASS_CUTOFF),
DECL(AL_DISTORTION_EQCENTER),
DECL(AL_DISTORTION_EQBANDWIDTH),
DECL(AL_ECHO_DELAY),
DECL(AL_ECHO_LRDELAY),
DECL(AL_ECHO_DAMPING),
DECL(AL_ECHO_FEEDBACK),
DECL(AL_ECHO_SPREAD),
DECL(AL_FLANGER_WAVEFORM),
DECL(AL_FLANGER_PHASE),
DECL(AL_FLANGER_RATE),
DECL(AL_FLANGER_DEPTH),
DECL(AL_FLANGER_FEEDBACK),
DECL(AL_FLANGER_DELAY),
DECL(AL_FREQUENCY_SHIFTER_FREQUENCY),
DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION),
DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION),
DECL(AL_RING_MODULATOR_FREQUENCY),
DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF),
DECL(AL_RING_MODULATOR_WAVEFORM),
DECL(AL_PITCH_SHIFTER_COARSE_TUNE),
DECL(AL_PITCH_SHIFTER_FINE_TUNE),
DECL(AL_COMPRESSOR_ONOFF),
DECL(AL_EQUALIZER_LOW_GAIN),
DECL(AL_EQUALIZER_LOW_CUTOFF),
DECL(AL_EQUALIZER_MID1_GAIN),
DECL(AL_EQUALIZER_MID1_CENTER),
DECL(AL_EQUALIZER_MID1_WIDTH),
DECL(AL_EQUALIZER_MID2_GAIN),
DECL(AL_EQUALIZER_MID2_CENTER),
DECL(AL_EQUALIZER_MID2_WIDTH),
DECL(AL_EQUALIZER_HIGH_GAIN),
DECL(AL_EQUALIZER_HIGH_CUTOFF),
DECL(AL_DEDICATED_GAIN),
DECL(AL_AUTOWAH_ATTACK_TIME),
DECL(AL_AUTOWAH_RELEASE_TIME),
DECL(AL_AUTOWAH_RESONANCE),
DECL(AL_AUTOWAH_PEAK_GAIN),
DECL(AL_VOCAL_MORPHER_PHONEMEA),
DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING),
DECL(AL_VOCAL_MORPHER_PHONEMEB),
DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING),
DECL(AL_VOCAL_MORPHER_WAVEFORM),
DECL(AL_VOCAL_MORPHER_RATE),
DECL(AL_EFFECTSLOT_TARGET_SOFT),
DECL(AL_NUM_RESAMPLERS_SOFT),
DECL(AL_DEFAULT_RESAMPLER_SOFT),
DECL(AL_SOURCE_RESAMPLER_SOFT),
DECL(AL_RESAMPLER_NAME_SOFT),
DECL(AL_SOURCE_SPATIALIZE_SOFT),
DECL(AL_AUTO_SOFT),
DECL(AL_MAP_READ_BIT_SOFT),
DECL(AL_MAP_WRITE_BIT_SOFT),
DECL(AL_MAP_PERSISTENT_BIT_SOFT),
DECL(AL_PRESERVE_DATA_BIT_SOFT),
DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT),
DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT),
DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT),
DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT),
DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT),
DECL(AL_DROP_UNMATCHED_SOFT),
DECL(AL_REMIX_UNMATCHED_SOFT),
DECL(AL_AMBISONIC_LAYOUT_SOFT),
DECL(AL_AMBISONIC_SCALING_SOFT),
DECL(AL_FUMA_SOFT),
DECL(AL_ACN_SOFT),
DECL(AL_SN3D_SOFT),
DECL(AL_N3D_SOFT),
DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT),
DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT),
DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT),
DECL(AL_EFFECT_CONVOLUTION_SOFT),
DECL(AL_EFFECTSLOT_STATE_SOFT),
DECL(AL_DONT_CARE_EXT),
DECL(AL_DEBUG_OUTPUT_EXT),
DECL(AL_DEBUG_CALLBACK_FUNCTION_EXT),
DECL(AL_DEBUG_CALLBACK_USER_PARAM_EXT),
DECL(AL_DEBUG_SOURCE_API_EXT),
DECL(AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT),
DECL(AL_DEBUG_SOURCE_THIRD_PARTY_EXT),
DECL(AL_DEBUG_SOURCE_APPLICATION_EXT),
DECL(AL_DEBUG_SOURCE_OTHER_EXT),
DECL(AL_DEBUG_TYPE_ERROR_EXT),
DECL(AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT),
DECL(AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT),
DECL(AL_DEBUG_TYPE_PORTABILITY_EXT),
DECL(AL_DEBUG_TYPE_PERFORMANCE_EXT),
DECL(AL_DEBUG_TYPE_MARKER_EXT),
DECL(AL_DEBUG_TYPE_PUSH_GROUP_EXT),
DECL(AL_DEBUG_TYPE_POP_GROUP_EXT),
DECL(AL_DEBUG_TYPE_OTHER_EXT),
DECL(AL_DEBUG_SEVERITY_HIGH_EXT),
DECL(AL_DEBUG_SEVERITY_MEDIUM_EXT),
DECL(AL_DEBUG_SEVERITY_LOW_EXT),
DECL(AL_DEBUG_SEVERITY_NOTIFICATION_EXT),
DECL(AL_DEBUG_LOGGED_MESSAGES_EXT),
DECL(AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT),
DECL(AL_MAX_DEBUG_MESSAGE_LENGTH_EXT),
DECL(AL_MAX_DEBUG_LOGGED_MESSAGES_EXT),
DECL(AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT),
DECL(AL_MAX_LABEL_LENGTH_EXT),
DECL(AL_STACK_OVERFLOW_EXT),
DECL(AL_STACK_UNDERFLOW_EXT),
DECL(AL_BUFFER_EXT),
DECL(AL_SOURCE_EXT),
DECL(AL_FILTER_EXT),
DECL(AL_EFFECT_EXT),
DECL(AL_AUXILIARY_EFFECT_SLOT_EXT),
DECL(AL_PANNING_ENABLED_SOFT),
DECL(AL_PAN_SOFT),
DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT),
};
#ifdef ALSOFT_EAX
inline const std::array eaxEnumerations{
DECL(AL_EAX_RAM_SIZE),
DECL(AL_EAX_RAM_FREE),
DECL(AL_STORAGE_AUTOMATIC),
DECL(AL_STORAGE_HARDWARE),
DECL(AL_STORAGE_ACCESSIBLE),
};
#endif // ALSOFT_EAX
#undef DECL
#endif /* ALC_EXPORT_LIST_H */

View file

@ -1,6 +1,7 @@
#ifndef INPROGEXT_H
#define INPROGEXT_H
/* NOLINTBEGIN */
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
@ -16,15 +17,23 @@ typedef unsigned int ALbitfieldSOFT;
#define AL_MAP_WRITE_BIT_SOFT 0x00000002
#define AL_MAP_PERSISTENT_BIT_SOFT 0x00000004
#define AL_PRESERVE_DATA_BIT_SOFT 0x00000008
typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags);
typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access);
typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer);
typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length);
typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17;
typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALBUFFERSTORAGEDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17;
typedef void* (AL_APIENTRY*LPALMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALUNMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT17;
typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17;
#ifdef AL_ALEXT_PROTOTYPES
AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags);
AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access);
AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer);
AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length);
AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT;
AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT;
void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT;
void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT;
void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT;
void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT;
#endif
#endif
@ -33,20 +42,11 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A
#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D
#endif
#ifndef AL_SOFT_convolution_reverb
#define AL_SOFT_convolution_reverb
#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000
#define AL_EFFECTSLOT_STATE_SOFT 0x199D
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid);
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids);
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid);
typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids);
#ifdef AL_ALEXT_PROTOTYPES
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid);
AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids);
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid);
AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids);
#endif
#ifndef AL_SOFT_convolution_effect
#define AL_SOFT_convolution_effect
#define AL_EFFECT_CONVOLUTION_SOFT 0xA000
#define AL_CONVOLUTION_ORIENTATION_SOFT 0x100F /* same as AL_ORIENTATION */
#define AL_EFFECTSLOT_STATE_SOFT 0x199E
#endif
#ifndef AL_SOFT_hold_on_disconnect
@ -55,19 +55,56 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *
#endif
/* Non-standard export. Not part of any extension. */
AL_API const ALchar* AL_APIENTRY alsoft_get_version(void);
#ifndef AL_EXT_32bit_formats
#define AL_EXT_32bit_formats
#define AL_FORMAT_MONO_I32 0x19DB
#define AL_FORMAT_STEREO_I32 0x19DC
#define AL_FORMAT_REAR_I32 0x19DD
#define AL_FORMAT_REAR_FLOAT32 0x19DE
#define AL_FORMAT_QUAD_I32 0x19DF
#define AL_FORMAT_QUAD_FLOAT32 0x19E0
#define AL_FORMAT_51CHN_I32 0x19E1
#define AL_FORMAT_51CHN_FLOAT32 0x19E2
#define AL_FORMAT_61CHN_I32 0x19E3
#define AL_FORMAT_61CHN_FLOAT32 0x19E4
#define AL_FORMAT_71CHN_I32 0x19E5
#define AL_FORMAT_71CHN_FLOAT32 0x19E6
#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E7
#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19E8
#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19E9
#endif
#ifndef AL_SOFT_source_panning
#define AL_SOFT_source_panning
#define AL_PANNING_ENABLED_SOFT 0x19EA
#define AL_PAN_SOFT 0x19EB
#endif
/* Non-standard exports. Not part of any extension. */
AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) noexcept;
typedef void (ALC_APIENTRY*LPALSOFTLOGCALLBACK)(void *userptr, char level, const char *message, int length) noexcept;
void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *userptr) noexcept;
/* Functions from abandoned extensions. Only here for binary compatibility. */
AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb,
const ALuint *buffers);
const ALuint *buffers) noexcept;
AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname);
AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values);
AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT;
AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT;
ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT;
void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT;
/* Not included in the public headers or export list, as a precaution for apps
* that check these to determine the behavior of the multi-channel *32 formats.
*/
#define AL_FORMAT_MONO32 0x1202
#define AL_FORMAT_STEREO32 0x1203
#ifdef __cplusplus
} /* extern "C" */
#endif
/* NOLINTEND */
#endif /* INPROGEXT_H */

View file

@ -22,53 +22,61 @@
#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <new>
#include <numeric>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "al/auxeffectslot.h"
#include "albit.h"
#include "alconfig.h"
#include "alc/context.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alspan.h"
#include "alstring.h"
#include "alu.h"
#include "core/ambdec.h"
#include "core/ambidefs.h"
#include "core/bformatdec.h"
#include "core/bufferline.h"
#include "core/bs2b.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effectslot.h"
#include "core/filters/nfc.h"
#include "core/filters/splitter.h"
#include "core/front_stablizer.h"
#include "core/hrtf.h"
#include "core/logging.h"
#include "core/mixer/hrtfdefs.h"
#include "core/uhjfilter.h"
#include "device.h"
#include "flexarray.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
namespace {
using namespace std::placeholders;
using namespace std::string_view_literals;
using std::chrono::seconds;
using std::chrono::nanoseconds;
inline const char *GetLabelFromChannel(Channel channel)
const char *GetLabelFromChannel(Channel channel)
{
switch(channel)
{
@ -90,6 +98,11 @@ inline const char *GetLabelFromChannel(Channel channel)
case TopBackCenter: return "top-back-center";
case TopBackRight: return "top-back-right";
case BottomFrontLeft: return "bottom-front-left";
case BottomFrontRight: return "bottom-front-right";
case BottomBackLeft: return "bottom-back-left";
case BottomBackRight: return "bottom-back-right";
case Aux0: return "Aux0";
case Aux1: return "Aux1";
case Aux2: return "Aux2";
@ -226,43 +239,47 @@ struct DecoderConfig<DualBand, 0> {
using DecoderView = DecoderConfig<DualBand, 0>;
void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d)
void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint order, const bool is3d)
{
static const uint chans_per_order2d[MaxAmbiOrder+1]{ 1, 2, 2, 2 };
static const uint chans_per_order3d[MaxAmbiOrder+1]{ 1, 3, 5, 7 };
static const std::array<uint,MaxAmbiOrder+1> chans_per_order2d{{1, 2, 2, 2}};
static const std::array<uint,MaxAmbiOrder+1> chans_per_order3d{{1, 3, 5, 7}};
/* NFC is only used when AvgSpeakerDist is greater than 0. */
if(!device->getConfigValueBool("decoder", "nfc", false) || !(ctrl_dist > 0.0f))
return;
device->AvgSpeakerDist = clampf(ctrl_dist, 0.1f, 10.0f);
device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f);
TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist);
const float w1{SpeedOfSoundMetersPerSec /
(device->AvgSpeakerDist * static_cast<float>(device->Frequency))};
device->mNFCtrlFilter.init(w1);
auto iter = std::copy_n(is3d ? chans_per_order3d : chans_per_order2d, order+1u,
std::begin(device->NumChannelsPerOrder));
std::fill(iter, std::end(device->NumChannelsPerOrder), 0u);
auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u,
device->NumChannelsPerOrder.begin());
std::fill(iter, device->NumChannelsPerOrder.end(), 0u);
}
void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
const al::span<const float,MAX_OUTPUT_CHANNELS> dists)
const al::span<const float,MaxOutputChannels> dists)
{
const float maxdist{std::accumulate(std::begin(dists), std::end(dists), 0.0f, maxf)};
const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f,
[](const float a, const float b) noexcept -> float { return std::max(a, b); })};
if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f))
return;
const auto distSampleScale = static_cast<float>(device->Frequency) / SpeedOfSoundMetersPerSec;
std::vector<DistanceComp::ChanData> ChanDelay;
struct DistCoeffs { uint Length{}; float Gain{}; };
std::vector<DistCoeffs> ChanDelay;
ChanDelay.reserve(device->RealOut.Buffer.size());
size_t total{0u};
for(size_t chidx{0};chidx < channels.size();++chidx)
{
const Channel ch{channels[chidx]};
const uint idx{device->RealOut.ChannelIndex[ch]};
const size_t idx{device->RealOut.ChannelIndex[ch]};
if(idx == InvalidChannelIndex)
continue;
@ -277,12 +294,12 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)};
if(delay > float{DistanceComp::MaxDelay-1})
{
ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx,
ERR("Delay for channel %zu (%s) exceeds buffer length (%f > %d)\n", idx,
GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1);
delay = float{DistanceComp::MaxDelay-1};
}
ChanDelay.resize(maxz(ChanDelay.size(), idx+1));
ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz));
ChanDelay[idx].Length = static_cast<uint>(delay);
ChanDelay[idx].Gain = distance / maxdist;
TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch),
@ -297,38 +314,39 @@ void InitDistanceComp(ALCdevice *device, const al::span<const Channel> channels,
if(total > 0)
{
auto chandelays = DistanceComp::Create(total);
auto chanbuffer = chandelays->mSamples.begin();
ChanDelay[0].Buffer = chandelays->mSamples.data();
auto set_bufptr = [](const DistanceComp::ChanData &last, const DistanceComp::ChanData &cur)
-> DistanceComp::ChanData
auto set_bufptr = [&chanbuffer](const DistCoeffs &data)
{
DistanceComp::ChanData ret{cur};
ret.Buffer = last.Buffer + RoundUp(last.Length, 4);
DistanceComp::ChanData ret{};
ret.Buffer = al::span{chanbuffer, data.Length};
ret.Gain = data.Gain;
chanbuffer += ptrdiff_t(RoundUp(data.Length, 4));
return ret;
};
std::partial_sum(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(),
std::transform(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(),
set_bufptr);
device->ChannelDelays = std::move(chandelays);
}
}
inline auto& GetAmbiScales(DevAmbiScaling scaletype) noexcept
constexpr auto GetAmbiScales(DevAmbiScaling scaletype) noexcept
{
if(scaletype == DevAmbiScaling::FuMa) return AmbiScale::FromFuMa();
if(scaletype == DevAmbiScaling::SN3D) return AmbiScale::FromSN3D();
return AmbiScale::FromN3D();
if(scaletype == DevAmbiScaling::FuMa) return al::span{AmbiScale::FromFuMa};
if(scaletype == DevAmbiScaling::SN3D) return al::span{AmbiScale::FromSN3D};
return al::span{AmbiScale::FromN3D};
}
inline auto& GetAmbiLayout(DevAmbiLayout layouttype) noexcept
constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept
{
if(layouttype == DevAmbiLayout::FuMa) return AmbiIndex::FromFuMa();
return AmbiIndex::FromACN();
if(layouttype == DevAmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa};
return al::span{AmbiIndex::FromACN};
}
DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
DecoderConfig<DualBand, MAX_OUTPUT_CHANNELS> &decoder)
DecoderConfig<DualBand,MaxOutputChannels> &decoder)
{
DecoderView ret{};
@ -345,23 +363,20 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break;
}
std::copy_n(std::begin(conf->HFOrderGain),
std::min(al::size(conf->HFOrderGain), al::size(decoder.mOrderGain)),
std::begin(decoder.mOrderGain));
std::copy_n(std::begin(conf->LFOrderGain),
std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)),
std::begin(decoder.mOrderGainLF));
const auto hfordermin = std::min(conf->HFOrderGain.size(), decoder.mOrderGain.size());
std::copy_n(conf->HFOrderGain.begin(), hfordermin, decoder.mOrderGain.begin());
const auto lfordermin = std::min(conf->LFOrderGain.size(), decoder.mOrderGainLF.size());
std::copy_n(conf->LFOrderGain.begin(), lfordermin, decoder.mOrderGainLF.begin());
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 idx_map = decoder.mIs3D ? al::span<const uint8_t>{AmbiIndex::FromACN}
: al::span<const uint8_t>{AmbiIndex::FromACN2D};
const auto hfmatrix = conf->HFMatrix;
const auto lfmatrix = conf->LFMatrix;
uint chan_count{0};
using const_speaker_span = al::span<const AmbDecConf::SpeakerConf>;
for(auto &speaker : const_speaker_span{conf->Speakers.get(), conf->NumSpeakers})
for(auto &speaker : al::span{std::as_const(conf->Speakers)})
{
/* NOTE: AmbDec does not define any standard speaker names, however
* for this to work we have to by able to find the output channel
@ -380,36 +395,48 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
* RFT = Top front right
* LBT = Top back left
* RBT = Top back right
* LFB = Bottom front left
* RFB = Bottom front right
* LBB = Bottom back left
* RBB = Bottom back right
*
* Additionally, surround51 will acknowledge back speakers for side
* channels, to avoid issues with an ambdec expecting 5.1 to use the
* back channels.
*/
Channel ch{};
if(speaker.Name == "LF")
if(speaker.Name == "LF"sv)
ch = FrontLeft;
else if(speaker.Name == "RF")
else if(speaker.Name == "RF"sv)
ch = FrontRight;
else if(speaker.Name == "CE")
else if(speaker.Name == "CE"sv)
ch = FrontCenter;
else if(speaker.Name == "LS")
else if(speaker.Name == "LS"sv)
ch = SideLeft;
else if(speaker.Name == "RS")
else if(speaker.Name == "RS"sv)
ch = SideRight;
else if(speaker.Name == "LB")
else if(speaker.Name == "LB"sv)
ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft;
else if(speaker.Name == "RB")
else if(speaker.Name == "RB"sv)
ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight;
else if(speaker.Name == "CB")
else if(speaker.Name == "CB"sv)
ch = BackCenter;
else if(speaker.Name == "LFT")
else if(speaker.Name == "LFT"sv)
ch = TopFrontLeft;
else if(speaker.Name == "RFT")
else if(speaker.Name == "RFT"sv)
ch = TopFrontRight;
else if(speaker.Name == "LBT")
else if(speaker.Name == "LBT"sv)
ch = TopBackLeft;
else if(speaker.Name == "RBT")
else if(speaker.Name == "RBT"sv)
ch = TopBackRight;
else if(speaker.Name == "LFB"sv)
ch = BottomFrontLeft;
else if(speaker.Name == "RFB"sv)
ch = BottomFrontRight;
else if(speaker.Name == "LBB"sv)
ch = BottomBackLeft;
else if(speaker.Name == "RBB"sv)
ch = BottomBackRight;
else
{
int idx{};
@ -445,13 +472,13 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf,
ret.mOrder = decoder.mOrder;
ret.mIs3D = decoder.mIs3D;
ret.mScaling = decoder.mScaling;
ret.mChannels = {decoder.mChannels.data(), chan_count};
ret.mChannels = al::span{decoder.mChannels}.first(chan_count);
ret.mOrderGain = decoder.mOrderGain;
ret.mCoeffs = {decoder.mCoeffs.data(), chan_count};
ret.mCoeffs = al::span{decoder.mCoeffs}.first(chan_count);
if(conf->FreqBands > 1)
{
ret.mOrderGainLF = decoder.mOrderGainLF;
ret.mCoeffsLF = {decoder.mCoeffsLF.data(), chan_count};
ret.mCoeffsLF = al::span{decoder.mCoeffsLF}.first(chan_count);
}
}
return ret;
@ -583,6 +610,44 @@ constexpr DecoderConfig<SingleBand, 10> X714Config{
{{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}},
}}
};
constexpr DecoderConfig<DualBand, 14> X7144Config{
1, true, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight, TopBackLeft, TopFrontLeft, TopFrontRight, TopBackRight, BottomBackLeft, BottomFrontLeft, BottomFrontRight, BottomBackRight}},
DevAmbiScaling::N3D,
/*HF*/{{2.64575131e+0f, 1.52752523e+0f}},
{{
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
}},
/*LF*/{{1.00000000e+0f, 1.00000000e+0f}},
{{
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}},
{{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}},
{{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
{{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}},
{{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}},
}}
};
void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false,
DecoderView decoder={})
@ -598,15 +663,16 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
case DevFmtX61: decoder = X61Config; break;
case DevFmtX71: decoder = X71Config; break;
case DevFmtX714: decoder = X714Config; break;
case DevFmtX7144: decoder = X7144Config; break;
case DevFmtX3D71: decoder = X3D71Config; break;
case DevFmtAmbi3D:
auto&& acnmap = GetAmbiLayout(device->mAmbiLayout);
auto&& n3dscale = GetAmbiScales(device->mAmbiScale);
/* For DevFmtAmbi3D, the ambisonic order is already set. */
const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)};
std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap),
[&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig
const auto acnmap = GetAmbiLayout(device->mAmbiLayout).first(count);
const auto n3dscale = GetAmbiScales(device->mAmbiScale);
std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(),
[n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f/n3dscale[acn], acn}; });
AllocChannels(device, count, 0);
device->m2DMixing = false;
@ -628,10 +694,10 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
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;
std::vector<ChannelDec> chancoeffs, chancoeffslf;
for(size_t i{0u};i < decoder.mChannels.size();++i)
{
const uint idx{device->channelIdxByName(decoder.mChannels[i])};
const size_t idx{device->channelIdxByName(decoder.mChannels[i])};
if(idx == InvalidChannelIndex)
{
ERR("Failed to find %s channel in device\n",
@ -639,10 +705,10 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
continue;
}
auto ordermap = decoder.mIs3D ? AmbiIndex::OrderFromChannel().data()
: AmbiIndex::OrderFrom2DChannel().data();
auto ordermap = decoder.mIs3D ? al::span<const uint8_t>{AmbiIndex::OrderFromChannel}
: al::span<const uint8_t>{AmbiIndex::OrderFrom2DChannel};
chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{});
chancoeffs.resize(std::max(chancoeffs.size(), idx+1_zu), ChannelDec{});
al::span<const float,MaxAmbiChannels> src{decoder.mCoeffs[i]};
al::span<float,MaxAmbiChannels> dst{chancoeffs[idx]};
for(size_t ambichan{0};ambichan < ambicount;++ambichan)
@ -651,7 +717,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
if(!dual_band)
continue;
chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{});
chancoeffslf.resize(std::max(chancoeffslf.size(), idx+1_zu), ChannelDec{});
src = decoder.mCoeffsLF[i];
dst = chancoeffslf[idx];
for(size_t ambichan{0};ambichan < ambicount;++ambichan)
@ -662,11 +728,11 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
device->mAmbiOrder = decoder.mOrder;
device->m2DMixing = !decoder.mIs3D;
const al::span<const uint8_t> acnmap{decoder.mIs3D ? AmbiIndex::FromACN().data() :
AmbiIndex::FromACN2D().data(), ambicount};
auto&& coeffscale = GetAmbiScales(decoder.mScaling);
std::transform(acnmap.begin(), acnmap.end(), std::begin(device->Dry.AmbiMap),
[&coeffscale](const uint8_t &acn) noexcept
const auto acnmap = decoder.mIs3D ? al::span{AmbiIndex::FromACN}.first(ambicount)
: al::span{AmbiIndex::FromACN2D}.first(ambicount);
const auto coeffscale = GetAmbiScales(decoder.mScaling);
std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(),
[coeffscale](const uint8_t &acn) noexcept
{ return BFChannelConfig{1.0f/coeffscale[acn], acn}; });
AllocChannels(device, ambicount, device->channelsFromFmt());
@ -676,7 +742,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
/* Only enable the stablizer if the decoder does not output to the
* front-center channel.
*/
const auto cidx = device->RealOut.ChannelIndex[FrontCenter];
const size_t cidx{device->RealOut.ChannelIndex[FrontCenter]};
bool hasfc{false};
if(cidx < chancoeffs.size())
{
@ -707,120 +773,126 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=
void InitHrtfPanning(ALCdevice *device)
{
constexpr float Deg180{al::numbers::pi_v<float>};
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 Deg122{2.124370686e+00f /*121~122 degrees*/};
static const AngularPoint AmbiPoints1O[]{
{ 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} },
}, AmbiPoints2O[]{
{ 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} },
{ EvRadians{-Deg_69}, AzRadians{-Deg_90} },
{ EvRadians{-Deg_69}, AzRadians{ Deg_90} },
{ EvRadians{ 0.0f}, AzRadians{-Deg_69} },
{ EvRadians{ 0.0f}, AzRadians{-Deg111} },
{ EvRadians{ 0.0f}, AzRadians{ Deg_69} },
{ EvRadians{ 0.0f}, AzRadians{ Deg111} },
{ EvRadians{ Deg_21}, AzRadians{ 0.0f} },
{ EvRadians{ Deg_21}, AzRadians{ Deg180} },
{ EvRadians{-Deg_21}, AzRadians{ 0.0f} },
{ EvRadians{-Deg_21}, AzRadians{ Deg180} },
{ 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} },
static constexpr float Deg180{al::numbers::pi_v<float>};
static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/};
static constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/};
static constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/};
static constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/};
static constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/};
static constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/};
static constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/};
static constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/};
static constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/};
static constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/};
static constexpr std::array AmbiPoints1O{
AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}},
AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}},
AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}},
AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}},
};
static const float AmbiMatrix1O[][MaxAmbiChannels]{
{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f },
{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f },
{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f },
{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f },
{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f },
{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f },
{ 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]{
{ 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, },
{ 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, },
{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, },
{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, },
{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, },
{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, },
{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, },
{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, },
{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, },
{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, },
{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, },
{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, },
{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, },
{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, },
{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, },
{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, },
{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, },
{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, },
static constexpr std::array AmbiPoints2O{
AngularPoint{EvRadians{-Deg_32}, AzRadians{ 0.0f}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_58}},
AngularPoint{EvRadians{ Deg_58}, AzRadians{ Deg_90}},
AngularPoint{EvRadians{ Deg_32}, AzRadians{ 0.0f}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg122}},
AngularPoint{EvRadians{-Deg_58}, AzRadians{-Deg_90}},
AngularPoint{EvRadians{-Deg_32}, AzRadians{ Deg180}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg122}},
AngularPoint{EvRadians{ Deg_58}, AzRadians{-Deg_90}},
AngularPoint{EvRadians{ Deg_32}, AzRadians{ Deg180}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_58}},
AngularPoint{EvRadians{-Deg_58}, AzRadians{ Deg_90}},
};
static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{
static constexpr std::array AmbiPoints3O{
AngularPoint{EvRadians{ Deg_69}, AzRadians{-Deg_90}},
AngularPoint{EvRadians{ Deg_69}, AzRadians{ Deg_90}},
AngularPoint{EvRadians{-Deg_69}, AzRadians{-Deg_90}},
AngularPoint{EvRadians{-Deg_69}, AzRadians{ Deg_90}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_69}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg111}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_69}},
AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg111}},
AngularPoint{EvRadians{ Deg_21}, AzRadians{ 0.0f}},
AngularPoint{EvRadians{ Deg_21}, AzRadians{ Deg180}},
AngularPoint{EvRadians{-Deg_21}, AzRadians{ 0.0f}},
AngularPoint{EvRadians{-Deg_21}, AzRadians{ Deg180}},
AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}},
AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}},
AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}},
AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}},
AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}},
};
static constexpr std::array AmbiMatrix1O{
ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f},
ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f},
ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f},
ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f},
ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f},
ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f},
ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f},
ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f},
};
static constexpr std::array AmbiMatrix2O{
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
};
static constexpr std::array AmbiMatrix3O{
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{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},
ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f},
ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f},
ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f},
ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f},
ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f},
ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f},
ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f},
ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f},
ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f},
ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f},
ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f},
ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f},
ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f},
ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f},
ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f},
ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f},
};
static constexpr std::array<float,MaxAmbiOrder+1> AmbiOrderHFGain1O{
/*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f
}, AmbiOrderHFGain2O[MaxAmbiOrder+1]{
};
static constexpr std::array<float,MaxAmbiOrder+1> AmbiOrderHFGain2O{
/*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*/
}, AmbiOrderHFGain3O[MaxAmbiOrder+1]{
};
static constexpr std::array<float,MaxAmbiOrder+1> AmbiOrderHFGain3O{
/*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*/
};
static_assert(al::size(AmbiPoints1O) == al::size(AmbiMatrix1O), "First-Order Ambisonic HRTF mismatch");
static_assert(al::size(AmbiPoints2O) == al::size(AmbiMatrix2O), "Second-Order Ambisonic HRTF mismatch");
static_assert(al::size(AmbiPoints3O) == al::size(AmbiMatrix3O), "Third-Order Ambisonic HRTF mismatch");
static_assert(AmbiPoints1O.size() == AmbiMatrix1O.size(), "First-Order Ambisonic HRTF mismatch");
static_assert(AmbiPoints2O.size() == AmbiMatrix2O.size(), "Second-Order Ambisonic HRTF mismatch");
static_assert(AmbiPoints3O.size() == AmbiMatrix3O.size(), "Third-Order Ambisonic HRTF mismatch");
/* A 700hz crossover frequency provides tighter sound imaging at the sweet
* spot with ambisonic decoding, as the distance between the ears is closer
@ -840,32 +912,32 @@ void InitHrtfPanning(ALCdevice *device)
*/
device->mRenderMode = RenderMode::Hrtf;
uint ambi_order{1};
if(auto modeopt = device->configValue<std::string>(nullptr, "hrtf-mode"))
if(auto modeopt = device->configValue<std::string>({}, "hrtf-mode"))
{
struct HrtfModeEntry {
char name[8];
std::string_view name;
RenderMode mode;
uint order;
};
static const HrtfModeEntry hrtf_modes[]{
{ "full", RenderMode::Hrtf, 1 },
{ "ambi1", RenderMode::Normal, 1 },
{ "ambi2", RenderMode::Normal, 2 },
{ "ambi3", RenderMode::Normal, 3 },
constexpr std::array hrtf_modes{
HrtfModeEntry{"full"sv, RenderMode::Hrtf, 1},
HrtfModeEntry{"ambi1"sv, RenderMode::Normal, 1},
HrtfModeEntry{"ambi2"sv, RenderMode::Normal, 2},
HrtfModeEntry{"ambi3"sv, RenderMode::Normal, 3},
};
const char *mode{modeopt->c_str()};
if(al::strcasecmp(mode, "basic") == 0)
std::string_view mode{*modeopt};
if(al::case_compare(mode, "basic"sv) == 0)
{
ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", mode, "ambi2");
ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", modeopt->c_str(), "ambi2");
mode = "ambi2";
}
auto match_entry = [mode](const HrtfModeEntry &entry) -> bool
{ return al::strcasecmp(mode, entry.name) == 0; };
auto iter = std::find_if(std::begin(hrtf_modes), std::end(hrtf_modes), match_entry);
if(iter == std::end(hrtf_modes))
ERR("Unexpected hrtf-mode: %s\n", mode);
{ return al::case_compare(mode, entry.name) == 0; };
auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry);
if(iter == hrtf_modes.end())
ERR("Unexpected hrtf-mode: %s\n", modeopt->c_str());
else
{
device->mRenderMode = iter->mode;
@ -873,17 +945,13 @@ void InitHrtfPanning(ALCdevice *device)
}
}
TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order,
(((ambi_order%100)/10) == 1) ? "th" :
((ambi_order%10) == 1) ? "st" :
((ambi_order%10) == 2) ? "nd" :
((ambi_order%10) == 3) ? "rd" : "th",
(device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "",
GetCounterSuffix(ambi_order), (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};
auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0);
auto AmbiMatrix = al::span{AmbiMatrix1O}.subspan(0);
auto AmbiOrderHFGain = al::span{AmbiOrderHFGain1O};
if(ambi_order >= 3)
{
perHrirMin = true;
@ -901,10 +969,9 @@ void InitHrtfPanning(ALCdevice *device)
device->m2DMixing = false;
const size_t count{AmbiChannelsFromOrder(ambi_order)};
std::transform(AmbiIndex::FromACN().begin(), AmbiIndex::FromACN().begin()+count,
std::begin(device->Dry.AmbiMap),
[](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }
);
const auto acnmap = al::span{AmbiIndex::FromACN}.first(count);
std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(),
[](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; });
AllocChannels(device, count, device->channelsFromFmt());
HrtfStore *Hrtf{device->mHrtf.get()};
@ -919,21 +986,21 @@ void InitHrtfPanning(ALCdevice *device)
void InitUhjPanning(ALCdevice *device)
{
/* UHJ is always 2D first-order. */
constexpr size_t count{Ambi2DChannelsFromOrder(1)};
static constexpr size_t count{Ambi2DChannelsFromOrder(1)};
device->mAmbiOrder = 1;
device->m2DMixing = true;
auto acnmap_begin = AmbiIndex::FromFuMa2D().begin();
std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap),
const auto acnmap = al::span{AmbiIndex::FromFuMa2D}.first<count>();
std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(),
[](const uint8_t &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; });
{ return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; });
AllocChannels(device, count, device->channelsFromFmt());
}
} // namespace
void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode)
void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode)
{
/* Hold the HRTF the device last used, in case it's used again. */
HrtfStorePtr old_hrtf{std::move(device->mHrtf)};
@ -960,6 +1027,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
case DevFmtX61: layout = "surround61"; break;
case DevFmtX71: layout = "surround71"; break;
case DevFmtX714: layout = "surround714"; break;
case DevFmtX7144: layout = "surround7144"; break;
case DevFmtX3D71: layout = "surround3d71"; break;
/* Mono, Stereo, and Ambisonics output don't use custom decoders. */
case DevFmtMono:
@ -968,9 +1036,9 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
break;
}
std::unique_ptr<DecoderConfig<DualBand,MAX_OUTPUT_CHANNELS>> decoder_store;
std::unique_ptr<DecoderConfig<DualBand,MaxOutputChannels>> decoder_store;
DecoderView decoder{};
float speakerdists[MAX_OUTPUT_CHANNELS]{};
std::array<float,MaxOutputChannels> speakerdists{};
auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config)
{
AmbDecConf conf{};
@ -978,28 +1046,42 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
{
ERR("Failed to load layout file %s\n", config);
ERR(" %s\n", err->c_str());
return false;
}
else if(conf.NumSpeakers > MAX_OUTPUT_CHANNELS)
ERR("Unsupported decoder speaker count %zu (max %d)\n", conf.NumSpeakers,
MAX_OUTPUT_CHANNELS);
else if(conf.ChanMask > Ambi3OrderMask)
if(conf.Speakers.size() > MaxOutputChannels)
{
ERR("Unsupported decoder speaker count %zu (max %zu)\n", conf.Speakers.size(),
MaxOutputChannels);
return false;
}
if(conf.ChanMask > Ambi3OrderMask)
{
ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask,
Ambi3OrderMask);
else
{
device->mXOverFreq = clampf(conf.XOverFreq, 100.0f, 1000.0f);
decoder_store = std::make_unique<DecoderConfig<DualBand,MAX_OUTPUT_CHANNELS>>();
decoder = MakeDecoderView(device, &conf, *decoder_store);
for(size_t i{0};i < decoder.mChannels.size();++i)
speakerdists[i] = conf.Speakers[i].Distance;
return false;
}
TRACE("Using %s decoder: \"%s\"\n", DevFmtChannelsString(device->FmtChans),
conf.Description.c_str());
device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f);
decoder_store = std::make_unique<DecoderConfig<DualBand,MaxOutputChannels>>();
decoder = MakeDecoderView(device, &conf, *decoder_store);
const auto confspeakers = al::span{std::as_const(conf.Speakers)}
.first(decoder.mChannels.size());
std::transform(confspeakers.cbegin(), confspeakers.cend(), speakerdists.begin(),
std::mem_fn(&AmbDecConf::SpeakerConf::Distance));
return true;
};
bool usingCustom{false};
if(layout)
{
if(auto decopt = device->configValue<std::string>("decoder", layout))
load_config(decopt->c_str());
usingCustom = load_config(decopt->c_str());
}
if(!usingCustom && device->FmtChans != DevFmtAmbi3D)
TRACE("Using built-in %s decoder\n", DevFmtChannelsString(device->FmtChans));
/* Enable the stablizer only for formats that have front-left, front-
* right, and front-center outputs.
@ -1007,8 +1089,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
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};
&& device->getConfigValueBool({}, "front-stablizer", false)};
const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true)};
InitPanning(device, hqdec, stablize, decoder);
if(decoder)
{
@ -1049,7 +1131,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
if(hrtf_id >= 0 && static_cast<uint>(hrtf_id) < device->mHrtfList.size())
{
const std::string &hrtfname = device->mHrtfList[static_cast<uint>(hrtf_id)];
const std::string_view hrtfname{device->mHrtfList[static_cast<uint>(hrtf_id)]};
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)})
{
device->mHrtf = std::move(hrtf);
@ -1059,7 +1141,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
if(!device->mHrtf)
{
for(const auto &hrtfname : device->mHrtfList)
for(const std::string_view hrtfname : device->mHrtfList)
{
if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)})
{
@ -1076,10 +1158,10 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
HrtfStore *hrtf{device->mHrtf.get()};
device->mIrSize = hrtf->mIrSize;
if(auto hrtfsizeopt = device->configValue<uint>(nullptr, "hrtf-size"))
if(auto hrtfsizeopt = device->configValue<uint>({}, "hrtf-size"))
{
if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize)
device->mIrSize = maxu(*hrtfsizeopt, MinIrLength);
device->mIrSize = std::max(*hrtfsizeopt, MinIrLength);
}
InitHrtfPanning(device);
@ -1115,13 +1197,13 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
device->mRenderMode = RenderMode::Pairwise;
if(device->Type != DeviceType::Loopback)
{
if(auto cflevopt = device->configValue<int>(nullptr, "cf_level"))
if(auto cflevopt = device->configValue<int>({}, "cf_level"))
{
if(*cflevopt > 0 && *cflevopt <= 6)
{
device->Bs2b = std::make_unique<bs2b>();
bs2b_set_params(device->Bs2b.get(), *cflevopt,
static_cast<int>(device->Frequency));
auto bs2b = std::make_unique<Bs2b::bs2b>();
bs2b->set_params(*cflevopt, static_cast<int>(device->Frequency));
device->Bs2b = std::move(bs2b);
TRACE("BS2B enabled\n");
InitPanning(device);
device->PostProcess = &ALCdevice::ProcessBs2b;
@ -1143,10 +1225,9 @@ void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context)
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}; });
const auto acnmap = al::span{AmbiIndex::FromACN}.first(count);
const auto iter = std::transform(acnmap.cbegin(), acnmap.cend(), 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 = slot->mWetBuffer;
}