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"
53 #include "panner_2in2out.h"
57 #include "pbd/mathfix.h"
60 using namespace ARDOUR;
63 static PanPluginDescriptor _descriptor = {
66 Panner2in2out::factory
69 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
71 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
74 if (!_pannable->has_state()) {
75 _pannable->pan_azimuth_control->set_value (0.5);
76 _pannable->pan_width_control->set_value (1.0);
82 left_interp[0] = left[0] = desired_left[0];
83 right_interp[0] = right[0] = desired_right[0];
86 left_interp[1] = left[1] = desired_left[1];
87 right_interp[1] = right[1] = desired_right[1];
89 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
90 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
93 Panner2in2out::~Panner2in2out ()
98 Panner2in2out::position () const
100 return _pannable->pan_azimuth_control->get_value();
104 Panner2in2out::width () const
106 return _pannable->pan_width_control->get_value();
110 Panner2in2out::set_position (double p)
112 if (clamp_position (p)) {
113 _pannable->pan_azimuth_control->set_value (p);
118 Panner2in2out::set_width (double p)
120 if (clamp_width (p)) {
121 _pannable->pan_width_control->set_value (p);
126 Panner2in2out::update ()
128 /* it would be very nice to split this out into a virtual function
129 that can be accessed from BaseStereoPanner and used in do_distribute_automated().
131 but the place where its used in do_distribute_automated() is a tight inner loop,
132 and making "nframes" virtual function calls to compute values is an absurd
136 /* x == 0 => hard left = 180.0 degrees
137 x == 1 => hard right = 0.0 degrees
141 double width = _pannable->pan_width_control->get_value();
142 const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
145 width = fabs (width);
146 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
147 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
149 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
150 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
153 /* compute target gain coefficients for both input signals */
155 float const pan_law_attenuation = -3.0f;
156 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
164 desired_left[0] = panL * (scale * panL + 1.0f - scale);
165 desired_right[0] = panR * (scale * panR + 1.0f - scale);
171 desired_left[1] = panL * (scale * panL + 1.0f - scale);
172 desired_right[1] = panR * (scale * panR + 1.0f - scale);
176 Panner2in2out::clamp_position (double& p)
178 double w = _pannable->pan_width_control->get_value();
179 return clamp_stereo_pan (p, w);
183 Panner2in2out::clamp_width (double& w)
185 double p = _pannable->pan_azimuth_control->get_value();
186 return clamp_stereo_pan (p, w);
190 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
195 width = max (min (width, 1.0), -1.0);
196 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
198 r_pos = direction_as_lr_fract + (width/2.0);
199 l_pos = direction_as_lr_fract - (width/2.0);
205 /* if the new left position is less than or equal to zero (hard left) and the left panner
206 is already there, we're not moving the left signal.
213 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
214 is already there, we're not moving the right signal.
226 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
228 assert (obufs.count().n_audio() == 2);
234 Sample* const src = srcbuf.data();
238 dst = obufs.get_audio(0).data();
240 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
242 /* we've moving the pan by an appreciable amount, so we must
243 interpolate over 64 frames or nframes, whichever is smaller */
245 pframes_t const limit = min ((pframes_t) 64, nframes);
248 delta = -(delta / (float) (limit));
250 for (n = 0; n < limit; n++) {
251 left_interp[which] = left_interp[which] + delta;
252 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
253 dst[n] += src[n] * left[which] * gain_coeff;
256 /* then pan the rest of the buffer; no need for interpolation for this bit */
258 pan = left[which] * gain_coeff;
260 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
264 left[which] = desired_left[which];
265 left_interp[which] = left[which];
267 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
271 /* pan is 1 but also not 0, so we must do it "properly" */
273 mix_buffers_with_gain(dst,src,nframes,pan);
275 /* mark that we wrote into the buffer */
283 /* pan is 1 so we can just copy the input samples straight in */
285 mix_buffers_no_gain(dst,src,nframes);
287 /* XXX it would be nice to mark that we wrote into the buffer */
293 dst = obufs.get_audio(1).data();
295 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
297 /* we're moving the pan by an appreciable amount, so we must
298 interpolate over 64 frames or nframes, whichever is smaller */
300 pframes_t const limit = min ((pframes_t) 64, nframes);
303 delta = -(delta / (float) (limit));
305 for (n = 0; n < limit; n++) {
306 right_interp[which] = right_interp[which] + delta;
307 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
308 dst[n] += src[n] * right[which] * gain_coeff;
311 /* then pan the rest of the buffer, no need for interpolation for this bit */
313 pan = right[which] * gain_coeff;
315 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
317 /* XXX it would be nice to mark the buffer as written to */
321 right[which] = desired_right[which];
322 right_interp[which] = right[which];
324 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
328 /* pan is not 1 but also not 0, so we must do it "properly" */
330 mix_buffers_with_gain(dst,src,nframes,pan);
332 /* XXX it would be nice to mark the buffer as written to */
337 /* pan is 1 so we can just copy the input samples straight in */
339 mix_buffers_no_gain(dst,src,nframes);
341 /* XXX it would be nice to mark the buffer as written to */
347 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
348 framepos_t start, framepos_t end, pframes_t nframes,
349 pan_t** buffers, uint32_t which)
351 assert (obufs.count().n_audio() == 2);
355 Sample* const src = srcbuf.data();
356 pan_t* const position = buffers[0];
357 pan_t* const width = buffers[1];
359 /* fetch positional data */
361 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
363 distribute_one (srcbuf, obufs, 1.0, nframes, which);
367 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
369 distribute_one (srcbuf, obufs, 1.0, nframes, which);
373 /* apply pan law to convert positional data into pan coefficients for
377 const float pan_law_attenuation = -3.0f;
378 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
380 for (pframes_t n = 0; n < nframes; ++n) {
385 // panning left signal
386 panR = position[n] - (width[n]/2.0f); // center - width/2
388 // panning right signal
389 panR = position[n] + (width[n]/2.0f); // center - width/2
392 const float panL = 1 - panR;
394 /* note that are overwriting buffers, but its OK
395 because we're finished with their old contents
396 (position/width automation data) and are
397 replacing it with panning/gain coefficients
398 that we need to actually process the data.
401 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
402 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
407 dst = obufs.get_audio(0).data();
410 for (pframes_t n = 0; n < nframes; ++n) {
411 dst[n] += src[n] * pbuf[n];
414 /* XXX it would be nice to mark the buffer as written to */
418 dst = obufs.get_audio(1).data();
421 for (pframes_t n = 0; n < nframes; ++n) {
422 dst[n] += src[n] * pbuf[n];
425 /* XXX it would be nice to mark the buffer as written to */
429 Panner2in2out::factory (boost::shared_ptr<Pannable> p, Speakers& /* ignored */)
431 return new Panner2in2out (p);
435 Panner2in2out::get_state (void)
441 Panner2in2out::state (bool /*full_state*/)
443 XMLNode& root (Panner::get_state ());
444 root.add_property (X_("type"), _descriptor.name);
449 Panner2in2out::set_state (const XMLNode& node, int version)
451 LocaleGuard lg (X_("POSIX"));
452 Panner::set_state (node, version);
456 std::set<Evoral::Parameter>
457 Panner2in2out::what_can_be_automated() const
459 set<Evoral::Parameter> s;
460 s.insert (Evoral::Parameter (PanAzimuthAutomation));
461 s.insert (Evoral::Parameter (PanWidthAutomation));
466 Panner2in2out::describe_parameter (Evoral::Parameter p)
469 case PanAzimuthAutomation:
471 case PanWidthAutomation:
474 return _pannable->describe_parameter (p);
479 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
481 /* DO NOT USE LocaleGuard HERE */
482 double val = ac->get_value();
484 switch (ac->parameter().type()) {
485 case PanAzimuthAutomation:
486 /* We show the position of the center of the image relative to the left & right.
487 This is expressed as a pair of percentage values that ranges from (100,0)
488 (hard left) through (50,50) (hard center) to (0,100) (hard right).
490 This is pretty wierd, but its the way audio engineers expect it. Just remember that
491 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
494 return string_compose (_("L:%1 R:%2"), (int) rint (100.0 * (1.0 - val)),
495 (int) rint (100.0 * val));
497 case PanWidthAutomation:
498 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
501 return _pannable->value_as_string (ac);