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