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 = _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 = _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];
166 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
167 big_sm_g = -100000.0;
169 gains[0] = gains[1] = gains[2] = 0;
170 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
172 for (i = 0; i < _speakers->n_tuples(); i++) {
174 small_g = 10000000.0;
176 for (j = 0; j < _speakers->dimension(); j++) {
180 for (k = 0; k < _speakers->dimension(); k++) {
181 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
184 if (gtmp[j] < small_g) {
189 if (small_g > big_sm_g) {
196 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
197 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
199 if (_speakers->dimension() == 3) {
201 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
209 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
219 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
222 vector<Signal*>::iterator s;
224 assert (inbufs.count().n_audio() == _signals.size());
226 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
230 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
232 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
237 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
239 Sample* const src = srcbuf.data();
240 Signal* signal (_signals[which]);
242 /* VBAP may distribute the signal across up to 3 speakers depending on
243 the configuration of the speakers.
245 But the set of speakers in use "this time" may be different from
246 the set of speakers "the last time". So we have up to 6 speakers
247 involved, and we have to interpolate so that those no longer
248 in use are rapidly faded to silence and those newly in use
249 are rapidly faded to their correct level. This prevents clicks
250 as we change the set of speakers used to put the signal in
253 However, the speakers are represented by output buffers, and other
254 speakers may write to the same buffers, so we cannot use
255 anything here that will simply assign new (sample) values
256 to the output buffers - everything must be done via mixing
257 functions and not assignment/copying.
260 vector<double>::size_type sz = signal->gains.size();
262 assert (sz == obufs.count().n_audio());
264 int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
266 /* set initial state of each output "record"
269 for (uint32_t o = 0; o < sz; ++o) {
273 /* for all outputs used this time and last time,
274 change the output record to show what has
279 for (int o = 0; o < 3; ++o) {
280 if (signal->outputs[o] != -1) {
282 outputs[signal->outputs[o]] |= 1;
285 if (signal->desired_outputs[o] != -1) {
287 outputs[signal->desired_outputs[o]] |= 1<<1;
291 /* at this point, we can test a speaker's status:
293 (*outputs[o] & 1) <= in use before
294 (*outputs[o] & 2) <= in use this time
295 (*outputs[o] & 3) == 3 <= in use both times
296 *outputs[o] == 0 <= not in use either time
300 for (int o = 0; o < 3; ++o) {
302 int output = signal->desired_outputs[o];
308 pan = gain_coefficient * signal->desired_gains[o];
310 if (pan == 0.0 && signal->gains[output] == 0.0) {
312 /* nothing deing delivered to this output */
314 signal->gains[output] = 0.0;
316 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
318 /* signal to this output but the gain coefficient has changed, so
319 interpolate between them.
322 AudioBuffer& buf (obufs.get_audio (output));
323 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
324 signal->gains[output] = pan;
328 /* signal to this output, same gain as before so just copy with gain
331 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
332 signal->gains[output] = pan;
336 /* clean up the outputs that were used last time but not this time
339 for (uint32_t o = 0; o < sz; ++o) {
340 if (outputs[o] == 1) {
341 /* take signal and deliver with a rapid fade out
343 AudioBuffer& buf (obufs.get_audio (o));
344 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
345 signal->gains[o] = 0.0;
349 /* note that the output buffers were all silenced at some point
350 so anything we didn't write to with this signal (or any others)
351 is just as it should be.
356 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
357 framepos_t /*start*/, framepos_t /*end*/,
358 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
360 /* XXX to be implemented */
364 VBAPanner::get_state ()
366 XMLNode& node (Panner::get_state());
367 node.add_property (X_("uri"), _descriptor.panner_uri);
368 /* this is needed to allow new sessions to load with old Ardour: */
369 node.add_property (X_("type"), _descriptor.name);
374 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
376 return new VBAPanner (p, s);
380 VBAPanner::in() const
382 return ChanCount (DataType::AUDIO, _signals.size());
386 VBAPanner::out() const
388 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
391 std::set<Evoral::Parameter>
392 VBAPanner::what_can_be_automated() const
394 set<Evoral::Parameter> s;
395 s.insert (Evoral::Parameter (PanAzimuthAutomation));
396 if (_signals.size() > 1) {
397 s.insert (Evoral::Parameter (PanWidthAutomation));
399 if (_speakers->dimension() == 3) {
400 s.insert (Evoral::Parameter (PanElevationAutomation));
406 VBAPanner::describe_parameter (Evoral::Parameter p)
409 case PanAzimuthAutomation:
410 return _("Direction");
411 case PanWidthAutomation:
412 return _("Diffusion");
413 case PanElevationAutomation:
414 return _("Elevation");
416 return _pannable->describe_parameter (p);
421 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
423 /* DO NOT USE LocaleGuard HERE */
424 double val = ac->get_value();
426 switch (ac->parameter().type()) {
427 case PanAzimuthAutomation: /* direction */
428 return string_compose (_("%1\u00B0"), int (rint (val * 360.0)));
430 case PanWidthAutomation: /* diffusion */
431 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
433 case PanElevationAutomation: /* elevation */
434 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
437 return _pannable->value_as_string (ac);
442 VBAPanner::signal_position (uint32_t n) const
444 if (n < _signals.size()) {
445 return _signals[n]->direction;
448 return AngularVector();
451 boost::shared_ptr<Speakers>
452 VBAPanner::get_speakers () const
454 return _speakers->parent();
458 VBAPanner::set_position (double p)
460 /* map into 0..1 range */
462 over -= (p >= 0) ? 0 : 1;
464 _pannable->pan_azimuth_control->set_value (p);
468 VBAPanner::set_width (double w)
470 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
474 VBAPanner::set_elevation (double e)
476 _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)));
483 if (_signals.size() > 1) {
484 set_width (1.0 - (1.0 / (double)_signals.size()));