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