switch cartesian/spherical function names and make them use length. still a tweak...
[ardour2.git] / libs / panners / vbap / vbap.cc
blobeff8a5ee43d40cc03c05db3e097e5fadeb3ba3ab
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;
126 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
128 Signal* signal = *s;
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;
135 } else if (_signals.size() == 1) {
137 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
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);
145 void
146 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
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];
155 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
156 big_sm_g = -100000.0;
158 gains[0] = gains[1] = gains[2] = 0;
159 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
161 for (i = 0; i < _speakers->n_tuples(); i++) {
163 small_g = 10000000.0;
165 for (j = 0; j < _speakers->dimension(); j++) {
167 gtmp[j] = 0.0;
169 for (k = 0; k < _speakers->dimension(); k++) {
170 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
173 if (gtmp[j] < small_g) {
174 small_g = gtmp[j];
178 if (small_g > big_sm_g) {
180 big_sm_g = small_g;
182 gains[0] = gtmp[0];
183 gains[1] = gtmp[1];
185 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
186 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
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;
198 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
200 if (power > 0) {
201 gains[0] /= power;
202 gains[1] /= power;
203 gains[2] /= power;
207 void
208 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
210 uint32_t n;
211 vector<Signal*>::iterator s;
213 assert (inbufs.count().n_audio() == _signals.size());
215 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
217 Signal* signal (*s);
219 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
221 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
225 void
226 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
228 Sample* const src = srcbuf.data();
229 Signal* signal (_signals[which]);
231 /* VBAP may distribute the signal across up to 3 speakers depending on
232 the configuration of the speakers.
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.
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.
249 vector<double>::size_type sz = signal->gains.size();
251 assert (sz == obufs.count().n_audio());
253 int8_t outputs[sz]; // on the stack, no malloc
255 /* set initial state of each output "record"
258 for (uint32_t o = 0; o < sz; ++o) {
259 outputs[o] = 0;
262 /* for all outputs used this time and last time,
263 change the output record to show what has
264 happened.
268 for (int o = 0; o < 3; ++o) {
269 if (signal->outputs[o] != -1) {
270 /* used last time */
271 outputs[signal->outputs[o]] |= 1;
274 if (signal->desired_outputs[o] != -1) {
275 /* used this time */
276 outputs[signal->desired_outputs[o]] |= 1<<1;
280 /* at this point, we can test a speaker's status:
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
289 for (int o = 0; o < 3; ++o) {
290 pan_t pan;
291 int output = signal->desired_outputs[o];
293 if (output == -1) {
294 continue;
297 pan = gain_coefficient * signal->desired_gains[o];
299 if (pan == 0.0 && signal->gains[output] == 0.0) {
301 /* nothing deing delivered to this output */
303 signal->gains[output] = 0.0;
305 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
307 /* signal to this output but the gain coefficient has changed, so
308 interpolate between them.
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;
315 } else {
317 /* signal to this output, same gain as before so just copy with gain
320 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
321 signal->gains[output] = pan;
325 /* clean up the outputs that were used last time but not this time
328 for (uint32_t o = 0; o < sz; ++o) {
329 if (outputs[o] == 1) {
330 /* take signal and deliver with a rapid fade out
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;
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.
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)
350 XMLNode&
351 VBAPanner::get_state ()
353 return state (true);
356 XMLNode&
357 VBAPanner::state (bool full_state)
359 XMLNode& node (Panner::get_state());
360 node.add_property (X_("type"), _descriptor.name);
361 return node;
365 VBAPanner::set_state (const XMLNode& node, int /*version*/)
367 return 0;
370 Panner*
371 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
373 return new VBAPanner (p, s);
376 ChanCount
377 VBAPanner::in() const
379 return ChanCount (DataType::AUDIO, _signals.size());
382 ChanCount
383 VBAPanner::out() const
385 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
388 std::set<Evoral::Parameter>
389 VBAPanner::what_can_be_automated() const
391 set<Evoral::Parameter> s;
392 s.insert (Evoral::Parameter (PanAzimuthAutomation));
393 if (_signals.size() > 1) {
394 s.insert (Evoral::Parameter (PanWidthAutomation));
396 return s;
399 string
400 VBAPanner::describe_parameter (Evoral::Parameter p)
402 switch (p.type()) {
403 case PanAzimuthAutomation:
404 return _("Direction");
405 case PanWidthAutomation:
406 return _("Diffusion");
407 default:
408 return _pannable->describe_parameter (p);
412 string
413 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
415 /* DO NOT USE LocaleGuard HERE */
416 double val = ac->get_value();
418 switch (ac->parameter().type()) {
419 case PanAzimuthAutomation: /* direction */
420 return string_compose (_("%1"), int (rint (val * 360.0)));
422 case PanWidthAutomation: /* diffusion */
423 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
425 default:
426 return _pannable->value_as_string (ac);
430 AngularVector
431 VBAPanner::signal_position (uint32_t n) const
433 if (n < _signals.size()) {
434 return _signals[n]->direction;
437 return AngularVector();
440 boost::shared_ptr<Speakers>
441 VBAPanner::get_speakers () const
443 return _speakers->parent();
446 void
447 VBAPanner::set_position (double p)
449 if (p < 0.0) {
450 p = 1.0 + p;
453 if (p > 1.0) {
454 p = fmod (p, 1.0);
457 _pannable->pan_azimuth_control->set_value (p);
460 void
461 VBAPanner::set_width (double w)
463 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));