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