X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fpanner.cc;h=d3ce72679a6513d6898d0776e84d265c859e7291;hb=f938687f8798d094c99cd4308ad6aa1c467e4a97;hp=deb131e6fcd005c2c8a8252e3b81fbbc45b81831;hpb=c268314b64c2235b0d69c3854e303accd2cad4d9;p=ardour.git diff --git a/libs/ardour/panner.cc b/libs/ardour/panner.cc index deb131e6fc..d3ce72679a 100644 --- a/libs/ardour/panner.cc +++ b/libs/ardour/panner.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2004 Paul Davis + Copyright (C) 2004 Paul Davis 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 @@ -17,6 +17,9 @@ */ +#define __STDC_FORMAT_MACROS 1 +#include + #include #include #include @@ -29,20 +32,25 @@ #include -#include -#include -#include -#include +#include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/xml++.h" +#include "pbd/enumwriter.h" + +#include "evoral/Curve.hpp" -#include -#include -#include +#include "ardour/session.h" +#include "ardour/panner.h" +#include "ardour/utils.h" +#include "ardour/audio_buffer.h" -#include +#include "ardour/runtime_functions.h" +#include "ardour/buffer_set.h" +#include "ardour/audio_buffer.h" #include "i18n.h" -#include +#include "pbd/mathfix.h" using namespace std; using namespace ARDOUR; @@ -53,29 +61,29 @@ float Panner::current_automation_version_number = 1.0; string EqualPowerStereoPanner::name = "Equal Power Stereo"; string Multi2dPanner::name = "Multiple (2D)"; -/* this is a default mapper of control values to a pan position - others can be imagined. +/* this is a default mapper of control values to a pan position + others can be imagined. */ -static pan_t direct_control_to_pan (double fract) { +static pan_t direct_control_to_pan (double fract) +{ return fract; } -static double direct_pan_to_control (pan_t val) { - return val; -} - -StreamPanner::StreamPanner (Panner& p) - : parent (p), - _control (X_("panner"), *this) +StreamPanner::StreamPanner (Panner& p, Evoral::Parameter param) + : parent (p) { + assert (param.type() != NullAutomation); + _muted = false; + _mono = false; - parent.session().add_controllable (&_control); + /* get our AutomationControl from our parent Panner, creating it if required */ + _control = boost::dynamic_pointer_cast (parent.control (param, true)); - x = 0.5; - y = 0.5; - z = 0.5; + _x = 0.5; + _y = 0.5; + _z = 0.5; } StreamPanner::~StreamPanner () @@ -83,29 +91,25 @@ StreamPanner::~StreamPanner () } void -StreamPanner::PanControllable::set_value (float val) +StreamPanner::set_mono (bool yn) { - panner.set_position (direct_control_to_pan (val)); + if (yn != _mono) { + _mono = yn; + StateChanged (); + } } -float -StreamPanner::PanControllable::get_value (void) const +void +Panner::PanControllable::set_value (float val) { - float xpos; - panner.get_effective_position (xpos); - return direct_pan_to_control (xpos); + panner.streampanner (parameter().id()).set_position (direct_control_to_pan (val)); + AutomationControl::set_value(val); } -bool -StreamPanner::PanControllable::can_send_feedback () const +float +Panner::PanControllable::get_value (void) const { - AutoState astate = panner.get_parent().automation_state (); - - if ((astate == Play) || (astate == Touch && !panner.get_parent().touching())) { - return true; - } - - return false; + return AutomationControl::get_value(); } void @@ -124,11 +128,11 @@ StreamPanner::set_position (float xpos, bool link_call) parent.set_position (xpos, *this); } - if (x != xpos) { - x = xpos; + if (_x != xpos) { + _x = xpos; update (); Changed (); - _control.Changed (); + _control->Changed (); } } @@ -139,10 +143,9 @@ StreamPanner::set_position (float xpos, float ypos, bool link_call) parent.set_position (xpos, ypos, *this); } - if (x != xpos || y != ypos) { - - x = xpos; - y = ypos; + if (_x != xpos || _y != ypos) { + _x = xpos; + _y = ypos; update (); Changed (); } @@ -155,25 +158,29 @@ StreamPanner::set_position (float xpos, float ypos, float zpos, bool link_call) parent.set_position (xpos, ypos, zpos, *this); } - if (x != xpos || y != ypos || z != zpos) { - x = xpos; - y = ypos; - z = zpos; + if (_x != xpos || _y != ypos || _z != zpos) { + _x = xpos; + _y = ypos; + _z = zpos; update (); Changed (); } } int -StreamPanner::set_state (const XMLNode& node) +StreamPanner::set_state (const XMLNode& node, int /*version*/) { const XMLProperty* prop; XMLNodeConstIterator iter; if ((prop = node.property (X_("muted")))) { - set_muted (prop->value() == "yes"); + set_muted (string_is_affirmative (prop->value())); } + if ((prop = node.property (X_("mono")))) { + set_mono (string_is_affirmative (prop->value())); + } + return 0; } @@ -181,54 +188,51 @@ void StreamPanner::add_state (XMLNode& node) { node.add_property (X_("muted"), (muted() ? "yes" : "no")); -} - -/*---------------------------------------------------------------------- */ - -BaseStereoPanner::BaseStereoPanner (Panner& p) - : StreamPanner (p), _automation (0.0, 1.0, 0.5) -{ -} - -BaseStereoPanner::~BaseStereoPanner () -{ + node.add_property (X_("mono"), (_mono ? "yes" : "no")); } void -BaseStereoPanner::snapshot (nframes_t now) +StreamPanner::distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes) { - if (_automation.automation_state() == Write || _automation.automation_state() == Touch) { - _automation.rt_add (now, x); + if (_mono) { + /* we're in mono mode, so just pan the input to all outputs equally */ + int const N = parent.nouts (); + for (int i = 0; i < N; ++i) { + mix_buffers_with_gain (obufs.get_audio(i).data(), src.data(), nframes, gain_coeff); + } + } else { + /* normal mode, call the `real' distribute method */ + do_distribute (src, obufs, gain_coeff, nframes); } } void -BaseStereoPanner::transport_stopped (nframes_t frame) -{ - _automation.reposition_for_rt_add (frame); - - if (_automation.automation_state() != Off) { - set_position (_automation.eval (frame)); +StreamPanner::distribute_automated (AudioBuffer& src, BufferSet& obufs, + nframes_t start, nframes_t end, nframes_t nframes, pan_t** buffers) +{ + if (_mono) { + /* we're in mono mode, so just pan the input to all outputs equally */ + int const N = parent.nouts (); + for (int i = 0; i < N; ++i) { + mix_buffers_with_gain (obufs.get_audio(i).data(), src.data(), nframes, 1.0); + } + } else { + /* normal mode, call the `real' distribute method */ + do_distribute_automated (src, obufs, start, end, nframes, buffers); } + } -void -BaseStereoPanner::set_automation_style (AutoStyle style) + +/*---------------------------------------------------------------------- */ + +BaseStereoPanner::BaseStereoPanner (Panner& p, Evoral::Parameter param) + : StreamPanner (p, param) { - _automation.set_automation_style (style); } -void -BaseStereoPanner::set_automation_state (AutoState state) +BaseStereoPanner::~BaseStereoPanner () { - if (state != _automation.automation_state()) { - - _automation.set_automation_state (state); - - if (state != Off) { - set_position (_automation.eval (parent.session().transport_frame())); - } - } } int @@ -236,11 +240,11 @@ BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) { char line[128]; LocaleGuard lg (X_("POSIX")); - - _automation.clear (); + + _control->list()->clear (); while (in.getline (line, sizeof (line), '\n')) { - jack_nframes_t when; + nframes_t when; double value; ++linecnt; @@ -254,19 +258,21 @@ BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) continue; } - _automation.fast_simple_add (when, value); + _control->list()->fast_simple_add (when, value); } /* now that we are done loading */ - _automation.StateChanged (); + ((AutomationList*)_control->list().get())->StateChanged (); return 0; } void -BaseStereoPanner::distribute (Sample* src, Sample** obufs, gain_t gain_coeff, nframes_t nframes) +BaseStereoPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes) { + assert(obufs.count().n_audio() == 2); + pan_t delta; Sample* dst; pan_t pan; @@ -275,65 +281,75 @@ BaseStereoPanner::distribute (Sample* src, Sample** obufs, gain_t gain_coeff, nf return; } + Sample* const src = srcbuf.data(); + /* LEFT */ - dst = obufs[0]; + dst = obufs.get_audio(0).data(); + + if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc - if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc - - /* interpolate over 64 frames or nframes, whichever is smaller */ - - nframes_t limit = min ((nframes_t)64, nframes); + /* we've moving the pan by an appreciable amount, so we must + interpolate over 64 frames or nframes, whichever is smaller */ + + nframes_t const limit = min ((nframes_t)64, nframes); nframes_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; - Session::mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); - + 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) { - - Session::mix_buffers_with_gain(dst,src,nframes,pan); + + /* 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 { - - Session::mix_buffers_no_gain(dst,src,nframes); - + + /* pan is 1 so we can just copy the input samples straight in */ + + mix_buffers_no_gain(dst,src,nframes); + /* mark that we wrote into the buffer */ - + // obufs[0] = 0; } } /* RIGHT */ - dst = obufs[1]; - - if (fabsf ((delta = (right - desired_right))) > 0.002) { // about 1 degree of arc - - /* interpolate over 64 frames or nframes, whichever is smaller */ - - nframes_t limit = min ((nframes_t)64, nframes); + 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 */ + + nframes_t const limit = min ((nframes_t)64, nframes); nframes_t n; delta = -(delta / (float) (limit)); @@ -343,31 +359,37 @@ BaseStereoPanner::distribute (Sample* src, Sample** obufs, gain_t gain_coeff, nf 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; - - Session::mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); - + + 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" */ - Session::mix_buffers_with_gain(dst,src,nframes,pan); - + 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 */ - Session::mix_buffers_no_gain(dst,src,nframes); - + mix_buffers_no_gain(dst,src,nframes); + /* XXX it would be nice to mark the buffer as written to */ } } @@ -375,8 +397,8 @@ BaseStereoPanner::distribute (Sample* src, Sample** obufs, gain_t gain_coeff, nf /*---------------------------------------------------------------------- */ -EqualPowerStereoPanner::EqualPowerStereoPanner (Panner& p) - : BaseStereoPanner (p) +EqualPowerStereoPanner::EqualPowerStereoPanner (Panner& p, Evoral::Parameter param) + : BaseStereoPanner (p, param) { update (); @@ -394,9 +416,9 @@ void EqualPowerStereoPanner::update () { /* it would be very nice to split this out into a virtual function - that can be accessed from BaseStereoPanner and used in distribute_automated(). - - but the place where its used in distribute_automated() is a tight inner loop, + 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. */ @@ -405,40 +427,45 @@ EqualPowerStereoPanner::update () x == 1 => hard right */ - float panR = x; - float panL = 1 - panR; + float const panR = _x; + float const panL = 1 - panR; + + float const pan_law_attenuation = -3.0f; + float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - const float pan_law_attenuation = -3.0f; - const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - desired_left = panL * (scale * panL + 1.0f - scale); desired_right = panR * (scale * panR + 1.0f - scale); - effective_x = x; + effective_x = _x; + //_control->set_value(x); } void -EqualPowerStereoPanner::distribute_automated (Sample* src, Sample** obufs, - nframes_t start, nframes_t end, nframes_t nframes, - pan_t** buffers) +EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet& obufs, + nframes_t start, nframes_t end, nframes_t nframes, + pan_t** buffers) { + assert (obufs.count().n_audio() == 2); + Sample* dst; pan_t* pbuf; + Sample* const src = srcbuf.data(); /* fetch positional data */ - if (!_automation.rt_safe_get_vector (start, end, buffers[0], nframes)) { + if (!_control->list()->curve().rt_safe_get_vector (start, end, buffers[0], nframes)) { /* fallback */ if (!_muted) { - distribute (src, obufs, 1.0, nframes); + do_distribute (srcbuf, obufs, 1.0, nframes); } return; } /* store effective pan position. do this even if we are muted */ - if (nframes > 0) + if (nframes > 0) { effective_x = buffers[0][nframes-1]; + } if (_muted) { return; @@ -453,40 +480,40 @@ EqualPowerStereoPanner::distribute_automated (Sample* src, Sample** obufs, for (nframes_t n = 0; n < nframes; ++n) { - float panR = buffers[0][n]; - float panL = 1 - panR; - + float const panR = buffers[0][n]; + float const panL = 1 - panR; + buffers[0][n] = panL * (scale * panL + 1.0f - scale); buffers[1][n] = panR * (scale * panR + 1.0f - scale); } /* LEFT */ - dst = obufs[0]; + dst = obufs.get_audio(0).data(); pbuf = buffers[0]; - + for (nframes_t n = 0; n < nframes; ++n) { dst[n] += src[n] * pbuf[n]; - } + } - /* XXX it would be nice to mark the buffer as written to */ + /* XXX it would be nice to mark the buffer as written to */ /* RIGHT */ - dst = obufs[1]; + dst = obufs.get_audio(1).data(); pbuf = buffers[1]; for (nframes_t n = 0; n < nframes; ++n) { dst[n] += src[n] * pbuf[n]; - } - - /* XXX it would be nice to mark the buffer as written to */ + } + + /* XXX it would be nice to mark the buffer as written to */ } StreamPanner* -EqualPowerStereoPanner::factory (Panner& parent) +EqualPowerStereoPanner::factory (Panner& parent, Evoral::Parameter param) { - return new EqualPowerStereoPanner (parent); + return new EqualPowerStereoPanner (parent, param); } XMLNode& @@ -496,29 +523,27 @@ EqualPowerStereoPanner::get_state (void) } XMLNode& -EqualPowerStereoPanner::state (bool full_state) +EqualPowerStereoPanner::state (bool /*full_state*/) { XMLNode* root = new XMLNode ("StreamPanner"); char buf[64]; LocaleGuard lg (X_("POSIX")); - snprintf (buf, sizeof (buf), "%.12g", x); + snprintf (buf, sizeof (buf), "%.12g", _x); root->add_property (X_("x"), buf); root->add_property (X_("type"), EqualPowerStereoPanner::name); - XMLNode* autonode = new XMLNode (X_("Automation")); - autonode->add_child_nocopy (_automation.state (full_state)); - root->add_child_nocopy (*autonode); + // XXX: dont save automation here... its part of the automatable panner now. StreamPanner::add_state (*root); - root->add_child_nocopy (_control.get_state ()); + root->add_child_nocopy (_control->get_state ()); return *root; } int -EqualPowerStereoPanner::set_state (const XMLNode& node) +EqualPowerStereoPanner::set_state (const XMLNode& node, int version) { const XMLProperty* prop; float pos; @@ -527,23 +552,23 @@ EqualPowerStereoPanner::set_state (const XMLNode& node) if ((prop = node.property (X_("x")))) { pos = atof (prop->value().c_str()); set_position (pos, true); - } + } - StreamPanner::set_state (node); + StreamPanner::set_state (node, version); for (XMLNodeConstIterator iter = node.children().begin(); iter != node.children().end(); ++iter) { - if ((*iter)->name() == X_("controllable")) { + if ((*iter)->name() == X_("Controllable")) { if ((prop = (*iter)->property("name")) != 0 && prop->value() == "panner") { - _control.set_state (**iter); + _control->set_state (**iter, version); } } else if ((*iter)->name() == X_("Automation")) { - _automation.set_state (*((*iter)->children().front())); + _control->alist()->set_state (*((*iter)->children().front()), version); - if (_automation.automation_state() != Off) { - set_position (_automation.eval (parent.session().transport_frame())); + if (_control->alist()->automation_state() != Off) { + set_position (_control->list()->eval (parent.session().transport_frame())); } } } @@ -553,8 +578,8 @@ EqualPowerStereoPanner::set_state (const XMLNode& node) /*----------------------------------------------------------------------*/ -Multi2dPanner::Multi2dPanner (Panner& p) - : StreamPanner (p), _automation (0.0, 1.0, 0.5) // XXX useless +Multi2dPanner::Multi2dPanner (Panner& p, Evoral::Parameter param) + : StreamPanner (p, param) { update (); } @@ -563,36 +588,12 @@ Multi2dPanner::~Multi2dPanner () { } -void -Multi2dPanner::snapshot (nframes_t now) -{ - // how? -} - -void -Multi2dPanner::transport_stopped (nframes_t frame) -{ - //what? -} - -void -Multi2dPanner::set_automation_style (AutoStyle style) -{ - //what? -} - -void -Multi2dPanner::set_automation_state (AutoState state) -{ - // what? -} - void Multi2dPanner::update () { static const float BIAS = FLT_MIN; uint32_t i; - uint32_t nouts = parent.outputs.size(); + uint32_t const nouts = parent.nouts (); float dsq[nouts]; float f, fr; vector pans; @@ -600,7 +601,7 @@ Multi2dPanner::update () f = 0.0f; for (i = 0; i < nouts; i++) { - dsq[i] = ((x - parent.outputs[i].x) * (x - parent.outputs[i].x) + (y - parent.outputs[i].y) * (y - parent.outputs[i].y) + BIAS); + dsq[i] = ((_x - parent.output(i).x) * (_x - parent.output(i).x) + (_y - parent.output(i).y) * (_y - parent.output(i).y) + BIAS); if (dsq[i] < 0.0) { dsq[i] = 0.0; } @@ -611,89 +612,78 @@ Multi2dPanner::update () fr = (float) (1.0 / sqrt((double)f)); #else fr = 1.0 / sqrtf(f); -#endif +#endif for (i = 0; i < nouts; ++i) { - parent.outputs[i].desired_pan = 1.0f - (dsq[i] * fr); + parent.output(i).desired_pan = 1.0f - (dsq[i] * fr); } - effective_x = x; + effective_x = _x; } void -Multi2dPanner::distribute (Sample* src, Sample** obufs, gain_t gain_coeff, nframes_t nframes) +Multi2dPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes) { Sample* dst; pan_t pan; - vector::iterator o; - uint32_t n; if (_muted) { return; } + Sample* const src = srcbuf.data(); - for (n = 0, o = parent.outputs.begin(); o != parent.outputs.end(); ++o, ++n) { + uint32_t const N = parent.nouts (); + for (uint32_t n = 0; n < N; ++n) { + Panner::Output& o = parent.output (n); + + dst = obufs.get_audio(n).data(); - dst = obufs[n]; - #ifdef CAN_INTERP - if (fabsf ((delta = (left_interp - desired_left))) > 0.002) { // about 1 degree of arc - + if (fabsf ((delta = (left_interp - desired_left))) > 0.002) { // about 1 degree of arc + /* interpolate over 64 frames or nframes, whichever is smaller */ - + nframes_t limit = min ((nframes_t)64, nframes); nframes_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; } - + pan = left * gain_coeff; - - for (; n < nframes; ++n) { - dst[n] += src[n] * pan; - } - + mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); + } else { -#else - pan = (*o).desired_pan; - +#else + pan = o.desired_pan; + if ((pan *= gain_coeff) != 1.0f) { - - if (pan != 0.0f) { - - for (nframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n] * pan; - } - - } + if (pan != 0.0f) { + mix_buffers_with_gain(dst,src,nframes,pan); + } } else { - - for (nframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n]; - } - + mix_buffers_no_gain(dst,src,nframes); } #endif #ifdef CAN_INTERP } #endif } - + return; } void -Multi2dPanner::distribute_automated (Sample* src, Sample** obufs, - nframes_t start, nframes_t end, nframes_t nframes, - pan_t** buffers) +Multi2dPanner::do_distribute_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/, + nframes_t /*start*/, nframes_t /*end*/, nframes_t /*nframes*/, + pan_t** /*buffers*/) { if (_muted) { return; @@ -705,13 +695,13 @@ Multi2dPanner::distribute_automated (Sample* src, Sample** obufs, } StreamPanner* -Multi2dPanner::factory (Panner& p) +Multi2dPanner::factory (Panner& p, Evoral::Parameter param) { - return new Multi2dPanner (p); + return new Multi2dPanner (p, param); } int -Multi2dPanner::load (istream& in, string path, uint32_t& linecnt) +Multi2dPanner::load (istream& /*in*/, string /*path*/, uint32_t& /*linecnt*/) { return 0; } @@ -723,15 +713,15 @@ Multi2dPanner::get_state (void) } XMLNode& -Multi2dPanner::state (bool full_state) +Multi2dPanner::state (bool /*full_state*/) { XMLNode* root = new XMLNode ("StreamPanner"); char buf[64]; LocaleGuard lg (X_("POSIX")); - snprintf (buf, sizeof (buf), "%.12g", x); + snprintf (buf, sizeof (buf), "%.12g", _x); root->add_property (X_("x"), buf); - snprintf (buf, sizeof (buf), "%.12g", y); + snprintf (buf, sizeof (buf), "%.12g", _y); root->add_property (X_("y"), buf); root->add_property (X_("type"), Multi2dPanner::name); @@ -741,7 +731,7 @@ Multi2dPanner::state (bool full_state) } int -Multi2dPanner::set_state (const XMLNode& node) +Multi2dPanner::set_state (const XMLNode& node, int /*version*/) { const XMLProperty* prop; float newx,newy; @@ -753,17 +743,17 @@ Multi2dPanner::set_state (const XMLNode& node) if ((prop = node.property (X_("x")))) { newx = atof (prop->value().c_str()); } - + if ((prop = node.property (X_("y")))) { newy = atof (prop->value().c_str()); } - - if (x < 0 || y < 0) { + + if (_x < 0 || _y < 0) { error << _("badly-formed positional data for Multi2dPanner - ignored") << endmsg; return -1; - } - + } + set_position (newx, newy); return 0; } @@ -771,13 +761,16 @@ Multi2dPanner::set_state (const XMLNode& node) /*---------------------------------------------------------------------- */ Panner::Panner (string name, Session& s) - : _session (s) + : SessionObject (s, name) + , AutomatableControls (s) { + //set_name_old_auto (name); set_name (name); _linked = false; _link_direction = SameDirection; _bypassed = false; + _mono = false; } Panner::~Panner () @@ -804,6 +797,7 @@ Panner::set_link_direction (LinkDirection ld) } } + void Panner::set_bypassed (bool yn) { @@ -814,18 +808,104 @@ Panner::set_bypassed (bool yn) } +void +Panner::reset_to_default () +{ + vector positions; + + switch (outputs.size()) { + case 0: + case 1: + return; + } + + if (outputs.size() == 2) { + switch (_streampanners.size()) { + case 1: + _streampanners.front()->set_position (0.5); + _streampanners.front()->pan_control()->list()->reset_default (0.5); + return; + break; + case 2: + _streampanners.front()->set_position (0.0); + _streampanners.front()->pan_control()->list()->reset_default (0.0); + _streampanners.back()->set_position (1.0); + _streampanners.back()->pan_control()->list()->reset_default (1.0); + return; + default: + break; + } + } + + vector::iterator o; + vector::iterator p; + + for (o = outputs.begin(), p = _streampanners.begin(); o != outputs.end() && p != _streampanners.end(); ++o, ++p) { + (*p)->set_position ((*o).x, (*o).y); + } +} + +void +Panner::reset_streampanner (uint32_t which) +{ + if (which >= _streampanners.size() || which >= outputs.size()) { + return; + } + + switch (outputs.size()) { + case 0: + case 1: + return; + + case 2: + switch (_streampanners.size()) { + case 1: + /* stereo out, 1 stream, default = middle */ + _streampanners.front()->set_position (0.5); + _streampanners.front()->pan_control()->list()->reset_default (0.5); + break; + case 2: + /* stereo out, 2 streams, default = hard left/right */ + if (which == 0) { + _streampanners.front()->set_position (0.0); + _streampanners.front()->pan_control()->list()->reset_default (0.0); + } else { + _streampanners.back()->set_position (1.0); + _streampanners.back()->pan_control()->list()->reset_default (1.0); + } + break; + } + return; + + default: + _streampanners[which]->set_position (outputs[which].x, outputs[which].y); + } +} + +/** + * Reset the panner with a given number of outs and panners (and hence inputs) + * + * \param nouts Number of outputs. + * \param npans Number of panners. + */ void Panner::reset (uint32_t nouts, uint32_t npans) { uint32_t n; bool changed = false; + bool do_not_and_did_not_need_panning = ((nouts < 2) && (outputs.size() < 2)); - if (nouts < 2 || (nouts == outputs.size() && npans == size())) { + /* if new and old config don't need panning, or if + the config hasn't changed, we're done. + */ + + if (do_not_and_did_not_need_panning || + ((nouts == outputs.size()) && (npans == _streampanners.size()))) { return; - } + } - n = size(); - clear (); + n = _streampanners.size(); + clear_panners (); if (n != npans) { changed = true; @@ -838,24 +918,33 @@ Panner::reset (uint32_t nouts, uint32_t npans) changed = true; } + if (nouts < 2) { + /* no need for panning with less than 2 outputs */ + if (changed) { + Changed (); /* EMIT SIGNAL */ + } + return; + } + switch (nouts) { case 0: + /* XXX: this can never happen */ break; case 1: + /* XXX: this can never happen */ fatal << _("programming error:") << X_("Panner::reset() called with a single output") << endmsg; /*NOTREACHED*/ break; - case 2: - /* line */ + case 2: // line outputs.push_back (Output (0, 0)); outputs.push_back (Output (1.0, 0)); for (n = 0; n < npans; ++n) { - push_back (new EqualPowerStereoPanner (*this)); + _streampanners.push_back (new EqualPowerStereoPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); } break; @@ -865,10 +954,10 @@ Panner::reset (uint32_t nouts, uint32_t npans) outputs.push_back (Output (1.0, 1.0)); for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); + _streampanners.push_back (new Multi2dPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); } - break; + break; case 4: // square outputs.push_back (Output (0, 0)); @@ -877,10 +966,10 @@ Panner::reset (uint32_t nouts, uint32_t npans) outputs.push_back (Output (0, 1.0)); for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); + _streampanners.push_back (new Multi2dPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); } - break; + break; case 5: //square+offcenter center outputs.push_back (Output (0, 0)); @@ -890,7 +979,7 @@ Panner::reset (uint32_t nouts, uint32_t npans) outputs.push_back (Output (0.5, 0.75)); for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); + _streampanners.push_back (new Multi2dPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); } break; @@ -902,19 +991,27 @@ Panner::reset (uint32_t nouts, uint32_t npans) } for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); + _streampanners.push_back (new Multi2dPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); } break; } - for (iterator x = begin(); x != end(); ++x) { + for (std::vector::iterator x = _streampanners.begin(); x != _streampanners.end(); ++x) { (*x)->update (); } - /* force hard left/right panning in a common case: 2in/2out + /* must emit Changed here, otherwise the changes to the pan_control below raise further + signals which the GUI is not prepared for until it has seen the Changed here. */ + if (changed) { + Changed (); /* EMIT SIGNAL */ + } + + /* force hard left/right panning in a common case: 2in/2out + */ + if (npans == 2 && outputs.size() == 2) { /* Do this only if we changed configuration, or our configuration @@ -924,117 +1021,94 @@ Panner::reset (uint32_t nouts, uint32_t npans) float left; float right; - front()->get_position (left); - back()->get_position (right); + _streampanners.front()->get_position (left); + _streampanners.back()->get_position (right); if (changed || ((left == 0.5) && (right == 0.5))) { - - front()->set_position (0.0); - front()->automation().reset_default (0.0); - - back()->set_position (1.0); - back()->automation().reset_default (1.0); - - changed = true; - } - } - if (changed) { - Changed (); /* EMIT SIGNAL */ - } + _streampanners.front()->set_position (0.0); + _streampanners.front()->pan_control()->list()->reset_default (0.0); - return; + _streampanners.back()->set_position (1.0); + _streampanners.back()->pan_control()->list()->reset_default (1.0); + } + } } void Panner::remove (uint32_t which) { vector::iterator i; - for (i = begin(); i != end() && which; ++i, --which); + for (i = _streampanners.begin(); i != _streampanners.end() && which; ++i, --which) {} - if (i != end()) { + if (i != _streampanners.end()) { delete *i; - erase (i); + _streampanners.erase (i); } } + +/** Remove all our StreamPanners */ void -Panner::clear () +Panner::clear_panners () { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { delete *i; } - vector::clear (); + _streampanners.clear (); } void Panner::set_automation_style (AutoStyle style) { - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->set_automation_style (style); + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_style (style); } _session.set_dirty (); -} +} void Panner::set_automation_state (AutoState state) { - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->set_automation_state (state); + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_state (state); } _session.set_dirty (); -} +} AutoState Panner::automation_state () const { + boost::shared_ptr l; if (!empty()) { - return front()->automation().automation_state (); - } else { - return Off; + boost::shared_ptr control = _streampanners.front()->pan_control(); + if (control) { + l = boost::dynamic_pointer_cast(control->list()); + } } + + return l ? l->automation_state() : Off; } AutoStyle Panner::automation_style () const { + boost::shared_ptr l; if (!empty()) { - return front()->automation().automation_style (); - } else { - return Absolute; - } -} - -void -Panner::transport_stopped (nframes_t frame) -{ - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->transport_stopped (frame); - } -} - -void -Panner::snapshot (nframes_t now) -{ - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->snapshot (now); + boost::shared_ptr control = _streampanners.front()->pan_control(); + if (control) { + l = boost::dynamic_pointer_cast(control->list()); + } } -} -void -Panner::clear_automation () -{ - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->automation().clear (); - } - _session.set_dirty (); -} + return l ? l->automation_style() : Absolute; +} struct PanPlugins { string name; uint32_t nouts; - StreamPanner* (*factory)(Panner&); + StreamPanner* (*factory)(Panner&, Evoral::Parameter); }; PanPlugins pan_plugins[] = { @@ -1052,14 +1126,13 @@ Panner::get_state (void) XMLNode& Panner::state (bool full) { - XMLNode* root = new XMLNode (X_("Panner")); - char buf[32]; + XMLNode* node = new XMLNode ("Panner"); - root->add_property (X_("linked"), (_linked ? "yes" : "no")); - root->add_property (X_("link_direction"), enum_2_string (_link_direction)); - root->add_property (X_("bypassed"), (bypassed() ? "yes" : "no")); + char buf[32]; - /* add each output */ + node->add_property (X_("linked"), (_linked ? "yes" : "no")); + node->add_property (X_("link_direction"), enum_2_string (_link_direction)); + node->add_property (X_("bypassed"), (bypassed() ? "yes" : "no")); for (vector::iterator o = outputs.begin(); o != outputs.end(); ++o) { XMLNode* onode = new XMLNode (X_("Output")); @@ -1067,36 +1140,43 @@ Panner::state (bool full) onode->add_property (X_("x"), buf); snprintf (buf, sizeof (buf), "%.12g", (*o).y); onode->add_property (X_("y"), buf); - root->add_child_nocopy (*onode); + node->add_child_nocopy (*onode); } - for (vector::const_iterator i = begin(); i != end(); ++i) { - root->add_child_nocopy ((*i)->state (full)); + for (vector::const_iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + node->add_child_nocopy ((*i)->state (full)); } - return *root; + + return *node; } int -Panner::set_state (const XMLNode& node) +Panner::set_state (const XMLNode& node, int version) { XMLNodeList nlist; XMLNodeConstIterator niter; const XMLProperty *prop; uint32_t i; + uint32_t num_panners = 0; StreamPanner* sp; LocaleGuard lg (X_("POSIX")); - clear (); + clear_panners (); + + ChanCount ins = ChanCount::ZERO; + ChanCount outs = ChanCount::ZERO; + + // XXX: this might not be necessary anymore outputs.clear (); if ((prop = node.property (X_("linked"))) != 0) { - set_linked (prop->value() == "yes"); + set_linked (string_is_affirmative (prop->value())); } if ((prop = node.property (X_("bypassed"))) != 0) { - set_bypassed (prop->value() == "yes"); + set_bypassed (string_is_affirmative (prop->value())); } if ((prop = node.property (X_("link_direction"))) != 0) { @@ -1108,15 +1188,15 @@ Panner::set_state (const XMLNode& node) for (niter = nlist.begin(); niter != nlist.end(); ++niter) { if ((*niter)->name() == X_("Output")) { - + float x, y; - + prop = (*niter)->property (X_("x")); sscanf (prop->value().c_str(), "%g", &x); - + prop = (*niter)->property (X_("y")); sscanf (prop->value().c_str(), "%g", &y); - + outputs.push_back (Output (x, y)); } } @@ -1124,29 +1204,30 @@ Panner::set_state (const XMLNode& node) for (niter = nlist.begin(); niter != nlist.end(); ++niter) { if ((*niter)->name() == X_("StreamPanner")) { - + if ((prop = (*niter)->property (X_("type")))) { - + for (i = 0; pan_plugins[i].factory; ++i) { if (prop->value() == pan_plugins[i].name) { - - + + /* note that we assume that all the stream panners are of the same type. pretty good - assumption, but its still an assumption. + assumption, but it's still an assumption. */ - - sp = pan_plugins[i].factory (*this); - - if (sp->set_state (**niter) == 0) { - push_back (sp); + + sp = pan_plugins[i].factory (*this, Evoral::Parameter(PanAutomation, 0, num_panners)); + num_panners++; + + if (sp->set_state (**niter, version) == 0) { + _streampanners.push_back (sp); } - + break; } } - - + + if (!pan_plugins[i].factory) { error << string_compose (_("Unknown panner plugin \"%1\" found in pan state - ignored"), prop->value()) @@ -1159,29 +1240,27 @@ Panner::set_state (const XMLNode& node) return -1; } - } + } } + reset (outputs.size (), num_panners); /* don't try to do old-school automation loading if it wasn't marked as existing */ if ((prop = node.property (X_("automation")))) { /* automation path is relative */ - - automation_path = _session.automation_dir(); - automation_path += prop->value (); - } + + automation_path = Glib::build_filename(_session.automation_dir(), prop->value ()); + } return 0; } - - bool Panner::touching () const { - for (vector::const_iterator i = begin(); i != end(); ++i) { - if ((*i)->automation().touching ()) { + for (vector::const_iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + if (((AutomationList*)(*i)->pan_control()->list().get())->touching ()) { return true; } } @@ -1198,10 +1277,10 @@ Panner::set_position (float xpos, StreamPanner& orig) orig.get_position (xnow); xdelta = xpos - xnow; - + if (_link_direction == SameDirection) { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { (*i)->set_position (xpos, true); } else { @@ -1214,7 +1293,7 @@ Panner::set_position (float xpos, StreamPanner& orig) } else { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { (*i)->set_position (xpos, true); } else { @@ -1237,10 +1316,10 @@ Panner::set_position (float xpos, float ypos, StreamPanner& orig) orig.get_position (xnow, ynow); xdelta = xpos - xnow; ydelta = ypos - ynow; - + if (_link_direction == SameDirection) { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { (*i)->set_position (xpos, ypos, true); } else { @@ -1258,12 +1337,12 @@ Panner::set_position (float xpos, float ypos, StreamPanner& orig) } else { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { (*i)->set_position (xpos, ypos, true); } else { (*i)->get_position (xnow, ynow); - + xnew = min (1.0f, xnow - xdelta); xnew = max (0.0f, xnew); @@ -1290,12 +1369,12 @@ Panner::set_position (float xpos, float ypos, float zpos, StreamPanner& orig) if (_link_direction == SameDirection) { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { (*i)->set_position (xpos, ypos, zpos, true); } else { (*i)->get_position (xnow, ynow, znow); - + xnew = min (1.0f, xnow + xdelta); xnew = max (0.0f, xnew); @@ -1311,7 +1390,7 @@ Panner::set_position (float xpos, float ypos, float zpos, StreamPanner& orig) } else { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { (*i)->set_position (xpos, ypos, true); } else { @@ -1332,17 +1411,148 @@ Panner::set_position (float xpos, float ypos, float zpos, StreamPanner& orig) } } +void +Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, nframes_t nframes, gain_t gain_coeff) +{ + if (outbufs.count().n_audio() == 0) { + // Don't want to lose audio... + assert(inbufs.count().n_audio() == 0); + return; + } + + // We shouldn't be called in the first place... + assert(!bypassed()); + assert(!empty()); + + + if (outbufs.count().n_audio() == 1) { + + AudioBuffer& dst = outbufs.get_audio(0); + + if (gain_coeff == 0.0f) { + + /* only one output, and gain was zero, so make it silent */ + + dst.silence (nframes); + + } else if (gain_coeff == 1.0f){ + + /* mix all buffers into the output */ + + // copy the first + dst.read_from(inbufs.get_audio(0), nframes); + + // accumulate starting with the second + if (inbufs.count().n_audio() > 0) { + BufferSet::audio_iterator i = inbufs.audio_begin(); + for (++i; i != inbufs.audio_end(); ++i) { + dst.merge_from(*i, nframes); + } + } + + } else { + + /* mix all buffers into the output, scaling them all by the gain */ + + // copy the first + dst.read_from(inbufs.get_audio(0), nframes); + + // accumulate (with gain) starting with the second + if (inbufs.count().n_audio() > 0) { + BufferSet::audio_iterator i = inbufs.audio_begin(); + for (++i; i != inbufs.audio_end(); ++i) { + dst.accumulate_with_gain_from(*i, nframes, gain_coeff); + } + } + + } + + return; + } + + /* the terrible silence ... */ + for (BufferSet::audio_iterator i = outbufs.audio_begin(); i != outbufs.audio_end(); ++i) { + i->silence(nframes); + } + + BufferSet::audio_iterator i = inbufs.audio_begin(); + + for (vector::iterator pan = _streampanners.begin(); pan != _streampanners.end() && i != inbufs.audio_end(); ++pan, ++i) { + (*pan)->distribute (*i, outbufs, gain_coeff, nframes); + } +} + +void +Panner::run (BufferSet& inbufs, BufferSet& outbufs, sframes_t start_frame, sframes_t end_frame, nframes_t nframes) +{ + if (outbufs.count().n_audio() == 0) { + // Failing to deliver audio we were asked to deliver is a bug + assert(inbufs.count().n_audio() == 0); + return; + } + + // We shouldn't be called in the first place... + assert(!bypassed()); + assert(!empty()); + + // If we shouldn't play automation defer to distribute_no_automation + if (!(automation_state() & Play || ((automation_state() & Touch) && !touching()))) { + + // Speed quietning + gain_t gain_coeff = 1.0; + + if (fabsf(_session.transport_speed()) > 1.5f) { + gain_coeff = speed_quietning; + } + + distribute_no_automation (inbufs, outbufs, nframes, gain_coeff); + return; + } + + // Otherwise.. let the automation flow, baby + + if (outbufs.count().n_audio() == 1) { + + AudioBuffer& dst = outbufs.get_audio(0); + + // FIXME: apply gain automation? + + // copy the first + dst.read_from(inbufs.get_audio(0), nframes); + + // accumulate starting with the second + BufferSet::audio_iterator i = inbufs.audio_begin(); + for (++i; i != inbufs.audio_end(); ++i) { + dst.merge_from(*i, nframes); + } + + return; + } + + // More than 1 output, we should have 1 panner for each input + //assert(_streampanners.size() == inbufs.count().n_audio()); + + /* the terrible silence ... */ + for (BufferSet::audio_iterator i = outbufs.audio_begin(); i != outbufs.audio_end(); ++i) { + i->silence(nframes); + } + + BufferSet::audio_iterator i = inbufs.audio_begin(); + for (vector::iterator pan = _streampanners.begin(); pan != _streampanners.end(); ++pan, ++i) { + (*pan)->distribute_automated (*i, outbufs, start_frame, end_frame, nframes, _session.pan_automation_buffer()); + } +} + /* old school automation handling */ +/* void Panner::set_name (string str) { - automation_path = _session.automation_dir(); - automation_path += _session.snap_name(); - automation_path += "-pan-"; - automation_path += legalize_for_path (str); - automation_path += ".automation"; + automation_path = Glib::build_filename(_session.automation_dir(), + _session.snap_name() + "-pan-" + legalize_for_path (str) + ".automation"); } +*/ int Panner::load () @@ -1350,13 +1560,13 @@ Panner::load () char line[128]; uint32_t linecnt = 0; float version; - iterator sp; + vector::iterator sp; LocaleGuard lg (X_("POSIX")); if (automation_path.length() == 0) { return 0; } - + if (access (automation_path.c_str(), F_OK)) { return 0; } @@ -1370,7 +1580,7 @@ Panner::load () return -1; } - sp = begin(); + sp = _streampanners.begin(); while (in.getline (line, sizeof(line), '\n')) { @@ -1381,7 +1591,7 @@ Panner::load () return -1; } } else { - error << string_compose(_("no version information in pan automation event file \"%1\" (first line = %2)"), + error << string_compose(_("no version information in pan automation event file \"%1\" (first line = %2)"), automation_path, line) << endmsg; return -1; } @@ -1394,8 +1604,8 @@ Panner::load () } if (strcmp (line, "begin") == 0) { - - if (sp == end()) { + + if (sp == _streampanners.end()) { error << string_compose (_("too many panner states found in pan automation file %1"), automation_path) << endmsg; @@ -1405,10 +1615,23 @@ Panner::load () if ((*sp)->load (in, automation_path, linecnt)) { return -1; } - + ++sp; } } return 0; } + +void +Panner::set_mono (bool yn) +{ + if (yn != _mono) { + _mono = yn; + StateChanged (); + } + + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + (*i)->set_mono (yn); + } +}