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