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 width (diffusion) parameters)
94 /* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
97 double center = _pannable->pan_azimuth_control->get_value() * 360.0;
99 /* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
100 so that a width of 1 corresponds to a signal equally present from all directions,
101 and a width of zero corresponds to a point source from the "center" (above)
104 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
106 double min_dir = center - w;
107 min_dir = max (min (min_dir, 360.0), 0.0);
109 double max_dir = center + w;
110 max_dir = max (min (max_dir, 360.0), 0.0);
112 double degree_step_per_signal = (max_dir - min_dir) / _signals.size();
113 double signal_direction = min_dir;
115 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
119 signal->direction = AngularVector (signal_direction, 0.0);
120 compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
121 signal_direction += degree_step_per_signal;
126 VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
128 /* calculates gain factors using loudspeaker setup and given direction */
133 double big_sm_g, gtmp[3];
135 azi_ele_to_cart (azi,ele, cartdir[0], cartdir[1], cartdir[2]);
136 big_sm_g = -100000.0;
138 gains[0] = gains[1] = gains[2] = 0;
139 speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
141 for (i = 0; i < _speakers->n_tuples(); i++) {
143 small_g = 10000000.0;
145 for (j = 0; j < _speakers->dimension(); j++) {
149 for (k = 0; k < _speakers->dimension(); k++) {
150 gtmp[j] += cartdir[k] * _speakers->matrix(i)[j*_speakers->dimension()+k];
153 if (gtmp[j] < small_g) {
158 if (small_g > big_sm_g) {
165 speaker_ids[0] = _speakers->speaker_for_tuple (i, 0);
166 speaker_ids[1] = _speakers->speaker_for_tuple (i, 1);
168 if (_speakers->dimension() == 3) {
170 speaker_ids[2] = _speakers->speaker_for_tuple (i, 2);
178 power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
188 VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
191 vector<Signal*>::iterator s;
193 assert (inbufs.count().n_audio() == _signals.size());
195 for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
199 distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
201 memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
206 VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
208 Sample* const src = srcbuf.data();
209 Signal* signal (_signals[which]);
211 /* VBAP may distribute the signal across up to 3 speakers depending on
212 the configuration of the speakers.
214 But the set of speakers in use "this time" may be different from
215 the set of speakers "the last time". So we have up to 6 speakers
216 involved, and we have to interpolate so that those no longer
217 in use are rapidly faded to silence and those newly in use
218 are rapidly faded to their correct level. This prevents clicks
219 as we change the set of speakers used to put the signal in
222 However, the speakers are represented by output buffers, and other
223 speakers may write to the same buffers, so we cannot use
224 anything here that will simply assign new (sample) values
225 to the output buffers - everything must be done via mixing
226 functions and not assignment/copying.
229 vector<double>::size_type sz = signal->gains.size();
231 assert (sz == obufs.count().n_audio());
233 int8_t outputs[sz]; // on the stack, no malloc
235 /* set initial state of each output "record"
238 for (uint32_t o = 0; o < sz; ++o) {
242 /* for all outputs used this time and last time,
243 change the output record to show what has
248 for (int o = 0; o < 3; ++o) {
249 if (signal->outputs[o] != -1) {
251 outputs[signal->outputs[o]] |= 1;
254 if (signal->desired_outputs[o] != -1) {
256 outputs[signal->desired_outputs[o]] |= 1<<1;
260 /* at this point, we can test a speaker's status:
262 (outputs[o] & 1) <= in use before
263 (outputs[o] & 2) <= in use this time
264 (outputs[o] & 3) == 3 <= in use both times
265 outputs[o] == 0 <= not in use either time
269 for (int o = 0; o < 3; ++o) {
271 int output = signal->desired_outputs[o];
277 pan = gain_coefficient * signal->desired_gains[o];
279 if (pan == 0.0 && signal->gains[output] == 0.0) {
281 /* nothing deing delivered to this output */
283 signal->gains[output] = 0.0;
285 } else if (fabs (pan - signal->gains[output]) > 0.00001) {
287 /* signal to this output but the gain coefficient has changed, so
288 interpolate between them.
291 AudioBuffer& buf (obufs.get_audio (output));
292 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[output], pan, 0);
293 signal->gains[output] = pan;
297 /* signal to this output, same gain as before so just copy with gain
300 mix_buffers_with_gain (obufs.get_audio (output).data(),src,nframes,pan);
301 signal->gains[output] = pan;
305 /* clean up the outputs that were used last time but not this time
308 for (uint32_t o = 0; o < sz; ++o) {
309 if (outputs[o] == 1) {
310 /* take signal and deliver with a rapid fade out
312 AudioBuffer& buf (obufs.get_audio (o));
313 buf.accumulate_with_ramped_gain_from (srcbuf.data(), nframes, signal->gains[o], 0.0, 0);
314 signal->gains[o] = 0.0;
318 /* note that the output buffers were all silenced at some point
319 so anything we didn't write to with this signal (or any others)
320 is just as it should be.
325 VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
326 framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
331 VBAPanner::get_state ()
337 VBAPanner::state (bool full_state)
339 XMLNode& node (Panner::get_state());
340 node.add_property (X_("type"), _descriptor.name);
345 VBAPanner::set_state (const XMLNode& node, int /*version*/)
351 VBAPanner::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> s)
353 return new VBAPanner (p, s);
357 VBAPanner::in() const
359 return ChanCount (DataType::AUDIO, _signals.size());
363 VBAPanner::out() const
365 return ChanCount (DataType::AUDIO, _speakers->n_speakers());
368 std::set<Evoral::Parameter>
369 VBAPanner::what_can_be_automated() const
371 set<Evoral::Parameter> s;
372 s.insert (Evoral::Parameter (PanAzimuthAutomation));
373 s.insert (Evoral::Parameter (PanWidthAutomation));
378 VBAPanner::describe_parameter (Evoral::Parameter p)
381 case PanAzimuthAutomation:
382 return _("Direction");
383 case PanWidthAutomation:
384 return _("Diffusion");
386 return _pannable->describe_parameter (p);
391 VBAPanner::value_as_string (boost::shared_ptr<AutomationControl> ac) const
393 /* DO NOT USE LocaleGuard HERE */
394 double val = ac->get_value();
396 switch (ac->parameter().type()) {
397 case PanAzimuthAutomation: /* direction */
398 return string_compose (_("%1"), val * 360.0);
400 case PanWidthAutomation: /* diffusion */
401 return string_compose (_("%1%%"), (int) floor (100.0 * fabs(val)));
404 return _pannable->value_as_string (ac);
409 VBAPanner::signal_position (uint32_t n) const
411 if (n < _signals.size()) {
412 return _signals[n]->direction;
415 return AngularVector();
418 boost::shared_ptr<Speakers>
419 VBAPanner::get_speakers () const
421 return _speakers->parent();
425 VBAPanner::set_position (double p)
427 _pannable->pan_azimuth_control->set_value (p);
431 VBAPanner::set_width (double p)
433 _pannable->pan_width_control->set_value (p);