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::update ()
129 /* it would be very nice to split this out into a virtual function
130 that can be accessed from BaseStereoPanner and used in do_distribute_automated().
132 but the place where its used in do_distribute_automated() is a tight inner loop,
133 and making "nframes" virtual function calls to compute values is an absurd
137 /* x == 0 => hard left = 180.0 degrees
138 x == 1 => hard right = 0.0 degrees
142 double width = _pannable->pan_width_control->get_value();
143 const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
147 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
148 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
150 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
151 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
154 /* compute target gain coefficients for both input signals */
156 float const pan_law_attenuation = -3.0f;
157 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
165 desired_left[0] = panL * (scale * panL + 1.0f - scale);
166 desired_right[0] = panR * (scale * panR + 1.0f - scale);
172 desired_left[1] = panL * (scale * panL + 1.0f - scale);
173 desired_right[1] = panR * (scale * panR + 1.0f - scale);
177 Panner2in2out::clamp_position (double& p)
179 double w = _pannable->pan_width_control->get_value();
180 return clamp_stereo_pan (p, w);
184 Panner2in2out::clamp_width (double& w)
186 double p = _pannable->pan_azimuth_control->get_value();
187 return clamp_stereo_pan (p, w);
191 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
196 width = max (min (width, 1.0), -1.0);
197 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
199 r_pos = direction_as_lr_fract + (width/2.0);
200 l_pos = direction_as_lr_fract - (width/2.0);
206 /* if the new left position is less than or equal to zero (hard left) and the left panner
207 is already there, we're not moving the left signal.
214 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
215 is already there, we're not moving the right signal.
227 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
229 assert (obufs.count().n_audio() == 2);
235 Sample* const src = srcbuf.data();
239 dst = obufs.get_audio(0).data();
241 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
243 /* we've moving the pan by an appreciable amount, so we must
244 interpolate over 64 frames or nframes, whichever is smaller */
246 pframes_t const limit = min ((pframes_t) 64, nframes);
249 delta = -(delta / (float) (limit));
251 for (n = 0; n < limit; n++) {
252 left_interp[which] = left_interp[which] + delta;
253 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
254 dst[n] += src[n] * left[which] * gain_coeff;
257 /* then pan the rest of the buffer; no need for interpolation for this bit */
259 pan = left[which] * gain_coeff;
261 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
265 left[which] = desired_left[which];
266 left_interp[which] = left[which];
268 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
272 /* pan is 1 but also not 0, so we must do it "properly" */
274 //obufs.get_audio(1).read_from (srcbuf, nframes);
275 mix_buffers_with_gain(dst,src,nframes,pan);
277 /* mark that we wrote into the buffer */
285 /* pan is 1 so we can just copy the input samples straight in */
287 mix_buffers_no_gain(dst,src,nframes);
289 /* XXX it would be nice to mark that we wrote into the buffer */
295 dst = obufs.get_audio(1).data();
297 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
299 /* we're moving the pan by an appreciable amount, so we must
300 interpolate over 64 frames or nframes, whichever is smaller */
302 pframes_t const limit = min ((pframes_t) 64, nframes);
305 delta = -(delta / (float) (limit));
307 for (n = 0; n < limit; n++) {
308 right_interp[which] = right_interp[which] + delta;
309 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
310 dst[n] += src[n] * right[which] * gain_coeff;
313 /* then pan the rest of the buffer, no need for interpolation for this bit */
315 pan = right[which] * gain_coeff;
317 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
319 /* XXX it would be nice to mark the buffer as written to */
323 right[which] = desired_right[which];
324 right_interp[which] = right[which];
326 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
330 /* pan is not 1 but also not 0, so we must do it "properly" */
332 mix_buffers_with_gain(dst,src,nframes,pan);
333 // obufs.get_audio(1).read_from (srcbuf, nframes);
335 /* XXX it would be nice to mark the buffer as written to */
340 /* pan is 1 so we can just copy the input samples straight in */
342 mix_buffers_no_gain(dst,src,nframes);
344 /* XXX it would be nice to mark the buffer as written to */
350 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
351 framepos_t start, framepos_t end, pframes_t nframes,
352 pan_t** buffers, uint32_t which)
354 assert (obufs.count().n_audio() == 2);
358 Sample* const src = srcbuf.data();
359 pan_t* const position = buffers[0];
360 pan_t* const width = buffers[1];
362 /* fetch positional data */
364 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
366 distribute_one (srcbuf, obufs, 1.0, nframes, which);
370 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
372 distribute_one (srcbuf, obufs, 1.0, nframes, which);
376 /* apply pan law to convert positional data into pan coefficients for
380 const float pan_law_attenuation = -3.0f;
381 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
383 for (pframes_t n = 0; n < nframes; ++n) {
388 // panning left signal
389 panR = position[n] - (width[n]/2.0f); // center - width/2
391 // panning right signal
392 panR = position[n] + (width[n]/2.0f); // center - width/2
395 const float panL = 1 - panR;
397 /* note that are overwriting buffers, but its OK
398 because we're finished with their old contents
399 (position/width automation data) and are
400 replacing it with panning/gain coefficients
401 that we need to actually process the data.
404 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
405 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
410 dst = obufs.get_audio(0).data();
413 for (pframes_t n = 0; n < nframes; ++n) {
414 dst[n] += src[n] * pbuf[n];
417 /* XXX it would be nice to mark the buffer as written to */
421 dst = obufs.get_audio(1).data();
424 for (pframes_t n = 0; n < nframes; ++n) {
425 dst[n] += src[n] * pbuf[n];
428 /* XXX it would be nice to mark the buffer as written to */
432 Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
434 return new Panner2in2out (p);
438 Panner2in2out::get_state ()
440 XMLNode& root (Panner::get_state ());
441 root.add_property (X_("type"), _descriptor.name);
445 std::set<Evoral::Parameter>
446 Panner2in2out::what_can_be_automated() const
448 set<Evoral::Parameter> s;
449 s.insert (Evoral::Parameter (PanAzimuthAutomation));
450 s.insert (Evoral::Parameter (PanWidthAutomation));
455 Panner2in2out::describe_parameter (Evoral::Parameter p)
458 case PanAzimuthAutomation:
460 case PanWidthAutomation:
463 return _pannable->describe_parameter (p);
468 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
470 /* DO NOT USE LocaleGuard HERE */
471 double val = ac->get_value();
473 switch (ac->parameter().type()) {
474 case PanAzimuthAutomation:
475 /* We show the position of the center of the image relative to the left & right.
476 This is expressed as a pair of percentage values that ranges from (100,0)
477 (hard left) through (50,50) (hard center) to (0,100) (hard right).
479 This is pretty wierd, but its the way audio engineers expect it. Just remember that
480 the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
483 return string_compose (_("L:%1 R:%2"), (int) rint (100.0 * (1.0 - val)),
484 (int) rint (100.0 * val));
486 case PanWidthAutomation:
487 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
490 return _pannable->value_as_string (ac);