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 = {
53 "http://ardour.org/plugin/panner_vbap",
54 "http://ardour.org/plugin/panner_vbap#ui",
60 extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
62 VBAPanner::Signal::Signal (Session&, VBAPanner&, uint32_t, uint32_t n_speakers)
64 resize_gains (n_speakers);
66 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
67 outputs[0] = outputs[1] = outputs[2] = -1;
68 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
72 VBAPanner::Signal::resize_gains (uint32_t n)
74 gains.assign (n, 0.0);
77 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
79 , _speakers (new VBAPSpeakers (s))
81 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
82 _pannable->pan_elevation_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
83 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
88 VBAPanner::~VBAPanner ()
94 VBAPanner::clear_signals ()
96 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
103 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
105 uint32_t n = in.n_audio();
109 for (uint32_t i = 0; i < n; ++i) {
110 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
111 _signals.push_back (s);
121 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
124 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
126 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
127 double elevation = _pannable->pan_elevation_control->get_value() * 90.0;
129 if (_signals.size() > 1) {
131 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
132 so that a width of 1 corresponds to a signal equally present from all directions,
133 and a width of zero corresponds to a point source from the "center" (above) point
134 on the perimeter of the speaker array.
137 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
139 double min_dir = center - (w/2.0);
141 min_dir = 360.0 + min_dir; // its already negative
143 min_dir = max (min (min_dir, 360.0), 0.0);
145 double max_dir = center + (w/2.0);
146 if (max_dir > 360.0) {
147 max_dir = max_dir - 360.0;
149 max_dir = max (min (max_dir, 360.0), 0.0);
151 if (max_dir < min_dir) {
152 swap (max_dir, min_dir);
155 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
156 double signal_direction = min_dir;
160 /* positive width - normal order of signal spread */
162 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
166 signal->direction = AngularVector (signal_direction, elevation);
167 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
168 signal_direction += degree_step_per_signal;
172 /* inverted width - reverse order of signal spread */
174 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
178 signal->direction = AngularVector (signal_direction, elevation);
179 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
180 signal_direction += degree_step_per_signal;
184 } else if (_signals.size() == 1) {
186 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
188 Signal* s = _signals.front();
189 s->direction = AngularVector (center, elevation);
190 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
193 SignalPositionChanged(); /* emit */
197 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
199 /* calculates gain factors using loudspeaker setup and given direction */
204 double big_sm_g, gtmp[3];
206 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
207 big_sm_g = -100000.0;
209 gains[0] = gains[1] = gains[2] = 0;
210 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
212 for (i = 0; i < _speakers->n_tuples(); i++) {
214 small_g = 10000000.0;
216 for (j = 0; j < _speakers->dimension(); j++) {
220 for (k = 0; k < _speakers->dimension(); k++) {
221 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
224 if (gtmp[j] < small_g) {
229 if (small_g > big_sm_g) {
236 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
237 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
239 if (_speakers->dimension() == 3) {
241 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
249 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
259 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
262 vector<Signal*>::iterator s;
264 assert (inbufs.count().n_audio() == _signals.size());
266 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
270 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
272 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
277 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
279 Sample* const src = srcbuf.data();
280 Signal* signal (_signals[which]);
282 /* VBAP may distribute the signal across up to 3 speakers depending on
283 the configuration of the speakers.
285 But the set of speakers in use "this time" may be different from
286 the set of speakers "the last time". So we have up to 6 speakers
287 involved, and we have to interpolate so that those no longer
288 in use are rapidly faded to silence and those newly in use
289 are rapidly faded to their correct level. This prevents clicks
290 as we change the set of speakers used to put the signal in
293 However, the speakers are represented by output buffers, and other
294 speakers may write to the same buffers, so we cannot use
295 anything here that will simply assign new (sample) values
296 to the output buffers - everything must be done via mixing
297 functions and not assignment/copying.
300 vector<double>::size_type sz = signal->gains.size();
302 assert (sz == obufs.count().n_audio());
304 int8_t *outputs = (int8_t*)alloca(sz); // on the stack, no malloc
306 /* set initial state of each output "record"
309 for (uint32_t o = 0; o < sz; ++o) {
313 /* for all outputs used this time and last time,
314 change the output record to show what has
319 for (int o = 0; o < 3; ++o) {
320 if (signal->outputs[o] != -1) {
322 outputs[signal->outputs[o]] |= 1;
325 if (signal->desired_outputs[o] != -1) {
327 outputs[signal->desired_outputs[o]] |= 1<<1;
331 /* at this point, we can test a speaker's status:
333 (*outputs[o] & 1) <= in use before
334 (*outputs[o] & 2) <= in use this time
335 (*outputs[o] & 3) == 3 <= in use both times
336 *outputs[o] == 0 <= not in use either time
340 for (int o = 0; o < 3; ++o) {
342 int output = signal->desired_outputs[o];
348 pan = gain_coefficient * signal->desired_gains[o];
350 if (pan == 0.0 && signal->gains[output] == 0.0) {
352 /* nothing deing delivered to this output */
354 signal->gains[output] = 0.0;
356 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
358 /* signal to this output but the gain coefficient has changed, so
359 interpolate between them.
362 AudioBuffer& buf (obufs.get_audio (output));
363 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
364 signal->gains[output] = pan;
368 /* signal to this output, same gain as before so just copy with gain
371 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
372 signal->gains[output] = pan;
376 /* clean up the outputs that were used last time but not this time
379 for (uint32_t o = 0; o < sz; ++o) {
380 if (outputs[o] == 1) {
381 /* take signal and deliver with a rapid fade out
383 AudioBuffer& buf (obufs.get_audio (o));
384 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
385 signal->gains[o] = 0.0;
389 /* note that the output buffers were all silenced at some point
390 so anything we didn't write to with this signal (or any others)
391 is just as it should be.
396 VBAPanner::distribute_one_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
397 framepos_t /*start*/, framepos_t /*end*/,
398 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
400 /* XXX to be implemented */
404 VBAPanner::get_state ()
406 XMLNode& node (Panner::get_state());
407 node.add_property (X_("uri"), _descriptor.panner_uri);
408 /* this is needed to allow new sessions to load with old Ardour: */
409 node.add_property (X_("type"), _descriptor.name);
414 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
416 return new VBAPanner (p, s);
420 VBAPanner::in() const
422 return ChanCount (DataType::AUDIO, _signals.size());
426 VBAPanner::out() const
428 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
431 std::set<Evoral::Parameter>
432 VBAPanner::what_can_be_automated() const
434 set<Evoral::Parameter> s;
435 s.insert (Evoral::Parameter (PanAzimuthAutomation));
436 if (_signals.size() > 1) {
437 s.insert (Evoral::Parameter (PanWidthAutomation));
439 if (_speakers->dimension() == 3) {
440 s.insert (Evoral::Parameter (PanElevationAutomation));
446 VBAPanner::describe_parameter (Evoral::Parameter p)
449 case PanAzimuthAutomation:
450 return _("Direction");
451 case PanWidthAutomation:
452 return _("Diffusion");
453 case PanElevationAutomation:
454 return _("Elevation");
456 return _pannable->describe_parameter (p);
461 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
463 /* DO NOT USE LocaleGuard HERE */
464 double val = ac->get_value();
466 switch (ac->parameter().type()) {
467 case PanAzimuthAutomation: /* direction */
468 return string_compose (_("%1\u00B0"), int (rint (val * 360.0)));
470 case PanWidthAutomation: /* diffusion */
471 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
473 case PanElevationAutomation: /* elevation */
474 return string_compose (_("%1\u00B0"), (int) floor (90.0 * fabs(val)));
477 return _pannable->value_as_string (ac);
482 VBAPanner::signal_position (uint32_t n) const
484 if (n < _signals.size()) {
485 return _signals[n]->direction;
488 return AngularVector();
491 boost::shared_ptr<Speakers>
492 VBAPanner::get_speakers () const
494 return _speakers->parent();
498 VBAPanner::set_position (double p)
500 /* map into 0..1 range */
502 over -= (p >= 0) ? 0 : 1;
504 _pannable->pan_azimuth_control->set_value (p);
508 VBAPanner::set_width (double w)
510 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));
514 VBAPanner::set_elevation (double e)
516 _pannable->pan_elevation_control->set_value (min (1.0, max (0.0, e)));