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

Fuzzy beats fix. #4366

Merged
merged 15 commits into from
Oct 23, 2021
Merged

Fuzzy beats fix. #4366

merged 15 commits into from
Oct 23, 2021

Conversation

daschuer
Copy link
Member

@daschuer daschuer commented Oct 7, 2021

I have reviewed all edge case for the beat snapping algorithm and adjusted it as it makes most sense.
Normally the functions should not snap to a beat. In most cases this creates exceptional code paths which are hard to maintain, because snapping of an inner function is not expected when reading the code.

We have two exceptions where snapping is still applied:

  • The "beat_next" "beat_prev" COs used for caching the result of findPrevNextBeats(), along with some grown code build on it. I don't want to touch it to limit the risk of more regressions
  • The looping code. Here we have some odd situations related to the quantitation done by one audio buffer cycle. In case of beatloops, we need to be sure that the loop is still considered between two beats even if it overshoots slightly due to an audio buffer.

The additional double rounding issue of the BeatGrid code should be fixed now.

I am unsure about the BeatGridIterator it is implemented by repeated m_currentPosition += m_beatLengthFrames. I am unsure if it is exactly the same if we use n * m_beatLengthFrames. I am afraid not. Does anyone know?
Luckily it is only used for visual beats.

This should make #4334 obsolete and fix https://bugs.launchpad.net/mixxx/+bug/1945238

@daschuer
Copy link
Member Author

daschuer commented Oct 7, 2021

The CI error is unrelated:

An unexpected error has occurred: CalledProcessError: command: ('/github/home/.cache/pre-commit/repowbjfifn_/node_env-default/bin/node', '/github/home/.cache/pre-commit/repowbjfifn_/node_env-default/bin/npm', 'install', '-g', '/github/home/.cache/pre-commit/repowbjfifn_/dummy_package-0.0.0.tgz', 'eslint@7.32.0')

@Holzhaus
Copy link
Member

Holzhaus commented Oct 8, 2021

I restarted CI.

Copy link
Member

@Holzhaus Holzhaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some first comments

const double nextBeat = ceil(beatFraction);

audio::FramePos closestBeatPosition;
double startBeat = (position - firstBeatPosition()) / m_beatLengthFrames;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the naming is a bit confusing. Maybe something like this?

Suggested change
double startBeat = (position - firstBeatPosition()) / m_beatLengthFrames;
double beatIndex = (position - firstBeatPosition()) / m_beatLengthFrames;

@@ -49,7 +49,22 @@ BeatsPointer Beats::fromBeatPositions(
return BeatMap::makeBeatMap(sampleRate, subVersion, beatPositions);
}

audio::FramePos Beats::snapPosToNearBeat(audio::FramePos position) const {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is very similar to Beats::findClosestBeat. Can we unify this? https://github.com/mixxxdj/mixxx/blob/main/src/track/beats.cpp#L73

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that code is a little dangerous for beatmap tracks which have no beats before the beginning. Loops will always snap to the first beat even if the loop start is before the beginning (i.e., no prev beat). (I have tracks where that's necessary because the track starts on an off-beat)

Copy link
Member

@Holzhaus Holzhaus Oct 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could either

  • introduce a maxDelta argument to prevent this, or
  • make this method call findClosestBeat and then return the closest beat position if abs(position - closestBeatPosition) > kEpsilon and position otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for now it's acceptable to have loops snap to the first available beat. Note that with the current code, loops initiated before the first beat don't get created at all, so we should at least fix that. Reusing this existing function might fix that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is very similar to Beats::findClosestBeat.

Beats::snapPosToNearBeat snaps only if a beat is within the snapping range. Beats::findClosestBeat finds the beat in any range. I think the description in the header file is already explaining this difference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that code is a little dangerous for beatmap tracks which have no beats before the beginning. Loops will always snap to the first beat even if the loop start is before the beginning (i.e., no prev beat). (I have tracks where that's necessary because the track starts on an off-beat)

You can't set beat loops in beatmap tracks before the first beat. However loops are snapped to the next beat when you are in the snapping region. Extrapolating into the preroll would be a new feature. I think We should postpone it to a follow up PR.

@@ -1266,20 +1266,21 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
} else {
newloopInfo.startPosition = currentPosition;
}
} else {
} else if (m_pQuantizeEnabled->toBool()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that you moved this here to avoid an unnecessary beats lookup. This nesting is getting deep and confusing, is there a way we can rearrange the logic to simplify it? I think the whole block could be pulled into a new small function ("findLoopStart"?) that takes the current start position and loop length and returns a new one. That would allow us to return as soon as we find the new start position, which I think is the only output of this block:

ex:

if (!m_pQuantizeEnabled->toBool()) { return currentPosition; }

@ywwg
Copy link
Member

ywwg commented Oct 8, 2021

This code works correctly with my most recent failure case. For beatmap tracks, trying to create a loop before the first beat results in nothing -- clicking "make a 4 beat loop" here has no effect:
image

@daschuer
Copy link
Member Author

Review comments are integrated. A bug when playing reverse was fixed and we can now set a beat loop one beat before the first beat.

@Be-ing Be-ing requested a review from ywwg October 12, 2021 18:32
@ywwg
Copy link
Member

ywwg commented Oct 21, 2021

This still doesn't create any loop if the playhead is before the first beat in a beatmap track. Can we make it round up to the first beat in that situation? (see my previous comment)

@daschuer
Copy link
Member Author

The feature is limited in the region of one beat length before the first beat.
This should be sufficient to fix the original rounding bug.

This PR does not fix the issue when you are way before the first beat.

A solution is more complicated, because normally the first beats is not really significant for a suitable tempo in an initial loop.

We need a more sophisticated solution that consideres all other beats in the loop.
I have something in mind that we add the beat length of the next bear after loop out, as if you have shifted the loop one beat back snapping the loop out position.

A topic for another PR.

Copy link
Member

@ywwg ywwg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I would still like something where we propagate the first beat length backward in time to work around these issues.

OK I see how this still works when the playhead is slightly before the first beat. That is good enough for now. thanks! LGTM

@Holzhaus
Copy link
Member

Yeah I would still like something where we propagate the first beat length backward in time to work around these issues.

Please check out #4255 which does exactly that :D

@ywwg ywwg merged commit c3f913a into mixxxdj:main Oct 23, 2021
@daschuer daschuer deleted the fuzzy_beats branch April 14, 2022 21:20
@mixxxbot mixxxbot mentioned this pull request Aug 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants