VBAP GUI depends on signal-position (not parameter changes)
[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         SignalPositionChanged(); /* emit */
194 }
195
196 void 
197 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele) 
198 {
199         /* calculates gain factors using loudspeaker setup and given direction */
200         double cartdir[3];
201         double power;
202         int i,j,k;
203         double small_g;
204         double big_sm_g, gtmp[3];
205
206         spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);  
207         big_sm_g = -100000.0;
208
209         gains[0] = gains[1] = gains[2] = 0;
210         speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
211
212         for (i = 0; i < _speakers->n_tuples(); i++) {
213
214                 small_g = 10000000.0;
215
216                 for (j = 0; j < _speakers->dimension(); j++) {
217
218                         gtmp[j] = 0.0;
219
220                         for (k = 0; k < _speakers->dimension(); k++) {
221                                 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k]; 
222                         }
223
224                         if (gtmp[j] < small_g) {
225                                 small_g = gtmp[j];
226                         }
227                 }
228
229                 if (small_g > big_sm_g) {
230
231                         big_sm_g = small_g;
232
233                         gains[0] = gtmp[0]; 
234                         gains[1] = gtmp[1]; 
235
236                         speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
237                         speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
238
239                         if (_speakers->dimension() == 3) {
240                                 gains[2] = gtmp[2];
241                                 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
242                         } else {
243                                 gains[2] = 0.0;
244                                 speaker_ids[2] = -1;
245                         }
246                 }
247         }
248         
249         power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
250
251         if (power > 0) {
252                 gains[0] /= power; 
253                 gains[1] /= power;
254                 gains[2] /= power;
255         }
256 }
257
258 void
259 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
260 {
261         uint32_t n;
262         vector<Signal*>::iterator s;
263
264         assert (inbufs.count().n_audio() == _signals.size());
265
266         for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
267
268                 Signal* signal (*s);
269
270                 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
271
272                 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
273         }
274 }
275
276 void
277 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
278 {
279         Sample* const src = srcbuf.data();
280         Signal* signal (_signals[which]);
281
282         /* VBAP may distribute the signal across up to 3 speakers depending on
283            the configuration of the speakers.
284
285            But the set of speakers in use "this time" may be different from
286            the set of speakers "the last time". So we have up to 6 speakers
287            involved, and we have to interpolate so that those no longer
288            in use are rapidly faded to silence and those newly in use
289            are rapidly faded to their correct level. This prevents clicks
290            as we change the set of speakers used to put the signal in
291            a given position.
292
293            However, the speakers are represented by output buffers, and other
294            speakers may write to the same buffers, so we cannot use
295            anything here that will simply assign new (sample) values
296            to the output buffers - everything must be done via mixing
297            functions and not assignment/copying.
298         */
299
300         vector<double>::size_type sz = signal->gains.size();
301
302         assert (sz == obufs.count().n_audio());
303
304         int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
305         
306         /* set initial state of each output "record"
307          */
308
309         for (uint32_t o = 0; o < sz; ++o) {
310                 outputs[o] = 0;
311         }
312
313         /* for all outputs used this time and last time,
314            change the output record to show what has
315            happened.
316         */
317
318
319         for (int o = 0; o < 3; ++o) {
320                 if (signal->outputs[o] != -1) {
321                         /* used last time */
322                         outputs[signal->outputs[o]] |= 1;
323                 } 
324
325                 if (signal->desired_outputs[o] != -1) {
326                         /* used this time */
327                         outputs[signal->desired_outputs[o]] |= 1<<1;
328                 } 
329         }
330
331         /* at this point, we can test a speaker's status:
332
333            (*outputs[o] & 1)      <= in use before
334            (*outputs[o] & 2)      <= in use this time
335            (*outputs[o] & 3) == 3 <= in use both times
336             *outputs[o] == 0      <= not in use either time
337            
338         */
339
340         for (int o = 0; o < 3; ++o) {
341                 pan_t pan;
342                 int output = signal->desired_outputs[o];
343
344                 if (output == -1) {
345                         continue;
346                 }
347
348                 pan = gain_coefficient * signal->desired_gains[o];
349
350                 if (pan == 0.0 && signal->gains[output] == 0.0) {
351                         
352                         /* nothing deing delivered to this output */
353
354                         signal->gains[output] = 0.0;
355                         
356                 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
357                         
358                         /* signal to this output but the gain coefficient has changed, so 
359                            interpolate between them.
360                         */
361
362                         AudioBuffer& buf (obufs.get_audio (output));
363                         buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
364                         signal->gains[output] = pan;
365
366                 } else {
367                         
368                         /* signal to this output, same gain as before so just copy with gain
369                          */
370                            
371                         mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
372                         signal->gains[output] = pan;
373                 }
374         }
375
376         /* clean up the outputs that were used last time but not this time
377          */
378
379         for (uint32_t o = 0; o < sz; ++o) {
380                 if (outputs[o] == 1) {
381                         /* take signal and deliver with a rapid fade out
382                          */
383                         AudioBuffer& buf (obufs.get_audio (o));
384                         buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
385                         signal->gains[o] = 0.0;
386                 }
387         }
388
389         /* note that the output buffers were all silenced at some point
390            so anything we didn't write to with this signal (or any others)
391            is just as it should be.
392         */
393 }
394
395 void 
396 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
397                                      framepos_t /*start*/, framepos_t /*end*/, 
398                                      pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
399 {
400         /* XXX to be implemented */
401 }
402
403 XMLNode&
404 VBAPanner::get_state ()
405 {
406         XMLNode& node (Panner::get_state());
407         node.add_property (X_("uri"), _descriptor.panner_uri);
408         /* this is needed to allow new sessions to load with old Ardour: */
409         node.add_property (X_("type"), _descriptor.name);
410         return node;
411 }
412
413 Panner*
414 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
415 {
416         return new VBAPanner (p, s);
417 }
418
419 ChanCount
420 VBAPanner::in() const
421 {
422         return ChanCount (DataType::AUDIO, _signals.size());
423 }
424
425 ChanCount
426 VBAPanner::out() const
427 {
428         return ChanCount (DataType::AUDIO, _speakers->n_speakers());
429 }
430
431 std::set<Evoral::Parameter> 
432 VBAPanner::what_can_be_automated() const
433 {
434         set<Evoral::Parameter> s;
435         s.insert (Evoral::Parameter (PanAzimuthAutomation));
436         if (_signals.size() > 1) {
437                 s.insert (Evoral::Parameter (PanWidthAutomation));
438         }
439         if (_speakers->dimension() == 3) {
440                 s.insert (Evoral::Parameter (PanElevationAutomation));
441         }
442         return s;
443 }
444         
445 string
446 VBAPanner::describe_parameter (Evoral::Parameter p)
447 {
448         switch (p.type()) {
449         case PanAzimuthAutomation:
450                 return _("Direction");
451         case PanWidthAutomation:
452                 return _("Diffusion");
453         case PanElevationAutomation:
454                 return _("Elevation");
455         default:
456                 return _pannable->describe_parameter (p);
457         }
458 }
459
460 string 
461 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
462 {
463         /* DO NOT USE LocaleGuard HERE */
464         double val = ac->get_value();
465
466         switch (ac->parameter().type()) {
467         case PanAzimuthAutomation: /* direction */
468                 return string_compose (_("%1\u00B0"), int (rint (val * 360.0)));
469                 
470         case PanWidthAutomation: /* diffusion */
471                 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
472
473         case PanElevationAutomation: /* elevation */
474                 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
475                 
476         default:
477                 return _pannable->value_as_string (ac);
478         }
479 }
480
481 AngularVector
482 VBAPanner::signal_position (uint32_t n) const
483 {
484         if (n < _signals.size()) {
485                 return _signals[n]->direction;
486         }
487
488         return AngularVector();
489 }
490
491 boost::shared_ptr<Speakers>
492 VBAPanner::get_speakers () const 
493 {
494         return _speakers->parent();
495 }
496
497 void
498 VBAPanner::set_position (double p)
499 {
500         /* map into 0..1 range */
501         int over = p;
502         over -= (p >= 0) ? 0 : 1;
503         p -= (double)over;
504         _pannable->pan_azimuth_control->set_value (p);
505 }
506
507 void
508 VBAPanner::set_width (double w)
509 {
510         _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
511 }
512
513 void
514 VBAPanner::set_elevation (double e)
515 {
516         _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)));
517 }
518
519 void
520 VBAPanner::reset ()
521 {
522         set_position (0);
523         set_width (1);
524         set_elevation (0);
525
526         update ();
527 }