fix width handling in vbap panner
[ardour2.git] / libs / panners / vbap / vbap.cc
blobdf1f5d0876d1ae25609bbc13ef1e20b7530c29a6
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) point
103 on the perimeter of the speaker array.
106 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
108 double min_dir = center - (w/2.0);
109 if (min_dir < 0) {
110 min_dir = 360.0 + min_dir; // its already negative
112 min_dir = max (min (min_dir, 360.0), 0.0);
114 double max_dir = center + (w/2.0);
115 if (max_dir > 360.0) {
116 max_dir = max_dir - 360.0;
118 max_dir = max (min (max_dir, 360.0), 0.0);
120 if (max_dir < min_dir) {
121 swap (max_dir, min_dir);
124 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
125 double signal_direction = min_dir;
127 if (w >= 0.0) {
129 /* positive width - normal order of signal spread */
131 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
133 Signal* signal = *s;
135 signal->direction = AngularVector (signal_direction, 0.0);
136 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
137 signal_direction += degree_step_per_signal;
139 } else {
141 /* inverted width - reverse order of signal spread */
143 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
145 Signal* signal = *s;
147 signal->direction = AngularVector (signal_direction, 0.0);
148 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
149 signal_direction += degree_step_per_signal;
153 } else if (_signals.size() == 1) {
155 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
157 Signal* s = _signals.front();
158 s->direction = AngularVector (center, 0);
159 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
163 void
164 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
166 /* calculates gain factors using loudspeaker setup and given direction */
167 double cartdir[3];
168 double power;
169 int i,j,k;
170 double small_g;
171 double big_sm_g, gtmp[3];
173 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
174 big_sm_g = -100000.0;
176 gains[0] = gains[1] = gains[2] = 0;
177 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
179 for (i = 0; i < _speakers->n_tuples(); i++) {
181 small_g = 10000000.0;
183 for (j = 0; j < _speakers->dimension(); j++) {
185 gtmp[j] = 0.0;
187 for (k = 0; k < _speakers->dimension(); k++) {
188 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
191 if (gtmp[j] < small_g) {
192 small_g = gtmp[j];
196 if (small_g > big_sm_g) {
198 big_sm_g = small_g;
200 gains[0] = gtmp[0];
201 gains[1] = gtmp[1];
203 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
204 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
206 if (_speakers->dimension() == 3) {
207 gains[2] = gtmp[2];
208 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
209 } else {
210 gains[2] = 0.0;
211 speaker_ids[2] = -1;
216 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
218 if (power > 0) {
219 gains[0] /= power;
220 gains[1] /= power;
221 gains[2] /= power;
225 void
226 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
228 uint32_t n;
229 vector<Signal*>::iterator s;
231 assert (inbufs.count().n_audio() == _signals.size());
233 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
235 Signal* signal (*s);
237 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
239 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
243 void
244 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
246 Sample* const src = srcbuf.data();
247 Signal* signal (_signals[which]);
249 /* VBAP may distribute the signal across up to 3 speakers depending on
250 the configuration of the speakers.
252 But the set of speakers in use "this time" may be different from
253 the set of speakers "the last time". So we have up to 6 speakers
254 involved, and we have to interpolate so that those no longer
255 in use are rapidly faded to silence and those newly in use
256 are rapidly faded to their correct level. This prevents clicks
257 as we change the set of speakers used to put the signal in
258 a given position.
260 However, the speakers are represented by output buffers, and other
261 speakers may write to the same buffers, so we cannot use
262 anything here that will simply assign new (sample) values
263 to the output buffers - everything must be done via mixing
264 functions and not assignment/copying.
267 vector<double>::size_type sz = signal->gains.size();
269 assert (sz == obufs.count().n_audio());
271 int8_t outputs[sz]; // on the stack, no malloc
273 /* set initial state of each output "record"
276 for (uint32_t o = 0; o < sz; ++o) {
277 outputs[o] = 0;
280 /* for all outputs used this time and last time,
281 change the output record to show what has
282 happened.
286 for (int o = 0; o < 3; ++o) {
287 if (signal->outputs[o] != -1) {
288 /* used last time */
289 outputs[signal->outputs[o]] |= 1;
292 if (signal->desired_outputs[o] != -1) {
293 /* used this time */
294 outputs[signal->desired_outputs[o]] |= 1<<1;
298 /* at this point, we can test a speaker's status:
300 (outputs[o] & 1) <= in use before
301 (outputs[o] & 2) <= in use this time
302 (outputs[o] & 3) == 3 <= in use both times
303 outputs[o] == 0 <= not in use either time
307 for (int o = 0; o < 3; ++o) {
308 pan_t pan;
309 int output = signal->desired_outputs[o];
311 if (output == -1) {
312 continue;
315 pan = gain_coefficient * signal->desired_gains[o];
317 if (pan == 0.0 && signal->gains[output] == 0.0) {
319 /* nothing deing delivered to this output */
321 signal->gains[output] = 0.0;
323 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
325 /* signal to this output but the gain coefficient has changed, so
326 interpolate between them.
329 AudioBuffer& buf (obufs.get_audio (output));
330 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
331 signal->gains[output] = pan;
333 } else {
335 /* signal to this output, same gain as before so just copy with gain
338 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
339 signal->gains[output] = pan;
343 /* clean up the outputs that were used last time but not this time
346 for (uint32_t o = 0; o < sz; ++o) {
347 if (outputs[o] == 1) {
348 /* take signal and deliver with a rapid fade out
350 AudioBuffer& buf (obufs.get_audio (o));
351 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
352 signal->gains[o] = 0.0;
356 /* note that the output buffers were all silenced at some point
357 so anything we didn't write to with this signal (or any others)
358 is just as it should be.
362 void
363 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
364 framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
368 XMLNode&
369 VBAPanner::get_state ()
371 return state (true);
374 XMLNode&
375 VBAPanner::state (bool full_state)
377 XMLNode& node (Panner::get_state());
378 node.add_property (X_("type"), _descriptor.name);
379 return node;
383 VBAPanner::set_state (const XMLNode& node, int /*version*/)
385 return 0;
388 Panner*
389 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
391 return new VBAPanner (p, s);
394 ChanCount
395 VBAPanner::in() const
397 return ChanCount (DataType::AUDIO, _signals.size());
400 ChanCount
401 VBAPanner::out() const
403 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
406 std::set<Evoral::Parameter>
407 VBAPanner::what_can_be_automated() const
409 set<Evoral::Parameter> s;
410 s.insert (Evoral::Parameter (PanAzimuthAutomation));
411 if (_signals.size() > 1) {
412 s.insert (Evoral::Parameter (PanWidthAutomation));
414 return s;
417 string
418 VBAPanner::describe_parameter (Evoral::Parameter p)
420 switch (p.type()) {
421 case PanAzimuthAutomation:
422 return _("Direction");
423 case PanWidthAutomation:
424 return _("Diffusion");
425 default:
426 return _pannable->describe_parameter (p);
430 string
431 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
433 /* DO NOT USE LocaleGuard HERE */
434 double val = ac->get_value();
436 switch (ac->parameter().type()) {
437 case PanAzimuthAutomation: /* direction */
438 return string_compose (_("%1"), int (rint (val * 360.0)));
440 case PanWidthAutomation: /* diffusion */
441 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
443 default:
444 return _pannable->value_as_string (ac);
448 AngularVector
449 VBAPanner::signal_position (uint32_t n) const
451 if (n < _signals.size()) {
452 return _signals[n]->direction;
455 return AngularVector();
458 boost::shared_ptr<Speakers>
459 VBAPanner::get_speakers () const
461 return _speakers->parent();
464 void
465 VBAPanner::set_position (double p)
467 if (p < 0.0) {
468 p = 1.0 + p;
471 if (p > 1.0) {
472 p = fmod (p, 1.0);
475 _pannable->pan_azimuth_control->set_value (p);
478 void
479 VBAPanner::set_width (double w)
481 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));