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);
83 double const w = width();
84 double const wrange = min (position(), (1 - position())) * 2;
85 if (fabs(w) > wrange) {
86 set_width(w > 0 ? wrange : -wrange);
93 left_interp[0] = left[0] = desired_left[0];
94 right_interp[0] = right[0] = desired_right[0];
97 left_interp[1] = left[1] = desired_left[1];
98 right_interp[1] = right[1] = desired_right[1];
100 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
101 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
104 Panner2in2out::~Panner2in2out ()
109 Panner2in2out::position () const
111 return _pannable->pan_azimuth_control->get_value();
115 Panner2in2out::width () const
117 return _pannable->pan_width_control->get_value();
121 Panner2in2out::set_position (double p)
123 if (clamp_position (p)) {
124 _pannable->pan_azimuth_control->set_value (p);
129 Panner2in2out::set_width (double p)
131 if (clamp_width (p)) {
132 _pannable->pan_width_control->set_value (p);
137 Panner2in2out::thaw ()
146 Panner2in2out::update ()
152 /* it would be very nice to split this out into a virtual function
153 that can be accessed from BaseStereoPanner and used in do_distribute_automated().
155 but the place where its used in do_distribute_automated() is a tight inner loop,
156 and making "nframes" virtual function calls to compute values is an absurd
160 /* x == 0 => hard left = 180.0 degrees
161 x == 1 => hard right = 0.0 degrees
165 double width = this->width ();
166 const double direction_as_lr_fract = position ();
168 double const wrange = min (position(), (1 - position())) * 2;
169 if (fabs(width) > wrange) {
170 width = (width > 0 ? wrange : -wrange);
175 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
176 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
178 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
179 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
182 /* compute target gain coefficients for both input signals */
184 float const pan_law_attenuation = -3.0f;
185 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
193 desired_left[0] = panL * (scale * panL + 1.0f - scale);
194 desired_right[0] = panR * (scale * panR + 1.0f - scale);
200 desired_left[1] = panL * (scale * panL + 1.0f - scale);
201 desired_right[1] = panR * (scale * panR + 1.0f - scale);
205 Panner2in2out::clamp_position (double& p)
208 return clamp_stereo_pan (p, w);
212 Panner2in2out::clamp_width (double& w)
214 double p = position ();
215 return clamp_stereo_pan (p, w);
219 Panner2in2out::position_range () const
221 return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
225 Panner2in2out::width_range () const
227 double const w = min (position(), (1 - position())) * 2;
228 return make_pair (-w, w);
232 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
237 width = max (min (width, 1.0), -1.0);
238 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
240 r_pos = direction_as_lr_fract + (width/2.0);
241 l_pos = direction_as_lr_fract - (width/2.0);
247 /* if the new left position is less than or equal to zero (hard left) and the left panner
248 is already there, we're not moving the left signal.
255 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
256 is already there, we're not moving the right signal.
268 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
270 assert (obufs.count().n_audio() == 2);
276 Sample* const src = srcbuf.data();
280 dst = obufs.get_audio(0).data();
282 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
284 /* we've moving the pan by an appreciable amount, so we must
285 interpolate over 64 frames or nframes, whichever is smaller */
287 pframes_t const limit = min ((pframes_t) 64, nframes);
290 delta = -(delta / (float) (limit));
292 for (n = 0; n < limit; n++) {
293 left_interp[which] = left_interp[which] + delta;
294 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
295 dst[n] += src[n] * left[which] * gain_coeff;
298 /* then pan the rest of the buffer; no need for interpolation for this bit */
300 pan = left[which] * gain_coeff;
302 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
306 left[which] = desired_left[which];
307 left_interp[which] = left[which];
309 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
313 /* pan is 1 but also not 0, so we must do it "properly" */
315 //obufs.get_audio(1).read_from (srcbuf, nframes);
316 mix_buffers_with_gain(dst,src,nframes,pan);
318 /* mark that we wrote into the buffer */
326 /* pan is 1 so we can just copy the input samples straight in */
328 mix_buffers_no_gain(dst,src,nframes);
330 /* XXX it would be nice to mark that we wrote into the buffer */
336 dst = obufs.get_audio(1).data();
338 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
340 /* we're moving the pan by an appreciable amount, so we must
341 interpolate over 64 frames or nframes, whichever is smaller */
343 pframes_t const limit = min ((pframes_t) 64, nframes);
346 delta = -(delta / (float) (limit));
348 for (n = 0; n < limit; n++) {
349 right_interp[which] = right_interp[which] + delta;
350 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
351 dst[n] += src[n] * right[which] * gain_coeff;
354 /* then pan the rest of the buffer, no need for interpolation for this bit */
356 pan = right[which] * gain_coeff;
358 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
360 /* XXX it would be nice to mark the buffer as written to */
364 right[which] = desired_right[which];
365 right_interp[which] = right[which];
367 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
371 /* pan is not 1 but also not 0, so we must do it "properly" */
373 mix_buffers_with_gain(dst,src,nframes,pan);
374 // obufs.get_audio(1).read_from (srcbuf, nframes);
376 /* XXX it would be nice to mark the buffer as written to */
381 /* pan is 1 so we can just copy the input samples straight in */
383 mix_buffers_no_gain(dst,src,nframes);
385 /* XXX it would be nice to mark the buffer as written to */
391 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
392 framepos_t start, framepos_t end, pframes_t nframes,
393 pan_t** buffers, uint32_t which)
395 assert (obufs.count().n_audio() == 2);
399 Sample* const src = srcbuf.data();
400 pan_t* const position = buffers[0];
401 pan_t* const width = buffers[1];
403 /* fetch positional data */
405 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
407 distribute_one (srcbuf, obufs, 1.0, nframes, which);
411 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
413 distribute_one (srcbuf, obufs, 1.0, nframes, which);
417 /* apply pan law to convert positional data into pan coefficients for
421 const float pan_law_attenuation = -3.0f;
422 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
424 for (pframes_t n = 0; n < nframes; ++n) {
429 // panning left signal
430 panR = position[n] - (width[n]/2.0f); // center - width/2
432 // panning right signal
433 panR = position[n] + (width[n]/2.0f); // center - width/2
436 panR = max(0.f, min(1.f, panR));
438 const float panL = 1 - panR;
440 /* note that are overwriting buffers, but its OK
441 because we're finished with their old contents
442 (position/width automation data) and are
443 replacing it with panning/gain coefficients
444 that we need to actually process the data.
447 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
448 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
453 dst = obufs.get_audio(0).data();
456 for (pframes_t n = 0; n < nframes; ++n) {
457 dst[n] += src[n] * pbuf[n];
460 /* XXX it would be nice to mark the buffer as written to */
464 dst = obufs.get_audio(1).data();
467 for (pframes_t n = 0; n < nframes; ++n) {
468 dst[n] += src[n] * pbuf[n];
471 /* XXX it would be nice to mark the buffer as written to */
475 Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
477 return new Panner2in2out (p);
481 Panner2in2out::get_state ()
483 XMLNode& root (Panner::get_state ());
484 root.add_property (X_("uri"), _descriptor.panner_uri);
485 /* this is needed to allow new sessions to load with old Ardour: */
486 root.add_property (X_("type"), _descriptor.name);
490 std::set<Evoral::Parameter>
491 Panner2in2out::what_can_be_automated() const
493 set<Evoral::Parameter> s;
494 s.insert (Evoral::Parameter (PanAzimuthAutomation));
495 s.insert (Evoral::Parameter (PanWidthAutomation));
500 Panner2in2out::describe_parameter (Evoral::Parameter p)
503 case PanAzimuthAutomation:
505 case PanWidthAutomation:
508 return _pannable->describe_parameter (p);
513 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
515 /* DO NOT USE LocaleGuard HERE */
516 double val = ac->get_value();
518 switch (ac->parameter().type()) {
519 case PanAzimuthAutomation:
520 /* We show the position of the center of the image relative to the left & right.
521 This is expressed as a pair of percentage values that ranges from (100,0)
522 (hard left) through (50,50) (hard center) to (0,100) (hard right).
524 This is pretty wierd, but its the way audio engineers expect it. Just remember that
525 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
527 This is designed to be as narrow as possible. Dedicated
528 panner GUIs can do their own version of this if they need
529 something less compact.
532 return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
533 (int) rint (100.0 * val));
535 case PanWidthAutomation:
536 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
544 Panner2in2out::reset ()