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