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));
80 if (!_pannable->has_state()) {
87 VBAPanner::~VBAPanner ()
93 VBAPanner::clear_signals ()
95 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
102 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
104 uint32_t n = in.n_audio();
108 for (uint32_t i = 0; i < n; ++i) {
109 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
110 _signals.push_back (s);
120 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) and elevation parameters */
121 double elevation = _pannable->pan_elevation_control->get_value() * 90.0;
123 if (_signals.size() > 1) {
124 double w = - (_pannable->pan_width_control->get_value());
125 double signal_direction = 1.0 - (_pannable->pan_azimuth_control->get_value() + (w/2));
126 double grd_step_per_signal = w / (_signals.size() - 1);
127 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
131 int over = signal_direction;
132 over -= (signal_direction >= 0) ? 0 : 1;
133 signal_direction -= (double)over;
135 signal->direction = AngularVector (signal_direction * 360.0, elevation);
136 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
137 signal_direction += grd_step_per_signal;
139 } else if (_signals.size() == 1) {
140 double center = (1.0 - _pannable->pan_azimuth_control->get_value()) * 360.0;
142 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
144 Signal* s = _signals.front();
145 s->direction = AngularVector (center, elevation);
146 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
149 SignalPositionChanged(); /* emit */
153 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
155 /* calculates gain factors using loudspeaker setup and given direction */
160 double big_sm_g, gtmp[3];
162 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
163 big_sm_g = -100000.0;
165 gains[0] = gains[1] = gains[2] = 0;
166 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
168 for (i = 0; i < _speakers->n_tuples(); i++) {
170 small_g = 10000000.0;
172 for (j = 0; j < _speakers->dimension(); j++) {
176 for (k = 0; k < _speakers->dimension(); k++) {
177 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
180 if (gtmp[j] < small_g) {
185 if (small_g > big_sm_g) {
192 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
193 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
195 if (_speakers->dimension() == 3) {
197 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
205 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
215 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
218 vector<Signal*>::iterator s;
220 assert (inbufs.count().n_audio() == _signals.size());
222 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
226 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
228 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
233 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
235 Sample* const src = srcbuf.data();
236 Signal* signal (_signals[which]);
238 /* VBAP may distribute the signal across up to 3 speakers depending on
239 the configuration of the speakers.
241 But the set of speakers in use "this time" may be different from
242 the set of speakers "the last time". So we have up to 6 speakers
243 involved, and we have to interpolate so that those no longer
244 in use are rapidly faded to silence and those newly in use
245 are rapidly faded to their correct level. This prevents clicks
246 as we change the set of speakers used to put the signal in
249 However, the speakers are represented by output buffers, and other
250 speakers may write to the same buffers, so we cannot use
251 anything here that will simply assign new (sample) values
252 to the output buffers - everything must be done via mixing
253 functions and not assignment/copying.
256 vector<double>::size_type sz = signal->gains.size();
258 assert (sz == obufs.count().n_audio());
260 int8_t outputs[sz]; // on the stack, no malloc
262 /* set initial state of each output "record"
265 for (uint32_t o = 0; o < sz; ++o) {
269 /* for all outputs used this time and last time,
270 change the output record to show what has
275 for (int o = 0; o < 3; ++o) {
276 if (signal->outputs[o] != -1) {
278 outputs[signal->outputs[o]] |= 1;
281 if (signal->desired_outputs[o] != -1) {
283 outputs[signal->desired_outputs[o]] |= 1<<1;
287 /* at this point, we can test a speaker's status:
289 (outputs[o] & 1) <= in use before
290 (outputs[o] & 2) <= in use this time
291 (outputs[o] & 3) == 3 <= in use both times
292 outputs[o] == 0 <= not in use either time
296 for (int o = 0; o < 3; ++o) {
298 int output = signal->desired_outputs[o];
304 pan = gain_coefficient * signal->desired_gains[o];
306 if (pan == 0.0 && signal->gains[output] == 0.0) {
308 /* nothing deing delivered to this output */
310 signal->gains[output] = 0.0;
312 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
314 /* signal to this output but the gain coefficient has changed, so
315 interpolate between them.
318 AudioBuffer& buf (obufs.get_audio (output));
319 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
320 signal->gains[output] = pan;
324 /* signal to this output, same gain as before so just copy with gain
327 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
328 signal->gains[output] = pan;
332 /* clean up the outputs that were used last time but not this time
335 for (uint32_t o = 0; o < sz; ++o) {
336 if (outputs[o] == 1) {
337 /* take signal and deliver with a rapid fade out
339 AudioBuffer& buf (obufs.get_audio (o));
340 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
341 signal->gains[o] = 0.0;
345 /* note that the output buffers were all silenced at some point
346 so anything we didn't write to with this signal (or any others)
347 is just as it should be.
352 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
353 framepos_t /*start*/, framepos_t /*end*/,
354 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
356 /* XXX to be implemented */
360 VBAPanner::get_state ()
362 XMLNode& node (Panner::get_state());
363 node.add_property (X_("uri"), _descriptor.panner_uri);
364 /* this is needed to allow new sessions to load with old Ardour: */
365 node.add_property (X_("type"), _descriptor.name);
370 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
372 return new VBAPanner (p, s);
376 VBAPanner::in() const
378 return ChanCount (DataType::AUDIO, _signals.size());
382 VBAPanner::out() const
384 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
387 std::set<Evoral::Parameter>
388 VBAPanner::what_can_be_automated() const
390 set<Evoral::Parameter> s;
391 s.insert (Evoral::Parameter (PanAzimuthAutomation));
392 if (_signals.size() > 1) {
393 s.insert (Evoral::Parameter (PanWidthAutomation));
395 if (_speakers->dimension() == 3) {
396 s.insert (Evoral::Parameter (PanElevationAutomation));
402 VBAPanner::describe_parameter (Evoral::Parameter p)
405 case PanAzimuthAutomation:
406 return _("Direction");
407 case PanWidthAutomation:
409 case PanElevationAutomation:
410 return _("Elevation");
412 return _pannable->describe_parameter (p);
417 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
419 /* DO NOT USE LocaleGuard HERE */
420 double val = ac->get_value();
422 switch (ac->parameter().type()) {
423 case PanAzimuthAutomation: /* direction */
424 return string_compose (_("%1\u00B0"), (int (rint (val * 360.0))+180)%360);
426 case PanWidthAutomation: /* diffusion */
427 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
429 case PanElevationAutomation: /* elevation */
430 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
438 VBAPanner::signal_position (uint32_t n) const
440 if (n < _signals.size()) {
441 return _signals[n]->direction;
444 return AngularVector();
447 boost::shared_ptr<Speakers>
448 VBAPanner::get_speakers () const
450 return _speakers->parent();
454 VBAPanner::set_position (double p)
456 /* map into 0..1 range */
458 over -= (p >= 0) ? 0 : 1;
460 _pannable->pan_azimuth_control->set_value (p);
464 VBAPanner::set_width (double w)
466 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
470 VBAPanner::set_elevation (double e)
472 _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)));
479 if (_signals.size() > 1) {
480 set_width (1.0 - (1.0 / (double)_signals.size()));