From cdace71a3808a75238bb7ce0df7308cf15cf22e3 Mon Sep 17 00:00:00 2001 From: Sebastian Reichelt Date: Thu, 27 Feb 2014 14:45:20 +0100 Subject: [PATCH] re-based delay-panner patch - closes #63 --- gtk2_ardour/session_option_editor.cc | 38 +++ gtk2_ardour/session_option_editor.h | 2 + libs/ardour/ardour/pan_delay_buffer.h | 120 ++++++++++ libs/ardour/ardour/pan_distribution_buffer.h | 115 +++++++++ libs/ardour/ardour/panner.h | 4 + libs/ardour/ardour/panner_manager.h | 2 +- libs/ardour/ardour/panner_shell.h | 2 + .../ardour/session_configuration_vars.h | 2 + libs/ardour/pan_delay_buffer.cc | 226 ++++++++++++++++++ libs/ardour/pan_distribution_buffer.cc | 91 +++++++ libs/ardour/panner_manager.cc | 14 +- libs/ardour/panner_shell.cc | 17 +- libs/ardour/wscript | 4 + libs/panners/1in2out/panner_1in2out.cc | 163 +++---------- libs/panners/1in2out/panner_1in2out.h | 7 +- libs/panners/1in2out/panner_1in2out_delay.cc | 56 +++++ libs/panners/1in2out/panner_1in2out_delay.h | 38 +++ libs/panners/1in2out/wscript | 11 + libs/panners/2in2out/panner_2in2out.cc | 201 ++++------------ libs/panners/2in2out/panner_2in2out.h | 17 +- libs/panners/2in2out/panner_2in2out_delay.cc | 58 +++++ libs/panners/2in2out/panner_2in2out_delay.h | 38 +++ libs/panners/2in2out/wscript | 11 + libs/panners/stereobalance/panner_balance.cc | 121 ++++------ libs/panners/stereobalance/panner_balance.h | 15 +- 25 files changed, 993 insertions(+), 380 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 create mode 100644 libs/panners/1in2out/panner_1in2out_delay.cc create mode 100644 libs/panners/1in2out/panner_1in2out_delay.h create mode 100644 libs/panners/2in2out/panner_2in2out_delay.cc create mode 100644 libs/panners/2in2out/panner_2in2out_delay.h diff --git a/gtk2_ardour/session_option_editor.cc b/gtk2_ardour/session_option_editor.cc index f243973013b..038ebe3c5ee 100644 --- a/gtk2_ardour/session_option_editor.cc +++ b/gtk2_ardour/session_option_editor.cc @@ -18,6 +18,8 @@ */ #include "ardour/session.h" +#include "ardour/route.h" +#include "ardour/panner_shell.h" #include "gui_thread.h" #include "session_option_editor.h" @@ -27,6 +29,7 @@ using namespace std; using namespace ARDOUR; using namespace Timecode; +using namespace Gtk; SessionOptionEditor::SessionOptionEditor (Session* s) : OptionEditor (&(s->config), _("Session Properties")) @@ -272,6 +275,24 @@ SessionOptionEditor::SessionOptionEditor (Session* s) sigc::mem_fun (*_session_config, &SessionConfiguration::set_glue_new_regions_to_bars_and_beats) )); + add_option (_("Misc"), new OptionEditorHeading (_("Panning"))); + + add_option (_("Misc"), new BoolOption ( + "use-delay-panners", + _("Use delay-based (Haas effect) panners by default"), + sigc::mem_fun (*_session_config, &SessionConfiguration::get_use_delay_panners), + sigc::mem_fun (*this, &SessionOptionEditor::set_use_delay_panners) + )); + + Gtk::Adjustment *panning_delay_adjustment = manage (new Gtk::Adjustment (0, 0, 10, .1, 1)); + add_option (_("Misc"), new HSliderOption ( + "panning-delay", + _("Applied delay in relation to\n 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) + )); + add_option (_("Meterbridge"), new OptionEditorHeading (_("Route Display"))); add_option (_("Meterbridge"), new BoolOption ( @@ -381,3 +402,20 @@ SessionOptionEditor::get_use_monitor_section () { return _session->monitor_out() != 0; } + +bool +SessionOptionEditor::set_use_delay_panners (bool yn) +{ + bool changed = _session_config->set_use_delay_panners (yn); + + string txt = _("Would you like to reset all selectable panners to their new default?"); + MessageDialog msg (txt, false, MESSAGE_QUESTION, BUTTONS_YES_NO, true); + if (msg.run() == RESPONSE_YES) { + boost::shared_ptr routes = _session->get_routes (); + for (RouteList::const_iterator i = routes->begin(); i != routes->end(); ++i) { + (*i)->panner_shell()->select_default_panner (); + } + } + + return changed; +} diff --git a/gtk2_ardour/session_option_editor.h b/gtk2_ardour/session_option_editor.h index 73b9b2b0336..80e74f3ce80 100644 --- a/gtk2_ardour/session_option_editor.h +++ b/gtk2_ardour/session_option_editor.h @@ -37,5 +37,7 @@ class SessionOptionEditor : public OptionEditor bool set_use_monitor_section (bool); bool get_use_monitor_section (); + bool set_use_delay_panners (bool); + ComboOption* _vpu; }; diff --git a/libs/ardour/ardour/pan_delay_buffer.h b/libs/ardour/ardour/pan_delay_buffer.h new file mode 100644 index 00000000000..02142cfa0b2 --- /dev/null +++ b/libs/ardour/ardour/pan_delay_buffer.h @@ -0,0 +1,120 @@ +/* + 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. + */ +class PanDelayBuffer : public PanDistributionBuffer, public SessionHandleRef +{ + public: + PanDelayBuffer(Session &s); + virtual ~PanDelayBuffer(); + + /* Overridden to update _session_delay_coeff according to the delay + * specified in the session configuration. */ + virtual void update_session_config(); + + protected: + /* Overridden to update the delay according to the given panner + * position. */ + virtual void do_set_pan_position(float pan_position); + + /* Overridden to append the @a input sample to the delay buffer and + * remove and returns the oldest sample in the buffer. */ + virtual Sample do_process(Sample input); + + /* Overridden to honor delay. */ + virtual void do_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; + + /* Updates _session_delay_coeff and _active. */ + void update_session_delay_coeff(); + + /* Called by do_process() if _interp_active is true. */ + Sample interpolate(Sample input); +}; + +} // 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..c64460cc146 --- /dev/null +++ b/libs/ardour/ardour/pan_distribution_buffer.h @@ -0,0 +1,115 @@ +/* + 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 { + +/** Helper class for panners to manage distribution of signals to outputs. + * + * The only method in this class that actually does something interesting + * is mix_buffers(). All others exist purely to be overridden by subclasses. + * (There is currently just one subclass called PanDelayBuffer.) + * + * 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 two helper methods + * set_pan_position_and_process() and mix_buffers() can be used instead. + * + * Since set_pan_position() and process() are potentially called for each + * sample, their most common case is inlined. Subclasses can make sure that + * this inlined code is used by setting _active to false. + * + * For more information, see pan_delay_buffer.h. + */ +class PanDistributionBuffer +{ + public: + PanDistributionBuffer(); + virtual ~PanDistributionBuffer(); + + /** Updates internal data according to the session configuration. */ + virtual void 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) + { + if (_active) { + do_set_pan_position(pan_position); + } + } + + /** Processes one sample, and returns the sample that should actually + * be output. */ + Sample process(Sample input) + { + if (_active) { + return do_process(input); + } else { + return input; + } + } + + /** Same as set_pan_position() followed by process(). */ + Sample set_pan_position_and_process(float pan_position, Sample input) + { + if (_active) { + do_set_pan_position(pan_position); + return do_process(input); + } else { + return 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. + * + * 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); + + protected: + /* If this is false, do_set_pan_position() and do_process() are assumed + * to be no-ops and are therefore skipped. Must be set by subclasses. */ + bool _active; + + virtual void do_set_pan_position(float pan_position); + virtual Sample do_process(Sample input); + virtual void do_mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float gain); + + private: + /* Maximum number of frames to interpolate between gains (used by + * mix_buffers(); must be a multiple of 16). */ + static const pframes_t _gain_interp_frames = 64; +}; + +} // namespace + +#endif /* __libardour_pan_distribution_buffer_h__ */ diff --git a/libs/ardour/ardour/panner.h b/libs/ardour/ardour/panner.h index 18e3e8045a5..6ec4bdefad5 100644 --- a/libs/ardour/ardour/panner.h +++ b/libs/ardour/ardour/panner.h @@ -170,6 +170,8 @@ class Panner : public PBD::Stateful, public PBD::ScopedConnectionList 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 @@ -183,6 +185,8 @@ struct PanPluginDescriptor { int32_t out; uint32_t priority; ARDOUR::Panner* (*factory)(boost::shared_ptr, boost::shared_ptr); + + static const uint32_t priority_delay_flag = 0x80000000; }; } diff --git a/libs/ardour/ardour/panner_manager.h b/libs/ardour/ardour/panner_manager.h index 4a29e1c36b5..b18bb133402 100644 --- a/libs/ardour/ardour/panner_manager.h +++ b/libs/ardour/ardour/panner_manager.h @@ -51,7 +51,7 @@ class PannerManager : public ARDOUR::SessionHandlePtr void discover_panners (); std::list panner_info; - PannerInfo* select_panner (ChanCount in, ChanCount out, std::string const uri = ""); + PannerInfo* select_panner (ChanCount in, ChanCount out, bool use_delay_panners, std::string const uri = ""); PannerInfo* get_by_uri (std::string uri) const; PannerUriMap get_available_panners(uint32_t const a_in, uint32_t const a_out) const; diff --git a/libs/ardour/ardour/panner_shell.h b/libs/ardour/ardour/panner_shell.h index da4e6e3bee2..c1db85ff271 100644 --- a/libs/ardour/ardour/panner_shell.h +++ b/libs/ardour/ardour/panner_shell.h @@ -83,10 +83,12 @@ class PannerShell : public SessionObject /* this function takes the process lock: */ bool select_panner_by_uri (std::string const uri); + void select_default_panner (); private: void distribute_no_automation (BufferSet& src, BufferSet& dest, pframes_t nframes, gain_t gain_coeff); bool set_user_selected_panner_uri (std::string const uri); + void reselect_panner (); boost::shared_ptr _panner; diff --git a/libs/ardour/ardour/session_configuration_vars.h b/libs/ardour/ardour/session_configuration_vars.h index 6349692e774..6b829851a9f 100644 --- a/libs/ardour/ardour/session_configuration_vars.h +++ b/libs/ardour/ardour/session_configuration_vars.h @@ -57,6 +57,8 @@ CONFIG_VARIABLE (bool, midi_copy_is_fork, "midi-copy-is-fork", false) CONFIG_VARIABLE (bool, glue_new_regions_to_bars_and_beats, "glue-new-regions-to-bars-and-beats", false) CONFIG_VARIABLE (bool, use_video_file_fps, "use-video-file-fps", false) CONFIG_VARIABLE (bool, videotimeline_pullup, "videotimeline-pullup", true) +CONFIG_VARIABLE (bool, use_delay_panners, "use-delay-panners", false) +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..aaec6a9604a --- /dev/null +++ b/libs/ardour/pan_delay_buffer.cc @@ -0,0 +1,226 @@ +/* + 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/runtime_functions.h" +#include "ardour/session.h" + +using namespace std; +using namespace ARDOUR; + +PanDelayBuffer::PanDelayBuffer(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_delay_coeff(); +} + +PanDelayBuffer::~PanDelayBuffer() +{ + delete [] _buffer; +} + +void +PanDelayBuffer::update_session_config() +{ + update_session_delay_coeff(); + + if (!_active) { + /* client can skip calls to process() in this case, so make sure that _samples_processed gets set correctly (more or less) */ + _samples_processed = true; + } +} + +void +PanDelayBuffer::update_session_delay_coeff() +{ + _session_delay_coeff = _session.config.get_panning_delay() * _session.frame_rate() * 0.001f; + + if (_session_delay_coeff > 0.0f) { + _active = true; + } else if (_desired_delay == 0 && !_interp_active) { + /* only set _active to false if no further interpolation is necessary; otherwise it will be set to false by interpolate() */ + _active = false; + } +} + +void +PanDelayBuffer::do_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; + } +} + +Sample +PanDelayBuffer::do_process(Sample input) +{ + _samples_processed = true; + + Sample result; + if (_interp_active) { + /* interpolating between integer delays; continue in separate function */ + 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; +} + +Sample +PanDelayBuffer::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; + if (_session_delay_coeff <= 0.0f) { + /* see update_session_config() */ + _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 +PanDelayBuffer::do_mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float gain) +{ + if (_desired_delay == 0 && !_interp_active) { + /* fast path: no delay */ + /* (no need to check for _active since !_active implies _desired_delay == 0 and !_interp_active) */ + + PanDistributionBuffer::do_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); + } + } + } + + _samples_processed = true; +} diff --git a/libs/ardour/pan_distribution_buffer.cc b/libs/ardour/pan_distribution_buffer.cc new file mode 100644 index 00000000000..a91b83646c8 --- /dev/null +++ b/libs/ardour/pan_distribution_buffer.cc @@ -0,0 +1,91 @@ +/* + 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 std; +using namespace ARDOUR; + +PanDistributionBuffer::PanDistributionBuffer() + : _active(false) +{ +} + +PanDistributionBuffer::~PanDistributionBuffer() +{ +} + +void +PanDistributionBuffer::update_session_config() +{ +} + +void +PanDistributionBuffer::do_set_pan_position(float pan_position) +{ +} + +Sample +PanDistributionBuffer::do_process(Sample input) +{ + return input; +} + +void +PanDistributionBuffer::mix_buffers(Sample *dst, const Sample *src, pframes_t nframes, float prev_gain, float gain) +{ + if (nframes == 0) { + return; + } + + if (gain == prev_gain) { + do_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 = 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] += process(src[n] * current_gain); + } + + if (n < nframes) { + do_mix_buffers(dst + n, src + n, nframes - n, gain); + } + } +} + +void +PanDistributionBuffer::do_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/panner_manager.cc b/libs/ardour/panner_manager.cc index ec5b6757313..ee5a6345744 100644 --- a/libs/ardour/panner_manager.cc +++ b/libs/ardour/panner_manager.cc @@ -144,7 +144,7 @@ PannerManager::get_descriptor (string path) } PannerInfo* -PannerManager::select_panner (ChanCount in, ChanCount out, std::string const uri) +PannerManager::select_panner (ChanCount in, ChanCount out, bool use_delay_panners, std::string const uri) { PannerInfo* rv = NULL; PanPluginDescriptor* d; @@ -166,6 +166,9 @@ PannerManager::select_panner (ChanCount in, ChanCount out, std::string const uri for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { d = &(*p)->descriptor; + if ((d->priority & PanPluginDescriptor::priority_delay_flag) && !use_delay_panners) { + continue; + } if (d->in == nin && d->out == nout && d->priority > priority) { priority = d->priority; rv = *p; @@ -179,6 +182,9 @@ PannerManager::select_panner (ChanCount in, ChanCount out, std::string const uri for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { d = &(*p)->descriptor; + if ((d->priority & PanPluginDescriptor::priority_delay_flag) && !use_delay_panners) { + continue; + } if (d->in == nin && d->out == -1 && d->priority > priority) { priority = d->priority; rv = *p; @@ -192,6 +198,9 @@ PannerManager::select_panner (ChanCount in, ChanCount out, std::string const uri for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { d = &(*p)->descriptor; + if ((d->priority & PanPluginDescriptor::priority_delay_flag) && !use_delay_panners) { + continue; + } if (d->in == -1 && d->out == nout && d->priority > priority) { priority = d->priority; rv = *p; @@ -205,6 +214,9 @@ PannerManager::select_panner (ChanCount in, ChanCount out, std::string const uri for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { d = &(*p)->descriptor; + if ((d->priority & PanPluginDescriptor::priority_delay_flag) && !use_delay_panners) { + continue; + } if (d->in == -1 && d->out == -1 && d->priority > priority) { priority = d->priority; rv = *p; diff --git a/libs/ardour/panner_shell.cc b/libs/ardour/panner_shell.cc index 75543433c51..7ce2b5eaed1 100644 --- a/libs/ardour/panner_shell.cc +++ b/libs/ardour/panner_shell.cc @@ -117,7 +117,7 @@ PannerShell::configure_io (ChanCount in, ChanCount out) return; } - PannerInfo* pi = PannerManager::instance().select_panner (in, out, _user_selected_panner_uri); + PannerInfo* pi = PannerManager::instance().select_panner (in, out, _session.config.get_use_delay_panners(), _user_selected_panner_uri); if (!pi) { cerr << "No panner found: check that panners are being discovered correctly during startup.\n"; assert (pi); @@ -437,6 +437,20 @@ PannerShell::select_panner_by_uri (std::string const uri) if (uri == _user_selected_panner_uri) return false; _user_selected_panner_uri = uri; if (uri == _current_panner_uri) return false; + reselect_panner(); + return true; +} + +void +PannerShell::select_default_panner () +{ + _user_selected_panner_uri.clear(); + reselect_panner(); +} + +void +PannerShell::reselect_panner () +{ _force_reselect = true; if (_panner) { Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock ()); @@ -448,7 +462,6 @@ PannerShell::select_panner_by_uri (std::string const uri) } _session.set_dirty (); } - return true; } void diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 608bbf19919..f8bd1d5b100 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', @@ -295,6 +297,8 @@ def configure(conf): conf.define('CURRENT_SESSION_FILE_VERSION', CURRENT_SESSION_FILE_VERSION) + conf.define('BOOST_CB_DISABLE_DEBUG', 1) + conf.check(header_name='sys/vfs.h', define_name='HAVE_SYS_VFS_H',mandatory=False) conf.check(header_name='sys/statvfs.h', define_name='HAVE_SYS_STATVFS_H',mandatory=False) diff --git a/libs/panners/1in2out/panner_1in2out.cc b/libs/panners/1in2out/panner_1in2out.cc index 4dd21493e6a..1290e8f281a 100644 --- a/libs/panners/1in2out/panner_1in2out.cc +++ b/libs/panners/1in2out/panner_1in2out.cc @@ -47,10 +47,10 @@ #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" +#include "ardour/pan_distribution_buffer.h" #include "i18n.h" #include "panner_1in2out.h" @@ -75,6 +75,11 @@ extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } Panner1in2out::Panner1in2out (boost::shared_ptr p) : Panner (p) { + // by default, a single "buffer" is enough, because PanDistributionBuffer itself doesn't have any state + boost::shared_ptr default_dist_buf(new PanDistributionBuffer()); + left_dist_buf = default_dist_buf; + right_dist_buf = default_dist_buf; + if (!_pannable->has_state()) { _pannable->pan_azimuth_control->set_value (0.5); } @@ -83,8 +88,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)); } @@ -97,14 +100,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 @@ -141,9 +142,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(); @@ -151,111 +150,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 = desired_left; - left_interp = left; - - if ((pan = (left * gain_coeff)) != 1.0f) { - - if (pan != 0.0f) { + left_dist_buf->update_session_config(); + left_dist_buf->set_pan_position(1.0 - position()); - /* pan is 1 but also not 0, so we must do it "properly" */ + left_dist_buf->mix_buffers(dst, src, nframes, left * gain_coeff, desired_left * gain_coeff); - mix_buffers_with_gain(dst,src,nframes,pan); + left = desired_left; - /* 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; - } - - /* then pan the rest of the buffer, no need for interpolation for this bit */ - - pan = right * gain_coeff; + right_dist_buf->update_session_config(); + right_dist_buf->set_pan_position(position()); - mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); + right_dist_buf->mix_buffers(dst, src, nframes, right * gain_coeff, desired_right * gain_coeff); - /* 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 @@ -266,7 +181,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]; @@ -278,36 +192,16 @@ 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]; + + dst[n] += left_dist_buf->set_pan_position_and_process(panL, src[n] * panL * (_pan_law_scale * panL + 1.0f - _pan_law_scale)); } /* XXX it would be nice to mark the buffer as written to */ @@ -315,10 +209,13 @@ 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]; + + dst[n] += right_dist_buf->set_pan_position_and_process(panR, 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 97d23495c71..94c406a5847 100644 --- a/libs/panners/1in2out/panner_1in2out.h +++ b/libs/panners/1in2out/panner_1in2out.h @@ -35,6 +35,8 @@ namespace ARDOUR { +class PanDistributionBuffer; + class Panner1in2out : public Panner { public: @@ -66,8 +68,9 @@ class Panner1in2out : public Panner float right; float desired_left; float desired_right; - float left_interp; - float right_interp; + + boost::shared_ptr left_dist_buf; + boost::shared_ptr 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/panner_1in2out_delay.cc b/libs/panners/1in2out/panner_1in2out_delay.cc new file mode 100644 index 00000000000..3049cc72009 --- /dev/null +++ b/libs/panners/1in2out/panner_1in2out_delay.cc @@ -0,0 +1,56 @@ +/* + 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 "panner_1in2out_delay.h" + +#include "ardour/pannable.h" +#include "ardour/pan_delay_buffer.h" + +using namespace std; +using namespace ARDOUR; + +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, + 10000 | PanPluginDescriptor::priority_delay_flag, + Panner1in2outDelay::factory +}; + +extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } } + +Panner1in2outDelay::Panner1in2outDelay (boost::shared_ptr p) + : Panner1in2out (p) +{ + Session& session = p->session(); + + left_dist_buf.reset(new PanDelayBuffer(session)); + right_dist_buf.reset(new PanDelayBuffer(session)); +} + +Panner1in2outDelay::~Panner1in2outDelay () +{ +} + +Panner* +Panner1in2outDelay::factory (boost::shared_ptr p, boost::shared_ptr /* ignored */) +{ + return new Panner1in2outDelay (p); +} diff --git a/libs/panners/1in2out/panner_1in2out_delay.h b/libs/panners/1in2out/panner_1in2out_delay.h new file mode 100644 index 00000000000..cc104b1110f --- /dev/null +++ b/libs/panners/1in2out/panner_1in2out_delay.h @@ -0,0 +1,38 @@ +/* + 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 __ardour_panner_1in2out_delay_h__ +#define __ardour_panner_1in2out_delay_h__ + +#include "panner_1in2out.h" + +namespace ARDOUR { + +class Panner1in2outDelay : public Panner1in2out +{ + public: + Panner1in2outDelay (boost::shared_ptr); + ~Panner1in2outDelay (); + + static Panner* factory (boost::shared_ptr, boost::shared_ptr); +}; + +} // namespace + +#endif /* __ardour_panner_1in2out_delay_h__ */ diff --git a/libs/panners/1in2out/wscript b/libs/panners/1in2out/wscript index 70c34d0779d..0420631a1dc 100644 --- a/libs/panners/1in2out/wscript +++ b/libs/panners/1in2out/wscript @@ -30,5 +30,16 @@ def build(bld): obj.vnum = LIBARDOUR_PAN1IN2OUT_LIB_VERSION obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'panners') + obj = bld(features = 'cxx cxxshlib') + obj.source = [ 'panner_1in2out_delay.cc' ] + obj.export_includes = ['.'] + obj.cxxflags = '-DPACKAGE="libardour_pan1in2out_delay"' + obj.includes = ['.'] + obj.name = 'libardour_pan1in2out_delay' + obj.target = 'pan1in2out_delay' + obj.use = 'libardour libardour_cp libardour_pan1in2out libpbd' + obj.vnum = LIBARDOUR_PAN1IN2OUT_LIB_VERSION + obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'panners') + def shutdown(): autowaf.shutdown() diff --git a/libs/panners/2in2out/panner_2in2out.cc b/libs/panners/2in2out/panner_2in2out.cc index 25ea1c401a1..55c8e25d762 100644 --- a/libs/panners/2in2out/panner_2in2out.cc +++ b/libs/panners/2in2out/panner_2in2out.cc @@ -45,8 +45,8 @@ #include "ardour/audio_buffer.h" #include "ardour/buffer_set.h" #include "ardour/pan_controllable.h" +#include "ardour/pan_distribution_buffer.h" #include "ardour/pannable.h" -#include "ardour/runtime_functions.h" #include "ardour/session.h" #include "ardour/utils.h" #include "ardour/mix.h" @@ -75,6 +75,13 @@ extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } Panner2in2out::Panner2in2out (boost::shared_ptr p) : Panner (p) { + // by default, a single "buffer" is enough, because PanDistributionBuffer itself doesn't have any state + boost::shared_ptr dist_buf(new PanDistributionBuffer()); + left_dist_buf[0] = dist_buf; + left_dist_buf[1] = dist_buf; + right_dist_buf[0] = dist_buf; + right_dist_buf[1] = dist_buf; + if (!_pannable->has_state()) { _pannable->pan_azimuth_control->set_value (0.5); _pannable->pan_width_control->set_value (1.0); @@ -90,12 +97,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 +177,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 +236,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 +268,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 +276,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[which] = desired_left[which]; - left_interp[which] = left[which]; - - if ((pan = (left[which] * gain_coeff)) != 1.0f) { - - if (pan != 0.0f) { + left_dist_buf[which]->update_session_config(); + left_dist_buf[which]->set_pan_position(1.0 - position()); - /* 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); + left_dist_buf[which]->mix_buffers(dst, src, nframes, left[which] * gain_coeff, desired_left[which] * gain_coeff); - /* mark that we wrote into the buffer */ + left[which] = desired_left[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 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); - - /* XXX it would be nice to mark the buffer as written to */ - - } else { + right_dist_buf[which]->update_session_config(); + right_dist_buf[which]->set_pan_position(position()); - right[which] = desired_right[which]; - right_interp[which] = right[which]; + right_dist_buf[which]->mix_buffers(dst, src, nframes, right[which] * gain_coeff, desired_right[which] * gain_coeff); - if ((pan = (right[which] * gain_coeff)) != 1.0f) { + right[which] = desired_right[which]; - 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 +307,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 +325,28 @@ 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 */ + + dst = obufs.get_audio(0).data(); - const float pan_law_attenuation = -3.0f; - const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); + 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]; + dst[n] += left_dist_buf[which]->set_pan_position_and_process(1.0f - position[n], 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 +354,21 @@ 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 + } + + dst[n] += right_dist_buf[which]->set_pan_position_and_process(position[n], 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..45d379fc6d7 100644 --- a/libs/panners/2in2out/panner_2in2out.h +++ b/libs/panners/2in2out/panner_2in2out.h @@ -30,13 +30,13 @@ #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" namespace ARDOUR { +class PanDistributionBuffer; + class Panner2in2out : public Panner { public: @@ -58,10 +58,10 @@ class Panner2in2out : public Panner double position () const; double width () const; - std::set what_can_be_automated() const; - static Panner* factory (boost::shared_ptr, boost::shared_ptr); + std::set what_can_be_automated() const; + std::string describe_parameter (Evoral::Parameter); std::string value_as_string (boost::shared_ptr) const; @@ -77,8 +77,11 @@ 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. */ + boost::shared_ptr left_dist_buf[2]; + boost::shared_ptr right_dist_buf[2]; private: bool clamp_stereo_pan (double& direction_as_lr_fract, double& width); diff --git a/libs/panners/2in2out/panner_2in2out_delay.cc b/libs/panners/2in2out/panner_2in2out_delay.cc new file mode 100644 index 00000000000..a47b74599ef --- /dev/null +++ b/libs/panners/2in2out/panner_2in2out_delay.cc @@ -0,0 +1,58 @@ +/* + 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 "panner_2in2out_delay.h" + +#include "ardour/pannable.h" +#include "ardour/pan_delay_buffer.h" + +using namespace std; +using namespace ARDOUR; + +static PanPluginDescriptor _descriptor = { + "Equal Power Stereo with Delay", + "http://ardour.org/plugin/panner_2in2out_delay", + "http://ardour.org/plugin/panner_2in2out#ui", + 2, 2, + 10000 | PanPluginDescriptor::priority_delay_flag, + Panner2in2outDelay::factory +}; + +extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } } + +Panner2in2outDelay::Panner2in2outDelay (boost::shared_ptr p) + : Panner2in2out (p) +{ + Session& session = p->session(); + + left_dist_buf[0].reset(new PanDelayBuffer(session)); + left_dist_buf[1].reset(new PanDelayBuffer(session)); + right_dist_buf[0].reset(new PanDelayBuffer(session)); + right_dist_buf[1].reset(new PanDelayBuffer(session)); +} + +Panner2in2outDelay::~Panner2in2outDelay () +{ +} + +Panner* +Panner2in2outDelay::factory (boost::shared_ptr p, boost::shared_ptr /* ignored */) +{ + return new Panner2in2outDelay (p); +} diff --git a/libs/panners/2in2out/panner_2in2out_delay.h b/libs/panners/2in2out/panner_2in2out_delay.h new file mode 100644 index 00000000000..283b70326ba --- /dev/null +++ b/libs/panners/2in2out/panner_2in2out_delay.h @@ -0,0 +1,38 @@ +/* + 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 __ardour_panner_2in2out_delay_h__ +#define __ardour_panner_2in2out_delay_h__ + +#include "panner_2in2out.h" + +namespace ARDOUR { + +class Panner2in2outDelay : public Panner2in2out +{ + public: + Panner2in2outDelay (boost::shared_ptr); + ~Panner2in2outDelay (); + + static Panner* factory (boost::shared_ptr, boost::shared_ptr); +}; + +} // namespace + +#endif /* __ardour_panner_2in2out_delay_h__ */ diff --git a/libs/panners/2in2out/wscript b/libs/panners/2in2out/wscript index 8288ef28f7a..185c2a8e6ea 100644 --- a/libs/panners/2in2out/wscript +++ b/libs/panners/2in2out/wscript @@ -30,5 +30,16 @@ def build(bld): obj.vnum = LIBARDOUR_PAN2IN2OUT_LIB_VERSION obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'panners') + obj = bld(features = 'cxx cxxshlib') + obj.source = [ 'panner_2in2out_delay.cc' ] + obj.export_includes = ['.'] + obj.cxxflags = '-DPACKAGE="libardour_pan2in2out_delay"' + obj.includes = ['.'] + obj.name = 'libardour_pan2in2out_delay' + obj.target = 'pan2in2out_delay' + obj.use = 'libardour libardour_cp libardour_pan2in2out libpbd' + obj.vnum = LIBARDOUR_PAN2IN2OUT_LIB_VERSION + obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'panners') + def shutdown(): autowaf.shutdown() diff --git a/libs/panners/stereobalance/panner_balance.cc b/libs/panners/stereobalance/panner_balance.cc index b7d8a2a0319..c12ed6da2c4 100644 --- a/libs/panners/stereobalance/panner_balance.cc +++ b/libs/panners/stereobalance/panner_balance.cc @@ -46,6 +46,7 @@ #include "ardour/audio_buffer.h" #include "ardour/buffer_set.h" #include "ardour/pan_controllable.h" +#include "ardour/pan_distribution_buffer.h" #include "ardour/pannable.h" #include "ardour/runtime_functions.h" #include "ardour/session.h" @@ -76,6 +77,11 @@ extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } Pannerbalance::Pannerbalance (boost::shared_ptr p) : Panner (p) { + // by default, a single "buffer" is enough, because PanDistributionBuffer itself doesn't have any state + boost::shared_ptr default_dist_buf(new PanDistributionBuffer()); + dist_buf[0] = default_dist_buf; + dist_buf[1] = default_dist_buf; + if (!_pannable->has_state()) { _pannable->pan_azimuth_control->set_value (0.5); } @@ -83,9 +89,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 +106,7 @@ Pannerbalance::position () const return _pannable->pan_azimuth_control->get_value(); } - void +void Pannerbalance::set_position (double p) { if (clamp_position (p)) { @@ -108,7 +114,7 @@ Pannerbalance::set_position (double p) } } - void +void Pannerbalance::thaw () { Panner::thaw (); @@ -124,17 +130,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 +162,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) { + dist_buf[which]->update_session_config(); + dist_buf[which]->set_pan_position(pos); - if (pan != 0.0f) { + dist_buf[which]->mix_buffers(dst, src, nframes, gain[which] * gain_coeff, desired_gain[which] * gain_coeff); - /* pan is 1 but also not 0, so we must do it "properly" */ + gain[which] = desired_gain[which]; - //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 the buffer as written to, depending on gain (see pan_distribution_buffer.cc) */ } void @@ -221,7 +191,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 +202,24 @@ 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]; + dst[n] += dist_buf[which]->set_pan_position_and_process(pos, src[n] * gain); } /* XXX it would be nice to mark the buffer as written to */ @@ -268,7 +231,7 @@ Pannerbalance::factory (boost::shared_ptr p, boost::shared_ptr); ~Pannerbalance (); @@ -64,14 +66,15 @@ 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]; + + boost::shared_ptr 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,