openal-soft/Alc/panning.cpp

1208 lines
46 KiB
C++
Raw Normal View History

/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2010 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
2018-12-09 17:24:00 -08:00
#include <numeric>
2018-12-08 14:22:20 -08:00
#include <algorithm>
#include "alMain.h"
#include "alAuxEffectSlot.h"
#include "alu.h"
#include "alconfig.h"
#include "ambdec.h"
#include "bformatdec.h"
#include "filters/splitter.h"
#include "uhjfilter.h"
#include "bs2b.h"
namespace {
constexpr ALsizei FuMa2ACN[MAX_AMBI_COEFFS] = {
0, /* W */
3, /* X */
1, /* Y */
2, /* Z */
6, /* R */
7, /* S */
5, /* T */
8, /* U */
4, /* V */
12, /* K */
13, /* L */
11, /* M */
14, /* N */
10, /* O */
15, /* P */
9, /* Q */
};
constexpr ALsizei ACN2ACN[MAX_AMBI_COEFFS] = {
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15
};
} // namespace
void CalcAmbiCoeffs(const ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread,
ALfloat (&coeffs)[MAX_AMBI_COEFFS])
Use an ambisonics-based panning method For mono sources, third-order ambisonics is utilized to generate panning gains. The general idea is that a panned mono sound can be encoded into b-format ambisonics as: w[i] = sample[i] * 0.7071; x[i] = sample[i] * dir[0]; y[i] = sample[i] * dir[1]; ... and subsequently rendered using: output[chan][i] = w[i] * w_coeffs[chan] + x[i] * x_coeffs[chan] + y[i] * y_coeffs[chan] + ...; By reordering the math, channel gains can be generated by doing: gain[chan] = 0.7071 * w_coeffs[chan] + dir[0] * x_coeffs[chan] + dir[1] * y_coeffs[chan] + ...; which then get applied as normal: output[chan][i] = sample[i] * gain[chan]; One of the reasons to use ambisonics for panning is that it provides arguably better reproduction for sounds emanating from between two speakers. As well, this makes it easier to pan in all 3 dimensions, with for instance a "3D7.1" or 8-channel cube speaker configuration by simply providing the necessary coefficients (this will need some work since some methods still use angle-based panpot, particularly multi-channel sources). Unfortunately, the math to reliably generate the coefficients for a given speaker configuration is too costly to do at run-time. They have to be pre- generated based on a pre-specified speaker arangement, which means the config options for tweaking speaker angles are no longer supportable. Eventually I hope to provide config options for custom coefficients, which can either be generated and written in manually, or via alsoft-config from user-specified speaker positions. The current default set of coefficients were generated using the MATLAB scripts (compatible with GNU Octave) from the excellent Ambisonic Decoder Toolbox, at https://bitbucket.org/ambidecodertoolbox/adt/
2014-09-30 07:33:13 -07:00
{
/* Zeroth-order */
2015-09-23 15:03:53 -07:00
coeffs[0] = 1.0f; /* ACN 0 = 1 */
/* First-order */
coeffs[1] = SQRTF_3 * y; /* ACN 1 = sqrt(3) * Y */
coeffs[2] = SQRTF_3 * z; /* ACN 2 = sqrt(3) * Z */
coeffs[3] = SQRTF_3 * x; /* ACN 3 = sqrt(3) * X */
/* Second-order */
coeffs[4] = 3.872983346f * x * y; /* ACN 4 = sqrt(15) * X * Y */
coeffs[5] = 3.872983346f * y * z; /* ACN 5 = sqrt(15) * Y * Z */
coeffs[6] = 1.118033989f * (3.0f*z*z - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */
coeffs[7] = 3.872983346f * x * z; /* ACN 7 = sqrt(15) * X * Z */
coeffs[8] = 1.936491673f * (x*x - y*y); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */
/* Third-order */
coeffs[9] = 2.091650066f * y * (3.0f*x*x - y*y); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */
coeffs[10] = 10.246950766f * z * x * y; /* ACN 10 = sqrt(105) * Z * X * Y */
coeffs[11] = 1.620185175f * y * (5.0f*z*z - 1.0f); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */
coeffs[12] = 1.322875656f * z * (5.0f*z*z - 3.0f); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */
coeffs[13] = 1.620185175f * x * (5.0f*z*z - 1.0f); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */
coeffs[14] = 5.123475383f * z * (x*x - y*y); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */
coeffs[15] = 2.091650066f * x * (x*x - 3.0f*y*y); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */
if(spread > 0.0f)
{
/* Implement the spread by using a spherical source that subtends the
* angle spread. See:
* http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3
*
* When adjusted for N3D normalization instead of SN3D, these
* calculations are:
*
* ZH0 = -sqrt(pi) * (-1+ca);
* ZH1 = 0.5*sqrt(pi) * sa*sa;
* ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1);
* ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1);
* ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3);
* ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1);
*
* The gain of the source is compensated for size, so that the
* loundness doesn't depend on the spread. Thus:
*
* ZH0 = 1.0f;
* ZH1 = 0.5f * (ca+1.0f);
* ZH2 = 0.5f * (ca+1.0f)*ca;
* ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f);
* ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca;
* ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f);
*/
ALfloat ca = cosf(spread * 0.5f);
/* Increase the source volume by up to +3dB for a full spread. */
ALfloat scale = sqrtf(1.0f + spread/F_TAU);
ALfloat ZH0_norm = scale;
ALfloat ZH1_norm = 0.5f * (ca+1.f) * scale;
ALfloat ZH2_norm = 0.5f * (ca+1.f)*ca * scale;
ALfloat ZH3_norm = 0.125f * (ca+1.f)*(5.f*ca*ca-1.f) * scale;
/* Zeroth-order */
coeffs[0] *= ZH0_norm;
/* First-order */
coeffs[1] *= ZH1_norm;
coeffs[2] *= ZH1_norm;
coeffs[3] *= ZH1_norm;
/* Second-order */
coeffs[4] *= ZH2_norm;
coeffs[5] *= ZH2_norm;
coeffs[6] *= ZH2_norm;
coeffs[7] *= ZH2_norm;
coeffs[8] *= ZH2_norm;
/* Third-order */
coeffs[9] *= ZH3_norm;
coeffs[10] *= ZH3_norm;
coeffs[11] *= ZH3_norm;
coeffs[12] *= ZH3_norm;
coeffs[13] *= ZH3_norm;
coeffs[14] *= ZH3_norm;
coeffs[15] *= ZH3_norm;
}
}
2018-12-08 14:22:20 -08:00
void ComputePanningGainsMC(const ChannelConfig *chancoeffs, ALsizei numchans, ALsizei numcoeffs, const ALfloat*RESTRICT coeffs, ALfloat ingain, ALfloat (&gains)[MAX_OUTPUT_CHANNELS])
{
2018-12-09 17:24:00 -08:00
ASSUME(numchans > 0);
auto iter = std::transform(chancoeffs, chancoeffs+numchans, std::begin(gains),
[numcoeffs,coeffs,ingain](const ChannelConfig &chancoeffs) -> ALfloat
{
ASSUME(numcoeffs > 0);
float gain{std::inner_product(std::begin(chancoeffs), std::begin(chancoeffs)+numcoeffs,
coeffs, float{0.0f})};
return clampf(gain, 0.0f, 1.0f) * ingain;
}
);
std::fill(iter, std::end(gains), 0.0f);
}
2018-12-08 14:22:20 -08:00
void ComputePanningGainsBF(const BFChannelConfig *chanmap, ALsizei numchans, const ALfloat*RESTRICT coeffs, ALfloat ingain, ALfloat (&gains)[MAX_OUTPUT_CHANNELS])
{
2018-12-09 17:24:00 -08:00
ASSUME(numchans > 0);
auto iter = std::transform(chanmap, chanmap+numchans, std::begin(gains),
[coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> ALfloat
{
ASSUME(chanmap.Index >= 0);
return chanmap.Scale * coeffs[chanmap.Index] * ingain;
}
);
std::fill(iter, std::end(gains), 0.0f);
}
static inline const char *GetLabelFromChannel(enum Channel channel)
{
switch(channel)
{
case FrontLeft: return "front-left";
case FrontRight: return "front-right";
case FrontCenter: return "front-center";
case LFE: return "lfe";
case BackLeft: return "back-left";
case BackRight: return "back-right";
case BackCenter: return "back-center";
case SideLeft: return "side-left";
case SideRight: return "side-right";
case UpperFrontLeft: return "upper-front-left";
case UpperFrontRight: return "upper-front-right";
case UpperBackLeft: return "upper-back-left";
case UpperBackRight: return "upper-back-right";
case LowerFrontLeft: return "lower-front-left";
case LowerFrontRight: return "lower-front-right";
case LowerBackLeft: return "lower-back-left";
case LowerBackRight: return "lower-back-right";
2016-03-16 06:49:35 -07:00
case Aux0: return "aux-0";
case Aux1: return "aux-1";
case Aux2: return "aux-2";
case Aux3: return "aux-3";
case Aux4: return "aux-4";
case Aux5: return "aux-5";
case Aux6: return "aux-6";
case Aux7: return "aux-7";
case Aux8: return "aux-8";
case Aux9: return "aux-9";
case Aux10: return "aux-10";
case Aux11: return "aux-11";
case Aux12: return "aux-12";
case Aux13: return "aux-13";
case Aux14: return "aux-14";
case Aux15: return "aux-15";
case InvalidChannel: break;
}
return "(unknown)";
}
2018-11-22 22:53:22 -08:00
struct ChannelMap {
Channel ChanName;
ChannelConfig Config;
2018-11-22 22:53:22 -08:00
};
2018-11-22 22:53:22 -08:00
static void SetChannelMap(const Channel (&devchans)[MAX_OUTPUT_CHANNELS],
ChannelConfig *ambicoeffs, const ChannelMap *chanmap,
ALsizei count, ALsizei *outcount)
{
auto copy_coeffs = [&devchans,ambicoeffs](ALsizei maxchans, const ChannelMap &channel) -> ALsizei
{
const ALint idx{GetChannelIndex(devchans, channel.ChanName)};
if(idx < 0)
{
ERR("Failed to find %s channel in device\n", GetLabelFromChannel(channel.ChanName));
return maxchans;
2018-11-22 22:53:22 -08:00
}
std::copy(std::begin(channel.Config), std::end(channel.Config), ambicoeffs[idx]);
return maxi(maxchans, idx+1);
};
ALsizei maxcount{std::accumulate(chanmap, chanmap+count, ALsizei{0}, copy_coeffs)};
*outcount = mini(maxcount, MAX_OUTPUT_CHANNELS);
}
2017-01-29 16:42:02 -08:00
static bool MakeSpeakerMap(ALCdevice *device, const AmbDecConf *conf, ALsizei speakermap[MAX_OUTPUT_CHANNELS])
{
ALsizei i;
for(i = 0;i < conf->NumSpeakers;i++)
{
enum Channel ch;
int chidx = -1;
/* NOTE: AmbDec does not define any standard speaker names, however
* for this to work we have to by able to find the output channel
* the speaker definition corresponds to. Therefore, OpenAL Soft
* requires these channel labels to be recognized:
*
* LF = Front left
* RF = Front right
* LS = Side left
* RS = Side right
* LB = Back left
* RB = Back right
* CE = Front center
* CB = Back center
*
* Additionally, surround51 will acknowledge back speakers for side
* channels, and surround51rear will acknowledge side speakers for
* back channels, to avoid issues with an ambdec expecting 5.1 to
* use the side channels when the device is configured for back,
* and vice-versa.
*/
2018-11-03 19:51:23 -07:00
if(conf->Speakers[i].Name == "LF")
ch = FrontLeft;
2018-11-03 19:51:23 -07:00
else if(conf->Speakers[i].Name == "RF")
ch = FrontRight;
2018-11-03 19:51:23 -07:00
else if(conf->Speakers[i].Name == "CE")
ch = FrontCenter;
2018-11-03 19:51:23 -07:00
else if(conf->Speakers[i].Name == "LS")
{
if(device->FmtChans == DevFmtX51Rear)
ch = BackLeft;
else
ch = SideLeft;
}
2018-11-03 19:51:23 -07:00
else if(conf->Speakers[i].Name == "RS")
{
if(device->FmtChans == DevFmtX51Rear)
ch = BackRight;
else
ch = SideRight;
}
2018-11-03 19:51:23 -07:00
else if(conf->Speakers[i].Name == "LB")
{
if(device->FmtChans == DevFmtX51)
ch = SideLeft;
else
ch = BackLeft;
}
2018-11-03 19:51:23 -07:00
else if(conf->Speakers[i].Name == "RB")
{
if(device->FmtChans == DevFmtX51)
ch = SideRight;
else
ch = BackRight;
}
2018-11-03 19:51:23 -07:00
else if(conf->Speakers[i].Name == "CB")
ch = BackCenter;
else
{
2018-11-03 19:51:23 -07:00
const char *name = conf->Speakers[i].Name.c_str();
unsigned int n;
char c;
if(sscanf(name, "AUX%u%c", &n, &c) == 1 && n < 16)
2018-11-03 19:02:11 -07:00
ch = static_cast<enum Channel>(Aux0+n);
else
{
ERR("AmbDec speaker label \"%s\" not recognized\n", name);
return false;
}
}
chidx = GetChannelIdxByName(&device->RealOut, ch);
if(chidx == -1)
{
ERR("Failed to lookup AmbDec speaker label %s\n",
2018-11-03 19:51:23 -07:00
conf->Speakers[i].Name.c_str());
return false;
}
speakermap[i] = chidx;
}
return true;
}
static const ChannelMap MonoCfg[1] = {
{ FrontCenter, { 1.0f } },
}, StereoCfg[2] = {
{ FrontLeft, { 5.00000000e-1f, 2.88675135e-1f, 0.0f, 5.52305643e-2f } },
{ FrontRight, { 5.00000000e-1f, -2.88675135e-1f, 0.0f, 5.52305643e-2f } },
}, QuadCfg[4] = {
{ BackLeft, { 3.53553391e-1f, 2.04124145e-1f, 0.0f, -2.04124145e-1f } },
{ FrontLeft, { 3.53553391e-1f, 2.04124145e-1f, 0.0f, 2.04124145e-1f } },
{ FrontRight, { 3.53553391e-1f, -2.04124145e-1f, 0.0f, 2.04124145e-1f } },
{ BackRight, { 3.53553391e-1f, -2.04124145e-1f, 0.0f, -2.04124145e-1f } },
}, X51SideCfg[4] = {
{ SideLeft, { 3.33000782e-1f, 1.89084803e-1f, 0.0f, -2.00042375e-1f, -2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
{ FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 0.0f, 1.66295695e-1f, 7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
{ FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 0.0f, 1.66295695e-1f, -7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
{ SideRight, { 3.33000782e-1f, -1.89084803e-1f, 0.0f, -2.00042375e-1f, 2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
}, X51RearCfg[4] = {
{ BackLeft, { 3.33000782e-1f, 1.89084803e-1f, 0.0f, -2.00042375e-1f, -2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
{ FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 0.0f, 1.66295695e-1f, 7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
{ FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 0.0f, 1.66295695e-1f, -7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
{ BackRight, { 3.33000782e-1f, -1.89084803e-1f, 0.0f, -2.00042375e-1f, 2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
}, X61Cfg[6] = {
{ SideLeft, { 2.04460341e-1f, 2.17177926e-1f, 0.0f, -4.39996780e-2f, -2.60790269e-2f, 0.0f, 0.0f, 0.0f, -6.87239792e-2f } },
{ FrontLeft, { 1.58923161e-1f, 9.21772680e-2f, 0.0f, 1.59658796e-1f, 6.66278083e-2f, 0.0f, 0.0f, 0.0f, 3.84686854e-2f } },
{ FrontRight, { 1.58923161e-1f, -9.21772680e-2f, 0.0f, 1.59658796e-1f, -6.66278083e-2f, 0.0f, 0.0f, 0.0f, 3.84686854e-2f } },
{ SideRight, { 2.04460341e-1f, -2.17177926e-1f, 0.0f, -4.39996780e-2f, 2.60790269e-2f, 0.0f, 0.0f, 0.0f, -6.87239792e-2f } },
{ BackCenter, { 2.50001688e-1f, 0.00000000e+0f, 0.0f, -2.50000094e-1f, 0.00000000e+0f, 0.0f, 0.0f, 0.0f, 6.05133395e-2f } },
}, X71Cfg[6] = {
{ BackLeft, { 2.04124145e-1f, 1.08880247e-1f, 0.0f, -1.88586120e-1f, -1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, 3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
{ SideLeft, { 2.04124145e-1f, 2.17760495e-1f, 0.0f, 0.00000000e+0f, 0.00000000e+0f, 0.0f, 0.0f, 0.0f, -1.49071198e-1f, -3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
{ FrontLeft, { 2.04124145e-1f, 1.08880247e-1f, 0.0f, 1.88586120e-1f, 1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, 3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
{ FrontRight, { 2.04124145e-1f, -1.08880247e-1f, 0.0f, 1.88586120e-1f, -1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, -3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
{ SideRight, { 2.04124145e-1f, -2.17760495e-1f, 0.0f, 0.00000000e+0f, 0.00000000e+0f, 0.0f, 0.0f, 0.0f, -1.49071198e-1f, 3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
{ BackRight, { 2.04124145e-1f, -1.08880247e-1f, 0.0f, -1.88586120e-1f, 1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, -3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
};
static void InitNearFieldCtrl(ALCdevice *device, ALfloat ctrl_dist, ALsizei order,
2018-10-29 11:32:50 -07:00
const ALsizei *RESTRICT chans_per_order)
{
2018-11-18 18:45:45 -08:00
const char *devname = device->DeviceName.c_str();
ALsizei i;
if(GetConfigValueBool(devname, "decoder", "nfc", 1) && ctrl_dist > 0.0f)
{
/* NFC is only used when AvgSpeakerDist is greater than 0, and can only
* be used when rendering to an ambisonic buffer.
*/
device->AvgSpeakerDist = minf(ctrl_dist, 10.0f);
TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist);
for(i = 0;i < order+1;i++)
2018-09-19 21:31:46 -07:00
device->NumChannelsPerOrder[i] = chans_per_order[i];
for(;i < MAX_AMBI_ORDER+1;i++)
2018-09-19 21:31:46 -07:00
device->NumChannelsPerOrder[i] = 0;
}
}
static void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS])
{
2018-11-18 18:45:45 -08:00
const char *devname = device->DeviceName.c_str();
ALfloat maxdist = 0.0f;
2018-01-11 10:55:35 -08:00
size_t total = 0;
ALsizei i;
for(i = 0;i < conf->NumSpeakers;i++)
maxdist = maxf(maxdist, conf->Speakers[i].Distance);
if(GetConfigValueBool(devname, "decoder", "distance-comp", 1) && maxdist > 0.0f)
{
ALfloat srate = (ALfloat)device->Frequency;
for(i = 0;i < conf->NumSpeakers;i++)
{
ALsizei chan = speakermap[i];
ALfloat delay;
/* Distance compensation only delays in steps of the sample rate.
* This is a bit less accurate since the delay time falls to the
* nearest sample time, but it's far simpler as it doesn't have to
* deal with phase offsets. This means at 48khz, for instance, the
* distance delay will be in steps of about 7 millimeters.
*/
delay = floorf((maxdist-conf->Speakers[i].Distance) / SPEEDOFSOUNDMETRESPERSEC *
srate + 0.5f);
if(delay >= (ALfloat)MAX_DELAY_LENGTH)
ERR("Delay for speaker \"%s\" exceeds buffer length (%f >= %u)\n",
2018-11-03 19:51:23 -07:00
conf->Speakers[i].Name.c_str(), delay, MAX_DELAY_LENGTH);
device->ChannelDelay[chan].Length = (ALsizei)clampf(
delay, 0.0f, (ALfloat)(MAX_DELAY_LENGTH-1)
);
device->ChannelDelay[chan].Gain = conf->Speakers[i].Distance / maxdist;
TRACE("Channel %u \"%s\" distance compensation: %d samples, %f gain\n", chan,
2018-11-03 19:51:23 -07:00
conf->Speakers[i].Name.c_str(), device->ChannelDelay[chan].Length,
device->ChannelDelay[chan].Gain
);
/* Round up to the next 4th sample, so each channel buffer starts
* 16-byte aligned.
*/
total += RoundUp(device->ChannelDelay[chan].Length, 4);
}
}
if(total > 0)
{
device->ChannelDelay.resize(total);
device->ChannelDelay[0].Buffer = device->ChannelDelay.data();
for(i = 1;i < MAX_OUTPUT_CHANNELS;i++)
{
size_t len = RoundUp(device->ChannelDelay[i-1].Length, 4);
device->ChannelDelay[i].Buffer = device->ChannelDelay[i-1].Buffer + len;
}
}
}
static void InitPanning(ALCdevice *device)
{
const ChannelMap *chanmap = NULL;
ALsizei coeffcount = 0;
ALsizei count = 0;
ALsizei i, j;
2014-09-10 17:53:01 -07:00
switch(device->FmtChans)
{
case DevFmtMono:
count = COUNTOF(MonoCfg);
chanmap = MonoCfg;
coeffcount = 1;
break;
case DevFmtStereo:
count = COUNTOF(StereoCfg);
chanmap = StereoCfg;
coeffcount = 4;
break;
case DevFmtQuad:
count = COUNTOF(QuadCfg);
chanmap = QuadCfg;
coeffcount = 4;
break;
case DevFmtX51:
count = COUNTOF(X51SideCfg);
chanmap = X51SideCfg;
coeffcount = 9;
break;
case DevFmtX51Rear:
count = COUNTOF(X51RearCfg);
chanmap = X51RearCfg;
coeffcount = 9;
break;
case DevFmtX61:
count = COUNTOF(X61Cfg);
chanmap = X61Cfg;
coeffcount = 9;
break;
case DevFmtX71:
count = COUNTOF(X71Cfg);
chanmap = X71Cfg;
coeffcount = 16;
break;
case DevFmtAmbi3D:
break;
}
if(device->FmtChans == DevFmtAmbi3D)
{
2018-11-18 18:45:45 -08:00
const char *devname = device->DeviceName.c_str();
const ALsizei *acnmap = (device->mAmbiLayout == AmbiLayout::FuMa) ? FuMa2ACN : ACN2ACN;
const ALfloat *n3dscale = (device->mAmbiScale == AmbiNorm::FuMa) ? FuMa2N3DScale :
(device->mAmbiScale == AmbiNorm::SN3D) ? SN3D2N3DScale :
/*(device->mAmbiScale == AmbiNorm::N3D) ?*/ N3D2N3DScale;
ALfloat nfc_delay = 0.0f;
count = (device->mAmbiOrder == 3) ? 16 :
(device->mAmbiOrder == 2) ? 9 :
(device->mAmbiOrder == 1) ? 4 : 1;
for(i = 0;i < count;i++)
{
ALsizei acn = acnmap[i];
device->Dry.Ambi.Map[i].Scale = 1.0f/n3dscale[acn];
device->Dry.Ambi.Map[i].Index = acn;
}
device->Dry.CoeffCount = 0;
device->Dry.NumChannels = count;
if(device->mAmbiOrder < 2)
{
device->FOAOut.Ambi = device->Dry.Ambi;
device->FOAOut.CoeffCount = device->Dry.CoeffCount;
device->FOAOut.NumChannels = 0;
}
else
{
ALfloat w_scale=1.0f, xyz_scale=1.0f;
/* FOA output is always ACN+N3D for higher-order ambisonic output.
* The upsampler expects this and will convert it for output.
*/
2018-12-08 14:22:20 -08:00
device->FOAOut.Ambi = AmbiConfig{};
for(i = 0;i < 4;i++)
{
device->FOAOut.Ambi.Map[i].Scale = 1.0f;
device->FOAOut.Ambi.Map[i].Index = i;
}
device->FOAOut.CoeffCount = 0;
device->FOAOut.NumChannels = 4;
if(device->mAmbiOrder >= 3)
{
w_scale = W_SCALE_3H3P;
xyz_scale = XYZ_SCALE_3H3P;
}
else
{
w_scale = W_SCALE_2H2P;
xyz_scale = XYZ_SCALE_2H2P;
}
device->AmbiUp->reset(device, w_scale, xyz_scale);
}
if(ConfigValueFloat(devname, "decoder", "nfc-ref-delay", &nfc_delay) && nfc_delay > 0.0f)
{
static const ALsizei chans_per_order[MAX_AMBI_ORDER+1] = {
1, 3, 5, 7
};
nfc_delay = clampf(nfc_delay, 0.001f, 1000.0f);
InitNearFieldCtrl(device, nfc_delay * SPEEDOFSOUNDMETRESPERSEC,
device->mAmbiOrder, chans_per_order);
}
}
else
{
ALfloat w_scale, xyz_scale;
SetChannelMap(device->RealOut.ChannelName, device->Dry.Ambi.Coeffs,
chanmap, count, &device->Dry.NumChannels);
device->Dry.CoeffCount = coeffcount;
w_scale = (device->Dry.CoeffCount > 9) ? W_SCALE_3H0P :
(device->Dry.CoeffCount > 4) ? W_SCALE_2H0P : 1.0f;
xyz_scale = (device->Dry.CoeffCount > 9) ? XYZ_SCALE_3H0P :
(device->Dry.CoeffCount > 4) ? XYZ_SCALE_2H0P : 1.0f;
2018-12-08 14:22:20 -08:00
device->FOAOut.Ambi = AmbiConfig{};
for(i = 0;i < device->Dry.NumChannels;i++)
{
device->FOAOut.Ambi.Coeffs[i][0] = device->Dry.Ambi.Coeffs[i][0] * w_scale;
for(j = 1;j < 4;j++)
device->FOAOut.Ambi.Coeffs[i][j] = device->Dry.Ambi.Coeffs[i][j] * xyz_scale;
}
device->FOAOut.CoeffCount = 4;
device->FOAOut.NumChannels = 0;
}
device->RealOut.NumChannels = 0;
}
2018-12-08 01:32:43 -08:00
static void InitCustomPanning(ALCdevice *device, const AmbDecConf *conf, const ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS])
{
ChannelMap chanmap[MAX_OUTPUT_CHANNELS];
2018-02-13 13:47:35 -08:00
const ALfloat *coeff_scale = N3D2N3DScale;
ALfloat w_scale = 1.0f;
ALfloat xyz_scale = 1.0f;
ALsizei i, j;
if(conf->FreqBands != 1)
ERR("Basic renderer uses the high-frequency matrix as single-band (xover_freq = %.0fhz)\n",
conf->XOverFreq);
if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
{
if(conf->ChanMask > 0x1ff)
{
w_scale = W_SCALE_3H3P;
xyz_scale = XYZ_SCALE_3H3P;
}
else if(conf->ChanMask > 0xf)
{
w_scale = W_SCALE_2H2P;
xyz_scale = XYZ_SCALE_2H2P;
}
}
else
{
if(conf->ChanMask > 0x1ff)
{
w_scale = W_SCALE_3H0P;
xyz_scale = XYZ_SCALE_3H0P;
}
else if(conf->ChanMask > 0xf)
{
w_scale = W_SCALE_2H0P;
xyz_scale = XYZ_SCALE_2H0P;
}
}
2018-11-03 19:51:23 -07:00
if(conf->CoeffScale == AmbDecScale::SN3D)
coeff_scale = SN3D2N3DScale;
2018-11-03 19:51:23 -07:00
else if(conf->CoeffScale == AmbDecScale::FuMa)
coeff_scale = FuMa2N3DScale;
for(i = 0;i < conf->NumSpeakers;i++)
{
ALsizei chan = speakermap[i];
ALfloat gain;
ALsizei k = 0;
for(j = 0;j < MAX_AMBI_COEFFS;j++)
chanmap[i].Config[j] = 0.0f;
chanmap[i].ChanName = device->RealOut.ChannelName[chan];
for(j = 0;j < MAX_AMBI_COEFFS;j++)
{
if(j == 0) gain = conf->HFOrderGain[0];
else if(j == 1) gain = conf->HFOrderGain[1];
else if(j == 4) gain = conf->HFOrderGain[2];
else if(j == 9) gain = conf->HFOrderGain[3];
if((conf->ChanMask&(1<<j)))
chanmap[i].Config[j] = conf->HFMatrix[i][k++] / coeff_scale[j] * gain;
}
}
SetChannelMap(device->RealOut.ChannelName, device->Dry.Ambi.Coeffs, chanmap,
conf->NumSpeakers, &device->Dry.NumChannels);
device->Dry.CoeffCount = (conf->ChanMask > 0x1ff) ? 16 :
(conf->ChanMask > 0xf) ? 9 : 4;
2018-12-08 14:22:20 -08:00
device->FOAOut.Ambi = AmbiConfig{};
for(i = 0;i < device->Dry.NumChannels;i++)
{
device->FOAOut.Ambi.Coeffs[i][0] = device->Dry.Ambi.Coeffs[i][0] * w_scale;
for(j = 1;j < 4;j++)
device->FOAOut.Ambi.Coeffs[i][j] = device->Dry.Ambi.Coeffs[i][j] * xyz_scale;
}
device->FOAOut.CoeffCount = 4;
device->FOAOut.NumChannels = 0;
device->RealOut.NumChannels = 0;
InitDistanceComp(device, conf, speakermap);
}
2018-12-08 01:32:43 -08:00
static void InitHQPanning(ALCdevice *device, const AmbDecConf *conf, const ALsizei (&speakermap)[MAX_OUTPUT_CHANNELS])
{
2018-12-08 01:32:43 -08:00
static constexpr ALsizei chans_per_order2d[MAX_AMBI_ORDER+1] = { 1, 2, 2, 2 };
static constexpr ALsizei chans_per_order3d[MAX_AMBI_ORDER+1] = { 1, 3, 5, 7 };
ALsizei count;
if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
{
2018-12-09 22:34:21 -08:00
static constexpr int map[MAX_AMBI_COEFFS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
count = (conf->ChanMask > 0x1ff) ? 16 :
(conf->ChanMask > 0xf) ? 9 : 4;
2018-12-09 22:34:21 -08:00
std::transform(std::begin(map), std::begin(map)+count, std::begin(device->Dry.Ambi.Map),
[](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; }
);
}
else
{
2018-12-09 22:34:21 -08:00
static constexpr int map[MAX_AMBI2D_COEFFS] = { 0, 1, 3, 4, 8, 9, 15 };
count = (conf->ChanMask > 0x1ff) ? 7 :
(conf->ChanMask > 0xf) ? 5 : 3;
2018-12-09 22:34:21 -08:00
std::transform(std::begin(map), std::begin(map)+count, std::begin(device->Dry.Ambi.Map),
[](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; }
);
}
device->Dry.CoeffCount = 0;
device->Dry.NumChannels = count;
TRACE("Enabling %s-band %s-order%s ambisonic decoder\n",
(conf->FreqBands == 1) ? "single" : "dual",
(conf->ChanMask > 0xf) ? (conf->ChanMask > 0x1ff) ? "third" : "second" : "first",
(conf->ChanMask&AMBI_PERIPHONIC_MASK) ? " periphonic" : ""
);
device->AmbiDecoder->reset(conf, count, device->Frequency, speakermap);
2018-10-29 02:05:45 +01:00
if(conf->ChanMask <= 0xf)
{
device->FOAOut.Ambi = device->Dry.Ambi;
device->FOAOut.CoeffCount = device->Dry.CoeffCount;
device->FOAOut.NumChannels = 0;
}
else
{
2018-12-08 14:22:20 -08:00
device->FOAOut.Ambi = AmbiConfig{};
if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
{
2018-12-09 22:34:21 -08:00
static constexpr int map[4] = { 0, 1, 2, 3 };
count = 4;
2018-12-09 22:34:21 -08:00
std::transform(std::begin(map), std::begin(map)+count, std::begin(device->FOAOut.Ambi.Map),
[](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; }
);
}
else
{
2018-12-09 22:34:21 -08:00
static constexpr int map[3] = { 0, 1, 3 };
count = 3;
2018-12-09 22:34:21 -08:00
std::transform(std::begin(map), std::begin(map)+count, std::begin(device->FOAOut.Ambi.Map),
[](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; }
);
}
device->FOAOut.CoeffCount = 0;
device->FOAOut.NumChannels = count;
}
device->RealOut.NumChannels = ChannelsFromDevFmt(device->FmtChans, device->mAmbiOrder);
2018-12-09 22:34:21 -08:00
using namespace std::placeholders;
auto accum_spkr_dist = std::bind(
std::plus<float>{}, _1, std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2)
);
const ALfloat avg_dist{
std::accumulate(std::begin(conf->Speakers), std::begin(conf->Speakers)+conf->NumSpeakers,
float{0.0f}, accum_spkr_dist) / (ALfloat)conf->NumSpeakers
};
InitNearFieldCtrl(device, avg_dist,
(conf->ChanMask > 0x1ff) ? 3 : (conf->ChanMask > 0xf) ? 2 : 1,
(conf->ChanMask&AMBI_PERIPHONIC_MASK) ? chans_per_order3d : chans_per_order2d
);
InitDistanceComp(device, conf, speakermap);
}
static void InitHrtfPanning(ALCdevice *device)
{
/* NOTE: azimuth goes clockwise. */
2018-12-09 22:34:21 -08:00
static constexpr struct AngularPoint AmbiPoints[] = {
{ DEG2RAD( 90.0f), DEG2RAD( 0.0f) },
{ DEG2RAD( 35.2643897f), DEG2RAD( 45.0f) },
{ DEG2RAD( 35.2643897f), DEG2RAD( 135.0f) },
{ DEG2RAD( 35.2643897f), DEG2RAD(-135.0f) },
{ DEG2RAD( 35.2643897f), DEG2RAD( -45.0f) },
{ DEG2RAD( 0.0f), DEG2RAD( 0.0f) },
{ DEG2RAD( 0.0f), DEG2RAD( 45.0f) },
{ DEG2RAD( 0.0f), DEG2RAD( 90.0f) },
{ DEG2RAD( 0.0f), DEG2RAD( 135.0f) },
{ DEG2RAD( 0.0f), DEG2RAD( 180.0f) },
{ DEG2RAD( 0.0f), DEG2RAD(-135.0f) },
{ DEG2RAD( 0.0f), DEG2RAD( -90.0f) },
{ DEG2RAD( 0.0f), DEG2RAD( -45.0f) },
{ DEG2RAD(-35.2643897f), DEG2RAD( 45.0f) },
{ DEG2RAD(-35.2643897f), DEG2RAD( 135.0f) },
{ DEG2RAD(-35.2643897f), DEG2RAD(-135.0f) },
{ DEG2RAD(-35.2643897f), DEG2RAD( -45.0f) },
{ DEG2RAD(-90.0f), DEG2RAD( 0.0f) },
};
2018-12-09 22:34:21 -08:00
static constexpr ALfloat AmbiMatrixFOA[][MAX_AMBI_COEFFS] = {
{ 5.55555556e-02f, 0.00000000e+00f, 1.23717915e-01f, 0.00000000e+00f },
{ 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f },
{ 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f },
{ 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f },
{ 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f },
{ 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, 8.66025404e-02f },
{ 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f },
{ 5.55555556e-02f, -8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f },
{ 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f },
{ 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, -8.66025404e-02f },
{ 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f },
{ 5.55555556e-02f, 8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f },
{ 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f },
{ 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f },
{ 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f },
{ 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f },
{ 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f },
{ 5.55555556e-02f, 0.00000000e+00f, -1.23717915e-01f, 0.00000000e+00f },
}, AmbiMatrixHOA[][MAX_AMBI_COEFFS] = {
{ 5.55555556e-02f, 0.00000000e+00f, 1.23717915e-01f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f },
{ 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, 8.66025404e-02f, 0.00000000e+00f, 1.29099445e-01f },
{ 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f, -6.83467648e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, -8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -1.29099445e-01f },
{ 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f, 6.83467648e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, -8.66025404e-02f, 0.00000000e+00f, 1.29099445e-01f },
{ 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f, -6.83467648e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -1.29099445e-01f },
{ 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f, 6.83467648e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
{ 5.55555556e-02f, 0.00000000e+00f, -1.23717915e-01f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f },
};
2018-12-09 22:34:21 -08:00
static constexpr ALfloat AmbiOrderHFGainFOA[MAX_AMBI_ORDER+1] = {
3.00000000e+00f, 1.73205081e+00f
}, AmbiOrderHFGainHOA[MAX_AMBI_ORDER+1] = {
2.40192231e+00f, 1.86052102e+00f, 9.60768923e-01f
};
2018-12-09 22:34:21 -08:00
static constexpr ALsizei IndexMap[6] = { 0, 1, 2, 3, 4, 8 };
static constexpr ALsizei ChansPerOrder[MAX_AMBI_ORDER+1] = { 1, 3, 2, 0 };
2018-10-29 11:32:50 -07:00
const ALfloat (*RESTRICT AmbiMatrix)[MAX_AMBI_COEFFS] = AmbiMatrixFOA;
const ALfloat *RESTRICT AmbiOrderHFGain = AmbiOrderHFGainFOA;
2018-12-09 22:34:21 -08:00
ALsizei count{4};
static_assert(COUNTOF(AmbiPoints) == COUNTOF(AmbiMatrixFOA), "FOA Ambisonic HRTF mismatch");
static_assert(COUNTOF(AmbiPoints) == COUNTOF(AmbiMatrixHOA), "HOA Ambisonic HRTF mismatch");
if(device->AmbiUp)
{
AmbiMatrix = AmbiMatrixHOA;
AmbiOrderHFGain = AmbiOrderHFGainHOA;
count = COUNTOF(IndexMap);
}
2018-11-22 07:54:29 -08:00
device->mHrtfState.reset(
new (al_calloc(16, FAM_SIZE(DirectHrtfState, Chan, count))) DirectHrtfState{});
2018-12-09 22:34:21 -08:00
std::transform(std::begin(IndexMap), std::begin(IndexMap)+count, std::begin(device->Dry.Ambi.Map),
[](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; }
);
device->Dry.CoeffCount = 0;
device->Dry.NumChannels = count;
if(device->AmbiUp)
{
2018-12-08 14:22:20 -08:00
device->FOAOut.Ambi = AmbiConfig{};
2018-12-09 22:34:21 -08:00
std::transform(std::begin(IndexMap), std::begin(IndexMap)+4, std::begin(device->FOAOut.Ambi.Map),
[](const ALsizei &index) noexcept { return BFChannelConfig{1.0f, index}; }
);
device->FOAOut.CoeffCount = 0;
device->FOAOut.NumChannels = 4;
device->AmbiUp->reset(device, AmbiOrderHFGainFOA[0] / AmbiOrderHFGain[0],
AmbiOrderHFGainFOA[1] / AmbiOrderHFGain[1]);
}
else
{
device->FOAOut.Ambi = device->Dry.Ambi;
device->FOAOut.CoeffCount = device->Dry.CoeffCount;
device->FOAOut.NumChannels = 0;
}
device->RealOut.NumChannels = ChannelsFromDevFmt(device->FmtChans, device->mAmbiOrder);
BuildBFormatHrtf(device->HrtfHandle,
2018-11-22 07:54:29 -08:00
device->mHrtfState.get(), device->Dry.NumChannels, AmbiPoints, AmbiMatrix,
COUNTOF(AmbiPoints), AmbiOrderHFGain
2016-08-24 00:25:28 -07:00
);
InitNearFieldCtrl(device, device->HrtfHandle->distance, device->AmbiUp ? 2 : 1,
ChansPerOrder);
}
static void InitUhjPanning(ALCdevice *device)
{
2018-12-09 22:34:21 -08:00
static constexpr ALsizei count{3};
2018-12-09 22:34:21 -08:00
std::transform(std::begin(FuMa2ACN), std::begin(FuMa2ACN)+count, std::begin(device->Dry.Ambi.Map),
[](const ALsizei &acn) noexcept -> BFChannelConfig
{ return BFChannelConfig{1.0f/FuMa2N3DScale[acn], acn}; }
);
device->Dry.CoeffCount = 0;
device->Dry.NumChannels = count;
device->FOAOut.Ambi = device->Dry.Ambi;
device->FOAOut.CoeffCount = device->Dry.CoeffCount;
device->FOAOut.NumChannels = 0;
device->RealOut.NumChannels = ChannelsFromDevFmt(device->FmtChans, device->mAmbiOrder);
}
void aluInitRenderer(ALCdevice *device, ALint hrtf_id, enum HrtfRequestMode hrtf_appreq, enum HrtfRequestMode hrtf_userreq)
{
/* Hold the HRTF the device last used, in case it's used again. */
struct Hrtf *old_hrtf = device->HrtfHandle;
2018-11-22 06:59:32 -08:00
device->mHrtfState = nullptr;
device->HrtfHandle = nullptr;
device->HrtfName.clear();
device->Render_Mode = NormalRender;
2018-12-05 16:35:06 -08:00
device->Dry.Ambi = AmbiConfig{};
device->Dry.CoeffCount = 0;
device->Dry.NumChannels = 0;
2018-12-05 16:35:06 -08:00
std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0);
device->AvgSpeakerDist = 0.0f;
device->ChannelDelay.clear();
device->Stablizer = nullptr;
if(device->FmtChans != DevFmtStereo)
{
2017-01-29 16:42:02 -08:00
ALsizei speakermap[MAX_OUTPUT_CHANNELS];
const char *devname, *layout = NULL;
AmbDecConf conf, *pconf = NULL;
if(old_hrtf)
Hrtf_DecRef(old_hrtf);
old_hrtf = NULL;
if(hrtf_appreq == Hrtf_Enable)
device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT;
2018-11-18 18:45:45 -08:00
devname = device->DeviceName.c_str();
switch(device->FmtChans)
{
case DevFmtQuad: layout = "quad"; break;
case DevFmtX51: /* fall-through */
case DevFmtX51Rear: layout = "surround51"; break;
case DevFmtX61: layout = "surround61"; break;
case DevFmtX71: layout = "surround71"; break;
/* Mono, Stereo, and Ambisonics output don't use custom decoders. */
case DevFmtMono:
case DevFmtStereo:
case DevFmtAmbi3D:
break;
}
if(layout)
{
const char *fname;
if(ConfigValueStr(devname, "decoder", layout, &fname))
{
2018-11-03 19:51:23 -07:00
if(!conf.load(fname))
ERR("Failed to load layout file %s\n", fname);
else
{
if(conf.ChanMask > 0xffff)
ERR("Unsupported channel mask 0x%04x (max 0xffff)\n", conf.ChanMask);
else
{
if(MakeSpeakerMap(device, &conf, speakermap))
pconf = &conf;
}
}
}
}
if(pconf && GetConfigValueBool(devname, "decoder", "hq-mode", 0))
{
device->AmbiUp = nullptr;
if(!device->AmbiDecoder)
device->AmbiDecoder.reset(new BFormatDec{});
}
else
{
device->AmbiDecoder = nullptr;
if(device->FmtChans != DevFmtAmbi3D || device->mAmbiOrder < 2)
device->AmbiUp = nullptr;
else if(!device->AmbiUp)
device->AmbiUp.reset(new AmbiUpsampler{});
}
if(!pconf)
InitPanning(device);
else if(device->AmbiDecoder)
InitHQPanning(device, pconf, speakermap);
else
InitCustomPanning(device, pconf, speakermap);
/* Enable the stablizer only for formats that have front-left, front-
* right, and front-center outputs.
*/
switch(device->FmtChans)
{
case DevFmtX51:
case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
if(GetConfigValueBool(devname, NULL, "front-stablizer", 0))
{
/* Initialize band-splitting filters for the front-left and
* front-right channels, with a crossover at 5khz (could be
* higher).
*/
ALfloat scale = (ALfloat)(5000.0 / device->Frequency);
std::unique_ptr<FrontStablizer> stablizer{new FrontStablizer{}};
stablizer->LFilter.init(scale);
stablizer->RFilter = stablizer->LFilter;
/* Initialize all-pass filters for all other channels. */
stablizer->APFilter[0].init(scale);
2018-12-05 16:35:06 -08:00
std::fill(std::begin(stablizer->APFilter)+1, std::end(stablizer->APFilter),
stablizer->APFilter[0]);
device->Stablizer = std::move(stablizer);
}
break;
case DevFmtMono:
case DevFmtStereo:
case DevFmtQuad:
case DevFmtAmbi3D:
break;
}
TRACE("Front stablizer %s\n", device->Stablizer ? "enabled" : "disabled");
return;
}
device->AmbiDecoder = nullptr;
2018-12-05 16:35:06 -08:00
bool headphones{device->IsHeadphones != AL_FALSE};
if(device->Type != Loopback)
{
const char *mode;
2018-11-18 18:45:45 -08:00
if(ConfigValueStr(device->DeviceName.c_str(), NULL, "stereo-mode", &mode))
{
if(strcasecmp(mode, "headphones") == 0)
headphones = true;
else if(strcasecmp(mode, "speakers") == 0)
headphones = false;
else if(strcasecmp(mode, "auto") != 0)
ERR("Unexpected stereo-mode: %s\n", mode);
}
}
if(hrtf_userreq == Hrtf_Default)
{
bool usehrtf = (headphones && hrtf_appreq != Hrtf_Disable) ||
(hrtf_appreq == Hrtf_Enable);
if(!usehrtf) goto no_hrtf;
device->HrtfStatus = ALC_HRTF_ENABLED_SOFT;
if(headphones && hrtf_appreq != Hrtf_Disable)
device->HrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT;
}
else
{
if(hrtf_userreq != Hrtf_Enable)
{
if(hrtf_appreq == Hrtf_Enable)
device->HrtfStatus = ALC_HRTF_DENIED_SOFT;
goto no_hrtf;
}
device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT;
}
if(device->HrtfList.empty())
2018-11-18 18:45:45 -08:00
device->HrtfList = EnumerateHrtf(device->DeviceName.c_str());
if(hrtf_id >= 0 && (size_t)hrtf_id < device->HrtfList.size())
{
const EnumeratedHrtf &entry = device->HrtfList[hrtf_id];
struct Hrtf *hrtf = GetLoadedHrtf(entry.hrtf);
if(hrtf && hrtf->sampleRate == device->Frequency)
{
device->HrtfHandle = hrtf;
device->HrtfName = entry.name;
}
else if(hrtf)
Hrtf_DecRef(hrtf);
}
2018-12-05 16:35:06 -08:00
if(!device->HrtfHandle)
{
2018-12-05 16:35:06 -08:00
auto find_hrtf = [device](const EnumeratedHrtf &entry) -> bool
{
2018-12-05 16:35:06 -08:00
struct Hrtf *hrtf = GetLoadedHrtf(entry.hrtf);
if(!hrtf) return false;
if(hrtf->sampleRate != device->Frequency)
{
Hrtf_DecRef(hrtf);
return false;
}
device->HrtfHandle = hrtf;
device->HrtfName = entry.name;
2018-12-05 16:35:06 -08:00
return true;
};
std::find_if(device->HrtfList.cbegin(), device->HrtfList.cend(), find_hrtf);
}
if(device->HrtfHandle)
{
if(old_hrtf)
Hrtf_DecRef(old_hrtf);
old_hrtf = NULL;
device->Render_Mode = HrtfRender;
2018-12-05 16:35:06 -08:00
const char *mode;
2018-11-18 18:45:45 -08:00
if(ConfigValueStr(device->DeviceName.c_str(), NULL, "hrtf-mode", &mode))
{
if(strcasecmp(mode, "full") == 0)
device->Render_Mode = HrtfRender;
else if(strcasecmp(mode, "basic") == 0)
device->Render_Mode = NormalRender;
else
ERR("Unexpected hrtf-mode: %s\n", mode);
}
if(device->Render_Mode == HrtfRender)
{
/* Don't bother with HOA when using full HRTF rendering. Nothing
* needs it, and it eases the CPU/memory load.
*/
device->AmbiUp = nullptr;
}
else
{
if(!device->AmbiUp)
device->AmbiUp.reset(new AmbiUpsampler{});
}
TRACE("%s HRTF rendering enabled, using \"%s\"\n",
((device->Render_Mode == HrtfRender) ? "Full" : "Basic"), device->HrtfName.c_str()
);
InitHrtfPanning(device);
return;
}
device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT;
no_hrtf:
if(old_hrtf)
Hrtf_DecRef(old_hrtf);
2018-11-22 06:49:37 -08:00
old_hrtf = nullptr;
TRACE("HRTF disabled\n");
device->Render_Mode = StereoPair;
device->AmbiUp = nullptr;
2018-12-05 16:35:06 -08:00
int bs2blevel{((headphones && hrtf_appreq != Hrtf_Disable) ||
(hrtf_appreq == Hrtf_Enable)) ? 5 : 0};
if(device->Type != Loopback)
2018-11-18 18:45:45 -08:00
ConfigValueInt(device->DeviceName.c_str(), NULL, "cf_level", &bs2blevel);
if(bs2blevel > 0 && bs2blevel <= 6)
{
2018-11-22 06:49:37 -08:00
device->Bs2b.reset(new bs2b{});
bs2b_set_params(device->Bs2b.get(), bs2blevel, device->Frequency);
TRACE("BS2B enabled\n");
InitPanning(device);
return;
}
TRACE("BS2B disabled\n");
2018-12-05 16:35:06 -08:00
const char *mode;
2018-11-18 18:45:45 -08:00
if(ConfigValueStr(device->DeviceName.c_str(), NULL, "stereo-encoding", &mode))
{
if(strcasecmp(mode, "uhj") == 0)
device->Render_Mode = NormalRender;
else if(strcasecmp(mode, "panpot") != 0)
ERR("Unexpected stereo-encoding: %s\n", mode);
}
if(device->Render_Mode == NormalRender)
{
2018-11-21 15:31:32 -08:00
device->Uhj_Encoder.reset(new Uhj2Encoder{});
TRACE("UHJ enabled\n");
InitUhjPanning(device);
return;
}
TRACE("UHJ disabled\n");
InitPanning(device);
}
void aluInitEffectPanning(ALeffectslot *slot)
{
2018-12-08 14:22:20 -08:00
ALsizei i{0};
for(auto &chanmap : slot->ChanMap)
{
2018-12-08 14:22:20 -08:00
chanmap.Scale = 1.0f;
chanmap.Index = i++;
}
slot->NumChannels = i;
}