From 5c17f9c70f09b345aa94aad468bd49f38984aa3c Mon Sep 17 00:00:00 2001 From: Sebastian Reichelt Date: Tue, 31 Dec 2013 18:39:27 +0100 Subject: [PATCH 1/2] added delay-based versions of mono and stereo panners These new panners apply a configurable delay to one of the output channels, proportionally to the amount of panning. The factor is a session property, to ensure consistency within the session. Other minor changes made along the way: - Use sqrt(1/2) instead of -3 dB for the pan law, as it is more accurate and available as a compile-time constant. - The gain interpolation that is used when the panner is moved no longer remembers any state across calls to distribute_one(). This seemed to be unintentional and slightly broken. --- gtk2_ardour/session_option_editor.cc | 15 ++ libs/ardour/ardour/pan_delay_buffer.h | 170 ++++++++++++++ libs/ardour/ardour/pan_distribution_buffer.h | 130 +++++++++++ libs/ardour/ardour/panner.h | 2 + .../ardour/session_configuration_vars.h | 1 + libs/ardour/pan_delay_buffer.cc | 149 ++++++++++++ libs/ardour/pan_distribution_buffer.cc | 35 +++ libs/ardour/wscript | 2 + libs/panners/1in2out/panner_1in2out.cc | 172 ++++---------- libs/panners/1in2out/panner_1in2out.h | 13 +- libs/panners/1in2out/wscript | 12 + libs/panners/2in2out/panner_2in2out.cc | 217 ++++++------------ libs/panners/2in2out/panner_2in2out.h | 28 ++- libs/panners/2in2out/wscript | 12 + libs/panners/stereobalance/panner_balance.cc | 132 +++++------ libs/panners/stereobalance/panner_balance.h | 28 ++- 16 files changed, 744 insertions(+), 374 deletions(-) create mode 100644 libs/ardour/ardour/pan_delay_buffer.h create mode 100644 libs/ardour/ardour/pan_distribution_buffer.h create mode 100644 libs/ardour/pan_delay_buffer.cc create mode 100644 libs/ardour/pan_distribution_buffer.cc diff --git a/gtk2_ardour/session_option_editor.cc b/gtk2_ardour/session_option_editor.cc index d3f6aa2c7f3..d2551827113 100644 --- a/gtk2_ardour/session_option_editor.cc +++ b/gtk2_ardour/session_option_editor.cc @@ -253,7 +253,9 @@ SessionOptionEditor::SessionOptionEditor (Session* s) sigc::mem_fun (*this, &SessionOptionEditor::get_use_monitor_section), sigc::mem_fun (*this, &SessionOptionEditor::set_use_monitor_section) )); + /* Meterbridge */ + add_option (_("Meterbridge"), new OptionEditorHeading (_("Route Display"))); add_option (_("Meterbridge"), new BoolOption ( @@ -316,6 +318,19 @@ SessionOptionEditor::SessionOptionEditor (Session* s) sigc::mem_fun (*_session_config, &SessionConfiguration::set_show_name_on_meterbridge) )); + /* Panning */ + + add_option (_("Panning"), new OptionEditorHeading (_("Delay-based Panners"))); + + Gtk::Adjustment *panning_delay_adjustment = manage (new Gtk::Adjustment (0, 0, 10, .1, 1)); + add_option (_("Panning"), new HSliderOption ( + "panning-delay", + _("Applied delay in relation to loudness difference [ms/100%]"), + panning_delay_adjustment, + sigc::mem_fun (*_session_config, &SessionConfiguration::get_panning_delay), + sigc::mem_fun (*_session_config, &SessionConfiguration::set_panning_delay) + )); + /* Misc */ add_option (_("Misc"), new OptionEditorHeading (_("MIDI Options"))); diff --git a/libs/ardour/ardour/pan_delay_buffer.h b/libs/ardour/ardour/pan_delay_buffer.h new file mode 100644 index 00000000000..2aca5a8eb2e --- /dev/null +++ b/libs/ardour/ardour/pan_delay_buffer.h @@ -0,0 +1,170 @@ +/* + Copyright (C) 2013-2014 Sebastian Reichelt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __libardour_pan_delay_buffer_h__ +#define __libardour_pan_delay_buffer_h__ + +#include + +#include "ardour/session_handle.h" +#include "ardour/pan_distribution_buffer.h" + +namespace ARDOUR { + +class Session; + +/** Buffer to add a delay to a panned channel. + * + * The delay is specified in the session properties, in ms/100%, where the + * percentage refers to the difference between the two channels (for example, + * L60R40 means 20% in this case). Only the position is relevant, not the + * width of the stereo panner. The delay is applied to the output channel with + * the lower percentage. (It might be nice if the width control affected the + * phase differences of the incoming stereo signal, but that is a different + * topic.) + * + * To keep things simple, the applied delay is always an integer number of + * frames. As long as this integer stays the same, the implementation matches + * a regular circular buffer. (We no longer use boost::circular_buffer because + * it does not offer a way to preallocate memory beyond its capacity.) Things + * become more complicated whenever the delay changes, as this requires + * non-integer interpolation between the old and new delay, to avoid minor + * clicks in the audio. + * + * Note that PanDelayBufferImpl just provides the internal implementation; + * use PanDelayBuffer instead. + */ +class LIBARDOUR_API PanDelayBufferImpl : public SessionHandleRef +{ + public: + PanDelayBufferImpl(Session &s); + ~PanDelayBufferImpl(); + + /* Updates _session_delay_coeff according to the delay specified in + * the session configuration. */ + void update_session_config(); + + /* Updates the delay according to the given panner position. */ + void set_pan_position(float pan_position) + { + /* convert panner position to percentage value that is 0 if pan_position is 0.5, and 1 if pan_position is 0 */ + float const delay_percentage = std::max(std::min(1.0f - 2.0f * pan_position, 1.0f), 0.0f); + + /* calculate delay in frames */ + pframes_t new_delay = rint(delay_percentage * _session_delay_coeff); + if (new_delay > _buffer_size) { + new_delay = _buffer_size; + } + + /* update _desired_delay */ + if (_desired_delay != new_delay) { + if (_samples_processed) { + /* set up interpolation */ + _interp_active = true; + } else { + /* no samples processed yet; change delay immediately */ + _current_delay = new_delay; + } + + _desired_delay = new_delay; + } + } + + /* Appends the @a input sample to the delay buffer and removes and + * returns the oldest sample in the buffer. */ + Sample process(Sample input) + { + _samples_processed = true; + + Sample result; + if (_interp_active) { + /* interpolating between integer delays; continue in + * non-inlined code because this only happens for + * short intervals */ + result = interpolate(input); + } else if (_desired_delay == 0) { + /* currently bypassed */ + return input; + } else { + /* get the oldest sample in the buffer */ + pframes_t buffer_read_pos = _buffer_write_pos < _desired_delay ? _buffer_size + _buffer_write_pos - _desired_delay : _buffer_write_pos - _desired_delay; + result = _buffer[buffer_read_pos]; + } + + /* write the current sample into the buffer */ + _buffer[_buffer_write_pos] = input; + if (++_buffer_write_pos >= _buffer_size) { + _buffer_write_pos = 0; + } + + return result; + } + + /* See BasePanDistributionBuffer. (Implementation is highly optimized.) */ + void mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float gain); + + private: + /* The delay buffer, which is an array of size _buffer_size that is + * used as a circular buffer. */ + Sample *_buffer; + + /* Size of the _buffer array. */ + pframes_t _buffer_size; + + /* Position in the buffer where the next sample will be written. + * Increased by 1 for every sample, then wraps around at _buffer_size. */ + pframes_t _buffer_write_pos; + + /* Delay coefficient according to session configuration (in frames + * instead of ms). */ + float _session_delay_coeff; + + /* Current delay when interpolating. */ + float _current_delay; + + /* Desired delay; matches current delay if _interp_active is false. */ + pframes_t _desired_delay; + + /* Interpolation mode: See comment for _buffer. If true, _current_delay + * approaches _desired_delay in small steps; interpolation is finished + * as soon as they are equal. */ + bool _interp_active; + + /* Set to true on the first call to process() or an equivalent + * convenience method (and by update_session_config() if it returns + * false). As long as it is false, set_pan_position() sets the delay + * immediately without interpolation. */ + bool _samples_processed; + + /* Maximum delay, needed for memory preallocation. */ + static const float _max_delay_in_ms = 10.0f; + + /* Step size for _current_delay if _interp_active is true. */ + static const float _interp_inc = 1.0f / 16; + + /* Called by process() if _interp_active is true. */ + Sample interpolate(Sample input); +}; + +/** Actual pan distribution buffer class to be used by clients. */ +typedef BasePanDistributionBuffer PanDelayBuffer; + +} // namespace + +#endif /* __libardour_pan_delay_buffer_h__ */ diff --git a/libs/ardour/ardour/pan_distribution_buffer.h b/libs/ardour/ardour/pan_distribution_buffer.h new file mode 100644 index 00000000000..2eabf222457 --- /dev/null +++ b/libs/ardour/ardour/pan_distribution_buffer.h @@ -0,0 +1,130 @@ +/* + Copyright (C) 2014 Sebastian Reichelt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __libardour_pan_distribution_buffer_h__ +#define __libardour_pan_distribution_buffer_h__ + +#include "ardour/types.h" + +namespace ARDOUR { + +class Session; + +/* Maximum number of frames to interpolate between gains (used by + * BasePanDistributionBuffer::mix_buffers(); must be a multiple of 16). */ +static const pframes_t _gain_interp_frames = 64; + +/** Helper class for panners to manage distribution of signals to outputs. + * + * BasePanDistributionBuffer can be regarded as a "base class" of + * DummyPanDistributionBuffer and PanDelayBuffer. In reality, these + * "subclasses" are really typedefs that supply the inner implementation. + * The methods of BasePanDistributionBuffer define a compile-time interface; + * actual "subclass" to use must be selected using a template parameter or + * preprocessor macro. The reason is that set_pan_position() and process() + * are called in a tight loop and must not cause any unnecessary overhead. + * + * Clients should call update_session_config() whenever the session + * configuration might have changed, then set_pan_position() whenever the + * position of the panner might have changed, and then process() for every + * sample. For convenience and performance, the helper method mix_buffers() + * can be used instead if the panner position stays constant. + * + * For more information, see pan_delay_buffer.h. + */ +template +class BasePanDistributionBuffer +{ + public: + BasePanDistributionBuffer(Session& session) : _impl(session) {} + + /** Updates internal data according to the session configuration. */ + void update_session_config() { _impl.update_session_config(); } + + /** Updates internal data according to the given panner position. + * + * @a pan_position should be a value between 0 and 1, and should not + * be a gain value that has been calculated according to the pan law. + * For a stereo output, the @a pan_position values of the left and + * right channel should sum to 1. */ + void set_pan_position(float pan_position) { _impl.set_pan_position(pan_position); } + + /** Processes one sample, and returns the sample that should actually + * be output. */ + Sample process(Sample input) { return _impl.process(input); } + + /** Same as calling process() for each sample in @a src multiplied by + * @a gain, and adding the result to @a dst. However, if @a prev_gain + * is different from @a gain, interpolates between gains for the + * first 64 samples. + * + * In simple cases, this is implemented using mix_buffers_no_gain() and + * mix_buffers_with_gain() from runtime_functions.h. */ + void mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float prev_gain, float gain) + { + if (nframes == 0) { + return; + } + + if (gain == prev_gain) { + _impl.mix_buffers(dst, src, nframes, gain); + } else { + /* gain has changed, so we must interpolate over 64 frames or nframes, whichever is smaller */ + /* (code adapted from panner_1in2out.cc and panner_2in2out.cc) */ + + pframes_t const limit = std::min(_gain_interp_frames, nframes); + float const delta = (gain - prev_gain) / limit; + float current_gain = prev_gain; + pframes_t n = 0; + + for (; n < limit; n++) { + prev_gain += delta; + current_gain = prev_gain + 0.9 * (current_gain - prev_gain); + dst[n] += _impl.process(src[n] * current_gain); + } + + if (n < nframes) { + _impl.mix_buffers(dst + n, src + n, nframes - n, gain); + } + } + } + + private: + Impl _impl; +}; + +/** Internal class used by DummyPanDistributionBuffer; do not reference directly. */ +class LIBARDOUR_API DummyPanDistributionBufferImpl +{ + public: + DummyPanDistributionBufferImpl(Session& session) {} + + static void update_session_config() {} + static void set_pan_position(float pan_position) {} + static Sample process(Sample input) { return input; } + + static void mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float gain); +}; + +/** Dummy "distribtion buffer" which just forwards the samples. */ +typedef BasePanDistributionBuffer DummyPanDistributionBuffer; + +} // namespace + +#endif /* __libardour_pan_distribution_buffer_h__ */ diff --git a/libs/ardour/ardour/panner.h b/libs/ardour/ardour/panner.h index 211ad0ca8eb..37123e3ecc0 100644 --- a/libs/ardour/ardour/panner.h +++ b/libs/ardour/ardour/panner.h @@ -183,6 +183,8 @@ class LIBARDOUR_API Panner : public PBD::Stateful, public PBD::ScopedConnectionL pan_t** buffers, uint32_t which) = 0; int32_t _frozen; + + static const float _pan_law_scale = 2.0f - 4.0f * M_SQRT1_2; /* -3 dB */ }; } // namespace diff --git a/libs/ardour/ardour/session_configuration_vars.h b/libs/ardour/ardour/session_configuration_vars.h index 823cf5fe073..e5fdf0013ac 100644 --- a/libs/ardour/ardour/session_configuration_vars.h +++ b/libs/ardour/ardour/session_configuration_vars.h @@ -65,6 +65,7 @@ CONFIG_VARIABLE (bool, show_group_tabs, "show-group-tabs", true) CONFIG_VARIABLE (bool, show_region_fades, "show-region-fades", true) CONFIG_VARIABLE (bool, use_video_file_fps, "use-video-file-fps", false) CONFIG_VARIABLE (bool, videotimeline_pullup, "videotimeline-pullup", true) +CONFIG_VARIABLE (float, panning_delay, "panning-delay", 1.0f) CONFIG_VARIABLE (bool, show_busses_on_meterbridge, "show-busses-on-meterbridge", false) CONFIG_VARIABLE (bool, show_master_on_meterbridge, "show-master-on-meterbridge", true) CONFIG_VARIABLE (bool, show_midi_on_meterbridge, "show-midi-on-meterbridge", true) diff --git a/libs/ardour/pan_delay_buffer.cc b/libs/ardour/pan_delay_buffer.cc new file mode 100644 index 00000000000..78112f8f05d --- /dev/null +++ b/libs/ardour/pan_delay_buffer.cc @@ -0,0 +1,149 @@ +/* + Copyright (C) 2013 Sebastian Reichelt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "ardour/pan_delay_buffer.h" +#include "ardour/session.h" + +using namespace std; +using namespace ARDOUR; + +PanDelayBufferImpl::PanDelayBufferImpl(Session &s) + : SessionHandleRef(s) + , _buffer(0) + , _buffer_size(rint(_max_delay_in_ms * s.frame_rate() * 0.001f)) + , _buffer_write_pos(0) + , _session_delay_coeff(0.0f) + , _current_delay(0.0f) + , _desired_delay(0) + , _interp_active(false) + , _samples_processed(false) +{ + _buffer = new Sample[_buffer_size]; + for (pframes_t n = 0; n < _buffer_size; n++) { + _buffer[n] = 0.0f; + } + + update_session_config(); +} + +PanDelayBufferImpl::~PanDelayBufferImpl() +{ + delete [] _buffer; +} + +void +PanDelayBufferImpl::update_session_config() +{ + _session_delay_coeff = _session.config.get_panning_delay() * _session.frame_rate() * 0.001f; +} + +Sample +PanDelayBufferImpl::interpolate(Sample input) +{ + /* can always decrease the current delay, so do it right away (in contrast to increasing; see below) */ + /* (use >= instead of > to avoid getting stuck in interpolation mode) */ + if (_current_delay >= _desired_delay) { + _current_delay -= _interp_inc; + /* check if interpolation is finished */ + if (_current_delay <= _desired_delay) { + _current_delay = _desired_delay; + _interp_active = false; + /* could return here, but this is not necessary */ + } + } + + /* check which two samples we need, and which coefficients we should apply */ + pframes_t current_delay_int = _current_delay; + float interp_coeff = _current_delay - current_delay_int; + pframes_t buffer_read_pos = _buffer_write_pos < current_delay_int ? _buffer_size + _buffer_write_pos - current_delay_int : _buffer_write_pos - current_delay_int; + + /* interpolate between the two samples */ + Sample const first = _buffer[buffer_read_pos == 0 ? _buffer_size - 1 : buffer_read_pos - 1]; + Sample const second = current_delay_int == 0 ? input : _buffer[buffer_read_pos]; + Sample const result = first * interp_coeff + second * (1.0f - interp_coeff); + + /* increase the current delay at the end instead of the beginning, since the buffer may not have been filled enough at first */ + if (_current_delay < _desired_delay) { + _current_delay += _interp_inc; + /* check if interpolation is finished */ + if (_current_delay >= _desired_delay) { + _current_delay = _desired_delay; + _interp_active = false; + } + } + + return result; +} + +void +PanDelayBufferImpl::mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float gain) +{ + _samples_processed = true; + + if (_desired_delay == 0 && !_interp_active) { + /* fast path: no delay */ + + DummyPanDistributionBufferImpl::mix_buffers(dst, src, nframes, gain); + } else { + pframes_t n = 0; + + /* process samples normally as long as interpolation is active */ + for (; _interp_active && n < nframes; n++) { + dst[n] += process(src[n] * gain); + } + + /* try to bypass the buffer as much as possible */ + pframes_t bypass_start = n + _desired_delay; + if (bypass_start < nframes) { + /* fast path: have more samples left than the length of the delay */ + + /* first output the tail of the buffer */ + pframes_t buffer_read_pos = _buffer_write_pos < _desired_delay ? _buffer_size + _buffer_write_pos - _desired_delay : _buffer_write_pos - _desired_delay; + /* n < bypass_start implies n < nframes because bypass_start < nframes */ + for (; n < bypass_start; n++) { + dst[n] += _buffer[buffer_read_pos]; + if (++buffer_read_pos >= _buffer_size) { + buffer_read_pos = 0; + } + } + + /* then copy as many samples directly as possible */ + if (gain != 0.0f) { + for (; n < nframes; n++) { + /* n >= _desired_delay because n >= bypass_start */ + dst[n] += (src[n - _desired_delay] * gain); + } + } + + /* finally, fill the buffer with the remaining samples */ + /* n >= _desired_delay because n >= bypass_start */ + for (n -= _desired_delay; n < nframes; n++) { + _buffer[_buffer_write_pos] = src[n] * gain; + if (++_buffer_write_pos >= _buffer_size) { + _buffer_write_pos = 0; + } + } + } else { + /* general case: process samples normally */ + for (; n < nframes; n++) { + dst[n] += process(src[n] * gain); + } + } + } +} diff --git a/libs/ardour/pan_distribution_buffer.cc b/libs/ardour/pan_distribution_buffer.cc new file mode 100644 index 00000000000..99a95b4b594 --- /dev/null +++ b/libs/ardour/pan_distribution_buffer.cc @@ -0,0 +1,35 @@ +/* + Copyright (C) 2014 Sebastian Reichelt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "ardour/pan_distribution_buffer.h" +#include "ardour/runtime_functions.h" + +using namespace ARDOUR; + +void +DummyPanDistributionBufferImpl::mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float gain) +{ + if (gain == 1.0f) { + /* gain is 1 so we can just copy the input samples straight in */ + mix_buffers_no_gain(dst, src, nframes); + } else if (gain != 0.0f) { + /* gain is not 1 but also not 0, so we must do it "properly" */ + mix_buffers_with_gain(dst, src, nframes, gain); + } +} diff --git a/libs/ardour/wscript b/libs/ardour/wscript index c732161ec8f..2710827fbc5 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -137,6 +137,8 @@ libardour_sources = [ 'onset_detector.cc', 'operations.cc', 'pan_controllable.cc', + 'pan_delay_buffer.cc', + 'pan_distribution_buffer.cc', 'pannable.cc', 'panner.cc', 'panner_manager.cc', diff --git a/libs/panners/1in2out/panner_1in2out.cc b/libs/panners/1in2out/panner_1in2out.cc index 50c058b1602..cd7fd45489f 100644 --- a/libs/panners/1in2out/panner_1in2out.cc +++ b/libs/panners/1in2out/panner_1in2out.cc @@ -47,7 +47,6 @@ #include "ardour/audio_buffer.h" #include "ardour/debug.h" -#include "ardour/runtime_functions.h" #include "ardour/buffer_set.h" #include "ardour/audio_buffer.h" #include "ardour/pannable.h" @@ -62,6 +61,16 @@ using namespace std; using namespace ARDOUR; using namespace PBD; +#ifdef ENABLE_PANNING_DELAY +static PanPluginDescriptor _descriptor = { + "Mono to Stereo Panner with Delay", + "http://ardour.org/plugin/panner_1in2out_delay", + "http://ardour.org/plugin/panner_1in2out#ui", + 1, 2, + 1000, + Panner1in2out::factory +}; +#else /* !defined(ENABLE_PANNING_DELAY) */ static PanPluginDescriptor _descriptor = { "Mono to Stereo Panner", "http://ardour.org/plugin/panner_1in2out", @@ -70,11 +79,14 @@ static PanPluginDescriptor _descriptor = { 10000, Panner1in2out::factory }; +#endif /* !defined(ENABLE_PANNING_DELAY) */ extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; } Panner1in2out::Panner1in2out (boost::shared_ptr p) : Panner (p) + , left_dist_buf (p->session()) + , right_dist_buf (p->session()) { if (!Profile->get_trx () ) { if (!_pannable->has_state ()) { @@ -86,8 +98,6 @@ Panner1in2out::Panner1in2out (boost::shared_ptr p) left = desired_left; right = desired_right; - left_interp = left; - right_interp = right; _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner1in2out::update, this)); } @@ -100,14 +110,12 @@ void Panner1in2out::update () { float panR, panL; - float const pan_law_attenuation = -3.0f; - float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - panR = _pannable->pan_azimuth_control->get_value(); - panL = 1 - panR; + panR = position(); + panL = 1.0f - panR; - desired_left = panL * (scale * panL + 1.0f - scale); - desired_right = panR * (scale * panR + 1.0f - scale); + desired_left = panL * (_pan_law_scale * panL + 1.0f - _pan_law_scale); + desired_right = panR * (_pan_law_scale * panR + 1.0f - _pan_law_scale); } void @@ -144,9 +152,7 @@ Panner1in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai { assert (obufs.count().n_audio() == 2); - pan_t delta; Sample* dst; - pan_t pan; Sample* const src = srcbuf.data(); @@ -154,111 +160,27 @@ Panner1in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai dst = obufs.get_audio(0).data(); - if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc - - /* we've moving the pan by an appreciable amount, so we must - interpolate over 64 frames or nframes, whichever is smaller */ - - pframes_t const limit = min ((pframes_t) 64, nframes); - pframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - left_interp = left_interp + delta; - left = left_interp + 0.9 * (left - left_interp); - dst[n] += src[n] * left * gain_coeff; - } - - /* then pan the rest of the buffer; no need for interpolation for this bit */ - - pan = left * gain_coeff; - - mix_buffers_with_gain (dst+n,src+n,nframes-n,pan); - - } else { + left_dist_buf.update_session_config(); + left_dist_buf.set_pan_position(1.0 - position()); - left = desired_left; - left_interp = left; + left_dist_buf.mix_buffers(dst, src, nframes, left * gain_coeff, desired_left * gain_coeff); - if ((pan = (left * gain_coeff)) != 1.0f) { + left = desired_left; - if (pan != 0.0f) { - - /* pan is 1 but also not 0, so we must do it "properly" */ - - mix_buffers_with_gain(dst,src,nframes,pan); - - /* mark that we wrote into the buffer */ - - // obufs[0] = 0; - - } - - } else { - - /* pan is 1 so we can just copy the input samples straight in */ - - mix_buffers_no_gain(dst,src,nframes); - - /* XXX it would be nice to mark that we wrote into the buffer */ - } - } + /* XXX it would be nice to mark the buffer as written to, depending on gain (see pan_distribution_buffer.cc) */ /* RIGHT OUTPUT */ dst = obufs.get_audio(1).data(); - if (fabsf ((delta = (right - desired_right))) > 0.002) { // about 1 degree of arc - - /* we're moving the pan by an appreciable amount, so we must - interpolate over 64 frames or nframes, whichever is smaller */ - - pframes_t const limit = min ((pframes_t) 64, nframes); - pframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - right_interp = right_interp + delta; - right = right_interp + 0.9 * (right - right_interp); - dst[n] += src[n] * right * gain_coeff; - } + right_dist_buf.update_session_config(); + right_dist_buf.set_pan_position(position()); - /* then pan the rest of the buffer, no need for interpolation for this bit */ + right_dist_buf.mix_buffers(dst, src, nframes, right * gain_coeff, desired_right * gain_coeff); - pan = right * gain_coeff; - - mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); - - /* XXX it would be nice to mark the buffer as written to */ - - } else { - - right = desired_right; - right_interp = right; - - if ((pan = (right * gain_coeff)) != 1.0f) { - - if (pan != 0.0f) { - - /* pan is not 1 but also not 0, so we must do it "properly" */ - - mix_buffers_with_gain(dst,src,nframes,pan); - - /* XXX it would be nice to mark the buffer as written to */ - } - - } else { - - /* pan is 1 so we can just copy the input samples straight in */ - - mix_buffers_no_gain(dst,src,nframes); - - /* XXX it would be nice to mark the buffer as written to */ - } - } + right = desired_right; + /* XXX it would be nice to mark the buffer as written to, depending on gain (see pan_distribution_buffer.cc) */ } void @@ -269,7 +191,6 @@ Panner1in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, assert (obufs.count().n_audio() == 2); Sample* dst; - pan_t* pbuf; Sample* const src = srcbuf.data(); pan_t* const position = buffers[0]; @@ -281,36 +202,17 @@ Panner1in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, return; } - /* apply pan law to convert positional data into pan coefficients for - each buffer (output) - */ - - const float pan_law_attenuation = -3.0f; - const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - - for (pframes_t n = 0; n < nframes; ++n) { - - float panR = position[n]; - const float panL = 1 - panR; - - /* note that are overwriting buffers, but its OK - because we're finished with their old contents - (position automation data) and are - replacing it with panning/gain coefficients - that we need to actually process the data. - */ - - buffers[0][n] = panL * (scale * panL + 1.0f - scale); - buffers[1][n] = panR * (scale * panR + 1.0f - scale); - } - /* LEFT OUTPUT */ dst = obufs.get_audio(0).data(); - pbuf = buffers[0]; + + left_dist_buf.update_session_config(); for (pframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n] * pbuf[n]; + const float panL = 1.0f - position[n]; + + left_dist_buf.set_pan_position(panL); + dst[n] += left_dist_buf.process(src[n] * panL * (_pan_law_scale * panL + 1.0f - _pan_law_scale)); } /* XXX it would be nice to mark the buffer as written to */ @@ -318,10 +220,14 @@ Panner1in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, /* RIGHT OUTPUT */ dst = obufs.get_audio(1).data(); - pbuf = buffers[1]; + + right_dist_buf.update_session_config(); for (pframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n] * pbuf[n]; + const float panR = position[n]; + + right_dist_buf.set_pan_position(panR); + dst[n] += right_dist_buf.process(src[n] * panR * (_pan_law_scale * panR + 1.0f - _pan_law_scale)); } /* XXX it would be nice to mark the buffer as written to */ diff --git a/libs/panners/1in2out/panner_1in2out.h b/libs/panners/1in2out/panner_1in2out.h index 912072096bd..904abde9777 100644 --- a/libs/panners/1in2out/panner_1in2out.h +++ b/libs/panners/1in2out/panner_1in2out.h @@ -32,7 +32,15 @@ #include "ardour/types.h" #include "ardour/panner.h" +#include "ardour/pan_distribution_buffer.h" +#ifdef ENABLE_PANNING_DELAY +#define Panner1in2out Panner1in2outDelay +#include "ardour/pan_delay_buffer.h" +#define PanDistributionBuffer PanDelayBuffer +#else /* !defined(ENABLE_PANNING_DELAY) */ +#define PanDistributionBuffer DummyPanDistributionBuffer +#endif /* !defined(ENABLE_PANNING_DELAY) */ namespace ARDOUR { @@ -67,8 +75,9 @@ class Panner1in2out : public Panner float right; float desired_left; float desired_right; - float left_interp; - float right_interp; + + PanDistributionBuffer left_dist_buf; + PanDistributionBuffer right_dist_buf; void distribute_one (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which); void distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, diff --git a/libs/panners/1in2out/wscript b/libs/panners/1in2out/wscript index 2c1d9f12939..9ea28c8abdc 100644 --- a/libs/panners/1in2out/wscript +++ b/libs/panners/1in2out/wscript @@ -24,5 +24,17 @@ def build(bld): obj.use = 'libardour libardour_cp libpbd' obj.install_path = os.path.join(bld.env['LIBDIR'], 'panners') + obj = bld(features = 'cxx cxxshlib') + obj.source = [ 'panner_1in2out.cc' ] + obj.export_includes = ['.'] + obj.defines = [ 'PACKAGE="libardour_pan1in2out_delay"' ] + obj.defines += [ 'ARDOURPANNER_DLL_EXPORTS' ] + obj.defines += [ 'ENABLE_PANNING_DELAY' ] + obj.includes = ['.'] + obj.name = 'libardour_pan1in2out_delay' + obj.target = 'pan1in2out_delay' + obj.use = 'libardour libardour_cp libpbd' + obj.install_path = os.path.join(bld.env['LIBDIR'], 'panners') + def shutdown(): autowaf.shutdown() diff --git a/libs/panners/2in2out/panner_2in2out.cc b/libs/panners/2in2out/panner_2in2out.cc index f18dd94f45a..99439f6805d 100644 --- a/libs/panners/2in2out/panner_2in2out.cc +++ b/libs/panners/2in2out/panner_2in2out.cc @@ -46,7 +46,6 @@ #include "ardour/buffer_set.h" #include "ardour/pan_controllable.h" #include "ardour/pannable.h" -#include "ardour/runtime_functions.h" #include "ardour/session.h" #include "ardour/utils.h" #include "ardour/mix.h" @@ -61,6 +60,16 @@ using namespace std; using namespace ARDOUR; using namespace PBD; +#ifdef ENABLE_PANNING_DELAY +static PanPluginDescriptor _descriptor = { + "Equal Power Stereo with Delay", + "http://ardour.org/plugin/panner_2in2out_delay", + "http://ardour.org/plugin/panner_2in2out#ui", + 2, 2, + 1000, + Panner2in2out::factory +}; +#else /* !defined(ENABLE_PANNING_DELAY) */ static PanPluginDescriptor _descriptor = { "Equal Power Stereo", "http://ardour.org/plugin/panner_2in2out", @@ -69,12 +78,22 @@ static PanPluginDescriptor _descriptor = { 10000, Panner2in2out::factory }; +#endif /* !defined(ENABLE_PANNING_DELAY) */ extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; } Panner2in2out::Panner2in2out (boost::shared_ptr p) : Panner (p) + , left_dist_buf_0 (p->session()) + , left_dist_buf_1 (p->session()) + , right_dist_buf_0 (p->session()) + , right_dist_buf_1 (p->session()) { + left_dist_buf[0] = &left_dist_buf_0; + left_dist_buf[1] = &left_dist_buf_1; + right_dist_buf[0] = &right_dist_buf_0; + right_dist_buf[1] = &right_dist_buf_1; + if (!_pannable->has_state()) { _pannable->pan_azimuth_control->set_value (0.5); _pannable->pan_width_control->set_value (1.0); @@ -90,12 +109,12 @@ Panner2in2out::Panner2in2out (boost::shared_ptr p) update (); /* LEFT SIGNAL */ - left_interp[0] = left[0] = desired_left[0]; - right_interp[0] = right[0] = desired_right[0]; + left[0] = desired_left[0]; + right[0] = desired_right[0]; /* RIGHT SIGNAL */ - left_interp[1] = left[1] = desired_left[1]; - right_interp[1] = right[1] = desired_right[1]; + left[1] = desired_left[1]; + right[1] = desired_right[1]; _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this)); _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this)); @@ -170,35 +189,27 @@ Panner2in2out::update () width = (width > 0 ? wrange : -wrange); } - if (width < 0.0) { - width = -width; - pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract - pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract - } else { - pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract - pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract - } + pos[0] = direction_as_lr_fract - (width*0.5); // left signal lr_fract + pos[1] = direction_as_lr_fract + (width*0.5); // right signal lr_fract /* compute target gain coefficients for both input signals */ - float const pan_law_attenuation = -3.0f; - float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); float panR; float panL; /* left signal */ panR = pos[0]; - panL = 1 - panR; - desired_left[0] = panL * (scale * panL + 1.0f - scale); - desired_right[0] = panR * (scale * panR + 1.0f - scale); + panL = 1.0f - panR; + desired_left[0] = panL * (_pan_law_scale * panL + 1.0f - _pan_law_scale); + desired_right[0] = panR * (_pan_law_scale * panR + 1.0f - _pan_law_scale); /* right signal */ panR = pos[1]; - panL = 1 - panR; - desired_left[1] = panL * (scale * panL + 1.0f - scale); - desired_right[1] = panR * (scale * panR + 1.0f - scale); + panL = 1.0f - panR; + desired_left[1] = panL * (_pan_law_scale * panL + 1.0f - _pan_law_scale); + desired_right[1] = panR * (_pan_law_scale * panR + 1.0f - _pan_law_scale); } bool @@ -237,8 +248,8 @@ Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width) width = max (min (width, 1.0), -1.0); direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0); - r_pos = direction_as_lr_fract + (width/2.0); - l_pos = direction_as_lr_fract - (width/2.0); + r_pos = direction_as_lr_fract + (width*0.5); + l_pos = direction_as_lr_fract - (width*0.5); if (width < 0.0) { swap (r_pos, l_pos); @@ -269,9 +280,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai { assert (obufs.count().n_audio() == 2); - pan_t delta; Sample* dst; - pan_t pan; Sample* const src = srcbuf.data(); @@ -279,112 +288,27 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai dst = obufs.get_audio(0).data(); - if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc - - /* we've moving the pan by an appreciable amount, so we must - interpolate over 64 frames or nframes, whichever is smaller */ - - pframes_t const limit = min ((pframes_t) 64, nframes); - pframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - left_interp[which] = left_interp[which] + delta; - left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]); - dst[n] += src[n] * left[which] * gain_coeff; - } - - /* then pan the rest of the buffer; no need for interpolation for this bit */ - - pan = left[which] * gain_coeff; - - mix_buffers_with_gain (dst+n,src+n,nframes-n,pan); - - } else { + left_dist_buf[which]->update_session_config(); + left_dist_buf[which]->set_pan_position(1.0 - position()); - left[which] = desired_left[which]; - left_interp[which] = left[which]; + left_dist_buf[which]->mix_buffers(dst, src, nframes, left[which] * gain_coeff, desired_left[which] * gain_coeff); - if ((pan = (left[which] * gain_coeff)) != 1.0f) { + left[which] = desired_left[which]; - if (pan != 0.0f) { - - /* pan is 1 but also not 0, so we must do it "properly" */ - - //obufs.get_audio(1).read_from (srcbuf, nframes); - mix_buffers_with_gain(dst,src,nframes,pan); - - /* mark that we wrote into the buffer */ - - // obufs[0] = 0; - - } - - } else { - - /* pan is 1 so we can just copy the input samples straight in */ - - mix_buffers_no_gain(dst,src,nframes); - - /* XXX it would be nice to mark that we wrote into the buffer */ - } - } + /* XXX it would be nice to mark the buffer as written to, depending on gain (see pan_distribution_buffer.cc) */ /* RIGHT OUTPUT */ dst = obufs.get_audio(1).data(); - if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc - - /* we're moving the pan by an appreciable amount, so we must - interpolate over 64 frames or nframes, whichever is smaller */ - - pframes_t const limit = min ((pframes_t) 64, nframes); - pframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - right_interp[which] = right_interp[which] + delta; - right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]); - dst[n] += src[n] * right[which] * gain_coeff; - } - - /* then pan the rest of the buffer, no need for interpolation for this bit */ - - pan = right[which] * gain_coeff; - - mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); + right_dist_buf[which]->update_session_config(); + right_dist_buf[which]->set_pan_position(position()); - /* XXX it would be nice to mark the buffer as written to */ + right_dist_buf[which]->mix_buffers(dst, src, nframes, right[which] * gain_coeff, desired_right[which] * gain_coeff); - } else { + right[which] = desired_right[which]; - right[which] = desired_right[which]; - right_interp[which] = right[which]; - - if ((pan = (right[which] * gain_coeff)) != 1.0f) { - - if (pan != 0.0f) { - - /* pan is not 1 but also not 0, so we must do it "properly" */ - - mix_buffers_with_gain(dst,src,nframes,pan); - // obufs.get_audio(1).read_from (srcbuf, nframes); - - /* XXX it would be nice to mark the buffer as written to */ - } - - } else { - - /* pan is 1 so we can just copy the input samples straight in */ - - mix_buffers_no_gain(dst,src,nframes); - - /* XXX it would be nice to mark the buffer as written to */ - } - } + /* XXX it would be nice to mark the buffer as written to, depending on gain (see pan_distribution_buffer.cc) */ } void @@ -395,7 +319,6 @@ Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, assert (obufs.count().n_audio() == 2); Sample* dst; - pan_t* pbuf; Sample* const src = srcbuf.data(); pan_t* const position = buffers[0]; pan_t* const width = buffers[1]; @@ -414,47 +337,29 @@ Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, return; } - /* apply pan law to convert positional data into pan coefficients for - each buffer (output) - */ + /* LEFT OUTPUT */ - const float pan_law_attenuation = -3.0f; - const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); + dst = obufs.get_audio(0).data(); - for (pframes_t n = 0; n < nframes; ++n) { + left_dist_buf[which]->update_session_config(); + for (pframes_t n = 0; n < nframes; ++n) { float panR; if (which == 0) { // panning left signal - panR = position[n] - (width[n]/2.0f); // center - width/2 + panR = position[n] - (width[n]*0.5f); // center - width/2 } else { // panning right signal - panR = position[n] + (width[n]/2.0f); // center - width/2 + panR = position[n] + (width[n]*0.5f); // center + width/2 } panR = max(0.f, min(1.f, panR)); const float panL = 1 - panR; - /* note that are overwriting buffers, but its OK - because we're finished with their old contents - (position/width automation data) and are - replacing it with panning/gain coefficients - that we need to actually process the data. - */ - - buffers[0][n] = panL * (scale * panL + 1.0f - scale); - buffers[1][n] = panR * (scale * panR + 1.0f - scale); - } - - /* LEFT OUTPUT */ - - dst = obufs.get_audio(0).data(); - pbuf = buffers[0]; - - for (pframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n] * pbuf[n]; + left_dist_buf[which]->set_pan_position(1.0f - position[n]); + dst[n] += left_dist_buf[which]->process(src[n] * panL * (_pan_law_scale * panL + 1.0f - _pan_law_scale)); } /* XXX it would be nice to mark the buffer as written to */ @@ -462,10 +367,24 @@ Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, /* RIGHT OUTPUT */ dst = obufs.get_audio(1).data(); - pbuf = buffers[1]; + + right_dist_buf[which]->update_session_config(); for (pframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n] * pbuf[n]; + float panR; + + if (which == 0) { + // panning left signal + panR = position[n] - (width[n]*0.5f); // center - width/2 + } else { + // panning right signal + panR = position[n] + (width[n]*0.5f); // center + width/2 + } + + panR = max(0.f, min(1.f, panR)); + + right_dist_buf[which]->set_pan_position(position[n]); + dst[n] += right_dist_buf[which]->process(src[n] * panR * (_pan_law_scale * panR + 1.0f - _pan_law_scale)); } /* XXX it would be nice to mark the buffer as written to */ diff --git a/libs/panners/2in2out/panner_2in2out.h b/libs/panners/2in2out/panner_2in2out.h index a34556d463b..48fea81958b 100644 --- a/libs/panners/2in2out/panner_2in2out.h +++ b/libs/panners/2in2out/panner_2in2out.h @@ -30,10 +30,17 @@ #include "pbd/controllable.h" #include "pbd/cartesian.h" -#include "ardour/automation_control.h" -#include "ardour/automatable.h" -#include "ardour/panner.h" #include "ardour/types.h" +#include "ardour/panner.h" +#include "ardour/pan_distribution_buffer.h" + +#ifdef ENABLE_PANNING_DELAY +#define Panner2in2out Panner2in2outDelay +#include "ardour/pan_delay_buffer.h" +#define PanDistributionBuffer PanDelayBuffer +#else /* !defined(ENABLE_PANNING_DELAY) */ +#define PanDistributionBuffer DummyPanDistributionBuffer +#endif /* !defined(ENABLE_PANNING_DELAY) */ namespace ARDOUR { @@ -77,8 +84,19 @@ class Panner2in2out : public Panner float right[2]; float desired_left[2]; float desired_right[2]; - float left_interp[2]; - float right_interp[2]; + + /* We need four distribution buffers instead of two because distribute_one() + * is called separately for each input. */ + PanDistributionBuffer left_dist_buf_0; + PanDistributionBuffer left_dist_buf_1; + PanDistributionBuffer right_dist_buf_0; + PanDistributionBuffer right_dist_buf_1; + + /* Pointers to the four buffers arranged as arrays, for convenience. + * (The members above are only needed because PanDistributionBuffer is not + * default-constructible.) */ + PanDistributionBuffer* left_dist_buf[2]; + PanDistributionBuffer* right_dist_buf[2]; private: bool clamp_stereo_pan (double& direction_as_lr_fract, double& width); diff --git a/libs/panners/2in2out/wscript b/libs/panners/2in2out/wscript index bd82526801d..b04d0b9cf5b 100644 --- a/libs/panners/2in2out/wscript +++ b/libs/panners/2in2out/wscript @@ -24,5 +24,17 @@ def build(bld): obj.use = 'libardour libardour_cp libpbd' obj.install_path = os.path.join(bld.env['LIBDIR'], 'panners') + obj = bld(features = 'cxx cxxshlib') + obj.source = [ 'panner_2in2out.cc' ] + obj.export_includes = ['.'] + obj.defines = [ 'PACKAGE="libardour_pan2in2out_delay"' ] + obj.defines += [ 'ARDOURPANNER_DLL_EXPORTS' ] + obj.defines += [ 'ENABLE_PANNING_DELAY' ] + obj.includes = ['.'] + obj.name = 'libardour_pan2in2out_delay' + obj.target = 'pan2in2out_delay' + obj.use = 'libardour libardour_cp libpbd' + obj.install_path = os.path.join(bld.env['LIBDIR'], 'panners') + def shutdown(): autowaf.shutdown() diff --git a/libs/panners/stereobalance/panner_balance.cc b/libs/panners/stereobalance/panner_balance.cc index 7c0d7e7ff60..a51bdbe4515 100644 --- a/libs/panners/stereobalance/panner_balance.cc +++ b/libs/panners/stereobalance/panner_balance.cc @@ -62,6 +62,16 @@ using namespace std; using namespace ARDOUR; using namespace PBD; +#ifdef ENABLE_PANNING_DELAY +static PanPluginDescriptor _descriptor = { + "Stereo Balance with Delay", + "http://ardour.org/plugin/panner_balance_delay", + "http://ardour.org/plugin/panner_balance#ui", + 2, 2, + 200, + Pannerbalance::factory +}; +#else /* !defined(ENABLE_PANNING_DELAY) */ static PanPluginDescriptor _descriptor = { "Stereo Balance", "http://ardour.org/plugin/panner_balance", @@ -70,12 +80,18 @@ static PanPluginDescriptor _descriptor = { 2000, Pannerbalance::factory }; +#endif /* !defined(ENABLE_PANNING_DELAY) */ extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; } Pannerbalance::Pannerbalance (boost::shared_ptr p) : Panner (p) + , dist_buf_0 (p->session()) + , dist_buf_1 (p->session()) { + dist_buf[0] = &dist_buf_0; + dist_buf[1] = &dist_buf_1; + if (!_pannable->has_state()) { _pannable->pan_azimuth_control->set_value (0.5); } @@ -83,9 +99,9 @@ Pannerbalance::Pannerbalance (boost::shared_ptr p) update (); /* LEFT SIGNAL */ - pos_interp[0] = pos[0] = desired_pos[0]; + gain[0] = desired_gain[0]; /* RIGHT SIGNAL */ - pos_interp[1] = pos[1] = desired_pos[1]; + gain[1] = desired_gain[1]; _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Pannerbalance::update, this)); } @@ -100,7 +116,7 @@ Pannerbalance::position () const return _pannable->pan_azimuth_control->get_value(); } - void +void Pannerbalance::set_position (double p) { if (clamp_position (p)) { @@ -108,7 +124,7 @@ Pannerbalance::set_position (double p) } } - void +void Pannerbalance::thaw () { Panner::thaw (); @@ -124,17 +140,17 @@ Pannerbalance::update () return; } - float const pos = _pannable->pan_azimuth_control->get_value(); + double const pos = position(); if (pos == .5) { - desired_pos[0] = 1.0; - desired_pos[1] = 1.0; + desired_gain[0] = 1.0; + desired_gain[1] = 1.0; } else if (pos > .5) { - desired_pos[0] = 2 - 2. * pos; - desired_pos[1] = 1.0; + desired_gain[0] = 2 - 2. * pos; + desired_gain[1] = 1.0; } else { - desired_pos[0] = 1.0; - desired_pos[1] = 2. * pos; + desired_gain[0] = 1.0; + desired_gain[1] = 2. * pos; } } @@ -156,61 +172,25 @@ Pannerbalance::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai { assert (obufs.count().n_audio() == 2); - pan_t delta; Sample* dst; - pan_t pan; + + double pos = position(); + if (which == 0) { + pos = 1.0 - pos; + } Sample* const src = srcbuf.data(); dst = obufs.get_audio(which).data(); - if (fabsf ((delta = (pos[which] - desired_pos[which]))) > 0.002) { // about 1 degree of arc - - /* we've moving the pan by an appreciable amount, so we must - interpolate over 64 frames or nframes, whichever is smaller */ - - pframes_t const limit = min ((pframes_t) 64, nframes); - pframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - pos_interp[which] = pos_interp[which] + delta; - pos[which] = pos_interp[which] + 0.9 * (pos[which] - pos_interp[which]); - dst[n] += src[n] * pos[which] * gain_coeff; - } - - /* then pan the rest of the buffer; no need for interpolation for this bit */ - - pan = pos[which] * gain_coeff; - - mix_buffers_with_gain (dst+n,src+n,nframes-n,pan); - - } else { - - pos[which] = desired_pos[which]; - pos_interp[which] = pos[which]; - - if ((pan = (pos[which] * gain_coeff)) != 1.0f) { - - if (pan != 0.0f) { - - /* pan is 1 but also not 0, so we must do it "properly" */ + dist_buf[which]->update_session_config(); + dist_buf[which]->set_pan_position(pos); - //obufs.get_audio(1).read_from (srcbuf, nframes); - mix_buffers_with_gain(dst,src,nframes,pan); + dist_buf[which]->mix_buffers(dst, src, nframes, gain[which] * gain_coeff, desired_gain[which] * gain_coeff); - /* mark that we wrote into the buffer */ + gain[which] = desired_gain[which]; - // obufs[0] = 0; - - } - - } else { - /* pan is 1 so we can just copy the input samples straight in */ - mix_buffers_no_gain(dst,src,nframes); - } - } + /* XXX it would be nice to mark the buffer as written to, depending on gain (see pan_distribution_buffer.cc) */ } void @@ -221,7 +201,6 @@ Pannerbalance::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, assert (obufs.count().n_audio() == 2); Sample* dst; - pan_t* pbuf; Sample* const src = srcbuf.data(); pan_t* const position = buffers[0]; @@ -233,30 +212,25 @@ Pannerbalance::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, return; } - for (pframes_t n = 0; n < nframes; ++n) { + dst = obufs.get_audio(which).data(); + + dist_buf[which]->update_session_config(); - float const pos = position[n]; - - if (which == 0) { // Left - if (pos > .5) { - buffers[which][n] = 2 - 2. * pos; - } else { - buffers[which][n] = 1.0; - } - } else { // Right - if (pos < .5) { - buffers[which][n] = 2. * pos; - } else { - buffers[which][n] = 1.0; - } + for (pframes_t n = 0; n < nframes; ++n) { + float pos = position[n]; + if (which == 0) { + pos = 1.0f - pos; } - } - dst = obufs.get_audio(which).data(); - pbuf = buffers[which]; + float gain; + if (pos < .5f) { + gain = 2.0f * pos; + } else { + gain = 1.0f; + } - for (pframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n] * pbuf[n]; + dist_buf[which]->set_pan_position(pos); + dst[n] += dist_buf[which]->process(src[n] * gain); } /* XXX it would be nice to mark the buffer as written to */ @@ -268,7 +242,7 @@ Pannerbalance::factory (boost::shared_ptr p, boost::shared_ptr); ~Pannerbalance (); @@ -64,14 +73,21 @@ class Pannerbalance : public Panner void reset (); void thaw (); - protected: - float pos[2]; - float desired_pos[2]; - float pos_interp[2]; + protected: + float gain[2]; + float desired_gain[2]; + + PanDistributionBuffer dist_buf_0; + PanDistributionBuffer dist_buf_1; + + /* Pointers to the two buffers arranged as an array, for convenience. + * (The members above are only needed because PanDistributionBuffer is not + * default-constructible.) */ + PanDistributionBuffer* dist_buf[2]; void update (); - private: + private: void distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which); void distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, framepos_t start, framepos_t end, pframes_t nframes, From 571e6159b8b8c222f1660d55a552cc2a017f8e0f Mon Sep 17 00:00:00 2001 From: Sebastian Reichelt Date: Fri, 10 Apr 2015 20:57:56 +0200 Subject: [PATCH 2/2] fixed error message when loading of panner fails --- libs/ardour/panner_manager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ardour/panner_manager.cc b/libs/ardour/panner_manager.cc index 906e979862a..e4e068c3d59 100644 --- a/libs/ardour/panner_manager.cc +++ b/libs/ardour/panner_manager.cc @@ -136,7 +136,7 @@ PannerManager::get_descriptor (string path) PanPluginDescriptor* (*dfunc)(void); void* func = 0; - if (!module) { + if (!(*module)) { error << string_compose(_("PannerManager: cannot load module \"%1\" (%2)"), path, Glib::Module::get_last_error()) << endmsg; delete module;