=== modified file 'mixxx/src/engine/positionscratchcontroller.cpp' --- mixxx/src/engine/positionscratchcontroller.cpp 2012-11-21 04:45:02 +0000 +++ mixxx/src/engine/positionscratchcontroller.cpp 2013-02-16 22:26:02 +0000 @@ -10,15 +10,12 @@ class VelocityController { public: - VelocityController() { - m_p = 0.0; - m_i = 0.0; - m_d = 0.0; - m_target_position = 0; - m_last_error = 0.0; - m_last_time = 0.0; - m_error_sum = 0.0; - m_last_velocity = 0.0; + VelocityController() + : m_last_error(0.0), + m_error_sum(0.0), + m_p(0.0), + m_i(0.0), + m_d(0.0) { } void setPID(double p, double i, double d) { @@ -27,39 +24,26 @@ m_d = d; } - void reset(double position, double time, double target_position) { - m_target_position = target_position; - m_last_error = m_target_position - position; - m_last_time = time; + void reset() { + m_last_error = 0; m_error_sum = 0.0; - m_last_velocity = 0.0; - } - - void setTarget(double target_position) { - m_target_position = target_position; - } - - double observation(double position, double time) { - // limit dt to 100 microseconds to prevent blowup (nobody has latency - // that low so this shouldn't happen in practice). - double dt = math_max(time - m_last_time, 0.000100); - - const double error = m_target_position - position; + } + + double observation(double position, double target_position, double dt) { + Q_UNUSED(dt) // Since the controller runs with constant sample rate + // we don't have to deal with dt inside the controller + + const double error = target_position - position; // Calculate integral component of PID - m_error_sum += error * dt; + // In case of error too small then stop integration + if (abs(error) > 0.1) { + m_error_sum += error; + } // Calculate differential component of PID. Positive if we're getting // worse, negative if we're getting closer. - double error_change = (error - m_last_error) / dt; - - // Indicator that can possibly tell if we've gone unstable and are - // oscillating around the target. - //const bool error_flip = (error < 0 && m_last_error > 0) || (error > 0 && m_last_error < 0); - - // Protect against silly error_change values. - if (isnan(error_change) || isinf(error_change)) - error_change = 0.0; + double error_change = (error - m_last_error); // qDebug() << "target:" << m_target_position << "position:" << position // << "error:" << error << "change:" << error_change << "sum:" << m_error_sum; @@ -77,40 +61,28 @@ //qDebug() << "clamp decay" << decay << "output" << output; //} - m_last_velocity = output; m_last_error = error; return output; } - double getTarget() { - return m_target_position; - } - private: - double m_target_position; double m_last_error; - double m_last_time; - double m_last_velocity; double m_error_sum; double m_p, m_i, m_d; }; PositionScratchController::PositionScratchController(const char* pGroup) - : m_group(pGroup) { + : m_group(pGroup), + m_bScratching(false), + m_bEnableInertia(false), + m_dLastPlaypos(0), + m_dPositionDeltaSum(0), + m_dStartScratchPosition(0), + m_dRate(0) { m_pScratchEnable = new ControlObject(ConfigKey(pGroup, "scratch_position_enable")); m_pScratchPosition = new ControlObject(ConfigKey(pGroup, "scratch_position")); m_pMasterSampleRate = ControlObject::getControl(ConfigKey("[Master]", "samplerate")); - m_pScratchControllerP = new ControlObject(ConfigKey(pGroup, "scratch_constant_p")); - m_pScratchControllerI = new ControlObject(ConfigKey(pGroup, "scratch_constant_i")); - m_pScratchControllerD = new ControlObject(ConfigKey(pGroup, "scratch_constant_d")); m_pVelocityController = new VelocityController(); - m_bScratching = false; - m_dScratchTime = 0; - m_bEnableInertia = false; - m_dRate = 0.; - m_pScratchControllerP->set(0.0002); - m_pScratchControllerI->set(0.0); - m_pScratchControllerD->set(0.0); //m_pVelocityController->setPID(0.2, 1.0, 5.0); //m_pVelocityController->setPID(0.1, 0.0, 5.0); @@ -121,111 +93,133 @@ delete m_pScratchEnable; delete m_pScratchPosition; delete m_pVelocityController; - delete m_pScratchControllerP; - delete m_pScratchControllerI; - delete m_pScratchControllerD; } -void PositionScratchController::process(double currentSample, bool paused, int iBufferSize) { + +void PositionScratchController::process(double currentSample, bool paused, + int iBufferSize, double baserate) { bool scratchEnable = m_pScratchEnable->get() != 0; + + if (!m_bScratching && scratchEnable) { + // We were not previously in scratch mode are still not in scratch + // mode. Do nothing + } + double scratchPosition = m_pScratchPosition->get(); - m_pVelocityController->setPID(m_pScratchControllerP->get(), - m_pScratchControllerI->get(), - m_pScratchControllerD->get()); - - // The rate threshold above which disabling position scratching will enable - // an 'inertia' mode. - const double kThrowThreshold = 2.5; - - // If we're playing, then do not decay rate below 1. If we're not playing, - // then we want to decay all the way down to below 0.01 - const double kDecayThreshold = paused ? 0.01 : 1.0; - // Max velocity we would like to stop in a given time period. - const double kMaxVelocity = 100; - // Seconds to stop a throw at the max velocity. - const double kTimeToStop = 2.0; - + // The latency or time difference between process calls. - const double dt = static_cast(iBufferSize) / m_pMasterSampleRate->get(); + const double dt = static_cast(iBufferSize) + / m_pMasterSampleRate->get() / 2; - // We calculate the exponential decay constant based on the above - // constants. Roughly we backsolve what the decay should be if we want to - // stop a throw of max velocity kMaxVelocity in kTimeToStop seconds. Here is - // the derivation: - // kMaxVelocity * alpha ^ (# callbacks to stop in) = kDecayThreshold - // # callbacks = kTimeToStop / dt - // alpha = (kDecayThreshold / kMaxVelocity) ^ (dt / kTimeToStop) - const double kExponentialDecay = pow(kDecayThreshold / kMaxVelocity, dt / kTimeToStop); + // Tweak PID controller for different latencies + double p; + double i; + double d; + if (dt > 0.015) { + // High latency + p = 0.3; + i = 0; + d = 0; + } else { + // Low latency + p = 17 * dt; // ~ 0.2 for 11,6 ms + i = 0; + d = 0; + } + m_pVelocityController->setPID(p, i, d); if (m_bScratching) { - if (scratchEnable || m_bEnableInertia) { - // We were previously in scratch mode and are still in scratch mode - // OR we are in inertia mode. - - if (scratchEnable) { - // If we're scratching, clear the inertia flag. This case should - // have been caught by the 'enable' case below, but just to make - // sure. + if (m_bEnableInertia) { + // If we got here then we're not scratching and we're in inertia + // mode. Take the previous rate that was set and apply a + // deceleration. + + // If we're playing, then do not decay rate below 1. If we're not playing, + // then we want to decay all the way down to below 0.01 + const double kDecayThreshold = paused ? 0.01 : 1.0; + + // Max velocity we would like to stop in a given time period. + const double kMaxVelocity = 100; + // Seconds to stop a throw at the max velocity. + const double kTimeToStop = 2.0; + + // We calculate the exponential decay constant based on the above + // constants. Roughly we backsolve what the decay should be if we want to + // stop a throw of max velocity kMaxVelocity in kTimeToStop seconds. Here is + // the derivation: + // kMaxVelocity * alpha ^ (# callbacks to stop in) = kDecayThreshold + // # callbacks = kTimeToStop / dt + // alpha = (kDecayThreshold / kMaxVelocity) ^ (dt / kTimeToStop) + const double kExponentialDecay = pow(kDecayThreshold / kMaxVelocity, dt / kTimeToStop); + + m_dRate *= kExponentialDecay; + + // If the rate has decayed below the threshold, or scartching is + // reanabled then leave inertia mode. + if (fabs(m_dRate) < kDecayThreshold || scratchEnable) { m_bEnableInertia = false; - - // Increment processing timer by time delta. - m_dScratchTime += dt; - - // Set the scratch target to the current set position - m_pVelocityController->setTarget(scratchPosition); - - // Measure the total distance travelled since last frame and add - // it to the running total. - m_dPositionDeltaSum += (currentSample - m_dLastPlaypos); - - m_dRate = m_pVelocityController->observation( - m_dPositionDeltaSum, m_dScratchTime); - //qDebug() << "continue" << m_dRate << iBufferSize; - } else { - // If we got here then we're not scratching and we're in inertia - // mode. Take the previous rate that was set and apply a - // deceleration. - m_dRate *= kExponentialDecay; - - // If the rate has decayed below the threshold, then leave - // inertia mode. - if (fabs(m_dRate) <= kDecayThreshold) { - m_bEnableInertia = false; - } - } + m_bScratching = false; + } + } else if (scratchEnable) { + // If we're scratching, clear the inertia flag. This case should + // have been caught by the 'enable' case below, but just to make + // sure. + m_bEnableInertia = false; + + // Set the scratch target to the current set position + // and normalize to one buffer + double targetDelta = (scratchPosition - m_dStartScratchPosition) / + (iBufferSize * baserate); + + // Measure the total distance traveled since last frame and add + // it to the running total. This is required to scratch within loop + // boundaries. Normalize to one buffer + m_dPositionDeltaSum += (currentSample - m_dLastPlaypos) / + (iBufferSize * baserate); + + m_dRate = m_pVelocityController->observation( + m_dPositionDeltaSum, targetDelta, dt); + //qDebug() << m_dRate << scratchPosition << targetDelta - m_dPositionDeltaSum << targetDelta << m_dPositionDeltaSum << dt; } else { // We were previously in scratch mode and are no longer in scratch // mode. Disable everything, or optionally enable inertia mode if // the previous rate was high enough to count as a 'throw' + + // The rate threshold above which disabling position scratching will enable + // an 'inertia' mode. + const double kThrowThreshold = 2.5; + if (fabs(m_dRate) > kThrowThreshold) { m_bEnableInertia = true; } else { - m_dRate = 0; m_bScratching = false; - m_dScratchTime = 0; } //qDebug() << "disable"; } - } else { - if (scratchEnable) { + } else if (scratchEnable) { // We were not previously in scratch mode but now are in scratch // mode. Enable scratching. m_bScratching = true; m_bEnableInertia = false; - m_dPositionDeltaSum = 0; - m_dScratchTime = 0; - m_pVelocityController->reset(0, m_dScratchTime, scratchPosition); - m_dRate = m_pVelocityController->observation(0, m_dScratchTime); - //qDebug() << "enable" << m_dRate << currentSample; - } else { - // We were not previously in scratch mode are still not in scratch - // mode. Do nothing - } - } + if (paused) { + m_dRate = 0; + m_dPositionDeltaSum = 0; + } else { + // deck is playing, start contoller in settled state + // with the remaining error of a P Controller + m_dRate = 1; + m_dPositionDeltaSum = -(1 / p); + } + + m_dStartScratchPosition = scratchPosition; + m_pVelocityController->reset(); + //qDebug() << "scratchEnable()" << currentSample; + } m_dLastPlaypos = currentSample; } bool PositionScratchController::isEnabled() { + // return true only if m_dRate is valid. return m_bScratching; } @@ -234,5 +228,7 @@ } void PositionScratchController::notifySeek(double currentSample) { + // scratching continues after seek due to calculating the relative distance traveled + // in m_dPositionDeltaSum m_dLastPlaypos = currentSample; } === modified file 'mixxx/src/engine/positionscratchcontroller.h' --- mixxx/src/engine/positionscratchcontroller.h 2012-11-21 04:45:02 +0000 +++ mixxx/src/engine/positionscratchcontroller.h 2013-02-16 20:16:13 +0000 @@ -13,7 +13,8 @@ PositionScratchController(const char* pGroup); virtual ~PositionScratchController(); - void process(double currentSample, bool paused, int iBufferSize); + void process(double currentSample, bool paused, + int iBufferSize, double baserate); bool isEnabled(); double getRate(); void notifySeek(double currentSample); @@ -23,15 +24,12 @@ ControlObject* m_pScratchEnable; ControlObject* m_pScratchPosition; ControlObject* m_pMasterSampleRate; - ControlObject* m_pScratchControllerP; - ControlObject* m_pScratchControllerI; - ControlObject* m_pScratchControllerD; VelocityController* m_pVelocityController; bool m_bScratching; bool m_bEnableInertia; double m_dLastPlaypos; double m_dPositionDeltaSum; - double m_dScratchTime; + double m_dStartScratchPosition; double m_dRate; }; === modified file 'mixxx/src/engine/ratecontrol.cpp' --- mixxx/src/engine/ratecontrol.cpp 2012-11-20 00:40:18 +0000 +++ mixxx/src/engine/ratecontrol.cpp 2013-02-13 22:34:32 +0000 @@ -30,18 +30,18 @@ enum RateControl::RATERAMP_MODE RateControl::m_eRateRampMode = RateControl::RATERAMP_STEP; RateControl::RateControl(const char* _group, - ConfigObject* _config) : - EngineControl(_group, _config), - m_bVinylControlEnabled(false), - m_bVinylControlScratching(false), - m_ePbCurrent(0), - m_ePbPressed(0), - m_bTempStarted(false), - m_dTempRateChange(0.0), - m_dRateTemp(0.0), - m_eRampBackMode(RATERAMP_RAMPBACK_NONE), - m_dRateTempRampbackChange(0.0), - m_dOldRate(0.0f) { + ConfigObject* _config) + : EngineControl(_group, _config), + m_bVinylControlEnabled(false), + m_bVinylControlScratching(false), + m_ePbCurrent(0), + m_ePbPressed(0), + m_bTempStarted(false), + m_dTempRateChange(0.0), + m_dRateTemp(0.0), + m_eRampBackMode(RATERAMP_RAMPBACK_NONE), + m_dRateTempRampbackChange(0.0), + m_dOldRate(0.0f) { m_pScratchController = new PositionScratchController(_group); m_pRateDir = new ControlObject(ConfigKey(_group, "rate_dir")); @@ -396,7 +396,7 @@ } double currentSample = getCurrentSample(); - m_pScratchController->process(currentSample, paused, iSamplesPerBuffer); + m_pScratchController->process(currentSample, paused, iSamplesPerBuffer, baserate); // If position control is enabled, override scratchFactor if (m_pScratchController->isEnabled()) { @@ -416,8 +416,11 @@ } else if (paused) { // Stopped. Wheel, jog and scratch controller all scrub through audio. // New scratch behavior overrides old - if (scratchEnable) rate = scratchFactor + jogFactor + wheelFactor*40.0; - else rate = oldScratchFactor + jogFactor*18 + wheelFactor; // Just remove oldScratchFactor in future + if (scratchEnable) { + rate = scratchFactor + jogFactor + wheelFactor*40.0; + } else { + rate = oldScratchFactor + jogFactor*18 + wheelFactor; // Just remove oldScratchFactor in future + } } else { // The buffer is playing, so calculate the buffer rate. @@ -431,8 +434,9 @@ rate += wheelFactor; // New scratch behavior - overrides playback speed (and old behavior) - if (scratchEnable) rate = scratchFactor; - else { + if (scratchEnable) { + rate = scratchFactor; + } else { // Deprecated old scratch behavior if (oldScratchFactor < 0.) { rate *= (oldScratchFactor-1.); === modified file 'mixxx/src/waveform/renderers/qtwaveformrendererfilteredsignal.cpp' --- mixxx/src/waveform/renderers/qtwaveformrendererfilteredsignal.cpp 2013-01-16 21:03:36 +0000 +++ mixxx/src/waveform/renderers/qtwaveformrendererfilteredsignal.cpp 2013-02-13 20:54:44 +0000 @@ -267,7 +267,7 @@ } } - //If channel are not displyed separatly we nne to close the loop properly + //If channel are not displayed separately we need to close the loop properly if (channelSeparation == 1) { setPoint(m_polygon[0][pointIndex], m_waveformRenderer->getWidth(), 0.0); setPoint(m_polygon[1][pointIndex], m_waveformRenderer->getWidth(), 0.0); === modified file 'mixxx/src/widget/wwaveformviewer.cpp' --- mixxx/src/widget/wwaveformviewer.cpp 2013-01-31 21:53:32 +0000 +++ mixxx/src/widget/wwaveformviewer.cpp 2013-02-13 20:54:44 +0000 @@ -30,9 +30,9 @@ connect(m_pZoom, SIGNAL(valueChanged(double)), this, SLOT(onZoomChange(double))); - m_pScratchEnable = new ControlObjectThreadMain( + m_pScratchPositionEnable = new ControlObjectThreadMain( ControlObject::getControl(ConfigKey(group, "scratch_position_enable"))); - m_pScratch = new ControlObjectThreadMain( + m_pScratchPosition = new ControlObjectThreadMain( ControlObject::getControl(ConfigKey(group, "scratch_position"))); setAttribute(Qt::WA_OpaquePaintEvent); @@ -45,8 +45,8 @@ //qDebug() << "~WWaveformViewer"; delete m_pZoom; - delete m_pScratchEnable; - delete m_pScratch; + delete m_pScratchPositionEnable; + delete m_pScratchPosition; } void WWaveformViewer::setup(QDomNode node) { @@ -71,14 +71,13 @@ m_bBending = false; } m_bScratching = true; - m_pScratch->slotSet(0.0f); - m_pScratchEnable->slotSet(1.0f); + m_pScratchPosition->slotSet(0.0f); + m_pScratchPositionEnable->slotSet(1.0f); } else if (event->button() == Qt::RightButton) { // If we are scratching then disable and reset because the two shouldn't // be used at once. if (m_bScratching) { - m_pScratch->slotSet(0.0f); - m_pScratchEnable->slotSet(0.0f); + m_pScratchPositionEnable->slotSet(0.0f); m_bScratching = false; } emit(valueChangedRightDown(64)); @@ -98,10 +97,10 @@ double audioSamplePerPixel = m_waveformWidget->getAudioSamplePerPixel(); double targetPosition = -1.0 * diff.x() * audioSamplePerPixel * 2; //qDebug() << "Target:" << targetPosition; - m_pScratch->slotSet(targetPosition); + m_pScratchPosition->slotSet(targetPosition); } else if (m_bBending) { // start at the middle of 0-127, and emit values based on - // how far the mouse has travelled horizontally + // how far the mouse has traveled horizontally double v = 64.0 + diff.x()/10.0f; // clamp to [0, 127] v = math_min(127.0, math_max(0.0, v)); @@ -109,10 +108,10 @@ } } -void WWaveformViewer::mouseReleaseEvent(QMouseEvent* /*event*/){ +void WWaveformViewer::mouseReleaseEvent(QMouseEvent* /*event*/) { if (m_bScratching) { - m_pScratchEnable->slotSet(0.0f); - m_pScratch->slotSet(0.0f); + m_pScratchPosition->slotSet(0.0f); + m_pScratchPositionEnable->slotSet(0.0f); m_bScratching = false; } if (m_bBending) { === modified file 'mixxx/src/widget/wwaveformviewer.h' --- mixxx/src/widget/wwaveformviewer.h 2013-01-31 21:53:32 +0000 +++ mixxx/src/widget/wwaveformviewer.h 2013-02-13 20:54:44 +0000 @@ -65,8 +65,8 @@ ConfigObject* m_pConfig; int m_zoomZoneWidth; ControlObjectThreadMain* m_pZoom; - ControlObjectThreadMain* m_pScratchEnable; - ControlObjectThreadMain* m_pScratch; + ControlObjectThreadMain* m_pScratchPositionEnable; + ControlObjectThreadMain* m_pScratchPosition; bool m_bScratching; bool m_bBending; QPoint m_mouseAnchor;