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",
55 extern "C" { 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::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[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_("uri"), _descriptor.panner_uri);
399 /* this is needed to allow new sessions to load with old Ardour: */
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)));