9 #include "pbd/cartesian.h"
11 #include "ardour/amp.h"
12 #include "ardour/audio_buffer.h"
13 #include "ardour/buffer_set.h"
14 #include "ardour/pan_controllable.h"
15 #include "ardour/pannable.h"
16 #include "ardour/speakers.h"
19 #include "vbap_speakers.h"
22 using namespace ARDOUR;
25 static PanPluginDescriptor _descriptor = {
31 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
33 VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n, uint32_t n_speakers)
35 resize_gains (n_speakers);
37 desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
38 outputs[0] = outputs[1] = outputs[2] = -1;
39 desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
43 VBAPanner::Signal::Signal::resize_gains (uint32_t n)
45 gains.assign (n, 0.0);
48 VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
50 , _speakers (new VBAPSpeakers (s))
52 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
53 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
58 VBAPanner::~VBAPanner ()
64 VBAPanner::clear_signals ()
66 for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
73 VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
75 uint32_t n = in.n_audio();
79 for (uint32_t i = 0; i < n; ++i) {
80 Signal* s = new Signal (_pannable->session(), *this, i, _speakers->n_speakers());
81 _signals.push_back (s);
91 /* recompute signal directions based on panner azimuth and, if relevant, width (diffusion) parameters)
94 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
96 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
98 if (_signals.size() > 1) {
100 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
101 so that a width of 1 corresponds to a signal equally present from all directions,
102 and a width of zero corresponds to a point source from the "center" (above)
105 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
107 double min_dir = center - w;
108 min_dir = max (min (min_dir, 360.0), 0.0);
110 double max_dir = center + w;
111 max_dir = max (min (max_dir, 360.0), 0.0);
113 double degree_step_per_signal = (max_dir - min_dir) / _signals.size();
114 double signal_direction = min_dir;
116 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
120 signal->direction = AngularVector (signal_direction, 0.0);
121 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
122 signal_direction += degree_step_per_signal;
125 } else if (_signals.size() == 1) {
127 /* width has no role to play if there is only 1 signal: VBAP does not do "diffusion" of a single channel */
129 Signal* s = _signals.front();
130 s->direction = AngularVector (center, 0);
131 compute_gains (s->desired_gains, s->desired_outputs, s->direction.azi, s->direction.ele);
136 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
138 /* calculates gain factors using loudspeaker setup and given direction */
143 double big_sm_g, gtmp[3];
145 azi_ele_to_cart (azi,ele, cartdir[0], cartdir[1], cartdir[2]);
146 big_sm_g = -100000.0;
148 gains[0] = gains[1] = gains[2] = 0;
149 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
151 for (i = 0; i < _speakers->n_tuples(); i++) {
153 small_g = 10000000.0;
155 for (j = 0; j < _speakers->dimension(); j++) {
159 for (k = 0; k < _speakers->dimension(); k++) {
160 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
163 if (gtmp[j] < small_g) {
168 if (small_g > big_sm_g) {
175 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
176 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
178 if (_speakers->dimension() == 3) {
180 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
188 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
198 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
201 vector<Signal*>::iterator s;
203 assert (inbufs.count().n_audio() == _signals.size());
205 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
209 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
211 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
216 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
218 Sample* const src = srcbuf.data();
219 Signal* signal (_signals[which]);
221 /* VBAP may distribute the signal across up to 3 speakers depending on
222 the configuration of the speakers.
224 But the set of speakers in use "this time" may be different from
225 the set of speakers "the last time". So we have up to 6 speakers
226 involved, and we have to interpolate so that those no longer
227 in use are rapidly faded to silence and those newly in use
228 are rapidly faded to their correct level. This prevents clicks
229 as we change the set of speakers used to put the signal in
232 However, the speakers are represented by output buffers, and other
233 speakers may write to the same buffers, so we cannot use
234 anything here that will simply assign new (sample) values
235 to the output buffers - everything must be done via mixing
236 functions and not assignment/copying.
239 vector<double>::size_type sz = signal->gains.size();
241 assert (sz == obufs.count().n_audio());
243 int8_t outputs[sz]; // on the stack, no malloc
245 /* set initial state of each output "record"
248 for (uint32_t o = 0; o < sz; ++o) {
252 /* for all outputs used this time and last time,
253 change the output record to show what has
258 for (int o = 0; o < 3; ++o) {
259 if (signal->outputs[o] != -1) {
261 outputs[signal->outputs[o]] |= 1;
264 if (signal->desired_outputs[o] != -1) {
266 outputs[signal->desired_outputs[o]] |= 1<<1;
270 /* at this point, we can test a speaker's status:
272 (outputs[o] & 1) <= in use before
273 (outputs[o] & 2) <= in use this time
274 (outputs[o] & 3) == 3 <= in use both times
275 outputs[o] == 0 <= not in use either time
279 for (int o = 0; o < 3; ++o) {
281 int output = signal->desired_outputs[o];
287 pan = gain_coefficient * signal->desired_gains[o];
289 if (pan == 0.0 && signal->gains[output] == 0.0) {
291 /* nothing deing delivered to this output */
293 signal->gains[output] = 0.0;
295 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
297 /* signal to this output but the gain coefficient has changed, so
298 interpolate between them.
301 AudioBuffer& buf (obufs.get_audio (output));
302 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
303 signal->gains[output] = pan;
307 /* signal to this output, same gain as before so just copy with gain
310 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
311 signal->gains[output] = pan;
315 /* clean up the outputs that were used last time but not this time
318 for (uint32_t o = 0; o < sz; ++o) {
319 if (outputs[o] == 1) {
320 /* take signal and deliver with a rapid fade out
322 AudioBuffer& buf (obufs.get_audio (o));
323 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
324 signal->gains[o] = 0.0;
328 /* note that the output buffers were all silenced at some point
329 so anything we didn't write to with this signal (or any others)
330 is just as it should be.
335 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
336 framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
341 VBAPanner::get_state ()
347 VBAPanner::state (bool full_state)
349 XMLNode& node (Panner::get_state());
350 node.add_property (X_("type"), _descriptor.name);
355 VBAPanner::set_state (const XMLNode& node, int /*version*/)
361 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
363 return new VBAPanner (p, s);
367 VBAPanner::in() const
369 return ChanCount (DataType::AUDIO, _signals.size());
373 VBAPanner::out() const
375 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
378 std::set<Evoral::Parameter>
379 VBAPanner::what_can_be_automated() const
381 set<Evoral::Parameter> s;
382 s.insert (Evoral::Parameter (PanAzimuthAutomation));
383 if (_signals.size() > 1) {
384 s.insert (Evoral::Parameter (PanWidthAutomation));
390 VBAPanner::describe_parameter (Evoral::Parameter p)
393 case PanAzimuthAutomation:
394 return _("Direction");
395 case PanWidthAutomation:
396 return _("Diffusion");
398 return _pannable->describe_parameter (p);
403 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
405 /* DO NOT USE LocaleGuard HERE */
406 double val = ac->get_value();
408 switch (ac->parameter().type()) {
409 case PanAzimuthAutomation: /* direction */
410 return string_compose (_("%1"), int (rint (val * 360.0)));
412 case PanWidthAutomation: /* diffusion */
413 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
416 return _pannable->value_as_string (ac);
421 VBAPanner::signal_position (uint32_t n) const
423 if (n < _signals.size()) {
424 return _signals[n]->direction;
427 return AngularVector();
430 boost::shared_ptr<Speakers>
431 VBAPanner::get_speakers () const
433 return _speakers->parent();
437 VBAPanner::set_position (double p)
439 _pannable->pan_azimuth_control->set_value (p);
443 VBAPanner::set_width (double p)
445 _pannable->pan_width_control->set_value (p);