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