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: allow undoing the last BPM/beats change #12954

Merged
merged 3 commits into from
May 14, 2024
Merged

Conversation

ronso0
Copy link
Member

@ronso0 ronso0 commented Mar 12, 2024

Allow undoing the last BPM/beats change.

Up to 10 BeatsPointers are stored on each BPM/beats change.
Apparently the beat analyzer set beats twice on initial scan, so the first result is added to the stack 🤷‍♂️
For changes done in quick succession (<800 ms currently) intermediate states are not stored.

  • add beats_undo_adjustment ControlPushButton
  • add "Undo last BPM/beats change" to BPM menu

Fixes #12774 (#10138)

TODO

  • add Undo button to skins to make this more accessible, probably to beatgrid controls section next to the waveforms
    (Tango + LateNight only? in Deere controls are already quite dense)
  • add a test?
  • remove debug output / transform into trace logs

Nice to have

Do we need 'Redo'?

I'd say no. Might be nice to have for comparison, but beats operations are easy to redo, so 🤷‍♂️
It would definitely complicate things.

@ronso0
Copy link
Member Author

ronso0 commented Mar 12, 2024

I think this can easily be extended to provide N undo steps, but I'm not sure how heavy Beats objects are.

Yet another coffee and it's done 🎉

  • 1o steps are stored
  • initial null beats are not stored
  • analyzer set beats twice, so the first result is added to the stack 🤷‍♂️

@ronso0
Copy link
Member Author

ronso0 commented Mar 17, 2024

Manually everything works as expected, just the tests fail. Need to wrap my head around setting beats and stuff.

@ronso0 ronso0 force-pushed the beats-undo branch 2 times, most recently from caf0a86 to 39a07c4 Compare March 19, 2024 02:31
Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

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

Thank you. Works good.

There is a usability issue when you have used the shift actions form the waveform, like "adjuste earliere" ten times.
You can do it fast via the GUI buttons, but undoing to the original state = 10 x undo is hard via the menu.

Is there a way to remove intermediate adjustments form the undo stack? I think Gimp has also such a strategy to not undo a brush stroke pixel by pixel.

src/track/track.cpp Outdated Show resolved Hide resolved
@@ -561,6 +566,8 @@ class Track : public QObject {

// Storage for the track's beats
mixxx::BeatsPointer m_pBeats;
QList<mixxx::BeatsPointer> m_pBeatsUndoStack;
Copy link
Member

Choose a reason for hiding this comment

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

Here we may use QStack with push() and pop()

Copy link
Member Author

Choose a reason for hiding this comment

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

Good to learn about QStack. So the benefit is to use push() and pop() instead of append() and takeLast()/removeLast(). Dunno if that is worth the extra include, but that's tiny, so I'll use it.

@ronso0
Copy link
Member Author

ronso0 commented Mar 19, 2024

There is a usability issue when you have used the shift actions form the waveform, like "adjuste earliere" ten times.
You can do it fast via the GUI buttons, but undoing to the original state = 10 x undo is hard via the menu.

Is there a way to remove intermediate adjustments form the undo stack? I think Gimp has also such a strategy to not undo a brush stroke pixel by pixel.

Sure, but I didn't look into it, yet.
So we want to "group" actions that we consider were done without thinking, so how long does thinking take? .5, .8 seconds?
We may simply store the last change timestamp and pop+push if the diff is smaller than X millis, so we essentially only keep the last action of a "batch".

Maybe such a timeout also helps with the initial useless beats setting.

@daschuer
Copy link
Member

This is on any case a feature, not blocking this PR.

@ronso0
Copy link
Member Author

ronso0 commented Mar 19, 2024

This is on any case a feature, not blocking this PR.

Was rather easy, let's check if the tests also work as desired.

The last commit adds an Undo button to the beatgrid edit controls in LateNight.
I made that commit a fixup! to block merging.

@ronso0 ronso0 force-pushed the beats-undo branch 2 times, most recently from d84766f to 2f1e1ca Compare March 19, 2024 15:06
@ronso0
Copy link
Member Author

ronso0 commented Mar 19, 2024

Was rather easy, let's check if the tests also work as desired.

Nope. Maybe the timing is different in the tests?

@ronso0 ronso0 force-pushed the beats-undo branch 3 times, most recently from 795b752 to 507aa04 Compare March 22, 2024 12:43
// Don't add null beats to the undo stack. Happens when beats are deserialized,
// e.g. when opening the track menu.
// Don't add beats to stack which we're about to undo.
mixxx::Duration time = mixxx::Time::elapsed();
Copy link
Member

Choose a reason for hiding this comment

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

I stumbled here for a short moment about "elapsed() since what?".
Maybe we should rename it to mixxx::Time::sinceStart() or such.

In addition we may want to use a "PerformanceTimer" object instead of implementing it inline.
Using start() restart() and elapsed()

Copy link
Member

Choose a reason for hiding this comment

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

Did you consider this?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm considering this, yes.
Pro argument for the current solution is that we don't create an extra timer object, we simply use the Mixxx time.
A performance timer would be running all the time for each track we touch, even if the user doesn't change the beats (apart from initial load/analyse). A regular one-shot timer (auto-stop) might also work (delta is 800 ms).
Hence I don't really see the necessity to change this.

Copy link
Member

Choose a reason for hiding this comment

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

First of all, I have to a strong demand here. We can keep it as it is.

But let's give some insights:
The PerformanceTimer object not "running" in terms of CPU. It is just a thin wrapper around a single member variable that stores the start time. See:

std::chrono::time_point<ClockT> m_startTime;

So basically your current solution wrapped by an API.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, sorry, should have looked closer.
I'll try that!

@mxmilkiib

This comment was marked as off-topic.

@ronso0

This comment was marked as off-topic.

@mxmilkiib

This comment was marked as off-topic.

@ronso0 ronso0 added this to the 2.5-beta milestone Apr 5, 2024
@ronso0
Copy link
Member Author

ronso0 commented Apr 5, 2024

Manually this works just fine, but I need help with the test (or merge without it).

* add `beats_undo_adjustment` ControlPushButton
* add "Undo last BPM/beats change" to BPM menu, active only if there's something to undo

Co-authored-by: Daniel Schürmann <[email protected]>
@daschuer
Copy link
Member

daschuer commented May 1, 2024

What is your plan here? Remove form 2.5-beta milestone?

@ronso0
Copy link
Member Author

ronso0 commented May 2, 2024

I need help with the tests, then this is ready.
Or we add the tests later on so we can already get feedback during beta.

@ronso0
Copy link
Member Author

ronso0 commented May 3, 2024

I've discovered some issue with the quick actions. (that might also be the reason why the test fails...)
Don't want to fix this in a hurry (might take some time to do it right anyways), so let's remove it from 2.5-beta.

@ronso0
Copy link
Member Author

ronso0 commented May 3, 2024

I've discovered some issue with the quick actions. (that might also be the reason why the test fails...)

Jup, that was it, the test passes.

Ready to remove the Undo button and the trace output.

Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

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

LGTM, ready for clean up. Thank you.

@ronso0
Copy link
Member Author

ronso0 commented May 14, 2024

The manual update is here mixxxdj/manual#654

@daschuer
Copy link
Member

Thank you

@daschuer daschuer merged commit 854ff93 into mixxxdj:main May 14, 2024
13 checks passed
@ronso0 ronso0 deleted the beats-undo branch May 14, 2024 19:15
@m0dB
Copy link
Contributor

m0dB commented Jul 13, 2024

@ronso0 Did you consider using QUndoStack, and if so, why did you prefer to implement your own stack?

I used QUndoStack for cue add/remove: #13469 . I have only looked briefly at your implementation, but I think it will be trivial to use the QUndoStack instead of the custom stack for the beat changes, but maybe I am missing something.

@ronso0
Copy link
Member Author

ronso0 commented Jul 15, 2024

No, I was not aware of QUndoStack. I'll comment in #13469 re the details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants