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

[WIP] Rhythm Analyzer #2877

Draft
wants to merge 46 commits into
base: main
Choose a base branch
from
Draft

Conversation

crisclacerda
Copy link

@crisclacerda crisclacerda commented Jun 17, 2020

What is this?

A new analyzer capable of detecting the beats positions in frames and the first beat of a measure, of a phrase and of a section. A measure is a combination of beats, a phrase is a combination of measures that have a complete musical sense. A section is a combination of phrases that has a complete musical sense and represents a major structural part of track.

The new analyzer is designed to work with new beats class #2861

What this implement/fixes?

  • Multi-feature beat detection - The algorithm used to calculate the onset likelihood function is the first step of beat tracking. Previously Mixxx relied on the most versatile method only, "Complex Domain". However other methods may yields better results in some cases, especially "Spectral Difference" may be appropriate for percussive recordings and "Phase Deviation" for non-percussive music. Now the analyzer will compute several algorithms, measure the agreement between each pair, and choose the one with the overall higher agreement. This not only improves the accuracy of the detection in some cases it's also useful to compute our confidence level in the detection. Empirical tests showed that this value could be used as follow: x > .25 - very good; 20 < x < 25 - good, 15 < x < 20 - regular, 10 < x < 15 - reasonable, x < 10 - poor. We may think of exposing this nicely to the user?
  • Handle multiple BPMs in one track - There are 2 types of tracks that have multiple BPMs, there are transition tracks, that have more than one tempo. (ie: starts at 85 bpm, ends at 126 bpm. And there tracks with unsteady tempo,(ie: the dummer not keeping with the beat, a track could be 100bpm, and at some point, the band is actually playing at 99 bpm, and on some other point at 101. Previously the beatgrid only allowed users to have a fixed tempo grid for the whole track and alternatively, there was the beatmap that used the raw positions from the beat detector, this had one major problem that it has a lot of noise which makes the tempo fluctuate a lot. Now the analyzer first filters and smooth the detect raw tempos to find the places where there is a significant tempo change. The filtered raw tempos are not good to use for counting the local bpm because they lose information. Instead, for each segment that has no significant change, we check if the tempo is truly constant and make one fixed beatgrid, or if the tempo is ritardando, accelerando or not steady, in this case we make a fixed beatgrid for each measure.
  • Downbeats detection - Using the qm-dsp we find the indices of the beats that are downbeats. The method computes the spectral difference of ever consecutive pair of beat and looks the for the sequence that leads to the highest mean change.
  • Beats per bar detection - Expanding of the method above we should be able to find the number of beats per, for now this is fixed at 4.
  • Phrases and Section detection - Also using qm-dsp we will find the beats that start a new phrase and a new section.

void
DownBeat::findDownBeats(const float *audio,
std::vector<double>
DownBeat::beatsSD(const float *audio,
Copy link
Contributor

Choose a reason for hiding this comment

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

What is "SD"? Please spell out this abbreviation.

Copy link
Member

Choose a reason for hiding this comment

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

SpectralDifference?

Copy link
Contributor

@uklotzde uklotzde left a comment

Choose a reason for hiding this comment

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

Just some minor initial comments.

I noticed that you directly modified qm-dsp files. Please remember that we need to push those changes upstream and place a .patch file in lib/qm-dsp until then. Otherwise they might get lost with the next update of external code!

for (int i = dbind; i < (int)beats.size(); i += timesig) {
downbeats.push_back(i);
}
return m_beatsd;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why return a copy and store it as a member? If we store it as a member we can return a const-ref.

// leaving the outer for for now as it might be useful later
std::tuple<int, int> AnalyzerRhythm::computeMeter(std::vector<double> &beatsSD) {
// lower is included, higher excluded [)
int lowerBeatsPerBar = 4, higherBeatsPerBar = 5;
Copy link
Contributor

Choose a reason for hiding this comment

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

Split onto separate lines

size_t downbeatPositions = 0;
// convert beats positions from df increments to frams
for (size_t i = 0; i < beats.size(); ++i) {
double result = (beats.at(i) * m_stepSize) - m_stepSize / 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

Try to avoid literals in the code. There is an engine constant for the fixed number (2) of channels.

//qDebug() << bpb;
//qDebug() << firstDownbeat;

constexpr bool useDownbeatOnly = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Move all tuneable constants into an anonymous namespace at the top of the file.


#include "analyzer/plugins/buffering_utils.h"
#include "util/samplebuffer.h"
#include "util/memory.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

You can simply use <memory> in new code

@crisclacerda
Copy link
Author

crisclacerda commented Jun 17, 2020

I noticed that you directly modified qm-dsp files. Please remember that we need to push those changes upstream and place a .patch file in lib/qm-dsp until then. Otherwise they might get lost with the next update of external code!

We need to think this through.

I made these small changes for having a nicer API to work with avoiding unnecessary processing, as the code I have deleted is moved into the rhythm analyzer itself, with some smalls modifications to compute the beats per bar together with the downbeat position. This didn't work very well. So there is no point in moving it to the library and pushing it upstream. Also, there is no point in pushing it without that code because it losses it's main functionality.
For now, I could simply restore to old API and waste the internal downbeat calculation.

However, as I move forward I suspect we would be better of forking qm-dsp and building a custom version that uses it's lower level methods (autocorrelation function, beats spectral difference) but moves the higher levels calculations (beats, downbeats, etc.) to the rhythm analyzer itself. I believe this would allow us better performance and improved accuracy.

While having nice implementations of these higher levels features I have the impression that qm-dsp was not built as a "true" library for having all these features computed sequentially, together and in a performance-critical environment like Mixxx. It seems more like a collection of reference implementations of the algorithms described in their papers for proving they work as shown.

So I believe we would better of having our own "library" using qm-dsp methods as a building block than to rely on it as a library.

@daschuer
Copy link
Member

in this state of the project we should not limit ourselves by a chunky API. I it is a good idea to change it as it is needed. Later we can discuss with QM upstream how it is best way to contribute the wok back.
I am sure they are interested in our results. I see a good chance that we finally can re-anable migration of upstream changes.

@Holzhaus
Copy link
Member

Yeah, I'm all for upstreaming the changes. It would be good to only make changes to QM in separate commits and prefix the commit messages with qm-dsp: or something like that. That way we can easily accumulate all changes to qm later on, clean them up and submit them as upstream patches.

@daschuer
Copy link
Member

We are currently in an experimental stage, where the upstream changes are quite unclear.
So I am afraid this separation does not work yet. But we should make sure that we keep back porting to upstream in mind and possible. This also helps to adjust the scope of the new classes.

@daschuer
Copy link
Member

@crisclacerda: Do you have receipt for manual testing? Do I need to merge other banches first?

@crisclacerda
Copy link
Author

No, just build it and it will make a beat grid with only downbeats positions.
There was a bug in the downbeat positions that I just fixed but results are still far from good.
But I would appreciate your testing and feedback

@Be-ing Be-ing marked this pull request as draft June 18, 2020 01:38
@Be-ing
Copy link
Contributor

Be-ing commented Jun 18, 2020

I agree with @Holzhaus that it would be a good idea to separate changes to the upstream library into separate commits.

@crisclacerda
Copy link
Author

This last commit fixes a big bug that was messing with the results and they should be pretty decent now.
I am going to close #2847 and pick its code straight to the rhythm analyzer. Long term goal should be to get rid of the beatutils class

Copy link
Contributor

@hacksdump hacksdump left a comment

Choose a reason for hiding this comment

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

I tried this on an artificially generated beat track and most downbeats are being placed at the correct position.

build/depends.py Show resolved Hide resolved

namespace {

// This determines the resolution of the resulting BeatMap.
Copy link
Contributor

Choose a reason for hiding this comment

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

BeatMap is a legacy term. Should we start using the term Beats or BeatVector?

src/preferences/dialog/dlgprefrhythm.ui Show resolved Hide resolved
m_iMaxSamplesToProcess = m_iTotalSamples;
m_iCurrentSample = 0;

m_stepSize = m_iSampleRate * kStepSecs;
Copy link
Contributor

Choose a reason for hiding this comment

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

m_stepSize can be derived from m_iSampleRate.
Can we introduce a function getStepSize() const to calculate this on-demand rather than storing it in a member variable?

Copy link
Contributor

Choose a reason for hiding this comment

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

Minimizing mutable state is a good idea.

m_iMaxSamplesToProcess(0),
m_iCurrentSample(0),
m_iMinBpm(0),
m_iMaxBpm(9999) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
m_iMaxBpm(9999) {
m_iMaxBpm(kMaxSupportedBpm) {

}

bool AnalyzerRhythm::shouldAnalyze(TrackPointer pTrack) const {

Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary new line.

@hacksdump
Copy link
Contributor

Can we add test cases with actual tracks? Normal + edge cases.

@crisclacerda
Copy link
Author

Thanks for your review @hacksdump as well, looking forward to integrating this with your work!

@crisclacerda
Copy link
Author

Can we add test cases with actual tracks? Normal + edge cases.

Yaays, I will work on that!

@Holzhaus
Copy link
Member

Can we add test cases with actual tracks? Normal + edge cases.

We could use CC-licensed tracks, but I'd rather not add large MP3 files to this repo. Or are you talking about manual tests?

@hacksdump
Copy link
Contributor

Can we add test cases with actual tracks? Normal + edge cases.

We could use CC-licensed tracks, but I'd rather not add large MP3 files to this repo. Or are you talking about manual tests?

First preference should be programmatically generated tracks.
Real MP3s can't be included with the source. They should be downloaded at runtime if tests are to be performed with them.

loadSettings();

// TODO(Cristiano) Create remaining connections..
connect(bBeatsAndTempoEnabled, SIGNAL(stateChanged(int)),
Copy link
Contributor

Choose a reason for hiding this comment

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

These SIGNAL/SLOT macros are legacy from before Qt 5. Use the Qt 5 functor signal/slot syntax for new code.
https://wiki.qt.io/New_Signal_Slot_Syntax

Copy link
Contributor

Choose a reason for hiding this comment

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

ping

Copy link
Author

Choose a reason for hiding this comment

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

Can you point to me somewhere the new syntax is used in Mixxx? Wasn't able to make sense of it by only looking into the qt website example.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Be-ing
Copy link
Contributor

Be-ing commented Jun 21, 2020

First preference should be programmatically generated tracks.
Real MP3s can't be included with the source. They should be downloaded at runtime if tests are to be performed with them.

I'd be okay with including short audio files in the Git repository. We don't need 5 minute long MP3s for tests, do we?

--nonZeroCount;
}

std::vector<double> df;
Copy link
Contributor

Choose a reason for hiding this comment

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

What does "df" mean?

@Be-ing
Copy link
Contributor

Be-ing commented Jun 21, 2020

I cannot build this:

src/analyzer/analyzerrhythmbpm.cpp: In member function ‘std::tuple<QVector<double>, QMap<int, double> > AnalyzerRhythm::FixBeatsPositions()’:
src/analyzer/analyzerrhythmbpm.cpp:126:98: error: no matching function for call to ‘QVector<double>::QVector(QVector<double>::iterator, QVector<double>::iterator)’
  126 |                     m_resultBeats.begin() + beatStart, m_resultBeats.begin() + beatStart + middle);
      |                                                                                                  ^
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:532:1: note: candidate: ‘QVector<T>::QVector(std::initializer_list<_Tp>) [with T = double]’
  532 | QVector<T>::QVector(std::initializer_list<T> args)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:532:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:75:5: note: candidate: ‘QVector<T>::QVector(QVector<T>&&) [with T = double]’
   75 |     QVector(QVector<T> &&other) Q_DECL_NOTHROW : d(other.d) { other.d = Data::sharedNull(); }
      |     ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:75:5: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:363:8: note: candidate: ‘QVector<T>::QVector(const QVector<T>&) [with T = double]’
  363 | inline QVector<T>::QVector(const QVector<T> &v)
      |        ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:363:8: note:   candidate expects 1 argument, 2 provided
/usr/include/qt5/QtCore/qvector.h:510:1: note: candidate: ‘QVector<T>::QVector(int, const T&) [with T = double]’
  510 | QVector<T>::QVector(int asize, const T &t)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:510:41: note:   no known conversion for argument 2 from ‘QVector<double>::iterator’ {aka ‘double*’} to ‘const double&’
  510 | QVector<T>::QVector(int asize, const T &t)
      |                                ~~~~~~~~~^
/usr/include/qt5/QtCore/qvector.h:496:1: note: candidate: ‘QVector<T>::QVector(int) [with T = double]’
  496 | QVector<T>::QVector(int asize)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:496:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:68:12: note: candidate: ‘QVector<T>::QVector() [with T = double]’
   68 |     inline QVector() Q_DECL_NOTHROW : d(Data::sharedNull()) { }
      |            ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:68:12: note:   candidate expects 0 arguments, 2 provided
src/analyzer/analyzerrhythmbpm.cpp:128:96: error: no matching function for call to ‘QVector<double>::QVector(QVector<double>::iterator, QVector<double>::iterator)’
  128 |                     m_resultBeats.begin() + beatStart + middle, m_resultBeats.begin() + beatEnd);
      |                                                                                                ^
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:532:1: note: candidate: ‘QVector<T>::QVector(std::initializer_list<_Tp>) [with T = double]’
  532 | QVector<T>::QVector(std::initializer_list<T> args)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:532:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:75:5: note: candidate: ‘QVector<T>::QVector(QVector<T>&&) [with T = double]’
   75 |     QVector(QVector<T> &&other) Q_DECL_NOTHROW : d(other.d) { other.d = Data::sharedNull(); }
      |     ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:75:5: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:363:8: note: candidate: ‘QVector<T>::QVector(const QVector<T>&) [with T = double]’
  363 | inline QVector<T>::QVector(const QVector<T> &v)
      |        ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:363:8: note:   candidate expects 1 argument, 2 provided
/usr/include/qt5/QtCore/qvector.h:510:1: note: candidate: ‘QVector<T>::QVector(int, const T&) [with T = double]’
  510 | QVector<T>::QVector(int asize, const T &t)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:510:41: note:   no known conversion for argument 2 from ‘QVector<double>::iterator’ {aka ‘double*’} to ‘const double&’
  510 | QVector<T>::QVector(int asize, const T &t)
      |                                ~~~~~~~~~^
/usr/include/qt5/QtCore/qvector.h:496:1: note: candidate: ‘QVector<T>::QVector(int) [with T = double]’
  496 | QVector<T>::QVector(int asize)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:496:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:68:12: note: candidate: ‘QVector<T>::QVector() [with T = double]’
   68 |     inline QVector() Q_DECL_NOTHROW : d(Data::sharedNull()) { }
      |            ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:68:12: note:   candidate expects 0 arguments, 2 provided
src/analyzer/analyzerrhythmbpm.cpp:138:74: error: no matching function for call to ‘QVector<double>::QVector(QVector<double>::iterator, QVector<double>::iterator)’
  138 |                         m_resultBeats.begin() + beatStart + m_beatsPerBar);
      |                                                                          ^
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:532:1: note: candidate: ‘QVector<T>::QVector(std::initializer_list<_Tp>) [with T = double]’
  532 | QVector<T>::QVector(std::initializer_list<T> args)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:532:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:75:5: note: candidate: ‘QVector<T>::QVector(QVector<T>&&) [with T = double]’
   75 |     QVector(QVector<T> &&other) Q_DECL_NOTHROW : d(other.d) { other.d = Data::sharedNull(); }
      |     ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:75:5: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:363:8: note: candidate: ‘QVector<T>::QVector(const QVector<T>&) [with T = double]’
  363 | inline QVector<T>::QVector(const QVector<T> &v)
      |        ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:363:8: note:   candidate expects 1 argument, 2 provided
/usr/include/qt5/QtCore/qvector.h:510:1: note: candidate: ‘QVector<T>::QVector(int, const T&) [with T = double]’
  510 | QVector<T>::QVector(int asize, const T &t)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:510:41: note:   no known conversion for argument 2 from ‘QVector<double>::iterator’ {aka ‘double*’} to ‘const double&’
  510 | QVector<T>::QVector(int asize, const T &t)
      |                                ~~~~~~~~~^
/usr/include/qt5/QtCore/qvector.h:496:1: note: candidate: ‘QVector<T>::QVector(int) [with T = double]’
  496 | QVector<T>::QVector(int asize)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:496:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:68:12: note: candidate: ‘QVector<T>::QVector() [with T = double]’
   68 |     inline QVector() Q_DECL_NOTHROW : d(Data::sharedNull()) { }
      |            ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:68:12: note:   candidate expects 0 arguments, 2 provided
src/analyzer/analyzerrhythmbpm.cpp:150:79: error: no matching function for call to ‘QVector<double>::QVector(QVector<double>::iterator, QVector<double>::iterator)’
  150 |             m_resultBeats.begin() + beatStart, m_resultBeats.begin() + beatEnd);
      |                                                                               ^
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:532:1: note: candidate: ‘QVector<T>::QVector(std::initializer_list<_Tp>) [with T = double]’
  532 | QVector<T>::QVector(std::initializer_list<T> args)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:532:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:75:5: note: candidate: ‘QVector<T>::QVector(QVector<T>&&) [with T = double]’
   75 |     QVector(QVector<T> &&other) Q_DECL_NOTHROW : d(other.d) { other.d = Data::sharedNull(); }
      |     ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:75:5: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:363:8: note: candidate: ‘QVector<T>::QVector(const QVector<T>&) [with T = double]’
  363 | inline QVector<T>::QVector(const QVector<T> &v)
      |        ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:363:8: note:   candidate expects 1 argument, 2 provided
/usr/include/qt5/QtCore/qvector.h:510:1: note: candidate: ‘QVector<T>::QVector(int, const T&) [with T = double]’
  510 | QVector<T>::QVector(int asize, const T &t)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:510:41: note:   no known conversion for argument 2 from ‘QVector<double>::iterator’ {aka ‘double*’} to ‘const double&’
  510 | QVector<T>::QVector(int asize, const T &t)
      |                                ~~~~~~~~~^
/usr/include/qt5/QtCore/qvector.h:496:1: note: candidate: ‘QVector<T>::QVector(int) [with T = double]’
  496 | QVector<T>::QVector(int asize)
      | ^~~~~~~~~~
/usr/include/qt5/QtCore/qvector.h:496:1: note:   candidate expects 1 argument, 2 provided
In file included from /usr/include/qt5/QtCore/qlist.h:48,
                 from /usr/include/qt5/QtCore/qhash.h:46,
                 from /usr/include/qt5/QtCore/QHash:1,
                 from src/analyzer/analyzerrhythm.h:3,
                 from src/analyzer/analyzerrhythmbpm.cpp:1:
/usr/include/qt5/QtCore/qvector.h:68:12: note: candidate: ‘QVector<T>::QVector() [with T = double]’
   68 |     inline QVector() Q_DECL_NOTHROW : d(Data::sharedNull()) { }
      |            ^~~~~~~
/usr/include/qt5/QtCore/qvector.h:68:12: note:   candidate expects 0 arguments, 2 provided
scons: *** [lin64_build/src/analyzer/analyzerrhythmbpm.o] Error 1
src/analyzer/analyzerrhythmstats.cpp: In member function ‘virtual double MovingMode::compute()’:
src/analyzer/analyzerrhythmstats.cpp:30:12: warning: ‘mode’ may be used uninitialized in this function [-Wmaybe-uninitialized]
   30 |     return mode;
      |            ^~~~

Using Qt 5.13.2

@crisclacerda
Copy link
Author

template QVector::QVector(InputIterator first, InputIterator last)
Constructs a vector with the contents in the iterator range [first, last).
The value type of InputIterator must be convertible to T.
This function was introduced in Qt 5.14.

That's the reason.
I will change this to an std::vector, should be fine then

@crisclacerda
Copy link
Author

crisclacerda commented Jun 23, 2020

Implemented a multi-feature beat detection. The analysis is only very slightly slower but accuracy has improved.
For electronic music, the method we were using (Complex Spectral Difference) is usually always the best, but for acoustic music a lot of times other methods yield better results.
This should also allow us to have confidence estimative and since downbeats detection is very susceptible to have good beats positions this confidence also impacts that.
Also, have fallen back to std::vector constructors so this is not dependable on QT 5.14

@Be-ing
Copy link
Contributor

Be-ing commented Aug 7, 2020

There are a handful of old review comments that have not yet been addressed. Please don't let those fall through the cracks.

bestBpm = .0;
bin = 0;
for (int k = tempogramMinBin; k <= tempogramMaxBin; k++){
float w = ((float)k/tempogramFftLength)*(tempogramInputSampleRate);
Copy link
Member

Choose a reason for hiding this comment

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

Please use static_cast instead of C-style casts

@@ -450,7 +460,18 @@ void AnalyzerRhythm::computeMeter() {
bestHierarchy = meter;
}
}
qDebug() << bestHierarchy;
auto pulseValues = accumulatedPulsesLenghtsAndWeights.keyValueBegin();
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to use auto here? I have no idea what type pulseValues is from looking at the right hand side of the assignment.

Comment on lines +174 to +175
auto beatsAtLeft = QVector<double>::fromStdVector(std::vector<double>(
m_resultBeats.begin() + beatStart, m_resultBeats.begin() + beatStart + middle));
Copy link
Member

Choose a reason for hiding this comment

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

/home/jan/Projects/mixxx/src/analyzer/analyzerrhythmbpm.cpp:174:49: warning: ‘static QVector<T> QVector<T>::fromStdVector(const std::vector<T>&) [with T = double]’ is deprecated: Use QVector<T>(vector.begin(), vector.end()) instead. [-Wdeprecated-declarations]
  174 |             auto beatsAtLeft = QVector<double>::fromStdVector(std::vector<double>

m_iMinBpm(0),
m_iMaxBpm(9999),
m_noveltyCurveMinV(pow(10, kNoveltyCurveMinDB / 20.0)) {
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
}
Q_UNUSED(pConfig);
}

std::vector<double> AnalyzerRhythm::computeSnapGrid() {
int size = m_detectionResults.size();

int dfType = 3; // ComplexSD
Copy link
Member

Choose a reason for hiding this comment

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

Unused variable

// Find peak beats within a window of 9 SDs (100 ms)
// This limits the detection result to 600 BPM
for (int i = 0; i < size; ++i) {
double beat = 0;
Copy link
Member

Choose a reason for hiding this comment

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

This is set but not used.

Comment on lines +546 to +547
double bestBpm;
int bin;
Copy link
Member

Choose a reason for hiding this comment

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

Set but not used. Please always build with -DDEBUG_ASSERTIONS_FATAL=ON -DWARNINGS_FATAL=ON to catch these issues during development.

@Holzhaus
Copy link
Member

Cool, the beatgrid detection works quite good for my non-const tempo test track Robert Ouimet - Rainbow's Refunked. Beat positions look correct. In contrast, Serato completely failed to detect the beatgrid on this one when I tried it.

@Holzhaus
Copy link
Member

On the other hand, the detected tempo on both T. Williams & James Jacob feat. Tim Deluxe - The Learning Process and MVZZIK - Lotta Love fluctuate quite a lot, especially in (but not limited to) the quiet sections without an audible beat.

@crisclacerda
Copy link
Author

In the current state it's just the raw beat detector output, same as 2.3 beatmap.
The code to handle these fluctuations is still unfinished. Latest version is available on #2930
Next step is to use phrase and downbeat from meter detection to help iron the beats on musically relevant context rather than random places.

@Be-ing
Copy link
Contributor

Be-ing commented Aug 23, 2020

The UX in this branch is quite confusing. Why are there two beat analyzer pages in the preferences? Does the old one do anything? If not, why is it still there? It seems the beatgrid has to be manually cleared to get a track to analyze, otherwise a constant grid is generated from the BPM in the file tag??

@Be-ing
Copy link
Contributor

Be-ing commented Aug 23, 2020

The code to handle these fluctuations is still unfinished. Latest version is available on #2930

Why is this still split between separate branches?? I don't understand what your plan is here.

@Be-ing Be-ing mentioned this pull request Aug 23, 2020
8 tasks
@crisclacerda
Copy link
Author

Why is this still split between separate branches?? I don't understand what your plan is here.

This is still very experimental and rough. Downbeat detection with QM downbeat alone has a very low accuracy, beats per bar detection is still unfinished and so is phrases. Although we can already "see" then on the meter code, we still need code to pick the right one..
The plan is to submit and hopefully merge #2930 as my GSoC working product submission, with the improvements for handling the fluctuations only. Then keep working on this to actually got downbeat and phrases detection working nicely afterwards and that will probably take a least another couple months..

The UX in this branch is quite confusing. Why are there two beat analyzer pages in the preferences? Does the old one do anything? If not, why is it still there? It seems the beatgrid has to be manually cleared to get a track to analyze, otherwise a constant grid is generated from the BPM in the file tag??

The preferences there was just a quick mock and actually do not do anything. The beat detection is disabled in this branch. And the rhythm detection settings aren't connect to anything. It will always use the new analyzer unless there is still already beats object saved for the track.

I have worked on some improvements in the preferences in a local branch. That will be useful in the early beta stages of this, the beat detection keep workings as it was, but by enabling the rhythm detection it disable the beat detection and use this instead?

@Be-ing
Copy link
Contributor

Be-ing commented Aug 23, 2020

It seems the beatgrid has to be manually cleared to get a track to analyze, otherwise a constant grid is generated from the BPM in the file tag??

FWIW this bug is also present in master. If I disable the "assume constant tempo" option, a constant grid is still generated from the BPM file tag.

@Be-ing
Copy link
Contributor

Be-ing commented Aug 23, 2020

the beat detection keep workings as it was, but by enabling the rhythm detection it disable the beat detection and use this instead?

I don't understand. Remove the old analyzer and its preferences entirely otherwise this is really confusing to use.

@Be-ing Be-ing changed the base branch from master to main October 23, 2020 23:22
@github-actions
Copy link

This PR is marked as stale because it has been open 90 days with no activity.

@github-actions github-actions bot added the stale Stale issues that haven't been updated for a long time. label Jan 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale Stale issues that haven't been updated for a long time.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants