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