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 = {
53 extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
55 VBAPanner::Signal::Signal (Session&, VBAPanner&, uint32_t, uint32_t n_speakers)
57 resize_gains (n_speakers);
59 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
60 outputs[0] = outputs[1] = outputs[2] = -1;
61 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
65 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
67 gains.assign (n, 0.0);
70 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
72 , _speakers (new VBAPSpeakers (s))
74 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
75 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
80 VBAPanner::~VBAPanner ()
86 VBAPanner::clear_signals ()
88 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
95 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
97 uint32_t n = in.n_audio();
101 for (uint32_t i = 0; i < n; ++i) {
102 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
103 _signals.push_back (s);
113 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
116 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
118 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
120 if (_signals.size() > 1) {
122 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
123 so that a width of 1 corresponds to a signal equally present from all directions,
124 and a width of zero corresponds to a point source from the "center" (above) point
125 on the perimeter of the speaker array.
128 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
130 double min_dir = center - (w/2.0);
132 min_dir = 360.0 + min_dir; // its already negative
134 min_dir = max (min (min_dir, 360.0), 0.0);
136 double max_dir = center + (w/2.0);
137 if (max_dir > 360.0) {
138 max_dir = max_dir - 360.0;
140 max_dir = max (min (max_dir, 360.0), 0.0);
142 if (max_dir < min_dir) {
143 swap (max_dir, min_dir);
146 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
147 double signal_direction = min_dir;
151 /* positive width - normal order of signal spread */
153 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
157 signal->direction = AngularVector (signal_direction, 0.0);
158 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
159 signal_direction += degree_step_per_signal;
163 /* inverted width - reverse order of signal spread */
165 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
169 signal->direction = AngularVector (signal_direction, 0.0);
170 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
171 signal_direction += degree_step_per_signal;
175 } else if (_signals.size() == 1) {
177 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
179 Signal* s = _signals.front();
180 s->direction = AngularVector (center, 0);
181 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
186 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
188 /* calculates gain factors using loudspeaker setup and given direction */
193 double big_sm_g, gtmp[3];
195 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
196 big_sm_g = -100000.0;
198 gains[0] = gains[1] = gains[2] = 0;
199 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
201 for (i = 0; i < _speakers->n_tuples(); i++) {
203 small_g = 10000000.0;
205 for (j = 0; j < _speakers->dimension(); j++) {
209 for (k = 0; k < _speakers->dimension(); k++) {
210 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
213 if (gtmp[j] < small_g) {
218 if (small_g > big_sm_g) {
225 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
226 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
228 if (_speakers->dimension() == 3) {
230 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
238 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
248 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
251 vector<Signal*>::iterator s;
253 assert (inbufs.count().n_audio() == _signals.size());
255 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
259 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
261 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
266 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
268 Sample* const src = srcbuf.data();
269 Signal* signal (_signals[which]);
271 /* VBAP may distribute the signal across up to 3 speakers depending on
272 the configuration of the speakers.
274 But the set of speakers in use "this time" may be different from
275 the set of speakers "the last time". So we have up to 6 speakers
276 involved, and we have to interpolate so that those no longer
277 in use are rapidly faded to silence and those newly in use
278 are rapidly faded to their correct level. This prevents clicks
279 as we change the set of speakers used to put the signal in
282 However, the speakers are represented by output buffers, and other
283 speakers may write to the same buffers, so we cannot use
284 anything here that will simply assign new (sample) values
285 to the output buffers - everything must be done via mixing
286 functions and not assignment/copying.
289 vector<double>::size_type sz = signal->gains.size();
291 assert (sz == obufs.count().n_audio());
293 int8_t outputs[sz]; // on the stack, no malloc
295 /* set initial state of each output "record"
298 for (uint32_t o = 0; o < sz; ++o) {
302 /* for all outputs used this time and last time,
303 change the output record to show what has
308 for (int o = 0; o < 3; ++o) {
309 if (signal->outputs[o] != -1) {
311 outputs[signal->outputs[o]] |= 1;
314 if (signal->desired_outputs[o] != -1) {
316 outputs[signal->desired_outputs[o]] |= 1<<1;
320 /* at this point, we can test a speaker's status:
322 (outputs[o] & 1) <= in use before
323 (outputs[o] & 2) <= in use this time
324 (outputs[o] & 3) == 3 <= in use both times
325 outputs[o] == 0 <= not in use either time
329 for (int o = 0; o < 3; ++o) {
331 int output = signal->desired_outputs[o];
337 pan = gain_coefficient * signal->desired_gains[o];
339 if (pan == 0.0 && signal->gains[output] == 0.0) {
341 /* nothing deing delivered to this output */
343 signal->gains[output] = 0.0;
345 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
347 /* signal to this output but the gain coefficient has changed, so
348 interpolate between them.
351 AudioBuffer& buf (obufs.get_audio (output));
352 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
353 signal->gains[output] = pan;
357 /* signal to this output, same gain as before so just copy with gain
360 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
361 signal->gains[output] = pan;
365 /* clean up the outputs that were used last time but not this time
368 for (uint32_t o = 0; o < sz; ++o) {
369 if (outputs[o] == 1) {
370 /* take signal and deliver with a rapid fade out
372 AudioBuffer& buf (obufs.get_audio (o));
373 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
374 signal->gains[o] = 0.0;
378 /* note that the output buffers were all silenced at some point
379 so anything we didn't write to with this signal (or any others)
380 is just as it should be.
385 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
386 framepos_t /*start*/, framepos_t /*end*/,
387 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
389 /* XXX to be implemented */
393 VBAPanner::get_state ()
395 XMLNode& node (Panner::get_state());
396 node.add_property (X_("type"), _descriptor.name);
401 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
403 return new VBAPanner (p, s);
407 VBAPanner::in() const
409 return ChanCount (DataType::AUDIO, _signals.size());
413 VBAPanner::out() const
415 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
418 std::set<Evoral::Parameter>
419 VBAPanner::what_can_be_automated() const
421 set<Evoral::Parameter> s;
422 s.insert (Evoral::Parameter (PanAzimuthAutomation));
423 if (_signals.size() > 1) {
424 s.insert (Evoral::Parameter (PanWidthAutomation));
430 VBAPanner::describe_parameter (Evoral::Parameter p)
433 case PanAzimuthAutomation:
434 return _("Direction");
435 case PanWidthAutomation:
436 return _("Diffusion");
438 return _pannable->describe_parameter (p);
443 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
445 /* DO NOT USE LocaleGuard HERE */
446 double val = ac->get_value();
448 switch (ac->parameter().type()) {
449 case PanAzimuthAutomation: /* direction */
450 return string_compose (_("%1"), int (rint (val * 360.0)));
452 case PanWidthAutomation: /* diffusion */
453 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
456 return _pannable->value_as_string (ac);
461 VBAPanner::signal_position (uint32_t n) const
463 if (n < _signals.size()) {
464 return _signals[n]->direction;
467 return AngularVector();
470 boost::shared_ptr<Speakers>
471 VBAPanner::get_speakers () const
473 return _speakers->parent();
477 VBAPanner::set_position (double p)
487 _pannable->pan_azimuth_control->set_value (p);
491 VBAPanner::set_width (double w)
493 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));