Skip to content

Enable beatloop activation directly after activating a hotcue#2190

Closed
goddisignz wants to merge 8 commits intomixxxdj:masterfrom
goddisignz:activate_cue_and_loop
Closed

Enable beatloop activation directly after activating a hotcue#2190
goddisignz wants to merge 8 commits intomixxxdj:masterfrom
goddisignz:activate_cue_and_loop

Conversation

@goddisignz
Copy link
Copy Markdown
Contributor

@goddisignz goddisignz commented Jun 24, 2019

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.

Comment thread src/engine/controls/loopingcontrol.cpp Outdated
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()){}
Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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.

Comment thread src/engine/controls/loopingcontrol.cpp
* 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
Copy link
Copy Markdown
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 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?

Comment thread src/engine/controls/loopingcontrol.cpp Outdated

// if a seek was queued in the engine buffer move the current sample to its position
ControlValueAtomic<double> seekPosition;
if(getEngineBuffer()->isSeekQueued(seekPosition)){
Copy link
Copy Markdown
Member

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

Comment thread src/engine/enginebuffer.cpp Outdated
setNewPlaypos(position, false);
adjustingPhase = true;
}
setNewPlaypos(syncPosition, adjustingPhase);
Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Contributor Author

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?

Comment thread src/engine/enginebuffer.cpp Outdated
return false;
}

bool EngineBuffer::isSeekQueued(ControlValueAtomic<double> &seekPos) {
Copy link
Copy Markdown
Member

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.

Comment thread src/engine/enginebuffer.cpp Outdated
}

bool EngineBuffer::isSeekQueued(ControlValueAtomic<double> &seekPos) {
if(!m_iSeekQueued){
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this check redundant?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yup, was.

Comment thread src/engine/controls/loopingcontrol.cpp Outdated
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;
Copy link
Copy Markdown
Member

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.

@goddisignz
Copy link
Copy Markdown
Contributor Author

goddisignz commented Jun 30, 2019

I will make changes and recommit.
It works as expected except in one case. If the synced position is in front of an active loop the playposition does not end up in front of the end of the loop. I would expect it to be like that.
Not sure if the loopcontroller can take care of that currently.

Comment thread src/engine/enginebuffer.cpp Outdated
}

// seek finished after setNewPlaypos
m_iSeekQueued.fetchAndStoreRelaxed(SEEK_NONE);
Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
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.

Some more comments. We are getting close. Thank you.

Comment thread src/engine/enginebuffer.cpp Outdated

SeekRequests seekType = static_cast<SeekRequest>(
m_iSeekQueued.fetchAndStoreRelease(SEEK_NONE));
m_iSeekQueued.fetchAndStoreRelaxed(SEEK_NONE));
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ups ... of cause.

Comment thread src/engine/controls/loopingcontrol.cpp Outdated

// This is called from the engine thread
void LoopingControl::notifySeek(double dNewPlaypos, bool adjustingPhase) {
void LoopingControl::notifySeek(double dNewPlaypos, double dSyncedNewPlayPos, bool adjustingPhase) {
Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Contributor Author

@goddisignz goddisignz Jul 2, 2019

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.

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
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.

some more comments.

LoopSamples loopSamples = m_loopSamples.getValue();

// if the request itself is outside loop do nothing
// loop will be disabled later by notifySeek(...)
Copy link
Copy Markdown
Member

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) {
Copy link
Copy Markdown
Member

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 <

Comment thread src/engine/controls/loopingcontrol.cpp Outdated
// 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);
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

@goddisignz goddisignz Jul 5, 2019

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?

Copy link
Copy Markdown
Member

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.

Comment thread src/engine/controls/loopingcontrol.cpp Outdated
// 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);
Copy link
Copy Markdown
Member

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.

Comment thread src/engine/enginebuffer.cpp Outdated
}

if (position != m_filepos_play) {
setNewPlaypos(position, adjustingPhase);
Copy link
Copy Markdown
Member

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.

@Holzhaus Holzhaus mentioned this pull request Jul 9, 2019
5 tasks
@goddisignz
Copy link
Copy Markdown
Contributor Author

I will use create a new PR to fix https://bugs.launchpad.net/mixxx/+bug/1778246 in the 2.1 release.
The remaining changes will be put to a new PR

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.

4 participants