make reversed width for the 2in/2out panner actually do something
[ardour.git] / libs / panners / 2in2out / panner_2in2out.cc
1 /*
2     Copyright (C) 2004-2011 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <inttypes.h>
21
22 #include <cmath>
23 #include <cerrno>
24 #include <fstream>
25 #include <cstdlib>
26 #include <string>
27 #include <cstdio>
28 #include <locale.h>
29 #include <unistd.h>
30 #include <float.h>
31 #include <iomanip>
32
33 #include <glibmm.h>
34
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"
41
42 #include "evoral/Curve.hpp"
43
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
53 #include "panner_2in2out.h"
54
55 #include "i18n.h"
56
57 #include "pbd/mathfix.h"
58
59 using namespace std;
60 using namespace ARDOUR;
61 using namespace PBD;
62
63 static PanPluginDescriptor _descriptor = {
64         "Equal Power Stereo",
65         2, 2,
66         Panner2in2out::factory
67 };
68
69 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
70
71 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
72         : Panner (p)
73 {
74         if (!_pannable->has_state()) {
75                 _pannable->pan_azimuth_control->set_value (0.5);
76                 _pannable->pan_width_control->set_value (1.0);
77         } 
78         
79         update ();
80         
81         /* LEFT SIGNAL */
82         left_interp[0] = left[0] = desired_left[0];
83         right_interp[0] = right[0] = desired_right[0]; 
84         
85         /* RIGHT SIGNAL */
86         left_interp[1] = left[1] = desired_left[1];
87         right_interp[1] = right[1] = desired_right[1];
88         
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));
91 }
92
93 Panner2in2out::~Panner2in2out ()
94 {
95 }
96
97 double 
98 Panner2in2out::position () const
99 {
100         return _pannable->pan_azimuth_control->get_value();
101 }
102
103 double 
104 Panner2in2out::width () const
105 {
106         return _pannable->pan_width_control->get_value();
107 }
108
109 void
110 Panner2in2out::set_position (double p)
111 {
112         if (clamp_position (p)) {
113                 _pannable->pan_azimuth_control->set_value (p);
114         }
115 }
116
117 void
118 Panner2in2out::set_width (double p)
119 {
120         if (clamp_width (p)) {
121                 _pannable->pan_width_control->set_value (p);
122         }
123 }
124
125 void
126 Panner2in2out::update ()
127 {
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().
130            
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
133            overhead.
134         */
135         
136         /* x == 0 => hard left = 180.0 degrees
137            x == 1 => hard right = 0.0 degrees
138         */
139         
140         float pos[2];
141         double width = _pannable->pan_width_control->get_value();
142         const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
143
144         if (width < 0.0) {
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
148         } else {
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
151         }
152         
153         /* compute target gain coefficients for both input signals */
154         
155         float const pan_law_attenuation = -3.0f;
156         float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
157         float panR;
158         float panL;
159         
160         /* left signal */
161         
162         panR = pos[0];
163         panL = 1 - panR;
164         desired_left[0] = panL * (scale * panL + 1.0f - scale);
165         desired_right[0] = panR * (scale * panR + 1.0f - scale);
166         
167         /* right signal */
168         
169         panR = pos[1];
170         panL = 1 - panR;
171         desired_left[1] = panL * (scale * panL + 1.0f - scale);
172         desired_right[1] = panR * (scale * panR + 1.0f - scale);
173 }
174
175 bool
176 Panner2in2out::clamp_position (double& p)
177 {
178         double w = _pannable->pan_width_control->get_value();
179         return clamp_stereo_pan (p, w);
180 }
181
182 bool
183 Panner2in2out::clamp_width (double& w)
184 {
185         double p = _pannable->pan_azimuth_control->get_value();
186         return clamp_stereo_pan (p, w);
187 }
188
189 bool
190 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
191 {
192         double r_pos;
193         double l_pos;
194
195         width = max (min (width, 1.0), -1.0);
196         direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
197
198         r_pos = direction_as_lr_fract + (width/2.0);
199         l_pos = direction_as_lr_fract - (width/2.0);
200
201         if (width < 0.0) {
202                 swap (r_pos, l_pos);
203         }
204
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. 
207         */
208         
209         if (l_pos < 0.0) {
210                 return false;
211         }
212
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. 
215         */
216         
217         if (r_pos > 1.0) {
218                 return false;
219                 
220         }
221
222         return true;
223 }
224
225 void
226 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
227 {
228         assert (obufs.count().n_audio() == 2);
229
230         pan_t delta;
231         Sample* dst;
232         pan_t pan;
233
234         Sample* const src = srcbuf.data();
235         
236         /* LEFT OUTPUT */
237
238         dst = obufs.get_audio(0).data();
239
240         if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
241
242                 /* we've moving the pan by an appreciable amount, so we must
243                    interpolate over 64 frames or nframes, whichever is smaller */
244
245                 pframes_t const limit = min ((pframes_t) 64, nframes);
246                 pframes_t n;
247
248                 delta = -(delta / (float) (limit));
249
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;
254                 }
255
256                 /* then pan the rest of the buffer; no need for interpolation for this bit */
257
258                 pan = left[which] * gain_coeff;
259
260                 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
261
262         } else {
263
264                 left[which] = desired_left[which];
265                 left_interp[which] = left[which];
266
267                 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
268
269                         if (pan != 0.0f) {
270
271                                 /* pan is 1 but also not 0, so we must do it "properly" */
272
273                                 mix_buffers_with_gain(dst,src,nframes,pan);
274
275                                 /* mark that we wrote into the buffer */
276
277                                 // obufs[0] = 0;
278
279                         }
280
281                 } else {
282
283                         /* pan is 1 so we can just copy the input samples straight in */
284
285                         mix_buffers_no_gain(dst,src,nframes);
286                         
287                         /* XXX it would be nice to mark that we wrote into the buffer */
288                 }
289         }
290
291         /* RIGHT OUTPUT */
292
293         dst = obufs.get_audio(1).data();
294
295         if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
296
297                 /* we're moving the pan by an appreciable amount, so we must
298                    interpolate over 64 frames or nframes, whichever is smaller */
299
300                 pframes_t const limit = min ((pframes_t) 64, nframes);
301                 pframes_t n;
302
303                 delta = -(delta / (float) (limit));
304
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;
309                 }
310
311                 /* then pan the rest of the buffer, no need for interpolation for this bit */
312
313                 pan = right[which] * gain_coeff;
314
315                 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
316
317                 /* XXX it would be nice to mark the buffer as written to */
318
319         } else {
320
321                 right[which] = desired_right[which];
322                 right_interp[which] = right[which];
323
324                 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
325
326                         if (pan != 0.0f) {
327
328                                 /* pan is not 1 but also not 0, so we must do it "properly" */
329                                 
330                                 mix_buffers_with_gain(dst,src,nframes,pan);
331
332                                 /* XXX it would be nice to mark the buffer as written to */
333                         }
334
335                 } else {
336
337                         /* pan is 1 so we can just copy the input samples straight in */
338                         
339                         mix_buffers_no_gain(dst,src,nframes);
340
341                         /* XXX it would be nice to mark the buffer as written to */
342                 }
343         }
344 }
345
346 void
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)
350 {
351         assert (obufs.count().n_audio() == 2);
352
353         Sample* dst;
354         pan_t* pbuf;
355         Sample* const src = srcbuf.data();
356         pan_t* const position = buffers[0];
357         pan_t* const width = buffers[1];
358
359         /* fetch positional data */
360
361         if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
362                 /* fallback */
363                 distribute_one (srcbuf, obufs, 1.0, nframes, which);
364                 return;
365         }
366
367         if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
368                 /* fallback */
369                 distribute_one (srcbuf, obufs, 1.0, nframes, which);
370                 return;
371         }
372
373         /* apply pan law to convert positional data into pan coefficients for
374            each buffer (output)
375         */
376
377         const float pan_law_attenuation = -3.0f;
378         const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
379
380         for (pframes_t n = 0; n < nframes; ++n) {
381
382                 float panR;
383
384                 if (which == 0) { 
385                         // panning left signal
386                         panR = position[n] - (width[n]/2.0f); // center - width/2
387                 } else {
388                         // panning right signal
389                         panR = position[n] + (width[n]/2.0f); // center - width/2
390                 }
391
392                 const float panL = 1 - panR;
393
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.
399                 */
400                 
401                 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
402                 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
403         }
404
405         /* LEFT OUTPUT */
406
407         dst = obufs.get_audio(0).data();
408         pbuf = buffers[0];
409
410         for (pframes_t n = 0; n < nframes; ++n) {
411                 dst[n] += src[n] * pbuf[n];
412         }
413
414         /* XXX it would be nice to mark the buffer as written to */
415
416         /* RIGHT OUTPUT */
417
418         dst = obufs.get_audio(1).data();
419         pbuf = buffers[1];
420
421         for (pframes_t n = 0; n < nframes; ++n) {
422                 dst[n] += src[n] * pbuf[n];
423         }
424
425         /* XXX it would be nice to mark the buffer as written to */
426 }
427
428 Panner*
429 Panner2in2out::factory (boost::shared_ptr<Pannable> p, Speakers& /* ignored */)
430 {
431         return new Panner2in2out (p);
432 }
433
434 XMLNode&
435 Panner2in2out::get_state (void)
436 {
437         return state (true);
438 }
439
440 XMLNode&
441 Panner2in2out::state (bool /*full_state*/)
442 {
443         XMLNode& root (Panner::get_state ());
444         root.add_property (X_("type"), _descriptor.name);
445         return root;
446 }
447
448 int
449 Panner2in2out::set_state (const XMLNode& node, int version)
450 {
451         LocaleGuard lg (X_("POSIX"));
452         Panner::set_state (node, version);
453         return 0;
454 }
455
456 std::set<Evoral::Parameter> 
457 Panner2in2out::what_can_be_automated() const
458 {
459         set<Evoral::Parameter> s;
460         s.insert (Evoral::Parameter (PanAzimuthAutomation));
461         s.insert (Evoral::Parameter (PanWidthAutomation));
462         return s;
463 }
464
465 string
466 Panner2in2out::describe_parameter (Evoral::Parameter p)
467 {
468         switch (p.type()) {
469         case PanAzimuthAutomation:
470                 return _("L/R");
471         case PanWidthAutomation:
472                 return _("Width");
473         default:
474                 return _pannable->describe_parameter (p);
475         }
476 }
477
478 string 
479 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
480 {
481         /* DO NOT USE LocaleGuard HERE */
482         double val = ac->get_value();
483
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).
489                    
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.
492                 */
493                 
494                 return string_compose (_("L:%1 R:%2"), (int) rint (100.0 * (1.0 - val)),
495                                        (int) rint (100.0 * val));
496                 
497         case PanWidthAutomation:
498                 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
499                 
500         default:
501                 return _pannable->value_as_string (ac);
502         }
503 }