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