2 Copyright (C) 2012 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
32 #include "pbd/cartesian.h"
33 #include "pbd/compose.h"
35 #include "ardour/amp.h"
36 #include "ardour/audio_buffer.h"
37 #include "ardour/buffer_set.h"
38 #include "ardour/pan_controllable.h"
39 #include "ardour/pannable.h"
40 #include "ardour/speakers.h"
43 #include "vbap_speakers.h"
48 using namespace ARDOUR;
51 static PanPluginDescriptor _descriptor = {
53 "http://ardour.org/plugin/panner_vbap",
54 "http://ardour.org/plugin/panner_vbap#ui",
60 extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
62 VBAPanner::Signal::Signal (Session&, VBAPanner&, uint32_t, uint32_t n_speakers)
64 resize_gains (n_speakers);
66 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
67 outputs[0] = outputs[1] = outputs[2] = -1;
68 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
72 VBAPanner::Signal::resize_gains (uint32_t n)
74 gains.assign (n, 0.0);
77 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
79 , _speakers (new VBAPSpeakers (s))
81 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
82 _pannable->pan_elevation_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
83 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
88 VBAPanner::~VBAPanner ()
94 VBAPanner::clear_signals ()
96 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
103 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
105 uint32_t n = in.n_audio();
109 for (uint32_t i = 0; i < n; ++i) {
110 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
111 _signals.push_back (s);
121 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
124 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
126 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
127 double elevation = _pannable->pan_elevation_control->get_value() * 90.0;
129 if (_signals.size() > 1) {
131 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
132 so that a width of 1 corresponds to a signal equally present from all directions,
133 and a width of zero corresponds to a point source from the "center" (above) point
134 on the perimeter of the speaker array.
137 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
139 double min_dir = center - (w/2.0);
141 min_dir = 360.0 + min_dir; // its already negative
143 min_dir = max (min (min_dir, 360.0), 0.0);
145 double max_dir = center + (w/2.0);
146 if (max_dir > 360.0) {
147 max_dir = max_dir - 360.0;
149 max_dir = max (min (max_dir, 360.0), 0.0);
151 if (max_dir < min_dir) {
152 swap (max_dir, min_dir);
155 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
156 double signal_direction = min_dir;
160 /* positive width - normal order of signal spread */
162 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
166 signal->direction = AngularVector (signal_direction, elevation);
167 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
168 signal_direction += degree_step_per_signal;
172 /* inverted width - reverse order of signal spread */
174 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
178 signal->direction = AngularVector (signal_direction, elevation);
179 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
180 signal_direction += degree_step_per_signal;
184 } else if (_signals.size() == 1) {
186 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
188 Signal* s = _signals.front();
189 s->direction = AngularVector (center, elevation);
190 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
195 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
197 /* calculates gain factors using loudspeaker setup and given direction */
202 double big_sm_g, gtmp[3];
204 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
205 big_sm_g = -100000.0;
207 gains[0] = gains[1] = gains[2] = 0;
208 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
210 for (i = 0; i < _speakers->n_tuples(); i++) {
212 small_g = 10000000.0;
214 for (j = 0; j < _speakers->dimension(); j++) {
218 for (k = 0; k < _speakers->dimension(); k++) {
219 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
222 if (gtmp[j] < small_g) {
227 if (small_g > big_sm_g) {
234 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
235 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
237 if (_speakers->dimension() == 3) {
239 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
247 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
257 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
260 vector<Signal*>::iterator s;
262 assert (inbufs.count().n_audio() == _signals.size());
264 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
268 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
270 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
275 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
277 Sample* const src = srcbuf.data();
278 Signal* signal (_signals[which]);
280 /* VBAP may distribute the signal across up to 3 speakers depending on
281 the configuration of the speakers.
283 But the set of speakers in use "this time" may be different from
284 the set of speakers "the last time". So we have up to 6 speakers
285 involved, and we have to interpolate so that those no longer
286 in use are rapidly faded to silence and those newly in use
287 are rapidly faded to their correct level. This prevents clicks
288 as we change the set of speakers used to put the signal in
291 However, the speakers are represented by output buffers, and other
292 speakers may write to the same buffers, so we cannot use
293 anything here that will simply assign new (sample) values
294 to the output buffers - everything must be done via mixing
295 functions and not assignment/copying.
298 vector<double>::size_type sz = signal->gains.size();
300 assert (sz == obufs.count().n_audio());
302 int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
304 /* set initial state of each output "record"
307 for (uint32_t o = 0; o < sz; ++o) {
311 /* for all outputs used this time and last time,
312 change the output record to show what has
317 for (int o = 0; o < 3; ++o) {
318 if (signal->outputs[o] != -1) {
320 outputs[signal->outputs[o]] |= 1;
323 if (signal->desired_outputs[o] != -1) {
325 outputs[signal->desired_outputs[o]] |= 1<<1;
329 /* at this point, we can test a speaker's status:
331 (*outputs[o] & 1) <= in use before
332 (*outputs[o] & 2) <= in use this time
333 (*outputs[o] & 3) == 3 <= in use both times
334 *outputs[o] == 0 <= not in use either time
338 for (int o = 0; o < 3; ++o) {
340 int output = signal->desired_outputs[o];
346 pan = gain_coefficient * signal->desired_gains[o];
348 if (pan == 0.0 && signal->gains[output] == 0.0) {
350 /* nothing deing delivered to this output */
352 signal->gains[output] = 0.0;
354 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
356 /* signal to this output but the gain coefficient has changed, so
357 interpolate between them.
360 AudioBuffer& buf (obufs.get_audio (output));
361 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
362 signal->gains[output] = pan;
366 /* signal to this output, same gain as before so just copy with gain
369 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
370 signal->gains[output] = pan;
374 /* clean up the outputs that were used last time but not this time
377 for (uint32_t o = 0; o < sz; ++o) {
378 if (outputs[o] == 1) {
379 /* take signal and deliver with a rapid fade out
381 AudioBuffer& buf (obufs.get_audio (o));
382 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
383 signal->gains[o] = 0.0;
387 /* note that the output buffers were all silenced at some point
388 so anything we didn't write to with this signal (or any others)
389 is just as it should be.
394 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
395 framepos_t /*start*/, framepos_t /*end*/,
396 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
398 /* XXX to be implemented */
402 VBAPanner::get_state ()
404 XMLNode& node (Panner::get_state());
405 node.add_property (X_("uri"), _descriptor.panner_uri);
406 /* this is needed to allow new sessions to load with old Ardour: */
407 node.add_property (X_("type"), _descriptor.name);
412 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
414 return new VBAPanner (p, s);
418 VBAPanner::in() const
420 return ChanCount (DataType::AUDIO, _signals.size());
424 VBAPanner::out() const
426 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
429 std::set<Evoral::Parameter>
430 VBAPanner::what_can_be_automated() const
432 set<Evoral::Parameter> s;
433 s.insert (Evoral::Parameter (PanAzimuthAutomation));
434 if (_signals.size() > 1) {
435 s.insert (Evoral::Parameter (PanWidthAutomation));
437 if (_speakers->dimension() == 3) {
438 s.insert (Evoral::Parameter (PanElevationAutomation));
444 VBAPanner::describe_parameter (Evoral::Parameter p)
447 case PanAzimuthAutomation:
448 return _("Direction");
449 case PanWidthAutomation:
450 return _("Diffusion");
451 case PanElevationAutomation:
452 return _("Elevation");
454 return _pannable->describe_parameter (p);
459 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
461 /* DO NOT USE LocaleGuard HERE */
462 double val = ac->get_value();
464 switch (ac->parameter().type()) {
465 case PanAzimuthAutomation: /* direction */
466 return string_compose (_("%1\u00B0"), int (rint (val * 360.0)));
468 case PanWidthAutomation: /* diffusion */
469 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
471 case PanElevationAutomation: /* elevation */
472 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
475 return _pannable->value_as_string (ac);
480 VBAPanner::signal_position (uint32_t n) const
482 if (n < _signals.size()) {
483 return _signals[n]->direction;
486 return AngularVector();
489 boost::shared_ptr<Speakers>
490 VBAPanner::get_speakers () const
492 return _speakers->parent();
496 VBAPanner::set_position (double p)
498 /* map into 0..1 range */
500 over -= (p >= 0) ? 0 : 1;
502 _pannable->pan_azimuth_control->set_value (p);
506 VBAPanner::set_width (double w)
508 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
512 VBAPanner::set_elevation (double e)
514 _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)));