BeatsPointer BeatFactory::makePreferredBeats( TrackPointer pTrack, QVector<double> beats, const QHash<QString, QString> extraVersionInfo, const bool bEnableFixedTempoCorrection, const bool bEnableOffsetCorrection, const int iSampleRate, const int iTotalSamples, const int iMinBpm, const int iMaxBpm) { const QString version = getPreferredVersion(bEnableFixedTempoCorrection); const QString subVersion = getPreferredSubVersion(bEnableFixedTempoCorrection, bEnableOffsetCorrection, iMinBpm, iMaxBpm, extraVersionInfo); BeatUtils::printBeatStatistics(beats, iSampleRate); if (version == BEAT_GRID_2_VERSION) { double globalBpm = BeatUtils::calculateBpm(beats, iSampleRate, iMinBpm, iMaxBpm); double firstBeat = BeatUtils::calculateFixedTempoFirstBeat( bEnableOffsetCorrection, beats, iSampleRate, iTotalSamples, globalBpm); BeatGrid* pGrid = new BeatGrid(pTrack.data(), iSampleRate); // firstBeat is in frames here and setGrid() takes samples. pGrid->setGrid(globalBpm, firstBeat * 2); pGrid->setSubVersion(subVersion); return BeatsPointer(pGrid, &BeatFactory::deleteBeats); } else if (version == BEAT_MAP_VERSION) { BeatMap* pBeatMap = new BeatMap(pTrack, iSampleRate, beats); pBeatMap->setSubVersion(subVersion); return BeatsPointer(pBeatMap, &BeatFactory::deleteBeats); } else { qDebug() << "ERROR: Could not determine what type of beatgrid to create."; return BeatsPointer(); } }
void TrackInfoObject::setBpm(double f) { if (f < 0) { return; } QMutexLocker lock(&m_qMutex); // TODO(rryan): Assume always dirties. bool dirty = false; if (f == 0.0) { // If the user sets the BPM to 0, we assume they want to clear the // beatgrid. setBeats(BeatsPointer()); dirty = true; } else if (!m_pBeats) { setBeats(BeatFactory::makeBeatGrid(this, f, 0)); dirty = true; } else if (m_pBeats->getBpm() != f) { m_pBeats->setBpm(f); dirty = true; } if (dirty) { setDirty(true); } lock.unlock(); // Tell the GUI to update the bpm label... //qDebug() << "TrackInfoObject signaling BPM update to" << f; emit(bpmUpdated(f)); }
BeatsPointer BeatFactory::loadBeatsFromByteArray(TrackPointer pTrack, QString beatsVersion, QString beatsSubVersion, QByteArray* beatsSerialized) { if (beatsVersion == BEAT_GRID_1_VERSION || beatsVersion == BEAT_GRID_2_VERSION) { BeatGrid* pGrid = new BeatGrid(pTrack.data(), 0, beatsSerialized); pGrid->setSubVersion(beatsSubVersion); qDebug() << "Successfully deserialized BeatGrid"; return BeatsPointer(pGrid, &BeatFactory::deleteBeats); } else if (beatsVersion == BEAT_MAP_VERSION) { BeatMap* pMap = new BeatMap(pTrack, 0, beatsSerialized); pMap->setSubVersion(beatsSubVersion); qDebug() << "Successfully deserialized BeatMap"; return BeatsPointer(pMap, &BeatFactory::deleteBeats); } qDebug() << "BeatFactory::loadBeatsFromByteArray could not parse serialized beats."; return BeatsPointer(); }
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; }
BeatsPointer BeatFactory::makeBeatGrid(Track* pTrack, double dBpm, double dFirstBeatSample) { BeatGrid* pGrid = new BeatGrid(pTrack, 0); pGrid->setGrid(dBpm, dFirstBeatSample); return BeatsPointer(pGrid, &BeatFactory::deleteBeats); }
bool BpmControl::syncPhase(EngineBuffer* pOtherEngineBuffer) { if (!pOtherEngineBuffer) { return false; } 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 (!m_pBeats || !otherBeats) { return false; } // Get the file BPM of each song. //double dThisBpm = m_pBeats->getBpm(); //double dOtherBpm = ControlObject::getControl( //ConfigKey(pOtherEngineBuffer->getGroup(), "file_bpm"))->get(); // Get the current position of both decks double dThisPosition = getCurrentSample(); double dOtherLength = ControlObject::getControl( ConfigKey(pOtherEngineBuffer->getGroup(), "track_samples"))->get(); double dOtherEnginePlayPos = ControlObject::getControl( ConfigKey(pOtherEngineBuffer->getGroup(), "visual_playposition"))->get(); double dOtherPosition = dOtherLength * dOtherEnginePlayPos; double dThisPrevBeat = m_pBeats->findPrevBeat(dThisPosition); double dThisNextBeat = m_pBeats->findNextBeat(dThisPosition); if (dThisPrevBeat == -1 || dThisNextBeat == -1 || dOtherEnginePlayPos == -1 || dOtherLength == 0) { return false; } // Protect against the case where we are sitting exactly on the beat. if (dThisPrevBeat == dThisNextBeat) { dThisNextBeat = m_pBeats->findNthBeat(dThisPosition, 2); } double dOtherPrevBeat = otherBeats->findPrevBeat(dOtherPosition); double dOtherNextBeat = otherBeats->findNextBeat(dOtherPosition); if (dOtherPrevBeat == -1 || dOtherNextBeat == -1) { return false; } // Protect against the case where we are sitting exactly on the beat. if (dOtherPrevBeat == dOtherNextBeat) { dOtherNextBeat = otherBeats->findNthBeat(dOtherPosition, 2); } double dThisBeatLength = fabs(dThisNextBeat - dThisPrevBeat); double dOtherBeatLength = fabs(dOtherNextBeat - dOtherPrevBeat); double dOtherBeatFraction = (dOtherPosition - dOtherPrevBeat) / dOtherBeatLength; double dNewPlaypos; bool this_near_next = dThisNextBeat - dThisPosition <= dThisPosition - dThisPrevBeat; bool other_near_next = dOtherNextBeat - dOtherPosition <= dOtherPosition - dOtherPrevBeat; // 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. if (this_near_next == other_near_next) { dNewPlaypos = dThisPrevBeat + dOtherBeatFraction * dThisBeatLength; } else if (this_near_next && !other_near_next) { dNewPlaypos = dThisNextBeat + dOtherBeatFraction * dThisBeatLength; } else { //!this_near_next && other_near_next dThisPrevBeat = m_pBeats->findNthBeat(dThisPosition, -2); dNewPlaypos = dThisPrevBeat + dOtherBeatFraction * dThisBeatLength; } // We might be seeking outside the loop. const bool loop_enabled = m_pLoopEnabled->get() > 0.0; 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) { const double loop_length = loop_end_position - loop_start_position; if (loop_length <= 0.0) { return false; } // TODO(rryan): If loop_length is not a multiple of dThisBeatLength should // we bail and not sync phase? // Syncing to after the loop end. double end_delta = dNewPlaypos - loop_end_position; if (end_delta > 0) { int i = end_delta / loop_length; dNewPlaypos = loop_start_position + end_delta - i * loop_length; } // Syncing to before the loop beginning. double start_delta = loop_start_position - dNewPlaypos; if (start_delta > 0) { int i = start_delta / loop_length; dNewPlaypos = loop_end_position - start_delta + i * loop_length; } } seekAbs(dNewPlaypos); return true; }