CSL  6.0
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 
25 
26 /// Returns a reference to the default layout.
27 /// If no layout has been set, it creates a stereo speaker layout.
28 
30  // if being used for the first time, create a new layout and set it as default.
31  if (sDefaultSpeakerLayout == 0)
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 
42  // prevent a null speaker layout.
43  if (sDefaultSpeakerLayout == 0)
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.
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 
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 
179  unsigned nnumSpeakers = mSpeakers.size();
180  if (mSpeakerDistanceDeltas != NULL) {
181  delete[] mSpeakerDistanceDeltas;
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 
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 
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 
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 
298  addSpeaker(-30);
299  addSpeaker(30);
300 }
301 
303  addSpeaker(-90);
304  addSpeaker(90);
305 }
void logMsg(const char *format,...)
These are the public logging messages.
Definition: CGestalt.cpp:292
static SpeakerLayout * sDefaultSpeakerLayout
Definition: SpeakerLayout.h:77
virtual ~SpeakerLayout()
destructor
float * mSpeakerDistanceDeltas
Holds the diference of the optimal speaker distance and the real one. Only used if distances are norm...
Definition: SpeakerLayout.h:78
float elevation()
float azimuth()
float radius()
AdditiveInstrument.h – Sum-of-sines synthesis instrument class.
Definition: Accessor.h:17
Speaker * speakerAtIndex(unsigned speakerIndex) const
Returns the speaker at the specified index.
Definition: SpeakerLayout.h:65
void normalizeSpeakerDistances(float radius=0)
Returns the number of loudspeakers in the layout.
Speaker(float azimuth, float elevation, float radius=1.0)
Speaker constructor. The speaker class should only be used by the speaker layout. Clients should modi...
SpeakerLayout(const char *filePath=NULL)
Creates an empty speaker layout. Optionally reads loudspeaker layout from file.
void readSpeakerFile(const char *filePath)
Reads the speaker listing file according to the specification.
void changed(void *argument)
this is what I send to myself to notify my observers; It's not overridden in general. It results in the observers receiving update() calls < override evaluate to filter updates to the observer map
Definition: CGestalt.cpp:540
void normalize()
Definition: CPoint.cpp:423
COORD_TYPE x
Definition: CPoint.h:41
Represents a speaker as a position relative to the center of a space.
#define kPolar
Definition: CPoint.h:31
#define CSL_PI
Definition: CSL_Types.h:334
COORD_TYPE z
Definition: CPoint.h:41
void dump()
Print speaker information.
COORD_TYPE y
Definition: CPoint.h:41
vector< Speaker * > mSpeakers
Vector of pointers to the loudspeakers.
Definition: SpeakerLayout.h:71
static void setDefaultSpeakerLayout(SpeakerLayout *defaultLayout)
Use it to set a layout as default. Clients (e.g. a Panner) can then make use of this layout...
void dump()
If any of the speakers in the layout has an elevation other than 0, it returns true.
unsigned numSpeakers() const
Definition: SpeakerLayout.h:58
void set(int a, int b)
Definition: CPoint.h:73
static SpeakerLayout * defaultSpeakerLayout()
Returns a pointer to the default layout. If no default exists, it creates one.
void setRadius(float radius)
Specify the distance from the center of the coordinate space to the speaker.
void addSpeaker(float azimuth, float elevation=0.0, float radius=1.0)
Add a speaker specifying its position in degrees from the center of the listening space...
CPoint mPosition
SpeakerLayout & operator=(const SpeakerLayout &layout)
Overloaded "=" operator allows copying the layout.
Standard "Stereo Speaker Layout", where two speakers are positioned 30¼ left, 30¼ right and no elevat...
Definition: SpeakerLayout.h:86