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.
30 #include "pbd/cartesian.h"
31 #include "pbd/compose.h"
33 #include "ardour/amp.h"
34 #include "ardour/audio_buffer.h"
35 #include "ardour/buffer_set.h"
36 #include "ardour/pan_controllable.h"
37 #include "ardour/pannable.h"
38 #include "ardour/speakers.h"
41 #include "vbap_speakers.h"
46 using namespace ARDOUR;
49 static PanPluginDescriptor _descriptor = {
55 extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
57 VBAPanner::Signal::Signal (Session&, VBAPanner&, uint32_t, uint32_t n_speakers)
59 resize_gains (n_speakers);
61 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
62 outputs[0] = outputs[1] = outputs[2] = -1;
63 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
67 VBAPanner::Signal::resize_gains (uint32_t n)
69 gains.assign (n, 0.0);
72 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
74 , _speakers (new VBAPSpeakers (s))
76 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
77 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
82 VBAPanner::~VBAPanner ()
88 VBAPanner::clear_signals ()
90 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
97 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
99 uint32_t n = in.n_audio();
103 for (uint32_t i = 0; i < n; ++i) {
104 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
105 _signals.push_back (s);
115 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
118 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
120 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
122 if (_signals.size() > 1) {
124 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
125 so that a width of 1 corresponds to a signal equally present from all directions,
126 and a width of zero corresponds to a point source from the "center" (above) point
127 on the perimeter of the speaker array.
130 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
132 double min_dir = center - (w/2.0);
134 min_dir = 360.0 + min_dir; // its already negative
136 min_dir = max (min (min_dir, 360.0), 0.0);
138 double max_dir = center + (w/2.0);
139 if (max_dir > 360.0) {
140 max_dir = max_dir - 360.0;
142 max_dir = max (min (max_dir, 360.0), 0.0);
144 if (max_dir < min_dir) {
145 swap (max_dir, min_dir);
148 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
149 double signal_direction = min_dir;
153 /* positive width - normal order of signal spread */
155 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
159 signal->direction = AngularVector (signal_direction, 0.0);
160 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
161 signal_direction += degree_step_per_signal;
165 /* inverted width - reverse order of signal spread */
167 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
171 signal->direction = AngularVector (signal_direction, 0.0);
172 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
173 signal_direction += degree_step_per_signal;
177 } else if (_signals.size() == 1) {
179 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
181 Signal* s = _signals.front();
182 s->direction = AngularVector (center, 0);
183 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
188 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
190 /* calculates gain factors using loudspeaker setup and given direction */
195 double big_sm_g, gtmp[3];
197 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
198 big_sm_g = -100000.0;
200 gains[0] = gains[1] = gains[2] = 0;
201 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
203 for (i = 0; i < _speakers->n_tuples(); i++) {
205 small_g = 10000000.0;
207 for (j = 0; j < _speakers->dimension(); j++) {
211 for (k = 0; k < _speakers->dimension(); k++) {
212 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
215 if (gtmp[j] < small_g) {
220 if (small_g > big_sm_g) {
227 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
228 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
230 if (_speakers->dimension() == 3) {
232 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
240 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
250 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
253 vector<Signal*>::iterator s;
255 assert (inbufs.count().n_audio() == _signals.size());
257 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
261 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
263 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
268 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
270 Sample* const src = srcbuf.data();
271 Signal* signal (_signals[which]);
273 /* VBAP may distribute the signal across up to 3 speakers depending on
274 the configuration of the speakers.
276 But the set of speakers in use "this time" may be different from
277 the set of speakers "the last time". So we have up to 6 speakers
278 involved, and we have to interpolate so that those no longer
279 in use are rapidly faded to silence and those newly in use
280 are rapidly faded to their correct level. This prevents clicks
281 as we change the set of speakers used to put the signal in
284 However, the speakers are represented by output buffers, and other
285 speakers may write to the same buffers, so we cannot use
286 anything here that will simply assign new (sample) values
287 to the output buffers - everything must be done via mixing
288 functions and not assignment/copying.
291 vector<double>::size_type sz = signal->gains.size();
293 assert (sz == obufs.count().n_audio());
295 int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
297 /* set initial state of each output "record"
300 for (uint32_t o = 0; o < sz; ++o) {
304 /* for all outputs used this time and last time,
305 change the output record to show what has
310 for (int o = 0; o < 3; ++o) {
311 if (signal->outputs[o] != -1) {
313 outputs[signal->outputs[o]] |= 1;
316 if (signal->desired_outputs[o] != -1) {
318 outputs[signal->desired_outputs[o]] |= 1<<1;
322 /* at this point, we can test a speaker's status:
324 (*outputs[o] & 1) <= in use before
325 (*outputs[o] & 2) <= in use this time
326 (*outputs[o] & 3) == 3 <= in use both times
327 *outputs[o] == 0 <= not in use either time
331 for (int o = 0; o < 3; ++o) {
333 int output = signal->desired_outputs[o];
339 pan = gain_coefficient * signal->desired_gains[o];
341 if (pan == 0.0 && signal->gains[output] == 0.0) {
343 /* nothing deing delivered to this output */
345 signal->gains[output] = 0.0;
347 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
349 /* signal to this output but the gain coefficient has changed, so
350 interpolate between them.
353 AudioBuffer& buf (obufs.get_audio (output));
354 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
355 signal->gains[output] = pan;
359 /* signal to this output, same gain as before so just copy with gain
362 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
363 signal->gains[output] = pan;
367 /* clean up the outputs that were used last time but not this time
370 for (uint32_t o = 0; o < sz; ++o) {
371 if (outputs[o] == 1) {
372 /* take signal and deliver with a rapid fade out
374 AudioBuffer& buf (obufs.get_audio (o));
375 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
376 signal->gains[o] = 0.0;
380 /* note that the output buffers were all silenced at some point
381 so anything we didn't write to with this signal (or any others)
382 is just as it should be.
387 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
388 framepos_t /*start*/, framepos_t /*end*/,
389 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
391 /* XXX to be implemented */
395 VBAPanner::get_state ()
397 XMLNode& node (Panner::get_state());
398 node.add_property (X_("type"), _descriptor.name);
403 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
405 return new VBAPanner (p, s);
409 VBAPanner::in() const
411 return ChanCount (DataType::AUDIO, _signals.size());
415 VBAPanner::out() const
417 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
420 std::set<Evoral::Parameter>
421 VBAPanner::what_can_be_automated() const
423 set<Evoral::Parameter> s;
424 s.insert (Evoral::Parameter (PanAzimuthAutomation));
425 if (_signals.size() > 1) {
426 s.insert (Evoral::Parameter (PanWidthAutomation));
432 VBAPanner::describe_parameter (Evoral::Parameter p)
435 case PanAzimuthAutomation:
436 return _("Direction");
437 case PanWidthAutomation:
438 return _("Diffusion");
440 return _pannable->describe_parameter (p);
445 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
447 /* DO NOT USE LocaleGuard HERE */
448 double val = ac->get_value();
450 switch (ac->parameter().type()) {
451 case PanAzimuthAutomation: /* direction */
452 return string_compose (_("%1"), int (rint (val * 360.0)));
454 case PanWidthAutomation: /* diffusion */
455 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
458 return _pannable->value_as_string (ac);
463 VBAPanner::signal_position (uint32_t n) const
465 if (n < _signals.size()) {
466 return _signals[n]->direction;
469 return AngularVector();
472 boost::shared_ptr<Speakers>
473 VBAPanner::get_speakers () const
475 return _speakers->parent();
479 VBAPanner::set_position (double p)
489 _pannable->pan_azimuth_control->set_value (p);
493 VBAPanner::set_width (double w)
495 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));