-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[POC] Beats: Add editing controls and show downbeats on scrolling waveforms #4489
Conversation
If I read this right, this code assumes the "first beat" as recorded in the beat grid is a down beat. I have a lot of tracks where this isn't true, unfortunately. And since the rewrite, our beat detector currently makes some very strange choices for the first beat (often it's many beats into the track). Would it be possible to count downbeats from the primary CUE position? We don't need 100% flexibility yet, but some control would be nice. |
Isn't this this only the visual POC? I think before we activate this in a build by default, we need also a data model for measures and a basic editor. Else this will be useless clutter, that makes the mixing experience worse IMHO. |
I disagree, hardcoded 4/4 is more than sufficient even for shipping in the next stable feature. As a replacement for a basic editor we can also hardcode that the "beatgrid setter" (whatever gets called for the |
If it is default off, OK. Than one can enable it for every track where this basic assumption is correct. |
Opting-in for each track is not feasible for libraries containing more than lets say one hundred tracks IMO. |
I want the default setting to be on for the music I listen to, so a setting in the preferences would be appropriate in my opinion. |
I agree. I thought we had planned to hide this feature behind a hidden preference that isn't in the preferences GUI until it has matured to a state that it is useful. I agree that this cannot be useful without the ability to edit the downbeat data.
I disagree. Hardcoding 4/4 is sufficient for a temporary proof-of-concept hack but not for a release. We need to ensure that the data model works well for all planned features before committing to it. |
Okay, I should probably explained the motivation behind this PR better, sorry😅. First of all, I've put "Request for comments" in the PR title because I don't plan to get this merged as-is.
Nope, it's actually an API design experiment. The visuals are not that nice (i just tried to implement these Downbeat visuals in the laziest way possible). What I actually wanted to test here is how to access beat properties. Before the refactoring, every beat has a single property (the position). But for Downbeat support, we need more (such as the beat index in the bar, whether a beat is a downbeat, or, as @poelzi pointed out, also a way to check if a beat is a phrase start). Right now, our legacy API like
I agree that we need some kind of editing workflow, but that is out of scope for this PR. I can check if I can make a PoC for that. In the meantime, I'd prefer a opt-in approach via a hidden config option (like we did for QML). That lowers the barrier to test it out while keeping it disabled for the regular user, prevents merge conflicts and ensure everything builds via CI (in contrast to ifdef'ing it). We could even implement the beats editor that way to edit beats in-memory, and only change the persistent data model as the last step when everything else is done.
Hardcoding 4/4 makes it possible to not change the data model at all and be fully backwards-compatible with beatmaps/beatgrids in Mixxx 2.2/2.3. If you look at the diff, the protobuf format is unchanged. Regarding 4/4 being just a "temporary proof-of-concept hack": let's not get too overambitious here. Major DJ software such a Serato hardcodes 4/4 too and does not support other time signatures. So I think we could consider it sufficient for a stable feature, and refine it later. Not everything has to bee 100% perfect from the start. |
1086cdd
to
1422b32
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for pushing this forward. I have left some comments.
src/engine/controls/bpmcontrol.cpp
Outdated
return; | ||
} | ||
|
||
const auto currentPosition = frameInfo().currentPosition.toLowerFrameBoundary(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you use here toLowerFrameBoundary()?
What is auto here? The type is out of sight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you use here toLowerFrameBoundary()?
I removed it here, and moved the rounding to the later part where this position is used for inserting a beat marker.
What is auto here? The type is out of sight.
It's a frame position, which should be clear from the variable names.
src/engine/controls/bpmcontrol.cpp
Outdated
const auto currentPosition = frameInfo().currentPosition.toLowerFrameBoundary(); | ||
|
||
auto markers = pBeats->getMarkers(); | ||
auto markerIt = std::upper_bound(markers.begin(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you actually want std::lower_bound() ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. I added comments to clarify that.
src/engine/controls/bpmcontrol.cpp
Outdated
} | ||
|
||
auto endMarkerBpm = pBeats->getEndMarkerBpm(); | ||
if (markerIt == markers.end()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition can make use of a comment.
Can this ever be true, because if markerIt-- above?
src/engine/controls/bpmcontrol.cpp
Outdated
if (markerIt == markers.end()) { | ||
endMarkerBpm += kBpmAdjustStep; | ||
} else { | ||
auto it = pBeats->iteratorFrom(currentPosition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not already "markerIt" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, one is a std::vector<BeatMarker>::iterastor
and one is a Beats::ConstIterator
. I modified the code so that this second iterator isn't necessary anymore.
src/engine/controls/bpmcontrol.cpp
Outdated
markerIt = markers.erase(markerIt); | ||
markerIt = markers.emplace(markerIt, | ||
marker.position(), | ||
marker.beatsTillNextMarker() + it.beatsPerBar()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that markerIt is ahead of the new marker position, which is not guaranteed.
src/engine/controls/bpmcontrol.cpp
Outdated
auto markerIt = std::upper_bound(markers.begin(), | ||
markers.end(), | ||
currentPosition, | ||
[](const mixxx::audio::FramePos& position, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use the same lambda more often I think it makes sense to make a named function from it. The name will also improve readability.
I just had to look up how std::upper_bound() works exactly in edge cases. Given that its implementation is not has not more lines than the call here, I consider a normal loop even more readable ... just my two cents.
src/engine/controls/bpmcontrol.cpp
Outdated
|
||
auto endMarkerPosition = pBeats->getEndMarkerPosition(); | ||
if (markerIt == markers.end()) { | ||
const auto it = pBeats->iteratorFrom(endMarkerPosition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"it" can be anything. Can we give it a reasonable name? I do not understand the code. Maybe some comments will also help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it = markerIt? Why you look it up again?
return it; | ||
} | ||
|
||
it++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it make sense to cache the next iterator. Than we can give theme reasonable names to improve the readability as well.
1422b32
to
c9b8ab5
Compare
Thanks for the review, but I'd rather get some feedback on the editing workflow first, before delving into the code. If the workflow is considered insufficient, we will probably need another implementation anyway. |
12edebf
to
f940c8f
Compare
I think this is a good approach. The editing functions are good and this provides a solid benefit for the vast majority of tracks. I do think we need some sort of behavior for migration / first-use because the current algorithm makes bad choices for the first beat in many cases. Using the CUE position as a downbeat is a sufficient solution for me. |
f940c8f
to
2405f0d
Compare
I tested this with a jazz track which is 140 BPM for most of the track and gradually accelerates to 185 BPM by the end. It was really cool to use beatjumping and looping and actually have it be accurate.
In general, I think the approach of manually adjusting downbeat positions works well. However I think there is a lot of room for improvement here. First, showing tempo markers in a special color is odd. I want to see that when editing the beatgrid, but after I've done that, I don't want to be bothered by that information when mixing. I am usually against the idea of creating separate modes, but I think creating a beatgrid editing mode that shows where the manual edits have been made would be appropriate. I tested using a mouse and keyboard. The workflow of seeking to an exact point and pressing a button is quite difficult to use with this input method. This design may work okay with a controller with a jog wheel and a button, but I think we could do better with a design that works well for any input method. I propose to instead use a click-and-drag workflow to adjust beat positions. This would work well with both mice and touchscreens. On controllers it would be similar to the manual loop adjustment workflow, holding a button down while moving a jog wheel. For controllers this would have the advantage that you could see how the beats within the measure line up with the waveform as you drag the marker. This could also allow for fine tuning the position of any beat, not just downbeats. It is not obvious to me what is supposed to happen to surrounding regions when placing a new tempo marker. I didn't look at the behavior too closely but it seemed like it didn't always behave the same way? I also ran into an issue twice where setting a new marker somehow made the tempo way slower? I think the downbeats became the only beats or something like that? It was quite tedious to manually place every downbeat. I wish I could adjust the tempo for the beginning and end of an accelerating section and have Mixxx try to interpolate the tempo changes in between, then manually adjust as needed. My understanding, without looking at the code, is that you intend for Mixxx to interpolate evenly spaced beats between downbeats. But when I set tempo markers, the tempo is shown as varying within the bar and I don't know what's happening. |
I have just experienced an endless loop when trying to remove a beat marker:
This message is printed over and over again. |
I get also this when trying to set beats in a row:
|
2405f0d
to
f0faad9
Compare
Oooffps, clicked the __ button three times or more in a row and got std::bad_alloc
the L488 assert is repeated for like... very often, then it's a lot of L458, then x__x That happened after I was confused by the pink markers and result of 'set beat' didn't match my expectation, then I clicked it again a few times : ) |
Was just giving this a shot with some random tracks, and I didn't find UI instructions in the first post, and I didn't read the other 64 posts to look for them .) @Holzhaus Could you please add a basic introduction to the first post, about what the existing controls should do, what the different marker colors mean etc. Thanks! |
This PR is marked as stale because it has been open 90 days with no activity. |
Any updates on this? |
I'm pretty busy right now, can't make any promises. |
Related feature request: #10788 |
Hi there! I really hope 🙏 that downbeat support will be added soon, as it is a very useful feature for setting cue points. |
Here's a rebase of the branch onto After playing with it a bit, I gotta say, this is really nice! I know it's a POC, but think the downbeat visualization and editors are already nice improvements over the status quo. The only thing I did find a bit distracting was the fixed 4/4 though. I know, this has been discussed already, but I have to agree with @daschuer und @Be-ing on that one, wrong annotations are IMO worse than no annotations. Consider e.g. Ed Sheeran - Perfect, which is 6/8: The detected downbeats being all over the place and misaligned with the actual ones is more distracting than having no downbeat annotations on that track. Same goes for tracks that have e.g. measures with an extra beat, which is surprisingly not that uncommon even in EDM tracks. Yes, other DJ software does this too (hardcoding 4/4), but I don't think that's a good reason for us not to do better here. A lot of tracks are 4/4, so I don't think making the common case convenient is necessarily bad, but when given the choice I believe erring on the side of not setting/showing downbeats if the analyzer isn't "sure" the track is 4/4 would be nice. |
this is fantastic to see and i'm sure it was no small effort to rebase given how much has changed since the feature was initially developed. thank you! |
Nice one! Fwiw, here's a dancefloor orientated 6/8 track, and a broken beat/bruk (ctrl-f "6/8") style one at that; Distractions (Bugs In The Attic Remix) (see also the dub; Distractions (Bugz Co-operative Dub)). |
I'll close this in favor of the rebased version: |
Based on #4411.
To Do:
beatsTillNextMarker
is always a multiple ofbeatsPerBar
to fix downbeats support in beatmaps