Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beats frame refactor #4049

Merged
merged 6 commits into from
Jul 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/analyzer/analyzerbeats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ bool AnalyzerBeats::shouldAnalyze(TrackPointer pTrack) const {
return m_bPreferencesReanalyzeImported;
}

if (subVersion.isEmpty() && pBeats->findNextBeat(0) <= 0.0 &&
if (subVersion.isEmpty() && pBeats->firstBeat() <= mixxx::audio::kStartFramePos &&
m_pluginId != mixxx::AnalyzerSoundTouchBeats::pluginInfo().id()) {
// This happens if the beat grid was created from the metadata BPM value.
qDebug() << "First beat is 0 for grid so analyzing track to find first beat.";
Expand Down
54 changes: 32 additions & 22 deletions src/engine/controls/bpmcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,9 @@ void BpmControl::slotTranslateBeatsEarlier(double v) {
const mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (pBeats &&
(pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_TRANSLATE)) {
const double translate_dist = getSampleOfTrack().rate * -.01;
pTrack->trySetBeats(pBeats->translate(translate_dist));
const double sampleOffset = getSampleOfTrack().rate * -0.01;
const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount;
pTrack->trySetBeats(pBeats->translate(frameOffset));
}
}

Expand All @@ -223,8 +224,9 @@ void BpmControl::slotTranslateBeatsLater(double v) {
if (pBeats &&
(pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_TRANSLATE)) {
// TODO(rryan): Track::getSampleRate is possibly inaccurate!
const double translate_dist = getSampleOfTrack().rate * .01;
pTrack->trySetBeats(pBeats->translate(translate_dist));
const double sampleOffset = getSampleOfTrack().rate * 0.01;
const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount;
pTrack->trySetBeats(pBeats->translate(frameOffset));
}
}

Expand Down Expand Up @@ -582,22 +584,26 @@ bool BpmControl::getBeatContext(const mixxx::BeatsPointer& pBeats,
return false;
}

double dPrevBeat;
double dNextBeat;
if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat, false)) {
const auto position = mixxx::audio::FramePos::fromEngineSamplePos(dPosition);
mixxx::audio::FramePos prevBeatPosition;
mixxx::audio::FramePos nextBeatPosition;
if (!pBeats->findPrevNextBeats(position, &prevBeatPosition, &nextBeatPosition, false)) {
return false;
}

if (dpPrevBeat != nullptr) {
*dpPrevBeat = dPrevBeat;
*dpPrevBeat = prevBeatPosition.toEngineSamplePos();
}

if (dpNextBeat != nullptr) {
*dpNextBeat = dNextBeat;
*dpNextBeat = nextBeatPosition.toEngineSamplePos();
}

return getBeatContextNoLookup(dPosition, dPrevBeat, dNextBeat,
dpBeatLength, dpBeatPercentage);
return getBeatContextNoLookup(position.toEngineSamplePos(),
prevBeatPosition.toEngineSamplePos(),
nextBeatPosition.toEngineSamplePos(),
dpBeatLength,
dpBeatPercentage);
}

// static
Expand Down Expand Up @@ -733,7 +739,8 @@ double BpmControl::getNearestPositionInPhase(
} else if (this_near_next && !other_near_next) {
dNewPlaypos += dThisNextBeat;
} else { //!this_near_next && other_near_next
dThisPrevBeat = pBeats->findNthBeat(dThisPosition, -2);
const auto thisBeatPosition = mixxx::audio::FramePos::fromEngineSamplePos(dThisPosition);
dThisPrevBeat = pBeats->findNthBeat(thisBeatPosition, -2).toEngineSamplePos();
dNewPlaypos += dThisPrevBeat;
}

Expand Down Expand Up @@ -1035,12 +1042,12 @@ void BpmControl::slotBeatsTranslate(double v) {
}
const mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (pBeats && (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_TRANSLATE)) {
double currentSample = getSampleOfTrack().current;
double closestBeat = pBeats->findClosestBeat(currentSample);
int delta = static_cast<int>(currentSample - closestBeat);
if (delta % 2 != 0) {
delta--;
}
const auto currentPosition =
mixxx::audio::FramePos::fromEngineSamplePos(
getSampleOfTrack().current)
.toLowerFrameBoundary();
const auto closestBeat = pBeats->findClosestBeat(currentPosition);
const mixxx::audio::FrameDiff_t delta = currentPosition - closestBeat;
pTrack->trySetBeats(pBeats->translate(delta));
}
}
Expand All @@ -1059,8 +1066,9 @@ void BpmControl::slotBeatsTranslateMatchAlignment(double v) {
// otherwise it will always return 0 if master sync is active.
m_dUserOffset.setValue(0.0);

double offset = getPhaseOffset(getSampleOfTrack().current);
pTrack->trySetBeats(pBeats->translate(-offset));
double sampleOffset = getPhaseOffset(getSampleOfTrack().current);
const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount;
pTrack->trySetBeats(pBeats->translate(-frameOffset));
}
}

Expand All @@ -1069,8 +1077,10 @@ mixxx::Bpm BpmControl::updateLocalBpm() {
mixxx::Bpm localBpm;
const mixxx::BeatsPointer pBeats = m_pBeats;
if (pBeats) {
localBpm = pBeats->getBpmAroundPosition(
getSampleOfTrack().current, kLocalBpmSpan);
const auto currentPosition =
mixxx::audio::FramePos::fromEngineSamplePos(
getSampleOfTrack().current);
localBpm = pBeats->getBpmAroundPosition(currentPosition, kLocalBpmSpan);
if (!localBpm.isValid()) {
localBpm = pBeats->getBpm();
}
Expand Down
14 changes: 11 additions & 3 deletions src/engine/controls/clockcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,18 @@ void ClockControl::updateIndicators(const double dRate,
if (pBeats) {
if ((currentSample >= m_NextBeatSamples) ||
(currentSample <= m_PrevBeatSamples)) {
pBeats->findPrevNextBeats(currentSample,
&m_PrevBeatSamples,
&m_NextBeatSamples,
mixxx::audio::FramePos prevBeatPosition;
mixxx::audio::FramePos nextBeatPosition;
pBeats->findPrevNextBeats(mixxx::audio::FramePos::fromEngineSamplePos(currentSample),
&prevBeatPosition,
&nextBeatPosition,
false); // Precise compare without tolerance needed
m_PrevBeatSamples = prevBeatPosition.isValid()
? prevBeatPosition.toEngineSamplePos()
: -1;
m_NextBeatSamples = nextBeatPosition.isValid()
? nextBeatPosition.toEngineSamplePos()
: -1;
}
} else {
m_PrevBeatSamples = -1;
Expand Down
18 changes: 15 additions & 3 deletions src/engine/controls/cuecontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,17 @@ void CueControl::hotcueSet(HotcueControl* pControl, double value, HotcueSetMode
if (beatloopSize <= 0 || !pBeats) {
return;
}
cueEndPosition = pBeats->findNBeatsFromSample(cueStartPosition, beatloopSize);
mixxx::audio::FramePos cueEndPositionFrames;
if (cueStartPosition != Cue::kNoPosition) {
const auto cueStartPositionFrames =
mixxx::audio::FramePos::fromEngineSamplePos(
cueStartPosition);
cueEndPositionFrames = pBeats->findNBeatsFromPosition(
cueStartPositionFrames, beatloopSize);
}
cueEndPosition = cueEndPositionFrames.isValid()
? cueEndPositionFrames.toEngineSamplePos()
: Cue::kNoPosition;
}
cueType = mixxx::CueType::Loop;
break;
Expand Down Expand Up @@ -1979,10 +1989,12 @@ double CueControl::quantizeCuePoint(double cuePos) {
return cuePos;
}

double closestBeat = pBeats->findClosestBeat(cuePos);
const auto cuePosition = mixxx::audio::FramePos::fromEngineSamplePos(cuePos);
const auto closestBeatPosition = pBeats->findClosestBeat(cuePosition);
// The closest beat can be an unreachable interpolated beat past the end of
// the track.
if (closestBeat != -1.0 && closestBeat <= total) {
const double closestBeat = closestBeatPosition.toEngineSamplePos();
uklotzde marked this conversation as resolved.
Show resolved Hide resolved
if (closestBeatPosition.isValid() && closestBeat <= total) {
return closestBeat;
}

Expand Down
111 changes: 81 additions & 30 deletions src/engine/controls/loopingcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ double LoopingControl::getSyncPositionInsideLoop(double dRequestedPlaypos, doubl
return dSyncedPlayPos;
}

void LoopingControl::setBeatLoop(double startPosition, bool enabled) {
VERIFY_OR_DEBUG_ASSERT(startPosition != Cue::kNoPosition) {
void LoopingControl::setBeatLoop(double startPositionSamples, bool enabled) {
VERIFY_OR_DEBUG_ASSERT(startPositionSamples != Cue::kNoPosition) {
return;
}

Expand All @@ -541,9 +541,12 @@ void LoopingControl::setBeatLoop(double startPosition, bool enabled) {

// TODO(XXX): This is not realtime safe. See this Zulip discussion for details:
// https://mixxx.zulipchat.com/#narrow/stream/109171-development/topic/getting.20locks.20out.20of.20Beats
double endPosition = pBeats->findNBeatsFromSample(startPosition, beatloopSize);
const auto startPosition = mixxx::audio::FramePos::fromEngineSamplePos(startPositionSamples);
const auto endPosition = pBeats->findNBeatsFromPosition(startPosition, beatloopSize);

setLoop(startPosition, endPosition, enabled);
if (startPosition.isValid() && endPosition.isValid()) {
setLoop(startPositionSamples, endPosition.toEngineSamplePos(), enabled);
}
}

void LoopingControl::setLoop(double startPosition, double endPosition, bool enabled) {
Expand Down Expand Up @@ -615,8 +618,11 @@ void LoopingControl::setLoopInToCurrentPosition() {
if (loopSamples.end != kNoTrigger &&
(loopSamples.end - pos) < MINIMUM_AUDIBLE_LOOP_SIZE) {
if (quantizedBeat != -1 && pBeats) {
pos = pBeats->findNthBeat(quantizedBeat, -2);
if (pos == -1 || (loopSamples.end - pos) < MINIMUM_AUDIBLE_LOOP_SIZE) {
const auto quantizedBeatPosition =
mixxx::audio::FramePos::fromEngineSamplePos(quantizedBeat);
const auto position = pBeats->findNthBeat(quantizedBeatPosition, -2);
pos = position.toEngineSamplePos();
if (!position.isValid() || (loopSamples.end - pos) < MINIMUM_AUDIBLE_LOOP_SIZE) {
pos = loopSamples.end - MINIMUM_AUDIBLE_LOOP_SIZE;
}
} else {
Expand All @@ -640,8 +646,9 @@ void LoopingControl::setLoopInToCurrentPosition() {
if (m_pQuantizeEnabled->toBool()
&& loopSamples.start < loopSamples.end
&& pBeats) {
m_pCOBeatLoopSize->setAndConfirm(
pBeats->numBeatsInRange(loopSamples.start, loopSamples.end));
const auto startPosition = mixxx::audio::FramePos::fromEngineSamplePos(loopSamples.start);
const auto endPosition = mixxx::audio::FramePos::fromEngineSamplePos(loopSamples.end);
m_pCOBeatLoopSize->setAndConfirm(pBeats->numBeatsInRange(startPosition, endPosition));
updateBeatLoopingControls();
} else {
clearActiveBeatLoop();
Expand Down Expand Up @@ -721,8 +728,11 @@ void LoopingControl::setLoopOutToCurrentPosition() {
// use the smallest pre-defined beatloop instead (when possible)
if ((pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) {
if (quantizedBeat != -1 && pBeats) {
pos = static_cast<int>(floor(pBeats->findNthBeat(quantizedBeat, 2)));
if (pos == -1 || (pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) {
const auto quantizedBeatPosition =
mixxx::audio::FramePos::fromEngineSamplePos(quantizedBeat);
const auto position = pBeats->findNthBeat(quantizedBeatPosition, 2);
pos = static_cast<int>(position.toLowerFrameBoundary().toEngineSamplePos());
if (!position.isValid() || (pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) {
pos = loopSamples.start + MINIMUM_AUDIBLE_LOOP_SIZE;
}
} else {
Expand All @@ -745,8 +755,9 @@ void LoopingControl::setLoopOutToCurrentPosition() {
}

if (m_pQuantizeEnabled->toBool() && pBeats) {
m_pCOBeatLoopSize->setAndConfirm(
pBeats->numBeatsInRange(loopSamples.start, loopSamples.end));
const auto startPosition = mixxx::audio::FramePos::fromEngineSamplePos(loopSamples.start);
const auto endPosition = mixxx::audio::FramePos::fromEngineSamplePos(loopSamples.end);
m_pCOBeatLoopSize->setAndConfirm(pBeats->numBeatsInRange(startPosition, endPosition));
updateBeatLoopingControls();
} else {
clearActiveBeatLoop();
Expand Down Expand Up @@ -1097,8 +1108,12 @@ bool LoopingControl::currentLoopMatchesBeatloopSize() {
LoopSamples loopSamples = m_loopSamples.getValue();

// Calculate where the loop out point would be if it is a beatloop
double beatLoopOutPoint =
pBeats->findNBeatsFromSample(loopSamples.start, m_pCOBeatLoopSize->get());
const auto loopStartPosition = mixxx::audio::FramePos::fromEngineSamplePos(loopSamples.start);
const auto loopEndPosition = pBeats->findNBeatsFromPosition(
loopStartPosition, m_pCOBeatLoopSize->get());
const double beatLoopOutPoint = loopEndPosition.isValid()
? loopEndPosition.toEngineSamplePos()
: -1;

return loopSamples.end > beatLoopOutPoint - 2 &&
loopSamples.end < beatLoopOutPoint + 2;
Expand All @@ -1110,11 +1125,15 @@ double LoopingControl::findBeatloopSizeForLoop(double start, double end) const {
return -1;
}

const auto loopStartPosition = mixxx::audio::FramePos::fromEngineSamplePos(start);
for (unsigned int i = 0; i < (sizeof(s_dBeatSizes) / sizeof(s_dBeatSizes[0])); ++i) {
double beatLoopOutPoint =
pBeats->findNBeatsFromSample(start, s_dBeatSizes[i]);
if (end > beatLoopOutPoint - 2 && end < beatLoopOutPoint + 2) {
return s_dBeatSizes[i];
const auto loopEndPosition = pBeats->findNBeatsFromPosition(
loopStartPosition, s_dBeatSizes[i]);
if (loopEndPosition.isValid()) {
const double beatLoopOutPoint = loopEndPosition.toEngineSamplePos();
if (end > beatLoopOutPoint - 2 && end < beatLoopOutPoint + 2) {
return s_dBeatSizes[i];
}
}
}
return -1;
Expand Down Expand Up @@ -1192,15 +1211,26 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
} else {
// loop_in is set to the closest beat if quantize is on and the loop size is >= 1 beat.
// The closest beat might be ahead of play position and will cause a catching loop.
double prevBeat;
double nextBeat;
pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat, true);
mixxx::audio::FramePos prevBeatPosition;
mixxx::audio::FramePos nextBeatPosition;
const auto currentPosition = mixxx::audio::FramePos::fromEngineSamplePos(currentSample);
pBeats->findPrevNextBeats(currentPosition, &prevBeatPosition, &nextBeatPosition, true);
const double prevBeat = prevBeatPosition.isValid()
? prevBeatPosition.toEngineSamplePos()
: -1;
const double nextBeat = nextBeatPosition.isValid()
uklotzde marked this conversation as resolved.
Show resolved Hide resolved
? nextBeatPosition.toEngineSamplePos()
: -1;

if (m_pQuantizeEnabled->toBool() && prevBeat != -1) {
double beatLength = nextBeat - prevBeat;
double loopLength = beatLength * beats;

double closestBeat = pBeats->findClosestBeat(currentSample);
const mixxx::audio::FramePos closestBeatPosition =
pBeats->findClosestBeat(currentPosition);
double closestBeat = closestBeatPosition.isValid()
? closestBeatPosition.toEngineSamplePos()
: -1;
if (beats >= 1.0) {
newloopSamples.start = closestBeat;
} else {
Expand Down Expand Up @@ -1239,16 +1269,24 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
}
}

newloopSamples.end = pBeats->findNBeatsFromSample(newloopSamples.start, beats);
const auto newLoopStartPosition =
mixxx::audio::FramePos::fromEngineSamplePos(newloopSamples.start);
const auto newLoopEndPosition = pBeats->findNBeatsFromPosition(newLoopStartPosition, beats);
newloopSamples.end = newLoopEndPosition.isValid() ? newLoopEndPosition.toEngineSamplePos() : -1;

if (newloopSamples.start >= newloopSamples.end // happens when the call above fails
|| newloopSamples.end > samples) { // Do not allow beat loops to go beyond the end of the track
// If a track is loaded with beatloop_size larger than
// the distance between the loop in point and
// the end of the track, let beatloop_size be set to
// a smaller size, but not get larger.
double previousBeatloopSize = m_pCOBeatLoopSize->get();
double previousBeatloopOutPoint = pBeats->findNBeatsFromSample(
newloopSamples.start, previousBeatloopSize);
const mixxx::audio::FramePos previousLoopEndPosition =
pBeats->findNBeatsFromPosition(
newLoopStartPosition, previousBeatloopSize);
double previousBeatloopOutPoint = previousLoopEndPosition.isValid()
? previousLoopEndPosition.toEngineSamplePos()
: -1;
if (previousBeatloopOutPoint < newloopSamples.start
&& beats < previousBeatloopSize) {
m_pCOBeatLoopSize->setAndConfirm(beats);
Expand Down Expand Up @@ -1352,7 +1390,11 @@ void LoopingControl::slotBeatJump(double beats) {
slotLoopMove(beats);
} else {
// seekExact bypasses Quantize, because a beat jump is implicit quantized
seekExact(pBeats->findNBeatsFromSample(currentSample, beats));
const auto currentPosition = mixxx::audio::FramePos::fromEngineSamplePos(currentSample);
const auto seekPosition = pBeats->findNBeatsFromPosition(currentPosition, beats);
if (seekPosition.isValid()) {
seekExact(seekPosition.toEngineSamplePos());
}
}
}

Expand Down Expand Up @@ -1380,10 +1422,19 @@ void LoopingControl::slotLoopMove(double beats) {

if (BpmControl::getBeatContext(pBeats, m_currentSample.getValue(),
nullptr, nullptr, nullptr, nullptr)) {
double new_loop_in = pBeats->findNBeatsFromSample(loopSamples.start, beats);
double new_loop_out = currentLoopMatchesBeatloopSize() ?
pBeats->findNBeatsFromSample(new_loop_in, m_pCOBeatLoopSize->get()) :
pBeats->findNBeatsFromSample(loopSamples.end, beats);
const auto loopStartPosition =
mixxx::audio::FramePos::fromEngineSamplePos(loopSamples.start);
const auto loopEndPosition = mixxx::audio::FramePos::fromEngineSamplePos(loopSamples.end);
const auto newLoopStartPosition = pBeats->findNBeatsFromPosition(loopStartPosition, beats);
const auto newLoopEndPosition = currentLoopMatchesBeatloopSize()
? pBeats->findNBeatsFromPosition(newLoopStartPosition, m_pCOBeatLoopSize->get())
: pBeats->findNBeatsFromPosition(loopEndPosition, beats);
double new_loop_in = newLoopStartPosition.isValid()
? newLoopStartPosition.toEngineSamplePos()
: kNoTrigger;
double new_loop_out = newLoopEndPosition.isValid()
? newLoopEndPosition.toEngineSamplePos()
: kNoTrigger;

// The track would stop as soon as the playhead crosses track end,
// so we don't allow moving a loop beyond end.
Expand Down
Loading