diff --git a/CMakeLists.txt b/CMakeLists.txt
index a444543ce47..40cdb2a4d02 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -713,8 +713,6 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/sources/soundsourcesndfile.cpp
src/track/albuminfo.cpp
src/track/beatfactory.cpp
- src/track/beatgrid.cpp
- src/track/beatmap.cpp
src/track/beats.cpp
src/track/beatutils.cpp
src/track/bpm.cpp
@@ -1223,8 +1221,7 @@ add_executable(mixxx-test
src/test/audiotaperpot_test.cpp
src/test/autodjprocessor_test.cpp
src/test/baseeffecttest.cpp
- src/test/beatgridtest.cpp
- src/test/beatmaptest.cpp
+ src/test/beatstest.cpp
src/test/beatstranslatetest.cpp
src/test/bpmcontrol_test.cpp
src/test/broadcastprofile_test.cpp
diff --git a/build/depends.py b/build/depends.py
index 8fe2422d0d6..c282fc7a868 100644
--- a/build/depends.py
+++ b/build/depends.py
@@ -1222,8 +1222,6 @@ def sources(self, build):
"src/skin/launchimage.cpp",
"src/track/beatfactory.cpp",
- "src/track/beatgrid.cpp",
- "src/track/beatmap.cpp",
"src/track/beatutils.cpp",
"src/track/beats.cpp",
"src/track/bpm.cpp",
diff --git a/res/skins/Tango/buttons/btn_beats_curpos.svg b/res/skins/Tango/buttons/btn_beats_curpos.svg
index 6dabfab5a4f..98de1217ec6 100644
--- a/res/skins/Tango/buttons/btn_beats_curpos.svg
+++ b/res/skins/Tango/buttons/btn_beats_curpos.svg
@@ -1,2 +1,7 @@
-
+
diff --git a/src/analyzer/analyzerbeats.cpp b/src/analyzer/analyzerbeats.cpp
index cdbdbf50128..96f9368abb4 100644
--- a/src/analyzer/analyzerbeats.cpp
+++ b/src/analyzer/analyzerbeats.cpp
@@ -9,7 +9,7 @@
#include "analyzer/plugins/analyzerqueenmarybeats.h"
#include "analyzer/plugins/analyzersoundtouchbeats.h"
#include "track/beatfactory.h"
-#include "track/beatmap.h"
+#include "track/beats.h"
#include "track/beatutils.h"
#include "track/track.h"
@@ -43,7 +43,7 @@ AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool enforceBpmDetecti
m_iMaxBpm(9999) {
}
-bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSamples) {
+bool AnalyzerBeats::initialize(TrackPointer pTrack, int sampleRate, int totalSamples) {
if (totalSamples == 0) {
return false;
}
@@ -55,7 +55,7 @@ bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSample
return false;
}
- bool bpmLock = tio->isBpmLocked();
+ bool bpmLock = pTrack->isBpmLocked();
if (bpmLock) {
qDebug() << "Track is BpmLocked: Beat calculation will not start";
return false;
@@ -101,8 +101,7 @@ bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSample
m_iCurrentSample = 0;
// if we can load a stored track don't reanalyze it
- bool bShouldAnalyze = shouldAnalyze(tio);
-
+ bool bShouldAnalyze = shouldAnalyze(pTrack);
DEBUG_ASSERT(!m_pPlugin);
if (bShouldAnalyze) {
@@ -131,11 +130,11 @@ bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSample
return bShouldAnalyze;
}
-bool AnalyzerBeats::shouldAnalyze(TrackPointer tio) const {
+bool AnalyzerBeats::shouldAnalyze(TrackPointer pTrack) const {
int iMinBpm = m_bpmSettings.getBpmRangeStart();
int iMaxBpm = m_bpmSettings.getBpmRangeEnd();
- bool bpmLock = tio->isBpmLocked();
+ bool bpmLock = pTrack->isBpmLocked();
if (bpmLock) {
qDebug() << "Track is BpmLocked: Beat calculation will not start";
return false;
@@ -148,11 +147,11 @@ bool AnalyzerBeats::shouldAnalyze(TrackPointer tio) const {
// If the track already has a Beats object then we need to decide whether to
// analyze this track or not.
- mixxx::BeatsPointer pBeats = tio->getBeats();
+ mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (!pBeats) {
return true;
}
- if (!mixxx::Bpm::isValidValue(pBeats->getBpm())) {
+ if (!mixxx::Bpm::isValidValue(pBeats->getBpm().getValue())) {
// Tracks with an invalid bpm <= 0 should be re-analyzed,
// independent of the preference settings. We expect that
// all tracks have a bpm > 0 when analyzed. Users that want
@@ -161,7 +160,7 @@ bool AnalyzerBeats::shouldAnalyze(TrackPointer tio) const {
qDebug() << "Re-analyzing track with invalid BPM despite preference settings.";
return true;
}
- if (pBeats->findNextBeat(0) <= 0.0) {
+ if (pBeats->findNextBeat(mixxx::FramePos(0)) <= mixxx::FramePos(0.0)) {
qDebug() << "First beat is 0 for grid so analyzing track to find first beat.";
return true;
}
@@ -188,8 +187,8 @@ bool AnalyzerBeats::shouldAnalyze(TrackPointer tio) const {
// Beat grid exists but version and settings differ
if (!m_bPreferencesReanalyzeOldBpm) {
qDebug() << "Beat calculation skips analyzing because the track has"
- << "a BPM computed by a previous Mixxx version and user"
- << "preferences indicate we should not change it.";
+ << "a BPM computed by a previous Mixxx version and user"
+ << "preferences indicate we should not change it.";
return false;
}
@@ -213,7 +212,7 @@ void AnalyzerBeats::cleanup() {
m_pPlugin.reset();
}
-void AnalyzerBeats::storeResults(TrackPointer tio) {
+void AnalyzerBeats::storeResults(TrackPointer pTrack) {
VERIFY_OR_DEBUG_ASSERT(m_pPlugin) {
return;
}
@@ -229,57 +228,58 @@ void AnalyzerBeats::storeResults(TrackPointer tio) {
QHash extraVersionInfo = getExtraVersionInfo(
m_pluginId, m_bPreferencesFastAnalysis);
pBeats = BeatFactory::makePreferredBeats(
- *tio,
+ pTrack,
beats,
extraVersionInfo,
m_bPreferencesFixedTempo,
m_bPreferencesOffsetCorrection,
- m_iSampleRate,
m_iTotalSamples,
m_iMinBpm,
m_iMaxBpm);
qDebug() << "AnalyzerBeats plugin detected" << beats.size()
- << "beats. Average BPM:" << (pBeats ? pBeats->getBpm() : 0.0);
+ << "beats. Average BPM:" << (pBeats ? pBeats->getBpm().getValue() : 0.0);
} else {
- float bpm = m_pPlugin->getBpm();
+ mixxx::Bpm bpm = mixxx::Bpm(m_pPlugin->getBpm());
qDebug() << "AnalyzerBeats plugin detected constant BPM: " << bpm;
- pBeats = BeatFactory::makeBeatGrid(*tio, bpm, 0.0f);
+ pBeats = std::make_shared(pTrack.get());
+ pBeats->setGrid(bpm);
+ pTrack->setBeats(pBeats);
}
- mixxx::BeatsPointer pCurrentBeats = tio->getBeats();
+ mixxx::BeatsPointer pCurrentBeats = pTrack->getBeats();
// If the track has no beats object then set our newly generated one
// regardless of beat lock.
if (!pCurrentBeats) {
- tio->setBeats(pBeats);
+ pTrack->setBeats(pBeats);
return;
}
// If the track received the beat lock while we were analyzing it then we
// abort setting it.
- if (tio->isBpmLocked()) {
+ if (pTrack->isBpmLocked()) {
qDebug() << "Track was BPM-locked as we were analyzing it. Aborting analysis.";
return;
}
// If the user prefers to replace old beatgrids with newly generated ones or
// the old beatgrid has 0-bpm then we replace it.
- bool zeroCurrentBpm = pCurrentBeats->getBpm() == 0.0;
+ bool zeroCurrentBpm = pCurrentBeats->getBpm().getValue() == 0.0;
if (m_bPreferencesReanalyzeOldBpm || zeroCurrentBpm) {
if (zeroCurrentBpm) {
qDebug() << "Replacing 0-BPM beatgrid with a" << pBeats->getBpm()
<< "beatgrid.";
}
- tio->setBeats(pBeats);
+ pTrack->setBeats(pBeats);
return;
}
// If we got here then the user doesn't want to replace the beatgrid but
// since the first beat is zero we'll apply the offset we just detected.
- double currentFirstBeat = pCurrentBeats->findNextBeat(0);
- double newFirstBeat = pBeats->findNextBeat(0);
- if (currentFirstBeat == 0.0 && newFirstBeat > 0) {
- pCurrentBeats->translate(newFirstBeat);
+ mixxx::FramePos currentFirstBeat = pCurrentBeats->findNextBeat(mixxx::FramePos(0));
+ mixxx::FramePos newFirstBeat = pBeats->findNextBeat(mixxx::FramePos(0));
+ if (currentFirstBeat == mixxx::FramePos(0.0) && newFirstBeat > mixxx::FramePos(0)) {
+ pCurrentBeats->translate(newFirstBeat - currentFirstBeat);
}
}
diff --git a/src/analyzer/analyzerbeats.h b/src/analyzer/analyzerbeats.h
index 5dbb9a7f5f4..a82b7966900 100644
--- a/src/analyzer/analyzerbeats.h
+++ b/src/analyzer/analyzerbeats.h
@@ -27,13 +27,13 @@ class AnalyzerBeats : public Analyzer {
static QList availablePlugins();
static mixxx::AnalyzerPluginInfo defaultPlugin();
- bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override;
+ bool initialize(TrackPointer pTrack, int sampleRate, int totalSamples) override;
bool processSamples(const CSAMPLE *pIn, const int iLen) override;
- void storeResults(TrackPointer tio) override;
+ void storeResults(TrackPointer pTrack) override;
void cleanup() override;
private:
- bool shouldAnalyze(TrackPointer tio) const;
+ bool shouldAnalyze(TrackPointer pTrack) const;
static QHash getExtraVersionInfo(
QString pluginId, bool bPreferencesFastAnalysis);
diff --git a/src/controllers/controlpickermenu.cpp b/src/controllers/controlpickermenu.cpp
index 6ddfd3e6ef2..75ed45c58ca 100644
--- a/src/controllers/controlpickermenu.cpp
+++ b/src/controllers/controlpickermenu.cpp
@@ -27,8 +27,18 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent)
// Master Controls
QMenu* mixerMenu = addSubmenu(tr("Mixer"));
- addControl("[Master]", "balance", tr("Main Output Balance"), tr("Main output balance"), mixerMenu, true);
- addControl("[Master]", "delay", tr("Main Output Delay"), tr("Main output delay"), mixerMenu, true);
+ addControl("[Master]",
+ "balance",
+ tr("Main Output Balance"),
+ tr("Main output balance"),
+ mixerMenu,
+ true);
+ addControl("[Master]",
+ "delay",
+ tr("Main Output Delay"),
+ tr("Main output delay"),
+ mixerMenu,
+ true);
addControl("[Master]", "crossfader", tr("Crossfader"), tr("Crossfader"), mixerMenu, true);
addControl("[Master]", "gain", tr("Main Output Gain"), tr("Main output gain"), mixerMenu, true);
addControl("[Master]", "headGain", tr("Headphone Gain"), tr("Headphone gain"), mixerMenu, true);
diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp
index c2baca7c263..284f24a8d8c 100644
--- a/src/engine/controls/bpmcontrol.cpp
+++ b/src/engine/controls/bpmcontrol.cpp
@@ -2,15 +2,14 @@
#include
-#include "control/controllinpotmeter.h"
#include "control/controlobject.h"
#include "control/controlproxy.h"
-#include "control/controlpushbutton.h"
#include "engine/channels/enginechannel.h"
#include "engine/enginebuffer.h"
#include "engine/enginemaster.h"
#include "util/assert.h"
#include "util/duration.h"
+#include "util/frameadapter.h"
#include "util/logger.h"
#include "util/math.h"
#include "waveform/visualplayposition.h"
@@ -35,7 +34,7 @@ constexpr int kBpmTapFilterLength = 5;
// The local_bpm is calculated forward and backward this number of beats, so
// the actual number of beats is this x2.
constexpr int kLocalBpmSpan = 4;
-constexpr SINT kSamplesPerFrame = 2;
+constexpr double kSlightBeatsTranslateFactor = 0.01;
}
BpmControl::BpmControl(QString group,
@@ -48,37 +47,49 @@ BpmControl::BpmControl(QString group,
m_dSyncTargetBeatDistance.setValue(0.0);
m_dUserOffset.setValue(0.0);
- m_pPlayButton = new ControlProxy(group, "play", this);
- m_pReverseButton = new ControlProxy(group, "reverse", this);
- m_pRateRatio = new ControlProxy(group, "rate_ratio", this);
+ m_pPlayButton = make_parented(group, "play", this);
+ m_pReverseButton = make_parented(group, "reverse", this);
+ m_pRateRatio = make_parented(group, "rate_ratio", this);
m_pRateRatio->connectValueChanged(this, &BpmControl::slotUpdateEngineBpm,
Qt::DirectConnection);
m_pQuantize = ControlObject::getControl(group, "quantize");
- m_pPrevBeat.reset(new ControlProxy(group, "beat_prev"));
- m_pNextBeat.reset(new ControlProxy(group, "beat_next"));
+ m_pPrevBeat = make_parented(group, "beat_prev", this);
+ m_pNextBeat = make_parented(group, "beat_next", this);
- m_pLoopEnabled = new ControlProxy(group, "loop_enabled", this);
- m_pLoopStartPosition = new ControlProxy(group, "loop_start_position", this);
- m_pLoopEndPosition = new ControlProxy(group, "loop_end_position", this);
+ m_pLoopEnabled = make_parented(group, "loop_enabled", this);
+ m_pLoopStartPosition = make_parented(group, "loop_start_position", this);
+ m_pLoopEndPosition = make_parented(group, "loop_end_position", this);
- m_pLocalBpm = new ControlObject(ConfigKey(group, "local_bpm"));
- m_pAdjustBeatsFaster = new ControlPushButton(ConfigKey(group, "beats_adjust_faster"), false);
- connect(m_pAdjustBeatsFaster, &ControlObject::valueChanged,
- this, &BpmControl::slotAdjustBeatsFaster,
+ m_pLocalBpm = std::make_unique(ConfigKey(group, "local_bpm"));
+ m_pAdjustBeatsFaster = std::make_unique(
+ ConfigKey(group, "beats_adjust_faster"), false);
+ connect(m_pAdjustBeatsFaster.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotAdjustBeatsFaster,
Qt::DirectConnection);
- m_pAdjustBeatsSlower = new ControlPushButton(ConfigKey(group, "beats_adjust_slower"), false);
- connect(m_pAdjustBeatsSlower, &ControlObject::valueChanged,
- this, &BpmControl::slotAdjustBeatsSlower,
+ m_pAdjustBeatsSlower = std::make_unique(
+ ConfigKey(group, "beats_adjust_slower"), false);
+ connect(m_pAdjustBeatsSlower.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotAdjustBeatsSlower,
Qt::DirectConnection);
- m_pTranslateBeatsEarlier = new ControlPushButton(ConfigKey(group, "beats_translate_earlier"), false);
- connect(m_pTranslateBeatsEarlier, &ControlObject::valueChanged,
- this, &BpmControl::slotTranslateBeatsEarlier,
+ m_pTranslateBeatsEarlier = std::make_unique(
+ ConfigKey(group, "beats_translate_earlier"), false);
+ connect(m_pTranslateBeatsEarlier.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotTranslateBeatsEarlier,
Qt::DirectConnection);
- m_pTranslateBeatsLater = new ControlPushButton(ConfigKey(group, "beats_translate_later"), false);
- connect(m_pTranslateBeatsLater, &ControlObject::valueChanged,
- this, &BpmControl::slotTranslateBeatsLater,
+ m_pTranslateBeatsLater = std::make_unique(
+ ConfigKey(group, "beats_translate_later"), false);
+ connect(m_pTranslateBeatsLater.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotTranslateBeatsLater,
Qt::DirectConnection);
// Pick a wide range (kBpmRangeMin to kBpmRangeMax) and allow out of bounds sets. This lets you
@@ -86,71 +97,78 @@ BpmControl::BpmControl(QString group,
// bpm_down controls.
// bpm_up / bpm_down steps by kBpmRangeStep
// bpm_up_small / bpm_down_small steps by kBpmRangeSmallStep
- m_pEngineBpm = new ControlLinPotmeter(
- ConfigKey(group, "bpm"),
+ m_pEngineBpm = std::make_unique(ConfigKey(group, "bpm"),
kBpmRangeMin,
kBpmRangeMax,
kBpmRangeStep,
kBpmRangeSmallStep,
true);
m_pEngineBpm->set(0.0);
- connect(m_pEngineBpm, &ControlObject::valueChanged,
- this, &BpmControl::slotUpdateRateSlider,
+ connect(m_pEngineBpm.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotUpdateRateSlider,
Qt::DirectConnection);
- m_pButtonTap = new ControlPushButton(ConfigKey(group, "bpm_tap"));
- connect(m_pButtonTap, &ControlObject::valueChanged,
- this, &BpmControl::slotBpmTap,
+ m_pButtonTap =
+ std::make_unique(ConfigKey(group, "bpm_tap"));
+ connect(m_pButtonTap.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotBpmTap,
Qt::DirectConnection);
// Beat sync (scale buffer tempo relative to tempo of other buffer)
- m_pButtonSync = new ControlPushButton(ConfigKey(group, "beatsync"));
- connect(m_pButtonSync, &ControlObject::valueChanged,
- this, &BpmControl::slotControlBeatSync,
+ m_pButtonSync =
+ std::make_unique(ConfigKey(group, "beatsync"));
+ connect(m_pButtonSync.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotControlBeatSync,
Qt::DirectConnection);
- m_pButtonSyncPhase = new ControlPushButton(ConfigKey(group, "beatsync_phase"));
- connect(m_pButtonSyncPhase, &ControlObject::valueChanged,
- this, &BpmControl::slotControlBeatSyncPhase,
+ m_pButtonSyncPhase = std::make_unique(
+ ConfigKey(group, "beatsync_phase"));
+ connect(m_pButtonSyncPhase.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotControlBeatSyncPhase,
Qt::DirectConnection);
- m_pButtonSyncTempo = new ControlPushButton(ConfigKey(group, "beatsync_tempo"));
- connect(m_pButtonSyncTempo, &ControlObject::valueChanged,
- this, &BpmControl::slotControlBeatSyncTempo,
+ m_pButtonSyncTempo = std::make_unique(
+ ConfigKey(group, "beatsync_tempo"));
+ connect(m_pButtonSyncTempo.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotControlBeatSyncTempo,
Qt::DirectConnection);
- m_pTranslateBeats = new ControlPushButton(ConfigKey(group, "beats_translate_curpos"));
- connect(m_pTranslateBeats, &ControlObject::valueChanged,
- this, &BpmControl::slotBeatsTranslate,
+ m_pTranslateBeats = std::make_unique(
+ ConfigKey(group, "beats_translate_curpos"));
+ connect(m_pTranslateBeats.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotBeatsTranslate,
Qt::DirectConnection);
- m_pBeatsTranslateMatchAlignment = new ControlPushButton(ConfigKey(group, "beats_translate_match_alignment"));
- connect(m_pBeatsTranslateMatchAlignment, &ControlObject::valueChanged,
- this, &BpmControl::slotBeatsTranslateMatchAlignment,
+ m_pBeatsTranslateMatchAlignment = std::make_unique(
+ ConfigKey(group, "beats_translate_match_alignment"));
+ connect(m_pBeatsTranslateMatchAlignment.get(),
+ &ControlObject::valueChanged,
+ this,
+ &BpmControl::slotBeatsTranslateMatchAlignment,
Qt::DirectConnection);
- connect(&m_tapFilter, &TapFilter::tapped,
- this, &BpmControl::slotTapFilter,
+ connect(&m_tapFilter,
+ &TapFilter::tapped,
+ this,
+ &BpmControl::slotTapFilter,
Qt::DirectConnection);
// Measures distance from last beat in percentage: 0.5 = half-beat away.
- m_pThisBeatDistance = new ControlProxy(group, "beat_distance", this);
- m_pSyncMode = new ControlProxy(group, "sync_mode", this);
-}
-
-BpmControl::~BpmControl() {
- delete m_pLocalBpm;
- delete m_pEngineBpm;
- delete m_pButtonTap;
- delete m_pButtonSync;
- delete m_pButtonSyncPhase;
- delete m_pButtonSyncTempo;
- delete m_pTranslateBeats;
- delete m_pBeatsTranslateMatchAlignment;
- delete m_pTranslateBeatsEarlier;
- delete m_pTranslateBeatsLater;
- delete m_pAdjustBeatsFaster;
- delete m_pAdjustBeatsSlower;
+ m_pThisBeatDistance =
+ make_parented(group, "beat_distance", this);
+ m_pSyncMode = make_parented(group, "sync_mode", this);
}
double BpmControl::getBpm() const {
@@ -158,39 +176,35 @@ double BpmControl::getBpm() const {
}
void BpmControl::slotAdjustBeatsFaster(double v) {
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (v > 0 && pBeats && (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM)) {
- double bpm = pBeats->getBpm();
- double adjustedBpm = bpm + kBpmAdjustStep;
- pBeats->setBpm(adjustedBpm);
+ if (v > 0 && m_pBeats) {
+ auto bpm = m_pBeats->getBpm();
+ auto adjustedBpm = bpm + kBpmAdjustStep;
+ m_pBeats->setBpm(adjustedBpm);
}
}
void BpmControl::slotAdjustBeatsSlower(double v) {
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (v > 0 && pBeats && (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM)) {
- double bpm = pBeats->getBpm();
- double adjustedBpm = math_max(kBpmAdjustMin, bpm - kBpmAdjustStep);
- pBeats->setBpm(adjustedBpm);
+ if (v > 0 && m_pBeats) {
+ auto bpm = m_pBeats->getBpm();
+ auto adjustedBpm = mixxx::Bpm(math_max(kBpmAdjustMin, (bpm - kBpmAdjustStep).getValue()));
+ m_pBeats->setBpm(adjustedBpm);
}
}
void BpmControl::slotTranslateBeatsEarlier(double v) {
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (v > 0 && pBeats &&
- (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_TRANSLATE)) {
- const int translate_dist = getSampleOfTrack().rate * -.01;
- pBeats->translate(translate_dist);
+ if (v > 0 && m_pBeats) {
+ const mixxx::FrameDiff_t translateDistFrames = samplesToFrames(
+ getSampleOfTrack().rate * -kSlightBeatsTranslateFactor);
+ m_pBeats->translate(translateDistFrames);
}
}
void BpmControl::slotTranslateBeatsLater(double v) {
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (v > 0 && pBeats &&
- (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_TRANSLATE)) {
+ if (v > 0 && m_pBeats) {
// TODO(rryan): Track::getSampleRate is possibly inaccurate!
- const int translate_dist = getSampleOfTrack().rate * .01;
- pBeats->translate(translate_dist);
+ const mixxx::FrameDiff_t translateDistFrames = samplesToFrames(
+ getSampleOfTrack().rate * kSlightBeatsTranslateFactor);
+ m_pBeats->translate(translateDistFrames);
}
}
@@ -208,8 +222,7 @@ void BpmControl::slotTapFilter(double averageLength, int numSamples) {
return;
}
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (!pBeats) {
+ if (!m_pBeats) {
return;
}
@@ -220,12 +233,13 @@ void BpmControl::slotTapFilter(double averageLength, int numSamples) {
// (60 seconds per minute) * (1000 milliseconds per second) / (X millis per
// beat) = Y beats/minute
- double averageBpm = 60.0 * 1000.0 / averageLength / rateRatio;
- pBeats->setBpm(averageBpm);
+ mixxx::Bpm averageBpm(60.0 * 1000.0 / averageLength / rateRatio);
+ m_pBeats->setBpm(averageBpm);
}
void BpmControl::slotControlBeatSyncPhase(double v) {
- if (!v) return;
+ if (!v)
+ return;
if (isSynchronized()) {
m_dUserOffset.setValue(0.0);
@@ -234,12 +248,14 @@ void BpmControl::slotControlBeatSyncPhase(double v) {
}
void BpmControl::slotControlBeatSyncTempo(double v) {
- if (!v) return;
+ if (!v)
+ return;
syncTempo();
}
void BpmControl::slotControlBeatSync(double v) {
- if (!v) return;
+ if (!v)
+ return;
if (!syncTempo()) {
// syncTempo failed, nothing else to do
return;
@@ -326,8 +342,8 @@ bool BpmControl::syncTempo() {
}
// static
-double BpmControl::shortestPercentageChange(const double& current_percentage,
- const double& target_percentage) {
+double BpmControl::shortestPercentageChange(
+ const double& current_percentage, const double& target_percentage) {
if (current_percentage == target_percentage) {
return 0.0;
} else if (current_percentage < target_percentage) {
@@ -343,27 +359,32 @@ double BpmControl::shortestPercentageChange(const double& current_percentage,
// my: 0.25 target: 0.5 backwards: -0.75
// my: 0.25 target: 0.75 backwards: -0.5
// my: 0.98 target: 0.99 backwards: -0.99
- const double backwardsDistance = target_percentage - current_percentage - 1.0;
+ const double backwardsDistance =
+ target_percentage - current_percentage - 1.0;
- return (fabs(forwardDistance) < fabs(backwardsDistance)) ?
- forwardDistance : backwardsDistance;
+ return (fabs(forwardDistance) < fabs(backwardsDistance))
+ ? forwardDistance
+ : backwardsDistance;
} else { // current_percentage > target_percentage
// Invariant: forwardDistance - backwardsDistance == 1.0
// my: 0.99 target: 0.01 forwards: 0.02
- const double forwardDistance = 1.0 - current_percentage + target_percentage;
+ const double forwardDistance =
+ 1.0 - current_percentage + target_percentage;
// my: 0.99 target:0.01 backwards: -0.98
const double backwardsDistance = target_percentage - current_percentage;
- return (fabs(forwardDistance) < fabs(backwardsDistance)) ?
- forwardDistance : backwardsDistance;
+ return (fabs(forwardDistance) < fabs(backwardsDistance))
+ ? forwardDistance
+ : backwardsDistance;
}
}
double BpmControl::calcSyncedRate(double userTweak) {
if (kLogger.traceEnabled()) {
- kLogger.trace() << getGroup() << "BpmControl::calcSyncedRate, tweak " << userTweak;
+ kLogger.trace() << getGroup() << "BpmControl::calcSyncedRate, tweak "
+ << userTweak;
}
m_dUserTweakingSync = userTweak != 0.0;
double rate = 1.0;
@@ -374,8 +395,8 @@ double BpmControl::calcSyncedRate(double userTweak) {
// If we are not quantized, or there are no beats, or we're master,
// or we're in reverse, just return the rate as-is.
- if (!m_pQuantize->get() || isMaster(getSyncMode()) ||
- !m_pBeats || m_pReverseButton->get()) {
+ if (!m_pQuantize->get() || isMaster(getSyncMode()) || !m_pBeats ||
+ m_pReverseButton->get()) {
m_resetSyncAdjustment = true;
return rate + userTweak;
}
@@ -393,9 +414,9 @@ double BpmControl::calcSyncedRate(double userTweak) {
// Now that we have our beat distance we can also check how large the
// current loop is. If we are in a <1 beat loop, don't worry about offset.
const bool loop_enabled = m_pLoopEnabled->toBool();
- const double loop_size = (m_pLoopEndPosition->get() -
- m_pLoopStartPosition->get()) /
- dBeatLength;
+ const double loop_size =
+ (m_pLoopEndPosition->get() - m_pLoopStartPosition->get()) /
+ dBeatLength;
if (loop_enabled && loop_size < 1.0 && loop_size > 0) {
m_resetSyncAdjustment = true;
return rate + userTweak;
@@ -427,9 +448,10 @@ double BpmControl::calcSyncAdjustment(bool userTweakingSync) {
double syncTargetBeatDistance = m_dSyncTargetBeatDistance.getValue();
// We want the untweaked beat distance, so we have to add the offset here.
- double thisBeatDistance = m_pThisBeatDistance->get() + m_dUserOffset.getValue();
- double shortest_distance = shortestPercentageChange(
- syncTargetBeatDistance, thisBeatDistance);
+ double thisBeatDistance =
+ m_pThisBeatDistance->get() + m_dUserOffset.getValue();
+ double shortest_distance =
+ shortestPercentageChange(syncTargetBeatDistance, thisBeatDistance);
if (kLogger.traceEnabled()) {
kLogger.trace() << m_group << "****************";
@@ -471,9 +493,10 @@ double BpmControl::calcSyncAdjustment(bool userTweakingSync) {
delta = math_clamp(delta, -kSyncDeltaCap, kSyncDeltaCap);
// Cap the adjustment between -kSyncAdjustmentCap and +kSyncAdjustmentCap
- adjustment = 1.0 + math_clamp(
- m_dLastSyncAdjustment - 1.0 + delta,
- -kSyncAdjustmentCap, kSyncAdjustmentCap);
+ adjustment = 1.0 +
+ math_clamp(m_dLastSyncAdjustment - 1.0 + delta,
+ -kSyncAdjustmentCap,
+ kSyncAdjustmentCap);
} else {
// We are in sync, no adjustment needed.
adjustment = 1.0;
@@ -490,7 +513,8 @@ double BpmControl::getBeatDistance(double dThisPosition) const {
// we don't adjust the reported distance the track will try to adjust
// sync against itself.
if (kLogger.traceEnabled()) {
- kLogger.trace() << getGroup() << "BpmControl::getBeatDistance" << dThisPosition;
+ kLogger.trace() << getGroup() << "BpmControl::getBeatDistance"
+ << dThisPosition;
}
double dPrevBeat = m_pPrevBeat->get();
double dNextBeat = m_pNextBeat->get();
@@ -500,109 +524,122 @@ double BpmControl::getBeatDistance(double dThisPosition) const {
}
double dBeatLength = dNextBeat - dPrevBeat;
- double dBeatPercentage = dBeatLength == 0.0 ? 0.0 :
- (dThisPosition - dPrevBeat) / dBeatLength;
+ double dBeatPercentage = dBeatLength == 0.0
+ ? 0.0
+ : (dThisPosition - dPrevBeat) / dBeatLength;
// Because findNext and findPrev have an epsilon built in, sometimes
// the beat percentage is out of range. Fix it.
- if (dBeatPercentage < 0) ++dBeatPercentage;
- if (dBeatPercentage > 1) --dBeatPercentage;
+ if (dBeatPercentage < 0)
+ ++dBeatPercentage;
+ if (dBeatPercentage > 1)
+ --dBeatPercentage;
return dBeatPercentage - m_dUserOffset.getValue();
}
// static
bool BpmControl::getBeatContext(const mixxx::BeatsPointer& pBeats,
- const double dPosition,
- double* dpPrevBeat,
- double* dpNextBeat,
- double* dpBeatLength,
+ const mixxx::FramePos position,
+ mixxx::FramePos* pPrevBeat,
+ mixxx::FramePos* pNextBeat,
+ mixxx::FrameDiff_t* dpBeatLength,
double* dpBeatPercentage) {
if (!pBeats) {
return false;
}
- double dPrevBeat;
- double dNextBeat;
- if (!pBeats->findPrevNextBeats(dPosition, &dPrevBeat, &dNextBeat)) {
+ mixxx::FramePos pPrevBeatInner, pNextBeatInner;
+ if (!pBeats->findPrevNextBeats(
+ position, &pPrevBeatInner, &pNextBeatInner)) {
return false;
}
- if (dpPrevBeat != NULL) {
- *dpPrevBeat = dPrevBeat;
+ if (pPrevBeat != nullptr) {
+ *pPrevBeat = pPrevBeatInner;
}
- if (dpNextBeat != NULL) {
- *dpNextBeat = dNextBeat;
+ if (pNextBeat != nullptr) {
+ *pNextBeat = pNextBeatInner;
}
- return getBeatContextNoLookup(dPosition, dPrevBeat, dNextBeat,
- dpBeatLength, dpBeatPercentage);
+ return getBeatContextNoLookup(position,
+ pPrevBeatInner,
+ pNextBeatInner,
+ dpBeatLength,
+ dpBeatPercentage);
}
// static
bool BpmControl::getBeatContextNoLookup(
- const double dPosition,
- const double dPrevBeat,
- const double dNextBeat,
- double* dpBeatLength,
- double* dpBeatPercentage) {
- if (dPrevBeat == -1 || dNextBeat == -1) {
+ const mixxx::FramePos position,
+ mixxx::FramePos pPrevBeat,
+ mixxx::FramePos pNextBeat,
+ mixxx::FrameDiff_t* dpBeatLength,
+ double* dpBeatPercentage) {
+ if (pPrevBeat == mixxx::kInvalidFramePos || pNextBeat == mixxx::kInvalidFramePos) {
return false;
}
- double dBeatLength = dNextBeat - dPrevBeat;
+ mixxx::FrameDiff_t dBeatLength = pNextBeat - pPrevBeat;
if (dpBeatLength != NULL) {
*dpBeatLength = dBeatLength;
}
if (dpBeatPercentage != NULL) {
- *dpBeatPercentage = dBeatLength == 0.0 ? 0.0 :
- (dPosition - dPrevBeat) / dBeatLength;
+ *dpBeatPercentage = dBeatLength == 0.0
+ ? 0.0
+ : (position - pPrevBeat) / dBeatLength;
// Because findNext and findPrev have an epsilon built in, sometimes
// the beat percentage is out of range. Fix it.
- if (*dpBeatPercentage < 0) ++*dpBeatPercentage;
- if (*dpBeatPercentage > 1) --*dpBeatPercentage;
+ if (*dpBeatPercentage < 0)
+ ++*dpBeatPercentage;
+ if (*dpBeatPercentage > 1)
+ --*dpBeatPercentage;
}
return true;
}
-double BpmControl::getNearestPositionInPhase(
- double dThisPosition, bool respectLoops, bool playing) {
+mixxx::FramePos BpmControl::getNearestPositionInPhase(
+ mixxx::FramePos thisPosition, bool respectLoops, bool playing) {
// Without a beatgrid, we don't know the phase offset.
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (!pBeats) {
- return dThisPosition;
+ if (!m_pBeats) {
+ return thisPosition;
}
-
SyncMode syncMode = getSyncMode();
// Explicit master buffer is always in sync!
if (syncMode == SYNC_MASTER_EXPLICIT) {
- return dThisPosition;
+ return thisPosition;
}
// Get the current position of this deck.
- double dThisPrevBeat = m_pPrevBeat->get();
- double dThisNextBeat = m_pNextBeat->get();
- double dThisBeatLength;
- if (dThisPosition > dThisNextBeat || dThisPosition < dThisPrevBeat) {
+ mixxx::FramePos thisPrevBeat = samplePosToFramePos(m_pPrevBeat->get());
+ mixxx::FramePos thisNextBeat = samplePosToFramePos(m_pNextBeat->get());
+ mixxx::FrameDiff_t dThisBeatLengthFrames;
+ if (thisPosition > thisNextBeat || thisPosition < thisPrevBeat) {
if (kLogger.traceEnabled()) {
- kLogger.trace() << "BpmControl::getNearestPositionInPhase out of date"
- << dThisPosition << dThisNextBeat << dThisPrevBeat;
+ kLogger.trace()
+ << "BpmControl::getNearestPositionInPhase out of date"
+ << thisPosition << thisNextBeat << thisPrevBeat;
}
// This happens if dThisPosition is the target position of a requested
// seek command
- if (!getBeatContext(pBeats, dThisPosition,
- &dThisPrevBeat, &dThisNextBeat,
- &dThisBeatLength, NULL)) {
- return dThisPosition;
+ if (!getBeatContext(m_pBeats,
+ thisPosition,
+ &thisPrevBeat,
+ &thisNextBeat,
+ &dThisBeatLengthFrames,
+ NULL)) {
+ return thisPosition;
}
} else {
- if (!getBeatContextNoLookup(dThisPosition,
- dThisPrevBeat, dThisNextBeat,
- &dThisBeatLength, NULL)) {
- return dThisPosition;
+ if (!getBeatContextNoLookup(thisPosition,
+ thisPrevBeat,
+ thisNextBeat,
+ &dThisBeatLengthFrames,
+ NULL)) {
+ return thisPosition;
}
}
@@ -624,7 +661,7 @@ double BpmControl::getNearestPositionInPhase(
if (!pOtherEngineBuffer) {
// no suitable sync buffer found
- return dThisPosition;
+ return thisPosition;
}
TrackPointer otherTrack = pOtherEngineBuffer->getLoadedTrack();
@@ -633,23 +670,24 @@ double BpmControl::getNearestPositionInPhase(
// If either track does not have beats, then we can't adjust the phase.
if (!otherBeats) {
- return dThisPosition;
+ return thisPosition;
}
- double dOtherPosition = pOtherEngineBuffer->getExactPlayPos();
+ mixxx::FramePos otherPosition =
+ samplePosToFramePos(pOtherEngineBuffer->getExactPlayPos());
if (!BpmControl::getBeatContext(otherBeats,
- dOtherPosition,
+ otherPosition,
NULL,
NULL,
NULL,
&dOtherBeatFraction)) {
- return dThisPosition;
+ return thisPosition;
}
}
bool this_near_next =
- dThisNextBeat - dThisPosition <= dThisPosition - dThisPrevBeat;
+ thisNextBeat - thisPosition <= thisPosition - thisPrevBeat;
bool other_near_next = dOtherBeatFraction >= 0.5;
// We want our beat fraction to be identical to theirs.
@@ -669,22 +707,22 @@ double BpmControl::getNearestPositionInPhase(
// infinite beatgrids because the assumption that findNthBeat(-2) always
// works will be wrong then.
- double dNewPlaypos =
- (dOtherBeatFraction + m_dUserOffset.getValue()) * dThisBeatLength;
+ mixxx::FramePos newPlaypos = mixxx::FramePos(dThisBeatLengthFrames) *
+ (dOtherBeatFraction + m_dUserOffset.getValue());
if (this_near_next == other_near_next) {
- dNewPlaypos += dThisPrevBeat;
+ newPlaypos += thisPrevBeat.getValue();
} else if (this_near_next && !other_near_next) {
- dNewPlaypos += dThisNextBeat;
+ newPlaypos += thisNextBeat.getValue();
} else { //!this_near_next && other_near_next
- dThisPrevBeat = pBeats->findNthBeat(dThisPosition, -2);
- dNewPlaypos += dThisPrevBeat;
+ thisPrevBeat = m_pBeats->findNthBeat(thisPosition, -1);
+ newPlaypos += thisPrevBeat.getValue();
}
if (respectLoops) {
// We might be seeking outside the loop.
const bool loop_enabled = m_pLoopEnabled->toBool();
- const double loop_start_position = m_pLoopStartPosition->get();
- const double loop_end_position = m_pLoopEndPosition->get();
+ mixxx::FramePos loopStartPositionFrames = samplePosToFramePos(m_pLoopStartPosition->get());
+ mixxx::FramePos loopEndPositionFrames = samplePosToFramePos(m_pLoopEndPosition->get());
// Cases for sanity:
//
@@ -699,23 +737,22 @@ double BpmControl::getNearestPositionInPhase(
// If sync target is 50% through the beat,
// If we are at the loop end point and hit sync, jump forward X samples.
-
// TODO(rryan): Revise this with something that keeps a broader number of
// cases in sync. This at least prevents breaking out of the loop.
- if (loop_enabled &&
- dThisPosition <= loop_end_position) {
- const double loop_length = loop_end_position - loop_start_position;
- const double end_delta = dNewPlaypos - loop_end_position;
+ if (loop_enabled && thisPosition <= loopEndPositionFrames) {
+ mixxx::FrameDiff_t loopLength = loopEndPositionFrames - loopStartPositionFrames;
+ mixxx::FrameDiff_t endDelta = newPlaypos - loopEndPositionFrames;
// Syncing to after the loop end.
- if (end_delta > 0 && loop_length > 0.0) {
- int i = end_delta / loop_length;
- dNewPlaypos = loop_start_position + end_delta - i * loop_length;
+ if (endDelta > 0 && loopLength > 0.0) {
+ int i = endDelta / loopLength;
+ newPlaypos = loopStartPositionFrames + endDelta - i * loopLength;
// Move new position after loop jump into phase as well.
// This is a recursive call, called only twice because of
// respectLoops = false
- dNewPlaypos = getNearestPositionInPhase(dNewPlaypos, false, playing);
+ newPlaypos =
+ getNearestPositionInPhase(newPlaypos, false, playing);
}
// Note: Syncing to before the loop beginning is allowed, because
@@ -723,18 +760,18 @@ double BpmControl::getNearestPositionInPhase(
}
}
- return dNewPlaypos;
+ return newPlaypos;
}
-double BpmControl::getBeatMatchPosition(
- double dThisPosition, bool respectLoops, bool playing) {
+mixxx::FramePos BpmControl::getBeatMatchPosition(
+ mixxx::FramePos thisPosition, bool respectLoops, bool playing) {
// Without a beatgrid, we don't know the phase offset.
if (!m_pBeats) {
- return dThisPosition;
+ return thisPosition;
}
// Explicit master buffer is always in sync!
if (getSyncMode() == SYNC_MASTER_EXPLICIT) {
- return dThisPosition;
+ return thisPosition;
}
EngineBuffer* pOtherEngineBuffer = nullptr;
@@ -750,78 +787,75 @@ double BpmControl::getBeatMatchPosition(
pOtherEngineBuffer = getEngineBuffer();
}
} else if (!pOtherEngineBuffer) {
- return dThisPosition;
+ return thisPosition;
}
// Get the current position of this deck.
- double dThisPrevBeat = m_pPrevBeat->get();
- double dThisNextBeat = m_pNextBeat->get();
- double dThisBeatLength = -1;
+ mixxx::FramePos thisPrevBeat = samplePosToFramePos(m_pPrevBeat->get());
+ mixxx::FramePos thisNextBeat = samplePosToFramePos(m_pNextBeat->get());
+ mixxx::FrameDiff_t dThisBeatLengthFrames = -1;
// Look up the next beat and beat length for the new position
- if (dThisNextBeat == -1 ||
- dThisPosition > dThisNextBeat ||
- (dThisPrevBeat != -1 && dThisPosition < dThisPrevBeat)) {
+ if (thisNextBeat == mixxx::kInvalidFramePos || thisPosition > thisNextBeat ||
+ (thisPrevBeat != mixxx::kInvalidFramePos && thisPosition < thisPrevBeat)) {
if (kLogger.traceEnabled()) {
kLogger.trace() << "BpmControl::getBeatMatchPosition out of date"
- << dThisPrevBeat << dThisPosition << dThisNextBeat;
+ << thisPrevBeat << thisPosition << thisNextBeat;
}
// This happens if dThisPosition is the target position of a requested
// seek command. Get new prev and next beats for the calculation.
- getBeatContext(
- m_pBeats,
- dThisPosition,
- &dThisPrevBeat,
- &dThisNextBeat,
- &dThisBeatLength,
+ getBeatContext(m_pBeats,
+ thisPosition,
+ &thisPrevBeat,
+ &thisNextBeat,
+ &dThisBeatLengthFrames,
nullptr);
// now we either have a useful next beat or there is none
- if (dThisNextBeat == -1) {
+ if (thisNextBeat == mixxx::kInvalidFramePos) {
// We can't match the next beat, give up.
- return dThisPosition;
+ return thisPosition;
}
} else {
if (kLogger.traceEnabled()) {
kLogger.trace() << "BpmControl::getBeatMatchPosition up to date"
- << dThisPrevBeat << dThisPosition << dThisNextBeat;
+ << thisPrevBeat << thisPosition << thisNextBeat;
}
// We are between the previous and next beats so we can try a standard
// lookup of the beat length.
- getBeatContextNoLookup(
- dThisPosition,
- dThisPrevBeat,
- dThisNextBeat,
- &dThisBeatLength,
+ getBeatContextNoLookup(thisPosition,
+ thisPrevBeat,
+ thisNextBeat,
+ &dThisBeatLengthFrames,
nullptr);
}
TrackPointer otherTrack = pOtherEngineBuffer->getLoadedTrack();
- mixxx::BeatsPointer otherBeats = otherTrack ? otherTrack->getBeats() : mixxx::BeatsPointer();
+ mixxx::BeatsPointer otherBeats =
+ otherTrack ? otherTrack->getBeats() : mixxx::BeatsPointer();
// If either track does not have beats, then we can't adjust the phase.
if (!otherBeats) {
- return dThisPosition;
+ return thisPosition;
}
- double dOtherPosition = pOtherEngineBuffer->getExactPlayPos();
+ mixxx::FramePos otherPosition = samplePosToFramePos(pOtherEngineBuffer->getExactPlayPos());
- double dOtherPrevBeat = -1;
- double dOtherNextBeat = -1;
- double dOtherBeatLength = -1;
+ mixxx::FramePos otherPrevBeat = mixxx::kInvalidFramePos;
+ mixxx::FramePos otherNextBeat = mixxx::kInvalidFramePos;
+ mixxx::FrameDiff_t dOtherBeatLength = -1;
double dOtherBeatFraction = -1;
- if (!BpmControl::getBeatContext(
- otherBeats,
- dOtherPosition,
- &dOtherPrevBeat,
- &dOtherNextBeat,
+ if (!BpmControl::getBeatContext(otherBeats,
+ otherPosition,
+ &otherPrevBeat,
+ &otherNextBeat,
&dOtherBeatLength,
&dOtherBeatFraction)) {
- return dThisPosition;
+ return thisPosition;
}
if (dOtherBeatLength == -1 || dOtherBeatFraction == -1) {
// the other Track has no usable beat info, do not seek.
- return dThisPosition;
+ return thisPosition;
}
double dThisSampleRate = m_pBeats->getSampleRate();
@@ -830,8 +864,8 @@ double BpmControl::getBeatMatchPosition(
// Seek our next beat to the other next beat
// This is the only thing we can do if the track has different BPM,
// playing the next beat together.
- double thisDivSec = (dThisNextBeat - dThisPosition) /
- dThisSampleRate / dThisRateRatio;
+ double thisDivSec =
+ (thisNextBeat - thisPosition) / dThisSampleRate / dThisRateRatio;
if (dOtherBeatFraction < 1.0 / 8) {
// the user has probably pressed play too late, sync the previous beat
@@ -839,29 +873,29 @@ double BpmControl::getBeatMatchPosition(
}
dOtherBeatFraction += m_dUserOffset.getValue();
- double otherDivSec = (1 - dOtherBeatFraction) *
- dOtherBeatLength / otherBeats->getSampleRate() / pOtherEngineBuffer->getRateRatio();
+ double otherDivSec = (1 - dOtherBeatFraction) * dOtherBeatLength /
+ otherBeats->getSampleRate() / pOtherEngineBuffer->getRateRatio();
// This matches the next beat in of both tracks.
- double seekMatch = (thisDivSec - otherDivSec) *
- dThisSampleRate * dThisRateRatio;
+ double seekMatch =
+ (thisDivSec - otherDivSec) * dThisSampleRate * dThisRateRatio;
- if (dThisBeatLength > 0) {
- if (dThisBeatLength / 2 < seekMatch) {
+ if (dThisBeatLengthFrames > 0) {
+ if (dThisBeatLengthFrames / 2 < seekMatch) {
// seek to previous beat, because of shorter distance
- seekMatch -= dThisBeatLength;
- } else if (dThisBeatLength / 2 < -seekMatch) {
+ seekMatch -= dThisBeatLengthFrames;
+ } else if (dThisBeatLengthFrames / 2 < -seekMatch) {
// seek to beat after next, because of shorter distance
- seekMatch += dThisBeatLength;
+ seekMatch += dThisBeatLengthFrames;
}
}
- double dNewPlaypos = dThisPosition + seekMatch;
+ mixxx::FramePos newPlaypos = thisPosition + seekMatch;
if (respectLoops) {
// We might be seeking outside the loop.
const bool loop_enabled = m_pLoopEnabled->toBool();
- const double loop_start_position = m_pLoopStartPosition->get();
- const double loop_end_position = m_pLoopEndPosition->get();
+ mixxx::FramePos loopStartPositionFrames = samplePosToFramePos(m_pLoopStartPosition->get());
+ mixxx::FramePos loopEndPositionFrames = samplePosToFramePos(m_pLoopEndPosition->get());
// Cases for sanity:
//
@@ -878,33 +912,34 @@ double BpmControl::getBeatMatchPosition(
// TODO(rryan): Revise this with something that keeps a broader number of
// cases in sync. This at least prevents breaking out of the loop.
- if (loop_enabled &&
- dThisPosition <= loop_end_position) {
- const double loop_length = loop_end_position - loop_start_position;
- const double end_delta = dNewPlaypos - loop_end_position;
+ if (loop_enabled && thisPosition <= loopEndPositionFrames) {
+ const mixxx::FrameDiff_t loopLength = loopEndPositionFrames - loopStartPositionFrames;
+ const mixxx::FrameDiff_t endDelta = newPlaypos - loopEndPositionFrames;
// Syncing to after the loop end.
- if (end_delta > 0 && loop_length > 0.0) {
- int i = end_delta / loop_length;
- dNewPlaypos = loop_start_position + end_delta - i * loop_length;
+ if (endDelta > 0 && loopLength > 0.0) {
+ int i = endDelta / loopLength;
+ newPlaypos = loopStartPositionFrames + endDelta - i * loopLength;
// Move new position after loop jump into phase as well.
// This is a recursive call, called only twice because of
// respectLoops = false
- dNewPlaypos = getNearestPositionInPhase(dNewPlaypos, false, playing);
+ newPlaypos =
+ getNearestPositionInPhase(newPlaypos, false, playing);
}
// Note: Syncing to before the loop beginning is allowed, because
// loops are catching
}
}
- return dNewPlaypos;
+ return newPlaypos;
}
-double BpmControl::getPhaseOffset(double dThisPosition) {
+mixxx::FrameDiff_t BpmControl::getPhaseOffset(mixxx::FramePos thisPosition) {
// This does not respect looping
- double dNewPlaypos = getNearestPositionInPhase(dThisPosition, false, false);
- return dNewPlaypos - dThisPosition;
+ mixxx::FramePos newPlayposFrames = getNearestPositionInPhase(
+ thisPosition, false, false);
+ return newPlayposFrames - thisPosition;
}
void BpmControl::slotUpdateEngineBpm(double value) {
@@ -944,7 +979,7 @@ void BpmControl::trackLoaded(TrackPointer pNewTrack) {
void BpmControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) {
if (kLogger.traceEnabled()) {
kLogger.trace() << getGroup() << "BpmControl::trackBeatsUpdated"
- << (pBeats ? pBeats->getBpm() : 0.0);
+ << (pBeats ? pBeats->getBpm() : mixxx::Bpm(0.0));
}
m_pBeats = pBeats;
updateLocalBpm();
@@ -952,49 +987,43 @@ void BpmControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) {
}
void BpmControl::slotBeatsTranslate(double v) {
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (v > 0 && pBeats && (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_TRANSLATE)) {
- double currentSample = getSampleOfTrack().current;
- double closestBeat = pBeats->findClosestBeat(currentSample);
- int delta = currentSample - closestBeat;
- if (delta % 2 != 0) {
- delta--;
- }
- pBeats->translate(delta);
+ if (v > 0 && m_pBeats) {
+ mixxx::FramePos currentFrame = getFrameOfTrack().currentFrame;
+ mixxx::FramePos closestBeat = m_pBeats->findClosestBeat(currentFrame);
+ mixxx::FrameDiff_t delta = currentFrame - closestBeat;
+ m_pBeats->translate(delta);
}
}
void BpmControl::slotBeatsTranslateMatchAlignment(double v) {
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (v > 0 && pBeats && (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_TRANSLATE)) {
+ if (v > 0 && m_pBeats) {
// Must reset the user offset *before* calling getPhaseOffset(),
// otherwise it will always return 0 if master sync is active.
m_dUserOffset.setValue(0.0);
- double offset = getPhaseOffset(getSampleOfTrack().current);
- pBeats->translate(-offset);
+ mixxx::FrameDiff_t offsetFrames = getPhaseOffset(getFrameOfTrack().currentFrame);
+ m_pBeats->translate(-offsetFrames);
}
}
double BpmControl::updateLocalBpm() {
- double prev_local_bpm = m_pLocalBpm->get();
- double local_bpm = 0;
- mixxx::BeatsPointer pBeats = m_pBeats;
- if (pBeats) {
- local_bpm = pBeats->getBpmAroundPosition(
- getSampleOfTrack().current, kLocalBpmSpan);
- if (local_bpm == -1) {
- local_bpm = pBeats->getBpm();
+ mixxx::Bpm prev_local_bpm(m_pLocalBpm->get());
+ mixxx::Bpm local_bpm;
+ if (m_pBeats) {
+ local_bpm = m_pBeats->getBpmAroundPosition(
+ getFrameOfTrack().currentFrame, kLocalBpmSpan);
+ if (local_bpm.getValue() == -1) {
+ local_bpm = m_pBeats->getBpm();
}
}
if (local_bpm != prev_local_bpm) {
if (kLogger.traceEnabled()) {
kLogger.trace() << getGroup() << "BpmControl::updateLocalBpm" << local_bpm;
}
- m_pLocalBpm->set(local_bpm);
+ m_pLocalBpm->set(local_bpm.getValue());
slotUpdateEngineBpm();
}
- return local_bpm;
+ return local_bpm.getValue();
}
double BpmControl::updateBeatDistance() {
@@ -1030,26 +1059,27 @@ void BpmControl::resetSyncAdjustment() {
void BpmControl::collectFeatures(GroupFeatureState* pGroupFeatures) const {
// Without a beatgrid we don't know any beat details.
- SampleOfTrack sot = getSampleOfTrack();
- if (!sot.rate || !m_pBeats) {
+ FrameOfTrack frameOfTrack = getFrameOfTrack();
+ if (!frameOfTrack.sampleRate || !m_pBeats) {
return;
}
// Get the current position of this deck.
- double dThisPrevBeat = m_pPrevBeat->get();
- double dThisNextBeat = m_pNextBeat->get();
- double dThisBeatLength;
+ mixxx::FramePos thisPrevBeat = samplePosToFramePos(m_pPrevBeat->get());
+ mixxx::FramePos thisNextBeat = samplePosToFramePos(m_pNextBeat->get());
+ mixxx::FrameDiff_t dThisBeatLengthFrames;
double dThisBeatFraction;
- if (getBeatContextNoLookup(sot.current,
- dThisPrevBeat, dThisNextBeat,
- &dThisBeatLength, &dThisBeatFraction)) {
-
+ if (getBeatContextNoLookup(frameOfTrack.currentFrame,
+ thisPrevBeat,
+ thisNextBeat,
+ &dThisBeatLengthFrames,
+ &dThisBeatFraction)) {
// Note: dThisBeatLength is fractional frames count * 2 (stereo samples)
- double sotPerSec = kSamplesPerFrame * sot.rate * m_pRateRatio->get();
- if (sotPerSec != 0.0) {
- pGroupFeatures->beat_length_sec = dThisBeatLength / sotPerSec;
+ mixxx::FrameDiff_t framesOfTrackPerSec = frameOfTrack.sampleRate * m_pRateRatio->get();
+ if (framesOfTrackPerSec != 0.0) {
+ pGroupFeatures->beat_length_sec = dThisBeatLengthFrames / framesOfTrackPerSec;
pGroupFeatures->has_beat_length_sec = true;
- } else {
+ } else {
pGroupFeatures->has_beat_length_sec = false;
}
diff --git a/src/engine/controls/bpmcontrol.h b/src/engine/controls/bpmcontrol.h
index d191f77ef73..173afaac5c2 100644
--- a/src/engine/controls/bpmcontrol.h
+++ b/src/engine/controls/bpmcontrol.h
@@ -3,15 +3,16 @@
#include
+#include "control/controllinpotmeter.h"
#include "control/controlobject.h"
+#include "control/controlpushbutton.h"
#include "engine/controls/enginecontrol.h"
#include "engine/sync/syncable.h"
+#include "util/parented_ptr.h"
#include "util/tapfilter.h"
class ControlObject;
-class ControlLinPotmeter;
class ControlProxy;
-class ControlPushButton;
class EngineBuffer;
class SyncControl;
@@ -23,7 +24,7 @@ class BpmControl : public EngineControl {
public:
BpmControl(QString group, UserSettingsPointer pConfig);
- ~BpmControl() override;
+ ~BpmControl() override = default;
double getBpm() const;
double getLocalBpm() const { return m_pLocalBpm ? m_pLocalBpm->get() : 0.0; }
@@ -36,9 +37,11 @@ class BpmControl : public EngineControl {
// out of sync.
double calcSyncedRate(double userTweak);
// Get the phase offset from the specified position.
- double getNearestPositionInPhase(double dThisPosition, bool respectLoops, bool playing);
- double getBeatMatchPosition(double dThisPosition, bool respectLoops, bool playing);
- double getPhaseOffset(double dThisPosition);
+ mixxx::FramePos getNearestPositionInPhase(
+ mixxx::FramePos thisPosition, bool respectLoops, bool playing);
+ mixxx::FramePos getBeatMatchPosition(
+ mixxx::FramePos thisPosition, bool respectLoops, bool playing);
+ mixxx::FrameDiff_t getPhaseOffset(mixxx::FramePos thisPosition);
/// getBeatDistance is adjusted to include the user offset so it's
/// transparent to other decks.
double getBeatDistance(double dThisPosition) const;
@@ -58,20 +61,20 @@ class BpmControl : public EngineControl {
// lies within the current beat). Returns false if a previous or next beat
// does not exist. NULL arguments are safe and ignored.
static bool getBeatContext(const mixxx::BeatsPointer& pBeats,
- const double dPosition,
- double* dpPrevBeat,
- double* dpNextBeat,
- double* dpBeatLength,
+ const mixxx::FramePos position,
+ mixxx::FramePos* pPrevBeat,
+ mixxx::FramePos* pNextBeat,
+ mixxx::FrameDiff_t* dpBeatLength,
double* dpBeatPercentage);
// Alternative version that works if the next and previous beat positions
// are already known.
static bool getBeatContextNoLookup(
- const double dPosition,
- const double dPrevBeat,
- const double dNextBeat,
- double* dpBeatLength,
- double* dpBeatPercentage);
+ const mixxx::FramePos position,
+ mixxx::FramePos pPrevBeat,
+ mixxx::FramePos pNextBeat,
+ mixxx::FrameDiff_t* dpBeatLength,
+ double* dpBeatPercentage);
// Returns the shortest change in percentage needed to achieve
// target_percentage.
@@ -111,50 +114,53 @@ class BpmControl : public EngineControl {
friend class SyncControl;
// ControlObjects that come from EngineBuffer
- ControlProxy* m_pPlayButton;
+ parented_ptr m_pPlayButton;
QAtomicInt m_oldPlayButton;
- ControlProxy* m_pReverseButton;
- ControlProxy* m_pRateRatio;
+ parented_ptr m_pReverseButton;
+ parented_ptr m_pRateRatio;
ControlObject* m_pQuantize;
// ControlObjects that come from QuantizeControl
- QScopedPointer m_pNextBeat;
- QScopedPointer m_pPrevBeat;
+ parented_ptr m_pNextBeat;
+ parented_ptr m_pPrevBeat;
+ parented_ptr m_pClosestBeat;
// ControlObjects that come from LoopingControl
- ControlProxy* m_pLoopEnabled;
- ControlProxy* m_pLoopStartPosition;
- ControlProxy* m_pLoopEndPosition;
+ parented_ptr m_pLoopEnabled;
+ parented_ptr m_pLoopStartPosition;
+ parented_ptr m_pLoopEndPosition;
// The average bpm around the current playposition;
- ControlObject* m_pLocalBpm;
- ControlPushButton* m_pAdjustBeatsFaster;
- ControlPushButton* m_pAdjustBeatsSlower;
- ControlPushButton* m_pTranslateBeatsEarlier;
- ControlPushButton* m_pTranslateBeatsLater;
+ std::unique_ptr m_pLocalBpm;
+ std::unique_ptr m_pAdjustBeatsFaster;
+ std::unique_ptr m_pAdjustBeatsSlower;
+ std::unique_ptr m_pTranslateBeatsEarlier;
+ std::unique_ptr m_pTranslateBeatsLater;
// The current effective BPM of the engine
- ControlLinPotmeter* m_pEngineBpm;
+ std::unique_ptr m_pEngineBpm;
// Used for bpm tapping from GUI and MIDI
- ControlPushButton* m_pButtonTap;
+ std::unique_ptr m_pButtonTap;
// Button for sync'ing with the other EngineBuffer
- ControlPushButton* m_pButtonSync;
- ControlPushButton* m_pButtonSyncPhase;
- ControlPushButton* m_pButtonSyncTempo;
+ std::unique_ptr m_pButtonSync;
+ std::unique_ptr m_pButtonSyncPhase;
+ std::unique_ptr m_pButtonSyncTempo;
// Button that translates the beats so the nearest beat is on the current
// playposition.
- ControlPushButton* m_pTranslateBeats;
+ std::unique_ptr m_pTranslateBeats;
// Button that translates beats to match another playing deck
- ControlPushButton* m_pBeatsTranslateMatchAlignment;
+ std::unique_ptr m_pBeatsTranslateMatchAlignment;
+ // Button to set the nearest beat as a downbeat
+ std::unique_ptr m_pBeatsSetDownbeat;
- ControlProxy* m_pThisBeatDistance;
+ parented_ptr m_pThisBeatDistance;
ControlValueAtomic m_dSyncTargetBeatDistance;
ControlValueAtomic m_dUserOffset;
QAtomicInt m_resetSyncAdjustment;
- ControlProxy* m_pSyncMode;
+ parented_ptr m_pSyncMode;
TapFilter m_tapFilter; // threadsafe
diff --git a/src/engine/controls/clockcontrol.cpp b/src/engine/controls/clockcontrol.cpp
index b560de06d9b..509f684bd38 100644
--- a/src/engine/controls/clockcontrol.cpp
+++ b/src/engine/controls/clockcontrol.cpp
@@ -47,8 +47,8 @@ void ClockControl::process(const double dRate,
mixxx::BeatsPointer pBeats = m_pBeats;
if (pBeats) {
- double closestBeat = pBeats->findClosestBeat(currentSample);
- double distanceToClosestBeat = fabs(currentSample - closestBeat);
+ mixxx::FramePos closestBeat = pBeats->findClosestBeat(mixxx::FramePos(currentSample / 2.0));
+ double distanceToClosestBeat = fabs(currentSample - closestBeat.getValue() * 2.0);
m_pCOBeatActive->set(distanceToClosestBeat < blinkIntervalSamples / 2.0);
}
}
diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp
index 314bcf7a7d8..4c285f11a72 100644
--- a/src/engine/controls/cuecontrol.cpp
+++ b/src/engine/controls/cuecontrol.cpp
@@ -1704,11 +1704,11 @@ double CueControl::quantizeCuePoint(double cuePos) {
return cuePos;
}
- double closestBeat = pBeats->findClosestBeat(cuePos);
+ mixxx::FramePos closestBeat = pBeats->findClosestBeat(mixxx::FramePos(cuePos / 2.0));
// The closest beat can be an unreachable interpolated beat past the end of
// the track.
- if (closestBeat != -1.0 && closestBeat <= total) {
- return closestBeat;
+ if (closestBeat != mixxx::FramePos(-1.0) && closestBeat <= mixxx::FramePos(total / 2.0)) {
+ return closestBeat.getValue() * 2;
}
return cuePos;
diff --git a/src/engine/controls/enginecontrol.cpp b/src/engine/controls/enginecontrol.cpp
index a5d0a0555f2..f841b0d61af 100644
--- a/src/engine/controls/enginecontrol.cpp
+++ b/src/engine/controls/enginecontrol.cpp
@@ -2,10 +2,12 @@
// Created 7/5/2009 by RJ Ryan (rryan@mit.edu)
#include "engine/controls/enginecontrol.h"
-#include "engine/enginemaster.h"
+
#include "engine/enginebuffer.h"
+#include "engine/enginemaster.h"
#include "engine/sync/enginesync.h"
#include "mixer/playermanager.h"
+#include "util/frameadapter.h"
EngineControl::EngineControl(QString group,
UserSettingsPointer pConfig)
@@ -115,3 +117,11 @@ EngineBuffer* EngineControl::pickSyncTarget() {
}
return nullptr;
}
+
+EngineControl::FrameOfTrack EngineControl::getFrameOfTrack() const {
+ FrameOfTrack frameOfTrack{
+ samplePosToFramePos(getSampleOfTrack().current),
+ samplePosToFramePos(getSampleOfTrack().total),
+ getSampleOfTrack().rate};
+ return frameOfTrack;
+}
diff --git a/src/engine/controls/enginecontrol.h b/src/engine/controls/enginecontrol.h
index 1fe3371e5e5..6fbea61f794 100644
--- a/src/engine/controls/enginecontrol.h
+++ b/src/engine/controls/enginecontrol.h
@@ -76,10 +76,16 @@ class EngineControl : public QObject {
double total;
double rate;
};
+ struct FrameOfTrack {
+ mixxx::FramePos currentFrame;
+ mixxx::FramePos totalFrames;
+ double sampleRate;
+ };
SampleOfTrack getSampleOfTrack() const {
return m_sampleOfTrack.getValue();
}
+ FrameOfTrack getFrameOfTrack() const;
void seek(double fractionalPosition);
void seekAbs(double sample);
// Seek to an exact sample and don't allow quantizing adjustment.
diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp
index fbce5c8a8d5..fc0bb96123d 100644
--- a/src/engine/controls/loopingcontrol.cpp
+++ b/src/engine/controls/loopingcontrol.cpp
@@ -2,22 +2,23 @@
// Created on Sep 23, 2008
// Author: asantoni, rryan
+#include "engine/controls/loopingcontrol.h"
+
#include
#include "control/controlobject.h"
#include "control/controlpushbutton.h"
#include "engine/controls/bpmcontrol.h"
#include "engine/controls/enginecontrol.h"
-#include "engine/controls/loopingcontrol.h"
#include "engine/enginebuffer.h"
#include "preferences/usersettings.h"
+#include "track/beats.h"
+#include "track/track.h"
#include "util/compatibility.h"
+#include "util/frameadapter.h"
#include "util/math.h"
#include "util/sample.h"
-#include "track/track.h"
-#include "track/beats.h"
-
double LoopingControl::s_dBeatSizes[] = { 0.03125, 0.0625, 0.125, 0.25, 0.5,
1, 2, 4, 8, 16, 32, 64, 128, 256, 512 };
@@ -517,12 +518,12 @@ void LoopingControl::setLoopInToCurrentPosition() {
mixxx::BeatsPointer pBeats = m_pBeats;
LoopSamples loopSamples = m_loopSamples.getValue();
double quantizedBeat = -1;
- double pos = m_currentSample.getValue();
+ double dPosSample = m_currentSample.getValue();
if (m_pQuantizeEnabled->toBool() && pBeats) {
if (m_bAdjustingLoopIn) {
- double closestBeat = m_pClosestBeat->get();
- if (closestBeat == m_currentSample.getValue()) {
- quantizedBeat = closestBeat;
+ mixxx::FramePos closestBeat(m_pClosestBeat->get() / 2.0);
+ if (closestBeat.getValue() * 2.0 == m_currentSample.getValue()) {
+ quantizedBeat = closestBeat.getValue() * 2.0;
} else {
quantizedBeat = m_pPreviousBeat->get();
}
@@ -530,14 +531,14 @@ void LoopingControl::setLoopInToCurrentPosition() {
quantizedBeat = m_pClosestBeat->get();
}
if (quantizedBeat != -1) {
- pos = quantizedBeat;
+ dPosSample = quantizedBeat;
}
}
// Reset the loop out position if it is before the loop in so that loops
// cannot be inverted.
if (loopSamples.end != kNoTrigger &&
- loopSamples.end < pos) {
+ loopSamples.end < dPosSample) {
loopSamples.end = kNoTrigger;
m_pCOLoopEndPosition->set(kNoTrigger);
}
@@ -546,18 +547,18 @@ void LoopingControl::setLoopInToCurrentPosition() {
// that the loop would be inaudible, set the in point to the smallest
// pre-defined beatloop size instead (when possible)
if (loopSamples.end != kNoTrigger &&
- (loopSamples.end - pos) < MINIMUM_AUDIBLE_LOOP_SIZE) {
+ (loopSamples.end - dPosSample) < MINIMUM_AUDIBLE_LOOP_SIZE) {
if (quantizedBeat != -1 && pBeats) {
- pos = pBeats->findNthBeat(quantizedBeat, -2);
- if (pos == -1 || (loopSamples.end - pos) < MINIMUM_AUDIBLE_LOOP_SIZE) {
- pos = loopSamples.end - MINIMUM_AUDIBLE_LOOP_SIZE;
+ dPosSample = pBeats->findNthBeat(mixxx::FramePos(quantizedBeat / 2.0), -2).getValue();
+ if (dPosSample == -1 || (loopSamples.end - dPosSample) < MINIMUM_AUDIBLE_LOOP_SIZE) {
+ dPosSample = loopSamples.end - MINIMUM_AUDIBLE_LOOP_SIZE;
}
} else {
- pos = loopSamples.end - MINIMUM_AUDIBLE_LOOP_SIZE;
+ dPosSample = loopSamples.end - MINIMUM_AUDIBLE_LOOP_SIZE;
}
}
- loopSamples.start = pos;
+ loopSamples.start = dPosSample;
m_pCOLoopStartPosition->set(loopSamples.start);
@@ -574,7 +575,8 @@ void LoopingControl::setLoopInToCurrentPosition() {
&& loopSamples.start < loopSamples.end
&& pBeats) {
m_pCOBeatLoopSize->setAndConfirm(
- pBeats->numBeatsInRange(loopSamples.start, loopSamples.end));
+ pBeats->numBeatsInRange(mixxx::FramePos(loopSamples.start / 2.0),
+ mixxx::FramePos(loopSamples.end / 2.0)));
updateBeatLoopingControls();
} else {
clearActiveBeatLoop();
@@ -610,8 +612,7 @@ void LoopingControl::slotLoopIn(double pressed) {
void LoopingControl::slotLoopInGoto(double pressed) {
if (pressed > 0.0) {
- seekAbs(static_cast(
- m_loopSamples.getValue().start));
+ seekAbs(static_cast(m_loopSamples.getValue().start));
}
}
@@ -647,8 +648,12 @@ void LoopingControl::setLoopOutToCurrentPosition() {
// use the smallest pre-defined beatloop instead (when possible)
if ((pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) {
if (quantizedBeat != -1 && pBeats) {
- pos = static_cast(floor(pBeats->findNthBeat(quantizedBeat, 2)));
- if (pos == -1 || (pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) {
+ pos = static_cast(floor(
+ pBeats->findNthBeat(mixxx::FramePos(quantizedBeat / 2.0), 1)
+ .getValue() *
+ 2.0));
+ if (pos == -1 ||
+ (pos - loopSamples.start) < MINIMUM_AUDIBLE_LOOP_SIZE) {
pos = loopSamples.start + MINIMUM_AUDIBLE_LOOP_SIZE;
}
} else {
@@ -662,8 +667,7 @@ void LoopingControl::setLoopOutToCurrentPosition() {
m_pCOLoopEndPosition->set(loopSamples.end);
// start looping
- if (loopSamples.start != kNoTrigger &&
- loopSamples.end != kNoTrigger) {
+ if (loopSamples.start != kNoTrigger && loopSamples.end != kNoTrigger) {
setLoopingEnabled(true);
loopSamples.seek = true;
} else {
@@ -672,7 +676,8 @@ void LoopingControl::setLoopOutToCurrentPosition() {
if (m_pQuantizeEnabled->toBool() && pBeats) {
m_pCOBeatLoopSize->setAndConfirm(
- pBeats->numBeatsInRange(loopSamples.start, loopSamples.end));
+ pBeats->numBeatsInRange(mixxx::FramePos(loopSamples.start / 2.0),
+ mixxx::FramePos(loopSamples.end / 2.0)));
updateBeatLoopingControls();
} else {
clearActiveBeatLoop();
@@ -720,8 +725,7 @@ void LoopingControl::slotLoopOut(double pressed) {
void LoopingControl::slotLoopOutGoto(double pressed) {
if (pressed > 0.0) {
- seekAbs(static_cast(
- m_loopSamples.getValue().end));
+ seekAbs(static_cast(m_loopSamples.getValue().end));
}
}
@@ -769,8 +773,7 @@ void LoopingControl::slotReloopToggle(double val) {
void LoopingControl::slotReloopAndStop(double pressed) {
if (pressed > 0) {
m_pPlayButton->set(0.0);
- seekAbs(static_cast(
- m_loopSamples.getValue().start));
+ seekAbs(static_cast(m_loopSamples.getValue().start));
setLoopingEnabled(true);
}
}
@@ -794,8 +797,7 @@ void LoopingControl::slotLoopStartPos(double pos) {
loopSamples.start = pos;
m_pCOLoopStartPosition->set(pos);
- if (loopSamples.end != kNoTrigger &&
- loopSamples.end <= loopSamples.start) {
+ if (loopSamples.end != kNoTrigger && loopSamples.end <= loopSamples.start) {
loopSamples.end = kNoTrigger;
m_pCOLoopEndPosition->set(kNoTrigger);
setLoopingEnabled(false);
@@ -886,8 +888,8 @@ void LoopingControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) {
if (m_pBeats) {
LoopSamples loopSamples = m_loopSamples.getValue();
if (loopSamples.start != kNoTrigger && loopSamples.end != kNoTrigger) {
- double loaded_loop_size = findBeatloopSizeForLoop(
- loopSamples.start, loopSamples.end);
+ double loaded_loop_size =
+ findBeatloopSizeForLoop(loopSamples.start, loopSamples.end);
if (loaded_loop_size != -1) {
m_pCOBeatLoopSize->setAndConfirm(loaded_loop_size);
}
@@ -895,7 +897,8 @@ void LoopingControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) {
}
}
-void LoopingControl::slotBeatLoopActivate(BeatLoopingControl* pBeatLoopControl) {
+void LoopingControl::slotBeatLoopActivate(
+ BeatLoopingControl* pBeatLoopControl) {
if (!m_pTrack) {
return;
}
@@ -907,10 +910,11 @@ void LoopingControl::slotBeatLoopActivate(BeatLoopingControl* pBeatLoopControl)
slotBeatLoop(pBeatLoopControl->getSize(), m_bLoopingEnabled, true);
}
-void LoopingControl::slotBeatLoopActivateRoll(BeatLoopingControl* pBeatLoopControl) {
- if (!m_pTrack) {
- return;
- }
+void LoopingControl::slotBeatLoopActivateRoll(
+ BeatLoopingControl* pBeatLoopControl) {
+ if (!m_pTrack) {
+ return;
+ }
// Disregard existing loops (except beatlooprolls).
m_pSlipEnabled->set(1);
@@ -919,12 +923,14 @@ void LoopingControl::slotBeatLoopActivateRoll(BeatLoopingControl* pBeatLoopContr
m_activeLoopRolls.push(pBeatLoopControl->getSize());
}
-void LoopingControl::slotBeatLoopDeactivate(BeatLoopingControl* pBeatLoopControl) {
+void LoopingControl::slotBeatLoopDeactivate(
+ BeatLoopingControl* pBeatLoopControl) {
Q_UNUSED(pBeatLoopControl);
setLoopingEnabled(false);
}
-void LoopingControl::slotBeatLoopDeactivateRoll(BeatLoopingControl* pBeatLoopControl) {
+void LoopingControl::slotBeatLoopDeactivateRoll(
+ BeatLoopingControl* pBeatLoopControl) {
pBeatLoopControl->deactivate();
const double size = pBeatLoopControl->getSize();
auto i = m_activeLoopRolls.begin();
@@ -951,7 +957,8 @@ void LoopingControl::slotBeatLoopDeactivateRoll(BeatLoopingControl* pBeatLoopCon
}
void LoopingControl::clearActiveBeatLoop() {
- BeatLoopingControl* pOldBeatLoop = m_pActiveBeatLoop.fetchAndStoreAcquire(nullptr);
+ BeatLoopingControl* pOldBeatLoop =
+ m_pActiveBeatLoop.fetchAndStoreAcquire(nullptr);
if (pOldBeatLoop != nullptr) {
pOldBeatLoop->deactivate();
}
@@ -966,11 +973,13 @@ bool LoopingControl::currentLoopMatchesBeatloopSize() {
LoopSamples loopSamples = m_loopSamples.getValue();
// Calculate where the loop out point would be if it is a beatloop
- double beatLoopOutPoint =
- pBeats->findNBeatsFromSample(loopSamples.start, m_pCOBeatLoopSize->get());
+ mixxx::FramePos beatLoopOutPoint = pBeats->findNBeatsFromFrame(
+ mixxx::FramePos(loopSamples.start / 2.0), m_pCOBeatLoopSize->get());
- return loopSamples.end > beatLoopOutPoint - 2 &&
- loopSamples.end < beatLoopOutPoint + 2;
+ return mixxx::FramePos(loopSamples.end / 2.0) >
+ beatLoopOutPoint - 1 &&
+ mixxx::FramePos(loopSamples.end / 2.0) <
+ beatLoopOutPoint + 1;
}
double LoopingControl::findBeatloopSizeForLoop(double start, double end) const {
@@ -979,9 +988,14 @@ double LoopingControl::findBeatloopSizeForLoop(double start, double end) const {
return -1;
}
- for (unsigned int i = 0; i < (sizeof(s_dBeatSizes) / sizeof(s_dBeatSizes[0])); ++i) {
+ for (unsigned int i = 0;
+ i < (sizeof(s_dBeatSizes) / sizeof(s_dBeatSizes[0]));
+ ++i) {
double beatLoopOutPoint =
- pBeats->findNBeatsFromSample(start, s_dBeatSizes[i]);
+ pBeats->findNBeatsFromFrame(
+ mixxx::FramePos(start / 2.0), s_dBeatSizes[i])
+ .getValue() *
+ 2.0;
if (end > beatLoopOutPoint - 2 && end < beatLoopOutPoint + 2) {
return s_dBeatSizes[i];
}
@@ -993,7 +1007,7 @@ void LoopingControl::updateBeatLoopingControls() {
// O(n) search, but there are only ~10-ish beatloop controls so this is
// fine.
double dBeatloopSize = m_pCOBeatLoopSize->get();
- for (BeatLoopingControl* pBeatLoopControl: qAsConst(m_beatLoops)) {
+ for (BeatLoopingControl* pBeatLoopControl : qAsConst(m_beatLoops)) {
if (pBeatLoopControl->getSize() == dBeatloopSize) {
if (m_bLoopingEnabled) {
pBeatLoopControl->activate();
@@ -1011,7 +1025,8 @@ void LoopingControl::updateBeatLoopingControls() {
clearActiveBeatLoop();
}
-void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable) {
+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
double p_seekPosition = 0;
if (getEngineBuffer()->getQueuedSeekPosition(&p_seekPosition)) {
@@ -1019,7 +1034,8 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
m_currentSample.setValue(p_seekPosition);
}
- double maxBeatSize = s_dBeatSizes[sizeof(s_dBeatSizes)/sizeof(s_dBeatSizes[0]) - 1];
+ double maxBeatSize =
+ s_dBeatSizes[sizeof(s_dBeatSizes) / sizeof(s_dBeatSizes[0]) - 1];
double minBeatSize = s_dBeatSizes[0];
if (beats < 0) {
// For now we do not handle negative beatloops.
@@ -1043,7 +1059,7 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
// give start and end defaults so we can detect problems
LoopSamples newloopSamples = {kNoTrigger, kNoTrigger, false};
LoopSamples loopSamples = m_loopSamples.getValue();
- double currentSample = m_currentSample.getValue();
+ mixxx::FramePos currentFramePos = getFrameOfTrack().currentFrame;
// Start from the current position/closest beat and
// create the loop around X beats from there.
@@ -1051,22 +1067,24 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
if (loopSamples.start != kNoTrigger) {
newloopSamples.start = loopSamples.start;
} else {
- newloopSamples.start = currentSample;
+ newloopSamples.start = framePosToSamplePos(currentFramePos);
}
} else {
// loop_in is set to the closest beat if quantize is on and the loop size is >= 1 beat.
// The closest beat might be ahead of play position and will cause a catching loop.
- double prevBeat;
- double nextBeat;
- pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat);
+ mixxx::FramePos prevBeat;
+ mixxx::FramePos nextBeat;
+ pBeats->findPrevNextBeats(
+ currentFramePos, &prevBeat, &nextBeat);
- if (m_pQuantizeEnabled->toBool() && prevBeat != -1) {
- double beatLength = nextBeat - prevBeat;
- double loopLength = beatLength * beats;
+ if (m_pQuantizeEnabled->toBool() && prevBeat.getValue() != -1) {
+ mixxx::FrameDiff_t beatLengthFrames = nextBeat - prevBeat;
+ mixxx::FrameDiff_t loopLengthFrames = beatLengthFrames * beats;
- double closestBeat = pBeats->findClosestBeat(currentSample);
+ mixxx::FramePos closestBeatFramePos =
+ pBeats->findClosestBeat(currentFramePos);
if (beats >= 1.0) {
- newloopSamples.start = closestBeat;
+ newloopSamples.start = framePosToSamplePos(closestBeatFramePos);
} else {
// In case of beat length less then 1 beat:
// (| - beats, ^ - current track's position):
@@ -1075,17 +1093,21 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
//
// If we press 1/2 beatloop we want loop from 50% to 100%,
// If I press 1/4 beatloop, we want loop from 50% to 75% etc
- double samplesSinceLastBeat = currentSample - prevBeat;
+ mixxx::FrameDiff_t framesSinceLastBeat =
+ currentFramePos - prevBeat;
// find the previous beat fraction and check if the current position is closer to this or the next one
// place the new loop start to the closer one
- double previousFractionBeat = prevBeat + floor(samplesSinceLastBeat / loopLength) * loopLength;
- double samplesSinceLastFractionBeat = currentSample - previousFractionBeat;
+ mixxx::FramePos previousFractionBeatFramePos = prevBeat +
+ floor(framesSinceLastBeat / loopLengthFrames) * loopLengthFrames;
+ mixxx::FrameDiff_t framesSinceLastFractionBeat =
+ currentFramePos - previousFractionBeatFramePos;
- if (samplesSinceLastFractionBeat <= (loopLength / 2.0)) {
- newloopSamples.start = previousFractionBeat;
+ if (framesSinceLastFractionBeat <= (loopLengthFrames / 2.0)) {
+ newloopSamples.start = framePosToSamplePos(previousFractionBeatFramePos);
} else {
- newloopSamples.start = previousFractionBeat + loopLength;
+ newloopSamples.start = framePosToSamplePos(
+ previousFractionBeatFramePos + loopLengthFrames);
}
}
@@ -1096,25 +1118,33 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
reverse = m_pRateControl->isReverseButtonPressed();
}
if (reverse) {
- newloopSamples.start -= loopLength;
+ newloopSamples.start -= framesToSamples(loopLengthFrames);
}
} else {
- newloopSamples.start = currentSample;
+ newloopSamples.start = framePosToSamplePos(currentFramePos);
}
}
- newloopSamples.end = pBeats->findNBeatsFromSample(newloopSamples.start, beats);
- if (newloopSamples.start >= newloopSamples.end // happens when the call above fails
- || newloopSamples.end > samples) { // Do not allow beat loops to go beyond the end of the track
+ newloopSamples.end =
+ framePosToSamplePos(pBeats->findNBeatsFromFrame(
+ samplePosToFramePos(newloopSamples.start), beats));
+
+ if (newloopSamples.start >=
+ newloopSamples.end // happens when the call above fails
+ || newloopSamples.end >
+ samples) { // Do not allow beat loops to go beyond the end of the track
// If a track is loaded with beatloop_size larger than
// the distance between the loop in point and
// the end of the track, let beatloop_size be set to
// a smaller size, but not get larger.
double previousBeatloopSize = m_pCOBeatLoopSize->get();
- double previousBeatloopOutPoint = pBeats->findNBeatsFromSample(
- newloopSamples.start, previousBeatloopSize);
- if (previousBeatloopOutPoint < newloopSamples.start
- && beats < previousBeatloopSize) {
+ mixxx::FramePos previousBeatloopOutPointFramePos =
+ pBeats->findNBeatsFromFrame(
+ samplePosToFramePos(newloopSamples.start),
+ previousBeatloopSize);
+
+ if (framePosToSamplePos(previousBeatloopOutPointFramePos) < newloopSamples.start &&
+ beats < previousBeatloopSize) {
m_pCOBeatLoopSize->setAndConfirm(beats);
}
return;
@@ -1135,7 +1165,8 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
// This check happens after setting m_pCOBeatLoopSize so
// beatloop_size can be prepared without having a track loaded.
- if ((newloopSamples.start == kNoTrigger) || (newloopSamples.end == kNoTrigger)) {
+ if ((newloopSamples.start == kNoTrigger) ||
+ (newloopSamples.end == kNoTrigger)) {
return;
}
@@ -1212,7 +1243,10 @@ void LoopingControl::slotBeatJump(double beats) {
// If inside an active loop, move loop
slotLoopMove(beats);
} else {
- seekAbs(pBeats->findNBeatsFromSample(currentSample, beats));
+ seekAbs(pBeats->findNBeatsFromFrame(
+ mixxx::FramePos(currentSample / 2.0), beats)
+ .getValue() *
+ 2);
}
}
@@ -1238,12 +1272,26 @@ void LoopingControl::slotLoopMove(double beats) {
return;
}
- if (BpmControl::getBeatContext(pBeats, m_currentSample.getValue(),
- nullptr, nullptr, nullptr, nullptr)) {
- double new_loop_in = pBeats->findNBeatsFromSample(loopSamples.start, beats);
- double new_loop_out = currentLoopMatchesBeatloopSize() ?
- pBeats->findNBeatsFromSample(new_loop_in, m_pCOBeatLoopSize->get()) :
- pBeats->findNBeatsFromSample(loopSamples.end, beats);
+ if (BpmControl::getBeatContext(pBeats,
+ mixxx::FramePos(m_currentSample.getValue() / mixxx::kEngineChannelCount),
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr)) {
+ double new_loop_in =
+ pBeats->findNBeatsFromFrame(
+ mixxx::FramePos(loopSamples.start / 2.0), beats)
+ .getValue() *
+ 2;
+ double new_loop_out = currentLoopMatchesBeatloopSize()
+ ? pBeats->findNBeatsFromFrame(mixxx::FramePos(new_loop_in / 2.0),
+ m_pCOBeatLoopSize->get())
+ .getValue() *
+ 2
+ : pBeats->findNBeatsFromFrame(
+ mixxx::FramePos(loopSamples.end / 2.0), beats)
+ .getValue() *
+ 2;
// If we are looping make sure that the play head does not leave the
// loop as a result of our adjustment.
diff --git a/src/engine/controls/quantizecontrol.cpp b/src/engine/controls/quantizecontrol.cpp
index 0960edf22ac..a0b84072260 100644
--- a/src/engine/controls/quantizecontrol.cpp
+++ b/src/engine/controls/quantizecontrol.cpp
@@ -2,14 +2,16 @@
// Created on Sat 5, 2011
// Author: pwhelan
+#include "engine/controls/quantizecontrol.h"
+
#include
#include "control/controlobject.h"
-#include "preferences/usersettings.h"
#include "control/controlpushbutton.h"
-#include "engine/controls/quantizecontrol.h"
#include "engine/controls/enginecontrol.h"
+#include "preferences/usersettings.h"
#include "util/assert.h"
+#include "util/frameadapter.h"
QuantizeControl::QuantizeControl(QString group,
UserSettingsPointer pConfig)
@@ -40,7 +42,7 @@ void QuantizeControl::trackLoaded(TrackPointer pNewTrack) {
lookupBeatPositions(0.0);
updateClosestBeat(0.0);
} else {
- m_pBeats.clear();
+ m_pBeats.reset();
m_pCOPrevBeat->set(-1);
m_pCONextBeat->set(-1);
m_pCOClosestBeat->set(-1);
@@ -81,10 +83,10 @@ void QuantizeControl::playPosChanged(double dNewPlaypos) {
void QuantizeControl::lookupBeatPositions(double dCurrentSample) {
mixxx::BeatsPointer pBeats = m_pBeats;
if (pBeats) {
- double prevBeat, nextBeat;
- pBeats->findPrevNextBeats(dCurrentSample, &prevBeat, &nextBeat);
- m_pCOPrevBeat->set(prevBeat);
- m_pCONextBeat->set(nextBeat);
+ mixxx::FramePos prevBeat, nextBeat;
+ pBeats->findPrevNextBeats(samplePosToFramePos(dCurrentSample), &prevBeat, &nextBeat);
+ m_pCOPrevBeat->set(framePosToSamplePos(prevBeat));
+ m_pCONextBeat->set(framePosToSamplePos(nextBeat));
}
}
diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp
index cc08b50d618..4a9c1c1c76e 100644
--- a/src/engine/enginebuffer.cpp
+++ b/src/engine/enginebuffer.cpp
@@ -1197,7 +1197,15 @@ void EngineBuffer::processSeek(bool paused) {
kLogger.trace() << "EngineBuffer::processSeek Seeking phase";
}
double requestedPosition = position;
- double syncPosition = m_pBpmControl->getBeatMatchPosition(position, true, true);
+ double syncPosition =
+ m_pBpmControl
+ ->getBeatMatchPosition(
+ mixxx::FramePos(
+ position / mixxx::kEngineChannelCount),
+ true,
+ true)
+ .getValue() *
+ mixxx::kEngineChannelCount;
position = m_pLoopingControl->getSyncPositionInsideLoop(requestedPosition, syncPosition);
if (kLogger.traceEnabled()) {
kLogger.trace()
diff --git a/src/engine/sync/synccontrol.cpp b/src/engine/sync/synccontrol.cpp
index 8c319fac8d8..9bde4b1d4bd 100644
--- a/src/engine/sync/synccontrol.cpp
+++ b/src/engine/sync/synccontrol.cpp
@@ -486,7 +486,7 @@ void SyncControl::notifySeek(double dNewPlaypos) {
double SyncControl::fileBpm() const {
mixxx::BeatsPointer pBeats = m_pBeats;
if (pBeats) {
- return pBeats->getBpm();
+ return pBeats->getBpm().getValue();
}
return 0.0;
}
diff --git a/src/library/banshee/bansheeplaylistmodel.cpp b/src/library/banshee/bansheeplaylistmodel.cpp
index c99460577fd..b8252213c57 100644
--- a/src/library/banshee/bansheeplaylistmodel.cpp
+++ b/src/library/banshee/bansheeplaylistmodel.cpp
@@ -278,7 +278,8 @@ TrackPointer BansheePlaylistModel::getTrack(const QModelIndex& index) const {
pTrack->setComposer(getFieldString(index, CLM_COMPOSER));
// If the track has a BPM, then give it a static beatgrid.
if (bpm > 0) {
- mixxx::BeatsPointer pBeats = BeatFactory::makeBeatGrid(*pTrack, bpm, 0.0);
+ mixxx::BeatsPointer pBeats = std::make_shared(mixxx::Beats(pTrack.get()));
+ pBeats->setGrid(mixxx::Bpm(bpm));
pTrack->setBeats(pBeats);
}
}
diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp
index 25d5a6691f5..8b97c8a563a 100644
--- a/src/library/dao/trackdao.cpp
+++ b/src/library/dao/trackdao.cpp
@@ -553,11 +553,11 @@ void bindTrackLibraryValues(
QString beatsSubVersion;
// Fall back on cached BPM
double dBpm = trackInfo.getBpm().getValue();
- if (!pBeats.isNull()) {
- beatsBlob = pBeats->toByteArray();
+ if (pBeats) {
+ beatsBlob = pBeats->toProtobuf();
beatsVersion = pBeats->getVersion();
beatsSubVersion = pBeats->getSubVersion();
- dBpm = pBeats->getBpm();
+ dBpm = pBeats->getBpm().getValue();
}
pTrackLibraryQuery->bindValue(":bpm", dBpm);
pTrackLibraryQuery->bindValue(":beats_version", beatsVersion);
@@ -1181,7 +1181,7 @@ bool setTrackBeats(const QSqlRecord& record, const int column,
QByteArray beatsBlob = record.value(column + 3).toByteArray();
bool bpmLocked = record.value(column + 4).toBool();
mixxx::BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(
- *pTrack, beatsVersion, beatsSubVersion, beatsBlob);
+ pTrack, beatsVersion, beatsSubVersion, beatsBlob);
if (pBeats) {
pTrack->setBeats(pBeats);
} else {
diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp
index 541fe0a0dde..f436c01c494 100644
--- a/src/library/dlgtrackinfo.cpp
+++ b/src/library/dlgtrackinfo.cpp
@@ -7,6 +7,7 @@
#include "library/coverartcache.h"
#include "library/coverartutils.h"
+#include "preferences/beatdetectionsettings.h"
#include "preferences/colorpalettesettings.h"
#include "sources/soundsourceproxy.h"
#include "track/beatfactory.h"
@@ -15,8 +16,8 @@
#include "track/keyutils.h"
#include "util/color/colorpalette.h"
#include "util/compatibility.h"
-#include "util/desktophelper.h"
#include "util/datetime.h"
+#include "util/desktophelper.h"
#include "util/duration.h"
const int kFilterLength = 80;
@@ -274,17 +275,20 @@ void DlgTrackInfo::populateFields(const Track& track) {
void DlgTrackInfo::reloadTrackBeats(const Track& track) {
mixxx::BeatsPointer pBeats = track.getBeats();
if (pBeats) {
- spinBpm->setValue(pBeats->getBpm());
+ spinBpm->setValue(pBeats->getBpm().getValue());
m_pBeatsClone = pBeats->clone();
} else {
- m_pBeatsClone.clear();
+ m_pBeatsClone.reset();
spinBpm->setValue(0.0);
}
- m_trackHasBeatMap = pBeats && !(pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM);
- bpmConst->setChecked(!m_trackHasBeatMap);
- bpmConst->setEnabled(m_trackHasBeatMap); // We cannot make turn a BeatGrid to a BeatMap
- spinBpm->setEnabled(!m_trackHasBeatMap); // We cannot change bpm continuously or tab them
- bpmTap->setEnabled(!m_trackHasBeatMap); // when we have a beatmap
+
+ m_bpmIsConst = pBeats &&
+ m_pConfig->getValue(
+ ConfigKey(BPM_CONFIG_KEY, BPM_FIXED_TEMPO_ASSUMPTION));
+ bpmConst->setChecked(m_bpmIsConst);
+ bpmConst->setEnabled(!m_bpmIsConst);
+ spinBpm->setEnabled(m_bpmIsConst); // We cannot change bpm continuously or tap them
+ bpmTap->setEnabled(m_bpmIsConst); // when we have a variable bpm.
if (track.isBpmLocked()) {
tabBPM->setEnabled(false);
@@ -445,7 +449,7 @@ void DlgTrackInfo::clear() {
txtTrackNumber->setText("");
txtComment->setPlainText("");
spinBpm->setValue(0.0);
- m_pBeatsClone.clear();
+ m_pBeatsClone.reset();
m_keysClone = Keys();
txtDuration->setText("");
@@ -461,53 +465,53 @@ void DlgTrackInfo::clear() {
}
void DlgTrackInfo::slotBpmDouble() {
- m_pBeatsClone->scale(mixxx::Beats::DOUBLE);
+ m_pBeatsClone->scale(mixxx::BeatsInternal::DOUBLE);
// read back the actual value
- double newValue = m_pBeatsClone->getBpm();
+ double newValue = m_pBeatsClone->getBpm().getValue();
spinBpm->setValue(newValue);
}
void DlgTrackInfo::slotBpmHalve() {
- m_pBeatsClone->scale(mixxx::Beats::HALVE);
+ m_pBeatsClone->scale(mixxx::BeatsInternal::HALVE);
// read back the actual value
- double newValue = m_pBeatsClone->getBpm();
+ double newValue = m_pBeatsClone->getBpm().getValue();
spinBpm->setValue(newValue);
}
void DlgTrackInfo::slotBpmTwoThirds() {
- m_pBeatsClone->scale(mixxx::Beats::TWOTHIRDS);
+ m_pBeatsClone->scale(mixxx::BeatsInternal::TWOTHIRDS);
// read back the actual value
- double newValue = m_pBeatsClone->getBpm();
+ double newValue = m_pBeatsClone->getBpm().getValue();
spinBpm->setValue(newValue);
}
void DlgTrackInfo::slotBpmThreeFourth() {
- m_pBeatsClone->scale(mixxx::Beats::THREEFOURTHS);
+ m_pBeatsClone->scale(mixxx::BeatsInternal::THREEFOURTHS);
// read back the actual value
- double newValue = m_pBeatsClone->getBpm();
+ double newValue = m_pBeatsClone->getBpm().getValue();
spinBpm->setValue(newValue);
}
void DlgTrackInfo::slotBpmFourThirds() {
- m_pBeatsClone->scale(mixxx::Beats::FOURTHIRDS);
+ m_pBeatsClone->scale(mixxx::BeatsInternal::FOURTHIRDS);
// read back the actual value
- double newValue = m_pBeatsClone->getBpm();
+ double newValue = m_pBeatsClone->getBpm().getValue();
spinBpm->setValue(newValue);
}
void DlgTrackInfo::slotBpmThreeHalves() {
- m_pBeatsClone->scale(mixxx::Beats::THREEHALVES);
+ m_pBeatsClone->scale(mixxx::BeatsInternal::THREEHALVES);
// read back the actual value
- double newValue = m_pBeatsClone->getBpm();
+ double newValue = m_pBeatsClone->getBpm().getValue();
spinBpm->setValue(newValue);
}
void DlgTrackInfo::slotBpmClear() {
spinBpm->setValue(0);
- m_pBeatsClone.clear();
+ m_pBeatsClone.reset();
bpmConst->setChecked(true);
- bpmConst->setEnabled(m_trackHasBeatMap);
+ bpmConst->setEnabled(!m_bpmIsConst);
spinBpm->setEnabled(true);
bpmTap->setEnabled(true);
}
@@ -523,10 +527,12 @@ void DlgTrackInfo::slotBpmConstChanged(int state) {
// The cue point should be set on a beat, so this seams
// to be a good alternative
CuePosition cue = m_pLoadedTrack->getCuePoint();
- m_pBeatsClone = BeatFactory::makeBeatGrid(
- *m_pLoadedTrack, spinBpm->value(), cue.getPosition());
+ m_pBeatsClone = std::make_shared(m_pLoadedTrack.get());
+ // setGrid accepts position in frames thus dividing by 2
+ m_pBeatsClone->setGrid(mixxx::Bpm(spinBpm->value()),
+ mixxx::FramePos(cue.getPosition() / 2.0));
} else {
- m_pBeatsClone.clear();
+ m_pBeatsClone.reset();
}
spinBpm->setEnabled(true);
bpmTap->setEnabled(true);
@@ -551,27 +557,28 @@ void DlgTrackInfo::slotBpmTap(double averageLength, int numSamples) {
void DlgTrackInfo::slotSpinBpmValueChanged(double value) {
if (value <= 0) {
- m_pBeatsClone.clear();
+ m_pBeatsClone.reset();
return;
}
if (!m_pBeatsClone) {
CuePosition cue = m_pLoadedTrack->getCuePoint();
- m_pBeatsClone = BeatFactory::makeBeatGrid(
- *m_pLoadedTrack, value, cue.getPosition());
+ m_pBeatsClone = std::make_shared(m_pLoadedTrack.get());
+ m_pBeatsClone->setGrid(mixxx::Bpm(spinBpm->value()),
+ mixxx::FramePos(cue.getPosition() / 2.0));
}
- double oldValue = m_pBeatsClone->getBpm();
+ double oldValue = m_pBeatsClone->getBpm().getValue();
if (oldValue == value) {
return;
}
- if (m_pBeatsClone->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM) {
- m_pBeatsClone->setBpm(value);
+ if (m_bpmIsConst) {
+ m_pBeatsClone->setBpm(mixxx::Bpm(value));
}
// read back the actual value
- double newValue = m_pBeatsClone->getBpm();
+ double newValue = m_pBeatsClone->getBpm().getValue();
spinBpm->setValue(newValue);
}
diff --git a/src/library/dlgtrackinfo.h b/src/library/dlgtrackinfo.h
index d4a06eea2bc..6b702fe3cad 100644
--- a/src/library/dlgtrackinfo.h
+++ b/src/library/dlgtrackinfo.h
@@ -92,7 +92,7 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo {
TrackPointer m_pLoadedTrack;
mixxx::BeatsPointer m_pBeatsClone;
Keys m_keysClone;
- bool m_trackHasBeatMap;
+ bool m_bpmIsConst;
QScopedPointer m_pTapFilter;
QScopedPointer m_pTagFetcher;
diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp
index c2ef77205d5..1a8c14ebf57 100644
--- a/src/library/rekordbox/rekordboxfeature.cpp
+++ b/src/library/rekordbox/rekordboxfeature.cpp
@@ -388,7 +388,9 @@ void insertTrack(
query.bindValue(":bitrate", bitrate);
query.bindValue(":analyze_path", anlzPath);
query.bindValue(":device", device);
- query.bindValue(":color", mixxx::RgbColor::toQVariant(colorFromID(static_cast(track->color_id()))));
+ query.bindValue(":color",
+ mixxx::RgbColor::toQVariant(
+ colorFromID(static_cast(track->color_id()))));
if (!query.exec()) {
LOG_FAILED_QUERY(query);
@@ -396,13 +398,13 @@ void insertTrack(
int trackID = -1;
QSqlQuery finderQuery(database);
- finderQuery.prepare("select id from " + kRekordboxLibraryTable + " where rb_id=:rb_id and device=:device");
+ finderQuery.prepare("select id from " + kRekordboxLibraryTable +
+ " where rb_id=:rb_id and device=:device");
finderQuery.bindValue(":rb_id", rbID);
finderQuery.bindValue(":device", device);
if (!finderQuery.exec()) {
- LOG_FAILED_QUERY(finderQuery)
- << "rbID:" << rbID;
+ LOG_FAILED_QUERY(finderQuery) << "rbID:" << rbID;
}
if (finderQuery.next()) {
@@ -415,13 +417,11 @@ void insertTrack(
if (!queryInsertIntoDevicePlaylistTracks.exec()) {
LOG_FAILED_QUERY(queryInsertIntoDevicePlaylistTracks)
- << "trackID:" << trackID
- << "position:" << audioFilesCount;
+ << "trackID:" << trackID << "position:" << audioFilesCount;
}
}
-void buildPlaylistTree(
- QSqlDatabase& database,
+void buildPlaylistTree(QSqlDatabase& database,
TreeItem* parent,
uint32_t parentID,
QMap& playlistNameMap,
@@ -431,11 +431,13 @@ void buildPlaylistTree(
QString playlistPath,
QString device);
-QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* deviceItem) {
+QString parseDeviceDB(
+ mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* deviceItem) {
QString device = deviceItem->getLabel();
QString devicePath = deviceItem->getData().toList()[0].toString();
- qDebug() << "parseDeviceDB device: " << device << " devicePath: " << devicePath;
+ qDebug() << "parseDeviceDB device: " << device
+ << " devicePath: " << devicePath;
QString dbPath = devicePath + QStringLiteral("/") + kPdbPath;
@@ -462,11 +464,11 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dev
ScopedTransaction transaction(database);
QSqlQuery query(database);
- query.prepare(
- "INSERT INTO " + kRekordboxLibraryTable +
+ query.prepare("INSERT INTO " + kRekordboxLibraryTable +
" (rb_id, artist, title, album, year,"
"genre,comment,tracknumber,bpm, bitrate,duration, location,"
- "rating,key,analyze_path,device,color) VALUES (:rb_id, :artist, :title, :album, :year,:genre,"
+ "rating,key,analyze_path,device,color) VALUES (:rb_id, :artist, "
+ ":title, :album, :year,:genre,"
":comment, :tracknumber,:bpm, :bitrate,:duration, :location,"
":rating,:key,:analyze_path,:device,:color)");
@@ -476,8 +478,8 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dev
int playlistID = createDevicePlaylist(database, devicePath);
QSqlQuery queryInsertIntoDevicePlaylistTracks(database);
- queryInsertIntoDevicePlaylistTracks.prepare(
- "INSERT INTO " + kRekordboxPlaylistTracksTable +
+ queryInsertIntoDevicePlaylistTracks.prepare("INSERT INTO " +
+ kRekordboxPlaylistTracksTable +
" (playlist_id, track_id, position) "
"VALUES (:playlist_id, :track_id, :position)");
@@ -518,25 +520,30 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dev
bool folderOrPlaylistFound = false;
- for (int tableOrderIndex = 0; tableOrderIndex < totalTables; tableOrderIndex++) {
- for (
- std::vector::iterator table = reckordboxDB.tables()->begin();
+ for (int tableOrderIndex = 0; tableOrderIndex < totalTables;
+ tableOrderIndex++) {
+ for (std::vector::iterator table =
+ reckordboxDB.tables()->begin();
table != reckordboxDB.tables()->end();
++table) {
if ((*table)->type() == tableOrder[tableOrderIndex]) {
uint16_t lastIndex = (*table)->last_page()->index();
- rekordbox_pdb_t::page_ref_t* currentRef = (*table)->first_page();
+ rekordbox_pdb_t::page_ref_t* currentRef =
+ (*table)->first_page();
while (true) {
rekordbox_pdb_t::page_t* page = currentRef->body();
if (page->is_data_page()) {
- for (
- std::vector::iterator rowGroup = page->row_groups()->begin();
+ for (std::vector::
+ iterator rowGroup =
+ page->row_groups()->begin();
rowGroup != page->row_groups()->end();
++rowGroup) {
- for (
- std::vector::iterator rowRef = (*rowGroup)->rows()->begin();
+ for (std::vector::
+ iterator rowRef = (*rowGroup)
+ ->rows()
+ ->begin();
rowRef != (*rowGroup)->rows()->end();
++rowRef) {
if ((*rowRef)->present()) {
@@ -544,49 +551,89 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dev
case rekordbox_pdb_t::PAGE_TYPE_KEYS: {
// Key found, update map
rekordbox_pdb_t::key_row_t* key =
- static_cast((*rowRef)->body());
- keysMap[key->id()] = getText(key->name());
+ static_cast(
+ (*rowRef)->body());
+ keysMap[key->id()] =
+ getText(key->name());
} break;
case rekordbox_pdb_t::PAGE_TYPE_GENRES: {
// Genre found, update map
rekordbox_pdb_t::genre_row_t* genre =
- static_cast((*rowRef)->body());
- genresMap[genre->id()] = getText(genre->name());
+ static_cast(
+ (*rowRef)->body());
+ genresMap[genre->id()] =
+ getText(genre->name());
} break;
case rekordbox_pdb_t::PAGE_TYPE_ARTISTS: {
// Artist found, update map
rekordbox_pdb_t::artist_row_t* artist =
- static_cast((*rowRef)->body());
- artistsMap[artist->id()] = getText(artist->name());
+ static_cast(
+ (*rowRef)->body());
+ artistsMap[artist->id()] =
+ getText(artist->name());
} break;
case rekordbox_pdb_t::PAGE_TYPE_ALBUMS: {
// Album found, update map
rekordbox_pdb_t::album_row_t* album =
- static_cast((*rowRef)->body());
- albumsMap[album->id()] = getText(album->name());
+ static_cast(
+ (*rowRef)->body());
+ albumsMap[album->id()] =
+ getText(album->name());
} break;
- case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES: {
+ case rekordbox_pdb_t::
+ PAGE_TYPE_PLAYLIST_ENTRIES: {
// Playlist to track mapping found, update map
- rekordbox_pdb_t::playlist_entry_row_t* playlistEntry =
- static_cast((*rowRef)->body());
- playlistTrackMap[playlistEntry->playlist_id()][playlistEntry->entry_index()] =
- playlistEntry->track_id();
+ rekordbox_pdb_t::playlist_entry_row_t*
+ playlistEntry = static_cast<
+ rekordbox_pdb_t::
+ playlist_entry_row_t*>(
+ (*rowRef)->body());
+ playlistTrackMap
+ [playlistEntry->playlist_id()]
+ [playlistEntry->entry_index()] =
+ playlistEntry
+ ->track_id();
} break;
case rekordbox_pdb_t::PAGE_TYPE_TRACKS: {
// Track found, insert into database
- insertTrack(
- database, static_cast((*rowRef)->body()), query, queryInsertIntoDevicePlaylistTracks, artistsMap, albumsMap, genresMap, keysMap, devicePath, device, audioFilesCount);
+ insertTrack(database,
+ static_cast(
+ (*rowRef)->body()),
+ query,
+ queryInsertIntoDevicePlaylistTracks,
+ artistsMap,
+ albumsMap,
+ genresMap,
+ keysMap,
+ devicePath,
+ device,
+ audioFilesCount);
audioFilesCount++;
} break;
- case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE: {
+ case rekordbox_pdb_t::
+ PAGE_TYPE_PLAYLIST_TREE: {
// Playlist tree node found, update map
- rekordbox_pdb_t::playlist_tree_row_t* playlistTree =
- static_cast((*rowRef)->body());
-
- playlistNameMap[playlistTree->id()] = getText(playlistTree->name());
- playlistIsFolderMap[playlistTree->id()] = playlistTree->is_folder();
- playlistTreeMap[playlistTree->parent_id()][playlistTree->sort_order()] = playlistTree->id();
+ rekordbox_pdb_t::playlist_tree_row_t*
+ playlistTree = static_cast<
+ rekordbox_pdb_t::
+ playlist_tree_row_t*>(
+ (*rowRef)->body());
+
+ playlistNameMap[playlistTree->id()] =
+ getText(playlistTree->name());
+ playlistIsFolderMap[playlistTree
+ ->id()] =
+ playlistTree->is_folder();
+ playlistTreeMap
+ [playlistTree->parent_id()]
+ [playlistTree->sort_order()] =
+ playlistTree->id();
folderOrPlaylistFound = true;
} break;
@@ -611,18 +658,26 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dev
if (audioFilesCount > 0 || folderOrPlaylistFound) {
// If we have found anything, recursively build playlist/folder TreeItem children
// for the original device TreeItem
- buildPlaylistTree(database, deviceItem, 0, playlistNameMap, playlistIsFolderMap, playlistTreeMap, playlistTrackMap, devicePath, device);
+ buildPlaylistTree(database,
+ deviceItem,
+ 0,
+ playlistNameMap,
+ playlistIsFolderMap,
+ playlistTreeMap,
+ playlistTrackMap,
+ devicePath,
+ device);
}
- qDebug() << "Found: " << audioFilesCount << " audio files in Rekordbox device " << device;
+ qDebug() << "Found: " << audioFilesCount
+ << " audio files in Rekordbox device " << device;
transaction.commit();
return devicePath;
}
-void buildPlaylistTree(
- QSqlDatabase& database,
+void buildPlaylistTree(QSqlDatabase& database,
TreeItem* parent,
uint32_t parentID,
QMap& playlistNameMap,
@@ -631,11 +686,14 @@ void buildPlaylistTree(
QMap>& playlistTrackMap,
QString playlistPath,
QString device) {
- for (uint32_t childIndex = 0; childIndex < (uint32_t)playlistTreeMap[parentID].size(); childIndex++) {
+ for (uint32_t childIndex = 0;
+ childIndex < (uint32_t)playlistTreeMap[parentID].size();
+ childIndex++) {
uint32_t childID = playlistTreeMap[parentID][childIndex];
QString playlistItemName = playlistNameMap[childID];
- QString currentPath = playlistPath + kPLaylistPathDelimiter + playlistItemName;
+ QString currentPath =
+ playlistPath + kPLaylistPathDelimiter + playlistItemName;
QList data;
@@ -646,8 +704,8 @@ void buildPlaylistTree(
// Create a playlist for this child
QSqlQuery queryInsertIntoPlaylist(database);
- queryInsertIntoPlaylist.prepare(
- "INSERT INTO " + kRekordboxPlaylistsTable +
+ queryInsertIntoPlaylist.prepare("INSERT INTO " +
+ kRekordboxPlaylistsTable +
" (name) "
"VALUES (:name)");
@@ -660,12 +718,12 @@ void buildPlaylistTree(
}
QSqlQuery idQuery(database);
- idQuery.prepare("select id from " + kRekordboxPlaylistsTable + " where name=:path");
+ idQuery.prepare("select id from " + kRekordboxPlaylistsTable +
+ " where name=:path");
idQuery.bindValue(":path", currentPath);
if (!idQuery.exec()) {
- LOG_FAILED_QUERY(idQuery)
- << "currentPath" << currentPath;
+ LOG_FAILED_QUERY(idQuery) << "currentPath" << currentPath;
return;
}
@@ -675,36 +733,42 @@ void buildPlaylistTree(
}
QSqlQuery queryInsertIntoPlaylistTracks(database);
- queryInsertIntoPlaylistTracks.prepare(
- "INSERT INTO " + kRekordboxPlaylistTracksTable +
+ queryInsertIntoPlaylistTracks.prepare("INSERT INTO " +
+ kRekordboxPlaylistTracksTable +
" (playlist_id, track_id, position) "
"VALUES (:playlist_id, :track_id, :position)");
if (playlistTrackMap.count(childID)) {
// Add playlist tracks for children
- for (uint32_t trackIndex = 1; trackIndex <= static_cast(playlistTrackMap[childID].size()); trackIndex++) {
+ for (uint32_t trackIndex = 1; trackIndex <=
+ static_cast(playlistTrackMap[childID].size());
+ trackIndex++) {
uint32_t rbTrackID = playlistTrackMap[childID][trackIndex];
int trackID = -1;
QSqlQuery finderQuery(database);
- finderQuery.prepare("select id from " + kRekordboxLibraryTable + " where rb_id=:rb_id and device=:device");
+ finderQuery.prepare("select id from " + kRekordboxLibraryTable +
+ " where rb_id=:rb_id and device=:device");
finderQuery.bindValue(":rb_id", rbTrackID);
finderQuery.bindValue(":device", device);
if (!finderQuery.exec()) {
LOG_FAILED_QUERY(finderQuery)
- << "rbTrackID:" << rbTrackID
- << "device:" << device;
+ << "rbTrackID:" << rbTrackID << "device:" << device;
return;
}
if (finderQuery.next()) {
- trackID = finderQuery.value(finderQuery.record().indexOf("id")).toInt();
+ trackID = finderQuery
+ .value(finderQuery.record().indexOf("id"))
+ .toInt();
}
- queryInsertIntoPlaylistTracks.bindValue(":playlist_id", playlistID);
+ queryInsertIntoPlaylistTracks.bindValue(
+ ":playlist_id", playlistID);
queryInsertIntoPlaylistTracks.bindValue(":track_id", trackID);
- queryInsertIntoPlaylistTracks.bindValue(":position", static_cast(trackIndex));
+ queryInsertIntoPlaylistTracks.bindValue(
+ ":position", static_cast(trackIndex));
if (!queryInsertIntoPlaylistTracks.exec()) {
LOG_FAILED_QUERY(queryInsertIntoPlaylistTracks)
@@ -719,7 +783,15 @@ void buildPlaylistTree(
if (playlistIsFolderMap[childID]) {
// If this child is a folder (playlists are only leaf nodes), build playlist tree for it
- buildPlaylistTree(database, child, childID, playlistNameMap, playlistIsFolderMap, playlistTreeMap, playlistTrackMap, currentPath, device);
+ buildPlaylistTree(database,
+ child,
+ childID,
+ playlistNameMap,
+ playlistIsFolderMap,
+ playlistTreeMap,
+ playlistTrackMap,
+ currentPath,
+ device);
}
}
}
@@ -730,34 +802,39 @@ void clearDeviceTables(QSqlDatabase& database, TreeItem* child) {
int trackID = -1;
int playlistID = -1;
QSqlQuery tracksQuery(database);
- tracksQuery.prepare("select id from " + kRekordboxLibraryTable + " where device=:device");
+ tracksQuery.prepare("select id from " + kRekordboxLibraryTable +
+ " where device=:device");
tracksQuery.bindValue(":device", child->getLabel());
QSqlQuery deletePlaylistsQuery(database);
- deletePlaylistsQuery.prepare("delete from " + kRekordboxPlaylistsTable + " where id=:id");
+ deletePlaylistsQuery.prepare(
+ "delete from " + kRekordboxPlaylistsTable + " where id=:id");
QSqlQuery deletePlaylistTracksQuery(database);
- deletePlaylistTracksQuery.prepare("delete from " + kRekordboxPlaylistTracksTable + " where playlist_id=:playlist_id");
+ deletePlaylistTracksQuery.prepare("delete from " +
+ kRekordboxPlaylistTracksTable + " where playlist_id=:playlist_id");
if (!tracksQuery.exec()) {
- LOG_FAILED_QUERY(tracksQuery)
- << "device:" << child->getLabel();
+ LOG_FAILED_QUERY(tracksQuery) << "device:" << child->getLabel();
}
while (tracksQuery.next()) {
trackID = tracksQuery.value(tracksQuery.record().indexOf("id")).toInt();
QSqlQuery playlistTracksQuery(database);
- playlistTracksQuery.prepare("select playlist_id from " + kRekordboxPlaylistTracksTable + " where track_id=:track_id");
+ playlistTracksQuery.prepare("select playlist_id from " +
+ kRekordboxPlaylistTracksTable + " where track_id=:track_id");
playlistTracksQuery.bindValue(":track_id", trackID);
if (!playlistTracksQuery.exec()) {
- LOG_FAILED_QUERY(playlistTracksQuery)
- << "trackID:" << trackID;
+ LOG_FAILED_QUERY(playlistTracksQuery) << "trackID:" << trackID;
}
while (playlistTracksQuery.next()) {
- playlistID = playlistTracksQuery.value(playlistTracksQuery.record().indexOf("playlist_id")).toInt();
+ playlistID = playlistTracksQuery
+ .value(playlistTracksQuery.record().indexOf(
+ "playlist_id"))
+ .toInt();
deletePlaylistsQuery.bindValue(":id", playlistID);
@@ -776,18 +853,23 @@ void clearDeviceTables(QSqlDatabase& database, TreeItem* child) {
}
QSqlQuery deleteTracksQuery(database);
- deleteTracksQuery.prepare("delete from " + kRekordboxLibraryTable + " where device=:device");
+ deleteTracksQuery.prepare(
+ "delete from " + kRekordboxLibraryTable + " where device=:device");
deleteTracksQuery.bindValue(":device", child->getLabel());
if (!deleteTracksQuery.exec()) {
- LOG_FAILED_QUERY(deleteTracksQuery)
- << "device:" << child->getLabel();
+ LOG_FAILED_QUERY(deleteTracksQuery) << "device:" << child->getLabel();
}
transaction.commit();
}
-void setHotCue(TrackPointer track, double startPosition, double endPosition, int id, QString label, mixxx::RgbColor::optional_t color) {
+void setHotCue(TrackPointer track,
+ double startPosition,
+ double endPosition,
+ int id,
+ QString label,
+ mixxx::RgbColor::optional_t color) {
CuePointer pCue;
bool hotCueFound = false;
@@ -817,12 +899,17 @@ void setHotCue(TrackPointer track, double startPosition, double endPosition, int
}
}
-void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool ignoreCues, QString anlzPath) {
+void readAnalyze(TrackPointer track,
+ double sampleRate,
+ int timingOffset,
+ bool ignoreCues,
+ QString anlzPath) {
if (!QFile(anlzPath).exists()) {
return;
}
- qDebug() << "Rekordbox ANLZ path:" << anlzPath << " for: " << track->getTitle();
+ qDebug() << "Rekordbox ANLZ path:" << anlzPath
+ << " for: " << track->getTitle();
std::ifstream ifs(anlzPath.toStdString(), std::ifstream::binary);
kaitai::kstream ks(&ifs);
@@ -835,18 +922,26 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
QList memoryCuesAndLoops;
int lastHotCueIndex = 0;
- for (std::vector::iterator section = anlz.sections()->begin(); section != anlz.sections()->end(); ++section) {
+ for (std::vector::iterator section =
+ anlz.sections()->begin();
+ section != anlz.sections()->end();
+ ++section) {
switch ((*section)->fourcc()) {
case rekordbox_anlz_t::SECTION_TAGS_BEAT_GRID: {
if (!ignoreCues) {
break;
}
- rekordbox_anlz_t::beat_grid_tag_t* beatGridTag = static_cast((*section)->body());
+ rekordbox_anlz_t::beat_grid_tag_t* beatGridTag =
+ static_cast(
+ (*section)->body());
QVector beats;
- for (std::vector::iterator beat = beatGridTag->beats()->begin(); beat != beatGridTag->beats()->end(); ++beat) {
+ for (std::vector::iterator
+ beat = beatGridTag->beats()->begin();
+ beat != beatGridTag->beats()->end();
+ ++beat) {
int time = static_cast((*beat)->time()) - timingOffset;
// Ensure no offset times are less than 1
if (time < 1) {
@@ -857,8 +952,14 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
QHash extraVersionInfo;
- mixxx::BeatsPointer pBeats = BeatFactory::makePreferredBeats(
- *track, beats, extraVersionInfo, false, false, sampleRate, 0, 0, 0);
+ mixxx::BeatsPointer pBeats = BeatFactory::makePreferredBeats(track,
+ beats,
+ extraVersionInfo,
+ false,
+ false,
+ 0,
+ 0,
+ 0);
track->setBeats(pBeats);
} break;
@@ -867,9 +968,14 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
break;
}
- rekordbox_anlz_t::cue_tag_t* cuesTag = static_cast((*section)->body());
+ rekordbox_anlz_t::cue_tag_t* cuesTag =
+ static_cast(
+ (*section)->body());
- for (std::vector::iterator cueEntry = cuesTag->cues()->begin(); cueEntry != cuesTag->cues()->end(); ++cueEntry) {
+ for (std::vector::iterator
+ cueEntry = cuesTag->cues()->begin();
+ cueEntry != cuesTag->cues()->end();
+ ++cueEntry) {
int time = static_cast((*cueEntry)->time()) - timingOffset;
// Ensure no offset times are less than 1
if (time < 1) {
@@ -888,7 +994,9 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
memoryCuesAndLoops << memoryCue;
} break;
case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: {
- int endTime = static_cast((*cueEntry)->loop_time()) - timingOffset;
+ int endTime =
+ static_cast((*cueEntry)->loop_time()) -
+ timingOffset;
// Ensure no offset times are less than 1
if (endTime < 1) {
endTime = 1;
@@ -896,19 +1004,20 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
memory_cue_loop_t loop;
loop.startPosition = position;
- loop.endPosition = samples * static_cast(endTime);
+ loop.endPosition =
+ samples * static_cast(endTime);
loop.color = mixxx::RgbColor::nullopt();
memoryCuesAndLoops << loop;
} break;
}
} break;
case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: {
- int hotCueIndex = static_cast((*cueEntry)->hot_cue() - 1);
+ int hotCueIndex =
+ static_cast((*cueEntry)->hot_cue() - 1);
if (hotCueIndex > lastHotCueIndex) {
lastHotCueIndex = hotCueIndex;
}
- setHotCue(
- track,
+ setHotCue(track,
position,
Cue::kNoPosition,
hotCueIndex,
@@ -923,10 +1032,16 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
break;
}
- rekordbox_anlz_t::cue_extended_tag_t* cuesExtendedTag = static_cast((*section)->body());
+ rekordbox_anlz_t::cue_extended_tag_t* cuesExtendedTag =
+ static_cast(
+ (*section)->body());
- for (std::vector::iterator cueExtendedEntry = cuesExtendedTag->cues()->begin(); cueExtendedEntry != cuesExtendedTag->cues()->end(); ++cueExtendedEntry) {
- int time = static_cast((*cueExtendedEntry)->time()) - timingOffset;
+ for (std::vector::iterator
+ cueExtendedEntry = cuesExtendedTag->cues()->begin();
+ cueExtendedEntry != cuesExtendedTag->cues()->end();
+ ++cueExtendedEntry) {
+ int time = static_cast((*cueExtendedEntry)->time()) -
+ timingOffset;
// Ensure no offset times are less than 1
if (time < 1) {
time = 1;
@@ -940,12 +1055,17 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
memory_cue_loop_t memoryCue;
memoryCue.startPosition = position;
memoryCue.endPosition = Cue::kNoPosition;
- memoryCue.comment = toUnicode((*cueExtendedEntry)->comment());
- memoryCue.color = colorFromID(static_cast((*cueExtendedEntry)->color_id()));
+ memoryCue.comment =
+ toUnicode((*cueExtendedEntry)->comment());
+ memoryCue.color = colorFromID(static_cast(
+ (*cueExtendedEntry)->color_id()));
memoryCuesAndLoops << memoryCue;
} break;
case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: {
- int endTime = static_cast((*cueExtendedEntry)->loop_time()) - timingOffset;
+ int endTime =
+ static_cast(
+ (*cueExtendedEntry)->loop_time()) -
+ timingOffset;
// Ensure no offset times are less than 1
if (endTime < 1) {
endTime = 1;
@@ -953,15 +1073,19 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
memory_cue_loop_t loop;
loop.startPosition = position;
- loop.endPosition = samples * static_cast(endTime);
- loop.comment = toUnicode((*cueExtendedEntry)->comment());
- loop.color = colorFromID(static_cast((*cueExtendedEntry)->color_id()));
+ loop.endPosition =
+ samples * static_cast(endTime);
+ loop.comment =
+ toUnicode((*cueExtendedEntry)->comment());
+ loop.color = colorFromID(static_cast(
+ (*cueExtendedEntry)->color_id()));
memoryCuesAndLoops << loop;
} break;
}
} break;
case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: {
- int hotCueIndex = static_cast((*cueExtendedEntry)->hot_cue() - 1);
+ int hotCueIndex = static_cast(
+ (*cueExtendedEntry)->hot_cue() - 1);
if (hotCueIndex > lastHotCueIndex) {
lastHotCueIndex = hotCueIndex;
}
@@ -987,20 +1111,26 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
}
if (memoryCuesAndLoops.size() > 0) {
- std::sort(memoryCuesAndLoops.begin(), memoryCuesAndLoops.end(), [](const memory_cue_loop_t& a, const memory_cue_loop_t& b) -> bool {
- return a.startPosition < b.startPosition;
- });
+ std::sort(memoryCuesAndLoops.begin(),
+ memoryCuesAndLoops.end(),
+ [](const memory_cue_loop_t& a, const memory_cue_loop_t& b)
+ -> bool { return a.startPosition < b.startPosition; });
bool mainCueFound = false;
// Add memory cues and loops
- for (int memoryCueOrLoopIndex = 0; memoryCueOrLoopIndex < memoryCuesAndLoops.size(); memoryCueOrLoopIndex++) {
- memory_cue_loop_t memoryCueOrLoop = memoryCuesAndLoops[memoryCueOrLoopIndex];
-
- if (!mainCueFound && memoryCueOrLoop.endPosition == Cue::kNoPosition) {
+ for (int memoryCueOrLoopIndex = 0;
+ memoryCueOrLoopIndex < memoryCuesAndLoops.size();
+ memoryCueOrLoopIndex++) {
+ memory_cue_loop_t memoryCueOrLoop =
+ memoryCuesAndLoops[memoryCueOrLoopIndex];
+
+ if (!mainCueFound &&
+ memoryCueOrLoop.endPosition == Cue::kNoPosition) {
// Set first chronological memory cue as Mixxx MainCue
track->setCuePoint(CuePosition(memoryCueOrLoop.startPosition));
- CuePointer pMainCue = track->findCueByType(mixxx::CueType::MainCue);
+ CuePointer pMainCue =
+ track->findCueByType(mixxx::CueType::MainCue);
pMainCue->setLabel(memoryCueOrLoop.comment);
pMainCue->setColor(*memoryCueOrLoop.color);
mainCueFound = true;
@@ -1008,8 +1138,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
// Mixxx v2.4 will feature multiple loops, so these saved here will be usable
// For 2.3, Mixxx treats them as hotcues and the first one will be loaded as the single loop Mixxx supports
lastHotCueIndex++;
- setHotCue(
- track,
+ setHotCue(track,
memoryCueOrLoop.startPosition,
memoryCueOrLoop.endPosition,
lastHotCueIndex,
@@ -1025,7 +1154,12 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i
RekordboxPlaylistModel::RekordboxPlaylistModel(QObject* parent,
TrackCollectionManager* trackCollectionManager,
QSharedPointer trackSource)
- : BaseExternalPlaylistModel(parent, trackCollectionManager, "mixxx.db.model.rekordbox.playlistmodel", kRekordboxPlaylistsTable, kRekordboxPlaylistTracksTable, trackSource) {
+ : BaseExternalPlaylistModel(parent,
+ trackCollectionManager,
+ "mixxx.db.model.rekordbox.playlistmodel",
+ kRekordboxPlaylistsTable,
+ kRekordboxPlaylistTracksTable,
+ trackSource) {
}
void RekordboxPlaylistModel::initSortColumnMapping() {
@@ -1034,35 +1168,67 @@ void RekordboxPlaylistModel::initSortColumnMapping() {
m_columnIndexBySortColumnId[i] = -1;
}
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ARTIST] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_TITLE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TITLE);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ALBUM] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUM);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ALBUMARTIST] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_YEAR] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_GENRE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GENRE);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COMPOSER] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_GROUPING] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_TRACKNUMBER] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_FILETYPE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_NATIVELOCATION] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COMMENT] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMMENT);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_DURATION] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_BITRATE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_BPM] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_REPLAYGAIN] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_DATETIMEADDED] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_TIMESPLAYED] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_RATING] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_KEY] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_PREVIEW] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COLOR] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COVERART] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART);
- m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_POSITION] = fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_POSITION);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ARTIST] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_TITLE] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TITLE);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ALBUM] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUM);
+ m_columnIndexBySortColumnId
+ [TrackModel::SortColumnId::SORTCOLUMN_ALBUMARTIST] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_YEAR] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_GENRE] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GENRE);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COMPOSER] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_GROUPING] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING);
+ m_columnIndexBySortColumnId
+ [TrackModel::SortColumnId::SORTCOLUMN_TRACKNUMBER] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_FILETYPE] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE);
+ m_columnIndexBySortColumnId
+ [TrackModel::SortColumnId::SORTCOLUMN_NATIVELOCATION] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COMMENT] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMMENT);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_DURATION] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_BITRATE] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_BPM] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM);
+ m_columnIndexBySortColumnId
+ [TrackModel::SortColumnId::SORTCOLUMN_REPLAYGAIN] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN);
+ m_columnIndexBySortColumnId
+ [TrackModel::SortColumnId::SORTCOLUMN_DATETIMEADDED] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED);
+ m_columnIndexBySortColumnId
+ [TrackModel::SortColumnId::SORTCOLUMN_TIMESPLAYED] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_RATING] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_KEY] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_PREVIEW] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COLOR] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COVERART] =
+ fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART);
+ m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_POSITION] =
+ fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_POSITION);
m_sortColumnIdByColumnIndex.clear();
for (int i = 0; i < TrackModel::SortColumnId::NUM_SORTCOLUMNIDS; ++i) {
- TrackModel::SortColumnId sortColumn = static_cast(i);
- m_sortColumnIdByColumnIndex.insert(m_columnIndexBySortColumnId[sortColumn], sortColumn);
+ TrackModel::SortColumnId sortColumn =
+ static_cast(i);
+ m_sortColumnIdByColumnIndex.insert(
+ m_columnIndexBySortColumnId[sortColumn], sortColumn);
}
}
@@ -1070,7 +1236,9 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const {
qDebug() << "RekordboxTrackModel::getTrack";
TrackPointer track = BaseExternalPlaylistModel::getTrack(index);
- QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
+ QString location = index.sibling(index.row(), fieldIndex("location"))
+ .data()
+ .toString();
if (!QFile(location).exists()) {
return track;
@@ -1088,9 +1256,11 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const {
int timingOffset = 0;
if (location.toLower().endsWith(".mp3")) {
- int timingShiftCase = mp3guessenc_timing_shift_case(location.toStdString().c_str());
+ int timingShiftCase =
+ mp3guessenc_timing_shift_case(location.toStdString().c_str());
- qDebug() << "Timing shift case:" << timingShiftCase << "for MP3 file:" << location;
+ qDebug() << "Timing shift case:" << timingShiftCase
+ << "for MP3 file:" << location;
switch (timingShiftCase) {
#ifdef __COREAUDIO__
@@ -1127,7 +1297,9 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const {
double sampleRate = static_cast(track->getSampleRate());
- QString anlzPath = index.sibling(index.row(), fieldIndex("analyze_path")).data().toString();
+ QString anlzPath = index.sibling(index.row(), fieldIndex("analyze_path"))
+ .data()
+ .toString();
QString anlzPathExt = anlzPath.left(anlzPath.length() - 3) + "EXT";
if (QFile(anlzPathExt).exists()) {
@@ -1140,9 +1312,12 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const {
// Assume that the key of the file the has been analyzed in Recordbox is correct
// and prevent the AnalyzerKey from re-analyzing.
- track->setKeys(KeyFactory::makeBasicKeysFromText(index.sibling(index.row(), fieldIndex("key")).data().toString(), mixxx::track::io::key::USER));
+ track->setKeys(KeyFactory::makeBasicKeysFromText(
+ index.sibling(index.row(), fieldIndex("key")).data().toString(),
+ mixxx::track::io::key::USER));
- track->setColor(mixxx::RgbColor::fromQVariant(index.sibling(index.row(), fieldIndex("color")).data()));
+ track->setColor(mixxx::RgbColor::fromQVariant(
+ index.sibling(index.row(), fieldIndex("color")).data()));
return track;
}
@@ -1155,7 +1330,8 @@ bool RekordboxPlaylistModel::isColumnHiddenByDefault(int column) {
}
bool RekordboxPlaylistModel::isColumnInternal(int column) {
- return column == fieldIndex(ColumnCache::COLUMN_REKORDBOX_ANALYZE_PATH) || BaseExternalPlaylistModel::isColumnInternal(column);
+ return column == fieldIndex(ColumnCache::COLUMN_REKORDBOX_ANALYZE_PATH) ||
+ BaseExternalPlaylistModel::isColumnInternal(column);
}
RekordboxFeature::RekordboxFeature(
diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp
index af176ed1977..a08734a316f 100644
--- a/src/library/searchquery.cpp
+++ b/src/library/searchquery.cpp
@@ -9,6 +9,13 @@
#include "util/db/dbconnection.h"
#include "util/db/sqllikewildcards.h"
+namespace {
+constexpr int kRoundToDecimal = 10;
+inline double reducePrecision(double num) {
+ return round(num * pow(10, kRoundToDecimal)) / pow(10, kRoundToDecimal);
+}
+} // namespace
+
QVariant getTrackValueForColumn(const TrackPointer& pTrack, const QString& column) {
if (column == LIBRARYTABLE_ARTIST) {
return pTrack->getArtist();
@@ -324,13 +331,34 @@ bool NumericFilterNode::match(const TrackPointer& pTrack) const {
continue;
}
- double dValue = value.toDouble();
+ const double dValue = value.toDouble();
+
+ // We are reducing the precision of double since an operation on Beats: getBpm
+ // results in a BPM that is dynamically calculated instead of using a stored
+ // precise value thus leading to a slight double precision error with comparison
+ // operations.
+ // This approximation can be removed with an implementation of beats
+ // that stores the BPM explicitly.
+ // TODO(hacksdump): Restore original functionality with BeatMarker implementation.
+ const double dValueReducedPrecision = reducePrecision(dValue);
+ const double dOperatorArgumentReducedPrecision = reducePrecision(m_dOperatorArgument);
+
if (m_bOperatorQuery) {
- if ((m_operator == "=" && dValue == m_dOperatorArgument) ||
- (m_operator == "<" && dValue < m_dOperatorArgument) ||
- (m_operator == ">" && dValue > m_dOperatorArgument) ||
- (m_operator == "<=" && dValue <= m_dOperatorArgument) ||
- (m_operator == ">=" && dValue >= m_dOperatorArgument)) {
+ if ((m_operator == "=" &&
+ dValueReducedPrecision ==
+ dOperatorArgumentReducedPrecision) ||
+ (m_operator == "<" &&
+ dValueReducedPrecision <
+ dOperatorArgumentReducedPrecision) ||
+ (m_operator == ">" &&
+ dValueReducedPrecision >
+ dOperatorArgumentReducedPrecision) ||
+ (m_operator == "<=" &&
+ dValueReducedPrecision <=
+ dOperatorArgumentReducedPrecision) ||
+ (m_operator == ">=" &&
+ dValueReducedPrecision >=
+ dOperatorArgumentReducedPrecision)) {
return true;
}
} else if (m_bRangeQuery && dValue >= m_dRangeLow &&
diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp
index 2058d66d6a4..c70fca9d919 100644
--- a/src/mixer/basetrackplayer.cpp
+++ b/src/mixer/basetrackplayer.cpp
@@ -14,7 +14,7 @@
#include "mixer/playerinfo.h"
#include "mixer/playermanager.h"
#include "sources/soundsourceproxy.h"
-#include "track/beatgrid.h"
+#include "track/beats.h"
#include "track/track.h"
#include "util/compatibility.h"
#include "util/platform.h"
@@ -32,7 +32,8 @@ const double kShiftCuesOffsetSmallMillis = 1;
inline double trackColorToDouble(mixxx::RgbColor::optional_t color) {
return (color ? static_cast(*color) : kNoTrackColor);
}
-}
+
+} // anonymous namespace
BaseTrackPlayer::BaseTrackPlayer(QObject* pParent, const QString& group)
: BasePlayer(pParent, group) {
diff --git a/src/proto/beats.proto b/src/proto/beats.proto
index 567b7f4e59d..99b83c563a6 100644
--- a/src/proto/beats.proto
+++ b/src/proto/beats.proto
@@ -1,3 +1,4 @@
+// This proto describes the beats information attached to a track.
syntax = "proto2";
package mixxx.track.io;
@@ -10,10 +11,42 @@ enum Source {
USER = 2;
}
-message Beat {
- optional int32 frame_position = 1;
- optional bool enabled = 2 [ default = true ];
- optional Source source = 3 [ default = ANALYZER ];
+enum SectionType {
+ INTRO = 0;
+ OUTRO = 1;
+ OTHER = 2;
+}
+
+// Setting grey as the default color
+message Color {
+ optional int32 red = 1 [ default = 100 ];
+ optional int32 green = 2 [ default = 100 ];
+ optional int32 blue = 3 [ default = 100 ];
+}
+
+message TimeSignature {
+ optional int32 beats_per_bar = 1 [ default = 4];
+ optional int32 note_value = 2 [ default = 4 ];
+}
+
+message TimeSignatureMarker {
+ optional int32 beat_index = 1 [ default = 0 ];
+ optional TimeSignature time_signature = 2;
+}
+
+message PhraseMarker {
+ optional int32 beat_index = 1 [ default = 0 ];
+}
+
+message Section {
+ optional string name = 1 [ default = "UNNAMED"];
+ optional SectionType type = 2 [ default = OTHER ];
+ optional Color color = 3;
+}
+
+message SectionMarker {
+ optional int32 beat_index = 1 [default = 0];
+ optional Section section = 2;
}
message Bpm {
@@ -21,11 +54,31 @@ message Bpm {
optional Source source = 2 [ default = ANALYZER ];
}
-message BeatMap {
+message Beat {
+ optional double frame_position = 1 [ default = 0 ];
+ optional bool enabled = 2 [ default = true ];
+ optional Source source = 3 [ default = ANALYZER ];
+}
+
+message Beats {
repeated Beat beat = 1;
+ repeated TimeSignatureMarker time_signature_markers = 3;
+ repeated PhraseMarker phrase_markers = 4;
+ repeated SectionMarker section_markers = 5;
}
-message BeatGrid {
- optional Bpm bpm = 1;
- optional Beat first_beat = 2;
+// These are legacy data structures for upgrading from Mixxx < 2.4
+message LegacyBeat {
+ optional int32 frame_position = 1;
+ optional bool enabled = 2 [ default = true ];
+ optional Source source = 3 [ default = ANALYZER ];
}
+
+message LegacyBeatMap {
+ repeated LegacyBeat beat = 1;
+}
+
+message LegacyBeatGrid {
+ optional Bpm bpm = 1;
+ optional LegacyBeat first_beat = 2;
+}
\ No newline at end of file
diff --git a/src/skin/tooltips.cpp b/src/skin/tooltips.cpp
index 11f10aa2c74..68f97cb3090 100644
--- a/src/skin/tooltips.cpp
+++ b/src/skin/tooltips.cpp
@@ -205,16 +205,17 @@ void Tooltips::addStandardTooltips() {
<< tr("Adjusts the headphone output gain.")
<< QString("%1: %2").arg(rightClick, resetToDefault);
- add("headMix")
- << tr("Headphone Mix")
- << tr("Crossfades the headphone output between the main mix and cueing (PFL or Pre-Fader Listening) signal.")
- << QString("%1: %2").arg(rightClick, resetToDefault);
-
- add("headSplit")
- << tr("Headphone Split Cue")
- << tr("If activated, the main mix signal plays in the right channel, while the cueing signal "
- "plays in the left channel.")
- << tr("Adjust the Headphone Mix so in the left channel is not the pure cueing signal.");
+ add("headMix") << tr("Headphone Mix")
+ << tr("Crossfades the headphone output between the main mix "
+ "and cueing (PFL or Pre-Fader Listening) signal.")
+ << QString("%1: %2").arg(rightClick, resetToDefault);
+
+ add("headSplit") << tr("Headphone Split Cue")
+ << tr("If activated, the main mix signal plays in the "
+ "right channel, while the cueing signal "
+ "plays in the left channel.")
+ << tr("Adjust the Headphone Mix so in the left channel is "
+ "not the pure cueing signal.");
add("orientation")
<< tr("Crossfader Orientation")
@@ -486,121 +487,137 @@ void Tooltips::addStandardTooltips() {
<< tr("Sends the selected channel's audio to the headphone output, "
"selected in Preferences -> Sound Hardware.");
- add("mute")
- << tr("Mute")
- << tr("Mutes the selected channel's audio in the main output.");
-
- add("master_enable")
- << tr("Main mix enable")
- << tr("Hold or short click for latching to "
- "mix this input into the main output.");
-
- add("back_start")
- << tr("Fast Rewind")
- << QString("%1: %2").arg(leftClick, tr("Fast rewind through the track."))
- << QString("%1: %2").arg(rightClick, tr("Jumps to the beginning of the track."));
-
- add("fwd_end")
- << tr("Fast Forward")
- << QString("%1: %2").arg(leftClick, tr("Fast forward through the track."))
- << QString("%1: %2").arg(rightClick, tr("Jumps to the end of the track."));
-
- // Ghetto-Sync (TM)
- add("beatsync_beatsync_tempo")
- << tr("Old Synchronize")
- << tr("(This skin should be updated to use Master Sync!)")
- << QString("%1: %2").arg(leftClick, tr("Syncs the tempo (BPM) and phase to that of the other track, "
- "if BPM is detected on both."))
- << QString("%1: %2").arg(rightClick, tr("Syncs the tempo (BPM) to that of the other track, "
- "if BPM is detected on both."))
- << tr("Syncs to the first deck (in numerical order) that is playing a track and has a BPM.")
- << tr("If no deck is playing, syncs to the first deck that has a BPM.")
- << tr("Decks can't sync to samplers and samplers can only sync to decks.");
-
- // Awesome-Sync (TM)
- add("sync_enabled")
- << tr("Enable Master Sync")
- << tr("Tap to sync the tempo to other playing tracks or the master clock.")
- << tr("Hold for at least a second to enable sync lock for this deck.")
- << tr("Decks with sync locked will all play at the same tempo, and decks that also have "
- "quantize enabled will always have their beats lined up.");
-
- // TODO(owen): find a better phrase for "the other deck"
- add("sync_reset_key")
- << tr("Sync and Reset Key")
- << QString("%1: %2").arg(leftClick, tr("Sets the pitch to a key that allows a harmonic transition "
- "from the other track. Requires a detected key on both involved decks."))
- << QString("%1: %2").arg(rightClick, tr("Resets the key to the original track key."));
-
- add("sync_master")
- << tr("Enable Sync Clock Master")
- << tr("When enabled, this device will serve as the master clock for all other decks.");
-
- add("rate")
- << tr("Speed Control")
- << tr("Changes the track playback speed (affects both the tempo and the pitch). If keylock is enabled, only the tempo is affected.")
- << QString("%1: %2").arg(rightClick, resetToDefault);
-
- add("pitch")
- << tr("Pitch Control")
- << tr("Changes the track pitch independent of the tempo.")
- << QString("%1: %2").arg(rightClick, resetToDefault);
-
- add("pitch_up")
- << tr("Pitch Control")
- << tr("Changes the track pitch independent of the tempo.")
- << QString("%1: %2").arg(leftClick, tr("Increases the pitch by one semitone."))
- << QString("%1: %2").arg(rightClick, tr("Increases the pitch by 10 cents."));
-
- add("pitch_down")
- << tr("Pitch Control")
- << tr("Changes the track pitch independent of the tempo.")
- << QString("%1: %2").arg(leftClick, tr("Decreases the pitch by one semitone."))
- << QString("%1: %2").arg(rightClick, tr("Decreases the pitch by 10 cents."));
-
- add("pitch_adjust")
- << tr("Pitch Adjust")
- << tr("Adjust the pitch in addition to the speed slider pitch.")
- << QString("%1: %2").arg(rightClick, resetToDefault);
-
-
- add("rate_display")
- << tr("Pitch Rate")
- << tr("Displays the current playback rate of the track.");
-
- add("repeat")
- << tr("Repeat")
- << tr("When active the track will repeat if you go past the end or reverse before the start.");
-
- add("eject")
- << tr("Eject")
- << tr("Ejects track from the player.");
-
- add("hotcue")
- << tr("Hotcue")
- << QString("%1: %2").arg(leftClick, tr("If hotcue is set, jumps to the hotcue."))
- << tr("If hotcue is not set, sets the hotcue to the current play position.")
- << quantizeSnap
- << QString("%1: %2").arg(rightClick, tr("Opens a menu to clear hotcues or edit their labels and colors."));
-
- // Status displays and toggle buttons
- add("toggle_recording")
- << tr("Record Mix")
- << tr("Toggle mix recording.");
-
- // Status displays and toggle buttons
- add("recording_duration")
- << tr("Recording Duration")
- << tr("Displays the duration of the running recording.");
-
- // For legacy reasons also add tooltips for "shoutcast_enabled".
- for (const char* key : {"shoutcast_enabled", "broadcast_enabled"}) {
- add(key)
- << tr("Enable Live Broadcasting")
- << tr("Stream your mix over the Internet.")
- << tr("Provides visual feedback for Live Broadcasting status:")
- << tr("disabled, connecting, connected, failure.");
- }
+ add("mute")
+ << tr("Mute")
+ << tr("Mutes the selected channel's audio in the main output.");
+
+ add("master_enable")
+ << tr("Main mix enable")
+ << tr("Hold or short click for latching to "
+ "mix this input into the main output.");
+
+ add("back_start")
+ << tr("Fast Rewind")
+ << QString("%1: %2").arg(leftClick, tr("Fast rewind through the track."))
+ << QString("%1: %2").arg(rightClick, tr("Jumps to the beginning of the track."));
+
+ add("fwd_end")
+ << tr("Fast Forward")
+ << QString("%1: %2").arg(leftClick, tr("Fast forward through the track."))
+ << QString("%1: %2").arg(rightClick, tr("Jumps to the end of the track."));
+
+ // Ghetto-Sync (TM)
+ add("beatsync_beatsync_tempo")
+ << tr("Old Synchronize")
+ << tr("(This skin should be updated to use Master Sync!)")
+ << QString("%1: %2").arg(leftClick,
+ tr("Syncs the tempo (BPM) and phase to that of the "
+ "other track, "
+ "if BPM is detected on both."))
+ << QString("%1: %2").arg(rightClick,
+ tr("Syncs the tempo (BPM) to that of the other track, "
+ "if BPM is detected on both."))
+ << tr("Syncs to the first deck (in numerical order) that is "
+ "playing a track and has a BPM.")
+ << tr("If no deck is playing, syncs to the first deck that has a "
+ "BPM.")
+ << tr("Decks can't sync to samplers and samplers can only sync "
+ "to decks.");
+
+ // Awesome-Sync (TM)
+ add("sync_enabled")
+ << tr("Enable Master Sync")
+ << tr("Tap to sync the tempo to other playing tracks or the "
+ "master clock.")
+ << tr("Hold for at least a second to enable sync lock for this "
+ "deck.")
+ << tr("Decks with sync locked will all play at the same tempo, "
+ "and decks that also have "
+ "quantize enabled will always have their beats lined up.");
+
+ // TODO(owen): find a better phrase for "the other deck"
+ add("sync_reset_key")
+ << tr("Sync and Reset Key")
+ << QString("%1: %2").arg(leftClick,
+ tr("Sets the pitch to a key that allows a harmonic "
+ "transition "
+ "from the other track. Requires a detected key on "
+ "both involved decks."))
+ << QString("%1: %2").arg(rightClick,
+ tr("Resets the key to the original track key."));
+
+ add("sync_master") << tr("Enable Sync Clock Master")
+ << tr("When enabled, this device will serve as the "
+ "master clock for all other decks.");
+
+ add("rate") << tr("Speed Control")
+ << tr("Changes the track playback speed (affects both the "
+ "tempo and the pitch). If keylock is enabled, only the "
+ "tempo is affected.")
+ << QString("%1: %2").arg(rightClick, resetToDefault);
+
+ add("pitch") << tr("Pitch Control")
+ << tr("Changes the track pitch independent of the tempo.")
+ << QString("%1: %2").arg(rightClick, resetToDefault);
+
+ add("pitch_up") << tr("Pitch Control")
+ << tr("Changes the track pitch independent of the tempo.")
+ << QString("%1: %2").arg(leftClick,
+ tr("Increases the pitch by one semitone."))
+ << QString("%1: %2").arg(rightClick,
+ tr("Increases the pitch by 10 cents."));
+
+ add("pitch_down")
+ << tr("Pitch Control")
+ << tr("Changes the track pitch independent of the tempo.")
+ << QString("%1: %2").arg(
+ leftClick, tr("Decreases the pitch by one semitone."))
+ << QString("%1: %2").arg(
+ rightClick, tr("Decreases the pitch by 10 cents."));
+
+ add("pitch_adjust")
+ << tr("Pitch Adjust")
+ << tr("Adjust the pitch in addition to the speed slider pitch.")
+ << QString("%1: %2").arg(rightClick, resetToDefault);
+
+ add("rate_display")
+ << tr("Pitch Rate")
+ << tr("Displays the current playback rate of the track.");
+
+ add("repeat") << tr("Repeat")
+ << tr("When active the track will repeat if you go past "
+ "the end or reverse before the start.");
+
+ add("eject") << tr("Eject") << tr("Ejects track from the player.");
+
+ add("hotcue") << tr("Hotcue")
+ << QString("%1: %2").arg(leftClick,
+ tr("If hotcue is set, jumps to the hotcue."))
+ << tr("If hotcue is not set, sets the hotcue to the "
+ "current play position.")
+ << quantizeSnap
+ << QString("%1: %2").arg(rightClick,
+ tr("Opens a menu to clear hotcues or edit their "
+ "labels and colors."));
+
+ // Status displays and toggle buttons
+ add("toggle_recording")
+ << tr("Record Mix")
+ << tr("Toggle mix recording.");
+
+ // Status displays and toggle buttons
+ add("recording_duration")
+ << tr("Recording Duration")
+ << tr("Displays the duration of the running recording.");
+
+ // For legacy reasons also add tooltips for "shoutcast_enabled".
+ for (const char* key : {"shoutcast_enabled", "broadcast_enabled"}) {
+ add(key)
+ << tr("Enable Live Broadcasting")
+ << tr("Stream your mix over the Internet.")
+ << tr("Provides visual feedback for Live Broadcasting status:")
+ << tr("disabled, connecting, connected, failure.");
+ }
add("passthrough_enabled")
<< tr("Enable Passthrough")
diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp
deleted file mode 100644
index d8c1f88c485..00000000000
--- a/src/test/beatgridtest.cpp
+++ /dev/null
@@ -1,196 +0,0 @@
-#include
-#include
-
-#include "track/beatgrid.h"
-#include "util/memory.h"
-
-using namespace mixxx;
-
-namespace {
-
-const double kMaxBeatError = 1e-9;
-
-TrackPointer newTrack(int sampleRate) {
- TrackPointer pTrack(Track::newTemporary());
- pTrack->setAudioProperties(
- mixxx::audio::ChannelCount(2),
- mixxx::audio::SampleRate(sampleRate),
- mixxx::audio::Bitrate(),
- mixxx::Duration::fromSeconds(180));
- return pTrack;
-}
-
-TEST(BeatGridTest, Scale) {
- int sampleRate = 44100;
- TrackPointer pTrack = newTrack(sampleRate);
-
- double bpm = 60.0;
- pTrack->setBpm(bpm);
-
- auto pGrid = std::make_unique(*pTrack, 0);
- pGrid->setBpm(bpm);
-
- EXPECT_DOUBLE_EQ(bpm, pGrid->getBpm());
- pGrid->scale(Beats::DOUBLE);
- EXPECT_DOUBLE_EQ(2 * bpm, pGrid->getBpm());
-
- pGrid->scale(Beats::HALVE);
- EXPECT_DOUBLE_EQ(bpm, pGrid->getBpm());
-
- pGrid->scale(Beats::TWOTHIRDS);
- EXPECT_DOUBLE_EQ(bpm * 2 / 3, pGrid->getBpm());
-
- pGrid->scale(Beats::THREEHALVES);
- EXPECT_DOUBLE_EQ(bpm, pGrid->getBpm());
-
- pGrid->scale(Beats::THREEFOURTHS);
- EXPECT_DOUBLE_EQ(bpm * 3 / 4, pGrid->getBpm());
-
- pGrid->scale(Beats::FOURTHIRDS);
- EXPECT_DOUBLE_EQ(bpm, pGrid->getBpm());
-}
-
-TEST(BeatGridTest, TestNthBeatWhenOnBeat) {
- int sampleRate = 44100;
- TrackPointer pTrack = newTrack(sampleRate);
-
- double bpm = 60.1;
- const int kFrameSize = 2;
- pTrack->setBpm(bpm);
- double beatLength = (60.0 * sampleRate / bpm) * kFrameSize;
-
- auto pGrid = std::make_unique(*pTrack, 0);
- pGrid->setBpm(bpm);
- // Pretend we're on the 20th beat;
- double position = beatLength * 20;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pGrid->findNthBeat(position, 0));
-
- // findNthBeat should return exactly the current beat if we ask for 1 or
- // -1. For all other values, it should return n times the beat length.
- for (int i = 1; i < 20; ++i) {
- EXPECT_NEAR(position + beatLength * (i - 1), pGrid->findNthBeat(position, i), kMaxBeatError);
- EXPECT_NEAR(position + beatLength * (-i + 1), pGrid->findNthBeat(position, -i), kMaxBeatError);
- }
-
- // Also test prev/next beat calculation.
- double prevBeat, nextBeat;
- pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_NEAR(position, prevBeat, kMaxBeatError);
- EXPECT_NEAR(position + beatLength, nextBeat, kMaxBeatError);
-
- // Both previous and next beat should return the current position.
- EXPECT_NEAR(position, pGrid->findNextBeat(position), kMaxBeatError);
- EXPECT_NEAR(position, pGrid->findPrevBeat(position), kMaxBeatError);
-}
-
-TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) {
- int sampleRate = 44100;
- TrackPointer pTrack = newTrack(sampleRate);
-
- double bpm = 60.1;
- const int kFrameSize = 2;
- pTrack->setBpm(bpm);
- double beatLength = (60.0 * sampleRate / bpm) * kFrameSize;
-
- auto pGrid = std::make_unique(*pTrack, 0);
- pGrid->setBpm(bpm);
-
- // Pretend we're just before the 20th beat.
- const double kClosestBeat = 20 * beatLength;
- double position = kClosestBeat - beatLength * 0.005;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pGrid->findNthBeat(position, 0));
-
- // findNthBeat should return exactly the current beat if we ask for 1 or
- // -1. For all other values, it should return n times the beat length.
- for (int i = 1; i < 20; ++i) {
- EXPECT_NEAR(kClosestBeat + beatLength * (i - 1), pGrid->findNthBeat(position, i), kMaxBeatError);
- EXPECT_NEAR(kClosestBeat + beatLength * (-i + 1), pGrid->findNthBeat(position, -i), kMaxBeatError);
- }
-
- // Also test prev/next beat calculation.
- double prevBeat, nextBeat;
- pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError);
- EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError);
-
- // Both previous and next beat should return the closest beat.
- EXPECT_NEAR(kClosestBeat, pGrid->findNextBeat(position), kMaxBeatError);
- EXPECT_NEAR(kClosestBeat, pGrid->findPrevBeat(position), kMaxBeatError);
-}
-
-TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) {
- int sampleRate = 44100;
- TrackPointer pTrack = newTrack(sampleRate);
-
- double bpm = 60.1;
- const int kFrameSize = 2;
- pTrack->setBpm(bpm);
- double beatLength = (60.0 * sampleRate / bpm) * kFrameSize;
-
- auto pGrid = std::make_unique(*pTrack, 0);
- pGrid->setBpm(bpm);
-
- // Pretend we're just before the 20th beat.
- const double kClosestBeat = 20 * beatLength;
- double position = kClosestBeat + beatLength * 0.005;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pGrid->findNthBeat(position, 0));
-
- // findNthBeat should return exactly the current beat if we ask for 1 or
- // -1. For all other values, it should return n times the beat length.
- for (int i = 1; i < 20; ++i) {
- EXPECT_NEAR(kClosestBeat + beatLength * (i - 1), pGrid->findNthBeat(position, i), kMaxBeatError);
- EXPECT_NEAR(kClosestBeat + beatLength * (-i + 1), pGrid->findNthBeat(position, -i), kMaxBeatError);
- }
-
- // Also test prev/next beat calculation.
- double prevBeat, nextBeat;
- pGrid->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_NEAR(kClosestBeat, prevBeat, kMaxBeatError);
- EXPECT_NEAR(kClosestBeat + beatLength, nextBeat, kMaxBeatError);
-
- // Both previous and next beat should return the closest beat.
- EXPECT_NEAR(kClosestBeat, pGrid->findNextBeat(position), kMaxBeatError);
- EXPECT_NEAR(kClosestBeat, pGrid->findPrevBeat(position), kMaxBeatError);
-}
-
-TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) {
- int sampleRate = 44100;
- TrackPointer pTrack = newTrack(sampleRate);
-
- double bpm = 60.1;
- const int kFrameSize = 2;
- pTrack->setBpm(bpm);
- double beatLength = (60.0 * sampleRate / bpm) * kFrameSize;
-
- auto pGrid = std::make_unique(*pTrack, 0);
- pGrid->setBpm(bpm);
-
- // Pretend we're half way between the 20th and 21st beat
- double previousBeat = beatLength * 20.0;
- double nextBeat = beatLength * 21.0;
- double position = (nextBeat + previousBeat) / 2.0;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pGrid->findNthBeat(position, 0));
-
- // findNthBeat should return multiples of beats starting from the next or
- // previous beat, depending on whether N is positive or negative.
- for (int i = 1; i < 20; ++i) {
- EXPECT_NEAR(nextBeat + beatLength*(i-1), pGrid->findNthBeat(position, i), kMaxBeatError);
- EXPECT_NEAR(previousBeat + beatLength*(-i+1), pGrid->findNthBeat(position, -i), kMaxBeatError);
- }
-
- // Also test prev/next beat calculation
- double foundPrevBeat, foundNextBeat;
- pGrid->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat);
- EXPECT_NEAR(previousBeat, foundPrevBeat, kMaxBeatError);
- EXPECT_NEAR(nextBeat, foundNextBeat, kMaxBeatError);
-}
-
-} // namespace
diff --git a/src/test/beatmaptest.cpp b/src/test/beatmaptest.cpp
deleted file mode 100644
index 4692fc191de..00000000000
--- a/src/test/beatmaptest.cpp
+++ /dev/null
@@ -1,296 +0,0 @@
-#include
-#include
-
-#include "track/beatmap.h"
-#include "util/memory.h"
-
-using namespace mixxx;
-
-namespace {
-
-class BeatMapTest : public testing::Test {
- protected:
-
- BeatMapTest()
- : m_pTrack(Track::newTemporary()),
- m_iSampleRate(100),
- m_iFrameSize(2) {
- m_pTrack->setAudioProperties(
- mixxx::audio::ChannelCount(2),
- mixxx::audio::SampleRate(m_iSampleRate),
- mixxx::audio::Bitrate(),
- mixxx::Duration::fromSeconds(180));
- }
-
- double getBeatLengthFrames(double bpm) {
- return (60.0 * m_iSampleRate / bpm);
- }
-
- double getBeatLengthSamples(double bpm) {
- return getBeatLengthFrames(bpm) * m_iFrameSize;
- }
-
- QVector createBeatVector(double first_beat,
- unsigned int num_beats,
- double beat_length) {
- QVector beats;
- for (unsigned int i = 0; i < num_beats; ++i) {
- beats.append(first_beat + i * beat_length);
- }
- return beats;
- }
-
- TrackPointer m_pTrack;
- int m_iSampleRate;
- int m_iFrameSize;
-};
-
-TEST_F(BeatMapTest, Scale) {
- const double bpm = 60.0;
- m_pTrack->setBpm(bpm);
- double beatLengthFrames = getBeatLengthFrames(bpm);
- double startOffsetFrames = 7;
- const int numBeats = 100;
- // Note beats must be in frames, not samples.
- QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pMap = std::make_unique(*m_pTrack, 0, beats);
-
- EXPECT_DOUBLE_EQ(bpm, pMap->getBpm());
- pMap->scale(Beats::DOUBLE);
- EXPECT_DOUBLE_EQ(2 * bpm, pMap->getBpm());
-
- pMap->scale(Beats::HALVE);
- EXPECT_DOUBLE_EQ(bpm, pMap->getBpm());
-
- pMap->scale(Beats::TWOTHIRDS);
- EXPECT_DOUBLE_EQ(bpm * 2 / 3, pMap->getBpm());
-
- pMap->scale(Beats::THREEHALVES);
- EXPECT_DOUBLE_EQ(bpm, pMap->getBpm());
-
- pMap->scale(Beats::THREEFOURTHS);
- EXPECT_DOUBLE_EQ(bpm * 3 / 4, pMap->getBpm());
-
- pMap->scale(Beats::FOURTHIRDS);
- EXPECT_DOUBLE_EQ(bpm, pMap->getBpm());
-}
-
-TEST_F(BeatMapTest, TestNthBeat) {
- const double bpm = 60.0;
- m_pTrack->setBpm(bpm);
- double beatLengthFrames = getBeatLengthFrames(bpm);
- double startOffsetFrames = 7;
- double beatLengthSamples = getBeatLengthSamples(bpm);
- double startOffsetSamples = startOffsetFrames * 2;
- const int numBeats = 100;
- // Note beats must be in frames, not samples.
- QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pMap = std::make_unique(*m_pTrack, 0, beats);
-
- // Check edge cases
- double firstBeat = startOffsetSamples + beatLengthSamples * 0;
- double lastBeat = startOffsetSamples + beatLengthSamples * (numBeats - 1);
- EXPECT_EQ(lastBeat, pMap->findNthBeat(lastBeat, 1));
- EXPECT_EQ(lastBeat, pMap->findNextBeat(lastBeat));
- EXPECT_EQ(-1, pMap->findNthBeat(lastBeat, 2));
- EXPECT_EQ(firstBeat, pMap->findNthBeat(firstBeat, -1));
- EXPECT_EQ(firstBeat, pMap->findPrevBeat(firstBeat));
- EXPECT_EQ(-1, pMap->findNthBeat(firstBeat, -2));
-
- double prevBeat, nextBeat;
- pMap->findPrevNextBeats(lastBeat, &prevBeat, &nextBeat);
- EXPECT_EQ(lastBeat, prevBeat);
- EXPECT_EQ(-1, nextBeat);
-
- pMap->findPrevNextBeats(firstBeat, &prevBeat, &nextBeat);
- EXPECT_EQ(firstBeat, prevBeat);
- EXPECT_EQ(firstBeat + beatLengthSamples, nextBeat);
-}
-
-TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) {
- const double bpm = 60.0;
- m_pTrack->setBpm(bpm);
- double beatLengthFrames = getBeatLengthFrames(bpm);
- double startOffsetFrames = 7;
- double beatLengthSamples = getBeatLengthSamples(bpm);
- double startOffsetSamples = startOffsetFrames * 2;
- const int numBeats = 100;
- // Note beats must be in frames, not samples.
- QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pMap = std::make_unique(*m_pTrack, 0, beats);
-
- // Pretend we're on the 20th beat;
- const int curBeat = 20;
- double position = startOffsetSamples + beatLengthSamples * curBeat;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pMap->findNthBeat(position, 0));
-
- // findNthBeat should return exactly the current beat if we ask for 1 or
- // -1. For all other values, it should return n times the beat length.
- for (int i = 1; i < curBeat; ++i) {
- EXPECT_DOUBLE_EQ(position + beatLengthSamples*(i-1), pMap->findNthBeat(position, i));
- EXPECT_DOUBLE_EQ(position + beatLengthSamples*(-i+1), pMap->findNthBeat(position, -i));
- }
-
- // Also test prev/next beat calculation.
- double prevBeat, nextBeat;
- pMap->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_EQ(position, prevBeat);
- EXPECT_EQ(position + beatLengthSamples, nextBeat);
-
- // Both previous and next beat should return the current position.
- EXPECT_EQ(position, pMap->findNextBeat(position));
- EXPECT_EQ(position, pMap->findPrevBeat(position));
-}
-
-TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) {
- const double bpm = 60.0;
- m_pTrack->setBpm(bpm);
- double beatLengthFrames = getBeatLengthFrames(bpm);
- double startOffsetFrames = 7;
- double beatLengthSamples = getBeatLengthSamples(bpm);
- double startOffsetSamples = startOffsetFrames * 2;
- const int numBeats = 100;
- // Note beats must be in frames, not samples.
- QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pMap = std::make_unique(*m_pTrack, 0, beats);
-
- // Pretend we're just before the 20th beat;
- const int curBeat = 20;
- const double kClosestBeat = startOffsetSamples + curBeat * beatLengthSamples;
- double position = kClosestBeat - beatLengthSamples * 0.005;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pMap->findNthBeat(position, 0));
-
- // findNthBeat should return exactly the current beat if we ask for 1 or
- // -1. For all other values, it should return n times the beat length.
- for (int i = 1; i < curBeat; ++i) {
- EXPECT_DOUBLE_EQ(kClosestBeat + beatLengthSamples*(i-1), pMap->findNthBeat(position, i));
- EXPECT_DOUBLE_EQ(kClosestBeat + beatLengthSamples*(-i+1), pMap->findNthBeat(position, -i));
- }
-
- // Also test prev/next beat calculation
- double prevBeat, nextBeat;
- pMap->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_EQ(kClosestBeat, prevBeat);
- EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat);
-
- // Both previous and next beat should return the closest beat.
- EXPECT_EQ(kClosestBeat, pMap->findNextBeat(position));
- EXPECT_EQ(kClosestBeat, pMap->findPrevBeat(position));
-
-}
-
-TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) {
- const double bpm = 60.0;
- m_pTrack->setBpm(bpm);
- double beatLengthFrames = getBeatLengthFrames(bpm);
- double startOffsetFrames = 7;
- double beatLengthSamples = getBeatLengthSamples(bpm);
- double startOffsetSamples = startOffsetFrames * 2;
- const int numBeats = 100;
- // Note beats must be in frames, not samples.
- QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pMap = std::make_unique(*m_pTrack, 0, beats);
-
- // Pretend we're just after the 20th beat;
- const int curBeat = 20;
- const double kClosestBeat = startOffsetSamples + curBeat * beatLengthSamples;
- double position = kClosestBeat + beatLengthSamples * 0.005;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pMap->findNthBeat(position, 0));
-
- EXPECT_EQ(kClosestBeat, pMap->findClosestBeat(position));
-
- // findNthBeat should return exactly the current beat if we ask for 1 or
- // -1. For all other values, it should return n times the beat length.
- for (int i = 1; i < curBeat; ++i) {
- EXPECT_DOUBLE_EQ(kClosestBeat + beatLengthSamples*(i-1), pMap->findNthBeat(position, i));
- EXPECT_DOUBLE_EQ(kClosestBeat + beatLengthSamples*(-i+1), pMap->findNthBeat(position, -i));
- }
-
- // Also test prev/next beat calculation.
- double prevBeat, nextBeat;
- pMap->findPrevNextBeats(position, &prevBeat, &nextBeat);
- EXPECT_EQ(kClosestBeat, prevBeat);
- EXPECT_EQ(kClosestBeat + beatLengthSamples, nextBeat);
-
- // Both previous and next beat should return the closest beat.
- EXPECT_EQ(kClosestBeat, pMap->findNextBeat(position));
- EXPECT_EQ(kClosestBeat, pMap->findPrevBeat(position));
-}
-
-TEST_F(BeatMapTest, TestNthBeatWhenNotOnBeat) {
- const double bpm = 60.0;
- m_pTrack->setBpm(bpm);
- double beatLengthFrames = getBeatLengthFrames(bpm);
- double startOffsetFrames = 7;
- double beatLengthSamples = getBeatLengthSamples(bpm);
- double startOffsetSamples = startOffsetFrames * 2;
- const int numBeats = 100;
- // Note beats must be in frames, not samples.
- QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pMap = std::make_unique(*m_pTrack, 0, beats);
-
- // Pretend we're half way between the 20th and 21st beat
- double previousBeat = startOffsetSamples + beatLengthSamples * 20.0;
- double nextBeat = startOffsetSamples + beatLengthSamples * 21.0;
- double position = (nextBeat + previousBeat) / 2.0;
-
- // The spec dictates that a value of 0 is always invalid and returns -1
- EXPECT_EQ(-1, pMap->findNthBeat(position, 0));
-
- // findNthBeat should return multiples of beats starting from the next or
- // previous beat, depending on whether N is positive or negative.
- for (int i = 1; i < 20; ++i) {
- EXPECT_DOUBLE_EQ(nextBeat + beatLengthSamples*(i-1),
- pMap->findNthBeat(position, i));
- EXPECT_DOUBLE_EQ(previousBeat - beatLengthSamples*(i-1),
- pMap->findNthBeat(position, -i));
- }
-
- // Also test prev/next beat calculation
- double foundPrevBeat, foundNextBeat;
- pMap->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat);
- EXPECT_EQ(previousBeat, foundPrevBeat);
- EXPECT_EQ(nextBeat, foundNextBeat);
-}
-
-TEST_F(BeatMapTest, TestBpmAround) {
- const double filebpm = 60.0;
- double approx_beat_length = getBeatLengthSamples(filebpm);
- m_pTrack->setBpm(filebpm);
- const int numBeats = 64;
-
- QVector beats;
- double beat_pos = 0;
- for (unsigned int i = 0, bpm=60; i < numBeats; ++i, ++bpm) {
- double beat_length = getBeatLengthFrames(bpm);
- beats.append(beat_pos);
- beat_pos += beat_length;
- }
-
- auto pMap = std::make_unique(*m_pTrack, 0, beats);
-
- // The average of the first 8 beats should be different than the average
- // of the last 8 beats.
- EXPECT_DOUBLE_EQ(64.024390243902445,
- pMap->getBpmAroundPosition(4 * approx_beat_length, 4));
- EXPECT_DOUBLE_EQ(118.98016997167139,
- pMap->getBpmAroundPosition(60 * approx_beat_length, 4));
- // Also test at the beginning and end of the track
- EXPECT_DOUBLE_EQ(62.968515742128936,
- pMap->getBpmAroundPosition(0, 4));
- EXPECT_DOUBLE_EQ(118.98016997167139,
- pMap->getBpmAroundPosition(65 * approx_beat_length, 4));
-
- // Try a really, really short track
- beats = createBeatVector(10, 3, getBeatLengthFrames(filebpm));
- pMap = std::make_unique(*m_pTrack, 0, beats);
- EXPECT_DOUBLE_EQ(filebpm, pMap->getBpmAroundPosition(1 * approx_beat_length, 4));
-}
-
-} // namespace
diff --git a/src/test/beatstest.cpp b/src/test/beatstest.cpp
new file mode 100644
index 00000000000..e9b67d84ea7
--- /dev/null
+++ b/src/test/beatstest.cpp
@@ -0,0 +1,409 @@
+#include
+
+#include
+
+#include "track/beats.h"
+#include "track/track.h"
+#include "util/memory.h"
+
+using namespace mixxx;
+
+namespace {
+
+class BeatsTest : public testing::Test {
+ protected:
+ BeatsTest()
+ : m_pTrack(Track::newTemporary()),
+ m_iChannelCount(2),
+ m_iSampleRate(100),
+ m_pBeats1(new Beats(m_pTrack.get())),
+ m_pBeats2(new Beats(m_pTrack.get())),
+ m_bpm(60),
+ m_startOffsetFrames(7) {
+ m_pTrack->setAudioProperties(
+ mixxx::audio::ChannelCount(m_iChannelCount),
+ mixxx::audio::SampleRate(m_iSampleRate),
+ mixxx::audio::Bitrate(),
+ mixxx::Duration::fromSeconds(180));
+ m_pBeats1->setGrid(m_bpm, m_startOffsetFrames);
+ m_pBeats2->setGrid(m_bpm, m_startOffsetFrames);
+ }
+
+ ~BeatsTest() {
+ }
+
+ FrameDiff_t getBeatLengthFrames(Bpm bpm) const {
+ if (bpm == Bpm()) {
+ DEBUG_ASSERT(false);
+ return 0;
+ }
+ return 60.0 * m_iSampleRate / bpm.getValue();
+ }
+
+ QVector createBeatVector(FramePos first_beat,
+ unsigned int num_beats,
+ FrameDiff_t beat_length) {
+ QVector beats;
+ for (unsigned int i = 0; i < num_beats; ++i) {
+ beats.append(first_beat + beat_length * i);
+ }
+ return beats;
+ }
+
+ TrackPointer m_pTrack;
+ const int m_iChannelCount;
+ // Sample Rate is a standard unit
+ const SINT m_iSampleRate;
+ // We internally use frames per second since a frame position
+ // actually matters when calculating beats.
+ BeatsPointer m_pBeats1;
+ BeatsPointer m_pBeats2;
+ const Bpm m_bpm;
+ const FramePos m_startOffsetFrames;
+};
+
+TEST_F(BeatsTest, Scale) {
+ // Initially must be the base value
+ EXPECT_EQ(m_bpm, m_pBeats1->getBpm());
+
+ m_pBeats1->scale(BeatsInternal::DOUBLE);
+ EXPECT_EQ(m_bpm * 2, m_pBeats1->getBpm());
+
+ m_pBeats1->scale(BeatsInternal::HALVE);
+ EXPECT_EQ(m_bpm, m_pBeats1->getBpm());
+
+ m_pBeats1->scale(BeatsInternal::TWOTHIRDS);
+ EXPECT_EQ(m_bpm * 2 / 3, m_pBeats1->getBpm());
+
+ m_pBeats1->scale(BeatsInternal::THREEHALVES);
+ EXPECT_EQ(m_bpm, m_pBeats1->getBpm());
+
+ m_pBeats1->scale(BeatsInternal::THREEFOURTHS);
+ EXPECT_EQ(m_bpm * 3 / 4, m_pBeats1->getBpm());
+
+ m_pBeats1->scale(BeatsInternal::FOURTHIRDS);
+ EXPECT_EQ(m_bpm, m_pBeats1->getBpm());
+}
+
+TEST_F(BeatsTest, NthBeat) {
+ // Check edge cases
+ EXPECT_EQ(m_pBeats1->getLastBeatPosition(),
+ m_pBeats1->findNthBeat(m_pBeats1->getLastBeatPosition(), 1));
+ EXPECT_EQ(m_pBeats1->getLastBeatPosition(),
+ m_pBeats1->findNextBeat(m_pBeats1->getLastBeatPosition()));
+ EXPECT_EQ(kInvalidFramePos,
+ m_pBeats1->findNthBeat(m_pBeats1->getLastBeatPosition(), 2));
+ EXPECT_EQ(m_pBeats1->getFirstBeatPosition(),
+ m_pBeats1->findNthBeat(m_pBeats1->getFirstBeatPosition(), -1));
+ EXPECT_EQ(m_pBeats1->getFirstBeatPosition(),
+ m_pBeats1->findPrevBeat(m_pBeats1->getFirstBeatPosition()));
+ EXPECT_EQ(kInvalidFramePos,
+ m_pBeats1->findNthBeat(m_pBeats1->getFirstBeatPosition(), -2));
+
+ // TODO(JVC) Add some tests in the middle
+}
+
+TEST_F(BeatsTest, PrevNextBeats) {
+ FramePos prevBeat, nextBeat;
+
+ m_pBeats1->findPrevNextBeats(
+ m_pBeats1->getLastBeatPosition(), &prevBeat, &nextBeat);
+ EXPECT_DOUBLE_EQ(
+ m_pBeats1->getLastBeatPosition().getValue(), prevBeat.getValue());
+ EXPECT_EQ(kInvalidFramePos, nextBeat);
+
+ m_pBeats1->findPrevNextBeats(
+ m_pBeats1->getFirstBeatPosition(), &prevBeat, &nextBeat);
+ EXPECT_DOUBLE_EQ(
+ m_pBeats1->getFirstBeatPosition().getValue(), prevBeat.getValue());
+ EXPECT_DOUBLE_EQ(
+ (m_pBeats1->getFirstBeatPosition() + getBeatLengthFrames(m_bpm))
+ .getValue(),
+ nextBeat.getValue());
+
+ // TODO(JVC) Add some tests in the middle
+}
+
+TEST_F(BeatsTest, NthBeatWhenOnBeat) {
+ // Pretend we're on the 20th beat;
+ const int curBeat = 20;
+ FramePos position = m_startOffsetFrames + getBeatLengthFrames(m_bpm) * curBeat;
+
+ // The spec dictates that a value of 0 is always invalid
+ EXPECT_EQ(kInvalidFramePos, m_pBeats1->findNthBeat(position, 0));
+
+ // findNthBeat should return exactly the current beat if we ask for 1 or
+ // -1. For all other values, it should return n times the beat length.
+ for (int i = 1; i < curBeat; ++i) {
+ EXPECT_DOUBLE_EQ(
+ (position + getBeatLengthFrames(m_bpm) * (i - 1)).getValue(),
+ m_pBeats1->findNthBeat(position, i).getValue());
+ EXPECT_DOUBLE_EQ(
+ (position + getBeatLengthFrames(m_bpm) * (-i + 1)).getValue(),
+ m_pBeats1->findNthBeat(position, -i).getValue());
+ }
+
+ // Also test prev/next beat calculation.
+ FramePos prevBeat, nextBeat;
+ m_pBeats1->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ EXPECT_EQ(position, prevBeat);
+ EXPECT_EQ(position + getBeatLengthFrames(m_bpm), nextBeat);
+
+ // Both previous and next beat should return the current position.
+ EXPECT_EQ(position, m_pBeats1->findNextBeat(position));
+ EXPECT_EQ(position, m_pBeats1->findPrevBeat(position));
+}
+
+TEST_F(BeatsTest, NthBeatWhenOnBeat_BeforeEpsilon) {
+ // Pretend we're just before the 20th beat;
+ const int curBeat = 20;
+ const FramePos kClosestBeat = m_startOffsetFrames + curBeat * getBeatLengthFrames(m_bpm);
+ FramePos position = kClosestBeat - getBeatLengthFrames(m_bpm) * 0.005;
+
+ // The spec dictates that a value of 0 is always invalid and returns -1
+ EXPECT_EQ(kInvalidFramePos, m_pBeats1->findNthBeat(position, 0));
+
+ // findNthBeat should return exactly the current beat if we ask for 1 or
+ // -1. For all other values, it should return n times the beat length.
+ for (int i = 1; i < curBeat; ++i) {
+ EXPECT_DOUBLE_EQ((kClosestBeat + getBeatLengthFrames(m_bpm) * (i - 1)).getValue(),
+ m_pBeats1->findNthBeat(position, i).getValue());
+ EXPECT_DOUBLE_EQ((kClosestBeat + getBeatLengthFrames(m_bpm) * (-i + 1)).getValue(),
+ m_pBeats1->findNthBeat(position, -i).getValue());
+ }
+
+ // Also test prev/next beat calculation
+ FramePos prevBeat, nextBeat;
+ m_pBeats1->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ EXPECT_EQ(kClosestBeat, prevBeat);
+ EXPECT_EQ(kClosestBeat + getBeatLengthFrames(m_bpm), nextBeat);
+
+ // Both previous and next beat should return the closest beat.
+ EXPECT_EQ(kClosestBeat, m_pBeats1->findNextBeat(position));
+ EXPECT_EQ(kClosestBeat, m_pBeats1->findPrevBeat(position));
+}
+
+TEST_F(BeatsTest, NthBeatWhenOnBeat_AfterEpsilon) {
+ // Pretend we're just after the 20th beat;
+ const int curBeat = 20;
+ const FramePos kClosestBeat = m_startOffsetFrames + curBeat * getBeatLengthFrames(m_bpm);
+ FramePos position =
+ kClosestBeat + getBeatLengthFrames(m_bpm) * 0.005;
+
+ // The spec dictates that a value of 0 is always invalid
+ EXPECT_EQ(kInvalidFramePos, m_pBeats1->findNthBeat(position, 0));
+
+ EXPECT_EQ(kClosestBeat, m_pBeats1->findClosestBeat(position));
+
+ // findNthBeat should return exactly the current beat if we ask for 1 or
+ // -1. For all other values, it should return n times the beat length.
+ for (int i = 1; i < curBeat; ++i) {
+ EXPECT_DOUBLE_EQ(
+ (kClosestBeat + getBeatLengthFrames(m_bpm) * (i - 1)).getValue(),
+ m_pBeats1->findNthBeat(position, i).getValue());
+ EXPECT_DOUBLE_EQ(
+ (kClosestBeat + getBeatLengthFrames(m_bpm) * (-i + 1)).getValue(),
+ m_pBeats1->findNthBeat(position, -i).getValue());
+ }
+
+ // Also test prev/next beat calculation.
+ FramePos prevBeat, nextBeat;
+ m_pBeats1->findPrevNextBeats(position, &prevBeat, &nextBeat);
+ EXPECT_EQ(kClosestBeat, prevBeat);
+ EXPECT_EQ(kClosestBeat + getBeatLengthFrames(m_bpm), nextBeat);
+
+ // Both previous and next beat should return the closest beat.
+ EXPECT_EQ(kClosestBeat, m_pBeats1->findNextBeat(position));
+ EXPECT_EQ(kClosestBeat, m_pBeats1->findPrevBeat(position));
+}
+
+TEST_F(BeatsTest, NthBeatWhenNotOnBeat) {
+ // Pretend we're half way between the 20th and 21st beat
+ FramePos previousBeat =
+ m_startOffsetFrames + getBeatLengthFrames(m_bpm) * 20.0;
+ FramePos nextBeat =
+ m_startOffsetFrames + getBeatLengthFrames(m_bpm) * 21.0;
+ FramePos position = FramePos((previousBeat.getValue() + nextBeat.getValue()) / 2);
+
+ // The spec dictates that a value of 0 is always invalid
+ EXPECT_EQ(kInvalidFramePos, m_pBeats1->findNthBeat(position, 0));
+
+ // findNthBeat should return multiples of beats starting from the next or
+ // previous beat, depending on whether N is positive or negative.
+ for (int i = 1; i < 20; ++i) {
+ EXPECT_DOUBLE_EQ(
+ (nextBeat + getBeatLengthFrames(m_bpm) * (i - 1))
+ .getValue(),
+ m_pBeats1->findNthBeat(position, i).getValue());
+ EXPECT_DOUBLE_EQ(
+ (previousBeat - getBeatLengthFrames(m_bpm) * (i - 1))
+ .getValue(),
+ m_pBeats1->findNthBeat(position, -i).getValue());
+ }
+
+ // Also test prev/next beat calculation
+ FramePos foundPrevBeat, foundNextBeat;
+ m_pBeats1->findPrevNextBeats(position, &foundPrevBeat, &foundNextBeat);
+ EXPECT_EQ(previousBeat, foundPrevBeat);
+ EXPECT_EQ(nextBeat, foundNextBeat);
+}
+
+TEST_F(BeatsTest, BpmAround) {
+ const FrameDiff_t approxBeatLengthFrames = getBeatLengthFrames(m_bpm);
+ const int numBeats = 64;
+
+ // Constant BPM, constructed in BeatsTest
+ for (unsigned int i = 0; i < 100; i++) {
+ EXPECT_EQ(Bpm(60), m_pBeats1->getBpmAroundPosition(FramePos(i), 5));
+ }
+
+ // Prepare a new Beats to test the behavior for variable BPM
+ QVector beats;
+ FramePos beat_pos;
+ Bpm bpm(60);
+ for (unsigned int i = 0; i < numBeats; ++i, bpm = bpm + 1) {
+ FrameDiff_t beat_length = getBeatLengthFrames(bpm);
+ beats.append(beat_pos);
+ beat_pos += beat_length;
+ }
+ BeatsPointer pMap =
+ std::make_unique(m_pTrack.get(), beats);
+
+ // The average of the first 8 beats should be different than the average
+ // of the last 8 beats.
+ EXPECT_DOUBLE_EQ(63.937454161267674,
+ pMap->getBpmAroundPosition(FramePos(0) + approxBeatLengthFrames * 4, 4).getValue());
+ EXPECT_DOUBLE_EQ(118.96637943082918,
+ pMap->getBpmAroundPosition(FramePos(0) + approxBeatLengthFrames * 60, 4).getValue());
+ // Also test at the beginning and end of the track
+ EXPECT_DOUBLE_EQ(62.936459878052396,
+ pMap->getBpmAroundPosition(FramePos(0), 4).getValue());
+ EXPECT_DOUBLE_EQ(118.96637943082918,
+ pMap->getBpmAroundPosition(FramePos(0) + approxBeatLengthFrames * 65, 4).getValue());
+
+ // Try a really, really short track
+ beats = createBeatVector(FramePos(10), 3, getBeatLengthFrames(m_bpm));
+ BeatsPointer pBeats =
+ std::make_unique(m_pTrack.get(), beats);
+ EXPECT_DOUBLE_EQ(m_bpm.getValue(),
+ pBeats->getBpmAroundPosition(FramePos(0) + approxBeatLengthFrames * 1, 4).getValue());
+}
+
+TEST_F(BeatsTest, Signature) {
+ // Undefined time signature must be default
+ EXPECT_EQ(m_pBeats1->getSignature(0),
+ TimeSignature())
+ << "If no Time Signature defined, it must be default(4/4)";
+
+ const auto timeSignatureInitial = TimeSignature(3, 4);
+ const auto timeSignatureIntermediate = TimeSignature(4, 4);
+ const auto timeSignatureLater = TimeSignature(5, 4);
+
+ // Add time signature to the beginning
+ m_pBeats1->setSignature(timeSignatureInitial, 0);
+ int firstSwitchBeatIndex = 3 * 4;
+ int secondSwitchBeatIndex = firstSwitchBeatIndex + 4 * 4;
+
+ // Add time signature in beats not at the beginning
+ m_pBeats1->setSignature(timeSignatureIntermediate, firstSwitchBeatIndex);
+ m_pBeats1->setSignature(timeSignatureLater, secondSwitchBeatIndex);
+
+ EXPECT_EQ(m_pBeats1->getSignature(0),
+ timeSignatureInitial)
+ << "Starting Time Signature must be"
+ << "3/4";
+ EXPECT_EQ(m_pBeats1->getSignature(firstSwitchBeatIndex / 2),
+ TimeSignature(3, 4))
+ << "Time Signature at " << firstSwitchBeatIndex / 2 << " must be"
+ << "3/4";
+ EXPECT_EQ(m_pBeats1->getSignature(firstSwitchBeatIndex),
+ timeSignatureIntermediate)
+ << "Time Signature at " << firstSwitchBeatIndex << " must be"
+ << "4/4";
+ EXPECT_EQ(m_pBeats1->getSignature(secondSwitchBeatIndex),
+ timeSignatureLater)
+ << "Time Signature at " << secondSwitchBeatIndex << " must be"
+ << "5/4";
+
+ // Add a signature past the end of the track, must have no effect, and check
+ // TODO(hacksdump): Implement this check in main source.
+}
+
+TEST_F(BeatsTest, Iterator) {
+ FramePos pos;
+
+ // Full Beatsbeat
+ auto iter1 = m_pBeats1->findBeats(m_pBeats1->getFirstBeatPosition(),
+ m_pBeats1->getLastBeatPosition());
+ EXPECT_DOUBLE_EQ(iter1->next().frame_position(), m_pBeats1->getFirstBeatPosition().getValue());
+ while (iter1->hasNext()) {
+ auto beat = iter1->next();
+ pos = FramePos(beat.frame_position());
+ EXPECT_TRUE(pos.getValue());
+ }
+ EXPECT_DOUBLE_EQ(pos.getValue(), m_pBeats1->getLastBeatPosition().getValue());
+
+ // Past end
+ auto iter2 = m_pBeats1->findBeats(m_pBeats1->getFirstBeatPosition(),
+ FramePos(m_pBeats1->getLastBeatPosition().getValue() + 10000000000));
+ while (iter2->hasNext()) {
+ auto beat = iter2->next();
+ pos = FramePos(beat.frame_position());
+ EXPECT_TRUE(pos.getValue());
+ }
+ EXPECT_DOUBLE_EQ(pos.getValue(), m_pBeats1->getLastBeatPosition().getValue());
+
+ // Before begining
+ auto iter3 = m_pBeats1->findBeats(
+ FramePos(m_pBeats1->getFirstBeatPosition().getValue() - 1000000),
+ m_pBeats1->getLastBeatPosition());
+ EXPECT_DOUBLE_EQ(iter3->next().frame_position(), m_pBeats1->getFirstBeatPosition().getValue());
+ while (iter3->hasNext()) {
+ auto beat = iter3->next();
+ pos = FramePos(beat.frame_position());
+ EXPECT_TRUE(pos.getValue());
+ }
+ EXPECT_DOUBLE_EQ(pos.getValue(), m_pBeats1->getLastBeatPosition().getValue());
+}
+
+TEST_F(BeatsTest, Translate) {
+ FrameDiff_t delta = 500;
+
+ // Move the grid delta frames
+ m_pBeats1->translate(delta);
+
+ // All beats must have been displaced by delta frames
+ auto iter1 = m_pBeats1->findBeats(m_pBeats1->getFirstBeatPosition(),
+ m_pBeats1->getLastBeatPosition());
+ auto iter2 = m_pBeats2->findBeats(m_pBeats2->getFirstBeatPosition(),
+ m_pBeats2->getLastBeatPosition());
+ while (iter1->hasNext()) {
+ double pos1 = iter1->next().frame_position();
+ double pos2 = iter2->next().frame_position();
+ EXPECT_DOUBLE_EQ(pos1, pos2 + delta);
+ }
+ EXPECT_EQ(iter1->hasNext(), iter2->hasNext());
+}
+
+TEST_F(BeatsTest, FindClosest) {
+ // Test deltas ranging from previous beat to next beat
+ for (FrameDiff_t delta = -m_iSampleRate; delta <= m_iSampleRate; delta++) {
+ auto iter1 = m_pBeats1->findBeats(m_pBeats1->getFirstBeatPosition(),
+ m_pBeats1->getLastBeatPosition());
+ while (iter1->hasNext()) {
+ FramePos pos = FramePos(iter1->next().frame_position());
+ FramePos foundPos = m_pBeats1->findClosestBeat(pos + delta);
+ // Correct change of beat
+ FramePos expectedPos = pos +
+ (delta > (m_iSampleRate / 2.0) ? m_iSampleRate : 0) +
+ (delta < (-m_iSampleRate / 2.0) ? -m_iSampleRate : 0);
+ // Enforce boundaries
+ expectedPos = std::min(expectedPos, m_pBeats1->getLastBeatPosition());
+ expectedPos = std::max(expectedPos, m_pBeats1->getFirstBeatPosition());
+ EXPECT_DOUBLE_EQ(foundPos.getValue(), expectedPos.getValue());
+ }
+ break;
+ }
+}
+
+} // namespace
diff --git a/src/test/beatstranslatetest.cpp b/src/test/beatstranslatetest.cpp
index 8a8021e05bf..115c6ebd2de 100644
--- a/src/test/beatstranslatetest.cpp
+++ b/src/test/beatstranslatetest.cpp
@@ -1,40 +1,47 @@
#include "test/mockedenginebackendtest.h"
-#include "track/beatgrid.h"
+#include "track/beats.h"
#include "util/memory.h"
+using namespace mixxx;
+
class BeatsTranslateTest : public MockedEngineBackendTest {
};
TEST_F(BeatsTranslateTest, SimpleTranslateMatch) {
- // Set up BeatGrids for decks 1 and 2.
- const double bpm = 60.0;
- const double firstBeat = 0.0;
- auto grid1 = new mixxx::BeatGrid(*m_pTrack1, m_pTrack1->getSampleRate());
- grid1->setGrid(bpm, firstBeat);
- m_pTrack1->setBeats(mixxx::BeatsPointer(grid1));
- ASSERT_DOUBLE_EQ(firstBeat, grid1->findClosestBeat(0));
-
- auto grid2 = new mixxx::BeatGrid(*m_pTrack2, m_pTrack2->getSampleRate());
- grid2->setGrid(bpm, firstBeat);
- m_pTrack2->setBeats(mixxx::BeatsPointer(grid2));
- ASSERT_DOUBLE_EQ(firstBeat, grid2->findClosestBeat(0));
-
- // Seek deck 1 forward a bit.
- const double delta = 2222.0;
- m_pChannel1->getEngineBuffer()->slotControlSeekAbs(delta);
+ const mixxx::Bpm bpm = Bpm(60.0);
+ const FramePos firstBeat(0.0);
+
+ // Set up Beats for decks 1 and 2.
+ auto beats1 = std::make_shared(m_pTrack1.get());
+ beats1->setGrid(bpm, firstBeat);
+ m_pTrack1->setBeats(beats1);
+ ASSERT_DOUBLE_EQ(firstBeat.getValue(), beats1->findClosestBeat(firstBeat).getValue());
+ auto beats2 = std::make_shared(m_pTrack2.get());
+ beats2->setGrid(bpm, firstBeat);
+ m_pTrack2->setBeats(beats2);
+ ASSERT_DOUBLE_EQ(firstBeat.getValue(), beats2->findClosestBeat(firstBeat).getValue());
+
+ const FrameDiff_t deltaFrames = 1111.0;
+ // Seek deck 1 forward a bit
+ m_pChannel1->getEngineBuffer()->slotControlSeekAbs(deltaFrames * 2);
ProcessBuffer();
EXPECT_TRUE(m_pChannel1->getEngineBuffer()->getVisualPlayPos() > 0);
+ ASSERT_DOUBLE_EQ(m_pChannel1->getEngineBuffer()->getExactPlayPos(), deltaFrames * 2);
// Make both decks playing.
ControlObject::getControl(m_sGroup1, "play")->set(1.0);
ControlObject::getControl(m_sGroup2, "play")->set(1.0);
ProcessBuffer();
- // Manually set the "bpm" control... I would like to figure out why this
- // doesn't get set naturally, but this will do for now.
+ ASSERT_DOUBLE_EQ(m_pChannel1->getEngineBuffer()->getExactPlayPos(),
+ m_pChannel2->getEngineBuffer()->getExactPlayPos() +
+ deltaFrames * 2);
+
+ // TODO(XXX) Manually set the "bpm" control... I would like to figure out
+ // why this doesn't get set naturally, but this will do for now.
auto pBpm1 = std::make_unique(m_sGroup1, "bpm");
auto pBpm2 = std::make_unique(m_sGroup1, "bpm");
- pBpm1->set(bpm);
- pBpm2->set(bpm);
+ pBpm1->set(bpm.getValue());
+ pBpm2->set(bpm.getValue());
ProcessBuffer();
// Push the button on deck 2.
@@ -44,7 +51,8 @@ TEST_F(BeatsTranslateTest, SimpleTranslateMatch) {
ProcessBuffer();
// Deck 1 is +delta away from its closest beat (which is at 0).
- // Deck 2 was left at 0. We translated grid 2 so that it is also +delta
- // away from its closest beat, so that beat should be at -delta.
- ASSERT_DOUBLE_EQ(-delta, grid2->findClosestBeat(0));
+ // Deck 2 was left at 0.
+ // We translated grid 2 so that it is also +delta away from its closest beat
+ // So that beat should be at deck 1 position -delta.
+ ASSERT_DOUBLE_EQ(-deltaFrames, beats2->findClosestBeat(FramePos(0)).getValue());
}
diff --git a/src/test/bpmcontrol_test.cpp b/src/test/bpmcontrol_test.cpp
index 5ff847df78c..608104c6dd1 100644
--- a/src/test/bpmcontrol_test.cpp
+++ b/src/test/bpmcontrol_test.cpp
@@ -1,16 +1,15 @@
+#include "engine/controls/bpmcontrol.h"
+
#include
-#include
#include
+#include
-#include "mixxxtest.h"
#include "control/controlobject.h"
#include "control/controlpushbutton.h"
-#include "engine/controls/bpmcontrol.h"
-#include "track/beats.h"
+#include "mixxxtest.h"
#include "track/beatfactory.h"
-#include "track/beatgrid.h"
-#include "track/beatmap.h"
+#include "track/beats.h"
#include "track/track.h"
class BpmControlTest : public MixxxTest {
@@ -34,18 +33,24 @@ TEST_F(BpmControlTest, BeatContext_BeatGrid) {
mixxx::audio::Bitrate(),
mixxx::Duration::fromSeconds(180));
- const double bpm = 60.0;
- const int kFrameSize = 2;
- const double expectedBeatLength = (60.0 * sampleRate / bpm) * kFrameSize;
+ const mixxx::Bpm bpm = mixxx::Bpm(60.0);
+ const mixxx::FrameDiff_t expectedBeatLengthFrames = (60.0 * sampleRate / bpm.getValue());
- mixxx::BeatsPointer pBeats = BeatFactory::makeBeatGrid(*pTrack, bpm, 0);
+ auto pBeats = std::make_shared(pTrack.get());
+ pBeats->setGrid(bpm);
// On a beat.
- double prevBeat, nextBeat, beatLength, beatPercentage;
- EXPECT_TRUE(BpmControl::getBeatContext(pBeats, 0.0, &prevBeat, &nextBeat,
- &beatLength, &beatPercentage));
- EXPECT_DOUBLE_EQ(0.0, prevBeat);
- EXPECT_DOUBLE_EQ(beatLength, nextBeat);
- EXPECT_DOUBLE_EQ(expectedBeatLength, beatLength);
+ mixxx::FramePos prevBeat, nextBeat;
+ mixxx::FrameDiff_t beatLength;
+ double beatPercentage;
+ EXPECT_TRUE(BpmControl::getBeatContext(pBeats,
+ mixxx::FramePos(0),
+ &prevBeat,
+ &nextBeat,
+ &beatLength,
+ &beatPercentage));
+ EXPECT_DOUBLE_EQ(0.0, prevBeat.getValue());
+ EXPECT_DOUBLE_EQ(beatLength, nextBeat.getValue());
+ EXPECT_DOUBLE_EQ(expectedBeatLengthFrames, beatLength);
EXPECT_DOUBLE_EQ(0.0, beatPercentage);
}
diff --git a/src/test/enginesynctest.cpp b/src/test/enginesynctest.cpp
index 58dc1e87560..c569906ae57 100644
--- a/src/test/enginesynctest.cpp
+++ b/src/test/enginesynctest.cpp
@@ -18,9 +18,11 @@
#include "test/mixxxtest.h"
#include "test/mockedenginebackendtest.h"
#include "track/beatfactory.h"
-#include "track/beatmap.h"
+#include "track/beats.h"
#include "util/memory.h"
+using namespace mixxx;
+
class EngineSyncTest : public MockedEngineBackendTest {
public:
QString getMasterGroup() {
@@ -189,9 +191,11 @@ TEST_F(EngineSyncTest, SetMasterSuccess) {
TEST_F(EngineSyncTest, ExplicitMasterPersists) {
// If we set an explicit master, enabling sync or pressing play on other decks
// doesn't cause the master to move around.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 120, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(120));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 124, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(124));
m_pTrack2->setBeats(pBeats2);
auto pButtonMasterSync1 =
@@ -221,11 +225,14 @@ TEST_F(EngineSyncTest, ExplicitMasterPersists) {
TEST_F(EngineSyncTest, SetMasterWhilePlaying) {
// Make sure we don't get two master lights if we change masters while playing.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 120, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(120));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 124, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(124));
m_pTrack2->setBeats(pBeats2);
- mixxx::BeatsPointer pBeats3 = BeatFactory::makeBeatGrid(*m_pTrack3, 128, 0.0);
+ auto pBeats3 = BeatsPointer(new Beats(m_pTrack3.get()));
+ pBeats3->setGrid(mixxx::Bpm(128));
m_pTrack3->setBeats(pBeats3);
auto pButtonMasterSync1 =
@@ -255,7 +262,8 @@ TEST_F(EngineSyncTest, SetMasterWhilePlaying) {
TEST_F(EngineSyncTest, SetEnabledBecomesMaster) {
// If we set the first channel with a valid tempo to follower, it should be master.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 80, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(80));
m_pTrack1->setBeats(pBeats1);
auto pButtonMasterSync1 =
std::make_unique(m_sGroup1, "sync_mode");
@@ -282,10 +290,12 @@ TEST_F(EngineSyncTest, DisableInternalMasterWhilePlaying) {
ASSERT_TRUE(isSoftMaster(m_sInternalClockGroup));
// Make sure both decks are playing.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 80, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(80));
m_pTrack1->setBeats(pBeats1);
ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 80, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(80));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup2, "play"))->set(1.0);
ProcessBuffer();
@@ -301,13 +311,15 @@ TEST_F(EngineSyncTest, DisableInternalMasterWhilePlaying) {
TEST_F(EngineSyncTest, DisableSyncOnMaster) {
// Channel 1 follower, channel 2 master.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
auto pButtonSyncMode1 =
std::make_unique(m_sGroup1, "sync_mode");
pButtonSyncMode1->slotSet(SYNC_FOLLOWER);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 130, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(130));
m_pTrack2->setBeats(pBeats2);
auto pButtonSyncMaster2 =
std::make_unique(m_sGroup2, "sync_master");
@@ -339,7 +351,8 @@ TEST_F(EngineSyncTest, InternalMasterSetFollowerSliderMoves) {
pMasterSyncSlider->set(100.0);
// Set the file bpm of channel 1 to 80 bpm.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 80, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(80));
m_pTrack1->setBeats(pBeats1);
auto pButtonMasterSync1 =
@@ -357,7 +370,8 @@ TEST_F(EngineSyncTest, AnySyncDeckSliderStays) {
// If there exists a sync deck, even if it's not playing, don't change the
// master BPM if a new deck enables sync.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 80, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(80));
m_pTrack1->setBeats(pBeats1);
auto pButtonSyncEnabled1 =
std::make_unique(m_sGroup1, "sync_enabled");
@@ -368,7 +382,8 @@ TEST_F(EngineSyncTest, AnySyncDeckSliderStays) {
ControlObject::getControl(ConfigKey(m_sInternalClockGroup, "bpm"))
->get());
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 100, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(100));
m_pTrack2->setBeats(pBeats2);
auto pButtonSyncEnabled2 =
std::make_unique(m_sGroup2, "sync_enabled");
@@ -392,11 +407,13 @@ TEST_F(EngineSyncTest, InternalClockFollowsFirstPlayingDeck) {
std::make_unique(m_sGroup2, "sync_enabled");
// Set up decks so they can be playing, and start deck 1.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 100, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(100));
m_pTrack1->setBeats(pBeats1);
ControlObject::set(ConfigKey(m_sGroup1, "rate"), getRateSliderValue(1.0));
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 130, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(130));
m_pTrack2->setBeats(pBeats2);
ControlObject::set(ConfigKey(m_sGroup2, "rate"), getRateSliderValue(1.0));
ControlObject::set(ConfigKey(m_sGroup2, "play"), 0.0);
@@ -462,10 +479,12 @@ TEST_F(EngineSyncTest, SetExplicitMasterByLights) {
std::make_unique(m_sGroup2, "sync_master");
// Set the file bpm of channel 1 to 160bpm.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
// Set the file bpm of channel 2 to 150bpm.
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack1, 150, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(150));
m_pTrack2->setBeats(pBeats2);
// Set channel 1 to be explicit master.
@@ -548,7 +567,8 @@ TEST_F(EngineSyncTest, RateChangeTest) {
ProcessBuffer();
// Set the file bpm of channel 1 to 160bpm.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
EXPECT_FLOAT_EQ(
160.0, ControlObject::get(ConfigKey(m_sGroup1, "file_bpm")));
@@ -567,7 +587,8 @@ TEST_F(EngineSyncTest, RateChangeTest) {
192.0, ControlObject::get(ConfigKey(m_sInternalClockGroup, "bpm")));
// Set the file bpm of channel 2 to 120bpm.
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 120, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(120));
m_pTrack2->setBeats(pBeats2);
EXPECT_FLOAT_EQ(
120.0, ControlObject::get(ConfigKey(m_sGroup2, "file_bpm")));
@@ -589,13 +610,15 @@ TEST_F(EngineSyncTest, RateChangeTestWeirdOrder) {
ProcessBuffer();
// Set the file bpm of channel 1 to 160bpm.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
EXPECT_FLOAT_EQ(
160.0, ControlObject::get(ConfigKey(m_sInternalClockGroup, "bpm")));
// Set the file bpm of channel 2 to 120bpm.
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 120, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(120));
m_pTrack2->setBeats(pBeats2);
// Set the rate slider of channel 1 to 1.2.
@@ -613,13 +636,15 @@ TEST_F(EngineSyncTest, RateChangeTestWeirdOrder) {
TEST_F(EngineSyncTest, RateChangeTestOrder3) {
// Set the file bpm of channel 1 to 160bpm.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
EXPECT_FLOAT_EQ(
160.0, ControlObject::get(ConfigKey(m_sGroup1, "file_bpm")));
// Set the file bpm of channel 2 to 120bpm.
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 120, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(120));
m_pTrack2->setBeats(pBeats2);
EXPECT_FLOAT_EQ(
120.0, ControlObject::get(ConfigKey(m_sGroup2, "file_bpm")));
@@ -656,11 +681,13 @@ TEST_F(EngineSyncTest, FollowerRateChange) {
ProcessBuffer();
// Set the file bpm of channel 1 to 160bpm.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
// Set the file bpm of channel 2 to 120bpm.
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 120, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(120));
m_pTrack2->setBeats(pBeats2);
// Set the rate slider of channel 1 to 1.2.
@@ -702,13 +729,15 @@ TEST_F(EngineSyncTest, InternalRateChangeTest) {
ASSERT_TRUE(isFollower(m_sGroup2));
// Set the file bpm of channel 1 to 160bpm.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
EXPECT_FLOAT_EQ(160.0,
ControlObject::getControl(ConfigKey(m_sGroup1, "file_bpm"))->get());
// Set the file bpm of channel 2 to 120bpm.
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 120, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(120));
m_pTrack2->setBeats(pBeats2);
EXPECT_FLOAT_EQ(120.0,
ControlObject::getControl(ConfigKey(m_sGroup2, "file_bpm"))->get());
@@ -755,9 +784,11 @@ TEST_F(EngineSyncTest, InternalRateChangeTest) {
TEST_F(EngineSyncTest, MasterStopSliderCheck) {
// If the master is playing, and stop is pushed, the sliders should stay the same.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 120, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(120));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 128, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(128));
m_pTrack2->setBeats(pBeats2);
auto pButtonMasterSync1 =
@@ -799,7 +830,8 @@ TEST_F(EngineSyncTest, EnableOneDeckInitsMaster) {
ProcessBuffer();
// Set up the deck to play.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))
->set(getRateSliderValue(1.0));
@@ -826,7 +858,8 @@ TEST_F(EngineSyncTest, EnableOneDeckInitsMaster) {
ConfigKey(m_sInternalClockGroup, "beat_distance")));
// Enable second deck, bpm and beat distance should still match original setting.
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 140, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(140));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup2, "rate"))
->set(getRateSliderValue(1.0));
@@ -853,7 +886,8 @@ TEST_F(EngineSyncTest, EnableOneDeckInitsMaster) {
TEST_F(EngineSyncTest, EnableOneDeckInitializesMaster) {
// Enabling sync on a deck causes it to be master, and sets bpm and clock.
// Set the deck to play.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))
->set(getRateSliderValue(1.0));
@@ -1050,7 +1084,8 @@ TEST_F(EngineSyncTest, EnableOneDeckSliderUpdates) {
auto pButtonSyncEnabled1 =
std::make_unique(m_sGroup1, "sync_enabled");
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))
->set(getRateSliderValue(1.0));
@@ -1077,11 +1112,13 @@ TEST_F(EngineSyncTest, SyncToNonSyncDeck) {
auto pButtonSyncEnabled2 =
std::make_unique(m_sGroup2, "sync_enabled");
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
ProcessBuffer();
ControlObject::set(ConfigKey(m_sGroup1, "rate"), getRateSliderValue(1.0));
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 100, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(100));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup2, "rate"))
->set(getRateSliderValue(1.0));
@@ -1160,11 +1197,13 @@ TEST_F(EngineSyncTest, MomentarySyncDependsOnPlayingStates) {
std::make_unique(m_sGroup2, "sync_enabled");
// Set up decks so they can be playing, and start deck 1.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 100, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(100));
m_pTrack1->setBeats(pBeats1);
ControlObject::set(ConfigKey(m_sGroup1, "rate"), getRateSliderValue(1.0));
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 130, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(130));
m_pTrack2->setBeats(pBeats2);
ControlObject::set(ConfigKey(m_sGroup2, "rate"), getRateSliderValue(1.0));
ControlObject::set(ConfigKey(m_sGroup2, "play"), 1.0);
@@ -1233,7 +1272,8 @@ TEST_F(EngineSyncTest, EjectTrackSyncRemains) {
std::make_unique(m_sGroup2, "sync_enabled");
auto pButtonEject1 = std::make_unique(m_sGroup1, "eject");
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 120, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(120));
m_pTrack1->setBeats(pBeats1);
pButtonSyncEnabled1->set(1.0);
ProcessBuffer();
@@ -1258,7 +1298,8 @@ TEST_F(EngineSyncTest, EjectTrackSyncRemains) {
m_pMixerDeck1->loadFakeTrack(false, 128.0);
EXPECT_FLOAT_EQ(128.0,
ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get());
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 135, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(135));
m_pTrack2->setBeats(pBeats2);
pButtonSyncEnabled2->set(1.0);
ProcessBuffer();
@@ -1278,21 +1319,24 @@ TEST_F(EngineSyncTest, EjectTrackSyncRemains) {
TEST_F(EngineSyncTest, FileBpmChangesDontAffectMaster) {
// If filebpm changes, don't treat it like a rate change.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 100, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(100));
m_pTrack1->setBeats(pBeats1);
auto pButtonSyncEnabled1 =
std::make_unique(m_sGroup1, "sync_enabled");
pButtonSyncEnabled1->set(1.0);
ProcessBuffer();
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 120, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(120));
m_pTrack2->setBeats(pBeats2);
auto pButtonSyncEnabled2 =
std::make_unique(m_sGroup2, "sync_enabled");
pButtonSyncEnabled2->set(1.0);
ProcessBuffer();
- pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
EXPECT_FLOAT_EQ(
100.0, ControlObject::get(ConfigKey(m_sInternalClockGroup, "bpm")));
@@ -1304,7 +1348,8 @@ TEST_F(EngineSyncTest, ExplicitMasterPostProcessed) {
auto pButtonMasterSync1 =
std::make_unique(m_sGroup1, "sync_mode");
pButtonMasterSync1->slotSet(SYNC_MASTER_EXPLICIT);
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 160, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(160));
m_pTrack1->setBeats(pBeats1);
ProcessBuffer();
ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0);
@@ -1317,7 +1362,8 @@ TEST_F(EngineSyncTest, ExplicitMasterPostProcessed) {
TEST_F(EngineSyncTest, ZeroBPMRateAdjustIgnored) {
// If a track isn't loaded (0 bpm), but the deck has sync enabled,
// don't pay attention to rate changes.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 0, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(0));
m_pTrack1->setBeats(pBeats1);
auto pButtonSyncEnabled1 =
std::make_unique(m_sGroup1, "sync_enabled");
@@ -1326,7 +1372,8 @@ TEST_F(EngineSyncTest, ZeroBPMRateAdjustIgnored) {
->set(getRateSliderValue(1.0));
ProcessBuffer();
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 120, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(120));
m_pTrack2->setBeats(pBeats2);
auto pButtonSyncEnabled2 =
std::make_unique(m_sGroup2, "sync_enabled");
@@ -1362,9 +1409,11 @@ TEST_F(EngineSyncTest, ZeroBPMRateAdjustIgnored) {
TEST_F(EngineSyncTest, ZeroLatencyRateChange) {
// Confirm that a rate change in an explicit master is instantly communicated
// to followers.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 128, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(128));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 128, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(128));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0);
@@ -1401,9 +1450,11 @@ TEST_F(EngineSyncTest, ZeroLatencyRateChange) {
}
TEST_F(EngineSyncTest, HalfDoubleBpmTest) {
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 70, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(70));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 140, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(140));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0);
@@ -1495,9 +1546,11 @@ TEST_F(EngineSyncTest, HalfDoubleBpmTest) {
TEST_F(EngineSyncTest, HalfDoubleThenPlay) {
// If a deck plays that had its multiplier set, we need to reset the
// internal clock.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 80, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(80));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 175, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(175));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup1, "rate"))
->set(getRateSliderValue(1.0));
@@ -1597,9 +1650,11 @@ TEST_F(EngineSyncTest, HalfDoubleThenPlay) {
TEST_F(EngineSyncTest, HalfDoubleInternalClockTest) {
// If we set the file_bpm CO's directly, the correct signals aren't fired.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 70, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(70));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 140, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(140));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0);
@@ -1618,11 +1673,12 @@ TEST_F(EngineSyncTest, HalfDoubleInternalClockTest) {
}
namespace {
-QVector createBeatVector(
- double first_beat, unsigned int num_beats, double beat_length) {
- QVector beats;
+QVector createBeatVector(FramePos first_beat,
+ unsigned int num_beats,
+ FrameDiff_t beat_length) {
+ QVector beats;
for (unsigned int i = 0; i < num_beats; ++i) {
- beats.append(first_beat + i * beat_length);
+ beats.append(first_beat + beat_length * i);
}
return beats;
}
@@ -1630,18 +1686,18 @@ QVector createBeatVector(
TEST_F(EngineSyncTest, HalfDoubleConsistency) {
// half-double matching should be consistent
- double beatLengthFrames = 60.0 * 44100 / 90.0;
- double startOffsetFrames = 0;
+ FrameDiff_t beatLengthFrames = 60.0 * 44100 / 90.0;
+ FramePos startOffsetFrames = FramePos(0);
const int numBeats = 100;
- QVector beats1 =
+ QVector beats1 =
createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pBeats1 = new mixxx::BeatMap(*m_pTrack1, 0, beats1);
+ auto pBeats1 = new mixxx::Beats(m_pTrack1.get(), beats1);
m_pTrack1->setBeats(mixxx::BeatsPointer(pBeats1));
beatLengthFrames = 60.0 * 44100 / 145.0;
- QVector beats2 =
+ QVector beats2 =
createBeatVector(startOffsetFrames, numBeats, beatLengthFrames);
- auto pBeats2 = new mixxx::BeatMap(*m_pTrack2, 0, beats2);
+ auto pBeats2 = new mixxx::Beats(m_pTrack2.get(), beats2);
m_pTrack2->setBeats(mixxx::BeatsPointer(pBeats2));
ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0);
@@ -1668,9 +1724,10 @@ TEST_F(EngineSyncTest, HalfDoubleConsistency) {
TEST_F(EngineSyncTest, SetFileBpmUpdatesLocalBpm) {
ControlObject::getControl(ConfigKey(m_sGroup1, "beat_distance"))->set(0.2);
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
- ASSERT_EQ(
+ EXPECT_FLOAT_EQ(
130.0, m_pEngineSync->getSyncableForGroup(m_sGroup1)->getBaseBpm());
}
@@ -1680,14 +1737,16 @@ TEST_F(EngineSyncTest, SyncPhaseToPlayingNonSyncDeck) {
auto pButtonSyncEnabled1 =
std::make_unique(m_sGroup1, "sync_enabled");
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0);
auto pButtonSyncEnabled2 =
std::make_unique(m_sGroup2, "sync_enabled");
ControlObject::set(ConfigKey(m_sGroup2, "rate_ratio"), 1.0);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 100, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(100));
m_pTrack2->setBeats(pBeats2);
// Set the sync deck playing with nothing else active.
@@ -1775,7 +1834,8 @@ TEST_F(EngineSyncTest, SyncPhaseToPlayingNonSyncDeck) {
std::make_unique(m_sGroup3, "sync_enabled");
ControlObject::set(ConfigKey(m_sGroup3, "beat_distance"), 0.6);
ControlObject::set(ConfigKey(m_sGroup2, "rate_ratio"), 1.0);
- mixxx::BeatsPointer pBeats3 = BeatFactory::makeBeatGrid(*m_pTrack3, 140, 0.0);
+ auto pBeats3 = BeatsPointer(new Beats(m_pTrack3.get()));
+ pBeats3->setGrid(mixxx::Bpm(140));
m_pTrack3->setBeats(pBeats3);
// This will sync to the first deck here and not the second (lp1784185)
pButtonSyncEnabled3->set(1.0);
@@ -1816,9 +1876,11 @@ TEST_F(EngineSyncTest, UserTweakBeatDistance) {
// If a deck has a user tweak, and another deck stops such that the first
// is used to reseed the master beat distance, make sure the user offset
// is reset.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 128, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(128));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 128, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(128));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0);
@@ -1869,9 +1931,11 @@ TEST_F(EngineSyncTest, UserTweakPreservedInSeek) {
// This is about 128 bpm, but results in nice round numbers of samples.
const double kDivisibleBpm = 44100.0 / 344.0;
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, kDivisibleBpm, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(kDivisibleBpm));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 130, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(130));
m_pTrack2->setBeats(pBeats2);
ControlObject::getControl(ConfigKey(m_sGroup2, "sync_enabled"))->set(1);
@@ -1929,7 +1993,8 @@ TEST_F(EngineSyncTest, UserTweakPreservedInSeek) {
}
TEST_F(EngineSyncTest, MasterBpmNeverZero) {
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 128, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(128));
m_pTrack1->setBeats(pBeats1);
auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled");
@@ -1944,7 +2009,8 @@ TEST_F(EngineSyncTest, ZeroBpmNaturalRate) {
// If a track has a zero bpm and a bad beatgrid, make sure the rate
// doesn't end up something crazy when sync is enabled..
// Maybe the beatgrid ended up at zero also.
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 0.0, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(0));
m_pTrack1->setBeats(pBeats1);
auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled");
@@ -1963,11 +2029,13 @@ TEST_F(EngineSyncTest, QuantizeImpliesSyncPhase) {
auto pButtonBeatsync1 = std::make_unique(m_sGroup1, "beatsync");
auto pButtonBeatsyncPhase1 = std::make_unique(m_sGroup1, "beatsync_phase");
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
ControlObject::set(ConfigKey(m_sGroup2, "rate"), getRateSliderValue(1.0));
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 100, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(100));
m_pTrack2->setBeats(pBeats2);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
@@ -2048,15 +2116,17 @@ TEST_F(EngineSyncTest, QuantizeImpliesSyncPhase) {
EXPECT_DOUBLE_EQ(100, ControlObject::get(ConfigKey(m_sGroup2, "bpm")));
// we align here to the past beat, because beat_distance < 1.0/8
- EXPECT_DOUBLE_EQ(
+ EXPECT_NEAR(
ControlObject::get(ConfigKey(m_sGroup1, "beat_distance")) / 130 * 100,
- ControlObject::get(ConfigKey(m_sGroup2, "beat_distance")));
+ ControlObject::get(ConfigKey(m_sGroup2, "beat_distance")),
+ 1e-15);
}
TEST_F(EngineSyncTest, SeekStayInPhase) {
ControlObject::set(ConfigKey(m_sGroup1, "quantize"), 1.0);
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
@@ -2077,7 +2147,8 @@ TEST_F(EngineSyncTest, SeekStayInPhase) {
ControlObject::set(ConfigKey(m_sGroup1, "play"), 0.0);
ProcessBuffer();
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(130));
m_pTrack2->setBeats(pBeats2);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
@@ -2099,7 +2170,8 @@ TEST_F(EngineSyncTest, SeekStayInPhase) {
TEST_F(EngineSyncTest, SyncWithoutBeatgrid) {
// this tests bug lp1783020, notresetting rate when other deck has no beatgrid
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 128, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(128));
m_pTrack1->setBeats(pBeats1);
m_pTrack2->setBeats(mixxx::BeatsPointer());
@@ -2118,12 +2190,13 @@ TEST_F(EngineSyncTest, SyncWithoutBeatgrid) {
}
TEST_F(EngineSyncTest, QuantizeHotCueActivate) {
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
auto pHotCue2Activate = std::make_unique(m_sGroup2, "hotcue_1_activate");
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 100, 0.0);
-
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(100));
m_pTrack2->setBeats(pBeats2);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
@@ -2178,7 +2251,8 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) {
auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled");
// set beatgrid
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 130, 0.0);
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack1.get()));
+ pBeats1->setGrid(mixxx::Bpm(130));
m_pTrack1->setBeats(pBeats1);
pButtonSyncEnabled1->set(1.0);
ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0);
@@ -2209,8 +2283,9 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) {
ASSERT_TRUE(isSoftMaster(m_sGroup1));
ASSERT_TRUE(isFollower(m_sGroup2));
- // Load a new beatgrid during playing, this happens when the analyser is finished
- mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(*m_pTrack2, 140, 0.0);
+ // Load a new beatgrid, this happens when the analyser is finisched
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2->setGrid(mixxx::Bpm(140));
m_pTrack2->setBeats(pBeats2);
ProcessBuffer();
@@ -2232,7 +2307,8 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) {
ASSERT_TRUE(isFollower(m_sGroup2));
// Load a new beatgrid again, this happens when the user adjusts the beatgrid
- mixxx::BeatsPointer pBeats2n = BeatFactory::makeBeatGrid(*m_pTrack2, 75, 0.0);
+ auto pBeats2n = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats2n->setGrid(mixxx::Bpm(75));
m_pTrack2->setBeats(pBeats2n);
ProcessBuffer();
@@ -2246,15 +2322,14 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) {
}
TEST_F(EngineSyncTest, BeatMapQantizePlay) {
- // This test demonstates https://bugs.launchpad.net/mixxx/+bug/1874918
- mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(*m_pTrack1, 120, 0.0);
+ // This test demonstrates https://bugs.launchpad.net/mixxx/+bug/1874918
+ auto pBeats1 = BeatsPointer(new Beats(m_pTrack2.get()));
+ pBeats1->setGrid(mixxx::Bpm(120));
m_pTrack1->setBeats(pBeats1);
- mixxx::BeatsPointer pBeats2 = mixxx::BeatsPointer(new mixxx::BeatMap(*m_pTrack2, 44100));
- // Add two beats at 120 Bpm
- pBeats2->addBeat(44100 / 2);
- pBeats2->addBeat(44100);
- m_pTrack2->setBeats(pBeats2);
+ auto pBeats2 = BeatsPointer(new Beats(m_pTrack2.get()));
+ // Set second track's BPM to be the same.
+ m_pTrack2->setBpm(120);
ControlObject::set(ConfigKey(m_sGroup1, "quantize"), 1.0);
ControlObject::set(ConfigKey(m_sGroup2, "quantize"), 1.0);
diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp
index 6e8685d1fb0..51e81ee1209 100644
--- a/src/test/looping_control_test.cpp
+++ b/src/test/looping_control_test.cpp
@@ -426,12 +426,12 @@ TEST_F(LoopingControlTest, LoopDoubleButton_IgnoresPastTrackEnd) {
TEST_F(LoopingControlTest, LoopDoubleButton_DoublesBeatloopSize) {
m_pTrack1->setBpm(120.0);
- m_pBeatLoopSize->set(16.0);
+ m_pBeatLoopSize->set(4.0);
m_pButtonBeatLoopActivate->set(1.0);
m_pButtonBeatLoopActivate->set(0.0);
m_pButtonLoopDouble->set(1.0);
m_pButtonLoopDouble->set(0.0);
- EXPECT_EQ(32.0, m_pBeatLoopSize->get());
+ EXPECT_EQ(8.0, m_pBeatLoopSize->get());
}
TEST_F(LoopingControlTest, LoopDoubleButton_DoesNotResizeManualLoop) {
@@ -477,12 +477,12 @@ TEST_F(LoopingControlTest, LoopHalveButton_IgnoresTooSmall) {
TEST_F(LoopingControlTest, LoopHalveButton_HalvesBeatloopSize) {
m_pTrack1->setBpm(120.0);
- m_pBeatLoopSize->set(64.0);
+ m_pBeatLoopSize->set(16.0);
m_pButtonBeatLoopActivate->set(1.0);
m_pButtonBeatLoopActivate->set(0.0);
m_pButtonLoopHalve->slotSet(1);
m_pButtonLoopHalve->slotSet(0);
- EXPECT_EQ(32.0, m_pBeatLoopSize->get());
+ EXPECT_EQ(8.0, m_pBeatLoopSize->get());
}
TEST_F(LoopingControlTest, LoopHalveButton_DoesNotResizeManualLoop) {
diff --git a/src/track/beatfactory.cpp b/src/track/beatfactory.cpp
index a5fb85ff9d3..c7eebeb00d6 100644
--- a/src/track/beatfactory.cpp
+++ b/src/track/beatfactory.cpp
@@ -1,45 +1,71 @@
-#include
+#include "track/beatfactory.h"
+
#include
+#include
-#include "track/beatgrid.h"
-#include "track/beatmap.h"
-#include "track/beatfactory.h"
+#include "track/beats.h"
#include "track/beatutils.h"
+#include "track/track.h"
-mixxx::BeatsPointer BeatFactory::loadBeatsFromByteArray(const Track& track,
+mixxx::BeatsPointer BeatFactory::loadBeatsFromByteArray(const TrackPointer& track,
QString beatsVersion,
QString beatsSubVersion,
const QByteArray& beatsSerialized) {
- if (beatsVersion == BEAT_GRID_1_VERSION ||
- beatsVersion == BEAT_GRID_2_VERSION) {
- mixxx::BeatGrid* pGrid = new mixxx::BeatGrid(track, 0, beatsSerialized);
+ // Now that the serialized representation is the same for BeatGrids and BeatMaps,
+ // they can be deserialized in a common function.
+ if (beatsVersion == mixxx::BeatsInternal::BEAT_GRID_1_VERSION ||
+ beatsVersion == mixxx::BeatsInternal::BEAT_GRID_2_VERSION) {
+ mixxx::track::io::LegacyBeatGrid legacyBeatGridProto;
+ if (!legacyBeatGridProto.ParseFromArray(
+ beatsSerialized.constData(), beatsSerialized.size())) {
+ qDebug()
+ << "ERROR: Could not parse legacy" << beatsVersion << "from QByteArray of size"
+ << beatsSerialized.size();
+ return mixxx::BeatsPointer();
+ }
+ mixxx::Beats* pGrid = new mixxx::Beats(track.get());
+ pGrid->setGrid(mixxx::Bpm(legacyBeatGridProto.bpm().bpm()),
+ mixxx::FramePos(
+ legacyBeatGridProto.first_beat().frame_position()));
pGrid->setSubVersion(beatsSubVersion);
- qDebug() << "Successfully deserialized BeatGrid";
+ qDebug() << "Successfully deserialized Beats from legacy data in format" << beatsVersion;
return mixxx::BeatsPointer(pGrid, &BeatFactory::deleteBeats);
- } else if (beatsVersion == BEAT_MAP_VERSION) {
- mixxx::BeatMap* pMap = new mixxx::BeatMap(track, 0, beatsSerialized);
+ } else if (beatsVersion == mixxx::BeatsInternal::BEAT_MAP_VERSION) {
+ mixxx::track::io::LegacyBeatMap legacyBeatMapProto;
+ if (!legacyBeatMapProto.ParseFromArray(
+ beatsSerialized.constData(), beatsSerialized.size())) {
+ qDebug() << "ERROR: Could not parse legacy" << beatsVersion << "from QByteArray of size"
+ << beatsSerialized.size();
+ return mixxx::BeatsPointer();
+ }
+ // Generate intermediate data from the old serialized representation.
+ QVector beatVector;
+ for (int i = 0; i < legacyBeatMapProto.beat_size(); ++i) {
+ const mixxx::track::io::LegacyBeat& beat = legacyBeatMapProto.beat(i);
+ beatVector.append(mixxx::FramePos(beat.frame_position()));
+ }
+ mixxx::Beats* pMap = new mixxx::Beats(track.get(), beatVector);
pMap->setSubVersion(beatsSubVersion);
- qDebug() << "Successfully deserialized BeatMap";
+ qDebug() << "Successfully deserialized Beats from legacy data in format" << beatsVersion;
return mixxx::BeatsPointer(pMap, &BeatFactory::deleteBeats);
+ } else if (beatsVersion == mixxx::BeatsInternal::BEATS_VERSION) {
+ mixxx::Beats* pBeats = new mixxx::Beats(track.get(), beatsSerialized);
+ pBeats->setSubVersion(beatsSubVersion);
+ qDebug() << "Successfully deserialized Beats";
+ return mixxx::BeatsPointer(pBeats, &BeatFactory::deleteBeats);
}
qDebug() << "BeatFactory::loadBeatsFromByteArray could not parse serialized beats.";
+ // TODO(JVC) May be launching a reanalysis to fix the data?
return mixxx::BeatsPointer();
}
-mixxx::BeatsPointer BeatFactory::makeBeatGrid(
- const Track& track, double dBpm, double dFirstBeatSample) {
- mixxx::BeatGrid* pGrid = new mixxx::BeatGrid(track, 0);
- pGrid->setGrid(dBpm, dFirstBeatSample);
- return mixxx::BeatsPointer(pGrid, &BeatFactory::deleteBeats);
-}
-
// static
QString BeatFactory::getPreferredVersion(
const bool bEnableFixedTempoCorrection) {
if (bEnableFixedTempoCorrection) {
- return BEAT_GRID_2_VERSION;
+ return mixxx::BeatsInternal::BEAT_GRID_2_VERSION;
}
- return BEAT_MAP_VERSION;
+ return mixxx::BeatsInternal::BEAT_MAP_VERSION;
}
QString BeatFactory::getPreferredSubVersion(
@@ -92,15 +118,15 @@ QString BeatFactory::getPreferredSubVersion(
: "";
}
-mixxx::BeatsPointer BeatFactory::makePreferredBeats(const Track& track,
+mixxx::BeatsPointer BeatFactory::makePreferredBeats(const TrackPointer& track,
QVector beats,
const QHash extraVersionInfo,
const bool bEnableFixedTempoCorrection,
const bool bEnableOffsetCorrection,
- const int iSampleRate,
const int iTotalSamples,
const int iMinBpm,
const int iMaxBpm) {
+ const int iSampleRate = track->getSampleRate();
const QString version = getPreferredVersion(bEnableFixedTempoCorrection);
const QString subVersion = getPreferredSubVersion(bEnableFixedTempoCorrection,
bEnableOffsetCorrection,
@@ -108,22 +134,38 @@ mixxx::BeatsPointer BeatFactory::makePreferredBeats(const Track& track,
extraVersionInfo);
BeatUtils::printBeatStatistics(beats, iSampleRate);
- if (version == BEAT_GRID_2_VERSION) {
- double globalBpm = BeatUtils::calculateBpm(beats, iSampleRate, iMinBpm, iMaxBpm);
- double firstBeat = BeatUtils::calculateFixedTempoFirstBeat(
- bEnableOffsetCorrection,
- beats, iSampleRate, iTotalSamples, globalBpm);
- mixxx::BeatGrid* pGrid = new mixxx::BeatGrid(track, iSampleRate);
- // firstBeat is in frames here and setGrid() takes samples.
- pGrid->setGrid(globalBpm, firstBeat * 2);
- 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 (version == mixxx::BeatsInternal::BEAT_GRID_2_VERSION) {
+ mixxx::Bpm globalBpm = BeatUtils::calculateBpm(beats, iSampleRate, iMinBpm, iMaxBpm);
+ mixxx::FramePos firstBeat(BeatUtils::calculateFixedTempoFirstBeat(
+ bEnableOffsetCorrection,
+ beats,
+ iSampleRate,
+ iTotalSamples,
+ globalBpm.getValue()));
+ mixxx::FrameDiff_t beatLength = iSampleRate * 60 / globalBpm.getValue();
+ double trackLengthSeconds = track->getDuration();
+ int numberOfBeats = globalBpm.getValue() / 60.0 * trackLengthSeconds;
+ QVector generatedBeats;
+ for (int i = 0; i < numberOfBeats; i++) {
+ generatedBeats.append(firstBeat + beatLength * i);
+ }
+ mixxx::Beats* pBeats = new mixxx::Beats(
+ track.get(), generatedBeats);
+ pBeats->setSubVersion(subVersion);
+ return mixxx::BeatsPointer(pBeats, &BeatFactory::deleteBeats);
+ } else if (version == mixxx::BeatsInternal::BEAT_MAP_VERSION) {
+ QVector intermediateBeatFrameVector;
+ intermediateBeatFrameVector.reserve(beats.size());
+ std::transform(beats.begin(),
+ beats.end(),
+ std::back_inserter(intermediateBeatFrameVector),
+ [](double value) { return mixxx::FramePos(value); });
+ mixxx::Beats* pBeats = new mixxx::Beats(
+ track.get(), intermediateBeatFrameVector);
+ pBeats->setSubVersion(subVersion);
+ return mixxx::BeatsPointer(pBeats, &BeatFactory::deleteBeats);
} else {
- qDebug() << "ERROR: Could not determine what type of beatgrid to create.";
+ DEBUG_ASSERT("Could not determine what type of beatgrid to create.");
return mixxx::BeatsPointer();
}
}
diff --git a/src/track/beatfactory.h b/src/track/beatfactory.h
index cb25021f19d..830bdab3d0d 100644
--- a/src/track/beatfactory.h
+++ b/src/track/beatfactory.h
@@ -8,11 +8,11 @@
class BeatFactory {
public:
- static mixxx::BeatsPointer loadBeatsFromByteArray(const Track& track,
+ static mixxx::BeatsPointer loadBeatsFromByteArray(const TrackPointer& track,
QString beatsVersion,
QString beatsSubVersion,
const QByteArray& beatsSerialized);
- static mixxx::BeatsPointer makeBeatGrid(const Track& track,
+ static mixxx::BeatsPointer makeBeatGrid(const TrackPointer& track,
double dBpm,
double dFirstBeatSample);
@@ -24,12 +24,11 @@ class BeatFactory {
const int iMinBpm, const int iMaxBpm,
const QHash extraVersionInfo);
- static mixxx::BeatsPointer makePreferredBeats(const Track& track,
+ static mixxx::BeatsPointer makePreferredBeats(const TrackPointer& track,
QVector beats,
const QHash extraVersionInfo,
const bool bEnableFixedTempoCorrection,
const bool bEnableOffsetCorrection,
- const int iSampleRate,
const int iTotalSamples,
const int iMinBpm,
const int iMaxBpm);
diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp
deleted file mode 100644
index 815314f736e..00000000000
--- a/src/track/beatgrid.cpp
+++ /dev/null
@@ -1,368 +0,0 @@
-#include
-#include
-
-#include "track/beatgrid.h"
-#include "util/math.h"
-
-static const int kFrameSize = 2;
-
-struct BeatGridData {
- double bpm;
- double firstBeat;
-};
-
-namespace mixxx {
-
-class BeatGridIterator : public BeatIterator {
- public:
- BeatGridIterator(double dBeatLength, double dFirstBeat, double dEndSample)
- : m_dBeatLength(dBeatLength),
- m_dCurrentSample(dFirstBeat),
- m_dEndSample(dEndSample) {
- }
-
- virtual bool hasNext() const {
- return m_dBeatLength > 0 && m_dCurrentSample <= m_dEndSample;
- }
-
- virtual double next() {
- double beat = m_dCurrentSample;
- m_dCurrentSample += m_dBeatLength;
- return beat;
- }
-
- private:
- double m_dBeatLength;
- double m_dCurrentSample;
- double m_dEndSample;
-};
-
-BeatGrid::BeatGrid(
- const Track& track,
- SINT iSampleRate)
- : m_mutex(QMutex::Recursive),
- m_iSampleRate(iSampleRate > 0 ? iSampleRate : track.getSampleRate()),
- m_dBeatLength(0.0) {
- // BeatGrid should live in the same thread as the track it is associated
- // with.
- moveToThread(track.thread());
-}
-
-BeatGrid::BeatGrid(
- const Track& track,
- SINT iSampleRate,
- const QByteArray& byteArray)
- : BeatGrid(track, iSampleRate) {
- readByteArray(byteArray);
-}
-
-BeatGrid::BeatGrid(const BeatGrid& other)
- : m_mutex(QMutex::Recursive),
- m_subVersion(other.m_subVersion),
- m_iSampleRate(other.m_iSampleRate),
- m_grid(other.m_grid),
- m_dBeatLength(other.m_dBeatLength) {
- moveToThread(other.thread());
-}
-
-void BeatGrid::setGrid(double dBpm, double dFirstBeatSample) {
- if (dBpm < 0) {
- dBpm = 0.0;
- }
-
- QMutexLocker lock(&m_mutex);
- m_grid.mutable_bpm()->set_bpm(dBpm);
- m_grid.mutable_first_beat()->set_frame_position(dFirstBeatSample / kFrameSize);
- // Calculate beat length as sample offsets
- m_dBeatLength = (60.0 * m_iSampleRate / dBpm) * kFrameSize;
-}
-
-QByteArray BeatGrid::toByteArray() const {
- QMutexLocker locker(&m_mutex);
- std::string output;
- m_grid.SerializeToString(&output);
- return QByteArray(output.data(), output.length());
-}
-
-BeatsPointer BeatGrid::clone() const {
- QMutexLocker locker(&m_mutex);
- BeatsPointer other(new BeatGrid(*this));
- return other;
-}
-
-void BeatGrid::readByteArray(const QByteArray& byteArray) {
- mixxx::track::io::BeatGrid grid;
- if (grid.ParseFromArray(byteArray.constData(), byteArray.length())) {
- m_grid = grid;
- m_dBeatLength = (60.0 * m_iSampleRate / bpm()) * kFrameSize;
- return;
- }
-
- // Legacy fallback for BeatGrid-1.0
- if (byteArray.size() != sizeof(BeatGridData)) {
- return;
- }
- const BeatGridData* blob = reinterpret_cast(byteArray.constData());
-
- // We serialize into frame offsets but use sample offsets at runtime
- setGrid(blob->bpm, blob->firstBeat * kFrameSize);
-}
-
-double BeatGrid::firstBeatSample() const {
- return m_grid.first_beat().frame_position() * kFrameSize;
-}
-
-double BeatGrid::bpm() const {
- return m_grid.bpm().bpm();
-}
-
-QString BeatGrid::getVersion() const {
- QMutexLocker locker(&m_mutex);
- return BEAT_GRID_2_VERSION;
-}
-
-QString BeatGrid::getSubVersion() const {
- QMutexLocker locker(&m_mutex);
- return m_subVersion;
-}
-
-void BeatGrid::setSubVersion(QString subVersion) {
- m_subVersion = subVersion;
-}
-
-// internal use only
-bool BeatGrid::isValid() const {
- return m_iSampleRate > 0 && bpm() > 0;
-}
-
-// This could be implemented in the Beats Class itself.
-// If necessary, the child class can redefine it.
-double BeatGrid::findNextBeat(double dSamples) const {
- return findNthBeat(dSamples, +1);
-}
-
-// This could be implemented in the Beats Class itself.
-// If necessary, the child class can redefine it.
-double BeatGrid::findPrevBeat(double dSamples) const {
- return findNthBeat(dSamples, -1);
-}
-
-// This is an internal call. This could be implemented in the Beats Class itself.
-double BeatGrid::findClosestBeat(double dSamples) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid()) {
- return -1;
- }
- double prevBeat;
- double nextBeat;
- findPrevNextBeats(dSamples, &prevBeat, &nextBeat);
- if (prevBeat == -1) {
- // If both values are -1, we correctly return -1.
- return nextBeat;
- } else if (nextBeat == -1) {
- return prevBeat;
- }
- return (nextBeat - dSamples > dSamples - prevBeat) ? prevBeat : nextBeat;
-}
-
-double BeatGrid::findNthBeat(double dSamples, int n) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid() || n == 0) {
- return -1;
- }
-
- double beatFraction = (dSamples - firstBeatSample()) / m_dBeatLength;
- double prevBeat = floor(beatFraction);
- double nextBeat = ceil(beatFraction);
-
- // If the position is within 1/100th of the next or previous beat, treat it
- // as if it is that beat.
- const double kEpsilon = .01;
-
- if (fabs(nextBeat - beatFraction) < kEpsilon) {
- // If we are going to pretend we were actually on nextBeat then prevBeat
- // needs to be re-calculated. Since it is floor(beatFraction), that's
- // the same as nextBeat. We only use prevBeat so no need to increment
- // nextBeat.
- prevBeat = nextBeat;
- } else if (fabs(prevBeat - beatFraction) < kEpsilon) {
- // If we are going to pretend we were actually on prevBeat then nextBeat
- // needs to be re-calculated. Since it is ceil(beatFraction), that's
- // the same as prevBeat. We will only use nextBeat so no need to
- // decrement prevBeat.
- nextBeat = prevBeat;
- }
-
- double dClosestBeat;
- if (n > 0) {
- // We're going forward, so use ceil to round up to the next multiple of
- // m_dBeatLength
- dClosestBeat = nextBeat * m_dBeatLength + firstBeatSample();
- n = n - 1;
- } else {
- // We're going backward, so use floor to round down to the next multiple
- // of m_dBeatLength
- dClosestBeat = prevBeat * m_dBeatLength + firstBeatSample();
- n = n + 1;
- }
-
- double dResult = dClosestBeat + n * m_dBeatLength;
- return dResult;
-}
-
-bool BeatGrid::findPrevNextBeats(double dSamples,
- double* dpPrevBeatSamples,
- double* dpNextBeatSamples) const {
- double dFirstBeatSample;
- double dBeatLength;
- {
- QMutexLocker locker(&m_mutex);
- if (!isValid()) {
- *dpPrevBeatSamples = -1.0;
- *dpNextBeatSamples = -1.0;
- return false;
- }
- dFirstBeatSample = firstBeatSample();
- dBeatLength = m_dBeatLength;
- }
-
- double beatFraction = (dSamples - dFirstBeatSample) / dBeatLength;
- double prevBeat = floor(beatFraction);
- double nextBeat = ceil(beatFraction);
-
- // If the position is within 1/100th of the next or previous beat, treat it
- // as if it is that beat.
- const double kEpsilon = .01;
-
- if (fabs(nextBeat - beatFraction) < kEpsilon) {
- beatFraction = nextBeat;
- // If we are going to pretend we were actually on nextBeat then prevBeatFraction
- // needs to be re-calculated. Since it is floor(beatFraction), that's
- // the same as nextBeat.
- prevBeat = nextBeat;
- // And nextBeat needs to be incremented.
- ++nextBeat;
- }
- *dpPrevBeatSamples = prevBeat * dBeatLength + dFirstBeatSample;
- *dpNextBeatSamples = nextBeat * dBeatLength + dFirstBeatSample;
- return true;
-}
-
-
-std::unique_ptr BeatGrid::findBeats(double startSample, double stopSample) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid() || startSample > stopSample) {
- return std::unique_ptr();
- }
- //qDebug() << "BeatGrid::findBeats startSample" << startSample << "stopSample"
- // << stopSample << "beatlength" << m_dBeatLength << "BPM" << bpm();
- double curBeat = findNextBeat(startSample);
- if (curBeat == -1.0) {
- return std::unique_ptr();
- }
- return std::make_unique(m_dBeatLength, curBeat, stopSample);
-}
-
-bool BeatGrid::hasBeatInRange(double startSample, double stopSample) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid() || startSample > stopSample) {
- return false;
- }
- double curBeat = findNextBeat(startSample);
- if (curBeat != -1.0 && curBeat <= stopSample) {
- return true;
- }
- return false;
-}
-
-double BeatGrid::getBpm() const {
- QMutexLocker locker(&m_mutex);
- if (!isValid()) {
- return 0;
- }
- return bpm();
-}
-
-double BeatGrid::getBpmRange(double startSample, double stopSample) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid() || startSample > stopSample) {
- return -1;
- }
- return bpm();
-}
-
-double BeatGrid::getBpmAroundPosition(double curSample, int n) const {
- Q_UNUSED(curSample);
- Q_UNUSED(n);
-
- QMutexLocker locker(&m_mutex);
- if (!isValid()) {
- return -1;
- }
- return bpm();
-}
-
-void BeatGrid::addBeat(double dBeatSample) {
- Q_UNUSED(dBeatSample);
- //QMutexLocker locker(&m_mutex);
- return;
-}
-
-void BeatGrid::removeBeat(double dBeatSample) {
- Q_UNUSED(dBeatSample);
- //QMutexLocker locker(&m_mutex);
- return;
-}
-
-void BeatGrid::translate(double dNumSamples) {
- QMutexLocker locker(&m_mutex);
- if (!isValid()) {
- return;
- }
- double newFirstBeatFrames = (firstBeatSample() + dNumSamples) / kFrameSize;
- m_grid.mutable_first_beat()->set_frame_position(newFirstBeatFrames);
- locker.unlock();
- emit updated();
-}
-
-void BeatGrid::scale(enum BPMScale scale) {
- double bpm = getBpm();
-
- switch (scale) {
- case DOUBLE:
- bpm *= 2;
- break;
- case HALVE:
- bpm *= 1.0 / 2;
- break;
- case TWOTHIRDS:
- bpm *= 2.0 / 3;
- break;
- case THREEFOURTHS:
- bpm *= 3.0 / 4;
- break;
- case FOURTHIRDS:
- bpm *= 4.0 / 3;
- break;
- case THREEHALVES:
- bpm *= 3.0 / 2;
- break;
- default:
- DEBUG_ASSERT(!"scale value invalid");
- return;
- }
- setBpm(bpm);
-}
-
-void BeatGrid::setBpm(double dBpm) {
- QMutexLocker locker(&m_mutex);
- if (dBpm > getMaxBpm()) {
- dBpm = getMaxBpm();
- }
- m_grid.mutable_bpm()->set_bpm(dBpm);
- m_dBeatLength = (60.0 * m_iSampleRate / dBpm) * kFrameSize;
- locker.unlock();
- emit updated();
-}
-
-} // namespace mixxx
diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h
deleted file mode 100644
index f1f09faf288..00000000000
--- a/src/track/beatgrid.h
+++ /dev/null
@@ -1,101 +0,0 @@
-#ifndef BEATGRID_H
-#define BEATGRID_H
-
-#include
-
-#include "track/track.h"
-#include "track/beats.h"
-#include "proto/beats.pb.h"
-
-#define BEAT_GRID_1_VERSION "BeatGrid-1.0"
-#define BEAT_GRID_2_VERSION "BeatGrid-2.0"
-
-namespace mixxx {
-
-// BeatGrid is an implementation of the Beats interface that implements an
-// infinite grid of beats, aligned to a song simply by a starting offset of the
-// first beat and the song's average beats-per-minute.
-class BeatGrid final : public Beats {
- public:
- // Construct a BeatGrid. If a more accurate sample rate is known, provide it
- // in the iSampleRate parameter -- otherwise pass 0.
- BeatGrid(const Track& track, SINT iSampleRate);
- // Construct a BeatGrid. If a more accurate sample rate is known, provide it
- // in the iSampleRate parameter -- otherwise pass 0. The BeatGrid will be
- // deserialized from the byte array.
- BeatGrid(const Track& track, SINT iSampleRate,
- const QByteArray& byteArray);
- ~BeatGrid() override = default;
-
- // Initializes the BeatGrid to have a BPM of dBpm and the first beat offset
- // of dFirstBeatSample. Does not generate an updated() signal, since it is
- // meant for initialization.
- void setGrid(double dBpm, double dFirstBeatSample);
-
- // The following are all methods from the Beats interface, see method
- // comments in beats.h
-
- Beats::CapabilitiesFlags getCapabilities() const override {
- return BEATSCAP_TRANSLATE | BEATSCAP_SCALE | BEATSCAP_SETBPM;
- }
-
- QByteArray toByteArray() const override;
- BeatsPointer clone() const override;
- QString getVersion() const override;
- QString getSubVersion() const override;
- virtual void setSubVersion(QString subVersion);
-
- ////////////////////////////////////////////////////////////////////////////
- // Beat calculations
- ////////////////////////////////////////////////////////////////////////////
-
- double findNextBeat(double dSamples) const override;
- double findPrevBeat(double dSamples) const override;
- bool findPrevNextBeats(double dSamples,
- double* dpPrevBeatSamples,
- double* dpNextBeatSamples) const override;
- double findClosestBeat(double dSamples) const override;
- double findNthBeat(double dSamples, int n) const override;
- std::unique_ptr findBeats(double startSample, double stopSample) const override;
- bool hasBeatInRange(double startSample, double stopSample) const override;
- double getBpm() const override;
- double getBpmRange(double startSample, double stopSample) const override;
- double getBpmAroundPosition(double curSample, int n) const override;
-
- ////////////////////////////////////////////////////////////////////////////
- // Beat mutations
- ////////////////////////////////////////////////////////////////////////////
-
- void addBeat(double dBeatSample) override;
- void removeBeat(double dBeatSample) override;
- void translate(double dNumSamples) override;
- void scale(enum BPMScale scale) override;
- void setBpm(double dBpm) override;
-
- SINT getSampleRate() const override {
- return m_iSampleRate;
- }
-
- private:
- BeatGrid(const BeatGrid& other);
- double firstBeatSample() const;
- double bpm() const;
-
- void readByteArray(const QByteArray& byteArray);
- // For internal use only.
- bool isValid() const;
-
- mutable QMutex m_mutex;
- // The sub-version of this beatgrid.
- QString m_subVersion;
- // The number of samples per second
- SINT m_iSampleRate;
- // Data storage for BeatGrid
- mixxx::track::io::BeatGrid m_grid;
- // The length of a beat in samples
- double m_dBeatLength;
-};
-
-} // namespace mixxx
-
-#endif /* BEATGRID_H */
diff --git a/src/track/beatiterator.h b/src/track/beatiterator.h
new file mode 100644
index 00000000000..ad2b1d04242
--- /dev/null
+++ b/src/track/beatiterator.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "track/beats.h"
+#include "track/beatutils.h"
+
+namespace mixxx {
+/// A forward iterator for Beats.
+/// BeatIterator do not fulfills the Iterator semantics, use with care.
+class BeatIterator final {
+ public:
+ BeatIterator(BeatList::const_iterator start, BeatList::const_iterator end)
+ : m_currentBeat(start),
+ m_endBeat(end) {
+ }
+
+ using iterator_category = std::forward_iterator_tag;
+
+ bool hasNext() const {
+ return m_currentBeat != m_endBeat;
+ }
+
+ /// Advances the iterator and returns the current beat frame position.
+ /// If you need the frame position make sure you store it, is not possible
+ /// to get it again.
+ track::io::Beat next() {
+ if (hasNext()) {
+ return *m_currentBeat++;
+ } else {
+ return track::io::Beat();
+ }
+ }
+
+ /*
+ // TODO(hacksdump): These will be removed from this iterator and
+ // will be made methods of the Beat class to be introduced in #2844.
+
+ /// Returns true if the current beat is a down beat.
+ bool isBar() const {
+ return m_currentBeat->type() == mixxx::track::io::BAR;
+ }
+
+ /// Returns true if the current beat is a phrase beat.
+ bool isPhrase() const {
+ return m_currentBeat->type() == mixxx::track::io::PHRASE;
+ }
+
+ /// The current beat becomes a regular beat.
+ void makeBeat() {
+ // TODO(JVC) Const_cast is needed until we manage to make BeatIterator read/write
+ const_cast(*m_currentBeat).set_type(mixxx::track::io::BEAT);
+ }
+
+ /// The current beat becomes a downbeat.
+ void makeBar() {
+ // TODO(JVC) Const_cast is needed until we manage to make BeatIterator read/write
+ const_cast(*m_currentBeat).set_type(mixxx::track::io::BAR);
+ }*/
+
+ private:
+ BeatList::const_iterator m_currentBeat;
+ BeatList::const_iterator m_endBeat;
+};
+
+} // Namespace mixxx
diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp
deleted file mode 100644
index 661f79e9f98..00000000000
--- a/src/track/beatmap.cpp
+++ /dev/null
@@ -1,729 +0,0 @@
-/*
- * beatmap.cpp
- *
- * Created on: 08/dic/2011
- * Author: vittorio
- */
-
-#include
-#include
-#include
-#include
-
-#include "track/beatmap.h"
-#include "track/beatutils.h"
-#include "util/math.h"
-
-using mixxx::track::io::Beat;
-
-const int kFrameSize = 2;
-
-inline double samplesToFrames(const double samples) {
- return floor(samples / kFrameSize);
-}
-
-inline double framesToSamples(const int frames) {
- return frames * kFrameSize;
-}
-
-bool BeatLessThan(const Beat& beat1, const Beat& beat2) {
- return beat1.frame_position() < beat2.frame_position();
-}
-
-namespace mixxx {
-
-class BeatMapIterator : public BeatIterator {
- public:
- BeatMapIterator(BeatList::const_iterator start, BeatList::const_iterator end)
- : m_currentBeat(start),
- m_endBeat(end) {
- // Advance to the first enabled beat.
- while (m_currentBeat != m_endBeat && !m_currentBeat->enabled()) {
- ++m_currentBeat;
- }
- }
-
- virtual bool hasNext() const {
- return m_currentBeat != m_endBeat;
- }
-
- virtual double next() {
- double beat = framesToSamples(m_currentBeat->frame_position());
- ++m_currentBeat;
- while (m_currentBeat != m_endBeat && !m_currentBeat->enabled()) {
- ++m_currentBeat;
- }
- return beat;
- }
-
- private:
- BeatList::const_iterator m_currentBeat;
- BeatList::const_iterator m_endBeat;
-};
-
-BeatMap::BeatMap(const Track& track, SINT iSampleRate)
- : m_mutex(QMutex::Recursive),
- m_iSampleRate(iSampleRate > 0 ? iSampleRate : track.getSampleRate()),
- m_dCachedBpm(0),
- m_dLastFrame(0) {
- // BeatMap should live in the same thread as the track it is associated
- // with.
- moveToThread(track.thread());
-}
-
-BeatMap::BeatMap(const Track& track, SINT iSampleRate,
- const QByteArray& byteArray)
- : BeatMap(track, iSampleRate) {
- readByteArray(byteArray);
-}
-
-BeatMap::BeatMap(const Track& track, SINT iSampleRate,
- const QVector& beats)
- : BeatMap(track, iSampleRate) {
- if (beats.size() > 0) {
- createFromBeatVector(beats);
- }
-}
-
-BeatMap::BeatMap (const BeatMap& other)
- : m_mutex(QMutex::Recursive),
- m_subVersion(other.m_subVersion),
- m_iSampleRate(other.m_iSampleRate),
- m_dCachedBpm(other.m_dCachedBpm),
- m_dLastFrame(other.m_dLastFrame),
- m_beats(other.m_beats) {
- moveToThread(other.thread());
-}
-
-QByteArray BeatMap::toByteArray() const {
- QMutexLocker locker(&m_mutex);
- // No guarantees BeatLists are made of a data type which located adjacent
- // items in adjacent memory locations.
- mixxx::track::io::BeatMap map;
-
- for (int i = 0; i < m_beats.size(); ++i) {
- map.add_beat()->CopyFrom(m_beats[i]);
- }
-
- std::string output;
- map.SerializeToString(&output);
- return QByteArray(output.data(), output.length());
-}
-
-BeatsPointer BeatMap::clone() const {
- QMutexLocker locker(&m_mutex);
- BeatsPointer other(new BeatMap(*this));
- return other;
-}
-
-bool BeatMap::readByteArray(const QByteArray& byteArray) {
- mixxx::track::io::BeatMap map;
- if (!map.ParseFromArray(byteArray.constData(), byteArray.size())) {
- qDebug() << "ERROR: Could not parse BeatMap from QByteArray of size"
- << byteArray.size();
- return false;
- }
- for (int i = 0; i < map.beat_size(); ++i) {
- const Beat& beat = map.beat(i);
- m_beats.append(beat);
- }
- onBeatlistChanged();
- return true;
-}
-
-void BeatMap::createFromBeatVector(const QVector& beats) {
- if (beats.isEmpty()) {
- return;
- }
- double previous_beatpos = -1;
- Beat beat;
-
- foreach (double beatpos, beats) {
- // beatpos is in frames. Do not accept fractional frames.
- beatpos = floor(beatpos);
- if (beatpos <= previous_beatpos || beatpos < 0) {
- qDebug() << "BeatMap::createFromVector: beats not in increasing order or negative";
- qDebug() << "discarding beat " << beatpos;
- } else {
- beat.set_frame_position(beatpos);
- m_beats.append(beat);
- previous_beatpos = beatpos;
- }
- }
- onBeatlistChanged();
-}
-
-QString BeatMap::getVersion() const {
- QMutexLocker locker(&m_mutex);
- return BEAT_MAP_VERSION;
-}
-
-QString BeatMap::getSubVersion() const {
- QMutexLocker locker(&m_mutex);
- return m_subVersion;
-}
-
-void BeatMap::setSubVersion(QString subVersion) {
- m_subVersion = subVersion;
-}
-
-bool BeatMap::isValid() const {
- return m_iSampleRate > 0 && m_beats.size() > 0;
-}
-
-double BeatMap::findNextBeat(double dSamples) const {
- return findNthBeat(dSamples, 1);
-}
-
-double BeatMap::findPrevBeat(double dSamples) const {
- return findNthBeat(dSamples, -1);
-}
-
-double BeatMap::findClosestBeat(double dSamples) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid()) {
- return -1;
- }
- double prevBeat;
- double nextBeat;
- findPrevNextBeats(dSamples, &prevBeat, &nextBeat);
- if (prevBeat == -1) {
- // If both values are -1, we correctly return -1.
- return nextBeat;
- } else if (nextBeat == -1) {
- return prevBeat;
- }
- return (nextBeat - dSamples > dSamples - prevBeat) ? prevBeat : nextBeat;
-}
-
-double BeatMap::findNthBeat(double dSamples, int n) const {
- QMutexLocker locker(&m_mutex);
-
- if (!isValid() || n == 0) {
- return -1;
- }
-
- Beat beat;
- // Reduce sample offset to a frame offset.
- beat.set_frame_position(samplesToFrames(dSamples));
-
- // it points at the first occurrence of beat or the next largest beat
- BeatList::const_iterator it =
- std::lower_bound(m_beats.constBegin(), m_beats.constEnd(), beat, BeatLessThan);
-
- // If the position is within 1/10th of a second of the next or previous
- // beat, pretend we are on that beat.
- const double kFrameEpsilon = 0.1 * m_iSampleRate;
-
- // Back-up by one.
- if (it != m_beats.begin()) {
- --it;
- }
-
- // Scan forward to find whether we are on a beat.
- BeatList::const_iterator on_beat = m_beats.constEnd();
- BeatList::const_iterator previous_beat = m_beats.constEnd();
- BeatList::const_iterator next_beat = m_beats.constEnd();
- for (; it != m_beats.end(); ++it) {
- qint32 delta = it->frame_position() - beat.frame_position();
-
- // We are "on" this beat.
- if (abs(delta) < kFrameEpsilon) {
- on_beat = it;
- break;
- }
-
- if (delta < 0) {
- // If we are not on the beat and delta < 0 then this beat comes
- // before our current position.
- previous_beat = it;
- } else {
- // If we are past the beat and we aren't on it then this beat comes
- // after our current position.
- next_beat = it;
- // Stop because we have everything we need now.
- break;
- }
- }
-
- // If we are within epsilon samples of a beat then the immediately next and
- // previous beats are the beat we are on.
- if (on_beat != m_beats.end()) {
- next_beat = on_beat;
- previous_beat = on_beat;
- }
-
- if (n > 0) {
- for (; next_beat != m_beats.end(); ++next_beat) {
- if (!next_beat->enabled()) {
- continue;
- }
- if (n == 1) {
- // Return a sample offset
- return framesToSamples(next_beat->frame_position());
- }
- --n;
- }
- } else if (n < 0 && previous_beat != m_beats.end()) {
- for (; true; --previous_beat) {
- if (previous_beat->enabled()) {
- if (n == -1) {
- // Return a sample offset
- return framesToSamples(previous_beat->frame_position());
- }
- ++n;
- }
-
- // Don't step before the start of the list.
- if (previous_beat == m_beats.begin()) {
- break;
- }
- }
- }
- return -1;
-}
-
-bool BeatMap::findPrevNextBeats(double dSamples,
- double* dpPrevBeatSamples,
- double* dpNextBeatSamples) const {
- QMutexLocker locker(&m_mutex);
-
- if (!isValid()) {
- *dpPrevBeatSamples = -1;
- *dpNextBeatSamples = -1;
- return false;
- }
-
- Beat beat;
- // Reduce sample offset to a frame offset.
- beat.set_frame_position(samplesToFrames(dSamples));
-
- // it points at the first occurrence of beat or the next largest beat
- BeatList::const_iterator it =
- std::lower_bound(m_beats.constBegin(), m_beats.constEnd(), beat, BeatLessThan);
-
- // If the position is within 1/10th of a second of the next or previous
- // beat, pretend we are on that beat.
- const double kFrameEpsilon = 0.1 * m_iSampleRate;
-
- // Back-up by one.
- if (it != m_beats.begin()) {
- --it;
- }
-
- // Scan forward to find whether we are on a beat.
- BeatList::const_iterator on_beat = m_beats.constEnd();
- BeatList::const_iterator previous_beat = m_beats.constEnd();
- BeatList::const_iterator next_beat = m_beats.constEnd();
- for (; it != m_beats.end(); ++it) {
- qint32 delta = it->frame_position() - beat.frame_position();
-
- // We are "on" this beat.
- if (abs(delta) < kFrameEpsilon) {
- on_beat = it;
- break;
- }
-
- if (delta < 0) {
- // If we are not on the beat and delta < 0 then this beat comes
- // before our current position.
- previous_beat = it;
- } else {
- // If we are past the beat and we aren't on it then this beat comes
- // after our current position.
- next_beat = it;
- // Stop because we have everything we need now.
- break;
- }
- }
-
- // If we are within epsilon samples of a beat then the immediately next and
- // previous beats are the beat we are on.
- if (on_beat != m_beats.end()) {
- previous_beat = on_beat;
- next_beat = on_beat + 1;
- }
-
- *dpPrevBeatSamples = -1;
- *dpNextBeatSamples = -1;
-
- for (; next_beat != m_beats.end(); ++next_beat) {
- if (!next_beat->enabled()) {
- continue;
- }
- *dpNextBeatSamples = framesToSamples(next_beat->frame_position());
- break;
- }
- if (previous_beat != m_beats.end()) {
- for (; true; --previous_beat) {
- if (previous_beat->enabled()) {
- *dpPrevBeatSamples = framesToSamples(previous_beat->frame_position());
- break;
- }
-
- // Don't step before the start of the list.
- if (previous_beat == m_beats.begin()) {
- break;
- }
- }
- }
- return *dpPrevBeatSamples != -1 && *dpNextBeatSamples != -1;
-}
-
-std::unique_ptr BeatMap::findBeats(double startSample, double stopSample) const {
- QMutexLocker locker(&m_mutex);
- //startSample and stopSample are sample offsets, converting them to
- //frames
- if (!isValid() || startSample > stopSample) {
- return std::unique_ptr();
- }
-
- Beat startBeat, stopBeat;
- startBeat.set_frame_position(samplesToFrames(startSample));
- stopBeat.set_frame_position(samplesToFrames(stopSample));
-
- BeatList::const_iterator curBeat =
- std::lower_bound(m_beats.constBegin(), m_beats.constEnd(),
- startBeat, BeatLessThan);
-
- BeatList::const_iterator lastBeat =
- std::upper_bound(m_beats.constBegin(), m_beats.constEnd(),
- stopBeat, BeatLessThan);
-
- if (curBeat >= lastBeat) {
- return std::unique_ptr();
- }
- return std::make_unique(curBeat, lastBeat);
-}
-
-bool BeatMap::hasBeatInRange(double startSample, double stopSample) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid() || startSample > stopSample) {
- return false;
- }
- double curBeat = findNextBeat(startSample);
- if (curBeat <= stopSample) {
- return true;
- }
- return false;
-}
-
-double BeatMap::getBpm() const {
- QMutexLocker locker(&m_mutex);
- if (!isValid())
- return -1;
- return m_dCachedBpm;
-}
-
-double BeatMap::getBpmRange(double startSample, double stopSample) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid())
- return -1;
- Beat startBeat, stopBeat;
- startBeat.set_frame_position(samplesToFrames(startSample));
- stopBeat.set_frame_position(samplesToFrames(stopSample));
- return calculateBpm(startBeat, stopBeat);
-}
-
-double BeatMap::getBpmAroundPosition(double curSample, int n) const {
- QMutexLocker locker(&m_mutex);
- if (!isValid())
- return -1;
-
- // To make sure we are always counting n beats, iterate backward to the
- // lower bound, then iterate forward from there to the upper bound.
- // a value of -1 indicates we went off the map -- count from the beginning.
- double lower_bound = findNthBeat(curSample, -n);
- if (lower_bound == -1) {
- lower_bound = framesToSamples(m_beats.first().frame_position());
- }
-
- // If we hit the end of the beat map, recalculate the lower bound.
- double upper_bound = findNthBeat(lower_bound, n * 2);
- if (upper_bound == -1) {
- upper_bound = framesToSamples(m_beats.last().frame_position());
- lower_bound = findNthBeat(upper_bound, n * -2);
- // Super edge-case -- the track doesn't have n beats! Do the best
- // we can.
- if (lower_bound == -1) {
- lower_bound = framesToSamples(m_beats.first().frame_position());
- }
- }
-
- Beat startBeat, stopBeat;
- startBeat.set_frame_position(samplesToFrames(lower_bound));
- stopBeat.set_frame_position(samplesToFrames(upper_bound));
- return calculateBpm(startBeat, stopBeat);
-}
-
-void BeatMap::addBeat(double dBeatSample) {
- QMutexLocker locker(&m_mutex);
- Beat beat;
- beat.set_frame_position(samplesToFrames(dBeatSample));
- BeatList::iterator it = std::lower_bound(
- m_beats.begin(), m_beats.end(), beat, BeatLessThan);
-
- // Don't insert a duplicate beat. TODO(XXX) determine what epsilon to
- // consider a beat identical to another.
- if (it != m_beats.end() && it->frame_position() == beat.frame_position()) {
- return;
- }
-
- m_beats.insert(it, beat);
- onBeatlistChanged();
- locker.unlock();
- emit updated();
-}
-
-void BeatMap::removeBeat(double dBeatSample) {
- QMutexLocker locker(&m_mutex);
- Beat beat;
- beat.set_frame_position(samplesToFrames(dBeatSample));
- BeatList::iterator it = std::lower_bound(
- m_beats.begin(), m_beats.end(), beat, BeatLessThan);
-
- // In case there are duplicates, remove every instance of dBeatSample
- // TODO(XXX) add invariant checks against this
- // TODO(XXX) determine what epsilon to consider a beat identical to another
- while (it->frame_position() == beat.frame_position()) {
- it = m_beats.erase(it);
- }
- onBeatlistChanged();
- locker.unlock();
- emit updated();
-}
-
-void BeatMap::translate(double dNumSamples) {
- QMutexLocker locker(&m_mutex);
- // Converting to frame offset
- if (!isValid()) {
- return;
- }
-
- double dNumFrames = samplesToFrames(dNumSamples);
- for (BeatList::iterator it = m_beats.begin();
- it != m_beats.end(); ) {
- double newpos = it->frame_position() + dNumFrames;
- if (newpos >= 0) {
- it->set_frame_position(newpos);
- ++it;
- } else {
- it = m_beats.erase(it);
- }
- }
- onBeatlistChanged();
- locker.unlock();
- emit updated();
-}
-
-void BeatMap::scale(enum BPMScale scale) {
-
- QMutexLocker locker(&m_mutex);
- if (!isValid() || m_beats.isEmpty()) {
- return;
- }
-
- switch (scale) {
- case DOUBLE:
- // introduce a new beat into every gap
- scaleDouble();
- break;
- case HALVE:
- // remove every second beat
- scaleHalve();
- break;
- case TWOTHIRDS:
- // introduce a new beat into every gap
- scaleDouble();
- // remove every second and third beat
- scaleThird();
- break;
- case THREEFOURTHS:
- // introduce two beats into every gap
- scaleTriple();
- // remove every second third and forth beat
- scaleFourth();
- break;
- case FOURTHIRDS:
- // introduce three beats into every gap
- scaleQuadruple();
- // remove every second third and forth beat
- scaleThird();
- break;
- case THREEHALVES:
- // introduce two beats into every gap
- scaleTriple();
- // remove every second beat
- scaleHalve();
- break;
- default:
- DEBUG_ASSERT(!"scale value invalid");
- return;
- }
- onBeatlistChanged();
- locker.unlock();
- emit updated();
-}
-
-void BeatMap::scaleDouble() {
- Beat prevBeat = m_beats.first();
- // Skip the first beat to preserve the first beat in a measure
- BeatList::iterator it = m_beats.begin() + 1;
- for (; it != m_beats.end(); ++it) {
- // Need to not accrue fractional frames.
- int distance = it->frame_position() - prevBeat.frame_position();
- Beat beat;
- beat.set_frame_position(prevBeat.frame_position() + distance / 2);
- it = m_beats.insert(it, beat);
- prevBeat = (++it)[0];
- }
-}
-
-void BeatMap::scaleTriple() {
- Beat prevBeat = m_beats.first();
- // Skip the first beat to preserve the first beat in a measure
- BeatList::iterator it = m_beats.begin() + 1;
- for (; it != m_beats.end(); ++it) {
- // Need to not accrue fractional frames.
- int distance = it->frame_position() - prevBeat.frame_position();
- Beat beat;
- beat.set_frame_position(prevBeat.frame_position() + distance / 3);
- it = m_beats.insert(it, beat);
- ++it;
- beat.set_frame_position(prevBeat.frame_position() + distance * 2 / 3);
- it = m_beats.insert(it, beat);
- prevBeat = (++it)[0];
- }
-}
-
-void BeatMap::scaleQuadruple() {
- Beat prevBeat = m_beats.first();
- // Skip the first beat to preserve the first beat in a measure
- BeatList::iterator it = m_beats.begin() + 1;
- for (; it != m_beats.end(); ++it) {
- // Need to not accrue fractional frames.
- int distance = it->frame_position() - prevBeat.frame_position();
- Beat beat;
- for (int i = 1; i <= 3; i++) {
- beat.set_frame_position(prevBeat.frame_position() + distance * i / 4);
- it = m_beats.insert(it, beat);
- ++it;
- }
- prevBeat = it[0];
- }
-}
-
-void BeatMap::scaleHalve() {
- // Skip the first beat to preserve the first beat in a measure
- BeatList::iterator it = m_beats.begin() + 1;
- for (; it != m_beats.end(); ++it) {
- it = m_beats.erase(it);
- if (it == m_beats.end()) {
- break;
- }
- }
-}
-
-void BeatMap::scaleThird() {
- // Skip the first beat to preserve the first beat in a measure
- BeatList::iterator it = m_beats.begin() + 1;
- for (; it != m_beats.end(); ++it) {
- it = m_beats.erase(it);
- if (it == m_beats.end()) {
- break;
- }
- it = m_beats.erase(it);
- if (it == m_beats.end()) {
- break;
- }
- }
-}
-
-void BeatMap::scaleFourth() {
- // Skip the first beat to preserve the first beat in a measure
- BeatList::iterator it = m_beats.begin() + 1;
- for (; it != m_beats.end(); ++it) {
- it = m_beats.erase(it);
- if (it == m_beats.end()) {
- break;
- }
- it = m_beats.erase(it);
- if (it == m_beats.end()) {
- break;
- }
- it = m_beats.erase(it);
- if (it == m_beats.end()) {
- break;
- }
- }
-}
-
-void BeatMap::setBpm(double dBpm) {
- Q_UNUSED(dBpm);
- DEBUG_ASSERT(!"BeatMap::setBpm() not implemented");
- return;
-
- /*
- * One of the problems of beattracking algorithms is the so called "octave error"
- * that is, calculated bpm is a power-of-two fraction of the bpm of the track.
- * But there is more. In an experiment, it had been proved that roughly 30% of the humans
- * fail to guess the correct bpm of a track by usually reporting it as the double or one
- * half of the correct one.
- * We can interpret it in two ways:
- * On one hand, a beattracking algorithm which totally avoid the octave error does not yet exists.
- * On the other hand, even if the algorithm guesses the correct bpm,
- * 30% of the users will perceive a different bpm and likely change it.
- * In this case, we assume that calculated beat markers are correctly placed. All
- * that we have to do is to delete or add some beat markers, while leaving others
- * so that the number of the beat markers per minute matches the new bpm.
- * We are jealous of our well-guessed beats since they belong to a time-expensive analysis.
- * When requested we simply turn them off instead of deleting them, so that they can be recollected.
- * If the new provided bpm is not a power-of-two fraction, we assume that the algorithm failed
- * at all to guess the bpm. I have no idea on how to deal with this.
- * If we assume that bpm does not change along the track, i.e. if we use
- * fixed tempo approximation (see analyzerbeat.*), this should coincide with the
- * method in beatgrid.cpp.
- *
- * - vittorio.
- */
-}
-
-void BeatMap::onBeatlistChanged() {
- if (!isValid()) {
- m_dLastFrame = 0;
- m_dCachedBpm = 0;
- return;
- }
- m_dLastFrame = m_beats.last().frame_position();
- Beat startBeat = m_beats.first();
- Beat stopBeat = m_beats.last();
- m_dCachedBpm = calculateBpm(startBeat, stopBeat);
-}
-
-double BeatMap::calculateBpm(const Beat& startBeat, const Beat& stopBeat) const {
- if (startBeat.frame_position() > stopBeat.frame_position()) {
- return -1;
- }
-
- BeatList::const_iterator curBeat =
- std::lower_bound(m_beats.constBegin(), m_beats.constEnd(), startBeat, BeatLessThan);
-
- BeatList::const_iterator lastBeat =
- std::upper_bound(m_beats.constBegin(), m_beats.constEnd(), stopBeat, BeatLessThan);
-
- QVector beatvect;
- for (; curBeat != lastBeat; ++curBeat) {
- const Beat& beat = *curBeat;
- if (beat.enabled()) {
- beatvect.append(beat.frame_position());
- }
- }
-
- if (beatvect.isEmpty()) {
- return -1;
- }
-
- return BeatUtils::calculateBpm(beatvect, m_iSampleRate, 0, 9999);
-}
-
-} // namespace mixxx
diff --git a/src/track/beatmap.h b/src/track/beatmap.h
deleted file mode 100644
index 5d1ce7df572..00000000000
--- a/src/track/beatmap.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * beatmap.h
- *
- * Created on: 08/dic/2011
- * Author: vittorio
- */
-
-#ifndef BEATMAP_H_
-#define BEATMAP_H_
-
-#include
-
-#include "track/track.h"
-#include "track/beats.h"
-#include "proto/beats.pb.h"
-
-#define BEAT_MAP_VERSION "BeatMap-1.0"
-
-typedef QList BeatList;
-
-namespace mixxx {
-
-class BeatMap final : public Beats {
- public:
- // Construct a BeatMap. iSampleRate may be provided if a more accurate
- // sample rate is known than the one associated with the Track.
- BeatMap(const Track& track, SINT iSampleRate);
- // Construct a BeatMap. iSampleRate may be provided if a more accurate
- // sample rate is known than the one associated with the Track. If it is
- // zero then the track's sample rate will be used. The BeatMap will be
- // deserialized from the byte array.
- BeatMap(const Track& track, SINT iSampleRate,
- const QByteArray& byteArray);
- // Construct a BeatMap. iSampleRate may be provided if a more accurate
- // sample rate is known than the one associated with the Track. If it is
- // zero then the track's sample rate will be used. A list of beat locations
- // in audio frames may be provided.
- BeatMap(const Track& track, SINT iSampleRate,
- const QVector