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 = {
57 extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
59 VBAPanner::Signal::Signal (Session&, VBAPanner&, uint32_t, uint32_t n_speakers)
61 resize_gains (n_speakers);
63 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
64 outputs[0] = outputs[1] = outputs[2] = -1;
65 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
69 VBAPanner::Signal::resize_gains (uint32_t n)
71 gains.assign (n, 0.0);
74 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
76 , _speakers (new VBAPSpeakers (s))
78 _pannable->pan_azimuth_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));
84 VBAPanner::~VBAPanner ()
90 VBAPanner::clear_signals ()
92 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
99 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
101 uint32_t n = in.n_audio();
105 for (uint32_t i = 0; i < n; ++i) {
106 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
107 _signals.push_back (s);
117 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
120 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
122 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
124 if (_signals.size() > 1) {
126 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
127 so that a width of 1 corresponds to a signal equally present from all directions,
128 and a width of zero corresponds to a point source from the "center" (above) point
129 on the perimeter of the speaker array.
132 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
134 double min_dir = center - (w/2.0);
136 min_dir = 360.0 + min_dir; // its already negative
138 min_dir = max (min (min_dir, 360.0), 0.0);
140 double max_dir = center + (w/2.0);
141 if (max_dir > 360.0) {
142 max_dir = max_dir - 360.0;
144 max_dir = max (min (max_dir, 360.0), 0.0);
146 if (max_dir < min_dir) {
147 swap (max_dir, min_dir);
150 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
151 double signal_direction = min_dir;
155 /* positive width - normal order of signal spread */
157 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
161 signal->direction = AngularVector (signal_direction, 0.0);
162 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
163 signal_direction += degree_step_per_signal;
167 /* inverted width - reverse order of signal spread */
169 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
173 signal->direction = AngularVector (signal_direction, 0.0);
174 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
175 signal_direction += degree_step_per_signal;
179 } else if (_signals.size() == 1) {
181 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
183 Signal* s = _signals.front();
184 s->direction = AngularVector (center, 0);
185 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
190 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
192 /* calculates gain factors using loudspeaker setup and given direction */
197 double big_sm_g, gtmp[3];
199 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
200 big_sm_g = -100000.0;
202 gains[0] = gains[1] = gains[2] = 0;
203 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
205 for (i = 0; i < _speakers->n_tuples(); i++) {
207 small_g = 10000000.0;
209 for (j = 0; j < _speakers->dimension(); j++) {
213 for (k = 0; k < _speakers->dimension(); k++) {
214 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
217 if (gtmp[j] < small_g) {
222 if (small_g > big_sm_g) {
229 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
230 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
232 if (_speakers->dimension() == 3) {
234 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
242 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
252 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
255 vector<Signal*>::iterator s;
257 assert (inbufs.count().n_audio() == _signals.size());
259 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
263 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
265 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
270 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
272 Sample* const src = srcbuf.data();
273 Signal* signal (_signals[which]);
275 /* VBAP may distribute the signal across up to 3 speakers depending on
276 the configuration of the speakers.
278 But the set of speakers in use "this time" may be different from
279 the set of speakers "the last time". So we have up to 6 speakers
280 involved, and we have to interpolate so that those no longer
281 in use are rapidly faded to silence and those newly in use
282 are rapidly faded to their correct level. This prevents clicks
283 as we change the set of speakers used to put the signal in
286 However, the speakers are represented by output buffers, and other
287 speakers may write to the same buffers, so we cannot use
288 anything here that will simply assign new (sample) values
289 to the output buffers - everything must be done via mixing
290 functions and not assignment/copying.
293 vector<double>::size_type sz = signal->gains.size();
295 assert (sz == obufs.count().n_audio());
297 int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
299 /* set initial state of each output "record"
302 for (uint32_t o = 0; o < sz; ++o) {
306 /* for all outputs used this time and last time,
307 change the output record to show what has
312 for (int o = 0; o < 3; ++o) {
313 if (signal->outputs[o] != -1) {
315 outputs[signal->outputs[o]] |= 1;
318 if (signal->desired_outputs[o] != -1) {
320 outputs[signal->desired_outputs[o]] |= 1<<1;
324 /* at this point, we can test a speaker's status:
326 (*outputs[o] & 1) <= in use before
327 (*outputs[o] & 2) <= in use this time
328 (*outputs[o] & 3) == 3 <= in use both times
329 *outputs[o] == 0 <= not in use either time
333 for (int o = 0; o < 3; ++o) {
335 int output = signal->desired_outputs[o];
341 pan = gain_coefficient * signal->desired_gains[o];
343 if (pan == 0.0 && signal->gains[output] == 0.0) {
345 /* nothing deing delivered to this output */
347 signal->gains[output] = 0.0;
349 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
351 /* signal to this output but the gain coefficient has changed, so
352 interpolate between them.
355 AudioBuffer& buf (obufs.get_audio (output));
356 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
357 signal->gains[output] = pan;
361 /* signal to this output, same gain as before so just copy with gain
364 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
365 signal->gains[output] = pan;
369 /* clean up the outputs that were used last time but not this time
372 for (uint32_t o = 0; o < sz; ++o) {
373 if (outputs[o] == 1) {
374 /* take signal and deliver with a rapid fade out
376 AudioBuffer& buf (obufs.get_audio (o));
377 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
378 signal->gains[o] = 0.0;
382 /* note that the output buffers were all silenced at some point
383 so anything we didn't write to with this signal (or any others)
384 is just as it should be.
389 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
390 framepos_t /*start*/, framepos_t /*end*/,
391 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
393 /* XXX to be implemented */
397 VBAPanner::get_state ()
399 XMLNode& node (Panner::get_state());
400 node.add_property (X_("type"), _descriptor.name);
405 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
407 return new VBAPanner (p, s);
411 VBAPanner::in() const
413 return ChanCount (DataType::AUDIO, _signals.size());
417 VBAPanner::out() const
419 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
422 std::set<Evoral::Parameter>
423 VBAPanner::what_can_be_automated() const
425 set<Evoral::Parameter> s;
426 s.insert (Evoral::Parameter (PanAzimuthAutomation));
427 if (_signals.size() > 1) {
428 s.insert (Evoral::Parameter (PanWidthAutomation));
434 VBAPanner::describe_parameter (Evoral::Parameter p)
437 case PanAzimuthAutomation:
438 return _("Direction");
439 case PanWidthAutomation:
440 return _("Diffusion");
442 return _pannable->describe_parameter (p);
447 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
449 /* DO NOT USE LocaleGuard HERE */
450 double val = ac->get_value();
452 switch (ac->parameter().type()) {
453 case PanAzimuthAutomation: /* direction */
454 return string_compose (_("%1"), int (rint (val * 360.0)));
456 case PanWidthAutomation: /* diffusion */
457 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
460 return _pannable->value_as_string (ac);
465 VBAPanner::signal_position (uint32_t n) const
467 if (n < _signals.size()) {
468 return _signals[n]->direction;
471 return AngularVector();
474 boost::shared_ptr<Speakers>
475 VBAPanner::get_speakers () const
477 return _speakers->parent();
481 VBAPanner::set_position (double p)
491 _pannable->pan_azimuth_control->set_value (p);
495 VBAPanner::set_width (double w)
497 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));