df1f5d0876d1ae25609bbc13ef1e20b7530c29a6
[ardour.git] / libs / panners / vbap / vbap.cc
1 #include <cmath>
2 #include <cstdlib>
3 #include <cstdio>
4 #include <cstring>
5
6 #include <iostream>
7 #include <string>
8
9 #include "pbd/cartesian.h"
10
11 #include "ardour/amp.h"
12 #include "ardour/audio_buffer.h"
13 #include "ardour/buffer_set.h"
14 #include "ardour/pan_controllable.h"
15 #include "ardour/pannable.h"
16 #include "ardour/speakers.h"
17
18 #include "vbap.h"
19 #include "vbap_speakers.h"
20
21 using namespace PBD;
22 using namespace ARDOUR;
23 using namespace std;
24
25 static PanPluginDescriptor _descriptor = {
26         "VBAP 2D panner",
27         -1, -1,
28         VBAPanner::factory
29 };
30
31 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
32
33 VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n, uint32_t n_speakers)
34 {
35         resize_gains (n_speakers);
36
37         desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
38         outputs[0] = outputs[1] = outputs[2] = -1;
39         desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
40 }
41
42 void
43 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
44 {
45         gains.assign (n, 0.0);
46 }        
47
48 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
49         : Panner (p)
50         , _speakers (new VBAPSpeakers (s))
51 {
52         _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
53         _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
54
55         update ();
56 }
57
58 VBAPanner::~VBAPanner ()
59 {
60         clear_signals ();
61 }
62
63 void
64 VBAPanner::clear_signals ()
65 {
66         for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
67                 delete *i;
68         }
69         _signals.clear ();
70 }
71
72 void
73 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
74 {
75         uint32_t n = in.n_audio();
76
77         clear_signals ();
78
79         for (uint32_t i = 0; i < n; ++i) {
80                 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
81                 _signals.push_back (s);
82                 
83         }
84
85         update ();
86 }
87
88 void
89 VBAPanner::update ()
90 {
91         /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
92          */
93
94         /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
95          */
96         double center = _pannable->pan_azimuth_control->get_value() * 360.0;
97
98         if (_signals.size() > 1) {
99
100                 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
101                    so that a width of 1 corresponds to a signal equally present from all directions, 
102                    and a width of zero corresponds to a point source from the "center" (above) point
103                    on the perimeter of the speaker array.
104                 */
105
106                 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
107                 
108                 double min_dir = center - (w/2.0);
109                 if (min_dir < 0) {
110                         min_dir = 360.0 + min_dir; // its already negative
111                 }
112                 min_dir = max (min (min_dir, 360.0), 0.0);
113                 
114                 double max_dir = center + (w/2.0);
115                 if (max_dir > 360.0) {
116                         max_dir = max_dir - 360.0;
117                 }
118                 max_dir = max (min (max_dir, 360.0), 0.0);
119                 
120                 if (max_dir < min_dir) {
121                         swap (max_dir, min_dir);
122                 }
123
124                 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
125                 double signal_direction = min_dir;
126
127                 if (w >= 0.0) {
128
129                         /* positive width - normal order of signal spread */
130
131                         for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
132                         
133                                 Signal* signal = *s;
134                                 
135                                 signal->direction = AngularVector (signal_direction, 0.0);
136                                 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
137                                 signal_direction += degree_step_per_signal;
138                         }
139                 } else {
140
141                         /* inverted width - reverse order of signal spread */
142
143                         for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
144                         
145                                 Signal* signal = *s;
146                                 
147                                 signal->direction = AngularVector (signal_direction, 0.0);
148                                 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
149                                 signal_direction += degree_step_per_signal;
150                         }
151                 }
152
153         } else if (_signals.size() == 1) {
154
155                 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
156
157                 Signal* s = _signals.front();
158                 s->direction = AngularVector (center, 0);
159                 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
160         }
161 }
162
163 void 
164 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele) 
165 {
166         /* calculates gain factors using loudspeaker setup and given direction */
167         double cartdir[3];
168         double power;
169         int i,j,k;
170         double small_g;
171         double big_sm_g, gtmp[3];
172
173         spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);  
174         big_sm_g = -100000.0;
175
176         gains[0] = gains[1] = gains[2] = 0;
177         speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
178
179         for (i = 0; i < _speakers->n_tuples(); i++) {
180
181                 small_g = 10000000.0;
182
183                 for (j = 0; j < _speakers->dimension(); j++) {
184
185                         gtmp[j] = 0.0;
186
187                         for (k = 0; k < _speakers->dimension(); k++) {
188                                 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k]; 
189                         }
190
191                         if (gtmp[j] < small_g) {
192                                 small_g = gtmp[j];
193                         }
194                 }
195
196                 if (small_g > big_sm_g) {
197
198                         big_sm_g = small_g;
199
200                         gains[0] = gtmp[0]; 
201                         gains[1] = gtmp[1]; 
202
203                         speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
204                         speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
205
206                         if (_speakers->dimension() == 3) {
207                                 gains[2] = gtmp[2];
208                                 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
209                         } else {
210                                 gains[2] = 0.0;
211                                 speaker_ids[2] = -1;
212                         }
213                 }
214         }
215         
216         power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
217
218         if (power > 0) {
219                 gains[0] /= power; 
220                 gains[1] /= power;
221                 gains[2] /= power;
222         }
223 }
224
225 void
226 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
227 {
228         uint32_t n;
229         vector<Signal*>::iterator s;
230
231         assert (inbufs.count().n_audio() == _signals.size());
232
233         for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
234
235                 Signal* signal (*s);
236
237                 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
238
239                 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
240         }
241 }
242
243 void
244 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
245 {
246         Sample* const src = srcbuf.data();
247         Signal* signal (_signals[which]);
248
249         /* VBAP may distribute the signal across up to 3 speakers depending on
250            the configuration of the speakers.
251
252            But the set of speakers in use "this time" may be different from
253            the set of speakers "the last time". So we have up to 6 speakers
254            involved, and we have to interpolate so that those no longer
255            in use are rapidly faded to silence and those newly in use
256            are rapidly faded to their correct level. This prevents clicks
257            as we change the set of speakers used to put the signal in
258            a given position.
259
260            However, the speakers are represented by output buffers, and other
261            speakers may write to the same buffers, so we cannot use
262            anything here that will simply assign new (sample) values
263            to the output buffers - everything must be done via mixing
264            functions and not assignment/copying.
265         */
266
267         vector<double>::size_type sz = signal->gains.size();
268
269         assert (sz == obufs.count().n_audio());
270
271         int8_t outputs[sz]; // on the stack, no malloc
272         
273         /* set initial state of each output "record"
274          */
275
276         for (uint32_t o = 0; o < sz; ++o) {
277                 outputs[o] = 0;
278         }
279
280         /* for all outputs used this time and last time,
281            change the output record to show what has
282            happened.
283         */
284
285
286         for (int o = 0; o < 3; ++o) {
287                 if (signal->outputs[o] != -1) {
288                         /* used last time */
289                         outputs[signal->outputs[o]] |= 1;
290                 } 
291
292                 if (signal->desired_outputs[o] != -1) {
293                         /* used this time */
294                         outputs[signal->desired_outputs[o]] |= 1<<1;
295                 } 
296         }
297
298         /* at this point, we can test a speaker's status:
299
300            (outputs[o] & 1)      <= in use before
301            (outputs[o] & 2)      <= in use this time
302            (outputs[o] & 3) == 3 <= in use both times
303             outputs[o] == 0      <= not in use either time
304            
305         */
306
307         for (int o = 0; o < 3; ++o) {
308                 pan_t pan;
309                 int output = signal->desired_outputs[o];
310
311                 if (output == -1) {
312                         continue;
313                 }
314
315                 pan = gain_coefficient * signal->desired_gains[o];
316
317                 if (pan == 0.0 && signal->gains[output] == 0.0) {
318                         
319                         /* nothing deing delivered to this output */
320
321                         signal->gains[output] = 0.0;
322                         
323                 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
324                         
325                         /* signal to this output but the gain coefficient has changed, so 
326                            interpolate between them.
327                         */
328
329                         AudioBuffer& buf (obufs.get_audio (output));
330                         buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
331                         signal->gains[output] = pan;
332
333                 } else {
334                         
335                         /* signal to this output, same gain as before so just copy with gain
336                          */
337                            
338                         mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
339                         signal->gains[output] = pan;
340                 }
341         }
342
343         /* clean up the outputs that were used last time but not this time
344          */
345
346         for (uint32_t o = 0; o < sz; ++o) {
347                 if (outputs[o] == 1) {
348                         /* take signal and deliver with a rapid fade out
349                          */
350                         AudioBuffer& buf (obufs.get_audio (o));
351                         buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
352                         signal->gains[o] = 0.0;
353                 }
354         }
355
356         /* note that the output buffers were all silenced at some point
357            so anything we didn't write to with this signal (or any others)
358            is just as it should be.
359         */
360 }
361
362 void 
363 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
364                                      framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
365 {
366 }
367
368 XMLNode&
369 VBAPanner::get_state ()
370 {
371         return state (true);
372 }
373
374 XMLNode&
375 VBAPanner::state (bool full_state)
376 {
377         XMLNode& node (Panner::get_state());
378         node.add_property (X_("type"), _descriptor.name);
379         return node;
380 }
381
382 int
383 VBAPanner::set_state (const XMLNode& node, int /*version*/)
384 {
385         return 0;
386 }
387
388 Panner*
389 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
390 {
391         return new VBAPanner (p, s);
392 }
393
394 ChanCount
395 VBAPanner::in() const
396 {
397         return ChanCount (DataType::AUDIO, _signals.size());
398 }
399
400 ChanCount
401 VBAPanner::out() const
402 {
403         return ChanCount (DataType::AUDIO, _speakers->n_speakers());
404 }
405
406 std::set<Evoral::Parameter> 
407 VBAPanner::what_can_be_automated() const
408 {
409         set<Evoral::Parameter> s;
410         s.insert (Evoral::Parameter (PanAzimuthAutomation));
411         if (_signals.size() > 1) {
412                 s.insert (Evoral::Parameter (PanWidthAutomation));
413         }
414         return s;
415 }
416         
417 string
418 VBAPanner::describe_parameter (Evoral::Parameter p)
419 {
420         switch (p.type()) {
421         case PanAzimuthAutomation:
422                 return _("Direction");
423         case PanWidthAutomation:
424                 return _("Diffusion");
425         default:
426                 return _pannable->describe_parameter (p);
427         }
428 }
429
430 string 
431 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
432 {
433         /* DO NOT USE LocaleGuard HERE */
434         double val = ac->get_value();
435
436         switch (ac->parameter().type()) {
437         case PanAzimuthAutomation: /* direction */
438                 return string_compose (_("%1"), int (rint (val * 360.0)));
439                 
440         case PanWidthAutomation: /* diffusion */
441                 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
442                 
443         default:
444                 return _pannable->value_as_string (ac);
445         }
446 }
447
448 AngularVector
449 VBAPanner::signal_position (uint32_t n) const
450 {
451         if (n < _signals.size()) {
452                 return _signals[n]->direction;
453         }
454
455         return AngularVector();
456 }
457
458 boost::shared_ptr<Speakers>
459 VBAPanner::get_speakers () const 
460 {
461         return _speakers->parent();
462 }
463
464 void
465 VBAPanner::set_position (double p)
466 {
467         if (p < 0.0) {
468                 p = 1.0 + p;
469         }
470
471         if (p > 1.0) {
472                 p = fmod (p, 1.0);
473         } 
474
475         _pannable->pan_azimuth_control->set_value (p);
476 }
477
478 void
479 VBAPanner::set_width (double w)
480 {
481         _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
482 }