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