prettification of vbap/2d panner GUI. lots of details still to fix. also signal place...
[ardour2.git] / libs / panners / vbap / vbap.cc
blob124b30bc30393f4ee1d2906fa25cc50b560c2a92
1 #include <cmath>
2 #include <cstdlib>
3 #include <cstdio>
4 #include <cstring>
6 #include <iostream>
7 #include <string>
9 #include "pbd/cartesian.h"
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"
18 #include "vbap.h"
19 #include "vbap_speakers.h"
21 using namespace PBD;
22 using namespace ARDOUR;
23 using namespace std;
25 static PanPluginDescriptor _descriptor = {
26 "VBAP 2D panner",
27 -1, -1,
28 VBAPanner::factory
31 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
33 VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n, uint32_t n_speakers)
35 resize_gains (n_speakers);
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;
42 void
43 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
45 gains.assign (n, 0.0);
48 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
49 : Panner (p)
50 , _speakers (new VBAPSpeakers (s))
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));
55 update ();
58 VBAPanner::~VBAPanner ()
60 clear_signals ();
63 void
64 VBAPanner::clear_signals ()
66 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
67 delete *i;
69 _signals.clear ();
72 void
73 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
75 uint32_t n = in.n_audio();
77 clear_signals ();
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);
85 update ();
88 void
89 VBAPanner::update ()
91 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
94 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
96 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
98 if (_signals.size() > 1) {
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)
105 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
107 double min_dir = center - w;
108 if (min_dir < 0) {
109 min_dir = 360.0 + min_dir; // its already negative
111 min_dir = max (min (min_dir, 360.0), 0.0);
113 double max_dir = center + w;
114 if (max_dir > 360.0) {
115 max_dir = max_dir - 360.0;
117 max_dir = max (min (max_dir, 360.0), 0.0);
119 if (max_dir < min_dir) {
120 swap (max_dir, min_dir);
123 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
124 double signal_direction = min_dir;
125 int x = 1;
127 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
129 Signal* signal = *s;
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;
136 } else if (_signals.size() == 1) {
138 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
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);
146 void
147 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
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];
156 azi_ele_to_cart (azi,ele, cartdir[0], cartdir[1], cartdir[2]);
157 big_sm_g = -100000.0;
159 gains[0] = gains[1] = gains[2] = 0;
160 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
162 for (i = 0; i < _speakers->n_tuples(); i++) {
164 small_g = 10000000.0;
166 for (j = 0; j < _speakers->dimension(); j++) {
168 gtmp[j] = 0.0;
170 for (k = 0; k < _speakers->dimension(); k++) {
171 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
174 if (gtmp[j] < small_g) {
175 small_g = gtmp[j];
179 if (small_g > big_sm_g) {
181 big_sm_g = small_g;
183 gains[0] = gtmp[0];
184 gains[1] = gtmp[1];
186 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
187 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
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;
199 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
201 if (power > 0) {
202 gains[0] /= power;
203 gains[1] /= power;
204 gains[2] /= power;
208 void
209 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
211 uint32_t n;
212 vector<Signal*>::iterator s;
214 assert (inbufs.count().n_audio() == _signals.size());
216 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
218 Signal* signal (*s);
220 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
222 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
226 void
227 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
229 Sample* const src = srcbuf.data();
230 Signal* signal (_signals[which]);
232 /* VBAP may distribute the signal across up to 3 speakers depending on
233 the configuration of the speakers.
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.
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.
250 vector<double>::size_type sz = signal->gains.size();
252 assert (sz == obufs.count().n_audio());
254 int8_t outputs[sz]; // on the stack, no malloc
256 /* set initial state of each output "record"
259 for (uint32_t o = 0; o < sz; ++o) {
260 outputs[o] = 0;
263 /* for all outputs used this time and last time,
264 change the output record to show what has
265 happened.
269 for (int o = 0; o < 3; ++o) {
270 if (signal->outputs[o] != -1) {
271 /* used last time */
272 outputs[signal->outputs[o]] |= 1;
275 if (signal->desired_outputs[o] != -1) {
276 /* used this time */
277 outputs[signal->desired_outputs[o]] |= 1<<1;
281 /* at this point, we can test a speaker's status:
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
290 for (int o = 0; o < 3; ++o) {
291 pan_t pan;
292 int output = signal->desired_outputs[o];
294 if (output == -1) {
295 continue;
298 pan = gain_coefficient * signal->desired_gains[o];
300 if (pan == 0.0 && signal->gains[output] == 0.0) {
302 /* nothing deing delivered to this output */
304 signal->gains[output] = 0.0;
306 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
308 /* signal to this output but the gain coefficient has changed, so
309 interpolate between them.
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;
316 } else {
318 /* signal to this output, same gain as before so just copy with gain
321 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
322 signal->gains[output] = pan;
326 /* clean up the outputs that were used last time but not this time
329 for (uint32_t o = 0; o < sz; ++o) {
330 if (outputs[o] == 1) {
331 /* take signal and deliver with a rapid fade out
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;
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.
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)
351 XMLNode&
352 VBAPanner::get_state ()
354 return state (true);
357 XMLNode&
358 VBAPanner::state (bool full_state)
360 XMLNode& node (Panner::get_state());
361 node.add_property (X_("type"), _descriptor.name);
362 return node;
366 VBAPanner::set_state (const XMLNode& node, int /*version*/)
368 return 0;
371 Panner*
372 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
374 return new VBAPanner (p, s);
377 ChanCount
378 VBAPanner::in() const
380 return ChanCount (DataType::AUDIO, _signals.size());
383 ChanCount
384 VBAPanner::out() const
386 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
389 std::set<Evoral::Parameter>
390 VBAPanner::what_can_be_automated() const
392 set<Evoral::Parameter> s;
393 s.insert (Evoral::Parameter (PanAzimuthAutomation));
394 if (_signals.size() > 1) {
395 s.insert (Evoral::Parameter (PanWidthAutomation));
397 return s;
400 string
401 VBAPanner::describe_parameter (Evoral::Parameter p)
403 switch (p.type()) {
404 case PanAzimuthAutomation:
405 return _("Direction");
406 case PanWidthAutomation:
407 return _("Diffusion");
408 default:
409 return _pannable->describe_parameter (p);
413 string
414 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
416 /* DO NOT USE LocaleGuard HERE */
417 double val = ac->get_value();
419 switch (ac->parameter().type()) {
420 case PanAzimuthAutomation: /* direction */
421 return string_compose (_("%1"), int (rint (val * 360.0)));
423 case PanWidthAutomation: /* diffusion */
424 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
426 default:
427 return _pannable->value_as_string (ac);
431 AngularVector
432 VBAPanner::signal_position (uint32_t n) const
434 if (n < _signals.size()) {
435 return _signals[n]->direction;
438 return AngularVector();
441 boost::shared_ptr<Speakers>
442 VBAPanner::get_speakers () const
444 return _speakers->parent();
447 void
448 VBAPanner::set_position (double p)
450 if (p < 0.0) {
451 p = 1.0 + p;
454 if (p > 1.0) {
455 p = fmod (p, 1.0);
458 _pannable->pan_azimuth_control->set_value (p);
461 void
462 VBAPanner::set_width (double w)
464 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));