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) and elevation parameters */
122 double elevation = _pannable->pan_elevation_control->get_value() * 90.0;
124 if (_signals.size() > 1) {
125 double w = - (_pannable->pan_width_control->get_value());
126 double signal_direction = _pannable->pan_azimuth_control->get_value() - (w/2);
127 double grd_step_per_signal = w / (_signals.size() - 1);
128 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
132 int over = signal_direction;
133 over -= (signal_direction >= 0) ? 0 : 1;
134 signal_direction -= (double)over;
136 signal->direction = AngularVector (signal_direction * 360.0, elevation);
137 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
138 signal_direction += grd_step_per_signal;
140 } else if (_signals.size() == 1) {
141 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
143 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
145 Signal* s = _signals.front();
146 s->direction = AngularVector (center, elevation);
147 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
150 SignalPositionChanged(); /* emit */
154 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
156 /* calculates gain factors using loudspeaker setup and given direction */
161 double big_sm_g, gtmp[3];
163 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
164 big_sm_g = -100000.0;
166 gains[0] = gains[1] = gains[2] = 0;
167 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
169 for (i = 0; i < _speakers->n_tuples(); i++) {
171 small_g = 10000000.0;
173 for (j = 0; j < _speakers->dimension(); j++) {
177 for (k = 0; k < _speakers->dimension(); k++) {
178 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
181 if (gtmp[j] < small_g) {
186 if (small_g > big_sm_g) {
193 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
194 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
196 if (_speakers->dimension() == 3) {
198 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
206 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
216 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
219 vector<Signal*>::iterator s;
221 assert (inbufs.count().n_audio() == _signals.size());
223 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
227 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
229 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
234 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
236 Sample* const src = srcbuf.data();
237 Signal* signal (_signals[which]);
239 /* VBAP may distribute the signal across up to 3 speakers depending on
240 the configuration of the speakers.
242 But the set of speakers in use "this time" may be different from
243 the set of speakers "the last time". So we have up to 6 speakers
244 involved, and we have to interpolate so that those no longer
245 in use are rapidly faded to silence and those newly in use
246 are rapidly faded to their correct level. This prevents clicks
247 as we change the set of speakers used to put the signal in
250 However, the speakers are represented by output buffers, and other
251 speakers may write to the same buffers, so we cannot use
252 anything here that will simply assign new (sample) values
253 to the output buffers - everything must be done via mixing
254 functions and not assignment/copying.
257 vector<double>::size_type sz = signal->gains.size();
259 assert (sz == obufs.count().n_audio());
261 int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
263 /* set initial state of each output "record"
266 for (uint32_t o = 0; o < sz; ++o) {
270 /* for all outputs used this time and last time,
271 change the output record to show what has
276 for (int o = 0; o < 3; ++o) {
277 if (signal->outputs[o] != -1) {
279 outputs[signal->outputs[o]] |= 1;
282 if (signal->desired_outputs[o] != -1) {
284 outputs[signal->desired_outputs[o]] |= 1<<1;
288 /* at this point, we can test a speaker's status:
290 (*outputs[o] & 1) <= in use before
291 (*outputs[o] & 2) <= in use this time
292 (*outputs[o] & 3) == 3 <= in use both times
293 *outputs[o] == 0 <= not in use either time
297 for (int o = 0; o < 3; ++o) {
299 int output = signal->desired_outputs[o];
305 pan = gain_coefficient * signal->desired_gains[o];
307 if (pan == 0.0 && signal->gains[output] == 0.0) {
309 /* nothing deing delivered to this output */
311 signal->gains[output] = 0.0;
313 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
315 /* signal to this output but the gain coefficient has changed, so
316 interpolate between them.
319 AudioBuffer& buf (obufs.get_audio (output));
320 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
321 signal->gains[output] = pan;
325 /* signal to this output, same gain as before so just copy with gain
328 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
329 signal->gains[output] = pan;
333 /* clean up the outputs that were used last time but not this time
336 for (uint32_t o = 0; o < sz; ++o) {
337 if (outputs[o] == 1) {
338 /* take signal and deliver with a rapid fade out
340 AudioBuffer& buf (obufs.get_audio (o));
341 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
342 signal->gains[o] = 0.0;
346 /* note that the output buffers were all silenced at some point
347 so anything we didn't write to with this signal (or any others)
348 is just as it should be.
353 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
354 framepos_t /*start*/, framepos_t /*end*/,
355 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
357 /* XXX to be implemented */
361 VBAPanner::get_state ()
363 XMLNode& node (Panner::get_state());
364 node.add_property (X_("uri"), _descriptor.panner_uri);
365 /* this is needed to allow new sessions to load with old Ardour: */
366 node.add_property (X_("type"), _descriptor.name);
371 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
373 return new VBAPanner (p, s);
377 VBAPanner::in() const
379 return ChanCount (DataType::AUDIO, _signals.size());
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 if (_speakers->dimension() == 3) {
397 s.insert (Evoral::Parameter (PanElevationAutomation));
403 VBAPanner::describe_parameter (Evoral::Parameter p)
406 case PanAzimuthAutomation:
407 return _("Direction");
408 case PanWidthAutomation:
409 return _("Diffusion");
410 case PanElevationAutomation:
411 return _("Elevation");
413 return _pannable->describe_parameter (p);
418 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
420 /* DO NOT USE LocaleGuard HERE */
421 double val = ac->get_value();
423 switch (ac->parameter().type()) {
424 case PanAzimuthAutomation: /* direction */
425 return string_compose (_("%1\u00B0"), int (rint (val * 360.0)));
427 case PanWidthAutomation: /* diffusion */
428 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
430 case PanElevationAutomation: /* elevation */
431 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
434 return _pannable->value_as_string (ac);
439 VBAPanner::signal_position (uint32_t n) const
441 if (n < _signals.size()) {
442 return _signals[n]->direction;
445 return AngularVector();
448 boost::shared_ptr<Speakers>
449 VBAPanner::get_speakers () const
451 return _speakers->parent();
455 VBAPanner::set_position (double p)
457 /* map into 0..1 range */
459 over -= (p >= 0) ? 0 : 1;
461 _pannable->pan_azimuth_control->set_value (p);
465 VBAPanner::set_width (double w)
467 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
471 VBAPanner::set_elevation (double e)
473 _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)));
480 if (_signals.size() > 1) {
481 set_width (1.0 - (1.0 / (double)_signals.size()));