Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/engine/positionscratchcontroller.cpp

Go to the documentation of this file.
00001 #include <QtDebug>
00002 
00003 #include "engine/positionscratchcontroller.h"
00004 #include "mathstuff.h"
00005 
00006 #ifdef _MSC_VER
00007 #include <float.h>  // for _finite() on VC++
00008 #define isinf(x) (!_finite(x))
00009 #endif
00010 
00011 class VelocityController {
00012   public:
00013     VelocityController() {
00014         m_p = 0.0;
00015         m_i = 0.0;
00016         m_d = 0.0;
00017         m_target_position = 0;
00018         m_last_error = 0.0;
00019         m_last_time = 0.0;
00020         m_error_sum = 0.0;
00021         m_samples_per_buffer = 0;
00022         m_last_velocity = 0.0;
00023     }
00024 
00025     void setPID(double p, double i, double d) {
00026         m_p = p;
00027         m_i = i;
00028         m_d = d;
00029     }
00030 
00031     void setSamplesPerBuffer(int iSamplesPerBuffer) {
00032         m_samples_per_buffer = iSamplesPerBuffer;
00033     }
00034 
00035     void reset(double position, double time, double target_position) {
00036         m_target_position = target_position;
00037         m_last_error = m_target_position - position;
00038         m_last_time = time;
00039         m_error_sum = 0.0;
00040         m_last_velocity = 0.0;
00041     }
00042 
00043     void setTarget(double target_position) {
00044         m_target_position = target_position;
00045     }
00046 
00047     double observation(double position, double time) {
00048         double dt = math_max(time - m_last_time, .001); // limit dt blowup
00049         dt = 1; // TODO(rryan) all time stuff disabled for now
00050 
00051         const double error = m_target_position - position;
00052 
00053         // Calculate integral component of PID
00054         m_error_sum += error * dt;
00055         // WTF ok..
00056         m_error_sum = m_last_error + error;
00057 
00058         // Calculate differential component of PID. Positive if we're getting
00059         // worse, negative if we're getting closer.
00060         double error_change = (error - m_last_error) / dt;
00061 
00062         // Indicator that can possibly tell if we've gone unstable and are
00063         // oscillating around the target.
00064         //const bool error_flip = (error < 0 && m_last_error > 0) || (error > 0 && m_last_error < 0);
00065 
00066         if (isnan(error_change) || isinf(error_change))
00067             error_change = 0.0;
00068 
00069         // qDebug() << "target:" << m_target_position << "position:" << position
00070         //          << "error:" << error << "change:" << error_change << "sum:" << m_error_sum;
00071 
00072         // Main PID calculation
00073         double output = m_p * error + m_i * m_error_sum + m_d * error_change;
00074 
00075         // Divide by samples per buffer to get a rate normalized for producing
00076         // m_samples_per_buffer samples for a value of 1.0
00077         output /= m_samples_per_buffer;
00078 
00079         // Try to stabilize us if we're close to the target. Otherwise we might
00080         // overshoot and oscillate.
00081         //if (fabs(error) < m_samples_per_buffer) {
00082             //double percent_remaining = error / m_samples_per_buffer;
00084             //double decay = (1.0 - pow(2, -fabs(percent_remaining)));
00085             //output = percent_remaining * decay;
00086             //qDebug() << "clamp decay" << decay << "output" << output;
00087         //}
00088 
00089         m_last_velocity = output;
00090         m_last_error = error;
00091         return output;
00092     }
00093 
00094     double getTarget() {
00095         return m_target_position;
00096     }
00097 
00098   private:
00099     double m_samples_per_buffer;
00100     double m_target_position;
00101     double m_last_error;
00102     double m_last_time;
00103     double m_last_velocity;
00104     double m_error_sum;
00105     double m_p, m_i, m_d;
00106 };
00107 
00108 PositionScratchController::PositionScratchController(const char* pGroup)
00109         : m_group(pGroup) {
00110     m_pScratchEnable = new ControlObject(ConfigKey(pGroup, "scratch_position_enable"));
00111     m_pScratchPosition = new ControlObject(ConfigKey(pGroup, "scratch_position"));
00112     m_pVelocityController = new VelocityController();
00113     m_bScratching = false;
00114     m_iScratchTime = 0;
00115     m_bEnableInertia = false;
00116     m_dRate = 0.;
00117 
00118     //m_pVelocityController->setPID(0.2, 1.0, 5.0);
00119     //m_pVelocityController->setPID(0.1, 0.0, 5.0);
00120     m_pVelocityController->setPID(0.1, 0.0, 0.00);
00121 }
00122 
00123 PositionScratchController::~PositionScratchController() {
00124     delete m_pScratchEnable;
00125     delete m_pScratchPosition;
00126     delete m_pVelocityController;
00127 }
00128 
00129 void PositionScratchController::process(double currentSample, bool paused, int iBufferSize) {
00130     bool scratchEnable = m_pScratchEnable->get() != 0;
00131     double scratchPosition = m_pScratchPosition->get();
00132     m_pVelocityController->setSamplesPerBuffer(iBufferSize);
00133 
00134     // The rate threshold above which disabling position scratching will enable
00135     // an 'inertia' mode.
00136     const double kThrowThreshold = 2.5;
00137     // The exponential decay factor that the rate will undergo if inertia mode
00138     // is triggered.
00139     const double kInertiaDecay = 0.9;
00140     // If we're playing, then do not decay rate below 1. If we're not playing,
00141     // then we want to decay all the way down to below 0.1
00142     const double kDecayThreshold = paused ? 0.1 : 1.0;
00143 
00144     if (m_bScratching) {
00145         if (scratchEnable || m_bEnableInertia) {
00146             // We were previously in scratch mode and are still in scratch mode
00147             // OR we are in inertia mode.
00148 
00149             if (scratchEnable) {
00150                 // If we're scratching, clear the inertia flag. This case should
00151                 // have been caught by the 'enable' case below, but just to make
00152                 // sure.
00153                 m_bEnableInertia = false;
00154 
00155                 // Increment processing timer by one.
00156                 m_iScratchTime += 1;
00157 
00158                 // Set the scratch target to the current set position
00159                 m_pVelocityController->setTarget(scratchPosition);
00160 
00161                 // Measure the total distance travelled since last frame and add
00162                 // it to the running total.
00163                 m_dPositionDeltaSum += (currentSample - m_dLastPlaypos);
00164 
00165                 m_dRate = m_pVelocityController->observation(
00166                     m_dPositionDeltaSum, m_iScratchTime);
00167                 //qDebug() << "continue" << m_dRate << iBufferSize;
00168             } else {
00169                 // If we got here then we're not scratching and we're in inertia
00170                 // mode. Take the previous rate that was set and apply an
00171                 // exponential decay.
00172                 m_dRate *= kInertiaDecay;
00173 
00174                 // If the rate has decayed below the threshold, then leave
00175                 // inertia mode.
00176                 if (fabs(m_dRate) < kDecayThreshold) {
00177                     m_bEnableInertia = false;
00178                 }
00179             }
00180         } else {
00181             // We were previously in scratch mode and are no longer in scratch
00182             // mode. Disable everything, or optionally enable inertia mode if
00183             // the previous rate was high enough to count as a 'throw'
00184             if (fabs(m_dRate) > kThrowThreshold) {
00185                 m_bEnableInertia = true;
00186             } else {
00187                 m_dRate = 0;
00188                 m_bScratching = false;
00189                 m_iScratchTime = 0;
00190             }
00191             //qDebug() << "disable";
00192         }
00193     } else {
00194         if (scratchEnable) {
00195             // We were not previously in scratch mode but now are in scratch
00196             // mode. Enable scratching.
00197             m_bScratching = true;
00198             m_bEnableInertia = false;
00199             m_dPositionDeltaSum = 0;
00200             m_iScratchTime = 0;
00201             m_pVelocityController->reset(0, m_iScratchTime, scratchPosition);
00202             m_dRate = m_pVelocityController->observation(0, m_iScratchTime);
00203             //qDebug() << "enable" << m_dRate << currentSample;
00204         } else {
00205             // We were not previously in scratch mode are still not in scratch
00206             // mode. Do nothing
00207         }
00208     }
00209     m_dLastPlaypos = currentSample;
00210 }
00211 
00212 bool PositionScratchController::isEnabled() {
00213     return m_bScratching;
00214 }
00215 
00216 double PositionScratchController::getRate() {
00217     return m_dRate;
00218 }
00219 
00220 void PositionScratchController::notifySeek(double currentSample) {
00221     m_dLastPlaypos = currentSample;
00222 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines