#include <cmath>
#include <cerrno>
-#include <fstream>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <locale.h>
#include <unistd.h>
#include <float.h>
-#include <iomanip>
#include <glibmm.h>
#include "ardour/runtime_functions.h"
#include "ardour/session.h"
#include "ardour/utils.h"
+#include "ardour/mix.h"
#include "panner_2in2out.h"
-#include "i18n.h"
+#include "pbd/i18n.h"
#include "pbd/mathfix.h"
static PanPluginDescriptor _descriptor = {
"Equal Power Stereo",
+ "http://ardour.org/plugin/panner_2in2out",
+ "http://ardour.org/plugin/panner_2in2out#ui",
2, 2,
+ 10000,
Panner2in2out::factory
};
-extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
+extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
: Panner (p)
{
- _pannable->pan_azimuth_control->set_value (0.5);
- _pannable->pan_width_control->set_value (1.0);
+ if (!_pannable->has_state()) {
+ _pannable->pan_azimuth_control->set_value (0.5, Controllable::NoGroup);
+ _pannable->pan_width_control->set_value (1.0, Controllable::NoGroup);
+ }
+
+ double const w = width();
+ double const wrange = min (position(), (1 - position())) * 2;
+ if (fabs(w) > wrange) {
+ set_width(w > 0 ? wrange : -wrange);
+ }
+
+
+ update ();
- /* LEFT SIGNAL, panned hard left */
- left[0] = 1.0;
- right[0] = 0.0;
- desired_left[0] = left_interp[0] = left[0];
- desired_right[0] = right_interp[0] = right[0];
+ /* LEFT SIGNAL */
+ left_interp[0] = left[0] = desired_left[0];
+ right_interp[0] = right[0] = desired_right[0];
- /* RIGHT SIGNAL, panned hard right */
- left[1] = 0;
- right[1] = 1.0;
- desired_left[1] = left_interp[1] = left[1];
- desired_right[1] = right_interp[1] = right[1];
+ /* RIGHT SIGNAL */
+ left_interp[1] = left[1] = desired_left[1];
+ right_interp[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));
{
}
-double
+double
Panner2in2out::position () const
{
return _pannable->pan_azimuth_control->get_value();
}
-double
+double
Panner2in2out::width () const
{
return _pannable->pan_width_control->get_value();
Panner2in2out::set_position (double p)
{
if (clamp_position (p)) {
- _pannable->pan_azimuth_control->set_value (p);
+ _pannable->pan_azimuth_control->set_value (p, Controllable::NoGroup);
}
}
Panner2in2out::set_width (double p)
{
if (clamp_width (p)) {
- _pannable->pan_width_control->set_value (p);
+ _pannable->pan_width_control->set_value (p, Controllable::NoGroup);
}
}
+void
+Panner2in2out::thaw ()
+{
+ Panner::thaw ();
+ if (_frozen == 0) {
+ update ();
+ }
+}
+
void
Panner2in2out::update ()
{
+ if (_frozen) {
+ return;
+ }
+
/* it would be very nice to split this out into a virtual function
that can be accessed from BaseStereoPanner and used in do_distribute_automated().
-
+
but the place where its used in do_distribute_automated() is a tight inner loop,
and making "nframes" virtual function calls to compute values is an absurd
overhead.
*/
-
+
/* x == 0 => hard left = 180.0 degrees
x == 1 => hard right = 0.0 degrees
*/
-
+
float pos[2];
- const double width = _pannable->pan_width_control->get_value();
- const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
+ double width = this->width ();
+ const double direction_as_lr_fract = position ();
- cerr << "new pan values width=" << width << " LR = " << direction_as_lr_fract << endl;
+ double const wrange = min (position(), (1 - position())) * 2;
+ if (fabs(width) > wrange) {
+ 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
}
-
+
/* 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);
-
+
/* right signal */
-
+
panR = pos[1];
panL = 1 - panR;
desired_left[1] = panL * (scale * panL + 1.0f - scale);
bool
Panner2in2out::clamp_position (double& p)
{
- double w = _pannable->pan_width_control->get_value();
+ double w = width ();
return clamp_stereo_pan (p, w);
}
bool
Panner2in2out::clamp_width (double& w)
{
- double p = _pannable->pan_azimuth_control->get_value();
+ double p = position ();
return clamp_stereo_pan (p, w);
}
+pair<double, double>
+Panner2in2out::position_range () const
+{
+ return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
+}
+
+pair<double, double>
+Panner2in2out::width_range () const
+{
+ double const w = min (position(), (1 - position())) * 2;
+ return make_pair (-w, w);
+}
+
bool
Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
{
- double r_pos = direction_as_lr_fract + (width/2.0);
- double l_pos = direction_as_lr_fract - (width/2.0);
- bool can_move_left = true;
- bool can_move_right = true;
+ double r_pos;
+ double l_pos;
- cerr << "Clamp pos = " << direction_as_lr_fract << " w = " << width << endl;
+ width = max (min (width, 1.0), -1.0);
+ direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
- if (width > 1.0 || width < 1.0) {
- return false;
- }
-
- if (direction_as_lr_fract > 1.0 || direction_as_lr_fract < 0.0) {
- return false;
- }
+ r_pos = direction_as_lr_fract + (width/2.0);
+ l_pos = direction_as_lr_fract - (width/2.0);
if (width < 0.0) {
swap (r_pos, l_pos);
}
/* if the new left position is less than or equal to zero (hard left) and the left panner
- is already there, we're not moving the left signal.
+ is already there, we're not moving the left signal.
*/
-
- if (l_pos <= 0.0 && desired_left[0] <= 0.0) {
- can_move_left = false;
+
+ if (l_pos < 0.0) {
+ return false;
}
/* if the new right position is less than or equal to 1.0 (hard right) and the right panner
- is already there, we're not moving the right signal.
+ is already there, we're not moving the right signal.
*/
-
- if (r_pos >= 1.0 && desired_right[1] >= 1.0) {
- can_move_right = false;
+
+ if (r_pos > 1.0) {
+ return false;
+
}
- return can_move_left && can_move_right;
+ return true;
}
void
pan_t pan;
Sample* const src = srcbuf.data();
-
+
/* LEFT OUTPUT */
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 */
+ interpolate over 64 samples or nframes, whichever is smaller */
pframes_t const limit = min ((pframes_t) 64, nframes);
pframes_t n;
/* 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 */
/* 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 */
}
}
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 */
+ interpolate over 64 samples or nframes, whichever is smaller */
pframes_t const limit = min ((pframes_t) 64, nframes);
pframes_t n;
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 */
void
Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
- framepos_t start, framepos_t end, pframes_t nframes,
+ samplepos_t start, samplepos_t end, pframes_t nframes,
pan_t** buffers, uint32_t which)
{
assert (obufs.count().n_audio() == 2);
float panR;
- if (which == 0) {
+ if (which == 0) {
// panning left signal
panR = position[n] - (width[n]/2.0f); // center - width/2
} else {
panR = position[n] + (width[n]/2.0f); // 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
+ 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);
}
}
Panner*
-Panner2in2out::factory (boost::shared_ptr<Pannable> p, Speakers& /* ignored */)
+Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
{
return new Panner2in2out (p);
}
XMLNode&
-Panner2in2out::get_state (void)
+Panner2in2out::get_state ()
{
- return state (true);
+ XMLNode& root (Panner::get_state ());
+ root.set_property (X_("uri"), _descriptor.panner_uri);
+ /* this is needed to allow new sessions to load with old Ardour: */
+ root.set_property (X_("type"), _descriptor.name);
+ return root;
}
-XMLNode&
-Panner2in2out::state (bool /*full_state*/)
+std::set<Evoral::Parameter>
+Panner2in2out::what_can_be_automated() const
{
- XMLNode& root (Panner::get_state ());
- root.add_property (X_("type"), _descriptor.name);
- return root;
+ set<Evoral::Parameter> s;
+ s.insert (Evoral::Parameter (PanAzimuthAutomation));
+ s.insert (Evoral::Parameter (PanWidthAutomation));
+ return s;
+}
+
+string
+Panner2in2out::describe_parameter (Evoral::Parameter p)
+{
+ switch (p.type()) {
+ case PanAzimuthAutomation:
+ return _("L/R");
+ case PanWidthAutomation:
+ return _("Width");
+ default:
+ return _pannable->describe_parameter (p);
+ }
}
-int
-Panner2in2out::set_state (const XMLNode& node, int version)
+string
+Panner2in2out::value_as_string (boost::shared_ptr<const AutomationControl> ac) const
{
- LocaleGuard lg (X_("POSIX"));
- Panner::set_state (node, version);
- return 0;
+ /* DO NOT USE LocaleGuard HERE */
+ double val = ac->get_value();
+
+ switch (ac->parameter().type()) {
+ case PanAzimuthAutomation:
+ /* We show the position of the center of the image relative to the left & right.
+ This is expressed as a pair of percentage values that ranges from (100,0)
+ (hard left) through (50,50) (hard center) to (0,100) (hard right).
+
+ This is pretty wierd, but its the way audio engineers expect it. Just remember that
+ the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
+
+ This is designed to be as narrow as possible. Dedicated
+ panner GUIs can do their own version of this if they need
+ something less compact.
+ */
+
+ return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
+ (int) rint (100.0 * val));
+
+ case PanWidthAutomation:
+ return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
+
+ default:
+ return _("unused");
+ }
}
+void
+Panner2in2out::reset ()
+{
+ set_position (0.5);
+ set_width (1);
+ update ();
+}