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",
69 Panner2in2out::factory
72 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
74 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
77 if (!_pannable->has_state()) {
78 _pannable->pan_azimuth_control->set_value (0.5);
79 _pannable->pan_width_control->set_value (1.0);
85 left_interp[0] = left[0] = desired_left[0];
86 right_interp[0] = right[0] = desired_right[0];
89 left_interp[1] = left[1] = desired_left[1];
90 right_interp[1] = right[1] = desired_right[1];
92 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
93 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
96 Panner2in2out::~Panner2in2out ()
101 Panner2in2out::position () const
103 return _pannable->pan_azimuth_control->get_value();
107 Panner2in2out::width () const
109 return _pannable->pan_width_control->get_value();
113 Panner2in2out::set_position (double p)
115 if (clamp_position (p)) {
116 _pannable->pan_azimuth_control->set_value (p);
121 Panner2in2out::set_width (double p)
123 if (clamp_width (p)) {
124 _pannable->pan_width_control->set_value (p);
129 Panner2in2out::thaw ()
138 Panner2in2out::update ()
144 /* it would be very nice to split this out into a virtual function
145 that can be accessed from BaseStereoPanner and used in do_distribute_automated().
147 but the place where its used in do_distribute_automated() is a tight inner loop,
148 and making "nframes" virtual function calls to compute values is an absurd
152 /* x == 0 => hard left = 180.0 degrees
153 x == 1 => hard right = 0.0 degrees
157 double width = this->width ();
158 const double direction_as_lr_fract = position ();
162 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
163 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
165 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
166 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
169 /* compute target gain coefficients for both input signals */
171 float const pan_law_attenuation = -3.0f;
172 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
180 desired_left[0] = panL * (scale * panL + 1.0f - scale);
181 desired_right[0] = panR * (scale * panR + 1.0f - scale);
187 desired_left[1] = panL * (scale * panL + 1.0f - scale);
188 desired_right[1] = panR * (scale * panR + 1.0f - scale);
192 Panner2in2out::clamp_position (double& p)
195 return clamp_stereo_pan (p, w);
199 Panner2in2out::clamp_width (double& w)
201 double p = position ();
202 return clamp_stereo_pan (p, w);
206 Panner2in2out::position_range () const
208 return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
212 Panner2in2out::width_range () const
214 double const w = min (position(), (1 - position())) * 2;
215 return make_pair (-w, w);
219 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
224 width = max (min (width, 1.0), -1.0);
225 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
227 r_pos = direction_as_lr_fract + (width/2.0);
228 l_pos = direction_as_lr_fract - (width/2.0);
234 /* if the new left position is less than or equal to zero (hard left) and the left panner
235 is already there, we're not moving the left signal.
242 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
243 is already there, we're not moving the right signal.
255 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
257 assert (obufs.count().n_audio() == 2);
263 Sample* const src = srcbuf.data();
267 dst = obufs.get_audio(0).data();
269 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
271 /* we've moving the pan by an appreciable amount, so we must
272 interpolate over 64 frames or nframes, whichever is smaller */
274 pframes_t const limit = min ((pframes_t) 64, nframes);
277 delta = -(delta / (float) (limit));
279 for (n = 0; n < limit; n++) {
280 left_interp[which] = left_interp[which] + delta;
281 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
282 dst[n] += src[n] * left[which] * gain_coeff;
285 /* then pan the rest of the buffer; no need for interpolation for this bit */
287 pan = left[which] * gain_coeff;
289 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
293 left[which] = desired_left[which];
294 left_interp[which] = left[which];
296 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
300 /* pan is 1 but also not 0, so we must do it "properly" */
302 //obufs.get_audio(1).read_from (srcbuf, nframes);
303 mix_buffers_with_gain(dst,src,nframes,pan);
305 /* mark that we wrote into the buffer */
313 /* pan is 1 so we can just copy the input samples straight in */
315 mix_buffers_no_gain(dst,src,nframes);
317 /* XXX it would be nice to mark that we wrote into the buffer */
323 dst = obufs.get_audio(1).data();
325 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
327 /* we're moving the pan by an appreciable amount, so we must
328 interpolate over 64 frames or nframes, whichever is smaller */
330 pframes_t const limit = min ((pframes_t) 64, nframes);
333 delta = -(delta / (float) (limit));
335 for (n = 0; n < limit; n++) {
336 right_interp[which] = right_interp[which] + delta;
337 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
338 dst[n] += src[n] * right[which] * gain_coeff;
341 /* then pan the rest of the buffer, no need for interpolation for this bit */
343 pan = right[which] * gain_coeff;
345 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
347 /* XXX it would be nice to mark the buffer as written to */
351 right[which] = desired_right[which];
352 right_interp[which] = right[which];
354 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
358 /* pan is not 1 but also not 0, so we must do it "properly" */
360 mix_buffers_with_gain(dst,src,nframes,pan);
361 // obufs.get_audio(1).read_from (srcbuf, nframes);
363 /* XXX it would be nice to mark the buffer as written to */
368 /* pan is 1 so we can just copy the input samples straight in */
370 mix_buffers_no_gain(dst,src,nframes);
372 /* XXX it would be nice to mark the buffer as written to */
378 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
379 framepos_t start, framepos_t end, pframes_t nframes,
380 pan_t** buffers, uint32_t which)
382 assert (obufs.count().n_audio() == 2);
386 Sample* const src = srcbuf.data();
387 pan_t* const position = buffers[0];
388 pan_t* const width = buffers[1];
390 /* fetch positional data */
392 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
394 distribute_one (srcbuf, obufs, 1.0, nframes, which);
398 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
400 distribute_one (srcbuf, obufs, 1.0, nframes, which);
404 /* apply pan law to convert positional data into pan coefficients for
408 const float pan_law_attenuation = -3.0f;
409 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
411 for (pframes_t n = 0; n < nframes; ++n) {
416 // panning left signal
417 panR = position[n] - (width[n]/2.0f); // center - width/2
419 // panning right signal
420 panR = position[n] + (width[n]/2.0f); // center - width/2
423 const float panL = 1 - panR;
425 /* note that are overwriting buffers, but its OK
426 because we're finished with their old contents
427 (position/width automation data) and are
428 replacing it with panning/gain coefficients
429 that we need to actually process the data.
432 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
433 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
438 dst = obufs.get_audio(0).data();
441 for (pframes_t n = 0; n < nframes; ++n) {
442 dst[n] += src[n] * pbuf[n];
445 /* XXX it would be nice to mark the buffer as written to */
449 dst = obufs.get_audio(1).data();
452 for (pframes_t n = 0; n < nframes; ++n) {
453 dst[n] += src[n] * pbuf[n];
456 /* XXX it would be nice to mark the buffer as written to */
460 Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
462 return new Panner2in2out (p);
466 Panner2in2out::get_state ()
468 XMLNode& root (Panner::get_state ());
469 root.add_property (X_("uri"), _descriptor.panner_uri);
470 /* this is needed to allow new sessions to load with old Ardour: */
471 root.add_property (X_("type"), _descriptor.name);
475 std::set<Evoral::Parameter>
476 Panner2in2out::what_can_be_automated() const
478 set<Evoral::Parameter> s;
479 s.insert (Evoral::Parameter (PanAzimuthAutomation));
480 s.insert (Evoral::Parameter (PanWidthAutomation));
485 Panner2in2out::describe_parameter (Evoral::Parameter p)
488 case PanAzimuthAutomation:
490 case PanWidthAutomation:
493 return _pannable->describe_parameter (p);
498 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
500 /* DO NOT USE LocaleGuard HERE */
501 double val = ac->get_value();
503 switch (ac->parameter().type()) {
504 case PanAzimuthAutomation:
505 /* We show the position of the center of the image relative to the left & right.
506 This is expressed as a pair of percentage values that ranges from (100,0)
507 (hard left) through (50,50) (hard center) to (0,100) (hard right).
509 This is pretty wierd, but its the way audio engineers expect it. Just remember that
510 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
512 This is designed to be as narrow as possible. Dedicated
513 panner GUIs can do their own version of this if they need
514 something less compact.
517 return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
518 (int) rint (100.0 * val));
520 case PanWidthAutomation:
521 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
524 return _pannable->value_as_string (ac);
529 Panner2in2out::reset ()