rework panning -- Squashed commit of the following:
[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 #include "ardour/mix.h"
53
54 #include "panner_2in2out.h"
55
56 #include "i18n.h"
57
58 #include "pbd/mathfix.h"
59
60 using namespace std;
61 using namespace ARDOUR;
62 using namespace PBD;
63
64 static PanPluginDescriptor _descriptor = {
65         "Equal Power Stereo",
66         "http://ardour.org/plugin/panner_2in2out",
67         "http://ardour.org/plugin/panner_2in2out#ui",
68         2, 2,
69         Panner2in2out::factory
70 };
71
72 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
73
74 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
75         : Panner (p)
76 {
77         if (!_pannable->has_state()) {
78                 _pannable->pan_azimuth_control->set_value (0.5);
79                 _pannable->pan_width_control->set_value (1.0);
80         } 
81         
82         update ();
83         
84         /* LEFT SIGNAL */
85         left_interp[0] = left[0] = desired_left[0];
86         right_interp[0] = right[0] = desired_right[0]; 
87         
88         /* RIGHT SIGNAL */
89         left_interp[1] = left[1] = desired_left[1];
90         right_interp[1] = right[1] = desired_right[1];
91         
92         _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
93         _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
94 }
95
96 Panner2in2out::~Panner2in2out ()
97 {
98 }
99
100 double 
101 Panner2in2out::position () const
102 {
103         return _pannable->pan_azimuth_control->get_value();
104 }
105
106 double 
107 Panner2in2out::width () const
108 {
109         return _pannable->pan_width_control->get_value();
110 }
111
112 void
113 Panner2in2out::set_position (double p)
114 {
115         if (clamp_position (p)) {
116                 _pannable->pan_azimuth_control->set_value (p);
117         }
118 }
119
120 void
121 Panner2in2out::set_width (double p)
122 {
123         if (clamp_width (p)) {
124                 _pannable->pan_width_control->set_value (p);
125         }
126 }
127
128 void
129 Panner2in2out::thaw ()
130 {
131         Panner::thaw ();
132         if (_frozen == 0) {
133                 update ();
134         }
135 }
136
137 void
138 Panner2in2out::update ()
139 {
140         if (_frozen) {
141                 return;
142         }
143
144         /* it would be very nice to split this out into a virtual function
145            that can be accessed from BaseStereoPanner and used in do_distribute_automated().
146            
147            but the place where its used in do_distribute_automated() is a tight inner loop,
148            and making "nframes" virtual function calls to compute values is an absurd
149            overhead.
150         */
151         
152         /* x == 0 => hard left = 180.0 degrees
153            x == 1 => hard right = 0.0 degrees
154         */
155         
156         float pos[2];
157         double width = this->width ();
158         const double direction_as_lr_fract = position ();
159
160         if (width < 0.0) {
161                 width = -width;
162                 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
163                 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
164         } else {
165                 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
166                 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
167         }
168         
169         /* compute target gain coefficients for both input signals */
170         
171         float const pan_law_attenuation = -3.0f;
172         float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
173         float panR;
174         float panL;
175         
176         /* left signal */
177         
178         panR = pos[0];
179         panL = 1 - panR;
180         desired_left[0] = panL * (scale * panL + 1.0f - scale);
181         desired_right[0] = panR * (scale * panR + 1.0f - scale);
182         
183         /* right signal */
184         
185         panR = pos[1];
186         panL = 1 - panR;
187         desired_left[1] = panL * (scale * panL + 1.0f - scale);
188         desired_right[1] = panR * (scale * panR + 1.0f - scale);
189 }
190
191 bool
192 Panner2in2out::clamp_position (double& p)
193 {
194         double w = width ();
195         return clamp_stereo_pan (p, w);
196 }
197
198 bool
199 Panner2in2out::clamp_width (double& w)
200 {
201         double p = position ();
202         return clamp_stereo_pan (p, w);
203 }
204
205 pair<double, double>
206 Panner2in2out::position_range () const
207 {
208         return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
209 }
210
211 pair<double, double>
212 Panner2in2out::width_range () const
213 {
214         double const w = min (position(), (1 - position())) * 2;
215         return make_pair (-w, w);
216 }
217
218 bool
219 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
220 {
221         double r_pos;
222         double l_pos;
223
224         width = max (min (width, 1.0), -1.0);
225         direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
226
227         r_pos = direction_as_lr_fract + (width/2.0);
228         l_pos = direction_as_lr_fract - (width/2.0);
229
230         if (width < 0.0) {
231                 swap (r_pos, l_pos);
232         }
233
234         /* if the new left position is less than or equal to zero (hard left) and the left panner
235            is already there, we're not moving the left signal. 
236         */
237         
238         if (l_pos < 0.0) {
239                 return false;
240         }
241
242         /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
243            is already there, we're not moving the right signal. 
244         */
245         
246         if (r_pos > 1.0) {
247                 return false;
248                 
249         }
250
251         return true;
252 }
253
254 void
255 Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
256 {
257         assert (obufs.count().n_audio() == 2);
258
259         pan_t delta;
260         Sample* dst;
261         pan_t pan;
262
263         Sample* const src = srcbuf.data();
264         
265         /* LEFT OUTPUT */
266
267         dst = obufs.get_audio(0).data();
268
269         if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
270
271                 /* we've moving the pan by an appreciable amount, so we must
272                    interpolate over 64 frames or nframes, whichever is smaller */
273
274                 pframes_t const limit = min ((pframes_t) 64, nframes);
275                 pframes_t n;
276
277                 delta = -(delta / (float) (limit));
278
279                 for (n = 0; n < limit; n++) {
280                         left_interp[which] = left_interp[which] + delta;
281                         left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
282                         dst[n] += src[n] * left[which] * gain_coeff;
283                 }
284
285                 /* then pan the rest of the buffer; no need for interpolation for this bit */
286
287                 pan = left[which] * gain_coeff;
288
289                 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
290
291         } else {
292
293                 left[which] = desired_left[which];
294                 left_interp[which] = left[which];
295
296                 if ((pan = (left[which] * gain_coeff)) != 1.0f) {
297
298                         if (pan != 0.0f) {
299
300                                 /* pan is 1 but also not 0, so we must do it "properly" */
301                                 
302                                 //obufs.get_audio(1).read_from (srcbuf, nframes);
303                                 mix_buffers_with_gain(dst,src,nframes,pan);
304
305                                 /* mark that we wrote into the buffer */
306
307                                 // obufs[0] = 0;
308
309                         }
310
311                 } else {
312
313                         /* pan is 1 so we can just copy the input samples straight in */
314
315                         mix_buffers_no_gain(dst,src,nframes);
316                         
317                         /* XXX it would be nice to mark that we wrote into the buffer */
318                 }
319         }
320
321         /* RIGHT OUTPUT */
322
323         dst = obufs.get_audio(1).data();
324
325         if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
326
327                 /* we're moving the pan by an appreciable amount, so we must
328                    interpolate over 64 frames or nframes, whichever is smaller */
329
330                 pframes_t const limit = min ((pframes_t) 64, nframes);
331                 pframes_t n;
332
333                 delta = -(delta / (float) (limit));
334
335                 for (n = 0; n < limit; n++) {
336                         right_interp[which] = right_interp[which] + delta;
337                         right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
338                         dst[n] += src[n] * right[which] * gain_coeff;
339                 }
340
341                 /* then pan the rest of the buffer, no need for interpolation for this bit */
342
343                 pan = right[which] * gain_coeff;
344
345                 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
346
347                 /* XXX it would be nice to mark the buffer as written to */
348
349         } else {
350
351                 right[which] = desired_right[which];
352                 right_interp[which] = right[which];
353
354                 if ((pan = (right[which] * gain_coeff)) != 1.0f) {
355
356                         if (pan != 0.0f) {
357
358                                 /* pan is not 1 but also not 0, so we must do it "properly" */
359                                 
360                                 mix_buffers_with_gain(dst,src,nframes,pan);
361                                 // obufs.get_audio(1).read_from (srcbuf, nframes);
362                                 
363                                 /* XXX it would be nice to mark the buffer as written to */
364                         }
365
366                 } else {
367
368                         /* pan is 1 so we can just copy the input samples straight in */
369                         
370                         mix_buffers_no_gain(dst,src,nframes);
371
372                         /* XXX it would be nice to mark the buffer as written to */
373                 }
374         }
375 }
376
377 void
378 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
379                                          framepos_t start, framepos_t end, pframes_t nframes,
380                                          pan_t** buffers, uint32_t which)
381 {
382         assert (obufs.count().n_audio() == 2);
383
384         Sample* dst;
385         pan_t* pbuf;
386         Sample* const src = srcbuf.data();
387         pan_t* const position = buffers[0];
388         pan_t* const width = buffers[1];
389
390         /* fetch positional data */
391
392         if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
393                 /* fallback */
394                 distribute_one (srcbuf, obufs, 1.0, nframes, which);
395                 return;
396         }
397
398         if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
399                 /* fallback */
400                 distribute_one (srcbuf, obufs, 1.0, nframes, which);
401                 return;
402         }
403
404         /* apply pan law to convert positional data into pan coefficients for
405            each buffer (output)
406         */
407
408         const float pan_law_attenuation = -3.0f;
409         const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
410
411         for (pframes_t n = 0; n < nframes; ++n) {
412
413                 float panR;
414
415                 if (which == 0) { 
416                         // panning left signal
417                         panR = position[n] - (width[n]/2.0f); // center - width/2
418                 } else {
419                         // panning right signal
420                         panR = position[n] + (width[n]/2.0f); // center - width/2
421                 }
422
423                 const float panL = 1 - panR;
424
425                 /* note that are overwriting buffers, but its OK
426                    because we're finished with their old contents
427                    (position/width automation data) and are
428                    replacing it with panning/gain coefficients 
429                    that we need to actually process the data.
430                 */
431                 
432                 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
433                 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
434         }
435
436         /* LEFT OUTPUT */
437
438         dst = obufs.get_audio(0).data();
439         pbuf = buffers[0];
440
441         for (pframes_t n = 0; n < nframes; ++n) {
442                 dst[n] += src[n] * pbuf[n];
443         }
444
445         /* XXX it would be nice to mark the buffer as written to */
446
447         /* RIGHT OUTPUT */
448
449         dst = obufs.get_audio(1).data();
450         pbuf = buffers[1];
451
452         for (pframes_t n = 0; n < nframes; ++n) {
453                 dst[n] += src[n] * pbuf[n];
454         }
455
456         /* XXX it would be nice to mark the buffer as written to */
457 }
458
459 Panner*
460 Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
461 {
462         return new Panner2in2out (p);
463 }
464
465 XMLNode&
466 Panner2in2out::get_state ()
467 {
468         XMLNode& root (Panner::get_state ());
469         root.add_property (X_("uri"), _descriptor.panner_uri);
470         /* this is needed to allow new sessions to load with old Ardour: */
471         root.add_property (X_("type"), _descriptor.name);
472         return root;
473 }
474
475 std::set<Evoral::Parameter> 
476 Panner2in2out::what_can_be_automated() const
477 {
478         set<Evoral::Parameter> s;
479         s.insert (Evoral::Parameter (PanAzimuthAutomation));
480         s.insert (Evoral::Parameter (PanWidthAutomation));
481         return s;
482 }
483
484 string
485 Panner2in2out::describe_parameter (Evoral::Parameter p)
486 {
487         switch (p.type()) {
488         case PanAzimuthAutomation:
489                 return _("L/R");
490         case PanWidthAutomation:
491                 return _("Width");
492         default:
493                 return _pannable->describe_parameter (p);
494         }
495 }
496
497 string 
498 Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
499 {
500         /* DO NOT USE LocaleGuard HERE */
501         double val = ac->get_value();
502
503         switch (ac->parameter().type()) {
504         case PanAzimuthAutomation:
505                 /* We show the position of the center of the image relative to the left & right.
506                    This is expressed as a pair of percentage values that ranges from (100,0) 
507                    (hard left) through (50,50) (hard center) to (0,100) (hard right).
508                    
509                    This is pretty wierd, but its the way audio engineers expect it. Just remember that
510                    the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
511                 
512                    This is designed to be as narrow as possible. Dedicated
513                    panner GUIs can do their own version of this if they need
514                    something less compact.
515                 */
516                 
517                 return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
518                                        (int) rint (100.0 * val));
519
520         case PanWidthAutomation:
521                 return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
522                 
523         default:
524                 return _pannable->value_as_string (ac);
525         }
526 }
527
528 void
529 Panner2in2out::reset ()
530 {
531         set_position (0.5);
532         set_width (1);
533         update ();
534 }