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