void SyncControl::slotSyncMasterEnabledChangeRequest(double state) { bool currentlyMaster = getSyncMode() == SYNC_MASTER; if (state > 0.0) { if (currentlyMaster) { // Already master. return; } if (m_pPassthroughEnabled->get()) { qDebug() << "Disallowing enabling of sync mode when passthrough active"; return; } m_pChannel->getEngineBuffer()->requestSyncMode(SYNC_MASTER); } else { // Turning off master goes back to follower mode. if (!currentlyMaster) { // Already not master. return; } m_pChannel->getEngineBuffer()->requestSyncMode(SYNC_FOLLOWER); } }
void SyncControl::trackLoaded(TrackPointer pNewTrack) { //qDebug() << getGroup() << "SyncControl::trackLoaded"; if (getSyncMode() == SYNC_MASTER) { // If we change or remove a new track while master, hand off. m_pChannel->getEngineBuffer()->requestSyncMode(SYNC_NONE); } if (pNewTrack) { m_masterBpmAdjustFactor = kBpmUnity; if (isSynchronized()) { // Because of the order signals get processed, the file/local_bpm COs and // rate slider are not updated as soon as we need them, so do that now. m_pFileBpm->set(pNewTrack->getBpm()); m_pLocalBpm->set(pNewTrack->getBpm()); double dRate = calcRateRatio(); // We used to set the m_pBpm here, but that causes a signal loop whereby // that was interpreted as a rate slider tweak, and the master bpm // was changed. Instead, now we pass the suggested bpm to enginesync // explicitly, and it can decide what to do with it. m_pEngineSync->notifyTrackLoaded(this, m_pLocalBpm->get() * dRate); } } }
double BpmControl::getNearestPositionInPhase(double dThisPosition, bool respectLoops, bool playing) { // Without a beatgrid, we don't know the phase offset. if (!m_pBeats) { return dThisPosition; } // Master buffer is always in sync! if (getSyncMode() == SYNC_MASTER) { return dThisPosition; } // Get the current position of this deck. double dThisPrevBeat = m_pPrevBeat->get(); double dThisNextBeat = m_pNextBeat->get(); double dThisBeatLength; if (dThisPosition > dThisNextBeat || dThisPosition < dThisPrevBeat) { // There's a chance the COs might be out of date, so do a lookup. // TODO: figure out a way so that quantized control can take care of // this so this call isn't necessary. if (!getBeatContext(m_pBeats, dThisPosition, &dThisPrevBeat, &dThisNextBeat, &dThisBeatLength, NULL)) { return dThisPosition; } } else { if (!getBeatContextNoLookup(dThisPosition, dThisPrevBeat, dThisNextBeat, &dThisBeatLength, NULL)) { return dThisPosition; } } double dOtherBeatFraction; if (getSyncMode() == SYNC_FOLLOWER) { // If we're a follower, it's easy to get the other beat fraction dOtherBeatFraction = m_dSyncTargetBeatDistance; } else { // If not, we have to figure it out EngineBuffer* pOtherEngineBuffer = pickSyncTarget(); if (pOtherEngineBuffer == NULL) { return dThisPosition; } if (playing) { // "this" track is playing, or just starting // only match phase if the sync target is playing as well if (pOtherEngineBuffer->getSpeed() == 0.0) { return dThisPosition; } } TrackPointer otherTrack = pOtherEngineBuffer->getLoadedTrack(); BeatsPointer otherBeats = otherTrack ? otherTrack->getBeats() : BeatsPointer(); // If either track does not have beats, then we can't adjust the phase. if (!otherBeats) { return dThisPosition; } double dOtherLength = ControlObject::getControl( ConfigKey(pOtherEngineBuffer->getGroup(), "track_samples"))->get(); double dOtherEnginePlayPos = pOtherEngineBuffer->getVisualPlayPos(); double dOtherPosition = dOtherLength * dOtherEnginePlayPos; if (!BpmControl::getBeatContext(otherBeats, dOtherPosition, NULL, NULL, NULL, &dOtherBeatFraction)) { return dThisPosition; } } bool this_near_next = dThisNextBeat - dThisPosition <= dThisPosition - dThisPrevBeat; bool other_near_next = dOtherBeatFraction >= 0.5; // We want our beat fraction to be identical to theirs. // If the two tracks have similar alignment, adjust phase is straight- // forward. Use the same fraction for both beats, starting from the previous // beat. But if This track is nearer to the next beat and the Other track // is nearer to the previous beat, use This Next beat as the starting point // for the phase. (ie, we pushed the sync button late). If This track // is nearer to the previous beat, but the Other track is nearer to the // next beat, we pushed the sync button early so use the double-previous // beat as the basis for the adjustment. // // This makes way more sense when you're actually mixing. // // TODO(XXX) Revisit this logic once we move away from tempo-locked, // infinite beatgrids because the assumption that findNthBeat(-2) always // works will be wrong then. double dNewPlaypos = (dOtherBeatFraction + m_dUserOffset) * dThisBeatLength; if (this_near_next == other_near_next) { dNewPlaypos += dThisPrevBeat; } else if (this_near_next && !other_near_next) { dNewPlaypos += dThisNextBeat; } else { //!this_near_next && other_near_next dThisPrevBeat = m_pBeats->findNthBeat(dThisPosition, -2); dNewPlaypos += dThisPrevBeat; } if (respectLoops) { // We might be seeking outside the loop. const bool loop_enabled = m_pLoopEnabled->toBool(); const double loop_start_position = m_pLoopStartPosition->get(); const double loop_end_position = m_pLoopEndPosition->get(); // Cases for sanity: // // CASE 1 // Two identical 1-beat loops, out of phase by X samples. // Other deck is at its loop start. // This deck is half way through. We want to jump forward X samples to the loop end point. // // Two identical 1-beat loop, out of phase by X samples. // Other deck is // If sync target is 50% through the beat, // If we are at the loop end point and hit sync, jump forward X samples. // TODO(rryan): Revise this with something that keeps a broader number of // cases in sync. This at least prevents breaking out of the loop. if (loop_enabled && dThisPosition <= loop_end_position) { const double loop_length = loop_end_position - loop_start_position; const double end_delta = dNewPlaypos - loop_end_position; // Syncing to after the loop end. if (end_delta > 0 && loop_length > 0.0) { int i = end_delta / loop_length; dNewPlaypos = loop_start_position + end_delta - i * loop_length; // Move new position after loop jump into phase as well. // This is a recursive call, called only twice because of // respectLoops = false dNewPlaypos = getNearestPositionInPhase(dNewPlaypos, false, playing); } // Note: Syncing to before the loop beginning is allowed, because // loops are catching } } return dNewPlaypos; }
double RateControl::calculateRate(double baserate, bool paused, int iSamplesPerBuffer, bool* isScratching) { double rate = (paused ? 0 : 1.0); double searching = m_pRateSearch->get(); if (searching) { // If searching is in progress, it overrides everything else rate = searching; } else { double wheelFactor = getWheelFactor(); double jogFactor = getJogFactor(); bool bVinylControlEnabled = m_pVCEnabled && m_pVCEnabled->get() > 0.0; bool scratchEnable = m_pScratchToggle->get() != 0 || bVinylControlEnabled; double scratchFactor = m_pScratch->get(); // Don't trust values from m_pScratch if (isnan(scratchFactor)) { scratchFactor = 0.0; } // Old Scratch works without scratchEnable double oldScratchFactor = m_pOldScratch->get(); // Deprecated // Don't trust values from m_pScratch if (isnan(oldScratchFactor)) { oldScratchFactor = 0.0; } // If vinyl control is enabled and scratching then also set isScratching bool bVinylControlScratching = m_pVCScratching && m_pVCScratching->get() > 0.0; if (bVinylControlEnabled && bVinylControlScratching) { *isScratching = true; } 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 { // Just remove oldScratchFactor in future rate = oldScratchFactor + jogFactor * 18 + wheelFactor; } } else { // The buffer is playing, so calculate the buffer rate. // There are four rate effects we apply: wheel, scratch, jog and temp. // Wheel: a linear additive effect (no spring-back) // Scratch: a rate multiplier // Jog: a linear additive effect whose value is filtered (springs back) // Temp: pitch bend // New scratch behavior - overrides playback speed (and old behavior) if (scratchEnable) { rate = scratchFactor; } else { rate = 1. + getRawRate() + getTempRate(); rate += wheelFactor; // Deprecated old scratch behavior if (oldScratchFactor < 0.) { rate *= (oldScratchFactor - 1.); } else if (oldScratchFactor > 0.) { rate *= (oldScratchFactor + 1.); } } rate += jogFactor; } double currentSample = getCurrentSample(); m_pScratchController->process(currentSample, rate, iSamplesPerBuffer, baserate); // If waveform scratch is enabled, override all other controls if (m_pScratchController->isEnabled()) { rate = m_pScratchController->getRate(); *isScratching = true; } // If master sync is on, respond to it -- but vinyl and scratch mode always override. if (getSyncMode() == SYNC_FOLLOWER && !paused && !bVinylControlEnabled && !*isScratching) { if (m_pBpmControl == NULL) { qDebug() << "ERROR: calculateRate m_pBpmControl is null during master sync"; return 1.0; } rate = m_pBpmControl->getSyncedRate(); double userTweak = getTempRate() + wheelFactor + jogFactor; bool userTweakingSync = userTweak != 0.0; rate += userTweak; rate *= m_pBpmControl->getSyncAdjustment(userTweakingSync); } // If we are reversing (and not scratching,) flip the rate. This is ok even when syncing. if (!scratchEnable && m_pReverseButton->get()) { rate = -rate; } } return rate; }
void SyncControl::slotPassthroughChanged(double enabled) { if (enabled && getSyncMode() != SYNC_NONE) { // If passthrough was enabled and sync was on, disable it. m_pChannel->getEngineBuffer()->requestSyncMode(SYNC_NONE); } }
void SyncControl::slotVinylControlChanged(double enabled) { if (enabled && getSyncMode() == SYNC_FOLLOWER) { // If vinyl control was enabled and we're a follower, disable sync mode. m_pChannel->getEngineBuffer()->requestSyncMode(SYNC_NONE); } }
double RateControl::calculateSpeed(double baserate, double speed, bool paused, int iSamplesPerBuffer, bool* pReportScratching, bool* pReportReverse) { *pReportScratching = false; *pReportReverse = false; double rate = (paused ? 0 : 1.0); double searching = m_pRateSearch->get(); if (searching) { // If searching is in progress, it overrides everything else rate = searching; } else { double wheelFactor = getWheelFactor(); double jogFactor = getJogFactor(); bool bVinylControlEnabled = m_pVCEnabled && m_pVCEnabled->toBool(); bool useScratch2Value = m_pScratch2Enable->get() != 0; // By default scratch2_enable is enough to determine if the user is // scratching or not. Moving platter controllers have to disable // "scratch2_indicates_scratching" if they are not scratching, // to allow things like key-lock. if (useScratch2Value && m_pScratch2Scratching->get()) { *pReportScratching = true; } if (bVinylControlEnabled) { if (m_pVCScratching->toBool()) { *pReportScratching = true; } rate = speed; } else { double scratchFactor = m_pScratch2->get(); // Don't trust values from m_pScratch2 if (isnan(scratchFactor)) { scratchFactor = 0.0; } if (paused) { // Stopped. Wheel, jog and scratch controller all scrub through audio. if (useScratch2Value) { rate = scratchFactor + jogFactor + wheelFactor * kWheelMultiplier; } else { rate = jogFactor * kPausedJogMultiplier + wheelFactor; } } else { // The buffer is playing, so calculate the buffer rate. // There are four rate effects we apply: wheel, scratch, jog and temp. // Wheel: a linear additive effect (no spring-back) // Scratch: a rate multiplier // Jog: a linear additive effect whose value is filtered (springs back) // Temp: pitch bend // New scratch behavior - overrides playback speed (and old behavior) if (useScratch2Value) { rate = scratchFactor; } else { rate = speed + getTempRate(); rate += wheelFactor; } rate += jogFactor; } } double currentSample = getCurrentSample(); m_pScratchController->process(currentSample, rate, iSamplesPerBuffer, baserate); // If waveform scratch is enabled, override all other controls if (m_pScratchController->isEnabled()) { rate = m_pScratchController->getRate(); *pReportScratching = true; } else { // If master sync is on, respond to it -- but vinyl and scratch mode always override. if (getSyncMode() == SYNC_FOLLOWER && !paused && !bVinylControlEnabled && !useScratch2Value) { if (m_pBpmControl == NULL) { qDebug() << "ERROR: calculateRate m_pBpmControl is null during master sync"; return 1.0; } double userTweak = 0.0; if (!*pReportScratching) { // Only report user tweak if the user is not scratching. userTweak = getTempRate() + wheelFactor + jogFactor; } rate = m_pBpmControl->calcSyncedRate(userTweak); } // If we are reversing (and not scratching,) flip the rate. This is ok even when syncing. // Reverse with vinyl is only ok if absolute mode isn't on. int vcmode = m_pVCMode ? m_pVCMode->get() : MIXXX_VCMODE_ABSOLUTE; // TODO(owen): Instead of just ignoring reverse mode, should we // disable absolute mode instead? if (m_pReverseButton->get() && !m_pScratch2Enable->get() && (!bVinylControlEnabled || vcmode != MIXXX_VCMODE_ABSOLUTE)) { rate = -rate; *pReportReverse = true; } } } return rate; }