fix bug when interpolating VBAP gains, and remove debug output from that code
[ardour.git] / libs / panners / vbap / vbap.cc
1 #include <cmath>
2 #include <cstdlib>
3 #include <cstdio>
4 #include <cstring>
5
6 #include <iostream>
7 #include <string>
8
9 #include "pbd/cartesian.h"
10
11 #include "ardour/amp.h"
12 #include "ardour/audio_buffer.h"
13 #include "ardour/buffer_set.h"
14 #include "ardour/pan_controllable.h"
15 #include "ardour/pannable.h"
16 #include "ardour/speakers.h"
17
18 #include "vbap.h"
19 #include "vbap_speakers.h"
20
21 using namespace PBD;
22 using namespace ARDOUR;
23 using namespace std;
24
25 static PanPluginDescriptor _descriptor = {
26         "VBAP 2D panner",
27         -1, -1,
28         VBAPanner::factory
29 };
30
31 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
32
33 VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n, uint32_t n_speakers)
34 {
35         resize_gains (n_speakers);
36
37         desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
38         outputs[0] = outputs[1] = outputs[2] = -1;
39         desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
40 }
41
42 void
43 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
44 {
45         gains.assign (n, 0.0);
46 }        
47
48 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
49         : Panner (p)
50         , _speakers (new VBAPSpeakers (s))
51 {
52         _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
53         _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
54
55         update ();
56 }
57
58 VBAPanner::~VBAPanner ()
59 {
60         clear_signals ();
61 }
62
63 void
64 VBAPanner::clear_signals ()
65 {
66         for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
67                 delete *i;
68         }
69         _signals.clear ();
70 }
71
72 void
73 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
74 {
75         uint32_t n = in.n_audio();
76
77         clear_signals ();
78
79         for (uint32_t i = 0; i < n; ++i) {
80                 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
81                 _signals.push_back (s);
82                 
83         }
84
85         update ();
86 }
87
88 void
89 VBAPanner::update ()
90 {
91         /* recompute signal directions based on panner azimuth and width (diffusion) parameters)
92          */
93
94         /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
95          */
96
97         double center = _pannable->pan_azimuth_control->get_value() * 360.0;
98
99         /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
100            so that a width of 1 corresponds to a signal equally present from all directions, 
101            and a width of zero corresponds to a point source from the "center" (above)
102         */
103
104         double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
105
106         double min_dir = center - w;
107         min_dir = max (min (min_dir, 360.0), 0.0);
108
109         double max_dir = center + w;
110         max_dir = max (min (max_dir, 360.0), 0.0);
111
112         double degree_step_per_signal = (max_dir - min_dir) / _signals.size();
113         double signal_direction = min_dir;
114
115         for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
116
117                 Signal* signal = *s;
118
119                 signal->direction = AngularVector (signal_direction, 0.0);
120                 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
121                 signal_direction += degree_step_per_signal;
122         }
123 }
124
125 void 
126 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele) 
127 {
128         /* calculates gain factors using loudspeaker setup and given direction */
129         double cartdir[3];
130         double power;
131         int i,j,k;
132         double small_g;
133         double big_sm_g, gtmp[3];
134
135         azi_ele_to_cart (azi,ele, cartdir[0], cartdir[1], cartdir[2]);  
136         big_sm_g = -100000.0;
137
138         gains[0] = gains[1] = gains[2] = 0;
139         speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
140
141         for (i = 0; i < _speakers->n_tuples(); i++) {
142
143                 small_g = 10000000.0;
144
145                 for (j = 0; j < _speakers->dimension(); j++) {
146
147                         gtmp[j] = 0.0;
148
149                         for (k = 0; k < _speakers->dimension(); k++) {
150                                 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k]; 
151                         }
152
153                         if (gtmp[j] < small_g) {
154                                 small_g = gtmp[j];
155                         }
156                 }
157
158                 if (small_g > big_sm_g) {
159
160                         big_sm_g = small_g;
161
162                         gains[0] = gtmp[0]; 
163                         gains[1] = gtmp[1]; 
164
165                         speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
166                         speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
167
168                         if (_speakers->dimension() == 3) {
169                                 gains[2] = gtmp[2];
170                                 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
171                         } else {
172                                 gains[2] = 0.0;
173                                 speaker_ids[2] = -1;
174                         }
175                 }
176         }
177         
178         power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
179
180         if (power > 0) {
181                 gains[0] /= power; 
182                 gains[1] /= power;
183                 gains[2] /= power;
184         }
185 }
186
187 void
188 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
189 {
190         uint32_t n;
191         vector<Signal*>::iterator s;
192
193         assert (inbufs.count().n_audio() == _signals.size());
194
195         for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
196
197                 Signal* signal (*s);
198
199                 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
200
201                 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
202         }
203 }
204
205 void
206 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
207 {
208         Sample* const src = srcbuf.data();
209         Signal* signal (_signals[which]);
210
211         /* VBAP may distribute the signal across up to 3 speakers depending on
212            the configuration of the speakers.
213
214            But the set of speakers in use "this time" may be different from
215            the set of speakers "the last time". So we have up to 6 speakers
216            involved, and we have to interpolate so that those no longer
217            in use are rapidly faded to silence and those newly in use
218            are rapidly faded to their correct level. This prevents clicks
219            as we change the set of speakers used to put the signal in
220            a given position.
221
222            However, the speakers are represented by output buffers, and other
223            speakers may write to the same buffers, so we cannot use
224            anything here that will simply assign new (sample) values
225            to the output buffers - everything must be done via mixing
226            functions and not assignment/copying.
227         */
228
229         vector<double>::size_type sz = signal->gains.size();
230
231         assert (sz == obufs.count().n_audio());
232
233         int8_t outputs[sz]; // on the stack, no malloc
234         
235         /* set initial state of each output "record"
236          */
237
238         for (uint32_t o = 0; o < sz; ++o) {
239                 outputs[o] = 0;
240         }
241
242         /* for all outputs used this time and last time,
243            change the output record to show what has
244            happened.
245         */
246
247
248         for (int o = 0; o < 3; ++o) {
249                 if (signal->outputs[o] != -1) {
250                         /* used last time */
251                         outputs[signal->outputs[o]] |= 1;
252                 } 
253
254                 if (signal->desired_outputs[o] != -1) {
255                         /* used this time */
256                         outputs[signal->desired_outputs[o]] |= 1<<1;
257                 } 
258         }
259
260         /* at this point, we can test a speaker's status:
261
262            (outputs[o] & 1)      <= in use before
263            (outputs[o] & 2)      <= in use this time
264            (outputs[o] & 3) == 3 <= in use both times
265             outputs[o] == 0      <= not in use either time
266            
267         */
268
269         for (int o = 0; o < 3; ++o) {
270                 pan_t pan;
271                 int output = signal->desired_outputs[o];
272
273                 if (output == -1) {
274                         continue;
275                 }
276
277                 pan = gain_coefficient * signal->desired_gains[o];
278
279                 if (pan == 0.0 && signal->gains[output] == 0.0) {
280                         
281                         /* nothing deing delivered to this output */
282
283                         signal->gains[output] = 0.0;
284                         
285                 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
286                         
287                         /* signal to this output but the gain coefficient has changed, so 
288                            interpolate between them.
289                         */
290
291                         AudioBuffer& buf (obufs.get_audio (output));
292                         buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
293                         signal->gains[output] = pan;
294
295                 } else {
296                         
297                         /* signal to this output, same gain as before so just copy with gain
298                          */
299                            
300                         mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
301                         signal->gains[output] = pan;
302                 }
303         }
304
305         /* clean up the outputs that were used last time but not this time
306          */
307
308         for (uint32_t o = 0; o < sz; ++o) {
309                 if (outputs[o] == 1) {
310                         /* take signal and deliver with a rapid fade out
311                          */
312                         AudioBuffer& buf (obufs.get_audio (o));
313                         buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
314                         signal->gains[o] = 0.0;
315                 }
316         }
317
318         /* note that the output buffers were all silenced at some point
319            so anything we didn't write to with this signal (or any others)
320            is just as it should be.
321         */
322 }
323
324 void 
325 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
326                                      framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
327 {
328 }
329
330 XMLNode&
331 VBAPanner::get_state ()
332 {
333         return state (true);
334 }
335
336 XMLNode&
337 VBAPanner::state (bool full_state)
338 {
339         XMLNode& node (Panner::get_state());
340         node.add_property (X_("type"), _descriptor.name);
341         return node;
342 }
343
344 int
345 VBAPanner::set_state (const XMLNode& node, int /*version*/)
346 {
347         return 0;
348 }
349
350 Panner*
351 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
352 {
353         return new VBAPanner (p, s);
354 }
355
356 ChanCount
357 VBAPanner::in() const
358 {
359         return ChanCount (DataType::AUDIO, _signals.size());
360 }
361
362 ChanCount
363 VBAPanner::out() const
364 {
365         return ChanCount (DataType::AUDIO, _speakers->n_speakers());
366 }
367
368 std::set<Evoral::Parameter> 
369 VBAPanner::what_can_be_automated() const
370 {
371         set<Evoral::Parameter> s;
372         s.insert (Evoral::Parameter (PanAzimuthAutomation));
373         s.insert (Evoral::Parameter (PanWidthAutomation));
374         return s;
375 }
376         
377 string
378 VBAPanner::describe_parameter (Evoral::Parameter p)
379 {
380         switch (p.type()) {
381         case PanAzimuthAutomation:
382                 return _("Direction");
383         case PanWidthAutomation:
384                 return _("Diffusion");
385         default:
386                 return _pannable->describe_parameter (p);
387         }
388 }
389
390 string 
391 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
392 {
393         /* DO NOT USE LocaleGuard HERE */
394         double val = ac->get_value();
395
396         switch (ac->parameter().type()) {
397         case PanAzimuthAutomation: /* direction */
398                 return string_compose (_("%1"), val * 360.0);
399                 
400         case PanWidthAutomation: /* diffusion */
401                 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
402                 
403         default:
404                 return _pannable->value_as_string (ac);
405         }
406 }
407
408 AngularVector
409 VBAPanner::signal_position (uint32_t n) const
410 {
411         if (n < _signals.size()) {
412                 return _signals[n]->direction;
413         }
414
415         return AngularVector();
416 }
417
418 boost::shared_ptr<Speakers>
419 VBAPanner::get_speakers () const 
420 {
421         return _speakers->parent();
422 }
423
424 void
425 VBAPanner::set_position (double p)
426 {
427         _pannable->pan_azimuth_control->set_value (p);
428 }
429
430 void
431 VBAPanner::set_width (double p)
432 {
433         _pannable->pan_width_control->set_value (p);
434 }
435