-
-
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
Slip mode: consider active (regular) loop #11435
Conversation
This is a draft PR, right. Please correct me if I am wrong. |
Well, the tests passed, both manually and CI, so as long as no better solution is in sight we might as well merge it. |
34d2f65
to
b40c5d1
Compare
I'm pretty confident this works as expected and doesn't break anything. I thought about moving it to a new function in LoopingControl, but that'd be just for the sake of doing the virtual looping in a looping class while having to pass a few paramters around. No win IMO. |
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. I have left some comments.
src/engine/enginebuffer.cpp
Outdated
// simulate looping | ||
const double loopStart = m_pLoopingControl->getLoopSamples().start; | ||
const double loopEnd = m_pLoopingControl->getLoopSamples().end; | ||
while (newPos > loopEnd) { |
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.
while (newPos > loopEnd) { | |
while (newPos >= loopEnd) { |
LoopEnd is the trigger point, the first point after the loop.
Not sure if we need also consider the playback direction here, probably not because the while loop works in both directions.
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 would move us to loop_in.
newPos <= loopStart
below would move us back to loop_end
Thanks for your review! I'll |
Seems to work. |
Oh, I misread this
If there really will be a 2.3.6 release I'll backport this, just the position types need to be changed back to double. |
I have no particular interest in a 2.3.6, but we will have a HEAD of the 2.3 branch just before releasing 2.4.0. with a separate Changelog for a future 2.3.6. It will not be available on our download page, because it is immediately replaces with 2.4.0. The only way around it is to freeze the 2.3 branch now, but is hard to predict if we need a 2.3.6 anyway. I have picked here the 2.3.6 label, because it was original targeted to 2.3. Since this is not a critical bug I don't mind where it should go. What is you idea? |
Well, #6993 is from 2013, with very few comments, IMO it's just a incidental bug that affects a handful of people (who might think it's intended behaviour) and (considering the little feedback we got when asking users to test bugfix releases on Zulip) I think it's actually fine to target 2.4 |
@@ -1587,6 +1597,18 @@ void LoopingControl::slotLoopMove(double beats) { | |||
} | |||
} | |||
|
|||
// Must be called from the engine thread only |
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 comment is no longer true, because m_loopInfo can read at any time and the function has no immediate effect to the loop.
Both functions can make use of a better name because they don't actually seek.
They adjust a given "currentPosition" which is also badly named, because it is not yet current.
Maybe adjustPositionForCurrentLoop(mixxx::audio::FramePos position)
If you want do a bit more, you may move LoopingControl::seekInsideAdjustedLoop() into an anonymous namespace. It is a function without "this" access.
src/engine/enginebuffer.cpp
Outdated
if (adjustedPos == mixxx::audio::kInvalidFramePos) { | ||
m_slipPosition = newPos; | ||
} else { | ||
m_slipPosition = adjustedPos; | ||
} |
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 conditional branch is a workaround for an implementation detail in seekInsideAdjustedLoop()
Please move it into seekInsideCurrentLoop() to make this code here more easy to read.
return currentPosition; | ||
} | ||
LoopInfo loopInfo = m_loopInfo.getValue(); | ||
return seekInsideAdjustedLoop(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.
If I return here loopInfo.endPosition, the loop is left after disable slip.
This is because the end position is the first position after the loop.
A bug in the first conditions in seekInsideAdjustedLoop.
I am afraid we need also consider the direction, but I have not tested yet reverse playback yet. An open TODO.
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.
If I return here loopInfo.endPosition, the loop is left after disable slip.
...
A bug in the first conditions in seekInsideAdjustedLoop.
True. I fixed that. Though, that only affected the new slip looping. All existing function (EngineBuffer, ReadAheadManager) already handle that appropriately.
This is because the end position is the first position after the loop.
I don't understand. If ReadAheadManager::getNextSamples
is exactly at the trigger point (loop_out, or _in if reverse) it'll consider that as "inside the loop, trigger reached" and wrap around.
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.
Yes, this means the play position is never at the loop end after the engine call is done.
If you explicitly seek to this position, the code treats it as being after the loop and continues without looping.
Oh man, trying to figure a (simple) solution was really a brainfuck, until I noticed that both Now it's workig as desired. I did some tests as mentioned above, incl. reverse
|
TODO: get rid of RateControl assertions, try EngineBuffer::getRateControl etc. |
Thank you. Works good now and is IMHO also a consistent solution. |
I was curious why this hasn't been implemented earlier, and couldn't really believe "this is super complicated though so it will be a lot of effort to fix" (2013, #6993).
So, this is POC and it performs nicely with active loops (no change for rolling loops).
I didn't run the tests, yet, only tested manually: play a track, set up a loop, clone that deck, then engage slip mode, mess around a bit with the waveform. Disabled slip mode and both decks were in sync.
I don't intend to finish this (unless just polishing is required for merging), I just want to share my experiment and give a starting point for others, so feel free to pick this up.
I guess the code can be improved and the tests should probably to be extended.