-
-
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
Enable beatloop activation directly after activating a hotcue #2190
Conversation
@@ -958,6 +960,10 @@ void LoopingControl::updateBeatLoopingControls() { | |||
} | |||
|
|||
void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable) { | |||
|
|||
// if a seek was queued in the engine buffer wait until it is executed | |||
while(getEngineBuffer()->isSeekQueued()){} |
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 doesn't look sane to me. A busy wait on a condition that is controlled by another thread?
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 expected something like this but was not sure which other mechanism to use. Just tell me what would me the right one and I will adapt my solution.
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.
A straight forward solution is to predict the effect of the scheduled seek here.
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 you need to make the
LoopSamples loopSamples = m_loopSamples.getValue(); conditional.
If there is a cued seek, you need to use m_queuedSeekPosition.getValue().
By luck you can ignore the quantize flag. If not you need to adjust the loop along with the m_queuedSeekPosition.
* fixes https://bugs.launchpad.net/mixxx/+bug/1464003 * furthermore, when a hotcue that is at the end of an active loop is activated, also disable the loop instead of jumping back to the loop entry
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 your update.
I have added some comments.
Does it work as expected now?
It would be great to have a unit test for this case.
Can you had one? Do you know how?
|
||
// if a seek was queued in the engine buffer move the current sample to its position | ||
ControlValueAtomic<double> seekPosition; | ||
if(getEngineBuffer()->isSeekQueued(seekPosition)){ |
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.
Please use git-clang-format HEAD~1 to fix some code style issues.
https://www.mixxx.org/wiki/doku.php/coding_guidelines#command_line
src/engine/enginebuffer.cpp
Outdated
setNewPlaypos(position, false); | ||
adjustingPhase = true; | ||
} | ||
setNewPlaypos(syncPosition, adjustingPhase); |
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.
The double setNewPlaypos() call reads a bit like a hack. Can we add for example a new parameter to identify the situation explicit?
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.
Yep, this is more like a workaround but as I am still new to the project I didn't want to change parameters of already existing methods. We could have a seekedPosition and syncedPosition in setNewPlaypos and notifySeek and let the controller decide what to do with this information. How about that?
src/engine/enginebuffer.cpp
Outdated
@@ -1284,6 +1296,14 @@ bool EngineBuffer::isTrackLoaded() { | |||
return false; | |||
} | |||
|
|||
bool EngineBuffer::isSeekQueued(ControlValueAtomic<double> &seekPos) { |
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.
we use only const references in Mixxx. I think we can use here a "double* pSeekPosition" as second return value.
src/engine/enginebuffer.cpp
Outdated
@@ -1284,6 +1296,14 @@ bool EngineBuffer::isTrackLoaded() { | |||
return false; | |||
} | |||
|
|||
bool EngineBuffer::isSeekQueued(ControlValueAtomic<double> &seekPos) { | |||
if(!m_iSeekQueued){ |
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 check redundant?
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.
Yup, was.
@@ -958,6 +959,14 @@ void LoopingControl::updateBeatLoopingControls() { | |||
} | |||
|
|||
void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable) { | |||
|
|||
// if a seek was queued in the engine buffer move the current sample to its position | |||
ControlValueAtomic<double> seekPosition; |
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.
we can use here a plain double, because it lives on the stack and is not used concurrently.
I will make changes and recommit. |
src/engine/enginebuffer.cpp
Outdated
} | ||
|
||
// seek finished after setNewPlaypos | ||
m_iSeekQueued.fetchAndStoreRelaxed(SEEK_NONE); |
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.
Are you able to justify that the reordering of atomic memory operations still works as expected?
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.
Actually, I couldn't. But it this line won't be necessary anymore.
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.
Some more comments. We are getting close. Thank you.
src/engine/enginebuffer.cpp
Outdated
@@ -1125,7 +1125,7 @@ void EngineBuffer::processSeek(bool paused) { | |||
// call anyway again. | |||
|
|||
SeekRequests seekType = static_cast<SeekRequest>( | |||
m_iSeekQueued.fetchAndStoreRelease(SEEK_NONE)); | |||
m_iSeekQueued.fetchAndStoreRelaxed(SEEK_NONE)); |
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.
here wee need to be sure that m_queuedSeekPosition.getValue() is not accessed before reading and writing m_iSeekQueued so I think fetchAndSubAcquire() would be the right call.
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.
That must be an autocomplete-mistake. I wanted to change it back to the original version.
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.
But it was originally wrong. We need the Aquire memory fence here, to stop the pipeline executing the reading of the seek position too early.
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.
OK, I will change that. But which value should I subtract?
Are you sure fetchAndSubAcquire
shouldn't be fetchAndStoreAcquire
?
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.
Ups ... of cause.
@@ -768,7 +769,7 @@ void LoopingControl::slotLoopEndPos(double pos) { | |||
} | |||
|
|||
// This is called from the engine thread | |||
void LoopingControl::notifySeek(double dNewPlaypos, bool adjustingPhase) { | |||
void LoopingControl::notifySeek(double dNewPlaypos, double dSyncedNewPlayPos, bool adjustingPhase) { |
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.
Can you clarify and finally ad a code comment why, we need to pass two positions here?
I am unsure, if we still need the adjustingPhase value. Is it only read here? Original it was to prevent jump out of a loop when adjusting phase.
Now we have the original and the adjusted position to detect this.
What was the case, you need this code for?
What are the cases, we need to considder?
Can you rename dNewPlayposition to
something more descriptive?
Like dRequestedPlaypos or such?
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.
Assume an active loop starting exactly at hotcue A. If I activate the hotcue the synced position is very likely in front of the loop. The adjustingPhase is still false, as it is was a SEEK_STANDARD_PHASE. If we set the adjustingPhase to true in that case, the loop would not be disabled for any other seek position outside the loop.
Thus, the loopcontroller should know about the position requested by the user but the remaining controllers should still be notified with the synced position.
I assume that the adjustPhase is still necessary. When there was no sync, both positions are equal. But (I guess) the same holds true if only a sync without any seeking is requested.
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 just had a thought on solving this case differently in the processSeek method. Let the loopingcontroller calculate a position inside a loop (if enabled) based on the requested and the synced position. If the requested position is outside the loop return the synced position and behave as currently. If the requested position is inside but the synced position is outside the loop, correct the synced position into the loop.
double syncPosition = position;
if (!paused && (seekType & SEEK_PHASE)) {
syncPosition = m_pBpmControl->getNearestPositionInPhase(position, true, true);
position = m_pLoopingControl->getSyncPositionInsideLoop(position, syncPosition);
}
The setNewPos and notifySeek could be left unchanged.
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, even better readability. Thank you.
But why do we need syncPosition outside the if block?
I think it would be even more clean to have a requested position as well and not reuse position for this.
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.
some more comments.
LoopSamples loopSamples = m_loopSamples.getValue(); | ||
|
||
// if the request itself is outside loop do nothing | ||
// loop will be disabled later by notifySeek(...) |
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.
or does it make sense to have the loop disable logic here?
|
||
// the synced position is in front of the loop | ||
// adjust the synced position to same amount in front of the loop end | ||
if (dSyncedPlayPos <= loopSamples.start) { |
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 should be just <
// the synced position is in front of the loop | ||
// adjust the synced position to same amount in front of the loop end | ||
if (dSyncedPlayPos <= loopSamples.start) { | ||
return loopSamples.end - (loopSamples.start - dSyncedPlayPos); |
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 can still be outside the loop in case the loop is only a fraction of a beat.
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're right, but easy to solve.
BTW: This whole method only works on beatloops (N-bar or fraction of N). Should we also care about loops which are non-beatloops or just ignore that case?
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 is independent from where loop in and out are set.
We may have this condition:
B____I__O
B=beat
I=loop in
O= loop out
We need only make sure that the time between. B and I is rolled of in the loop in a way that the requested point in the loop is reached as if the seek had been done to B.
So yes, we can ignore the case. The user is responsible to use a beat loop if he wishes the loop is in beat.
// the synced position is behind the loop | ||
// adjust the synced position to same amount behind the loop start | ||
if (dSyncedPlayPos >= loopSamples.end) { | ||
return loopSamples.start + (dSyncedPlayPos - loopSamples.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.
the same here, this can still be behind the loop in case of small loops.
src/engine/enginebuffer.cpp
Outdated
} | ||
|
||
if (position != m_filepos_play) { | ||
setNewPlaypos(position, adjustingPhase); |
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 we can now get rid of the adjustingPhase flag, because the position is always inside the loop now.
I will use create a new PR to fix https://bugs.launchpad.net/mixxx/+bug/1778246 in the 2.1 release. |
Fxes https://bugs.launchpad.net/mixxx/+bug/1464003 .
Basically the loopcontroller has to set the loop start position to the queued seek position before activating the loop.
Furthermore, when a hotcue that is at the end of an active loop is activated, also disable the loop instead of jumping back to the loop entry.