-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathRegionHandlers.cpp
More file actions
256 lines (220 loc) · 10.6 KB
/
RegionHandlers.cpp
File metadata and controls
256 lines (220 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*############################################################################*/
/*# #*/
/*# Region handlers for the point source panner #*/
/*# Copyright © 2020 Peter Stitt #*/
/*# #*/
/*# Filename: AdmRegionHandlers.cpp #*/
/*# Version: 0.1 #*/
/*# Date: 23/06/2020 #*/
/*# Author(s): Peter Stitt #*/
/*# Licence: LGPL + proprietary #*/
/*# #*/
/*############################################################################*/
#include "RegionHandlers.h"
#include <string>
#include <map>
namespace spaudio {
Triplet::Triplet(std::vector<unsigned int> chanInds, std::vector<PolarPosition<double>> polPos)
: RegionHandler(chanInds, polPos)
{
// calculate the unit vectors in each of the loudspeaker directions
std::vector<std::vector<double>> unitVectors(3, std::vector<double>(3, 0.));
for (int i = 0; i < 3; ++i)
{
polPos[i].distance = 1.;
CartesianPosition<double> cartPos = PolarToCartesian(polPos[i]);
unitVectors[i][0] = cartPos.x;
unitVectors[i][1] = cartPos.y;
unitVectors[i][2] = cartPos.z;
}
// Calculate the inverse of the matrix holding the unit vectors
m_inverseDirections = inverseMatrix(unitVectors);
}
void Triplet::CalculateGains(const std::vector<double>& directionUnitVec, std::vector<double>& gains)
{
assert(gains.capacity() >= 3);
gains.resize(3, 0.);
for (auto& g : gains)
g = 0.;
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
gains[i] += directionUnitVec[j] * m_inverseDirections[j][i];
// if any of the gains is negative then return zero
for (int i = 0; i < 3; ++i)
if (gains[i] < -m_tol)
{
for (int j = 0; j < 3; ++j)
gains[j] = 0.;
return;
}
// Normalise
double vecNorm = norm(gains);
for (int i = 0; i < 3; ++i)
gains[i] /= vecNorm;
}
//=======================================================================================
VirtualNgon::VirtualNgon(std::vector<unsigned int> chanInds, std::vector<PolarPosition<double>> polPos, PolarPosition<double> centrePosition)
: RegionHandler(chanInds, polPos)
{
m_nCh = (unsigned int)chanInds.size();
// See Rec. ITU-R BS.2127-0 sec. 6.1.3.1 at pg.27
m_downmixCoefficient = 1. / sqrt(double(m_nCh));
// Order the speakers so that they go anti-clockwise from the point of view of the origin to the centre speaker
std::vector<unsigned int> vertOrder = getNgonVectexOrder(polPos, centrePosition);
// Make a triplet from each adjacent pair of speakers and the virtual centre speaker
for (unsigned int iCh = 0; iCh < m_nCh; ++iCh)
{
unsigned int spk1 = vertOrder[iCh];
unsigned int spk2 = vertOrder[(iCh + 1) % m_nCh];
std::vector<unsigned int>channelIndSubset = { spk1,spk2,m_nCh };// { orderCh[spk1], orderCh[spk2], centreInd };
std::vector<PolarPosition<double>> tripletPositions(3);
tripletPositions[0] = polPos[spk1];
tripletPositions[1] = polPos[spk2];
tripletPositions[2] = centrePosition;
m_triplets.push_back(Triplet(channelIndSubset, tripletPositions));
}
m_tripletGains.resize(3, 0.);
}
void VirtualNgon::CalculateGains(const std::vector<double>& directionUnitVec, std::vector<double>& gains)
{
assert(gains.capacity() >= m_nCh);
gains.resize(m_nCh, 0.);
for (auto& g : gains)
g = 0.;
unsigned int nTriplets = (unsigned int)m_triplets.size();
unsigned int iTriplet = 0;
// All gains must be above this value for the triplet to be valid
// Select a very small negative number to account for rounding errors
for (iTriplet = 0; iTriplet < nTriplets; ++iTriplet)
{
m_triplets[iTriplet].CalculateGains(directionUnitVec, m_tripletGains);
double gainSum = std::accumulate(m_tripletGains.begin(), m_tripletGains.end(), 0.);
// All gains must be positive (within tolerance) and the sum should be greater than 0
if (m_tripletGains[0] > -m_tol && m_tripletGains[1] > -m_tol && m_tripletGains[2] > -m_tol
&& gainSum > m_tol)
{
break;
}
}
// If no triplet is found then return the vector of zero gains
if (iTriplet == nTriplets)
return;
std::vector<unsigned int>& tripletInds = m_triplets[iTriplet].m_channelInds;
for (int i = 0; i < 2; ++i)
gains[tripletInds[i]] += m_tripletGains[i];
for (unsigned int i = 0; i < m_nCh; ++i)
gains[i] += m_downmixCoefficient * m_tripletGains[2];
double gainNorm = 1. / norm(gains);
for (unsigned int i = 0; i < m_nCh; ++i)
gains[i] *= gainNorm;
}
//=======================================================================================
QuadRegion::QuadRegion(std::vector<unsigned int> chanInds, std::vector<PolarPosition<double>> polPos)
: RegionHandler(chanInds, polPos)
{
// Get the centre position of the four points
CartesianPosition<double> centrePosition;
centrePosition.x = 0.;
centrePosition.y = 0.;
centrePosition.z = 0.;
std::vector<CartesianPosition<double>> cartesianPositions;
for (int i = 0; i < 4; ++i)
{
cartesianPositions.push_back(PolarToCartesian(polPos[i]));
centrePosition.x += cartesianPositions[i].x / 4.;
centrePosition.y += cartesianPositions[i].y / 4.;
centrePosition.z += cartesianPositions[i].z / 4.;
}
// Get the order of the loudspeakers
PolarPosition<double> centrePolarPosition = CartesianToPolar(centrePosition);
m_vertOrder = getNgonVectexOrder(polPos, centrePolarPosition);
for (int i = 0; i < 4; ++i)
{
m_quadVertices.push_back(cartesianPositions[m_vertOrder[i]]);
}
// Calculate the polynomial coefficients
m_polynomialXProdX = CalculatePolyXProdTerms(m_quadVertices);
// For the Y terms rotate the order in which the vertices are sent
m_polynomialXProdY = CalculatePolyXProdTerms({ m_quadVertices[1],m_quadVertices[2],m_quadVertices[3],m_quadVertices[0] });
m_gainsTmp.resize(4);
m_gP.resize(3);
}
double QuadRegion::GetPanningValue(const std::vector<double>& directionUnitVec, std::vector<std::vector<double>>& xprodTerms)
{
// Take the dot product with the direction vector to get the polynomial terms
double a = dotProduct(xprodTerms[0], directionUnitVec);
double b = dotProduct(xprodTerms[1], directionUnitVec);
double c = dotProduct(xprodTerms[2], directionUnitVec);
double roots[2] = { -1. };
// No quadratic term so solve linear bx + c = 0
if (abs(a) < m_tol)
{
return -c / b;
}
// Find the roots of the quadratic equation
// TODO: Add some checks here for cases when values are close to zero
double d = b * b - 4 * a * c;
if (d >= 0.)
{
double sqrtTerm = sqrt(d);
roots[0] = (-b + sqrtTerm) / (2 * a);
roots[1] = (-b - sqrtTerm) / (2 * a);
for (int i = 0; i < 2; ++i)
{
if (roots[i] >= 0. && roots[i] <= 1.0)
return roots[i];
}
}
return -1.; // if no gain was found between 0 and 1 then return -1
}
void QuadRegion::CalculateGains(const std::vector<double>& directionUnitVec, std::vector<double>& gains)
{
assert(gains.capacity() >= 4);
gains.resize(4, 0.);
for (auto& g : gains)
g = 0.;
// Calculate the gains in anti-clockwise order
double x = GetPanningValue(directionUnitVec, m_polynomialXProdX);
double y = GetPanningValue(directionUnitVec, m_polynomialXProdY);
// Check that both of the panning values are between zero and one and that gP.d > 0
if (x > 1. + m_tol || x < -m_tol || y > 1. + m_tol || y < -m_tol)
return; // return zero gains
m_gainsTmp[0] = (1. - x) * (1. - y);
m_gainsTmp[1] = x * (1. - y);
m_gainsTmp[2] = x * y;
m_gainsTmp[3] = (1. - x) * y;
for (int i = 0; i < 3; ++i)
m_gP[i] = 0.;
for (int i = 0; i < 4; ++i)
{
m_gP[0] += m_gainsTmp[i] * m_quadVertices[i].x;
m_gP[1] += m_gainsTmp[i] * m_quadVertices[i].y;
m_gP[2] += m_gainsTmp[i] * m_quadVertices[i].z;
}
double dirCheck = dotProduct(m_gP, directionUnitVec);
if (dirCheck < 0.)
return; // Return zeros
double gainNorm = 1. / norm(m_gainsTmp);
for (int i = 0; i < 4; ++i)
m_gainsTmp[i] *= gainNorm;
// Map the gains to the order the channels were input
for (int i = 0; i < 4; ++i)
gains[m_vertOrder[i]] = m_gainsTmp[i];
}
std::vector<std::vector<double>> QuadRegion::CalculatePolyXProdTerms(const std::vector<CartesianPosition<double>>& quadVertices)
{
// See ITU Rec. ITU-R BS.2127-0 pg 24 last equation
std::vector<double> p1 = { quadVertices[0].x,quadVertices[0].y,quadVertices[0].z };
std::vector<double> p2 = { quadVertices[1].x,quadVertices[1].y,quadVertices[1].z };
std::vector<double> p3 = { quadVertices[2].x,quadVertices[2].y,quadVertices[2].z };
std::vector<double> p4 = { quadVertices[3].x,quadVertices[3].y,quadVertices[3].z };
std::vector<std::vector<double>> polyXProdTerms;
// Quadratic term
polyXProdTerms.push_back(crossProduct(vecSubtract(p2, p1), vecSubtract(p3, p4)));
// Linear term
polyXProdTerms.push_back(vecSum(crossProduct(p1, vecSubtract(p3, p4)), crossProduct(vecSubtract(p2, p1), p4)));
// Constant term
polyXProdTerms.push_back(crossProduct(p1, p4));
return polyXProdTerms;
}
} // namespace spaudio