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