Implement reset() in panners, and use it from the GUI (fixes #4196).
[ardour.git] / libs / panners / 1in2out / panner_1in2out.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/session.h"
45 #include "ardour/panner.h"
46 #include "ardour/utils.h"
47 #include "ardour/audio_buffer.h"
48
49 #include "ardour/debug.h"
50 #include "ardour/runtime_functions.h"
51 #include "ardour/buffer_set.h"
52 #include "ardour/audio_buffer.h"
53 #include "ardour/pannable.h"
54
55 #include "i18n.h"
56 #include "panner_1in2out.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         "Mono to Stereo Panner",
66         1, 2, 
67         Panner1in2out::factory
68 };
69
70 extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
71
72 Panner1in2out::Panner1in2out (boost::shared_ptr<Pannable> p)
73         : Panner (p)
74 {
75         if (!_pannable->has_state()) {
76                 _pannable->pan_azimuth_control->set_value (0.5);
77         }
78         
79         update ();
80
81         left = desired_left;
82         right = desired_right;
83         left_interp = left;
84         right_interp = right;
85
86         _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner1in2out::update, this));
87 }
88
89 Panner1in2out::~Panner1in2out ()
90 {
91 }
92
93 void
94 Panner1in2out::update ()
95 {
96         float panR, panL;
97         float const pan_law_attenuation = -3.0f;
98         float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
99
100         panR = _pannable->pan_azimuth_control->get_value();
101         panL = 1 - panR;
102
103         desired_left = panL * (scale * panL + 1.0f - scale);
104         desired_right = panR * (scale * panR + 1.0f - scale);
105 }
106
107 void
108 Panner1in2out::set_position (double p)
109 {
110         if (clamp_position (p)) {
111                 _pannable->pan_azimuth_control->set_value (p);
112         }
113 }
114
115 bool
116 Panner1in2out::clamp_position (double& p)
117 {
118         /* any position between 0.0 and 1.0 is legal */
119         DEBUG_TRACE (DEBUG::Panning, string_compose ("want to move panner to %1 - always allowed in 0.0-1.0 range\n", p));
120         p = max (min (p, 1.0), 0.0);
121         return true;
122 }
123
124 double 
125 Panner1in2out::position () const
126 {
127         return _pannable->pan_azimuth_control->get_value ();
128 }
129
130 void
131 Panner1in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t /* not used */)
132 {
133         assert (obufs.count().n_audio() == 2);
134
135         pan_t delta;
136         Sample* dst;
137         pan_t pan;
138
139         Sample* const src = srcbuf.data();
140         
141         /* LEFT OUTPUT */
142
143         dst = obufs.get_audio(0).data();
144
145         if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc
146
147                 /* we've moving the pan by an appreciable amount, so we must
148                    interpolate over 64 frames or nframes, whichever is smaller */
149
150                 pframes_t const limit = min ((pframes_t) 64, nframes);
151                 pframes_t n;
152
153                 delta = -(delta / (float) (limit));
154
155                 for (n = 0; n < limit; n++) {
156                         left_interp = left_interp + delta;
157                         left = left_interp + 0.9 * (left - left_interp);
158                         dst[n] += src[n] * left * gain_coeff;
159                 }
160
161                 /* then pan the rest of the buffer; no need for interpolation for this bit */
162
163                 pan = left * gain_coeff;
164
165                 mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
166
167         } else {
168
169                 left = desired_left;
170                 left_interp = left;
171
172                 if ((pan = (left * gain_coeff)) != 1.0f) {
173
174                         if (pan != 0.0f) {
175
176                                 /* pan is 1 but also not 0, so we must do it "properly" */
177
178                                 mix_buffers_with_gain(dst,src,nframes,pan);
179
180                                 /* mark that we wrote into the buffer */
181
182                                 // obufs[0] = 0;
183
184                         }
185
186                 } else {
187
188                         /* pan is 1 so we can just copy the input samples straight in */
189
190                         mix_buffers_no_gain(dst,src,nframes);
191                         
192                         /* XXX it would be nice to mark that we wrote into the buffer */
193                 }
194         }
195
196         /* RIGHT OUTPUT */
197
198         dst = obufs.get_audio(1).data();
199
200         if (fabsf ((delta = (right - desired_right))) > 0.002) { // about 1 degree of arc
201
202                 /* we're moving the pan by an appreciable amount, so we must
203                    interpolate over 64 frames or nframes, whichever is smaller */
204
205                 pframes_t const limit = min ((pframes_t) 64, nframes);
206                 pframes_t n;
207
208                 delta = -(delta / (float) (limit));
209
210                 for (n = 0; n < limit; n++) {
211                         right_interp = right_interp + delta;
212                         right = right_interp + 0.9 * (right - right_interp);
213                         dst[n] += src[n] * right * gain_coeff;
214                 }
215
216                 /* then pan the rest of the buffer, no need for interpolation for this bit */
217
218                 pan = right * gain_coeff;
219
220                 mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
221
222                 /* XXX it would be nice to mark the buffer as written to */
223
224         } else {
225
226                 right = desired_right;
227                 right_interp = right;
228
229                 if ((pan = (right * gain_coeff)) != 1.0f) {
230
231                         if (pan != 0.0f) {
232
233                                 /* pan is not 1 but also not 0, so we must do it "properly" */
234                                 
235                                 mix_buffers_with_gain(dst,src,nframes,pan);
236
237                                 /* XXX it would be nice to mark the buffer as written to */
238                         }
239
240                 } else {
241
242                         /* pan is 1 so we can just copy the input samples straight in */
243                         
244                         mix_buffers_no_gain(dst,src,nframes);
245
246                         /* XXX it would be nice to mark the buffer as written to */
247                 }
248         }
249
250 }
251
252 void
253 Panner1in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
254                                          framepos_t start, framepos_t end, pframes_t nframes,
255                                          pan_t** buffers, uint32_t which)
256 {
257         assert (obufs.count().n_audio() == 2);
258
259         Sample* dst;
260         pan_t* pbuf;
261         Sample* const src = srcbuf.data();
262         pan_t* const position = buffers[0];
263
264         /* fetch positional data */
265
266         if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
267                 /* fallback */
268                 distribute_one (srcbuf, obufs, 1.0, nframes, which);
269                 return;
270         }
271
272         /* apply pan law to convert positional data into pan coefficients for
273            each buffer (output)
274         */
275
276         const float pan_law_attenuation = -3.0f;
277         const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
278
279         for (pframes_t n = 0; n < nframes; ++n) {
280
281                 float panR = position[n];
282                 const float panL = 1 - panR;
283
284                 /* note that are overwriting buffers, but its OK
285                    because we're finished with their old contents
286                    (position automation data) and are
287                    replacing it with panning/gain coefficients 
288                    that we need to actually process the data.
289                 */
290                 
291                 buffers[0][n] = panL * (scale * panL + 1.0f - scale);
292                 buffers[1][n] = panR * (scale * panR + 1.0f - scale);
293         }
294
295         /* LEFT OUTPUT */
296
297         dst = obufs.get_audio(0).data();
298         pbuf = buffers[0];
299
300         for (pframes_t n = 0; n < nframes; ++n) {
301                 dst[n] += src[n] * pbuf[n];
302         }
303
304         /* XXX it would be nice to mark the buffer as written to */
305
306         /* RIGHT OUTPUT */
307
308         dst = obufs.get_audio(1).data();
309         pbuf = buffers[1];
310
311         for (pframes_t n = 0; n < nframes; ++n) {
312                 dst[n] += src[n] * pbuf[n];
313         }
314
315         /* XXX it would be nice to mark the buffer as written to */
316 }
317
318
319 Panner*
320 Panner1in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
321 {
322         return new Panner1in2out (p);
323 }
324
325 XMLNode&
326 Panner1in2out::get_state ()
327 {
328         XMLNode& root (Panner::get_state ());
329         root.add_property (X_("type"), _descriptor.name);
330         return root;
331 }
332
333
334 std::set<Evoral::Parameter> 
335 Panner1in2out::what_can_be_automated() const
336 {
337         set<Evoral::Parameter> s;
338         s.insert (Evoral::Parameter (PanAzimuthAutomation));
339         return s;
340 }
341
342 string
343 Panner1in2out::describe_parameter (Evoral::Parameter p)
344 {
345         switch (p.type()) {
346         case PanAzimuthAutomation:
347                 return _("L/R");
348         default:
349                 return _pannable->describe_parameter (p);
350         }
351 }
352
353 string 
354 Panner1in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
355 {
356         /* DO NOT USE LocaleGuard HERE */
357         double val = ac->get_value();
358
359         switch (ac->parameter().type()) {
360         case PanAzimuthAutomation:
361                 /* We show the position of the center of the image relative to the left & right.
362                    This is expressed as a pair of percentage values that ranges from (100,0) 
363                    (hard left) through (50,50) (hard center) to (0,100) (hard right).
364                    
365                    This is pretty wierd, but its the way audio engineers expect it. Just remember that
366                    the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
367                 */
368                 
369                 return string_compose (_("L:%1 R:%2"), (int) rint (100.0 * (1.0 - val)),
370                                        (int) rint (100.0 * val));
371                 
372         default:
373                 return _pannable->value_as_string (ac);
374         }
375 }
376
377 void
378 Panner1in2out::reset ()
379 {
380         set_position (0.5);
381         update ();
382 }