2 Copyright (C) 2004-2011 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.
33 #include "pbd/cartesian.h"
34 #include "pbd/convert.h"
35 #include "pbd/error.h"
36 #include "pbd/failed_constructor.h"
37 #include "pbd/xml++.h"
38 #include "pbd/enumwriter.h"
40 #include "evoral/Curve.hpp"
42 #include "ardour/audio_buffer.h"
43 #include "ardour/audio_buffer.h"
44 #include "ardour/buffer_set.h"
45 #include "ardour/pan_controllable.h"
46 #include "ardour/pannable.h"
47 #include "ardour/runtime_functions.h"
48 #include "ardour/session.h"
49 #include "ardour/utils.h"
50 #include "ardour/mix.h"
52 #include "panner_2in2out.h"
56 #include "pbd/mathfix.h"
59 using namespace ARDOUR;
62 static PanPluginDescriptor _descriptor = {
64 "http://ardour.org/plugin/panner_2in2out",
65 "http://ardour.org/plugin/panner_2in2out#ui",
68 Panner2in2out::factory
71 extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
73 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
76 if (!_pannable->has_state()) {
77 _pannable->pan_azimuth_control->set_value (0.5, Controllable::NoGroup);
78 _pannable->pan_width_control->set_value (1.0, Controllable::NoGroup);
81 double const w = width();
82 double const wrange = min (position(), (1 - position())) * 2;
83 if (fabs(w) > wrange) {
84 set_width(w > 0 ? wrange : -wrange);
91 left_interp[0] = left[0] = desired_left[0];
92 right_interp[0] = right[0] = desired_right[0];
95 left_interp[1] = left[1] = desired_left[1];
96 right_interp[1] = right[1] = desired_right[1];
98 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
99 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
102 Panner2in2out::~Panner2in2out ()
107 Panner2in2out::position () const
109 return _pannable->pan_azimuth_control->get_value();
113 Panner2in2out::width () const
115 return _pannable->pan_width_control->get_value();
119 Panner2in2out::set_position (double p)
121 if (clamp_position (p)) {
122 _pannable->pan_azimuth_control->set_value (p, Controllable::NoGroup);
127 Panner2in2out::set_width (double p)
129 if (clamp_width (p)) {
130 _pannable->pan_width_control->set_value (p, Controllable::NoGroup);
135 Panner2in2out::thaw ()
144 Panner2in2out::update ()
150 /* it would be very nice to split this out into a virtual function
151 that can be accessed from BaseStereoPanner and used in do_distribute_automated().
153 but the place where its used in do_distribute_automated() is a tight inner loop,
154 and making "nframes" virtual function calls to compute values is an absurd
158 /* x == 0 => hard left = 180.0 degrees
159 x == 1 => hard right = 0.0 degrees
163 double width = this->width ();
164 const double direction_as_lr_fract = position ();
166 double const wrange = min (position(), (1 - position())) * 2;
167 if (fabs(width) > wrange) {
168 width = (width > 0 ? wrange : -wrange);
173 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
174 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
176 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
177 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
180 /* compute target gain coefficients for both input signals */
182 float const pan_law_attenuation = -3.0f;
183 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
191 desired_left[0] = panL * (scale * panL + 1.0f - scale);
192 desired_right[0] = panR * (scale * panR + 1.0f - scale);
198 desired_left[1] = panL * (scale * panL + 1.0f - scale);
199 desired_right[1] = panR * (scale * panR + 1.0f - scale);
203 Panner2in2out::clamp_position (double& p)
206 return clamp_stereo_pan (p, w);
210 Panner2in2out::clamp_width (double& w)
212 double p = position ();
213 return clamp_stereo_pan (p, w);
217 Panner2in2out::position_range () const
219 return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
223 Panner2in2out::width_range () const
225 double const w = min (position(), (1 - position())) * 2;
226 return make_pair (-w, w);
230 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
235 width = max (min (width, 1.0), -1.0);
236 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
238 r_pos = direction_as_lr_fract + (width/2.0);
239 l_pos = direction_as_lr_fract - (width/2.0);
245 /* if the new left position is less than or equal to zero (hard left) and the left panner
246 is already there, we're not moving the left signal.
253 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
254 is already there, we're not moving the right signal.
266 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
268 assert (obufs.count().n_audio() == 2);
274 Sample* const src = srcbuf.data();
278 dst = obufs.get_audio(0).data();
280 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
282 /* we've moving the pan by an appreciable amount, so we must
283 interpolate over 64 frames or nframes, whichever is smaller */
285 pframes_t const limit = min ((pframes_t) 64, nframes);
288 delta = -(delta / (float) (limit));
290 for (n = 0; n < limit; n++) {
291 left_interp[which] = left_interp[which] + delta;
292 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
293 dst[n] += src[n] * left[which] * gain_coeff;
296 /* then pan the rest of the buffer; no need for interpolation for this bit */
298 pan = left[which] * gain_coeff;
300 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
304 left[which] = desired_left[which];
305 left_interp[which] = left[which];
307 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
311 /* pan is 1 but also not 0, so we must do it "properly" */
313 //obufs.get_audio(1).read_from (srcbuf, nframes);
314 mix_buffers_with_gain(dst,src,nframes,pan);
316 /* mark that we wrote into the buffer */
324 /* pan is 1 so we can just copy the input samples straight in */
326 mix_buffers_no_gain(dst,src,nframes);
328 /* XXX it would be nice to mark that we wrote into the buffer */
334 dst = obufs.get_audio(1).data();
336 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
338 /* we're moving the pan by an appreciable amount, so we must
339 interpolate over 64 frames or nframes, whichever is smaller */
341 pframes_t const limit = min ((pframes_t) 64, nframes);
344 delta = -(delta / (float) (limit));
346 for (n = 0; n < limit; n++) {
347 right_interp[which] = right_interp[which] + delta;
348 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
349 dst[n] += src[n] * right[which] * gain_coeff;
352 /* then pan the rest of the buffer, no need for interpolation for this bit */
354 pan = right[which] * gain_coeff;
356 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
358 /* XXX it would be nice to mark the buffer as written to */
362 right[which] = desired_right[which];
363 right_interp[which] = right[which];
365 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
369 /* pan is not 1 but also not 0, so we must do it "properly" */
371 mix_buffers_with_gain(dst,src,nframes,pan);
372 // obufs.get_audio(1).read_from (srcbuf, nframes);
374 /* XXX it would be nice to mark the buffer as written to */
379 /* pan is 1 so we can just copy the input samples straight in */
381 mix_buffers_no_gain(dst,src,nframes);
383 /* XXX it would be nice to mark the buffer as written to */
389 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
390 framepos_t start, framepos_t end, pframes_t nframes,
391 pan_t** buffers, uint32_t which)
393 assert (obufs.count().n_audio() == 2);
397 Sample* const src = srcbuf.data();
398 pan_t* const position = buffers[0];
399 pan_t* const width = buffers[1];
401 /* fetch positional data */
403 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
405 distribute_one (srcbuf, obufs, 1.0, nframes, which);
409 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
411 distribute_one (srcbuf, obufs, 1.0, nframes, which);
415 /* apply pan law to convert positional data into pan coefficients for
419 const float pan_law_attenuation = -3.0f;
420 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
422 for (pframes_t n = 0; n < nframes; ++n) {
427 // panning left signal
428 panR = position[n] - (width[n]/2.0f); // center - width/2
430 // panning right signal
431 panR = position[n] + (width[n]/2.0f); // center - width/2
434 panR = max(0.f, min(1.f, panR));
436 const float panL = 1 - panR;
438 /* note that are overwriting buffers, but its OK
439 because we're finished with their old contents
440 (position/width automation data) and are
441 replacing it with panning/gain coefficients
442 that we need to actually process the data.
445 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
446 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
451 dst = obufs.get_audio(0).data();
454 for (pframes_t n = 0; n < nframes; ++n) {
455 dst[n] += src[n] * pbuf[n];
458 /* XXX it would be nice to mark the buffer as written to */
462 dst = obufs.get_audio(1).data();
465 for (pframes_t n = 0; n < nframes; ++n) {
466 dst[n] += src[n] * pbuf[n];
469 /* XXX it would be nice to mark the buffer as written to */
473 Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
475 return new Panner2in2out (p);
479 Panner2in2out::get_state ()
481 XMLNode& root (Panner::get_state ());
482 root.add_property (X_("uri"), _descriptor.panner_uri);
483 /* this is needed to allow new sessions to load with old Ardour: */
484 root.add_property (X_("type"), _descriptor.name);
488 std::set<Evoral::Parameter>
489 Panner2in2out::what_can_be_automated() const
491 set<Evoral::Parameter> s;
492 s.insert (Evoral::Parameter (PanAzimuthAutomation));
493 s.insert (Evoral::Parameter (PanWidthAutomation));
498 Panner2in2out::describe_parameter (Evoral::Parameter p)
501 case PanAzimuthAutomation:
503 case PanWidthAutomation:
506 return _pannable->describe_parameter (p);
511 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
513 /* DO NOT USE LocaleGuard HERE */
514 double val = ac->get_value();
516 switch (ac->parameter().type()) {
517 case PanAzimuthAutomation:
518 /* We show the position of the center of the image relative to the left & right.
519 This is expressed as a pair of percentage values that ranges from (100,0)
520 (hard left) through (50,50) (hard center) to (0,100) (hard right).
522 This is pretty wierd, but its the way audio engineers expect it. Just remember that
523 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
525 This is designed to be as narrow as possible. Dedicated
526 panner GUIs can do their own version of this if they need
527 something less compact.
530 return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
531 (int) rint (100.0 * val));
533 case PanWidthAutomation:
534 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
542 Panner2in2out::reset ()