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