![]() |
Mixxx
|
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 }