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