merge 3.0-panexp (pan experiments) branch, revisions 8534-8585 into 3.0, thus ending...
[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, Speakers& /* ignored */)
321 {
322         return new Panner1in2out (p);
323 }
324
325 XMLNode&
326 Panner1in2out::get_state (void)
327 {
328         return state (true);
329 }
330
331 XMLNode&
332 Panner1in2out::state (bool /*full_state*/)
333 {
334         XMLNode& root (Panner::get_state ());
335         root.add_property (X_("type"), _descriptor.name);
336         return root;
337 }
338
339 int
340 Panner1in2out::set_state (const XMLNode& node, int version)
341 {
342         LocaleGuard lg (X_("POSIX"));
343         Panner::set_state (node, version);
344         return 0;
345 }
346
347 std::set<Evoral::Parameter> 
348 Panner1in2out::what_can_be_automated() const
349 {
350         set<Evoral::Parameter> s;
351         s.insert (Evoral::Parameter (PanAzimuthAutomation));
352         return s;
353 }
354
355 string
356 Panner1in2out::describe_parameter (Evoral::Parameter p)
357 {
358         switch (p.type()) {
359         case PanAzimuthAutomation:
360                 return _("L/R");
361         default:
362                 return _pannable->describe_parameter (p);
363         }
364 }