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