mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-15 16:44:36 +00:00
update openal
This commit is contained in:
parent
62f3b93ff9
commit
6721a6b021
287 changed files with 33851 additions and 27325 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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}}; }
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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, ¶ms, 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, ¶ms, 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)
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
95
Engine/lib/openal-soft/alc/events.cpp
Normal file
95
Engine/lib/openal-soft/alc/events.cpp
Normal 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;
|
||||
}
|
||||
50
Engine/lib/openal-soft/alc/events.h
Normal file
50
Engine/lib/openal-soft/alc/events.h
Normal 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 */
|
||||
917
Engine/lib/openal-soft/alc/export_list.h
Normal file
917
Engine/lib/openal-soft/alc/export_list.h
Normal 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 */
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue