CSL 5.2

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

VBAP.cpp
Go to the documentation of this file.
1 ///
2 /// VBAP.h -- Vector Base Amplitude Panning class (v2 - Nov 2006)
3 /// See the copyright notice and acknowledgment of authors in the file COPYRIGHT
4 /// Doug McCoy, 2004.
5 ///
6
7 #include "VBAP.h"
8
9 #define MAX_NUM_VBAP_TRIPLETS 512
10 #define MIN_VOLUME_TO_LENGTH_RATIO 0.01
11
12 using namespace csl;
13
14 typedef struct {
15 int firstNode;
16 int secondNode;
17 } Connection;
18
19
20 double vecAngle(CPoint v1, CPoint v2) {
21 double inner = v1*v2/(v1.len() * v2.len());
22 if (inner > 1.0)
23 inner = 1.0;
24 else if (inner < -1.0)
25 inner = -1.0;
26 return fabs(acos(inner) );
27 }
28
29 CPoint vectorMultiply(CPoint &v, CSLMatrix &m) {
30 unsigned dimensions = m.RowNo();
31 double temp[3] = {0};
32 for (unsigned i = 0; i < dimensions; i++) // for each node, insert speaker coordinates in matrix
33 for (unsigned j = 0; j < dimensions; j++) // for each node, insert speaker coordinates in matrix
34 temp[i] += v(j) * m(j,i);
35
36 CPoint result(temp[0], temp[1], temp[2]);
37 return result;
38 }
39
40 SpeakerSet::SpeakerSet(unsigned size) {
41 nodes = new unsigned[size];
42
43 }
44
45 SpeakerSet::~SpeakerSet() {
46 delete [] nodes;
47
48 }
49
50 void SpeakerSet::dump() {
51 cout << nodes[0] << "\t" << nodes[1] << "\t" << nodes[2] << endl;
52 }
53
54 ///////////////////////////////////////////////////
55 /* SPEAKER TRIPLET LAYOUT IMPLEMENTATION */
56 ///////////////////////////////////////////////////
57
58 SpeakerSetLayout::SpeakerSetLayout(SpeakerLayout *aLayout, VBAPMode mode) {
59 mSpeakerLayout = aLayout;
60
61 // mSpeakerLayout->attachObserver(this); // Tell the speaker layout to inform you of any changes
62 mMode = mode;
63 // Be smart about setting the mode.
64 if (mMode == kAuto) {
65 if (mSpeakerLayout->isPeriphonic())
66 mMode = kPeriphonic;
67 else
68 mMode = kPantophonic;
69 }
70
71 // if (mSpeakerLayout->isPeriphonic()) {
72 // if (mMode == 0)
73 // mMode = kPeriphonic;
74 // } else
75 // mMode = kPantophonic;
76
77 mTriplets = (SpeakerSet **) malloc(MAX_NUM_VBAP_TRIPLETS * sizeof(SpeakerSet*));
78 mNumTriplets = 0;
79 if (mMode == kPantophonic)
80 findSpeakerPairs();
81 else
82 findSpeakerTriplets();
83
84 dump();
85 }
86
87 SpeakerSetLayout::~SpeakerSetLayout() {
88
89 // mSpeakerLayout->detachObserver(this); // Remove yourself from the list of observers.
90
91 unsigned i = 0;
92 for (i = 0; i < mNumTriplets; i++)
93 delete mTriplets[i];
94
95 free(mTriplets);
96 }
97
98 void SpeakerSetLayout::addTriple(SpeakerSet *lst) {
99 if (mNumTriplets == MAX_NUM_VBAP_TRIPLETS) {
100 printf("Max number of VBAP triplets exceeded. Triplet not added\n");
101 return;
102 }
103 mTriplets[mNumTriplets++] = lst;
104 return;
105 }
106
107 void SpeakerSetLayout::removeTriple(SpeakerSet *s) {
108
109 // locate the item
110 for (unsigned i = 0; ((i < mNumTriplets)); i++) {
111 if (mTriplets[i] == s) {
112 delete mTriplets[i];
113 --mNumTriplets;
114
115 // shift the upper items down 1 in the list
116 for (; i < mNumTriplets; i++) {
117 mTriplets[i] = mTriplets[i+1];
118 }
119 break;
120 }
121 }
122
123 return;
124 }
125
126 void SpeakerSetLayout::dump() {
127 cout << "Number of SpeakerSets: " << mNumTriplets << endl;
128 for (unsigned i = 0; i < mNumTriplets; i++) {
129 cout << "Triple #" << i << ": ";
130 mTriplets[i]->dump();
131 }
132 }
133
134 void SpeakerSetLayout::invertTripleMatrix(SpeakerSet *lst) {
135 unsigned dimensions = mMode;
136 CSLMatrix L(dimensions,dimensions);
137 for (unsigned i = 0; i < dimensions; i++) { // for each node, insert speaker coordinates in matrix
138 CPoint temp = mSpeakerLayout->speakerAtIndex(lst->nodes[i])->position();
139 for (unsigned j = 0; j < dimensions; j++) // for each node, insert speaker coordinates in matrix
140 L(i,j) = temp(j);
141 }
142 lst->invL = new CSLMatrix (L.Inv()); // invert matrix and populate SpeakerSet member
143 return;
144 }
145
146 bool SpeakerSetLayout::evaluateCrossing(CPoint &li, CPoint &lj, CPoint &ln, CPoint &lm) {
147 CPoint cross((li^lj) ^ (ln^lm));
148 float ang_ci = vecAngle(li, cross);
149 float ang_cj = vecAngle(lj, cross);
150 float ang_ij = vecAngle(li, lj);
151 float ang_cn = vecAngle(ln, cross);
152 float ang_cm = vecAngle(lm, cross);
153 float ang_nm = vecAngle(ln, ln);
154
155 if (((ang_ci + ang_cj) == ang_ij) && ((ang_cn + ang_cm) == ang_nm) )
156 return true;
157 else {
158 ang_ci = vecAngle(li, -cross);
159 ang_cj = vecAngle(lj, -cross);
160 ang_cn = vecAngle(ln, -cross);
161 ang_cm = vecAngle(lm, -cross);
162
163 return (((ang_ci + ang_cj) == ang_ij) && ((ang_cn + ang_cm) == ang_nm) );
164 }
165 }
166
167 // If 2D VBAP, then find the pair of speakers.
168 void SpeakerSetLayout::findSpeakerPairs() throw(CException) {
169
170 unsigned numSpeakers = mSpeakerLayout->numSpeakers();
171 unsigned j, index;
172 unsigned speakerMapping[numSpeakers]; // To map unordered speakers into an ordered set.
173 float speakerAngles[numSpeakers];
174 float indexAngle;
175
176 logMsg("VBAP::finding loudspeaker pairs");
177
178 // Build a map to the speakers, that points to speaker indexes.
179 for (unsigned i = 0; i < numSpeakers; i++) {
180 speakerAngles[i] = mSpeakerLayout->speakerAtIndex(i)->azimuth();
181 speakerMapping[i] = i;
182 }
183
184 // Sort speakers into the map
185 for (unsigned i = 1; i < numSpeakers; i++) {
186 // Only sort speakers that have elevation == 0. Ignore all other.
187 if (mSpeakerLayout->speakerAtIndex(i)->elevation() == 0) {
188
189 indexAngle = speakerAngles[i];
190 index = speakerMapping[i];
191 j = i;
192
193 while ((j > 0) && (speakerAngles[j-1] > indexAngle)) {
194 speakerAngles[j] = speakerAngles[j-1];
195 speakerMapping[j] = speakerMapping[j-1];
196 j = j - 1;
197
198 }
199 speakerAngles[j] = indexAngle;
200 speakerMapping[j] = index;
201 }
202 }
203
204 //#ifdef CSL_DEBUG
205 for (unsigned i = 0; i < numSpeakers; i++)
206 mSpeakerLayout->speakerAtIndex(speakerMapping[i])->dump();
207 //#endif
208
209 for (unsigned i = 1; i < numSpeakers; i++){
210 SpeakerSet *sPair = new SpeakerSet(2);
211 sPair->nodes[0] = speakerMapping[i-1];
212 sPair->nodes[1] = speakerMapping[i];
213 invertTripleMatrix(sPair);
214 addTriple(sPair);
215 }
216 // Add the last speaker
217 SpeakerSet *sPair = new SpeakerSet(2);
218 sPair->nodes[0] = speakerMapping[numSpeakers-1];
219 sPair->nodes[1] = speakerMapping[0];
220 invertTripleMatrix(sPair);
221 addTriple(sPair);
222
223
224 }
225
226 void SpeakerSetLayout::findSpeakerTriplets() throw(CException) {
227 unsigned numSpeakers = mSpeakerLayout->numSpeakers();
228 // form all possible triples
229 for (unsigned i = 0; i < numSpeakers; i++){
230 for (unsigned j = i+1; j < numSpeakers; j++){
231 for (unsigned k = j+1; k < numSpeakers; k++){
232 SpeakerSet *triplet = new SpeakerSet;
233 triplet->nodes[0] = i;
234 triplet->nodes[1] = j;
235 triplet->nodes[2] = k;
236 addTriple(triplet);
237 }
238 }
239 }
240
241 // remove too narrow triples
242 for (unsigned i = 0; i < mNumTriplets; i++) {
243 SpeakerSet *trip = mTriplets[i];
244 CPoint p0, p1, p2;
245 p0 = mSpeakerLayout->speakerAtIndex(trip->nodes[0])->position();
246 p1 = mSpeakerLayout->speakerAtIndex(trip->nodes[1])->position();
247 p2 = mSpeakerLayout->speakerAtIndex(trip->nodes[2])->position();
248
249 CPoint xprod = p0 ^ p1; // ^ is cross product operator
250 float volume = fabsf(xprod * p2);
251 float length = fabsf(vecAngle(p0, p1) ) + fabsf(vecAngle(p0, p2) ) + fabsf(vecAngle(p1, p2) );
252 float ratio;
253 if (length > 0.00001)
254 ratio = volume / length;
255 else
256 ratio = 0.0;
257
258 if (ratio < MIN_VOLUME_TO_LENGTH_RATIO) {
259 removeTriple(trip);
260 i--;
261 }
262 }
263
264 // resolve sides that cross
265 for (unsigned i = 0; i < mNumTriplets; i++) {
266 // Holders to the triplets being tested, so they can be removed if needed.
267 SpeakerSet *currentOutterTriple, *currentInnerTriple;
268 Connection connections[6];
269 connections[0].firstNode = mTriplets[i]->nodes[0];
270 connections[0].secondNode = mTriplets[i]->nodes[1];
271
272 connections[1].firstNode = mTriplets[i]->nodes[1];
273 connections[1].secondNode = mTriplets[i]->nodes[2];
274
275 connections[2].firstNode = mTriplets[i]->nodes[0];
276 connections[2].secondNode = mTriplets[i]->nodes[2];
277
278 currentOutterTriple = mTriplets[i];
279 for (unsigned n = i+1; n < mNumTriplets; n++) {
280 connections[3].firstNode = mTriplets[n]->nodes[0];
281 connections[3].secondNode = mTriplets[n]->nodes[1];
282
283 connections[4].firstNode = mTriplets[n]->nodes[1];
284 connections[4].secondNode = mTriplets[n]->nodes[2];
285
286 connections[5].firstNode = mTriplets[n]->nodes[0];
287 connections[5].secondNode = mTriplets[n]->nodes[2];
288
289 currentInnerTriple = mTriplets[n];
290 bool removed = false;
291 bool startOver = false;
292 for (unsigned j = 0; j < 3; j++) {
293 CPoint v1 = mSpeakerLayout->speakerAtIndex(connections[j].firstNode)->position();
294 CPoint v2 = mSpeakerLayout->speakerAtIndex(connections[j].secondNode)->position();
295 for (unsigned k = 3; k < 6; k++) {
296 CPoint v3 = mSpeakerLayout->speakerAtIndex(connections[k].firstNode)->position();
297 CPoint v4 = mSpeakerLayout->speakerAtIndex(connections[k].secondNode)->position();
298
299 if (evaluateCrossing(v1, v2, v3, v4 ) ) { // if connections intersect
300 float length1 = v1.distance(v2);
301 float length2 = v3.distance(v4);
302 if (length1 > length2) { // remove triple with longer connection
303 removeTriple(currentOutterTriple );
304 i--; // ith triple deleted so don't increment i on next loop
305 startOver = true;
306 }
307 else {
308 removeTriple(currentInnerTriple );
309 n--; // nth triple deleted so don't increment n on next loop
310 }
311 removed = true;
312 }
313 if (removed) break; // break k loop
314 }
315 if (removed) break; // break j loop i.e. start a new n loop
316 }
317 if (startOver) break; // break n loop i.e. removed ith triple
318 }
319 }
320
321 // remove triangles that contain other Speakers
322 for (unsigned ii = 0; ii < mNumTriplets; ii++) {
323 invertTripleMatrix(mTriplets[ii]);
324 // cout << "outside invert: " << *(mTriplets[ii]->invL) << endl;
325
326 for (unsigned jj = 0; jj < numSpeakers; jj++) {
327 // check to see if the current speaker is one of the nodes of the triple
328 if ((jj == mTriplets[ii]->nodes[0]) || (jj == mTriplets[ii]->nodes[1]) || (jj == mTriplets[ii]->nodes[2]) )
329 continue;
330 CPoint p = mSpeakerLayout->speakerAtIndex(jj)->position();
331 CPoint v = vectorMultiply(p, (*(mTriplets[ii]->invL)) );
332 bool x_inside, y_inside, z_inside;
333 x_inside = (v.x >= -1e-4); // inside if positive or negative near zero, -1e-4 is a magic number
334 y_inside = (v.y >= -1e-4);
335 z_inside = (v.z >= -1e-4);
336
337 if ((x_inside) && (y_inside) && ((mMode == 2) || (z_inside) ) ) {
338 removeTriple(mTriplets[ii] ); // if LSpeaker is within triangle, delete triple
339 ii--;
340 break;
341 }
342 }
343 }
344 if (mNumTriplets == 0 )
345 throw LogicError("No SpeakerSets found. Check mode setting or speaker layout.");
346
347 return;
348 } // end find_triples()
349
350 //////////////////////////////////////
351 /* VBAP IMPLEMENTATION */
352 /////////////////////////////////////
353
354 /// The two parameters are optional. \n The mode defines wether to consider elevation (full 3D) positions, or horizontal only (kPantophonic).
355 /// If not specified, the mode is chosen automatically. If the SpeakerLayout contains speakers at different elevations, then it sets the mode to 3D.
356 /// Otherwise, it is set to 2D. \n The layout parameter allows to specify a different layout than the default. The recomended use is to create a layout
357 /// and set it as default. Then any Panner can make use of it. \n This parameter overrides the use of the default layout, using the layout passed here.
358 VBAP::VBAP(VBAPMode mode, SpeakerLayout *layout) : SpatialPanner(layout), mMode(mode) {
359
360 setCopyPolicy(kIgnore); // This is needed so the default kCopy doesn't overide the multiple channel panning done here.
361
362 // Be smart about setting the mode.
363 if (mMode == kAuto) {
364 if (mSpeakerLayout->isPeriphonic())
365 mMode = kPeriphonic;
366 else
367 mMode = kPantophonic;
368 }
369 setNumChannels(mSpeakerLayout->numSpeakers()); // Set the number of (output) channels to the number of loudspeakers in the layout
370 mSpeakerSetLayout = new SpeakerSetLayout(mSpeakerLayout, mMode);
371
372 }
373
374 VBAP::~VBAP() {
375 delete mSpeakerSetLayout;
376 }
377
378 void VBAP::speakerLayoutChanged() {
379
380 setNumChannels(mSpeakerLayout->numSpeakers()); // Set the number of (output) channels to the number of loudspeakers in the layout
381 delete mSpeakerSetLayout; // Delete the old triplets
382 mSpeakerSetLayout = new SpeakerSetLayout(mSpeakerLayout); // create a new triplet layout
383
384 }
385
386 void *VBAP::cache() {
387 return (void *)new VBAPSourceCache(); // Returns a pointer to an alocated cache data for its own use.
388 }
389
390 void VBAP::nextBuffer(Buffer &outputBuffer) throw(CException) {
391 this->nextBuffer(outputBuffer, 0);
392 }
393
394 void VBAP::nextBuffer(Buffer &outputBuffer, unsigned outBufNum) throw(CException) {
395 unsigned numFrames = outputBuffer.mNumFrames;
396 unsigned numTriplets = mSpeakerSetLayout->mNumTriplets;
397 outputBuffer.zeroBuffers(); // clear output buffer
398
399 #ifdef CSL_DEBUG
400 logMsg("VBAP::nextBuffer");
401 #endif
402
403 for (unsigned i = 0; i < mSources.size(); i++) { // i loops through inputs
404 SpatialSource *source = (SpatialSource*) mSources[i]; // Get the sound source
405 VBAPSourceCache *tcache = (VBAPSourceCache *) mCache[i]; // Get any cached data belonging to the sound source.
406
407 if (source->isActive()) { // if source is active
408 if (source->positionChanged()) { // if the position of the source has changed, recompute gains.
409 CPoint currentGains;
410 CPoint sourcePosition = *source->position(); // Get the source position.
411 sourcePosition.normalize(); // to keep calculations on sphere of Speakers
412
413 unsigned currentTripletIndex = tcache->tripletIndex; // Cached source placement, so it starts searching from there.
414
415 // Search thru the triplets array in search of a match for the source position.
416 for (unsigned count = 0; count < numTriplets; ++count) {
417 currentGains = vectorMultiply(sourcePosition, (*(mSpeakerSetLayout->mTriplets[currentTripletIndex]->invL)) );
418 if ((currentGains.x >= 0) && (currentGains.y >= 0) && ((mMode == 2) || (currentGains.z >= 0)) ) // need more sophisticated condition eventually
419 break;
420
421 ++currentTripletIndex;
422 if (currentTripletIndex >= numTriplets)
423 currentTripletIndex = 0;
424 }
425
426 tcache->tripletIndex = currentTripletIndex; // Store the new index
427
428 double soundPowerConstant = 1;
429 double gainScale = sqrt(soundPowerConstant) / currentGains.len();
430 currentGains *= gainScale;
431
432 // Store the gains for possible future use.
433 tcache->gains[0] = currentGains.x;
434 tcache->gains[1] = currentGains.y;
435 tcache->gains[2] = currentGains.z;
436 } // end of if (positionChanged)
437
438 SpeakerSet *currentTriplet = mSpeakerSetLayout->mTriplets[tcache->tripletIndex];
439
440 source->nextBuffer(mTempBuffer); // Ask the source to fill the buffer with the data to be processed
441
442 for (unsigned j = 0; j < mMode; j++) {
443 SampleBuffer out1 = outputBuffer.buffer(currentTriplet->nodes[j]); // get a pointer to the buffer of the speaker that needds some gain applied.
444 SampleBuffer opp1 = mTempBuffer.buffer(0); // this buffer has the data of the input source. Only mono for now. TO BE FIXED AND EXTENDED TO ...
445
446 for (unsigned k = 0; k < numFrames; k++) { // k loops through sample buffers
447 *out1++ += tcache->gains[j] * (*opp1++);
448 }
449 }
450
451 #ifdef CSL_DEBUG
452 logMsg("Speaker Gains: %.2f, \t %.2f, \t %.2f", tcache->gains[0], tcache->gains[1], tcache->gains[2]);
453 #endif
454
455 } // end if active
456 } // end for loop
457 return;
458 }
459
460
461 //////////////////////////////////////
462 /* STEREO PANNER IMPLEMENTATION */
463 /////////////////////////////////////
464
465 StereoPanner::StereoPanner() : VBAP(kPantophonic, new StereoSpeakerLayout) { }
466
467 StereoPanner::~StereoPanner() {
468 delete mSpeakerLayout; // This time I allocated the memory, so I have to get rid of it.
469 }
470
471 ////////////////////////////////////////
472 /* SURRORUND PANNER IMPLEMENTATION */
473 ///////////////////////////////////////
474
475 SurroundPanner::SurroundPanner(unsigned numSpeakers, bool useSubwoofer) : VBAP(kPantophonic, new StereoSpeakerLayout) { }
476
477 SurroundPanner::~SurroundPanner() {
478 delete mSpeakerLayout; // This time I allocated the memory, so I have to get rid of it.
479 }
480