draw pucks (signal positions) on vbap panner
[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/pannable.h"
12 #include "ardour/speakers.h"
13 #include "ardour/audio_buffer.h"
14 #include "ardour/buffer_set.h"
15 #include "ardour/pan_controllable.h"
16
17 #include "vbap.h"
18 #include "vbap_speakers.h"
19
20 using namespace PBD;
21 using namespace ARDOUR;
22 using namespace std;
23
24 static PanPluginDescriptor _descriptor = {
25         "VBAP 2D panner",
26         -1, -1,
27         VBAPanner::factory
28 };
29
30 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
31
32 VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n)
33 {
34         gains[0] = gains[1] = gains[2] = 0;
35         desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
36         outputs[0] = outputs[1] = outputs[2] = -1;
37         desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
38 };
39
40 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
41         : Panner (p)
42         , _speakers (new VBAPSpeakers (s))
43 {
44         _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
45         _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
46
47         update ();
48 }
49
50 VBAPanner::~VBAPanner ()
51 {
52         clear_signals ();
53 }
54
55 void
56 VBAPanner::clear_signals ()
57 {
58         for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
59                 delete *i;
60         }
61         _signals.clear ();
62 }
63
64 void
65 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
66 {
67         uint32_t n = in.n_audio();
68
69         clear_signals ();
70
71         for (uint32_t i = 0; i < n; ++i) {
72                 _signals.push_back (new Signal (_pannable->session(), *this, i));
73         }
74
75         update ();
76 }
77
78 void
79 VBAPanner::update ()
80 {
81         /* recompute signal directions based on panner azimuth and width (diffusion) parameters)
82          */
83
84         /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
85          */
86
87         double center = _pannable->pan_azimuth_control->get_value() * 360.0;
88
89         /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
90            so that a width of 1 corresponds to a signal equally present from all directions, 
91            and a width of zero corresponds to a point source from the "center" (above)
92         */
93
94         double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
95
96         double min_dir = center - w;
97         min_dir = max (min (min_dir, 360.0), 0.0);
98
99         double max_dir = center + w;
100         max_dir = max (min (max_dir, 360.0), 0.0);
101
102         double degree_step_per_signal = (max_dir - min_dir) / _signals.size();
103         double signal_direction = min_dir;
104
105         for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
106
107                 Signal* signal = *s;
108
109                 signal->direction = AngularVector (signal_direction, 0.0);
110
111                 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
112                         cerr << " @ " << signal->direction.azi << " /= " << signal->direction.ele
113                              << " Outputs: "
114                              << signal->desired_outputs[0] + 1 << ' '
115                              << signal->desired_outputs[1] + 1 << ' '
116                              << " Gains "
117                              << signal->desired_gains[0] << ' '
118                              << signal->desired_gains[1] << ' '
119                              << endl;
120
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->gains, signal->desired_gains, sizeof (signal->gains));
202                 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
203         }
204 }
205
206 void
207 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
208 {
209         Sample* const src = srcbuf.data();
210         Sample* dst;
211         pan_t pan;
212         uint32_t n_audio = obufs.count().n_audio();
213         bool todo[n_audio];
214         Signal* signal (_signals[which]);
215
216         for (uint32_t o = 0; o < n_audio; ++o) {
217                 todo[o] = true;
218         }
219         
220         /* VBAP may distribute the signal across up to 3 speakers depending on
221            the configuration of the speakers.
222         */
223
224         for (int o = 0; o < 3; ++o) {
225                 if (signal->desired_outputs[o] != -1) {
226                         
227                         pframes_t n = 0;
228
229                         /* XXX TODO: interpolate across changes in gain and/or outputs
230                          */
231
232                         dst = obufs.get_audio (signal->desired_outputs[o]).data();
233
234                         pan = gain_coefficient * signal->desired_gains[o];
235                         mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
236
237                         todo[o] = false;
238                 }
239         }
240         
241         for (uint32_t o = 0; o < n_audio; ++o) {
242                 if (todo[o]) {
243                         /* VBAP decided not to deliver any audio to this output, so we write silence */
244                         dst = obufs.get_audio(o).data();
245                         memset (dst, 0, sizeof (Sample) * nframes);
246                 }
247         }
248         
249 }
250
251 void 
252 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
253                                      framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
254 {
255 }
256
257 XMLNode&
258 VBAPanner::get_state ()
259 {
260         return state (true);
261 }
262
263 XMLNode&
264 VBAPanner::state (bool full_state)
265 {
266         XMLNode& node (Panner::get_state());
267         node.add_property (X_("type"), _descriptor.name);
268         return node;
269 }
270
271 int
272 VBAPanner::set_state (const XMLNode& node, int /*version*/)
273 {
274         return 0;
275 }
276
277 Panner*
278 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
279 {
280         return new VBAPanner (p, s);
281 }
282
283 ChanCount
284 VBAPanner::in() const
285 {
286         return ChanCount (DataType::AUDIO, _signals.size());
287 }
288
289 ChanCount
290 VBAPanner::out() const
291 {
292         return ChanCount (DataType::AUDIO, _speakers->n_speakers());
293 }
294
295 std::set<Evoral::Parameter> 
296 VBAPanner::what_can_be_automated() const
297 {
298         set<Evoral::Parameter> s;
299         s.insert (Evoral::Parameter (PanAzimuthAutomation));
300         s.insert (Evoral::Parameter (PanWidthAutomation));
301         return s;
302 }
303         
304 string
305 VBAPanner::describe_parameter (Evoral::Parameter p)
306 {
307         switch (p.type()) {
308         case PanAzimuthAutomation:
309                 return _("Direction");
310         case PanWidthAutomation:
311                 return _("Diffusion");
312         default:
313                 return _pannable->describe_parameter (p);
314         }
315 }
316
317 string 
318 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
319 {
320         /* DO NOT USE LocaleGuard HERE */
321         double val = ac->get_value();
322
323         switch (ac->parameter().type()) {
324         case PanAzimuthAutomation: /* direction */
325                 return string_compose (_("%1"), val * 360.0);
326                 
327         case PanWidthAutomation: /* diffusion */
328                 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
329                 
330         default:
331                 return _pannable->value_as_string (ac);
332         }
333 }
334
335 AngularVector
336 VBAPanner::signal_position (uint32_t n) const
337 {
338         if (n < _signals.size()) {
339                 return _signals[n]->direction;
340         }
341
342         return AngularVector();
343 }