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 = {
67 Panner2in2out::factory
70 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
72 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
75 if (!_pannable->has_state()) {
76 _pannable->pan_azimuth_control->set_value (0.5);
77 _pannable->pan_width_control->set_value (1.0);
83 left_interp[0] = left[0] = desired_left[0];
84 right_interp[0] = right[0] = desired_right[0];
87 left_interp[1] = left[1] = desired_left[1];
88 right_interp[1] = right[1] = desired_right[1];
90 _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
91 _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
94 Panner2in2out::~Panner2in2out ()
99 Panner2in2out::position () const
101 return _pannable->pan_azimuth_control->get_value();
105 Panner2in2out::width () const
107 return _pannable->pan_width_control->get_value();
111 Panner2in2out::set_position (double p)
113 if (clamp_position (p)) {
114 _pannable->pan_azimuth_control->set_value (p);
119 Panner2in2out::set_width (double p)
121 if (clamp_width (p)) {
122 _pannable->pan_width_control->set_value (p);
127 Panner2in2out::thaw ()
136 Panner2in2out::update ()
142 /* it would be very nice to split this out into a virtual function
143 that can be accessed from BaseStereoPanner and used in do_distribute_automated().
145 but the place where its used in do_distribute_automated() is a tight inner loop,
146 and making "nframes" virtual function calls to compute values is an absurd
150 /* x == 0 => hard left = 180.0 degrees
151 x == 1 => hard right = 0.0 degrees
155 double width = this->width ();
156 const double direction_as_lr_fract = position ();
160 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
161 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
163 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
164 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
167 /* compute target gain coefficients for both input signals */
169 float const pan_law_attenuation = -3.0f;
170 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
178 desired_left[0] = panL * (scale * panL + 1.0f - scale);
179 desired_right[0] = panR * (scale * panR + 1.0f - scale);
185 desired_left[1] = panL * (scale * panL + 1.0f - scale);
186 desired_right[1] = panR * (scale * panR + 1.0f - scale);
190 Panner2in2out::clamp_position (double& p)
193 return clamp_stereo_pan (p, w);
197 Panner2in2out::clamp_width (double& w)
199 double p = position ();
200 return clamp_stereo_pan (p, w);
204 Panner2in2out::position_range () const
206 return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
210 Panner2in2out::width_range () const
212 double const w = min (position(), (1 - position())) * 2;
213 return make_pair (-w, w);
217 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
222 width = max (min (width, 1.0), -1.0);
223 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
225 r_pos = direction_as_lr_fract + (width/2.0);
226 l_pos = direction_as_lr_fract - (width/2.0);
232 /* if the new left position is less than or equal to zero (hard left) and the left panner
233 is already there, we're not moving the left signal.
240 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
241 is already there, we're not moving the right signal.
253 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
255 assert (obufs.count().n_audio() == 2);
261 Sample* const src = srcbuf.data();
265 dst = obufs.get_audio(0).data();
267 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
269 /* we've moving the pan by an appreciable amount, so we must
270 interpolate over 64 frames or nframes, whichever is smaller */
272 pframes_t const limit = min ((pframes_t) 64, nframes);
275 delta = -(delta / (float) (limit));
277 for (n = 0; n < limit; n++) {
278 left_interp[which] = left_interp[which] + delta;
279 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
280 dst[n] += src[n] * left[which] * gain_coeff;
283 /* then pan the rest of the buffer; no need for interpolation for this bit */
285 pan = left[which] * gain_coeff;
287 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
291 left[which] = desired_left[which];
292 left_interp[which] = left[which];
294 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
298 /* pan is 1 but also not 0, so we must do it "properly" */
300 //obufs.get_audio(1).read_from (srcbuf, nframes);
301 mix_buffers_with_gain(dst,src,nframes,pan);
303 /* mark that we wrote into the buffer */
311 /* pan is 1 so we can just copy the input samples straight in */
313 mix_buffers_no_gain(dst,src,nframes);
315 /* XXX it would be nice to mark that we wrote into the buffer */
321 dst = obufs.get_audio(1).data();
323 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
325 /* we're moving the pan by an appreciable amount, so we must
326 interpolate over 64 frames or nframes, whichever is smaller */
328 pframes_t const limit = min ((pframes_t) 64, nframes);
331 delta = -(delta / (float) (limit));
333 for (n = 0; n < limit; n++) {
334 right_interp[which] = right_interp[which] + delta;
335 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
336 dst[n] += src[n] * right[which] * gain_coeff;
339 /* then pan the rest of the buffer, no need for interpolation for this bit */
341 pan = right[which] * gain_coeff;
343 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
345 /* XXX it would be nice to mark the buffer as written to */
349 right[which] = desired_right[which];
350 right_interp[which] = right[which];
352 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
356 /* pan is not 1 but also not 0, so we must do it "properly" */
358 mix_buffers_with_gain(dst,src,nframes,pan);
359 // obufs.get_audio(1).read_from (srcbuf, nframes);
361 /* XXX it would be nice to mark the buffer as written to */
366 /* pan is 1 so we can just copy the input samples straight in */
368 mix_buffers_no_gain(dst,src,nframes);
370 /* XXX it would be nice to mark the buffer as written to */
376 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
377 framepos_t start, framepos_t end, pframes_t nframes,
378 pan_t** buffers, uint32_t which)
380 assert (obufs.count().n_audio() == 2);
384 Sample* const src = srcbuf.data();
385 pan_t* const position = buffers[0];
386 pan_t* const width = buffers[1];
388 /* fetch positional data */
390 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
392 distribute_one (srcbuf, obufs, 1.0, nframes, which);
396 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
398 distribute_one (srcbuf, obufs, 1.0, nframes, which);
402 /* apply pan law to convert positional data into pan coefficients for
406 const float pan_law_attenuation = -3.0f;
407 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
409 for (pframes_t n = 0; n < nframes; ++n) {
414 // panning left signal
415 panR = position[n] - (width[n]/2.0f); // center - width/2
417 // panning right signal
418 panR = position[n] + (width[n]/2.0f); // center - width/2
421 const float panL = 1 - panR;
423 /* note that are overwriting buffers, but its OK
424 because we're finished with their old contents
425 (position/width automation data) and are
426 replacing it with panning/gain coefficients
427 that we need to actually process the data.
430 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
431 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
436 dst = obufs.get_audio(0).data();
439 for (pframes_t n = 0; n < nframes; ++n) {
440 dst[n] += src[n] * pbuf[n];
443 /* XXX it would be nice to mark the buffer as written to */
447 dst = obufs.get_audio(1).data();
450 for (pframes_t n = 0; n < nframes; ++n) {
451 dst[n] += src[n] * pbuf[n];
454 /* XXX it would be nice to mark the buffer as written to */
458 Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
460 return new Panner2in2out (p);
464 Panner2in2out::get_state ()
466 XMLNode& root (Panner::get_state ());
467 root.add_property (X_("type"), _descriptor.name);
471 std::set<Evoral::Parameter>
472 Panner2in2out::what_can_be_automated() const
474 set<Evoral::Parameter> s;
475 s.insert (Evoral::Parameter (PanAzimuthAutomation));
476 s.insert (Evoral::Parameter (PanWidthAutomation));
481 Panner2in2out::describe_parameter (Evoral::Parameter p)
484 case PanAzimuthAutomation:
486 case PanWidthAutomation:
489 return _pannable->describe_parameter (p);
494 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
496 /* DO NOT USE LocaleGuard HERE */
497 double val = ac->get_value();
499 switch (ac->parameter().type()) {
500 case PanAzimuthAutomation:
501 /* We show the position of the center of the image relative to the left & right.
502 This is expressed as a pair of percentage values that ranges from (100,0)
503 (hard left) through (50,50) (hard center) to (0,100) (hard right).
505 This is pretty wierd, but its the way audio engineers expect it. Just remember that
506 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
508 This is designed to be as narrow as possible. Dedicated
509 panner GUIs can do their own version of this if they need
510 something less compact.
513 return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
514 (int) rint (100.0 * val));
516 case PanWidthAutomation:
517 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
520 return _pannable->value_as_string (ac);
525 Panner2in2out::reset ()