Пример #1
0
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);
    }
}
Пример #2
0
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);
        }
    }
}
Пример #3
0
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;
}
Пример #4
0
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;
}
Пример #5
0
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);
    }
}
Пример #6
0
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);
    }
}
Пример #7
0
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;
}