Allolib  1.0
C++ Components For Interactive Multimedia
al_Ambisonics.hpp
1 #ifndef INCLUDE_AL_AMBISONICS_HPP
2 #define INCLUDE_AL_AMBISONICS_HPP
3 
4 /* Allocore --
5  Multimedia / virtual environment application class library
6 
7  Copyright (C) 2009. AlloSphere Research Group, Media Arts & Technology,
8  UCSB. Copyright (C) 2012. The Regents of the University of California. All
9  rights reserved.
10 
11  Redistribution and use in source and binary forms, with or without
12  modification, are permitted provided that the following conditions are
13  met:
14 
15  Redistributions of source code must retain the above copyright
16  notice, this list of conditions and the following disclaimer.
17 
18  Redistributions in binary form must reproduce the above
19  copyright notice, this list of conditions and the following disclaimer in the
20  documentation and/or other materials provided with the
21  distribution.
22 
23  Neither the name of the University of California nor the names
24  of its contributors may be used to endorse or promote products derived from
25  this software without specific prior written permission.
26 
27  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
28  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
31  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
34  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
35  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
36  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 
39 
40  File description:
41  Higher order Ambisonics encoding/decoding
42 
43  File author(s):
44  Graham Wakefield, 2010, grrrwaaa@gmail.com
45  Lance Putnam, 2010, putnam.lance@gmail.com
46 
47  Based on prior work also contributed to by:
48  Jorge Castellanos
49  Florian Hollerweger
50 */
51 
52 #include <stdio.h>
53 
54 #include <iostream>
55 
56 #include "al/math/al_Vec.hpp"
57 #include "al/sound/al_Spatializer.hpp"
58 #include "al/sound/al_Speaker.hpp"
59 #include "al/spatial/al_DistAtten.hpp"
60 #include "al/spatial/al_Pose.hpp"
61 
62 /*
63  TODO: near-field compensation
64  acknowledges that the speakers are at a finite distance from the
65  listener. This a simple Minimum Phase HP filter (on directional signals, not
66  the W component) to deal with "proximity effect" because the speakers are
67  'near' the listener.
68 
69  f = c / (2 * PI * d) = 54.6 / d Hz c : speed of sound 343 m/s
70  d : speaker distance m
71 
72  Should be IIR, because it mostly affects LF.
73 
74  If we pass speaker distance, then we might also want to attenuate/delay
75  speakers for irregular layouts.
76 
77  @see "Near Field filters for Higher Order Ambisonics" Fons ADRIAENSEN
78  http://kokkinizita.linuxaudio.org/papers/index.html
79 */
80 
107 namespace al {
108 
110 
114 class AmbiBase {
115 public:
118  AmbiBase(int dim, int order);
119 
120  virtual ~AmbiBase();
121 
123  int dim() const { return mDim; }
124 
126  int order() const { return mOrder; }
127 
129  const float *weights() const { return mWeights; }
130 
132  int channels() const { return mChannels; }
133 
135  void dim(int dim);
136 
138  void order(int order);
139 
141  virtual void onChannelsChange() {}
142 
143  static int channelsToUniformOrder(int channels);
144 
147  static void encodeWeightsFuMa(float *weights, int dim, int order,
148  float azimuth, float elevation);
149 
152  static void encodeWeightsFuMa(float *ws, int dim, int order, float x, float y,
153  float z);
154 
156  static void encodeWeightsFuMa16(float *weights, float azimuth,
157  float elevation);
159  static void encodeWeightsFuMa16(float *ws, float x, float y, float z);
160 
161  static int orderToChannels(int dim, int order);
162  static int orderToChannelsH(int orderH);
163  static int orderToChannelsV(int orderV);
164 
165  static int channelsToOrder(int channels);
166  static int channelsToDimensions(int channels);
167 
168 protected:
169  int mDim; // dimensions - 2d or 3d
170  int mOrder; // order - 0th, 1st, 2nd, or 3rd
171  int mChannels; // cached for efficiency
172  float *mWeights; // weights for each ambi channel
173 
174  template <typename T> static void resize(T *&a, int n);
175 };
176 
180 class AmbiDecode : public AmbiBase {
181 public:
186  AmbiDecode(int dim, int order, int numSpeakers, int flavor = 1);
187 
188  virtual ~AmbiDecode();
189 
195  virtual void decode(float *dec, const float *enc, int numDecFrames) const;
196 
197  float decodeWeight(int speaker, int channel) const {
198  return mWeights[channel] * mDecodeMatrix[speaker * channels() + channel];
199  }
200 
202  int flavor() const { return mFlavor; }
203 
205  int numSpeakers() const { return mNumSpeakers; }
206 
207  void print(std::ostream &stream) const;
208 
210  void flavor(int type);
211 
213  void numSpeakers(int num);
214 
215  void setSpeakerRadians(int index, int deviceChannel, float azimuth,
216  float elevation, float amp = 1.f);
217 
218  void setSpeaker(int index, int deviceChannel, float azimuth,
219  float elevation = 0, float amp = 1.f);
220  // void zero(); ///< Zeroes out internal
221  // ambisonic frame.
222 
223  void setSpeakers(Speakers *spkrs);
224  void setSpeakers(Speakers &spkrs);
225 
226  // float * azimuths(); ///< Returns pointer to
227  // speaker azimuths.
228  // float * elevations(); ///< Returns pointer to speaker
229  // elevations. float * frame() const; ///< Returns
230  // pointer to ambisonic channel frame used by decode(int)
231 
233  Speaker &speaker(int num) { return mSpeakers[num]; }
234 
235  virtual void onChannelsChange();
236 
237 protected:
238  int mNumSpeakers;
239  int mFlavor; // decode flavor
240  float *mDecodeMatrix; // deccoding matrix for each ambi channel & speaker
241  // cols are channels and rows are speakers
242  float mWOrder[5]; // weights for each order
243  Speakers mSpeakers;
244  // float * mPositions; // speakers' azimuths + elevations
245  // float * mFrame; // an ambisonic channel frame used for
246  // decode(int)
247 
248  void updateChanWeights();
249  void resizeArrays(int numChannels, int numSpeakers);
250 
251  float decode(float *encFrame, int encNumChannels,
252  int speakerNum); // is this useful?
253 
254  static float flavorWeights[4][5][5];
255 };
256 
260 class AmbiEncode : public AmbiBase {
261 public:
265 
266  // /// Encode input sample and set decoder frame.
267  // void encode (const AmbiDecode &dec, float input);
268  //
269  // /// Encode input sample and add to decoder frame.
270  // void encodeAdd(const AmbiDecode &dec, float input);
271 
273 
278  void encode(float *ambiChans, int numFrames, int timeIndex,
279  float timeSample) const;
280 
282 
286  void encode(float *ambiChans, const float *input, int numFrames);
287 
289 
295  template <class XYZ>
296  void encode(float *ambiChans, const XYZ *dir, const float *input,
297  int numFrames);
298 
300  void direction(float az, float el);
301 
304  void direction(Vec3f direction);
305 
308  void direction(float x, float y, float z);
309 
310  void print(std::ostream &stream);
311 };
312 
317 public:
319  AmbisonicsSpatializer(Speakers &sl, int dim = 2, int order = 1,
320  int flavor = 1);
321 
322  void zeroAmbi();
323 
324  void configure(int dim, int order, int flavor);
325 
326  float *ambiChans(unsigned channel = 0);
327 
328  virtual void compile() override;
329 
330  virtual void numFrames(unsigned int v) override;
331 
332  void numSpeakers(int num);
333 
334  void setSpeakerLayout(const Speakers &speakers);
335 
336  virtual void prepare(AudioIOData &io) override;
337 
338  virtual void renderBuffer(AudioIOData &io, const Pose &listeningPose,
339  const float *samples,
340  const unsigned int &numFrames) override;
341 
342  virtual void renderSample(AudioIOData &io, const Pose &listeningPose,
343  const float &sample,
344  const unsigned int &frameIndex) override;
345 
346  virtual void finalize(AudioIOData &io) override;
347 
348  virtual void print(std::ostream &stream = std::cout) override;
349 
350 private:
351  AmbiDecode mDecoder;
352  AmbiEncode mEncoder;
353  std::vector<float> mAmbiDomainChannels;
354  // Listener* mListener;
355 };
356 
357 // Implementation ______________________________________________________________
358 
359 // AmbiBase
360 inline int AmbiBase::orderToChannels(int dim, int order) {
361  int chans = orderToChannelsH(order);
362  return dim == 2 ? chans : chans + orderToChannelsV(order);
363 }
364 
365 inline int AmbiBase::orderToChannelsH(int orderH) { return (orderH << 1) + 1; }
366 inline int AmbiBase::orderToChannelsV(int orderV) { return orderV * orderV; }
367 
368 inline int AmbiBase::channelsToOrder(int channels) {
369  int order = -1;
370  switch (channels) {
371  case 3:
372  case 4:
373  order = 1;
374  break;
375  case 9:
376  order = 2;
377  break;
378  case 16:
379  order = 3;
380  break;
381  default:
382  order = -1;
383  }
384  return order;
385 }
386 
387 inline int AmbiBase::channelsToDimensions(int channels) {
388  int dim = 3;
389  switch (channels) {
390  case 3:
391  dim = 2;
392  break;
393  case 4:
394  case 9:
395  case 16:
396  dim = 3;
397  break;
398  default:
399  dim = -1;
400  }
401  return dim;
402 }
403 
404 template <typename T> void AmbiBase::resize(T *&a, int n) {
405  delete[] a;
406  a = new T[n];
407  memset(a, 0, n * sizeof(T));
408 }
409 
410 // AmbiDecode
411 
412 inline float AmbiDecode::decode(float *encFrame, int encNumChannels,
413  int speakerNum) {
414  float smp = 0;
415  float *dec = mDecodeMatrix + speakerNum * channels();
416  float *wc = mWeights;
417  for (int i = 0; i < encNumChannels; ++i)
418  smp += *dec++ * *wc++ * *encFrame++;
419  return smp;
420 }
421 
422 // inline float AmbiDecode::decode(int speakerNum){
423 // return decode(mFrame, channels(), speakerNum);
424 //}
425 
426 // inline float * AmbiDecode::azimuths(){ return mPositions; }
427 // inline float * AmbiDecode::elevations(){ return mPositions + mNumSpeakers; }
428 // inline float * AmbiDecode::frame() const { return mFrame; }
429 
430 // AmbiEncode
431 // inline void AmbiEncode::encode(const AmbiDecode &dec, float input){
432 // for(int c=0; c<dec.channels(); ++c) dec.frame()[c] = weights()[c] *
433 // input;
434 //}
435 //
436 // inline void AmbiEncode::encodeAdd(const AmbiDecode &dec, float input){
437 // for(int c=0; c<dec.channels(); ++c) dec.frame()[c] += weights()[c] *
438 // input;
439 //}
440 
441 inline void AmbiEncode::direction(float az, float el) {
442  AmbiBase::encodeWeightsFuMa(mWeights, mDim, mOrder, az, el);
443 }
444 
445 inline void AmbiEncode::direction(Vec3f vector) {
446  AmbiBase::encodeWeightsFuMa(mWeights, mDim, mOrder, vector.x, vector.y,
447  vector.z);
448 }
449 
450 inline void AmbiEncode::direction(float x, float y, float z) {
451  AmbiBase::encodeWeightsFuMa(mWeights, mDim, mOrder, x, y, z);
452 }
453 
454 inline void AmbiEncode::encode(float *ambiChans, int numFrames, int timeIndex,
455  float timeSample) const {
456 // "Iterate" through spherical harmonics using Duff's device.
457 // This requires only a simple jump per time sample.
458 #define CS(chanindex) \
459  case chanindex: \
460  ambiChans[chanindex * numFrames + timeIndex] += \
461  weights()[chanindex] * timeSample;
462  int ch = channels() - 1;
463  switch (ch) {
464  CS(15)
465  CS(14)
466  CS(13)
467  CS(12)
468  CS(11) CS(10) CS(9) CS(8) CS(7) CS(6) CS(5) CS(4) CS(3) CS(2) CS(1)
469  CS(0) default:;
470  }
471 #undef CS
472 }
473 
474 inline void AmbiEncode::encode(float *ambiChans, const float *input,
475  int numFrames) {
476  float *pAmbi = ambiChans; // non-interleaved ambi buffers, we can use fast
477  // pointer arithmetic
478 
479  for (int c = 0; c < channels(); ++c) {
480  const float *pInput = input;
481  float weight = weights()[c];
482  for (int i = 0; i < numFrames; ++i) {
483  *pAmbi++ += weight * *pInput++;
484  }
485  }
486 }
487 
488 template <class XYZ>
489 void AmbiEncode::encode(float *ambiChans, const XYZ *dir, const float *input,
490  int numFrames) {
491  // TODO: how can we efficiently encode a moving source?
492 
493  // Changing the position recomputes ALL the spherical harmonic weights.
494  // Ideally we only want to change the position for each time sample.
495  // However, for our loops to be most efficient, we want the inner loop
496  // to process over time since it will have around 64-512 iterations
497  // while the spatial loop will have at most 16 iterations.
498 
499  /* outer-space, inner-time
500  for(int c=0; c<channels(); ++c){
501  float * ambi = ambiChans[c];
502  //T
503  for(int i=0; i<numFrames; ++i){
504  position(pos[i][0], pos[i][1], pos[i][2]);
505  ambi[i] += weights()[c] * input[i];
506  }
507  }*/
508 
509  // outer-time, inner-space
510  for (int i = 0; i < numFrames; ++i) {
511  direction(dir[i][0], dir[i][1], dir[i][2]);
512  encode(ambiChans, numFrames, i, input[i]);
513  /*for(int c=0; c<channels(); ++c){
514  ambiChans[c][i] += weights()[c] * input[i];
515  }*/
516  }
517 }
518 
519 inline float *AmbisonicsSpatializer::ambiChans(unsigned channel) {
520  assert(mNumFrames != 0 && "number of frames not set.");
521  return &mAmbiDomainChannels[channel * mNumFrames];
522 }
523 
524 } // namespace al
525 #endif
static void encodeWeightsFuMa(float *ws, int dim, int order, float x, float y, float z)
void dim(int dim)
Set the number of dimensions.
static void encodeWeightsFuMa(float *weights, int dim, int order, float azimuth, float elevation)
static void encodeWeightsFuMa16(float *ws, float x, float y, float z)
(x,y,z unit vector in the listener's coordinate frame)
void order(int order)
Set the order.
int channels() const
Returns total number of Ambisonic domain (B-format) channels.
AmbiBase(int dim, int order)
const float * weights() const
Get Ambisonic channel weights.
int dim() const
Get number dimensions.
static void encodeWeightsFuMa16(float *weights, float azimuth, float elevation)
Brute force 3rd order. Weights must be of size 16.
virtual void onChannelsChange()
Called whenever the number of Ambisonic channels changes.
int order() const
Get order.
void numSpeakers(int num)
Set number of speakers. Positions are zeroed upon resize.
AmbiDecode(int dim, int order, int numSpeakers, int flavor=1)
int numSpeakers() const
Returns number of speakers.
int flavor() const
Returns decode flavor.
Speaker & speaker(int num)
Get speaker.
virtual void onChannelsChange()
Called whenever the number of Ambisonic channels changes.
void flavor(int type)
Set decoding algorithm.
virtual void decode(float *dec, const float *enc, int numDecFrames) const
AmbiEncode(int dim, int order)
void encode(float *ambiChans, int numFrames, int timeIndex, float timeSample) const
Encode a single time sample.
void direction(float az, float el)
Set spherical direction of source to be encoded.
virtual void print(std::ostream &stream=std::cout) override
Print out information about spatializer.
virtual void renderSample(AudioIOData &io, const Pose &listeningPose, const float &sample, const unsigned int &frameIndex) override
Render audio sample in position.
virtual void compile() override
virtual void numFrames(unsigned int v) override
Set number of frames.
virtual void finalize(AudioIOData &io) override
virtual void renderBuffer(AudioIOData &io, const Pose &listeningPose, const float *samples, const unsigned int &numFrames) override
Render audio buffer in position.
virtual void prepare(AudioIOData &io) override
A local coordinate frame.
Definition: al_Pose.hpp:63
int numSpeakers() const
Get number of speakers.
Definition: al_App.hpp:23
std::vector< Speaker > Speakers
A set of speakers.
Definition: al_Speaker.hpp:101