void CueControl::updateIndicators() { // No need for mutex lock because we are only touching COs. double cueMode = m_pCueMode->get(); if (cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) { // Cue button is only lit at cue point bool playing = m_pPlayButton->get() > 0; if (isTrackAtCue()) { // at cue point if (!playing) { m_pCueIndicator->setBlinkValue(ControlIndicator::ON); m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); } } else { m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); if (!playing) { if (getCurrentSample() < getTotalSamples() && cueMode != CUE_MODE_NUMARK) { // Play will move cue point m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } else { // At track end m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); } } } } else { // Here we have CUE_MODE_PIONEER or CUE_MODE_MIXXX // default to Pioneer mode if (!m_bPreviewing) { bool playing = m_pPlayButton->get() > 0; if (!playing) { if (!isTrackAtCue()) { if (getCurrentSample() < getTotalSamples()) { if (cueMode == CUE_MODE_MIXXX) { // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } else { // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_250MS); } } else { // At track end m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); } } else if (m_pCuePoint->get() != -1) { // Next Press is preview m_pCueIndicator->setBlinkValue(ControlIndicator::ON); } } } } }
void LoopingControl::slotLoopMove(double beats) { if (!m_pTrack || !m_pBeats) { return; } double dPosition = getCurrentSample(); double dBeatLength; if (BpmControl::getBeatContext(m_pBeats, dPosition, NULL, NULL, &dBeatLength, NULL)) { int old_loop_in = m_iLoopStartSample; int old_loop_out = m_iLoopEndSample; int new_loop_in = m_iLoopStartSample + (beats * dBeatLength); int new_loop_out = m_iLoopEndSample + (beats * dBeatLength); // Should we reject any shift that goes out of bounds? m_iLoopStartSample = new_loop_in; if (m_pActiveBeatLoop) { // Ugly hack -- slotBeatLoop takes "true" to mean "keep starting // point". It gets that in-point from m_iLoopStartSample, // which we just changed so that the loop actually shifts. slotBeatLoop(m_pActiveBeatLoop->getSize(), true); } else { m_pCOLoopStartPosition->set(new_loop_in); m_iLoopEndSample = new_loop_out; m_pCOLoopEndPosition->set(new_loop_out); } seekInsideAdjustedLoop(old_loop_in, old_loop_out, new_loop_in, new_loop_out); } }
void CueControl::hotcueSet(HotcueControl* pControl, double v) { //qDebug() << "CueControl::hotcueSet" << v; if (!v) return; QMutexLocker lock(&m_mutex); if (!m_pLoadedTrack) return; int hotcue = pControl->getHotcueNumber(); detachCue(hotcue); Cue* pCue = m_pLoadedTrack->addCue(); double cuePosition = (m_pQuantizeEnabled->get() > 0.0 && m_pClosestBeat->get() != -1) ? floorf(m_pClosestBeat->get()) : floorf(getCurrentSample()); if (!even(cuePosition)) cuePosition--; pCue->setPosition(cuePosition); pCue->setHotCue(hotcue); pCue->setLabel(""); pCue->setType(Cue::CUE); // TODO(XXX) deal with spurious signals attachCue(pCue, hotcue); // If quantize is enabled and we are not playing, jump to the cue point // since it's not necessarily where we currently are. TODO(XXX) is this // potentially invalid for vinyl control? bool playing = m_pPlayButton->get() > 0; if (!playing && m_pQuantizeEnabled->get() > 0.0) { lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(cuePosition); } }
TEST_F(CueControlTest, SeekOnLoadDefault_NoCue) { m_pSeekOnLoadMode->slotSet(SEEK_ON_LOAD_DEFAULT); TrackPointer pTrack = createTestTrack(); loadTrack(pTrack); EXPECT_DOUBLE_EQ(-1.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(0.0, getCurrentSample()); // Set cue and check if track is seeked to it. pTrack->setCuePoint(CuePosition(200.0, Cue::MANUAL)); ProcessBuffer(); EXPECT_DOUBLE_EQ(200.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(200.0, getCurrentSample()); }
void CueControl::cueCDJ(double v) { // This is how Pioneer cue buttons work: // If pressed while playing, stop playback and go to cue. // If pressed while stopped and at cue, play while pressed. // If pressed while stopped and not at cue, set new cue point. // If play is pressed while holding cue, the deck is now playing. (Handled in playFromCuePreview().) QMutexLocker lock(&m_mutex); bool playing = (m_pPlayButton->get() == 1.0); if (v) { if (playing || getCurrentSample() >= getTotalSamples()) { // Jump to cue when playing or when at end position // Just in case. m_bPreviewing = false; m_pPlayButton->set(0.0); // Need to unlock before emitting any signals to prevent deadlock. lock.unlock(); seekAbs(m_pCuePoint->get()); } else if (isTrackAtCue()) { // pause at cue point m_bPreviewing = true; m_pPlayButton->set(1.0); } else { // Pause not at cue point and not at end position cueSet(v); // Just in case. m_bPreviewing = false; // If quantize is enabled, jump to the cue point since it's not // necessarily where we currently are if (m_pQuantizeEnabled->get() > 0.0) { lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(m_pCuePoint->get()); } } } else if (m_bPreviewing) { m_bPreviewing = false; m_pPlayButton->set(0.0); // Need to unlock before emitting any signals to prevent deadlock. lock.unlock(); seekAbs(m_pCuePoint->get()); } // indicator may flash because the delayed adoption of seekAbs // Correct the Indicator set via play if (m_pLoadedTrack && !playing) { m_pCueIndicator->setBlinkValue(ControlIndicator::ON); } else { m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); } }
TEST_F(CueControlTest, SeekOnLoadMainCue) { m_pSeekOnLoadMode->slotSet(SEEK_ON_LOAD_MAIN_CUE); TrackPointer pTrack = createTestTrack(); pTrack->setCuePoint(CuePosition(100.0, Cue::MANUAL)); loadTrack(pTrack); EXPECT_DOUBLE_EQ(100.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(100.0, getCurrentSample()); // Move cue and check if track is following it. pTrack->setCuePoint(CuePosition(200.0, Cue::MANUAL)); ProcessBuffer(); EXPECT_DOUBLE_EQ(200.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(200.0, getCurrentSample()); }
void BpmControl::slotBeatsTranslateMatchAlignment(double v) { if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { // Must reset the user offset *before* calling getPhaseOffset(), // otherwise it will always return 0 if master sync is active. m_dUserOffset = 0.0; double offset = getPhaseOffset(getCurrentSample()); m_pBeats->translate(-offset); } }
void CueControl::cueCDJ(double v) { /* This is how CDJ cue buttons work: * If pressed while playing, stop playback and go to cue. * If pressed while stopped and at cue, play while pressed. * If pressed while stopped and not at cue, set new cue point. * If play is pressed while holding cue, the deck is now playing. (Handled in playFromCuePreview().) */ QMutexLocker lock(&m_mutex); bool playing = (m_pPlayButton->get() == 1.0); double cuePoint = m_pCuePoint->get(); if (v) { if (playing) { m_pPlayButton->set(0.0); // Just in case. m_bPreviewing = false; // Need to unlock before emitting any signals to prevent deadlock. lock.unlock(); seekAbs(cuePoint); } else { if (fabs(getCurrentSample() - m_pCuePoint->get()) < 1.0f) { m_pPlayButton->set(1.0); m_bPreviewing = true; } else { cueSet(v); // Just in case. m_bPreviewing = false; // If quantize is enabled, jump to the cue point since it's not // necessarily where we currently are if (m_pQuantizeEnabled->get() > 0.0) { lock.unlock(); // prevent deadlock. seekAbs(m_pCuePoint->get()); } } } } else if (m_bPreviewing) { m_pPlayButton->set(0.0); m_bPreviewing = false; // Need to unlock before emitting any signals to prevent deadlock. lock.unlock(); seekAbs(cuePoint); } else { // Re-trigger the play button value so controllers get the correct one // after playFromCuePreview() changes it. m_pPlayButton->set(m_pPlayButton->get()); } }
void BpmControl::slotBeatsTranslate(double v) { if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) { double currentSample = getCurrentSample(); double closestBeat = m_pBeats->findClosestBeat(currentSample); int delta = currentSample - closestBeat; if (delta % 2 != 0) { delta--; } m_pBeats->translate(delta); } }
TEST_F(CueControlTest, SeekOnLoadZeroPos) { m_pSeekOnLoadMode->slotSet(SEEK_ON_LOAD_ZERO_POS); TrackPointer pTrack = createTestTrack(); pTrack->setCuePoint(CuePosition(100.0, Cue::MANUAL)); loadTrack(pTrack); EXPECT_DOUBLE_EQ(100.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(0.0, getCurrentSample()); }
TEST_F(CueControlTest, SeekOnLoadIntroCue) { m_pSeekOnLoadMode->slotSet(SEEK_ON_LOAD_INTRO_CUE); TrackPointer pTrack = createTestTrack(); auto pIntro = pTrack->createAndAddCue(); pIntro->setType(Cue::INTRO); pIntro->setSource(Cue::MANUAL); pIntro->setPosition(200.0); loadTrack(pTrack); EXPECT_DOUBLE_EQ(200.0, m_pIntroStartPosition->get()); EXPECT_DOUBLE_EQ(200.0, getCurrentSample()); // Move cue and check if track is following it. pIntro->setPosition(400.0); ProcessBuffer(); EXPECT_DOUBLE_EQ(400.0, m_pIntroStartPosition->get()); EXPECT_DOUBLE_EQ(400.0, getCurrentSample()); }
void CueControl::cueSet(double v) { if (!v) return; QMutexLocker lock(&m_mutex); double cue = (m_pQuantizeEnabled->get() > 0.0 && m_pClosestBeat->get() != -1) ? floorf(m_pClosestBeat->get()) : floorf(getCurrentSample()); if (!even(cue)) cue--; m_pCuePoint->set(cue); saveCuePoint(cue); }
void LoopingControl::slotBeatJump(double beats) { if (!m_pTrack || !m_pBeats) { return; } double dPosition = getCurrentSample(); double dBeatLength; if (BpmControl::getBeatContext(m_pBeats, dPosition, NULL, NULL, &dBeatLength, NULL)) { seekAbs(dPosition + beats * dBeatLength); } }
TEST_F(CueControlTest, SeekOnLoadDefault_CueRecallDisabled) { m_pSeekOnLoadMode->slotSet(SEEK_ON_LOAD_DEFAULT); // Note: CueRecall uses inverse logic (0 means enabled). config()->set(ConfigKey("[Controls]", "CueRecall"), ConfigValue(1)); TrackPointer pTrack = createTestTrack(); pTrack->setCuePoint(CuePosition(100.0, Cue::MANUAL)); loadTrack(pTrack); EXPECT_DOUBLE_EQ(100.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(0.0, getCurrentSample()); }
// Moves the cue point to current position or to closest beat in case // quantize is enabled void CueControl::cueSet(double v) { if (!v) return; QMutexLocker lock(&m_mutex); double closestBeat = m_pClosestBeat->get(); double cue = (m_pQuantizeEnabled->get() > 0.0 && closestBeat != -1) ? closestBeat : getCurrentSample(); m_pCuePoint->set(cue); TrackPointer pLoadedTrack = m_pLoadedTrack; lock.unlock(); // Store cue point in loaded track if (pLoadedTrack) { pLoadedTrack->setCuePoint(cue); } }
double BpmControl::updateLocalBpm() { double prev_local_bpm = m_pLocalBpm->get(); double local_bpm = 0; if (m_pBeats) { local_bpm = m_pBeats->getBpmAroundPosition(getCurrentSample(), kLocalBpmSpan); if (local_bpm == -1) { local_bpm = m_pFileBpm->get(); } } else { local_bpm = m_pFileBpm->get(); } if (local_bpm != prev_local_bpm) { m_pLocalBpm->set(local_bpm); slotUpdateEngineBpm(); } return local_bpm; }
// Moves the cue point to current position or to closest beat in case // quantize is enabled void CueControl::cueSet(double v) { if (!v) return; QMutexLocker lock(&m_mutex); double cue = (m_pQuantizeEnabled->get() > 0.0 && m_pClosestBeat->get() != -1) ? floor(m_pClosestBeat->get()) : floor(getCurrentSample()); if (!even(static_cast<int>(cue))) { cue--; } m_pCuePoint->set(cue); TrackPointer pLoadedTrack = m_pLoadedTrack; lock.unlock(); // Store cue point in loaded track if (pLoadedTrack) { pLoadedTrack->setCuePoint(cue); } }
double BpmControl::calcSyncedRate(double userTweak) { double rate = 1.0; // Don't know what to do if there's no bpm. if (m_pLocalBpm->get() != 0.0) { rate = m_dSyncInstantaneousBpm / m_pLocalBpm->get(); } // If we are not quantized, or there are no beats, or we're master, // or we're in reverse, just return the rate as-is. if (!m_pQuantize->get() || getSyncMode() == SYNC_MASTER || m_pBeats == NULL || m_pReverseButton->get()) { m_resetSyncAdjustment = true; return rate + userTweak; } // Now we need to get our beat distance so we can figure out how // out of phase we are. double dThisPosition = getCurrentSample(); double dBeatLength; double my_percentage; if (!BpmControl::getBeatContextNoLookup(dThisPosition, m_pPrevBeat->get(), m_pNextBeat->get(), &dBeatLength, &my_percentage)) { m_resetSyncAdjustment = true; return rate + userTweak; } // Now that we have our beat distance we can also check how large the // current loop is. If we are in a <1 beat loop, don't worry about offset. const bool loop_enabled = m_pLoopEnabled->toBool(); const double loop_size = (m_pLoopEndPosition->get() - m_pLoopStartPosition->get()) / dBeatLength; if (loop_enabled && loop_size < 1.0 && loop_size > 0) { m_resetSyncAdjustment = true; return rate + userTweak; } // Now we have all we need to calculate the sync adjustment if any. double adjustment = calcSyncAdjustment(my_percentage, userTweak != 0.0); return (rate + userTweak) * adjustment; }
void CueControl::cuePlay(double v) { // This is how CUP button works: // If freely playing (i.e. playing and platter NOT being touched), press to go to cue and stop. // If not freely playing (i.e. stopped or platter IS being touched), press to go to cue and stop. // On release, start playing from cue point. QMutexLocker lock(&m_mutex); const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching(); // pressed if (v) { if (freely_playing) { m_bPreviewing = false; m_pPlay->set(0.0); // Need to unlock before emitting any signals to prevent deadlock. lock.unlock(); seekAbs(m_pCuePoint->get()); } else if (!isTrackAtCue() && getCurrentSample() <= getTotalSamples()) { // Pause not at cue point and not at end position cueSet(v); // Just in case. m_bPreviewing = false; m_pPlay->set(0.0); // If quantize is enabled, jump to the cue point since it's not // necessarily where we currently are if (m_pQuantizeEnabled->get() > 0.0) { lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(m_pCuePoint->get()); } } } else if (isTrackAtCue()){ m_bPreviewing = false; m_pPlay->set(1.0); lock.unlock(); } }
void BpmControl::collectFeatures(GroupFeatureState* pGroupFeatures) const { double fileBpm = m_pFileBpm->get(); if (fileBpm > 0) { pGroupFeatures->has_file_bpm = true; pGroupFeatures->file_bpm = fileBpm; } double bpm = m_pEngineBpm->get(); if (bpm > 0) { pGroupFeatures->has_bpm = true; pGroupFeatures->bpm = bpm; } // Without a beatgrid we don't know any beat details. if (!m_pBeats) { return; } // Get the current position of this deck. double dThisPosition = getCurrentSample(); double dThisPrevBeat = m_pPrevBeat->get(); double dThisNextBeat = m_pNextBeat->get(); double dThisBeatLength; double dThisBeatFraction; if (getBeatContextNoLookup(dThisPosition, dThisPrevBeat, dThisNextBeat, &dThisBeatLength, &dThisBeatFraction)) { pGroupFeatures->has_prev_beat = true; pGroupFeatures->prev_beat = dThisPrevBeat; pGroupFeatures->has_next_beat = true; pGroupFeatures->next_beat = dThisNextBeat; pGroupFeatures->has_beat_length = true; pGroupFeatures->beat_length = dThisBeatLength; pGroupFeatures->has_beat_fraction = true; pGroupFeatures->beat_fraction = dThisBeatFraction; } }
void BpmControl::slotFileBpmChanged(double bpm) { Q_UNUSED(bpm); // Adjust the file-bpm with the current setting of the rate to get the // engine BPM. We only do this for SYNC_NONE decks because EngineSync will // set our BPM if the file BPM changes. See SyncControl::fileBpmChanged(). if (m_pBeats) { const double beats_bpm = m_pBeats->getBpmAroundPosition(getCurrentSample(), kLocalBpmSpan); if (beats_bpm != -1) { m_pLocalBpm->set(beats_bpm); } else { m_pLocalBpm->set(bpm); } } else { m_pLocalBpm->set(bpm); } if (getSyncMode() == SYNC_NONE) { slotUpdateEngineBpm(); } resetSyncAdjustment(); }
void CueControl::hotcueSet(HotcueControl* pControl, double v) { //qDebug() << "CueControl::hotcueSet" << v; if (!v) return; QMutexLocker lock(&m_mutex); if (!m_pLoadedTrack) return; int hotcue = pControl->getHotcueNumber(); // Note: the cue is just detached from the hotcue control // It remains in the database for later use // TODO: find a rule, that allows us to delete the cue as well // https://bugs.launchpad.net/mixxx/+bug/1653276 hotcueClear(pControl, v); CuePointer pCue(m_pLoadedTrack->createAndAddCue()); double closestBeat = m_pClosestBeat->get(); double cuePosition = (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? closestBeat : getCurrentSample(); pCue->setPosition(cuePosition); pCue->setHotCue(hotcue); pCue->setLabel(""); pCue->setType(Cue::CUE); // TODO(XXX) deal with spurious signals attachCue(pCue, hotcue); // If quantize is enabled and we are not playing, jump to the cue point // since it's not necessarily where we currently are. TODO(XXX) is this // potentially invalid for vinyl control? bool playing = m_pPlay->toBool(); if (!playing && m_pQuantizeEnabled->get() > 0.0) { lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(cuePosition); } }
void LoopingControl::slotLoopMove(double beats) { if (!m_pTrack || !m_pBeats) { return; } if (m_iLoopStartSample == kNoTrigger || m_iLoopEndSample == kNoTrigger) { return; } double dPosition = getCurrentSample(); double dBeatLength; if (BpmControl::getBeatContext(m_pBeats, dPosition, NULL, NULL, &dBeatLength, NULL)) { int old_loop_in = m_iLoopStartSample; int old_loop_out = m_iLoopEndSample; int new_loop_in = old_loop_in + (beats * dBeatLength); int new_loop_out = old_loop_out + (beats * dBeatLength); if (!even(new_loop_in)) { --new_loop_in; } if (!even(new_loop_out)) { --new_loop_out; } m_iLoopStartSample = new_loop_in; m_pCOLoopStartPosition->set(new_loop_in); m_iLoopEndSample = new_loop_out; m_pCOLoopEndPosition->set(new_loop_out); // If we are looping make sure that the play head does not leave the // loop as a result of our adjustment. if (m_bLoopingEnabled) { seekInsideAdjustedLoop(old_loop_in, old_loop_out, new_loop_in, new_loop_out); } } }
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; }
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; }
void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint) { int samples = m_pTrackSamples->get(); if (!m_pTrack || samples == 0) { clearActiveBeatLoop(); return; } if (!m_pBeats) { clearActiveBeatLoop(); return; } // For now we do not handle negative beatloops. if (beats < 0) { clearActiveBeatLoop(); return; } // O(n) search, but there are only ~10-ish beatloop controls so this is // fine. foreach (BeatLoopingControl* pBeatLoopControl, m_beatLoops) { if (pBeatLoopControl->getSize() == beats) { if (m_pActiveBeatLoop != pBeatLoopControl) { if (m_pActiveBeatLoop) { m_pActiveBeatLoop->deactivate(); } m_pActiveBeatLoop = pBeatLoopControl; } pBeatLoopControl->activate(); break; } } // give loop_in and loop_out defaults so we can detect problems int loop_in = -1; int loop_out = -1; // For positive numbers we start from the current position/closest beat and // create the loop around X beats from there. if (beats > 0) { if (keepStartPoint) { loop_in = m_iLoopStartSample; } else { // loop_in is set to the previous beat if quantize is on. The // closest beat might be ahead of play position which would cause a seek. // TODO: If in reverse, should probably choose nextBeat. double cur_pos = getCurrentSample(); double prevBeat = floorf(m_pBeats->findPrevBeat(cur_pos)); if (m_pQuantizeEnabled->get() > 0.0 && prevBeat != -1) { if (beats >= 1.0) { loop_in = prevBeat; } else { // In case of beat length less then 1 beat: // (| - beats, ^ - current track's position): // // ...|...................^........|... // // If we press 1/2 beatloop we want loop from 50% to 100%, // If I press 1/4 beatloop, we want loop from 50% to 75% etc double nextBeat = floorf(m_pBeats->findNextBeat(cur_pos)); double beat_len = nextBeat - prevBeat; double loops_per_beat = 1.0 / beats; double beat_pos = cur_pos - prevBeat; int beat_frac = static_cast<int>(floor((beat_pos / beat_len) * loops_per_beat)); loop_in = prevBeat + beat_len / loops_per_beat * beat_frac; } } else { loop_in = floorf(cur_pos); } if (!even(loop_in)) { loop_in--; } } int fullbeats = static_cast<int>(floorf(beats)); double fracbeats = beats - static_cast<double>(fullbeats); // Now we need to calculate the length of the beatloop. We do this by // taking the current beat and the fullbeats'th beat and measuring the // distance between them. loop_out = loop_in; if (fullbeats > 0) { // Add the length between this beat and the fullbeats'th beat to the // loop_out position; double this_beat = m_pBeats->findNthBeat(loop_in, 1); double nth_beat = m_pBeats->findNthBeat(loop_in, 1 + fullbeats); loop_out += (nth_beat - this_beat); } if (fracbeats > 0) { // Add the fraction of the beat following the current loop_out // position to loop out. double loop_out_beat = m_pBeats->findNthBeat(loop_out, 1); double loop_out_next_beat = m_pBeats->findNthBeat(loop_out, 2); loop_out += (loop_out_next_beat - loop_out_beat) * fracbeats; } } if ((loop_in == -1) || ( loop_out == -1)) return; if (!even(loop_in)) loop_in--; if (!even(loop_out)) loop_out--; if (loop_in == loop_out) { if ((loop_out+2) > samples) { loop_in -= 2; } else { loop_out += 2; } } else if (loop_out > samples) { // Do not allow beat loops to go beyond the end of the track loop_out = samples; } m_iLoopStartSample = loop_in; m_pCOLoopStartPosition->set(loop_in); m_iLoopEndSample = loop_out; m_pCOLoopEndPosition->set(loop_out); setLoopingEnabled(true); }
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; }
bool CueControl::isTrackAtCue() { return (fabs(getCurrentSample() - m_pCuePoint->get()) < 1.0f); }
soundDataBuffer* sound_DecodeOggVorbis(struct OggVorbisDecoderState* decoder, size_t bufferSize) { size_t size = 0; #ifndef WZ_NOSOUND int result; #endif soundDataBuffer* buffer; ASSERT(decoder != NULL, "NULL decoder passed!"); #ifndef WZ_NOSOUND if (decoder->allowSeeking) { unsigned int sampleCount = getSampleCount(decoder); unsigned int sizeEstimate = sampleCount * decoder->VorbisInfo->channels * 2; if (((bufferSize == 0) || (bufferSize > sizeEstimate)) && (sizeEstimate != 0)) { bufferSize = (sampleCount - getCurrentSample(decoder)) * decoder->VorbisInfo->channels * 2; } } // If we can't seek nor receive any suggested size for our buffer, just quit if (bufferSize == 0) { debug(LOG_ERROR, "can't find a proper buffer size"); return NULL; } #else bufferSize = 0; #endif buffer = malloc(bufferSize + sizeof(soundDataBuffer)); if (buffer == NULL) { debug(LOG_ERROR, "couldn't allocate memory (%lu bytes requested)", (unsigned long) bufferSize + sizeof(soundDataBuffer)); return NULL; } buffer->data = (char*)(buffer + 1); buffer->bufferSize = bufferSize; buffer->bitsPerSample = 16; #ifndef WZ_NOSOUND buffer->channelCount = decoder->VorbisInfo->channels; buffer->frequency = decoder->VorbisInfo->rate; // Decode PCM data into the buffer until there is nothing to decode left do { // Decode int section; result = ov_read(&decoder->oggVorbis_stream, &buffer->data[size], bufferSize - size, OGG_ENDIAN, 2, 1, §ion); if (result < 0) { debug(LOG_ERROR, "error decoding from OggVorbis file; errorcode from ov_read: %d", result); free(buffer); return NULL; } else { size += result; } } while ((result != 0 && size < bufferSize)); #endif buffer->size = size; return buffer; }
double CueControl::updateIndicatorsAndModifyPlay(double play, bool playPossible) { QMutexLocker lock(&m_mutex); double cueMode = m_pCueMode->get(); if ((cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) && play > 0.0 && playPossible && m_pPlayButton->get() == 0.0) { // in Denon mode each play from pause moves the cue point // if not previewing cueSet(1.0); } // when previewing, "play" was set by cue button, a following toggle request // (play = 0.0) is used for latching play. bool previewing = false; if (m_bPreviewing || m_bPreviewingHotcue) { if (play == 0.0) { // play latch request: stop previewing and go into normal play mode. m_bPreviewing = false; m_bHotcueCancel = true; play = 1.0; } else { previewing = true; } } if (!playPossible) { // play not possible play = 0.0; m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); m_pStopButton->set(0.0); } else if (play && !previewing) { // Play: Indicates a latched Play m_pPlayIndicator->setBlinkValue(ControlIndicator::ON); m_pStopButton->set(0.0); } else { // Pause: m_pStopButton->set(1.0); if (cueMode == CUE_MODE_DENON) { if (isTrackAtCue()) { m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); } else { // Flashing indicates that a following play would move cue point m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } } else if (cueMode == CUE_MODE_MIXXX || cueMode == CUE_MODE_NUMARK) { m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); } else { // Flashing indicates that play is possible in Pioneer mode m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } } if (cueMode != CUE_MODE_DENON && cueMode != CUE_MODE_NUMARK) { if (m_pCuePoint->get() != -1) { if (play == 0.0 && !isTrackAtCue() && getCurrentSample() < getTotalSamples()) { if (cueMode == CUE_MODE_MIXXX) { // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } else { // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_250MS); } } else { m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); } } else { m_pCueIndicator->setBlinkValue(ControlIndicator::OFF); } } m_pPlayStutter->set(play); return play; }