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));
84 if (!_pannable->has_state()) {
91 VBAPanner::~VBAPanner ()
97 VBAPanner::clear_signals ()
99 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
106 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
108 uint32_t n = in.n_audio();
112 for (uint32_t i = 0; i < n; ++i) {
113 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
114 _signals.push_back (s);
124 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) and elevation parameters */
125 double elevation = _pannable->pan_elevation_control->get_value() * 90.0;
127 if (_signals.size() > 1) {
128 double w = - (_pannable->pan_width_control->get_value());
129 double signal_direction = 1.0 - (_pannable->pan_azimuth_control->get_value() + (w/2));
130 double grd_step_per_signal = w / (_signals.size() - 1);
131 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
135 int over = signal_direction;
136 over -= (signal_direction >= 0) ? 0 : 1;
137 signal_direction -= (double)over;
139 signal->direction = AngularVector (signal_direction * 360.0, elevation);
140 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
141 signal_direction += grd_step_per_signal;
143 } else if (_signals.size() == 1) {
144 double center = (1.0 - _pannable->pan_azimuth_control->get_value()) * 360.0;
146 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
148 Signal* s = _signals.front();
149 s->direction = AngularVector (center, elevation);
150 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
153 SignalPositionChanged(); /* emit */
157 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
159 /* calculates gain factors using loudspeaker setup and given direction */
164 double big_sm_g, gtmp[3];
165 const int dimension = _speakers->dimension();
166 assert(dimension == 2 || dimension == 3);
168 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
169 big_sm_g = -100000.0;
171 gains[0] = gains[1] = gains[2] = 0;
172 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
174 for (i = 0; i < _speakers->n_tuples(); i++) {
176 small_g = 10000000.0;
178 for (j = 0; j < dimension; j++) {
182 for (k = 0; k < dimension; k++) {
183 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j * dimension + k];
186 if (gtmp[j] < small_g) {
191 if (small_g > big_sm_g) {
198 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
199 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
201 if (_speakers->dimension() == 3) {
203 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
211 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
221 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
224 vector<Signal*>::iterator s;
226 assert (inbufs.count().n_audio() == _signals.size());
228 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
232 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
234 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
239 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
241 Sample* const src = srcbuf.data();
242 Signal* signal (_signals[which]);
244 /* VBAP may distribute the signal across up to 3 speakers depending on
245 the configuration of the speakers.
247 But the set of speakers in use "this time" may be different from
248 the set of speakers "the last time". So we have up to 6 speakers
249 involved, and we have to interpolate so that those no longer
250 in use are rapidly faded to silence and those newly in use
251 are rapidly faded to their correct level. This prevents clicks
252 as we change the set of speakers used to put the signal in
255 However, the speakers are represented by output buffers, and other
256 speakers may write to the same buffers, so we cannot use
257 anything here that will simply assign new (sample) values
258 to the output buffers - everything must be done via mixing
259 functions and not assignment/copying.
262 vector<double>::size_type sz = signal->gains.size();
264 assert (sz == obufs.count().n_audio());
266 int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
268 /* set initial state of each output "record"
271 for (uint32_t o = 0; o < sz; ++o) {
275 /* for all outputs used this time and last time,
276 change the output record to show what has
281 for (int o = 0; o < 3; ++o) {
282 if (signal->outputs[o] != -1) {
284 outputs[signal->outputs[o]] |= 1;
287 if (signal->desired_outputs[o] != -1) {
289 outputs[signal->desired_outputs[o]] |= 1<<1;
293 /* at this point, we can test a speaker's status:
295 (*outputs[o] & 1) <= in use before
296 (*outputs[o] & 2) <= in use this time
297 (*outputs[o] & 3) == 3 <= in use both times
298 *outputs[o] == 0 <= not in use either time
302 for (int o = 0; o < 3; ++o) {
304 int output = signal->desired_outputs[o];
310 pan = gain_coefficient * signal->desired_gains[o];
312 if (pan == 0.0 && signal->gains[output] == 0.0) {
314 /* nothing deing delivered to this output */
316 signal->gains[output] = 0.0;
318 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
320 /* signal to this output but the gain coefficient has changed, so
321 interpolate between them.
324 AudioBuffer& buf (obufs.get_audio (output));
325 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
326 signal->gains[output] = pan;
330 /* signal to this output, same gain as before so just copy with gain
333 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
334 signal->gains[output] = pan;
338 /* clean up the outputs that were used last time but not this time
341 for (uint32_t o = 0; o < sz; ++o) {
342 if (outputs[o] == 1) {
343 /* take signal and deliver with a rapid fade out
345 AudioBuffer& buf (obufs.get_audio (o));
346 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
347 signal->gains[o] = 0.0;
351 /* note that the output buffers were all silenced at some point
352 so anything we didn't write to with this signal (or any others)
353 is just as it should be.
358 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
359 framepos_t /*start*/, framepos_t /*end*/,
360 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
362 /* XXX to be implemented */
366 VBAPanner::get_state ()
368 XMLNode& node (Panner::get_state());
369 node.add_property (X_("uri"), _descriptor.panner_uri);
370 /* this is needed to allow new sessions to load with old Ardour: */
371 node.add_property (X_("type"), _descriptor.name);
376 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
378 return new VBAPanner (p, s);
382 VBAPanner::in() const
384 return ChanCount (DataType::AUDIO, _signals.size());
388 VBAPanner::out() const
390 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
393 std::set<Evoral::Parameter>
394 VBAPanner::what_can_be_automated() const
396 set<Evoral::Parameter> s;
397 s.insert (Evoral::Parameter (PanAzimuthAutomation));
398 if (_signals.size() > 1) {
399 s.insert (Evoral::Parameter (PanWidthAutomation));
401 if (_speakers->dimension() == 3) {
402 s.insert (Evoral::Parameter (PanElevationAutomation));
408 VBAPanner::describe_parameter (Evoral::Parameter p)
411 case PanAzimuthAutomation:
413 case PanWidthAutomation:
415 case PanElevationAutomation:
416 return _("Elevation");
418 return _pannable->describe_parameter (p);
423 VBAPanner::value_as_string (boost::shared_ptr<const AutomationControl> ac) const
425 /* DO NOT USE LocaleGuard HERE */
426 double val = ac->get_value();
428 switch (ac->parameter().type()) {
429 case PanAzimuthAutomation: /* direction */
430 return string_compose (_("%1\u00B0"), (int (rint (val * 360.0))+180)%360);
432 case PanWidthAutomation: /* diffusion */
433 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
435 case PanElevationAutomation: /* elevation */
436 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
444 VBAPanner::signal_position (uint32_t n) const
446 if (n < _signals.size()) {
447 return _signals[n]->direction;
450 return AngularVector();
453 boost::shared_ptr<Speakers>
454 VBAPanner::get_speakers () const
456 return _speakers->parent();
460 VBAPanner::set_position (double p)
462 /* map into 0..1 range */
464 over -= (p >= 0) ? 0 : 1;
466 _pannable->pan_azimuth_control->set_value (p, Controllable::NoGroup);
470 VBAPanner::set_width (double w)
472 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)), Controllable::NoGroup);
476 VBAPanner::set_elevation (double e)
478 _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)), Controllable::NoGroup);
485 if (_signals.size() > 1) {
486 set_width (1.0 - (1.0 / (double)_signals.size()));