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.
35 #include "pbd/cartesian.h"
36 #include "pbd/convert.h"
37 #include "pbd/error.h"
38 #include "pbd/failed_constructor.h"
39 #include "pbd/xml++.h"
40 #include "pbd/enumwriter.h"
42 #include "evoral/Curve.hpp"
44 #include "ardour/audio_buffer.h"
45 #include "ardour/audio_buffer.h"
46 #include "ardour/buffer_set.h"
47 #include "ardour/pan_controllable.h"
48 #include "ardour/pannable.h"
49 #include "ardour/runtime_functions.h"
50 #include "ardour/session.h"
51 #include "ardour/utils.h"
52 #include "ardour/mix.h"
54 #include "panner_2in2out.h"
58 #include "pbd/mathfix.h"
61 using namespace ARDOUR;
64 static PanPluginDescriptor _descriptor = {
66 "http://ardour.org/plugin/panner_2in2out",
67 "http://ardour.org/plugin/panner_2in2out#ui",
70 Panner2in2out::factory
73 extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
75 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
78 if (!_pannable->has_state()) {
79 _pannable->pan_azimuth_control->set_value (0.5);
80 _pannable->pan_width_control->set_value (1.0);
86 left_interp[0] = left[0] = desired_left[0];
87 right_interp[0] = right[0] = desired_right[0];
90 left_interp[1] = left[1] = desired_left[1];
91 right_interp[1] = right[1] = desired_right[1];
93 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
94 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
97 Panner2in2out::~Panner2in2out ()
102 Panner2in2out::position () const
104 return _pannable->pan_azimuth_control->get_value();
108 Panner2in2out::width () const
110 return _pannable->pan_width_control->get_value();
114 Panner2in2out::set_position (double p)
116 if (clamp_position (p)) {
117 _pannable->pan_azimuth_control->set_value (p);
122 Panner2in2out::set_width (double p)
124 if (clamp_width (p)) {
125 _pannable->pan_width_control->set_value (p);
130 Panner2in2out::thaw ()
139 Panner2in2out::update ()
145 /* it would be very nice to split this out into a virtual function
146 that can be accessed from BaseStereoPanner and used in do_distribute_automated().
148 but the place where its used in do_distribute_automated() is a tight inner loop,
149 and making "nframes" virtual function calls to compute values is an absurd
153 /* x == 0 => hard left = 180.0 degrees
154 x == 1 => hard right = 0.0 degrees
158 double width = this->width ();
159 const double direction_as_lr_fract = position ();
163 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
164 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
166 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
167 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
170 /* compute target gain coefficients for both input signals */
172 float const pan_law_attenuation = -3.0f;
173 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
181 desired_left[0] = panL * (scale * panL + 1.0f - scale);
182 desired_right[0] = panR * (scale * panR + 1.0f - scale);
188 desired_left[1] = panL * (scale * panL + 1.0f - scale);
189 desired_right[1] = panR * (scale * panR + 1.0f - scale);
193 Panner2in2out::clamp_position (double& p)
196 return clamp_stereo_pan (p, w);
200 Panner2in2out::clamp_width (double& w)
202 double p = position ();
203 return clamp_stereo_pan (p, w);
207 Panner2in2out::position_range () const
209 return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
213 Panner2in2out::width_range () const
215 double const w = min (position(), (1 - position())) * 2;
216 return make_pair (-w, w);
220 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
225 width = max (min (width, 1.0), -1.0);
226 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
228 r_pos = direction_as_lr_fract + (width/2.0);
229 l_pos = direction_as_lr_fract - (width/2.0);
235 /* if the new left position is less than or equal to zero (hard left) and the left panner
236 is already there, we're not moving the left signal.
243 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
244 is already there, we're not moving the right signal.
256 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
258 assert (obufs.count().n_audio() == 2);
264 Sample* const src = srcbuf.data();
268 dst = obufs.get_audio(0).data();
270 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
272 /* we've moving the pan by an appreciable amount, so we must
273 interpolate over 64 frames or nframes, whichever is smaller */
275 pframes_t const limit = min ((pframes_t) 64, nframes);
278 delta = -(delta / (float) (limit));
280 for (n = 0; n < limit; n++) {
281 left_interp[which] = left_interp[which] + delta;
282 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
283 dst[n] += src[n] * left[which] * gain_coeff;
286 /* then pan the rest of the buffer; no need for interpolation for this bit */
288 pan = left[which] * gain_coeff;
290 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
294 left[which] = desired_left[which];
295 left_interp[which] = left[which];
297 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
301 /* pan is 1 but also not 0, so we must do it "properly" */
303 //obufs.get_audio(1).read_from (srcbuf, nframes);
304 mix_buffers_with_gain(dst,src,nframes,pan);
306 /* mark that we wrote into the buffer */
314 /* pan is 1 so we can just copy the input samples straight in */
316 mix_buffers_no_gain(dst,src,nframes);
318 /* XXX it would be nice to mark that we wrote into the buffer */
324 dst = obufs.get_audio(1).data();
326 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
328 /* we're moving the pan by an appreciable amount, so we must
329 interpolate over 64 frames or nframes, whichever is smaller */
331 pframes_t const limit = min ((pframes_t) 64, nframes);
334 delta = -(delta / (float) (limit));
336 for (n = 0; n < limit; n++) {
337 right_interp[which] = right_interp[which] + delta;
338 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
339 dst[n] += src[n] * right[which] * gain_coeff;
342 /* then pan the rest of the buffer, no need for interpolation for this bit */
344 pan = right[which] * gain_coeff;
346 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
348 /* XXX it would be nice to mark the buffer as written to */
352 right[which] = desired_right[which];
353 right_interp[which] = right[which];
355 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
359 /* pan is not 1 but also not 0, so we must do it "properly" */
361 mix_buffers_with_gain(dst,src,nframes,pan);
362 // obufs.get_audio(1).read_from (srcbuf, nframes);
364 /* XXX it would be nice to mark the buffer as written to */
369 /* pan is 1 so we can just copy the input samples straight in */
371 mix_buffers_no_gain(dst,src,nframes);
373 /* XXX it would be nice to mark the buffer as written to */
379 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
380 framepos_t start, framepos_t end, pframes_t nframes,
381 pan_t** buffers, uint32_t which)
383 assert (obufs.count().n_audio() == 2);
387 Sample* const src = srcbuf.data();
388 pan_t* const position = buffers[0];
389 pan_t* const width = buffers[1];
391 /* fetch positional data */
393 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
395 distribute_one (srcbuf, obufs, 1.0, nframes, which);
399 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
401 distribute_one (srcbuf, obufs, 1.0, nframes, which);
405 /* apply pan law to convert positional data into pan coefficients for
409 const float pan_law_attenuation = -3.0f;
410 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
412 for (pframes_t n = 0; n < nframes; ++n) {
417 // panning left signal
418 panR = position[n] - (width[n]/2.0f); // center - width/2
420 // panning right signal
421 panR = position[n] + (width[n]/2.0f); // center - width/2
424 const float panL = 1 - panR;
426 /* note that are overwriting buffers, but its OK
427 because we're finished with their old contents
428 (position/width automation data) and are
429 replacing it with panning/gain coefficients
430 that we need to actually process the data.
433 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
434 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
439 dst = obufs.get_audio(0).data();
442 for (pframes_t n = 0; n < nframes; ++n) {
443 dst[n] += src[n] * pbuf[n];
446 /* XXX it would be nice to mark the buffer as written to */
450 dst = obufs.get_audio(1).data();
453 for (pframes_t n = 0; n < nframes; ++n) {
454 dst[n] += src[n] * pbuf[n];
457 /* XXX it would be nice to mark the buffer as written to */
461 Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
463 return new Panner2in2out (p);
467 Panner2in2out::get_state ()
469 XMLNode& root (Panner::get_state ());
470 root.add_property (X_("uri"), _descriptor.panner_uri);
471 /* this is needed to allow new sessions to load with old Ardour: */
472 root.add_property (X_("type"), _descriptor.name);
476 std::set<Evoral::Parameter>
477 Panner2in2out::what_can_be_automated() const
479 set<Evoral::Parameter> s;
480 s.insert (Evoral::Parameter (PanAzimuthAutomation));
481 s.insert (Evoral::Parameter (PanWidthAutomation));
486 Panner2in2out::describe_parameter (Evoral::Parameter p)
489 case PanAzimuthAutomation:
491 case PanWidthAutomation:
494 return _pannable->describe_parameter (p);
499 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
501 /* DO NOT USE LocaleGuard HERE */
502 double val = ac->get_value();
504 switch (ac->parameter().type()) {
505 case PanAzimuthAutomation:
506 /* We show the position of the center of the image relative to the left & right.
507 This is expressed as a pair of percentage values that ranges from (100,0)
508 (hard left) through (50,50) (hard center) to (0,100) (hard right).
510 This is pretty wierd, but its the way audio engineers expect it. Just remember that
511 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
513 This is designed to be as narrow as possible. Dedicated
514 panner GUIs can do their own version of this if they need
515 something less compact.
518 return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
519 (int) rint (100.0 * val));
521 case PanWidthAutomation:
522 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
525 return _pannable->value_as_string (ac);
530 Panner2in2out::reset ()