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 const double width = _pannable->pan_width_control->get_value();
142 const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
145 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
146 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
148 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
149 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
152 /* compute target gain coefficients for both input signals */
154 float const pan_law_attenuation = -3.0f;
155 float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
163 desired_left[0] = panL * (scale * panL + 1.0f - scale);
164 desired_right[0] = panR * (scale * panR + 1.0f - scale);
170 desired_left[1] = panL * (scale * panL + 1.0f - scale);
171 desired_right[1] = panR * (scale * panR + 1.0f - scale);
175 Panner2in2out::clamp_position (double& p)
177 double w = _pannable->pan_width_control->get_value();
178 return clamp_stereo_pan (p, w);
182 Panner2in2out::clamp_width (double& w)
184 double p = _pannable->pan_azimuth_control->get_value();
185 return clamp_stereo_pan (p, w);
189 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
194 width = max (min (width, 1.0), -1.0);
195 direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
197 r_pos = direction_as_lr_fract + (width/2.0);
198 l_pos = direction_as_lr_fract - (width/2.0);
204 /* if the new left position is less than or equal to zero (hard left) and the left panner
205 is already there, we're not moving the left signal.
212 /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
213 is already there, we're not moving the right signal.
225 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
227 assert (obufs.count().n_audio() == 2);
233 Sample* const src = srcbuf.data();
237 dst = obufs.get_audio(0).data();
239 if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
241 /* we've moving the pan by an appreciable amount, so we must
242 interpolate over 64 frames or nframes, whichever is smaller */
244 pframes_t const limit = min ((pframes_t) 64, nframes);
247 delta = -(delta / (float) (limit));
249 for (n = 0; n < limit; n++) {
250 left_interp[which] = left_interp[which] + delta;
251 left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
252 dst[n] += src[n] * left[which] * gain_coeff;
255 /* then pan the rest of the buffer; no need for interpolation for this bit */
257 pan = left[which] * gain_coeff;
259 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
263 left[which] = desired_left[which];
264 left_interp[which] = left[which];
266 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
270 /* pan is 1 but also not 0, so we must do it "properly" */
272 mix_buffers_with_gain(dst,src,nframes,pan);
274 /* mark that we wrote into the buffer */
282 /* pan is 1 so we can just copy the input samples straight in */
284 mix_buffers_no_gain(dst,src,nframes);
286 /* XXX it would be nice to mark that we wrote into the buffer */
292 dst = obufs.get_audio(1).data();
294 if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
296 /* we're moving the pan by an appreciable amount, so we must
297 interpolate over 64 frames or nframes, whichever is smaller */
299 pframes_t const limit = min ((pframes_t) 64, nframes);
302 delta = -(delta / (float) (limit));
304 for (n = 0; n < limit; n++) {
305 right_interp[which] = right_interp[which] + delta;
306 right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
307 dst[n] += src[n] * right[which] * gain_coeff;
310 /* then pan the rest of the buffer, no need for interpolation for this bit */
312 pan = right[which] * gain_coeff;
314 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
316 /* XXX it would be nice to mark the buffer as written to */
320 right[which] = desired_right[which];
321 right_interp[which] = right[which];
323 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
327 /* pan is not 1 but also not 0, so we must do it "properly" */
329 mix_buffers_with_gain(dst,src,nframes,pan);
331 /* XXX it would be nice to mark the buffer as written to */
336 /* pan is 1 so we can just copy the input samples straight in */
338 mix_buffers_no_gain(dst,src,nframes);
340 /* XXX it would be nice to mark the buffer as written to */
346 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
347 framepos_t start, framepos_t end, pframes_t nframes,
348 pan_t** buffers, uint32_t which)
350 assert (obufs.count().n_audio() == 2);
354 Sample* const src = srcbuf.data();
355 pan_t* const position = buffers[0];
356 pan_t* const width = buffers[1];
358 /* fetch positional data */
360 if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
362 distribute_one (srcbuf, obufs, 1.0, nframes, which);
366 if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
368 distribute_one (srcbuf, obufs, 1.0, nframes, which);
372 /* apply pan law to convert positional data into pan coefficients for
376 const float pan_law_attenuation = -3.0f;
377 const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
379 for (pframes_t n = 0; n < nframes; ++n) {
384 // panning left signal
385 panR = position[n] - (width[n]/2.0f); // center - width/2
387 // panning right signal
388 panR = position[n] + (width[n]/2.0f); // center - width/2
391 const float panL = 1 - panR;
393 /* note that are overwriting buffers, but its OK
394 because we're finished with their old contents
395 (position/width automation data) and are
396 replacing it with panning/gain coefficients
397 that we need to actually process the data.
400 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
401 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
406 dst = obufs.get_audio(0).data();
409 for (pframes_t n = 0; n < nframes; ++n) {
410 dst[n] += src[n] * pbuf[n];
413 /* XXX it would be nice to mark the buffer as written to */
417 dst = obufs.get_audio(1).data();
420 for (pframes_t n = 0; n < nframes; ++n) {
421 dst[n] += src[n] * pbuf[n];
424 /* XXX it would be nice to mark the buffer as written to */
428 Panner2in2out::factory (boost::shared_ptr<Pannable> p, Speakers& /* ignored */)
430 return new Panner2in2out (p);
434 Panner2in2out::get_state (void)
440 Panner2in2out::state (bool /*full_state*/)
442 XMLNode& root (Panner::get_state ());
443 root.add_property (X_("type"), _descriptor.name);
448 Panner2in2out::set_state (const XMLNode& node, int version)
450 LocaleGuard lg (X_("POSIX"));
451 Panner::set_state (node, version);
455 std::set<Evoral::Parameter>
456 Panner2in2out::what_can_be_automated() const
458 set<Evoral::Parameter> s;
459 s.insert (Evoral::Parameter (PanAzimuthAutomation));
460 s.insert (Evoral::Parameter (PanWidthAutomation));
465 Panner2in2out::describe_parameter (Evoral::Parameter p)
468 case PanAzimuthAutomation:
470 case PanWidthAutomation:
473 return _pannable->describe_parameter (p);