CSL 5.2

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

- File List
- File Members

SpeakerLayout.cpp
Go to the documentation of this file.
1 //
2 // SpeakerLayout.cpp -- Class for loading and parsing loudspeaker position information used
3 // by the Spatializer and Auralizer classes.
4 // See the copyright notice and acknowledgment of authors in the file COPYRIGHT
5 // Created by Jorge Castellanos on 6/16/06. Ammend for WFS, will wolcott, 6/07. Hacked 8/09 by STP.
6 //
7
8 #include <stdlib.h>
9 #include <math.h>
10 #include <iostream>
11 #include <fstream>
12 #include <string.h>
13
14 #include "SpeakerLayout.h"
15
16 #define INV_SQRT2 (1/1.414213562)
17
18 using std::cout;
19 using std::endl;
20 using namespace csl;
21
22 // A static private member that holds the default layout.
23
24 SpeakerLayout *SpeakerLayout::sDefaultSpeakerLayout = 0;
25
26 /// Returns a reference to the default layout.
27 /// If no layout has been set, it creates a stereo speaker layout.
28
29 SpeakerLayout *SpeakerLayout::defaultSpeakerLayout() {
30 // if being used for the first time, create a new layout and set it as default.
31 if (sDefaultSpeakerLayout == 0)
32 sDefaultSpeakerLayout = new StereoSpeakerLayout();
33 return sDefaultSpeakerLayout;
34 }
35
36 /// The default speaker layout is used by Panners, when not specified otherwise.
37 /// If using multiple Panners, but only one speaker setup, is best to set the layout
38 /// as default, and then forget about that; Panners will know to use such layout.
39 /// @note As a suggestion, set the default layout before creating any Panners. Otherwise they'll have to rebuild their data.
40
41 void SpeakerLayout::setDefaultSpeakerLayout(SpeakerLayout *defaultLayout) {
42 // prevent a null speaker layout.
43 if (sDefaultSpeakerLayout == 0)
44 sDefaultSpeakerLayout = SpeakerLayout::defaultSpeakerLayout();
45
46 // Make a "Hard-copy" of the layout. Client can then free their memory.
47 *sDefaultSpeakerLayout = *defaultLayout; // copy the layout to the new one.
48
49 // Notify clients that the layout has changed, so they update accordingly.
50 sDefaultSpeakerLayout->changed((void *)sDefaultSpeakerLayout);
51 }
52
53
54 // ******** Constructors ***********//
55
56 /**
57 Reads loudspeaker layout from a text file.
58 Loudspeaker layouts provided as textfiles should folow the format below:
59 -> A / indicates the start of a comment, which lasts until the end of the line (??????????????????????).
60 -> The Keywords CARTESIAN, SPHERICAL-DEGREES, SPHERICAL-RADIANS are used to specify the format of the provided speaker positions.
61 The class will do necessary conversions to the internal representation in spherical radians.
62 If no keyword is specified, spherical radians are assumed(??????????????????????).
63 -> Non-comment or non-keyword lines should contain speaker position information as three tab-separated coordinates.
64 Each line will be interpreted as a new speaker.
65 Examples of parsable speaker layout files are provided with the code (.dat files).
66
67 ---> needs .wfs file description
68
69 */
70 SpeakerLayout::SpeakerLayout(const char *filePath) : mDimensions(2) {
71 readSpeakerFile(filePath);
72 }
73
74 SpeakerLayout::~SpeakerLayout() {
75 for (unsigned i = 0; i < mSpeakers.size(); ++i)
76 delete mSpeakers[i];
77 // if (mSpeakerDistanceDeltas != NULL)
78 // delete[] mSpeakerDistanceDeltas;
79 // mSpeakerDistanceDeltas = 0;
80 }
81
82 // load in a config file
83
84 void SpeakerLayout::readSpeakerFile(const char *filePath){
85 if (filePath != NULL) {
86 char testChar;
87 char lineBuffer[1024]; // a space for reading lines of text into - arbitrary maximum length of 1023 characters
88 char *file = (char *) malloc(strlen(filePath));
89 strcpy(file, filePath);
90 const char *cartesian = "CARTESIAN";
91 const char *degrees = "SPHERICAL-DEGREES";
92 const char *normal = "WITH-NORMAL";
93 bool isCartesian = false;
94 bool isDegrees = false;
95 bool withNormal = false;
96 CPoint tempSpeaker;
97 CPoint tempNormal;
98 float tempGain;
99
100 ifstream inputFile(file, ios::in); // Open the file
101 if ( ! inputFile) { // Making sure file opened.
102 logMsg(kLogError, "SpeakerLayout unable to open file\n");
103 exit(1);
104 }
105 // read a character from file until reached end
106 while ((testChar = inputFile.peek()) != EOF) {
107 if (testChar == '/') { // skip any lines that begin with / character
108 inputFile.getline(lineBuffer, 1023);
109 continue;
110 } else if (testChar == ' ' || testChar == '\t' || testChar == '\n') {
111 inputFile.ignore(1); // ignore space, tab and newline characters
112 } else if ( ! isdigit(testChar) && testChar != '-') {
113 inputFile.get(lineBuffer, 32); // read the text flags (CARTESIAN, SPHERICAL-DEGREES etc)
114 if ( ! strcmp(lineBuffer, cartesian)) isCartesian = true;
115 if ( ! strcmp(lineBuffer, degrees)) isDegrees = true;
116 if ( ! strcmp(lineBuffer, normal)) withNormal = true;
117 } else {
118 if (withNormal){
119 // read speaker position and a point to which the speaker is facing
120 inputFile >> tempSpeaker.x >> tempSpeaker.y >> tempSpeaker.z >> tempNormal.x >> tempNormal.y >> tempNormal.z >> tempGain;
121 this->addSpeaker(tempSpeaker.x, tempSpeaker.y, tempSpeaker.z, tempNormal.x, tempNormal.y, tempNormal.z, tempGain);
122 }else{
123 // numeric information - read each word as consecutive speaker coordinates
124 inputFile >> tempSpeaker.x >> tempSpeaker.y >> tempSpeaker.z;
125 this->addSpeaker(tempSpeaker.x, tempSpeaker.y, tempSpeaker.z);
126 }
127 }
128 }
129
130 // if (isCartesian) {
131 // // conversion of cartesian to sperical (using radians)
132 // cartesianToSphericalRadians();
133 // } else if (isDegrees) {
134 // double degreeInRadians = CSL_PI/180.0;
135 // // conversion from degrees to radians
136 // for (unsigned i = 0; i < mNumSpeakers; i++) {
137 // mSpeakers[i]->coord1 *= degreeInRadians;
138 // mSpeakers[i]->coord3 *= degreeInRadians;
139 // mSpeakers[i]->coord3 = fabs(mSpeakers[mNumSpeakers]->coord3);
140 // }
141 // }
142
143 dump();
144 free(file);
145 }
146 }
147
148 /// Add a speaker to the layout. Parameters should be specified in degrees.
149
150 void SpeakerLayout::addSpeaker(float azimuth, float elevation, float radius) {
151 float scaleAz = azimuth * CSL_PI/180.0;
152 float scaledEl = elevation * CSL_PI/180.0;
153 Speaker * newSpeaker = new Speaker(scaleAz, scaledEl, radius);
154 mSpeakers.push_back(newSpeaker);
155 if (elevation) // If not only horizontally placed, then set as full periphonic.
156 mDimensions = 3;
157 // Notify clients that the layout has changed, so they update accordingly.
158 this->changed((void *)this);
159 // cout << " Pos = " << scaleAz << " @ "<< scaledEl << " @ "<< radius << endl;
160 // newSpeaker->dump();
161 }
162
163 /// Add a speaker to the layout. Parameters should be specified in x,y,z
164 void SpeakerLayout::addSpeaker(float x, float y, float z, float xNorm, float yNorm, float zNorm, float gain){
165
166 Speaker * newSpeaker = new Speaker(x,y,z,xNorm,yNorm,zNorm, gain);
167 mSpeakers.push_back(newSpeaker);
168
169 // Notify clients that the layout has changed, so they update accordingly.
170 this->changed((void *)this);
171 cout << " Pos = " << x << " "<< y << " "<< z << " Norm = " << xNorm << " " << yNorm << " " << zNorm << endl;
172 }
173
174 /// When speakers are not placed at the same distance from the "sweet spot" (the center of the listening space)
175 /// it's common to assume they are at a fixed distance by setting all of them as if they were, and then the output
176 /// data is delayed to compensate for this difference. See: ActiveSpeakerLayout.
177
178 void SpeakerLayout::normalizeSpeakerDistances(float radius) {
179 unsigned nnumSpeakers = mSpeakers.size();
180 if (mSpeakerDistanceDeltas != NULL) {
181 delete[] mSpeakerDistanceDeltas;
182 mSpeakerDistanceDeltas = 0;
183 }
184 mSpeakerDistanceDeltas = new float[nnumSpeakers];
185 float theRadius = radius;
186 if ( ! radius) { // If not specified a decired radius find the closest one.
187 float tempRadius = 0;
188 // Find the speaker that's placed the farthest from the center.
189 for (unsigned i = 0; i < nnumSpeakers; i++) {
190 tempRadius = mSpeakers[i]->radius();
191 if (tempRadius > theRadius)
192 theRadius = tempRadius;
193 }
194 }
195 // Go thru all the speakers, change their distance from the source to be the new radius and save the differenc to the real radius
196 for (unsigned i = 0; i < nnumSpeakers; i++) {
197 mSpeakerDistanceDeltas[i] = mSpeakers[i]->radius() - theRadius;
198 mSpeakers[i]->setRadius(theRadius);
199 }
200 }
201
202 /// Prints the number of speakers in the layout and their position.
203
204 void SpeakerLayout::dump() {
205 unsigned nnumSpeakers = mSpeakers.size();
206 cout << "Number of Loudspeakers: " << nnumSpeakers << endl;
207 for (unsigned i = 0; i < nnumSpeakers; i++) {
208 cout << "Speaker #" << i << ": ";
209 mSpeakers[i]->dump();
210 }
211 }
212
213 // Overloaded "=" operator
214
215 SpeakerLayout &SpeakerLayout::operator=(const SpeakerLayout &layout) {
216 if (this != &layout) {
217 unsigned nnumSpeakers = mSpeakers.size();
218 // Remove all speakers
219 for (unsigned i = 0; i < nnumSpeakers; ++i)
220 delete mSpeakers[i];
221 mSpeakers.clear(); // Erase all items in the vector.
222 nnumSpeakers = layout.numSpeakers(); // How many speakers the new layout has?
223 Speaker *tempSpeaker;
224 for (unsigned i = 0; i < nnumSpeakers; ++i) {
225 tempSpeaker = new Speaker(0, 0);
226 *tempSpeaker = *(layout.speakerAtIndex(i));
227 mSpeakers.push_back(tempSpeaker);
228 }
229 }
230 return *this;
231 }
232
233 Speaker::Speaker(float tazimuth, float televation, float tradius) {
234 mPosition.set(kPolar, tradius, tazimuth, televation);
235 mNormal.set(1.0, 0.0, 0.0);
236 mGain = 1.0f;
237 }
238
239 void Speaker::setRadius(float dist) {
240 if (radius() != dist) {
241 mPosition.normalize(); // make it unit length
242 mPosition *= dist; // multiply by "radius" so that length becomes that value.
243 }
244 }
245
246 Speaker::Speaker(float x, float y, float z, float xNorm, float yNorm, float zNorm, float gain)
247 {
248 mPosition.set(x, y, z); // set position
249
250 float spNormX,spNormY,spNormZ;
251
252 // was a point in which it pointed, convert to a vector emitting from the speaker
253 spNormX = xNorm - x; spNormY = yNorm - y; spNormZ = zNorm - z;
254
255 float mag = sqrt(spNormX * spNormX + spNormY * spNormY + spNormZ * spNormZ);
256
257 // ahhh! what the hell? just default to origin...this could cause some weird problems.
258 if (mag < 0.00001){
259 spNormX = -x;
260 spNormY = -y;
261 spNormZ - -z;
262 mag = sqrt(x * x + y * y + z * z);
263 }
264
265 // normalize!
266 spNormX /= mag;
267 spNormY /= mag;
268 spNormZ /= mag;
269
270 mNormal.set(spNormX, spNormY, spNormZ);
271 mGain = gain;
272 }
273
274 void Speaker::dump() {
275 cout << "Azimuth: " << azimuth() * 180/CSL_PI
276 << "\tElevation: " << elevation() * 180/CSL_PI
277 << "\tDistance: " << radius() << endl;
278 }
279
280
281 //void ActiveSpeakerLayout::nextBuffer(Buffer & outputBuffer, unsigned outBufNum) throw (Exception) {
282 //
283 // input->nextBuffer(outputBuffer);
284 //
285 // if
286 // mCompensatesForDistance;
287 // mRemapsChannels;
288 //
289 //
290 //
291 //}
292
293 //////////////////////////////////////
294 /* STEREO SPEAKER SETUP */
295 /////////////////////////////////////
296
297 StereoSpeakerLayout::StereoSpeakerLayout() : SpeakerLayout() {
298 addSpeaker(-30);
299 addSpeaker(30);
300 }
301
302 HeadphoneSpeakerLayout::HeadphoneSpeakerLayout() : SpeakerLayout() {
303 addSpeaker(-90);
304 addSpeaker(90);
305 }