Move panner bypass state up to the PannerShell so that it is preserved even when...
[ardour2.git] / libs / panners / vbap / vbap.cc
blobd4a7030d9b3c7d129ea360035df551d8bf66c69a
1 #include <cmath>
2 #include <cstdlib>
3 #include <cstdio>
4 #include <cstring>
6 #include <iostream>
7 #include <string>
9 #include "pbd/cartesian.h"
10 #include "pbd/compose.h"
12 #include "ardour/amp.h"
13 #include "ardour/audio_buffer.h"
14 #include "ardour/buffer_set.h"
15 #include "ardour/pan_controllable.h"
16 #include "ardour/pannable.h"
17 #include "ardour/speakers.h"
19 #include "vbap.h"
20 #include "vbap_speakers.h"
22 #include "i18n.h"
24 using namespace PBD;
25 using namespace ARDOUR;
26 using namespace std;
28 static PanPluginDescriptor _descriptor = {
29 "VBAP 2D panner",
30 -1, -1,
31 VBAPanner::factory
34 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
36 VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n, uint32_t n_speakers)
38 resize_gains (n_speakers);
40 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
41 outputs[0] = outputs[1] = outputs[2] = -1;
42 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
45 void
46 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
48 gains.assign (n, 0.0);
51 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
52 : Panner (p)
53 , _speakers (new VBAPSpeakers (s))
55 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
56 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
58 update ();
61 VBAPanner::~VBAPanner ()
63 clear_signals ();
66 void
67 VBAPanner::clear_signals ()
69 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
70 delete *i;
72 _signals.clear ();
75 void
76 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
78 uint32_t n = in.n_audio();
80 clear_signals ();
82 for (uint32_t i = 0; i < n; ++i) {
83 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
84 _signals.push_back (s);
88 update ();
91 void
92 VBAPanner::update ()
94 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
97 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
99 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
101 if (_signals.size() > 1) {
103 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
104 so that a width of 1 corresponds to a signal equally present from all directions,
105 and a width of zero corresponds to a point source from the "center" (above) point
106 on the perimeter of the speaker array.
109 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
111 double min_dir = center - (w/2.0);
112 if (min_dir < 0) {
113 min_dir = 360.0 + min_dir; // its already negative
115 min_dir = max (min (min_dir, 360.0), 0.0);
117 double max_dir = center + (w/2.0);
118 if (max_dir > 360.0) {
119 max_dir = max_dir - 360.0;
121 max_dir = max (min (max_dir, 360.0), 0.0);
123 if (max_dir < min_dir) {
124 swap (max_dir, min_dir);
127 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
128 double signal_direction = min_dir;
130 if (w >= 0.0) {
132 /* positive width - normal order of signal spread */
134 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
136 Signal* signal = *s;
138 signal->direction = AngularVector (signal_direction, 0.0);
139 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
140 signal_direction += degree_step_per_signal;
142 } else {
144 /* inverted width - reverse order of signal spread */
146 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
148 Signal* signal = *s;
150 signal->direction = AngularVector (signal_direction, 0.0);
151 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
152 signal_direction += degree_step_per_signal;
156 } else if (_signals.size() == 1) {
158 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
160 Signal* s = _signals.front();
161 s->direction = AngularVector (center, 0);
162 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
166 void
167 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
169 /* calculates gain factors using loudspeaker setup and given direction */
170 double cartdir[3];
171 double power;
172 int i,j,k;
173 double small_g;
174 double big_sm_g, gtmp[3];
176 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
177 big_sm_g = -100000.0;
179 gains[0] = gains[1] = gains[2] = 0;
180 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
182 for (i = 0; i < _speakers->n_tuples(); i++) {
184 small_g = 10000000.0;
186 for (j = 0; j < _speakers->dimension(); j++) {
188 gtmp[j] = 0.0;
190 for (k = 0; k < _speakers->dimension(); k++) {
191 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
194 if (gtmp[j] < small_g) {
195 small_g = gtmp[j];
199 if (small_g > big_sm_g) {
201 big_sm_g = small_g;
203 gains[0] = gtmp[0];
204 gains[1] = gtmp[1];
206 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
207 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
209 if (_speakers->dimension() == 3) {
210 gains[2] = gtmp[2];
211 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
212 } else {
213 gains[2] = 0.0;
214 speaker_ids[2] = -1;
219 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
221 if (power > 0) {
222 gains[0] /= power;
223 gains[1] /= power;
224 gains[2] /= power;
228 void
229 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
231 uint32_t n;
232 vector<Signal*>::iterator s;
234 assert (inbufs.count().n_audio() == _signals.size());
236 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
238 Signal* signal (*s);
240 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
242 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
246 void
247 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
249 Sample* const src = srcbuf.data();
250 Signal* signal (_signals[which]);
252 /* VBAP may distribute the signal across up to 3 speakers depending on
253 the configuration of the speakers.
255 But the set of speakers in use "this time" may be different from
256 the set of speakers "the last time". So we have up to 6 speakers
257 involved, and we have to interpolate so that those no longer
258 in use are rapidly faded to silence and those newly in use
259 are rapidly faded to their correct level. This prevents clicks
260 as we change the set of speakers used to put the signal in
261 a given position.
263 However, the speakers are represented by output buffers, and other
264 speakers may write to the same buffers, so we cannot use
265 anything here that will simply assign new (sample) values
266 to the output buffers - everything must be done via mixing
267 functions and not assignment/copying.
270 vector<double>::size_type sz = signal->gains.size();
272 assert (sz == obufs.count().n_audio());
274 int8_t outputs[sz]; // on the stack, no malloc
276 /* set initial state of each output "record"
279 for (uint32_t o = 0; o < sz; ++o) {
280 outputs[o] = 0;
283 /* for all outputs used this time and last time,
284 change the output record to show what has
285 happened.
289 for (int o = 0; o < 3; ++o) {
290 if (signal->outputs[o] != -1) {
291 /* used last time */
292 outputs[signal->outputs[o]] |= 1;
295 if (signal->desired_outputs[o] != -1) {
296 /* used this time */
297 outputs[signal->desired_outputs[o]] |= 1<<1;
301 /* at this point, we can test a speaker's status:
303 (outputs[o] & 1) <= in use before
304 (outputs[o] & 2) <= in use this time
305 (outputs[o] & 3) == 3 <= in use both times
306 outputs[o] == 0 <= not in use either time
310 for (int o = 0; o < 3; ++o) {
311 pan_t pan;
312 int output = signal->desired_outputs[o];
314 if (output == -1) {
315 continue;
318 pan = gain_coefficient * signal->desired_gains[o];
320 if (pan == 0.0 && signal->gains[output] == 0.0) {
322 /* nothing deing delivered to this output */
324 signal->gains[output] = 0.0;
326 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
328 /* signal to this output but the gain coefficient has changed, so
329 interpolate between them.
332 AudioBuffer& buf (obufs.get_audio (output));
333 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
334 signal->gains[output] = pan;
336 } else {
338 /* signal to this output, same gain as before so just copy with gain
341 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
342 signal->gains[output] = pan;
346 /* clean up the outputs that were used last time but not this time
349 for (uint32_t o = 0; o < sz; ++o) {
350 if (outputs[o] == 1) {
351 /* take signal and deliver with a rapid fade out
353 AudioBuffer& buf (obufs.get_audio (o));
354 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
355 signal->gains[o] = 0.0;
359 /* note that the output buffers were all silenced at some point
360 so anything we didn't write to with this signal (or any others)
361 is just as it should be.
365 void
366 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
367 framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
371 XMLNode&
372 VBAPanner::get_state ()
374 XMLNode& node (Panner::get_state());
375 node.add_property (X_("type"), _descriptor.name);
376 return node;
379 Panner*
380 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
382 return new VBAPanner (p, s);
385 ChanCount
386 VBAPanner::in() const
388 return ChanCount (DataType::AUDIO, _signals.size());
391 ChanCount
392 VBAPanner::out() const
394 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
397 std::set<Evoral::Parameter>
398 VBAPanner::what_can_be_automated() const
400 set<Evoral::Parameter> s;
401 s.insert (Evoral::Parameter (PanAzimuthAutomation));
402 if (_signals.size() > 1) {
403 s.insert (Evoral::Parameter (PanWidthAutomation));
405 return s;
408 string
409 VBAPanner::describe_parameter (Evoral::Parameter p)
411 switch (p.type()) {
412 case PanAzimuthAutomation:
413 return _("Direction");
414 case PanWidthAutomation:
415 return _("Diffusion");
416 default:
417 return _pannable->describe_parameter (p);
421 string
422 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
424 /* DO NOT USE LocaleGuard HERE */
425 double val = ac->get_value();
427 switch (ac->parameter().type()) {
428 case PanAzimuthAutomation: /* direction */
429 return string_compose (_("%1"), int (rint (val * 360.0)));
431 case PanWidthAutomation: /* diffusion */
432 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
434 default:
435 return _pannable->value_as_string (ac);
439 AngularVector
440 VBAPanner::signal_position (uint32_t n) const
442 if (n < _signals.size()) {
443 return _signals[n]->direction;
446 return AngularVector();
449 boost::shared_ptr<Speakers>
450 VBAPanner::get_speakers () const
452 return _speakers->parent();
455 void
456 VBAPanner::set_position (double p)
458 if (p < 0.0) {
459 p = 1.0 + p;
462 if (p > 1.0) {
463 p = fmod (p, 1.0);
466 _pannable->pan_azimuth_control->set_value (p);
469 void
470 VBAPanner::set_width (double w)
472 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));