Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added feature to automatically apply a delay to panned channels #63

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions gtk2_ardour/session_option_editor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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")));
Expand Down
170 changes: 170 additions & 0 deletions libs/ardour/ardour/pan_delay_buffer.h
Original file line number Diff line number Diff line change
@@ -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 <cmath>

#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<PanDelayBufferImpl> PanDelayBuffer;

} // namespace

#endif /* __libardour_pan_delay_buffer_h__ */
130 changes: 130 additions & 0 deletions libs/ardour/ardour/pan_distribution_buffer.h
Original file line number Diff line number Diff line change
@@ -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 Impl>
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<DummyPanDistributionBufferImpl> DummyPanDistributionBuffer;

} // namespace

#endif /* __libardour_pan_distribution_buffer_h__ */
2 changes: 2 additions & 0 deletions libs/ardour/ardour/panner.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions libs/ardour/ardour/session_configuration_vars.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading