9 #include "pbd/cartesian.h"
10 #include "pbd/compose.h"
12 #include "ardour/amp.h"
13 #include "ardour/audio_buffer.h"
14 #include "ardour/buffer_set.h"
15 #include "ardour/pan_controllable.h"
16 #include "ardour/pannable.h"
17 #include "ardour/speakers.h"
20 #include "vbap_speakers.h"
25 using namespace ARDOUR;
28 static PanPluginDescriptor _descriptor = {
34 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
36 VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n, uint32_t n_speakers)
38 resize_gains (n_speakers);
40 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
41 outputs[0] = outputs[1] = outputs[2] = -1;
42 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
46 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
48 gains.assign (n, 0.0);
51 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
53 , _speakers (new VBAPSpeakers (s))
55 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
56 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
61 VBAPanner::~VBAPanner ()
67 VBAPanner::clear_signals ()
69 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
76 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
78 uint32_t n = in.n_audio();
82 for (uint32_t i = 0; i < n; ++i) {
83 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
84 _signals.push_back (s);
94 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
97 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
99 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
101 if (_signals.size() > 1) {
103 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
104 so that a width of 1 corresponds to a signal equally present from all directions,
105 and a width of zero corresponds to a point source from the "center" (above) point
106 on the perimeter of the speaker array.
109 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
111 double min_dir = center - (w/2.0);
113 min_dir = 360.0 + min_dir; // its already negative
115 min_dir = max (min (min_dir, 360.0), 0.0);
117 double max_dir = center + (w/2.0);
118 if (max_dir > 360.0) {
119 max_dir = max_dir - 360.0;
121 max_dir = max (min (max_dir, 360.0), 0.0);
123 if (max_dir < min_dir) {
124 swap (max_dir, min_dir);
127 double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
128 double signal_direction = min_dir;
132 /* positive width - normal order of signal spread */
134 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
138 signal->direction = AngularVector (signal_direction, 0.0);
139 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
140 signal_direction += degree_step_per_signal;
144 /* inverted width - reverse order of signal spread */
146 for (vector<Signal*>::reverse_iterator s = _signals.rbegin(); s != _signals.rend(); ++s) {
150 signal->direction = AngularVector (signal_direction, 0.0);
151 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
152 signal_direction += degree_step_per_signal;
156 } else if (_signals.size() == 1) {
158 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
160 Signal* s = _signals.front();
161 s->direction = AngularVector (center, 0);
162 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
167 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
169 /* calculates gain factors using loudspeaker setup and given direction */
174 double big_sm_g, gtmp[3];
176 spherical_to_cartesian (azi, ele, 1.0, cartdir[0], cartdir[1], cartdir[2]);
177 big_sm_g = -100000.0;
179 gains[0] = gains[1] = gains[2] = 0;
180 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
182 for (i = 0; i < _speakers->n_tuples(); i++) {
184 small_g = 10000000.0;
186 for (j = 0; j < _speakers->dimension(); j++) {
190 for (k = 0; k < _speakers->dimension(); k++) {
191 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
194 if (gtmp[j] < small_g) {
199 if (small_g > big_sm_g) {
206 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
207 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
209 if (_speakers->dimension() == 3) {
211 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
219 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
229 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
232 vector<Signal*>::iterator s;
234 assert (inbufs.count().n_audio() == _signals.size());
236 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
240 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
242 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
247 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
249 Sample* const src = srcbuf.data();
250 Signal* signal (_signals[which]);
252 /* VBAP may distribute the signal across up to 3 speakers depending on
253 the configuration of the speakers.
255 But the set of speakers in use "this time" may be different from
256 the set of speakers "the last time". So we have up to 6 speakers
257 involved, and we have to interpolate so that those no longer
258 in use are rapidly faded to silence and those newly in use
259 are rapidly faded to their correct level. This prevents clicks
260 as we change the set of speakers used to put the signal in
263 However, the speakers are represented by output buffers, and other
264 speakers may write to the same buffers, so we cannot use
265 anything here that will simply assign new (sample) values
266 to the output buffers - everything must be done via mixing
267 functions and not assignment/copying.
270 vector<double>::size_type sz = signal->gains.size();
272 assert (sz == obufs.count().n_audio());
274 int8_t outputs[sz]; // on the stack, no malloc
276 /* set initial state of each output "record"
279 for (uint32_t o = 0; o < sz; ++o) {
283 /* for all outputs used this time and last time,
284 change the output record to show what has
289 for (int o = 0; o < 3; ++o) {
290 if (signal->outputs[o] != -1) {
292 outputs[signal->outputs[o]] |= 1;
295 if (signal->desired_outputs[o] != -1) {
297 outputs[signal->desired_outputs[o]] |= 1<<1;
301 /* at this point, we can test a speaker's status:
303 (outputs[o] & 1) <= in use before
304 (outputs[o] & 2) <= in use this time
305 (outputs[o] & 3) == 3 <= in use both times
306 outputs[o] == 0 <= not in use either time
310 for (int o = 0; o < 3; ++o) {
312 int output = signal->desired_outputs[o];
318 pan = gain_coefficient * signal->desired_gains[o];
320 if (pan == 0.0 && signal->gains[output] == 0.0) {
322 /* nothing deing delivered to this output */
324 signal->gains[output] = 0.0;
326 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
328 /* signal to this output but the gain coefficient has changed, so
329 interpolate between them.
332 AudioBuffer& buf (obufs.get_audio (output));
333 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
334 signal->gains[output] = pan;
338 /* signal to this output, same gain as before so just copy with gain
341 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
342 signal->gains[output] = pan;
346 /* clean up the outputs that were used last time but not this time
349 for (uint32_t o = 0; o < sz; ++o) {
350 if (outputs[o] == 1) {
351 /* take signal and deliver with a rapid fade out
353 AudioBuffer& buf (obufs.get_audio (o));
354 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
355 signal->gains[o] = 0.0;
359 /* note that the output buffers were all silenced at some point
360 so anything we didn't write to with this signal (or any others)
361 is just as it should be.
366 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
367 framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
372 VBAPanner::get_state ()
378 VBAPanner::state (bool full_state)
380 XMLNode& node (Panner::get_state());
381 node.add_property (X_("type"), _descriptor.name);
386 VBAPanner::set_state (const XMLNode& node, int /*version*/)
392 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
394 return new VBAPanner (p, s);
398 VBAPanner::in() const
400 return ChanCount (DataType::AUDIO, _signals.size());
404 VBAPanner::out() const
406 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
409 std::set<Evoral::Parameter>
410 VBAPanner::what_can_be_automated() const
412 set<Evoral::Parameter> s;
413 s.insert (Evoral::Parameter (PanAzimuthAutomation));
414 if (_signals.size() > 1) {
415 s.insert (Evoral::Parameter (PanWidthAutomation));
421 VBAPanner::describe_parameter (Evoral::Parameter p)
424 case PanAzimuthAutomation:
425 return _("Direction");
426 case PanWidthAutomation:
427 return _("Diffusion");
429 return _pannable->describe_parameter (p);
434 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
436 /* DO NOT USE LocaleGuard HERE */
437 double val = ac->get_value();
439 switch (ac->parameter().type()) {
440 case PanAzimuthAutomation: /* direction */
441 return string_compose (_("%1"), int (rint (val * 360.0)));
443 case PanWidthAutomation: /* diffusion */
444 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
447 return _pannable->value_as_string (ac);
452 VBAPanner::signal_position (uint32_t n) const
454 if (n < _signals.size()) {
455 return _signals[n]->direction;
458 return AngularVector();
461 boost::shared_ptr<Speakers>
462 VBAPanner::get_speakers () const
464 return _speakers->parent();
468 VBAPanner::set_position (double p)
478 _pannable->pan_azimuth_control->set_value (p);
482 VBAPanner::set_width (double w)
484 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));