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.
28 #include "pbd/cartesian.h"
29 #include "pbd/compose.h"
31 #include "ardour/amp.h"
32 #include "ardour/audio_buffer.h"
33 #include "ardour/buffer_set.h"
34 #include "ardour/pan_controllable.h"
35 #include "ardour/pannable.h"
36 #include "ardour/speakers.h"
39 #include "vbap_speakers.h"
44 using namespace ARDOUR;
47 static PanPluginDescriptor _descriptor = {
49 "http://ardour.org/plugin/panner_vbap",
50 "http://ardour.org/plugin/panner_vbap#ui",
56 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
58 VBAPanner::Signal::Signal (Session&, VBAPanner&, uint32_t, uint32_t n_speakers)
60 resize_gains (n_speakers);
62 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
63 outputs[0] = outputs[1] = outputs[2] = -1;
64 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
68 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
70 gains.assign (n, 0.0);
73 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
75 , _speakers (new VBAPSpeakers (s))
77 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
78 _pannable->pan_elevation_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
79 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
84 VBAPanner::~VBAPanner ()
90 VBAPanner::clear_signals ()
92 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
99 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
101 uint32_t n = in.n_audio();
105 for (uint32_t i = 0; i < n; ++i) {
106 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
107 _signals.push_back (s);
117 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) and elevation parameters */
118 double elevation = _pannable->pan_elevation_control->get_value() * 90.0;
120 if (_signals.size() > 1) {
121 double w = (_pannable->pan_width_control->get_value());
122 double signal_direction = _pannable->pan_azimuth_control->get_value() - (w/2);
123 double grd_step_per_signal = w / (_signals.size() - 1);
124 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
128 int over = signal_direction;
129 over -= (signal_direction >= 0) ? 0 : 1;
130 signal_direction -= (double)over;
132 signal->direction = AngularVector (signal_direction * 360.0, elevation);
133 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
134 signal_direction += grd_step_per_signal;
136 } else if (_signals.size() == 1) {
137 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
139 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
141 Signal* s = _signals.front();
142 s->direction = AngularVector (center, elevation);
143 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
146 SignalPositionChanged(); /* emit */
150 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
152 /* calculates gain factors using loudspeaker setup and given direction */
157 double big_sm_g, gtmp[3];
159 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
160 big_sm_g = -100000.0;
162 gains[0] = gains[1] = gains[2] = 0;
163 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
165 for (i = 0; i < _speakers->n_tuples(); i++) {
167 small_g = 10000000.0;
169 for (j = 0; j < _speakers->dimension(); j++) {
173 for (k = 0; k < _speakers->dimension(); k++) {
174 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
177 if (gtmp[j] < small_g) {
182 if (small_g > big_sm_g) {
189 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
190 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
192 if (_speakers->dimension() == 3) {
194 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
202 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
212 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
215 vector<Signal*>::iterator s;
217 assert (inbufs.count().n_audio() == _signals.size());
219 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
223 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
225 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
230 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
232 Sample* const src = srcbuf.data();
233 Signal* signal (_signals[which]);
235 /* VBAP may distribute the signal across up to 3 speakers depending on
236 the configuration of the speakers.
238 But the set of speakers in use "this time" may be different from
239 the set of speakers "the last time". So we have up to 6 speakers
240 involved, and we have to interpolate so that those no longer
241 in use are rapidly faded to silence and those newly in use
242 are rapidly faded to their correct level. This prevents clicks
243 as we change the set of speakers used to put the signal in
246 However, the speakers are represented by output buffers, and other
247 speakers may write to the same buffers, so we cannot use
248 anything here that will simply assign new (sample) values
249 to the output buffers - everything must be done via mixing
250 functions and not assignment/copying.
253 vector<double>::size_type sz = signal->gains.size();
255 assert (sz == obufs.count().n_audio());
257 int8_t outputs[sz]; // on the stack, no malloc
259 /* set initial state of each output "record"
262 for (uint32_t o = 0; o < sz; ++o) {
266 /* for all outputs used this time and last time,
267 change the output record to show what has
272 for (int o = 0; o < 3; ++o) {
273 if (signal->outputs[o] != -1) {
275 outputs[signal->outputs[o]] |= 1;
278 if (signal->desired_outputs[o] != -1) {
280 outputs[signal->desired_outputs[o]] |= 1<<1;
284 /* at this point, we can test a speaker's status:
286 (outputs[o] & 1) <= in use before
287 (outputs[o] & 2) <= in use this time
288 (outputs[o] & 3) == 3 <= in use both times
289 outputs[o] == 0 <= not in use either time
293 for (int o = 0; o < 3; ++o) {
295 int output = signal->desired_outputs[o];
301 pan = gain_coefficient * signal->desired_gains[o];
303 if (pan == 0.0 && signal->gains[output] == 0.0) {
305 /* nothing deing delivered to this output */
307 signal->gains[output] = 0.0;
309 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
311 /* signal to this output but the gain coefficient has changed, so
312 interpolate between them.
315 AudioBuffer& buf (obufs.get_audio (output));
316 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
317 signal->gains[output] = pan;
321 /* signal to this output, same gain as before so just copy with gain
324 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
325 signal->gains[output] = pan;
329 /* clean up the outputs that were used last time but not this time
332 for (uint32_t o = 0; o < sz; ++o) {
333 if (outputs[o] == 1) {
334 /* take signal and deliver with a rapid fade out
336 AudioBuffer& buf (obufs.get_audio (o));
337 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
338 signal->gains[o] = 0.0;
342 /* note that the output buffers were all silenced at some point
343 so anything we didn't write to with this signal (or any others)
344 is just as it should be.
349 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
350 framepos_t /*start*/, framepos_t /*end*/,
351 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
353 /* XXX to be implemented */
357 VBAPanner::get_state ()
359 XMLNode& node (Panner::get_state());
360 node.add_property (X_("uri"), _descriptor.panner_uri);
361 /* this is needed to allow new sessions to load with old Ardour: */
362 node.add_property (X_("type"), _descriptor.name);
367 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
369 return new VBAPanner (p, s);
373 VBAPanner::in() const
375 return ChanCount (DataType::AUDIO, _signals.size());
379 VBAPanner::out() const
381 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
384 std::set<Evoral::Parameter>
385 VBAPanner::what_can_be_automated() const
387 set<Evoral::Parameter> s;
388 s.insert (Evoral::Parameter (PanAzimuthAutomation));
389 if (_signals.size() > 1) {
390 s.insert (Evoral::Parameter (PanWidthAutomation));
392 if (_speakers->dimension() == 3) {
393 s.insert (Evoral::Parameter (PanElevationAutomation));
399 VBAPanner::describe_parameter (Evoral::Parameter p)
402 case PanAzimuthAutomation:
403 return _("Direction");
404 case PanWidthAutomation:
405 return _("Diffusion");
406 case PanElevationAutomation:
407 return _("Elevation");
409 return _pannable->describe_parameter (p);
414 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
416 /* DO NOT USE LocaleGuard HERE */
417 double val = ac->get_value();
419 switch (ac->parameter().type()) {
420 case PanAzimuthAutomation: /* direction */
421 return string_compose (_("%1\u00B0"), int (rint (val * 360.0)));
423 case PanWidthAutomation: /* diffusion */
424 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
426 case PanElevationAutomation: /* elevation */
427 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
430 return _pannable->value_as_string (ac);
435 VBAPanner::signal_position (uint32_t n) const
437 if (n < _signals.size()) {
438 return _signals[n]->direction;
441 return AngularVector();
444 boost::shared_ptr<Speakers>
445 VBAPanner::get_speakers () const
447 return _speakers->parent();
451 VBAPanner::set_position (double p)
453 /* map into 0..1 range */
455 over -= (p >= 0) ? 0 : 1;
457 _pannable->pan_azimuth_control->set_value (p);
461 VBAPanner::set_width (double w)
463 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
467 VBAPanner::set_elevation (double e)
469 _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)));
476 if (_signals.size() > 1) {
477 set_width (1.0 - (1.0 / (double)_signals.size()));