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&, VBAPanner&, uint32_t, 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*/,
368 pframes_t /*nframes*/, pan_t** /*buffers*/, uint32_t /*which*/)
370 /* XXX to be implemented */
374 VBAPanner::get_state ()
376 XMLNode& node (Panner::get_state());
377 node.add_property (X_("type"), _descriptor.name);
382 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
384 return new VBAPanner (p, s);
388 VBAPanner::in() const
390 return ChanCount (DataType::AUDIO, _signals.size());
394 VBAPanner::out() const
396 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
399 std::set<Evoral::Parameter>
400 VBAPanner::what_can_be_automated() const
402 set<Evoral::Parameter> s;
403 s.insert (Evoral::Parameter (PanAzimuthAutomation));
404 if (_signals.size() > 1) {
405 s.insert (Evoral::Parameter (PanWidthAutomation));
411 VBAPanner::describe_parameter (Evoral::Parameter p)
414 case PanAzimuthAutomation:
415 return _("Direction");
416 case PanWidthAutomation:
417 return _("Diffusion");
419 return _pannable->describe_parameter (p);
424 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
426 /* DO NOT USE LocaleGuard HERE */
427 double val = ac->get_value();
429 switch (ac->parameter().type()) {
430 case PanAzimuthAutomation: /* direction */
431 return string_compose (_("%1"), int (rint (val * 360.0)));
433 case PanWidthAutomation: /* diffusion */
434 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
437 return _pannable->value_as_string (ac);
442 VBAPanner::signal_position (uint32_t n) const
444 if (n < _signals.size()) {
445 return _signals[n]->direction;
448 return AngularVector();
451 boost::shared_ptr<Speakers>
452 VBAPanner::get_speakers () const
454 return _speakers->parent();
458 VBAPanner::set_position (double p)
468 _pannable->pan_azimuth_control->set_value (p);
472 VBAPanner::set_width (double w)
474 _pannable->pan_width_control->set_value (min (1.0, max (-1.0, w)));