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

Changing tempos support #2930

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4236315
Changing tempos support
crisclacerda Jul 10, 2020
21adb5b
better styling
crisclacerda Jul 11, 2020
4b60566
better comments and names
crisclacerda Jul 17, 2020
8cd56e6
Remove Small Arrhythmic regions of tracks that have const tempo
crisclacerda Jul 29, 2020
22119d1
ironing code improvements
crisclacerda Aug 25, 2020
50e4afc
no spikes on ironing...
crisclacerda Aug 26, 2020
47a2298
fix iron code bug, better names and coments
crisclacerda Aug 27, 2020
9928587
more names and comments improvements
crisclacerda Aug 27, 2020
b7b0b31
add wraper to silince qt range constructor warning
crisclacerda Aug 27, 2020
2cc53e5
implemented Daniel's red ironing algorithm
crisclacerda Aug 28, 2020
35236ac
start investigating the dip problem near tempo changes
crisclacerda Aug 28, 2020
289ac87
add preferences and subversioning for new features
crisclacerda Aug 28, 2020
74a95a7
failed to add beatfactory.h on previous commit
crisclacerda Aug 28, 2020
fb2b6ba
Merge branch 'master' of https://github.com/mixxxdj/mixxx into transi…
crisclacerda Aug 29, 2020
f712f66
decouple statistics classes from beats, use circular buffer, other st…
crisclacerda Aug 29, 2020
4c1ed3c
change build files
crisclacerda Aug 29, 2020
43f19a0
fix CMakeList.txt
crisclacerda Aug 29, 2020
879603f
fix formating issues with git-clang-format
crisclacerda Aug 29, 2020
9031aff
add analyzer debug mode
crisclacerda Aug 29, 2020
74cfc23
move debugBeats to beatFactory, structure output, add help info and t…
crisclacerda Aug 29, 2020
e144e92
should fix fast msvc build
crisclacerda Aug 29, 2020
e838292
fix dip add extra condition for remove arrythmic
crisclacerda Aug 30, 2020
266eade
style fixes on statistics files
crisclacerda Aug 30, 2020
da6d275
Apply suggestions from code review
crisclacerda Aug 30, 2020
7f4f0df
switch to const refs when possible, added \n before eof, use Sample r…
crisclacerda Aug 31, 2020
9dfb9a0
Apply suggestions from code review
crisclacerda Sep 4, 2020
ea016e0
use the beat lenght instead of bpm for processing
crisclacerda Sep 6, 2020
583cbaa
failed to add windowedstatistics.h on previous commit
crisclacerda Sep 6, 2020
1aa3fbc
add todo to remove new preferences before 2.4
crisclacerda Sep 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/util/workerthread.cpp
src/util/workerthreadscheduler.cpp
src/util/xml.cpp
src/util/descriptivestatistics.cpp
src/util/windowedstatistics.cpp
src/waveform/guitick.cpp
src/waveform/renderers/glslwaveformrenderersignal.cpp
src/waveform/renderers/glvsynctestrenderer.cpp
Expand Down
4 changes: 3 additions & 1 deletion build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,9 @@ def sources(self, build):
"src/util/widgethelper.cpp",
"src/util/widgetrendertimer.cpp",
"src/util/workerthread.cpp",
"src/util/workerthreadscheduler.cpp"
"src/util/workerthreadscheduler.cpp",
"src/util/descriptivestatistics.cpp",
"src/util/windowedstatistics.cpp"
]
proto_args = {
'PROTOCPROTOPATH': ['src'],
Expand Down
8 changes: 8 additions & 0 deletions src/analyzer/analyzerbeats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool enforceBpmDetecti
m_bPreferencesFixedTempo(true),
m_bPreferencesOffsetCorrection(false),
m_bPreferencesFastAnalysis(false),
m_bPreferencesEnableIroning(false),
m_bPreferencesEnableArrhythmicRemoval(false),
m_iSampleRate(0),
m_iTotalSamples(0),
m_iMaxSamplesToProcess(0),
Expand Down Expand Up @@ -68,6 +70,8 @@ bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSample
m_bPreferencesOffsetCorrection = m_bpmSettings.getFixedTempoOffsetCorrection();
m_bPreferencesReanalyzeOldBpm = m_bpmSettings.getReanalyzeWhenSettingsChange();
m_bPreferencesFastAnalysis = m_bpmSettings.getFastAnalysis();
m_bPreferencesEnableArrhythmicRemoval = m_bpmSettings.getEnableArrythmicRemoval();
m_bPreferencesEnableIroning = m_bpmSettings.getEnableIroning();

if (availablePlugins().size() > 0) {
m_pluginId = defaultPlugin().id;
Expand Down Expand Up @@ -177,6 +181,8 @@ bool AnalyzerBeats::shouldAnalyze(TrackPointer tio) const {
QString newSubVersion = BeatFactory::getPreferredSubVersion(
m_bPreferencesFixedTempo,
m_bPreferencesOffsetCorrection,
m_bPreferencesEnableIroning,
m_bPreferencesEnableArrhythmicRemoval,
iMinBpm,
iMaxBpm,
extraVersionInfo);
Expand Down Expand Up @@ -234,6 +240,8 @@ void AnalyzerBeats::storeResults(TrackPointer tio) {
extraVersionInfo,
m_bPreferencesFixedTempo,
m_bPreferencesOffsetCorrection,
m_bPreferencesEnableIroning,
m_bPreferencesEnableArrhythmicRemoval,
m_iSampleRate,
m_iTotalSamples,
m_iMinBpm,
Expand Down
2 changes: 2 additions & 0 deletions src/analyzer/analyzerbeats.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class AnalyzerBeats : public Analyzer {
bool m_bPreferencesFixedTempo;
bool m_bPreferencesOffsetCorrection;
bool m_bPreferencesFastAnalysis;
bool m_bPreferencesEnableIroning;
bool m_bPreferencesEnableArrhythmicRemoval;

int m_iSampleRate;
int m_iTotalSamples;
Expand Down
2 changes: 1 addition & 1 deletion src/library/rekordbox/rekordboxfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
QHash<QString, QString> extraVersionInfo;

mixxx::BeatsPointer pBeats = BeatFactory::makePreferredBeats(
*track, beats, extraVersionInfo, false, false, sampleRate, 0, 0, 0);
*track, beats, extraVersionInfo, false, false, false, false, sampleRate, 0, 0, 0);

track->setBeats(pBeats);
} break;
Expand Down
10 changes: 9 additions & 1 deletion src/preferences/beatdetectionsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
#define BPM_REANALYZE_WHEN_SETTINGS_CHANGE "ReanalyzeWhenSettingsChange"
#define BPM_FAST_ANALYSIS_ENABLED "FastAnalysisEnabled"

#define BPM_ENABLE_IRONING "BeatDetectionEnableIroning"
#define BPM_ENABLE_ARRYTHMIC_REGIONS_REMOVAL "BeatDetectionEnableArrythmicRemoval"

#define BPM_RANGE_START "BPMRangeStart"
#define BPM_RANGE_END "BPMRangeEnd"

Expand All @@ -35,7 +38,7 @@ class BeatDetectionSettings {

DEFINE_PREFERENCE_HELPERS(FixedTempoAssumption, bool,
BPM_CONFIG_KEY, BPM_FIXED_TEMPO_ASSUMPTION, true);

DEFINE_PREFERENCE_HELPERS(FixedTempoOffsetCorrection, bool,
BPM_CONFIG_KEY, BPM_FIXED_TEMPO_OFFSET_CORRECTION, true);

Expand All @@ -45,6 +48,11 @@ class BeatDetectionSettings {
DEFINE_PREFERENCE_HELPERS(FastAnalysis, bool,
BPM_CONFIG_KEY, BPM_FAST_ANALYSIS_ENABLED, false);

// TODO(xxx) Remove these preferences before 2.4 release
DEFINE_PREFERENCE_HELPERS(EnableIroning, bool, BPM_CONFIG_KEY, BPM_ENABLE_IRONING, true);

DEFINE_PREFERENCE_HELPERS(EnableArrythmicRemoval, bool, BPM_CONFIG_KEY, BPM_ENABLE_ARRYTHMIC_REGIONS_REMOVAL, true);

QString getBeatPluginId() const {
return m_pConfig->getValue<QString>(ConfigKey(
VAMP_CONFIG_KEY, VAMP_ANALYZER_BEAT_PLUGIN_ID));
Expand Down
27 changes: 25 additions & 2 deletions src/preferences/dialog/dlgprefbeats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ DlgPrefBeats::DlgPrefBeats(QWidget *parent, UserSettingsPointer pConfig)

connect(bReanalyse,SIGNAL(stateChanged(int)),
this, SLOT(slotReanalyzeChanged(int)));

connect(bIron, SIGNAL(stateChanged(int)), this, SLOT(ironingEnabled(int)));
connect(bRemoveArrythmic, SIGNAL(stateChanged(int)), this, SLOT(removeArrythmicEnabled(int)));
Comment on lines +47 to +48
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 new-style signal/slot syntax.

}

DlgPrefBeats::~DlgPrefBeats() {
Expand All @@ -59,11 +62,11 @@ void DlgPrefBeats::loadSettings() {
m_boffsetEnabled = m_bpmSettings.getFixedTempoOffsetCorrection();
m_bReanalyze = m_bpmSettings.getReanalyzeWhenSettingsChange();
m_FastAnalysisEnabled = m_bpmSettings.getFastAnalysis();

// TODO(rryan): Above range enabled is not exposed?
m_minBpm = m_bpmSettings.getBpmRangeStart();
m_maxBpm = m_bpmSettings.getBpmRangeEnd();

m_bEnableIroning = m_bpmSettings.getEnableIroning();
m_bEnableArrythmicRemoval = m_bpmSettings.getEnableArrythmicRemoval();
slotUpdate();
}

Expand All @@ -79,6 +82,8 @@ void DlgPrefBeats::slotResetToDefaults() {
// TODO(rryan): Above range enabled is not exposed?
m_minBpm = m_bpmSettings.getBpmRangeStartDefault();
m_maxBpm = m_bpmSettings.getBpmRangeEndDefault();
m_bEnableIroning = m_bpmSettings.getEnableIroningDefault();
m_bEnableArrythmicRemoval = m_bpmSettings.getEnableArrythmicRemovalDefault();
slotUpdate();
}

Expand Down Expand Up @@ -115,6 +120,16 @@ void DlgPrefBeats::maxBpmRangeChanged(int value) {
slotUpdate();
}

void DlgPrefBeats::removeArrythmicEnabled(int value) {
m_bEnableArrythmicRemoval = static_cast<bool>(value);
slotUpdate();
}

void DlgPrefBeats::ironingEnabled(int value) {
m_bEnableIroning = static_cast<bool>(value);
slotUpdate();
}

void DlgPrefBeats::slotUpdate() {
bfixedtempo->setEnabled(m_banalyzerEnabled);
boffset->setEnabled((m_banalyzerEnabled && m_bfixedtempoEnabled));
Expand All @@ -125,6 +140,9 @@ void DlgPrefBeats::slotUpdate() {
txtMaxBpm->setEnabled(m_banalyzerEnabled && m_bfixedtempoEnabled);
txtMinBpm->setEnabled(m_banalyzerEnabled && m_bfixedtempoEnabled);
bReanalyse->setEnabled(m_banalyzerEnabled);
// Only apply corrections on non-constant tempo beatgrids
bIron->setEnabled(m_banalyzerEnabled && !m_bfixedtempoEnabled);
bRemoveArrythmic->setEnabled(m_banalyzerEnabled && !m_bfixedtempoEnabled);

if (!m_banalyzerEnabled) {
return;
Expand Down Expand Up @@ -158,6 +176,9 @@ void DlgPrefBeats::slotUpdate() {
txtMaxBpm->setValue(m_maxBpm);
txtMinBpm->setValue(m_minBpm);
bReanalyse->setChecked(m_bReanalyze);

bIron->setChecked(m_bEnableIroning);
bRemoveArrythmic->setChecked(m_bEnableArrythmicRemoval);
}

void DlgPrefBeats::slotReanalyzeChanged(int value) {
Expand All @@ -179,4 +200,6 @@ void DlgPrefBeats::slotApply() {
m_bpmSettings.setFastAnalysis(m_FastAnalysisEnabled);
m_bpmSettings.setBpmRangeStart(m_minBpm);
m_bpmSettings.setBpmRangeEnd(m_maxBpm);
m_bpmSettings.setEnableIroning(m_bEnableIroning);
m_bpmSettings.setEnableArrythmicRemoval(m_bEnableArrythmicRemoval);
}
4 changes: 4 additions & 0 deletions src/preferences/dialog/dlgprefbeats.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class DlgPrefBeats : public DlgPreferencePage, public Ui::DlgBeatsDlg {
void minBpmRangeChanged(int value);
void maxBpmRangeChanged(int value);
void slotReanalyzeChanged(int value);
void ironingEnabled(int value);
void removeArrythmicEnabled(int value);

private:
void loadSettings();
Expand All @@ -52,6 +54,8 @@ class DlgPrefBeats : public DlgPreferencePage, public Ui::DlgBeatsDlg {
bool m_boffsetEnabled;
bool m_FastAnalysisEnabled;
bool m_bReanalyze;
bool m_bEnableIroning;
bool m_bEnableArrythmicRemoval;
};

#endif // DLGPREFBEATS_H
20 changes: 20 additions & 0 deletions src/preferences/dialog/dlgprefbeatsdlg.ui
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,26 @@ by analyzing the beats to discard outliers.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="bIron">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The detected beats outputed by the Queen Mary algorithm fluctuates around a center tempo value. Attempts to remove unintended tempo variations by making the longest possible sequence of equidistant beats that fall within a +-25ms phase error of a const tempo, which should sound unnoticeable.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable ironing detected beats (Recommended)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="bRemoveArrythmic">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sometimes - especially on percussion free, such as ambient music, or effects heavy regions, such as builds and breaks of EDM - the detector fails to keep track of the steady pulse and will deviate from the metronome tempo. Attempts to remove the beats on these outliers tempo values by making them conform to the previous and next stable detected tempo if they are the same and regions is small.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable removal of arrythmic regions (Recommended)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down
100 changes: 92 additions & 8 deletions src/track/beatfactory.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,68 @@
#include <QFile>
#include <QtDebug>
#include <QStringList>

#include "track/beatgrid.h"
#include "track/beatmap.h"
#include "track/beatfactory.h"
#include "track/beatutils.h"
#include "util/cmdlineargs.h"


namespace {

void debugBeats(const Track& track, const QVector<double>& rawBeats,
const QVector<double>& correctedBeats, QString beatsVersion, QString beatsSubVersion) {
if(!CmdlineArgs::Instance().getAnalyzerDebug()) {
return;
}
QString debugFilename = QDir(CmdlineArgs::Instance().getSettingsPath()).filePath("beatAnalyzerOutput.csv");
QFile debugFile(debugFilename);
if (!debugFile.open(QIODevice::Append | QIODevice::Text)) {
qWarning() << "ERROR: Could not open debug file:" << debugFilename;
return;
}
QString trackHeader;
trackHeader += track.getInfo();
trackHeader += ", analysed at ";
trackHeader += QDateTime::currentDateTime().toString("yyyy-MM-dd_hh'h'mm'm'ss's'");
trackHeader += ", with " + beatsVersion + beatsSubVersion;
debugFile.write(trackHeader.toLocal8Bit());
QString sRawBeats;
QString sRawBeatLenght;
auto previousBeat = rawBeats.begin();
sRawBeats += QString::number(*previousBeat, 'f') + ",";
for (auto beat = std::begin(rawBeats) + 1, end = std::end(rawBeats); beat != end; beat += 1) {
sRawBeats += QString::number(*beat, 'f') + ",";
sRawBeatLenght += QString::number(*beat - *previousBeat, 'f') + ",";
previousBeat = beat;
}
QString sCorrectedBeats;
QString sCorrectedBeatLenght;
previousBeat = correctedBeats.begin();
sCorrectedBeats += QString::number(*previousBeat, 'f') + ",";
for (auto beat = std::begin(correctedBeats) + 1, end = std::end(correctedBeats); beat != end; beat += 1) {
sCorrectedBeats += QString::number(*beat, 'f') + ",";
sCorrectedBeatLenght += QString::number(*beat - *previousBeat, 'f') + ",";
previousBeat = beat;
}
QString resultHeader = "\nRaw beats\n";
debugFile.write(resultHeader.toLocal8Bit());
debugFile.write(sRawBeats.toLocal8Bit());
resultHeader = "\nCorrected beats\n";
debugFile.write(resultHeader.toLocal8Bit());
debugFile.write(sCorrectedBeats.toLocal8Bit());
resultHeader = "\nRaw beat length\n";
debugFile.write(resultHeader.toLocal8Bit());
debugFile.write(sRawBeatLenght.toLocal8Bit());
resultHeader = "\nCorrected beat length\n";
debugFile.write(resultHeader.toLocal8Bit());
debugFile.write(sCorrectedBeatLenght.toLocal8Bit());
debugFile.write("\n");
debugFile.close();
}

}

mixxx::BeatsPointer BeatFactory::loadBeatsFromByteArray(const Track& track,
QString beatsVersion,
Expand Down Expand Up @@ -45,9 +103,11 @@ QString BeatFactory::getPreferredVersion(
QString BeatFactory::getPreferredSubVersion(
const bool bEnableFixedTempoCorrection,
const bool bEnableOffsetCorrection,
const bool bEnableIroning,
const bool bEnableArrytimicRemoval,
const int iMinBpm,
const int iMaxBpm,
const QHash<QString, QString> extraVersionInfo) {
const QHash<QString, QString>& extraVersionInfo) {
const char* kSubVersionKeyValueSeparator = "=";
const char* kSubVersionFragmentSeparator = "|";
QStringList fragments;
Expand Down Expand Up @@ -83,6 +143,18 @@ QString BeatFactory::getPreferredSubVersion(
QString::number(1));
}

if (bEnableIroning && !bEnableFixedTempoCorrection) {
fragments << QString("enable_ironing%1%2")
.arg(kSubVersionKeyValueSeparator,
QString::number(1));
}

if (bEnableArrytimicRemoval && !bEnableFixedTempoCorrection) {
fragments << QString("enable_arrytimic_removal%1%2")
.arg(kSubVersionKeyValueSeparator,
QString::number(1));
}

fragments << QString("rounding%1%2")
.arg(kSubVersionKeyValueSeparator,
QString::number(0.05));
Expand All @@ -94,18 +166,23 @@ QString BeatFactory::getPreferredSubVersion(

mixxx::BeatsPointer BeatFactory::makePreferredBeats(const Track& track,
QVector<double> beats,
const QHash<QString, QString> extraVersionInfo,
const QHash<QString, QString>& extraVersionInfo,
const bool bEnableFixedTempoCorrection,
const bool bEnableOffsetCorrection,
const bool bEnableIroning,
const bool bEnableArrytimicRemoval,
const int iSampleRate,
const int iTotalSamples,
const int iMinBpm,
const int iMaxBpm) {
const QString version = getPreferredVersion(bEnableFixedTempoCorrection);
const QString subVersion = getPreferredSubVersion(bEnableFixedTempoCorrection,
bEnableOffsetCorrection,
iMinBpm, iMaxBpm,
extraVersionInfo);
bEnableOffsetCorrection,
bEnableIroning,
bEnableArrytimicRemoval,
iMinBpm,
iMaxBpm,
extraVersionInfo);

BeatUtils::printBeatStatistics(beats, iSampleRate);
if (version == BEAT_GRID_2_VERSION) {
Expand All @@ -119,9 +196,16 @@ mixxx::BeatsPointer BeatFactory::makePreferredBeats(const Track& track,
pGrid->setSubVersion(subVersion);
return mixxx::BeatsPointer(pGrid, &BeatFactory::deleteBeats);
} else if (version == BEAT_MAP_VERSION) {
mixxx::BeatMap* pBeatMap = new mixxx::BeatMap(track, iSampleRate, beats);
pBeatMap->setSubVersion(subVersion);
return mixxx::BeatsPointer(pBeatMap, &BeatFactory::deleteBeats);
if (bEnableIroning) {
QVector<double> correctedBeats = BeatUtils::correctBeatmap(
beats, mixxx::audio::SampleRate(iSampleRate), bEnableArrytimicRemoval);
debugBeats(track, beats, correctedBeats, version, subVersion);
beats = correctedBeats;
}
auto pMap = new mixxx::BeatMap(track, iSampleRate, beats);
pMap->setSubVersion(subVersion);
return mixxx::BeatsPointer(pMap, &BeatFactory::deleteBeats);

} else {
qDebug() << "ERROR: Could not determine what type of beatgrid to create.";
return mixxx::BeatsPointer();
Expand Down
Loading