diff --git a/CMakeLists.txt b/CMakeLists.txt index 44dfdf79d94..fedc7bcedcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1475,6 +1475,7 @@ add_executable(mixxx-test src/test/beatgridtest.cpp src/test/beatmaptest.cpp src/test/beatstranslatetest.cpp + src/test/bpmtest.cpp src/test/bpmcontrol_test.cpp src/test/broadcastprofile_test.cpp src/test/broadcastsettings_test.cpp diff --git a/src/analyzer/analyzerbeats.cpp b/src/analyzer/analyzerbeats.cpp index 14c91c2566d..1b7dcb0939a 100644 --- a/src/analyzer/analyzerbeats.cpp +++ b/src/analyzer/analyzerbeats.cpp @@ -230,7 +230,7 @@ void AnalyzerBeats::storeResults(TrackPointer pTrack) { qDebug() << "AnalyzerBeats plugin detected" << beats.size() << "beats. Average BPM:" << (pBeats ? pBeats->getBpm() : 0.0); } else { - float bpm = m_pPlugin->getBpm(); + mixxx::Bpm bpm = m_pPlugin->getBpm(); qDebug() << "AnalyzerBeats plugin detected constant BPM: " << bpm; pBeats = BeatFactory::makeBeatGrid(m_sampleRate, bpm, mixxx::audio::kStartFramePos); } diff --git a/src/analyzer/plugins/analyzerplugin.h b/src/analyzer/plugins/analyzerplugin.h index cc6ec54b849..5ebc6ef62a9 100644 --- a/src/analyzer/plugins/analyzerplugin.h +++ b/src/analyzer/plugins/analyzerplugin.h @@ -4,6 +4,7 @@ #include "audio/frame.h" #include "track/beats.h" +#include "track/bpm.h" #include "track/keys.h" #include "util/types.h" @@ -69,8 +70,8 @@ class AnalyzerBeatsPlugin : public AnalyzerPlugin { ~AnalyzerBeatsPlugin() override = default; virtual bool supportsBeatTracking() const = 0; - virtual float getBpm() const { - return 0.0f; + virtual mixxx::Bpm getBpm() const { + return mixxx::Bpm(); } virtual QVector getBeats() const { return {}; diff --git a/src/analyzer/plugins/analyzersoundtouchbeats.cpp b/src/analyzer/plugins/analyzersoundtouchbeats.cpp index 8d122a3b29d..21e4c926d26 100644 --- a/src/analyzer/plugins/analyzersoundtouchbeats.cpp +++ b/src/analyzer/plugins/analyzersoundtouchbeats.cpp @@ -8,15 +8,14 @@ namespace mixxx { AnalyzerSoundTouchBeats::AnalyzerSoundTouchBeats() - : m_downmixBuffer(kAnalysisFramesPerChunk), // mono, i.e. 1 sample per frame - m_fResultBpm(0.0f) { + : m_downmixBuffer(kAnalysisFramesPerChunk) { } AnalyzerSoundTouchBeats::~AnalyzerSoundTouchBeats() { } bool AnalyzerSoundTouchBeats::initialize(int samplerate) { - m_fResultBpm = 0.0f; + m_resultBpm = mixxx::Bpm(); m_pSoundTouch = std::make_unique(2, samplerate); return true; } @@ -42,7 +41,7 @@ bool AnalyzerSoundTouchBeats::finalize() { if (!m_pSoundTouch) { return false; } - m_fResultBpm = m_pSoundTouch->getBpm(); + m_resultBpm = mixxx::Bpm(m_pSoundTouch->getBpm()); m_pSoundTouch.reset(); return true; } diff --git a/src/analyzer/plugins/analyzersoundtouchbeats.h b/src/analyzer/plugins/analyzersoundtouchbeats.h index 1c8e26a359b..34a1b1c8d28 100644 --- a/src/analyzer/plugins/analyzersoundtouchbeats.h +++ b/src/analyzer/plugins/analyzersoundtouchbeats.h @@ -37,14 +37,15 @@ class AnalyzerSoundTouchBeats : public AnalyzerBeatsPlugin { return false; } - float getBpm() const override { - return m_fResultBpm; + mixxx::Bpm getBpm() const override { + return m_resultBpm; } private: std::unique_ptr m_pSoundTouch; + /// mono, i.e. 1 sample per frame SampleBuffer m_downmixBuffer; - float m_fResultBpm; + mixxx::Bpm m_resultBpm; }; } // namespace mixxx diff --git a/src/engine/controls/bpmcontrol.cpp b/src/engine/controls/bpmcontrol.cpp index d72f6ff7921..11b601e0a4c 100644 --- a/src/engine/controls/bpmcontrol.cpp +++ b/src/engine/controls/bpmcontrol.cpp @@ -174,10 +174,10 @@ void BpmControl::adjustBeatsBpm(double deltaBpm) { const mixxx::BeatsPointer pBeats = pTrack->getBeats(); if (pBeats && (pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM)) { double bpm = pBeats->getBpm(); - double centerBpm = math_max(kBpmAdjustMin, bpm + deltaBpm); - double adjustedBpm = BeatUtils::roundBpmWithinRange( + const auto centerBpm = mixxx::Bpm(math_max(kBpmAdjustMin, bpm + deltaBpm)); + mixxx::Bpm adjustedBpm = BeatUtils::roundBpmWithinRange( centerBpm - kBpmAdjustStep / 2, centerBpm, centerBpm + kBpmAdjustStep / 2); - pTrack->trySetBeats(pBeats->setBpm(adjustedBpm)); + pTrack->trySetBeats(pBeats->setBpm(adjustedBpm.getValue())); } } @@ -257,11 +257,11 @@ 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; + auto averageBpm = mixxx::Bpm(60.0 * 1000.0 / averageLength / rateRatio); averageBpm = BeatUtils::roundBpmWithinRange(averageBpm - kBpmTabRounding, averageBpm, averageBpm + kBpmTabRounding); - pTrack->trySetBeats(pBeats->setBpm(averageBpm)); + pTrack->trySetBeats(pBeats->setBpm(averageBpm.getValue())); } void BpmControl::slotControlBeatSyncPhase(double value) { diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 5a7f004c139..4e9e3538aed 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -581,7 +581,8 @@ void bindTrackLibraryValues( QString beatsVersion; QString beatsSubVersion; // Fall back on cached BPM - double dBpm = trackInfo.getBpm().getValue(); + const mixxx::Bpm bpm = trackInfo.getBpm(); + double dBpm = bpm.hasValue() ? bpm.getValue() : mixxx::Bpm::kValueUndefined; if (!pBeats.isNull()) { beatsBlob = pBeats->toByteArray(); beatsVersion = pBeats->getVersion(); @@ -1240,7 +1241,7 @@ bool setTrackAudioProperties( bool setTrackBeats(const QSqlRecord& record, const int column, TrackPointer pTrack) { - double bpm = record.value(column).toDouble(); + const auto bpm = mixxx::Bpm(record.value(column).toDouble()); QString beatsVersion = record.value(column + 1).toString(); QString beatsSubVersion = record.value(column + 2).toString(); QByteArray beatsBlob = record.value(column + 3).toByteArray(); diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index 9e94a003aed..3e9226a70ad 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -44,7 +44,6 @@ DlgTrackInfo::DlgTrackInfo( : QDialog(nullptr), m_pTrackModel(trackModel), m_tapFilter(this, kFilterLength, kMaxInterval), - m_dLastTapedBpm(-1.), m_pWCoverArtLabel(make_parented(this)), m_pWStarRating(make_parented(nullptr, this)) { init(); @@ -583,7 +582,7 @@ void DlgTrackInfo::slotBpmConstChanged(int state) { CuePosition cue = m_pLoadedTrack->getCuePoint(); m_pBeatsClone = BeatFactory::makeBeatGrid(m_pLoadedTrack->getSampleRate(), - spinBpm->value(), + mixxx::Bpm(spinBpm->value()), mixxx::audio::FramePos::fromEngineSamplePos( cue.getPosition())); } else { @@ -602,18 +601,19 @@ void DlgTrackInfo::slotBpmTap(double averageLength, int numSamples) { if (averageLength == 0) { return; } - double averageBpm = 60.0 * 1000.0 / averageLength; + auto averageBpm = mixxx::Bpm(60.0 * 1000.0 / averageLength); averageBpm = BeatUtils::roundBpmWithinRange(averageBpm - kBpmTabRounding, averageBpm, averageBpm + kBpmTabRounding); - if (averageBpm != m_dLastTapedBpm) { - m_dLastTapedBpm = averageBpm; - spinBpm->setValue(averageBpm); + if (averageBpm != m_lastTapedBpm) { + m_lastTapedBpm = averageBpm; + spinBpm->setValue(averageBpm.getValue()); } } void DlgTrackInfo::slotSpinBpmValueChanged(double value) { - if (value <= 0) { + const auto bpm = mixxx::Bpm(value); + if (!bpm.hasValue()) { m_pBeatsClone.clear(); return; } @@ -622,17 +622,17 @@ void DlgTrackInfo::slotSpinBpmValueChanged(double value) { CuePosition cue = m_pLoadedTrack->getCuePoint(); m_pBeatsClone = BeatFactory::makeBeatGrid( m_pLoadedTrack->getSampleRate(), - value, + bpm, mixxx::audio::FramePos::fromEngineSamplePos(cue.getPosition())); } double oldValue = m_pBeatsClone->getBpm(); - if (oldValue == value) { + if (oldValue == bpm.getValue()) { return; } if (m_pBeatsClone->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM) { - m_pBeatsClone = m_pBeatsClone->setBpm(value); + m_pBeatsClone = m_pBeatsClone->setBpm(bpm.getValue()); } // read back the actual value diff --git a/src/library/dlgtrackinfo.h b/src/library/dlgtrackinfo.h index 2991ac0220a..fdb7ccf7905 100644 --- a/src/library/dlgtrackinfo.h +++ b/src/library/dlgtrackinfo.h @@ -115,7 +115,7 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { bool m_trackHasBeatMap; TapFilter m_tapFilter; - double m_dLastTapedBpm; + mixxx::Bpm m_lastTapedBpm; parented_ptr m_pWCoverArtLabel; parented_ptr m_pWStarRating; diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp index ed6776279e3..26b0f00b3e1 100644 --- a/src/test/beatgridtest.cpp +++ b/src/test/beatgridtest.cpp @@ -26,12 +26,12 @@ TEST(BeatGridTest, Scale) { int sampleRate = 44100; TrackPointer pTrack = newTrack(sampleRate); - double bpm = 60.0; + const auto bpm = 60.0; pTrack->trySetBpm(bpm); auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), - bpm, + mixxx::Bpm(bpm), mixxx::audio::kStartFramePos); EXPECT_DOUBLE_EQ(bpm, pGrid->getBpm()); @@ -65,7 +65,7 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) { auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), - bpm, + mixxx::Bpm(bpm), mixxx::audio::kStartFramePos); // Pretend we're on the 20th beat; double position = beatLength * 20; @@ -107,7 +107,7 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), - bpm, + mixxx::Bpm(bpm), mixxx::audio::kStartFramePos); // Pretend we're just before the 20th beat. @@ -151,7 +151,7 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) { auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), - bpm, + mixxx::Bpm(bpm), mixxx::audio::kStartFramePos); // Pretend we're just before the 20th beat. @@ -188,10 +188,10 @@ TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) { int sampleRate = 44100; TrackPointer pTrack = newTrack(sampleRate); - double bpm = 60.1; + const auto bpm = mixxx::Bpm(60.1); const int kFrameSize = 2; - pTrack->trySetBpm(bpm); - double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; + pTrack->trySetBpm(bpm.getValue()); + double beatLength = (60.0 * sampleRate / bpm.getValue()) * kFrameSize; auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), @@ -229,12 +229,12 @@ TEST(BeatGridTest, FromMetadata) { int sampleRate = 44100; TrackPointer pTrack = newTrack(sampleRate); - double bpm = 60.1; - ASSERT_TRUE(pTrack->trySetBpm(bpm)); - EXPECT_DOUBLE_EQ(pTrack->getBpm(), bpm); + const auto bpm = mixxx::Bpm(60.1); + ASSERT_TRUE(pTrack->trySetBpm(bpm.getValue())); + EXPECT_DOUBLE_EQ(pTrack->getBpm(), bpm.getValue()); auto pBeats = pTrack->getBeats(); - EXPECT_DOUBLE_EQ(pBeats->getBpm(), bpm); + EXPECT_DOUBLE_EQ(pBeats->getBpm(), bpm.getValue()); // Invalid bpm resets the bpm ASSERT_TRUE(pTrack->trySetBpm(-60.1)); diff --git a/src/test/beatstranslatetest.cpp b/src/test/beatstranslatetest.cpp index b95d681ef5a..43a5f562d83 100644 --- a/src/test/beatstranslatetest.cpp +++ b/src/test/beatstranslatetest.cpp @@ -7,7 +7,7 @@ class BeatsTranslateTest : public MockedEngineBackendTest { TEST_F(BeatsTranslateTest, SimpleTranslateMatch) { // Set up BeatGrids for decks 1 and 2. - const double bpm = 60.0; + const auto bpm = mixxx::Bpm(60.0); constexpr auto firstBeat = mixxx::audio::kStartFramePos; auto grid1 = mixxx::BeatGrid::makeBeatGrid( m_pTrack1->getSampleRate(), QString(), bpm, firstBeat); @@ -33,8 +33,8 @@ TEST_F(BeatsTranslateTest, SimpleTranslateMatch) { // 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. diff --git a/src/test/bpmcontrol_test.cpp b/src/test/bpmcontrol_test.cpp index 35053cb56ae..1f36f0aed13 100644 --- a/src/test/bpmcontrol_test.cpp +++ b/src/test/bpmcontrol_test.cpp @@ -33,9 +33,9 @@ TEST_F(BpmControlTest, BeatContext_BeatGrid) { mixxx::audio::Bitrate(), mixxx::Duration::fromSeconds(180)); - const double bpm = 60.0; + const auto bpm = mixxx::Bpm(60.0); const int kFrameSize = 2; - const double expectedBeatLength = (60.0 * sampleRate / bpm) * kFrameSize; + const double expectedBeatLength = (60.0 * sampleRate / bpm.getValue()) * kFrameSize; const mixxx::BeatsPointer pBeats = BeatFactory::makeBeatGrid( pTrack->getSampleRate(), bpm, mixxx::audio::kStartFramePos); diff --git a/src/test/bpmtest.cpp b/src/test/bpmtest.cpp new file mode 100644 index 00000000000..6152bf5fecb --- /dev/null +++ b/src/test/bpmtest.cpp @@ -0,0 +1,38 @@ +#include + +#include + +#include "track/bpm.h" + +class BpmTest : public testing::Test { +}; + +TEST_F(BpmTest, TestBpmComparisonOperators) { + EXPECT_EQ(mixxx::Bpm(120), mixxx::Bpm(120)); + EXPECT_EQ(mixxx::Bpm(120), mixxx::Bpm(60) * 2); + EXPECT_EQ(mixxx::Bpm(120), mixxx::Bpm(240) / 2); + + EXPECT_LT(mixxx::Bpm(120.0), mixxx::Bpm(130.0)); + EXPECT_LE(mixxx::Bpm(120.0), mixxx::Bpm(130.0)); + EXPECT_LE(mixxx::Bpm(120.0), mixxx::Bpm(120.0)); + + EXPECT_GT(mixxx::Bpm(130.0), mixxx::Bpm(120.0)); + EXPECT_GE(mixxx::Bpm(130.0), mixxx::Bpm(120.0)); + EXPECT_GE(mixxx::Bpm(130.0), mixxx::Bpm(130.0)); + + // Verify that invalid values are equal to each other, regardless of their + // actual value. + EXPECT_EQ(mixxx::Bpm(mixxx::Bpm::kValueUndefined), mixxx::Bpm()); + EXPECT_EQ(mixxx::Bpm(0.0), mixxx::Bpm()); + EXPECT_EQ(mixxx::Bpm(-120.0), mixxx::Bpm()); + EXPECT_EQ(mixxx::Bpm(-120.0), mixxx::Bpm(0.0)); + EXPECT_EQ(mixxx::Bpm(-120.0), mixxx::Bpm(-100.0)); + + // Here, both values are invalid and therefore equal, so both <= and >= returns true. + EXPECT_LE(mixxx::Bpm(-120.0), mixxx::Bpm(-100.0)); + EXPECT_GE(mixxx::Bpm(-120.0), mixxx::Bpm(-100.0)); + + // Verify that valid and invalid values are not equal + EXPECT_NE(mixxx::Bpm(120.0), mixxx::Bpm()); + EXPECT_NE(mixxx::Bpm(120.0), mixxx::Bpm(-120.0)); +} diff --git a/src/test/enginesynctest.cpp b/src/test/enginesynctest.cpp index c361c667e80..098a4e9a33b 100644 --- a/src/test/enginesynctest.cpp +++ b/src/test/enginesynctest.cpp @@ -245,10 +245,10 @@ 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->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 124, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(124), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonMasterSync1 = @@ -279,13 +279,13 @@ 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->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 124, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(124), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); mixxx::BeatsPointer pBeats3 = BeatFactory::makeBeatGrid( - m_pTrack3->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack3->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack3->trySetBeats(pBeats3); auto pButtonMasterSync1 = @@ -316,7 +316,7 @@ 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->getSampleRate(), 80, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(80), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonMasterSync1 = std::make_unique(m_sGroup1, "sync_mode"); @@ -343,11 +343,11 @@ TEST_F(EngineSyncTest, DisableInternalMasterWhilePlaying) { // Make sure both decks are playing. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 80, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(80), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 80, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(80), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "play"))->set(1.0); ProcessBuffer(); @@ -364,14 +364,14 @@ TEST_F(EngineSyncTest, DisableInternalMasterWhilePlaying) { TEST_F(EngineSyncTest, DisableSyncOnMaster) { // Channel 1 follower, channel 2 master. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncMode1 = std::make_unique(m_sGroup1, "sync_mode"); pButtonSyncMode1->slotSet(SYNC_FOLLOWER); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set deck two to explicit master. auto pButtonSyncMaster2 = @@ -410,7 +410,7 @@ TEST_F(EngineSyncTest, InternalMasterSetFollowerSliderMoves) { // Set the file bpm of channel 1 to 80 bpm. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 80, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(80), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonMasterSync1 = @@ -429,7 +429,7 @@ TEST_F(EngineSyncTest, AnySyncDeckSliderStays) { // master BPM if a new deck enables sync. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 80, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(80), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -441,7 +441,7 @@ TEST_F(EngineSyncTest, AnySyncDeckSliderStays) { ->get()); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); @@ -466,12 +466,12 @@ TEST_F(EngineSyncTest, InternalClockFollowsFirstPlayingDeck) { // Set up decks so they can be playing, and start deck 1. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(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->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::set(ConfigKey(m_sGroup2, "rate"), getRateSliderValue(1.0)); ControlObject::set(ConfigKey(m_sGroup2, "play"), 0.0); @@ -538,11 +538,11 @@ TEST_F(EngineSyncTest, SetExplicitMasterByLights) { // Set the file bpm of channel 1 to 160bpm. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); // Set the file bpm of channel 2 to 150bpm. mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 150, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(150), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -621,7 +621,7 @@ TEST_F(EngineSyncTest, RateChangeTest) { // Set the file bpm of channel 1 to 160bpm. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_DOUBLE_EQ( 160.0, ControlObject::get(ConfigKey(m_sGroup1, "file_bpm"))); @@ -641,7 +641,7 @@ TEST_F(EngineSyncTest, RateChangeTest) { // Set the file bpm of channel 2 to 120bpm. mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ( 120.0, ControlObject::get(ConfigKey(m_sGroup2, "file_bpm"))); @@ -664,14 +664,14 @@ TEST_F(EngineSyncTest, RateChangeTestWeirdOrder) { // Set the file bpm of channel 1 to 160bpm. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_DOUBLE_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->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set the rate slider of channel 1 to 1.2. @@ -690,14 +690,14 @@ TEST_F(EngineSyncTest, RateChangeTestWeirdOrder) { TEST_F(EngineSyncTest, RateChangeTestOrder3) { // Set the file bpm of channel 1 to 160bpm. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_DOUBLE_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->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ( 120.0, ControlObject::get(ConfigKey(m_sGroup2, "file_bpm"))); @@ -738,12 +738,12 @@ TEST_F(EngineSyncTest, FollowerRateChange) { // Set the file bpm of channel 1 to 160bpm. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); // Set the file bpm of channel 2 to 120bpm. mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set the rate slider of channel 1 to 1.2. @@ -786,14 +786,14 @@ TEST_F(EngineSyncTest, InternalRateChangeTest) { // Set the file bpm of channel 1 to 160bpm. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_DOUBLE_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->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ(120.0, ControlObject::getControl(ConfigKey(m_sGroup2, "file_bpm"))->get()); @@ -840,10 +840,10 @@ 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->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonMasterSync1 = @@ -886,7 +886,7 @@ TEST_F(EngineSyncTest, EnableOneDeckInitsMaster) { // Set up the deck to play. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -914,7 +914,7 @@ TEST_F(EngineSyncTest, EnableOneDeckInitsMaster) { // Enable second deck, bpm and beat distance should still match original setting. mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(140), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "rate")) ->set(getRateSliderValue(1.0)); @@ -943,10 +943,10 @@ TEST_F(EngineSyncTest, MomentarySyncAlgorithmTwo) { ConfigValue(EngineSync::PREFER_LOCK_BPM)); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "play"))->set(1.0); @@ -965,7 +965,7 @@ 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->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -1156,7 +1156,7 @@ TEST_F(EngineSyncTest, EnableOneDeckSliderUpdates) { std::make_unique(m_sGroup1, "sync_enabled"); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -1184,12 +1184,12 @@ TEST_F(EngineSyncTest, SyncToNonSyncDeck) { std::make_unique(m_sGroup2, "sync_enabled"); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ProcessBuffer(); ControlObject::set(ConfigKey(m_sGroup1, "rate"), getRateSliderValue(1.0)); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "rate")) ->set(getRateSliderValue(1.0)); @@ -1270,12 +1270,12 @@ TEST_F(EngineSyncTest, MomentarySyncDependsOnPlayingStates) { // Set up decks so they can be playing, and start deck 1. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(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->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::set(ConfigKey(m_sGroup2, "rate"), getRateSliderValue(1.0)); ControlObject::set(ConfigKey(m_sGroup2, "play"), 1.0); @@ -1346,7 +1346,7 @@ TEST_F(EngineSyncTest, EjectTrackSyncRemains) { auto pButtonEject1 = std::make_unique(m_sGroup1, "eject"); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); pButtonSyncEnabled1->set(1.0); ProcessBuffer(); @@ -1372,7 +1372,7 @@ TEST_F(EngineSyncTest, EjectTrackSyncRemains) { EXPECT_DOUBLE_EQ(128.0, ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 135, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(135), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); pButtonSyncEnabled2->set(1.0); ProcessBuffer(); @@ -1393,7 +1393,7 @@ TEST_F(EngineSyncTest, EjectTrackSyncRemains) { TEST_F(EngineSyncTest, FileBpmChangesDontAffectMaster) { // If filebpm changes, don't treat it like a rate change unless it's the master. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -1401,7 +1401,7 @@ TEST_F(EngineSyncTest, FileBpmChangesDontAffectMaster) { ProcessBuffer(); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); @@ -1413,7 +1413,7 @@ TEST_F(EngineSyncTest, FileBpmChangesDontAffectMaster) { // Update the master's beats -- update the internal clock pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_DOUBLE_EQ( 160.0, ControlObject::get(ConfigKey(m_sInternalClockGroup, "bpm"))); @@ -1422,7 +1422,7 @@ TEST_F(EngineSyncTest, FileBpmChangesDontAffectMaster) { // Update follower beats -- don't update internal clock. pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(140), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ( 160.0, ControlObject::get(ConfigKey(m_sInternalClockGroup, "bpm"))); @@ -1435,7 +1435,7 @@ TEST_F(EngineSyncTest, ExplicitMasterPostProcessed) { std::make_unique(m_sGroup1, "sync_mode"); pButtonMasterSync1->slotSet(SYNC_MASTER_EXPLICIT); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ProcessBuffer(); ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); @@ -1450,7 +1450,7 @@ 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->getSampleRate(), 0, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(0), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -1460,7 +1460,7 @@ TEST_F(EngineSyncTest, ZeroBPMRateAdjustIgnored) { ProcessBuffer(); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); @@ -1499,7 +1499,7 @@ TEST_F(EngineSyncTest, DISABLED_BeatDistanceBeforeStart) { // correctly. Unfortunately, this currently doesn't work. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::set(ConfigKey(m_sGroup1, "playposition"), -.05); ControlObject::getControl(ConfigKey(m_sGroup1, "sync_mode")) @@ -1516,10 +1516,10 @@ TEST_F(EngineSyncTest, ZeroLatencyRateChangeNoQuant) { // Confirm that a rate change in an explicit master is instantly communicated // to followers. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Make Channel2 master to weed out any channel ordering issues. @@ -1566,10 +1566,10 @@ TEST_F(EngineSyncTest, ZeroLatencyRateChangeQuant) { // Confirm that a rate change in an explicit master is instantly communicated // to followers. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0); @@ -1621,10 +1621,10 @@ TEST_F(EngineSyncTest, ZeroLatencyRateDiffQuant) { // Confirm that a rate change in an explicit master is instantly communicated // to followers. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 160, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(160), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "quantize"))->set(1.0); @@ -1677,10 +1677,10 @@ TEST_F(EngineSyncTest, ZeroLatencyRateDiffQuant) { // This test exercises https://bugs.launchpad.net/mixxx/+bug/1884324 TEST_F(EngineSyncTest, ActivatingSyncDoesNotCauseDrifting) { mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 150, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(150), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 150, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(150), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(0.0); @@ -1723,10 +1723,10 @@ TEST_F(EngineSyncTest, ActivatingSyncDoesNotCauseDrifting) { TEST_F(EngineSyncTest, HalfDoubleBpmTest) { mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 70, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(70), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(140), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Mixxx will choose the first playing deck to be master. Let's start deck 2 first. @@ -1822,10 +1822,10 @@ 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->getSampleRate(), 80, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(80), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 175, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(175), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -1947,10 +1947,10 @@ 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->getSampleRate(), 70, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(70), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(140), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0); @@ -2024,10 +2024,10 @@ TEST_F(EngineSyncTest, HalfDoubleEachOther) { // Half/Double decision. // This test demonstrates https://bugs.launchpad.net/mixxx/+bug/1921962 mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 144, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(144), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 105, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(105), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Threshold 1.414 sqrt(2); @@ -2053,7 +2053,7 @@ TEST_F(EngineSyncTest, HalfDoubleEachOther) { // expect 75 BPM mixxx::BeatsPointer pBeats1b = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 150, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(150), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1b); EXPECT_DOUBLE_EQ(150.0, @@ -2075,7 +2075,7 @@ TEST_F(EngineSyncTest, HalfDoubleEachOther) { TEST_F(EngineSyncTest, SetFileBpmUpdatesLocalBpm) { ControlObject::getControl(ConfigKey(m_sGroup1, "beat_distance"))->set(0.2); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_EQ( 130.0, m_pEngineSync->getSyncableForGroup(m_sGroup1)->getBaseBpm()); @@ -2088,7 +2088,7 @@ TEST_F(EngineSyncTest, SyncPhaseToPlayingNonSyncDeck) { auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0); @@ -2096,7 +2096,7 @@ TEST_F(EngineSyncTest, SyncPhaseToPlayingNonSyncDeck) { std::make_unique(m_sGroup2, "sync_enabled"); ControlObject::set(ConfigKey(m_sGroup2, "rate_ratio"), 1.0); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set the sync deck playing with nothing else active. @@ -2180,7 +2180,7 @@ TEST_F(EngineSyncTest, SyncPhaseToPlayingNonSyncDeck) { 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->getSampleRate(), 140, mixxx::audio::kStartFramePos); + m_pTrack3->getSampleRate(), mixxx::Bpm(140), mixxx::audio::kStartFramePos); m_pTrack3->trySetBeats(pBeats3); // This will sync to the first deck here and not the second (lp1784185) pButtonSyncEnabled3->set(1.0); @@ -2226,10 +2226,10 @@ TEST_F(EngineSyncTest, UserTweakBeatDistance) { // is used to reseed the master beat distance, make sure the user offset // is reset. mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0); @@ -2281,10 +2281,10 @@ 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->getSampleRate(), kDivisibleBpm, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(kDivisibleBpm), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "sync_enabled"))->set(1); @@ -2362,10 +2362,10 @@ TEST_F(EngineSyncTest, FollowerUserTweakPreservedInMasterChange) { // 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->getSampleRate(), kDivisibleBpm, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(kDivisibleBpm), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "sync_master"))->set(1); @@ -2415,10 +2415,10 @@ TEST_F(EngineSyncTest, MasterUserTweakPreservedInMasterChange) { // 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->getSampleRate(), kDivisibleBpm, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(kDivisibleBpm), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "sync_master"))->set(1); @@ -2464,7 +2464,7 @@ TEST_F(EngineSyncTest, MasterUserTweakPreservedInMasterChange) { TEST_F(EngineSyncTest, MasterBpmNeverZero) { mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -2480,7 +2480,7 @@ TEST_F(EngineSyncTest, ZeroBpmNaturalRate) { // 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->getSampleRate(), 0.0, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(0.0), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -2500,12 +2500,12 @@ TEST_F(EngineSyncTest, QuantizeImpliesSyncPhase) { auto pButtonBeatsyncPhase1 = std::make_unique(m_sGroup1, "beatsync_phase"); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::set(ConfigKey(m_sGroup2, "rate"), getRateSliderValue(1.0)); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ProcessBuffer(); @@ -2598,7 +2598,7 @@ TEST_F(EngineSyncTest, SeekStayInPhase) { ControlObject::set(ConfigKey(m_sGroup1, "quantize"), 1.0); mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -2621,7 +2621,7 @@ TEST_F(EngineSyncTest, SeekStayInPhase) { ProcessBuffer(); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -2644,7 +2644,7 @@ 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->getSampleRate(), 128, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(128), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); m_pTrack2->trySetBeats(mixxx::BeatsPointer()); @@ -2663,12 +2663,12 @@ TEST_F(EngineSyncTest, SyncWithoutBeatgrid) { TEST_F(EngineSyncTest, QuantizeHotCueActivate) { mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pHotCue2Activate = std::make_unique(m_sGroup2, "hotcue_1_activate"); mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(100), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); @@ -2730,7 +2730,7 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) { // set beatgrid for deck 1 mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(130), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); pButtonSyncEnabled1->set(1.0); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -2764,7 +2764,7 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) { // Load a new beatgrid during playing, this happens when the analyser is finished. mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(140), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ProcessBuffer(); @@ -2789,7 +2789,7 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) { // Load a new beatgrid again, this happens when the user adjusts the beatgrid mixxx::BeatsPointer pBeats2n = BeatFactory::makeBeatGrid( - m_pTrack2->getSampleRate(), 75, mixxx::audio::kStartFramePos); + m_pTrack2->getSampleRate(), mixxx::Bpm(75), mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2n); ProcessBuffer(); @@ -2805,7 +2805,7 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) { TEST_F(EngineSyncTest, BeatMapQuantizePlay) { // This test demonstates https://bugs.launchpad.net/mixxx/+bug/1874918 mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( - m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); + m_pTrack1->getSampleRate(), mixxx::Bpm(120), mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); constexpr auto kSampleRate = mixxx::audio::SampleRate(44100); diff --git a/src/test/seratobeatgridtest.cpp b/src/test/seratobeatgridtest.cpp index 7895fde0bb2..988304044c5 100644 --- a/src/test/seratobeatgridtest.cpp +++ b/src/test/seratobeatgridtest.cpp @@ -113,7 +113,7 @@ TEST_F(SeratoBeatGridTest, ParseEmptyDataFLAC) { TEST_F(SeratoBeatGridTest, SerializeBeatgrid) { // Create a const beatgrid at 120 BPM - constexpr double bpm = 120.0; + const auto bpm = mixxx::Bpm(120.0); const auto sampleRate = mixxx::audio::SampleRate(44100); EXPECT_EQ(sampleRate.isValid(), true); const auto pBeats = mixxx::BeatGrid::makeBeatGrid( @@ -126,7 +126,7 @@ TEST_F(SeratoBeatGridTest, SerializeBeatgrid) { seratoBeatGrid.setBeats(pBeats, signalInfo, duration, 0); EXPECT_EQ(seratoBeatGrid.nonTerminalMarkers().size(), 0); EXPECT_NE(seratoBeatGrid.terminalMarker(), nullptr); - EXPECT_FLOAT_EQ(seratoBeatGrid.terminalMarker()->bpm(), static_cast(bpm)); + EXPECT_FLOAT_EQ(seratoBeatGrid.terminalMarker()->bpm(), static_cast(bpm.getValue())); } TEST_F(SeratoBeatGridTest, SerializeBeatMap) { diff --git a/src/track/beatfactory.cpp b/src/track/beatfactory.cpp index 26c4a8801f2..d79b797c922 100644 --- a/src/track/beatfactory.cpp +++ b/src/track/beatfactory.cpp @@ -33,9 +33,9 @@ mixxx::BeatsPointer BeatFactory::loadBeatsFromByteArray( mixxx::BeatsPointer BeatFactory::makeBeatGrid( mixxx::audio::SampleRate sampleRate, - double dBpm, + mixxx::Bpm bpm, mixxx::audio::FramePos firstBeatFramePos) { - return mixxx::BeatGrid::makeBeatGrid(sampleRate, QString(), dBpm, firstBeatFramePos); + return mixxx::BeatGrid::makeBeatGrid(sampleRate, QString(), bpm, firstBeatFramePos); } // static @@ -102,7 +102,8 @@ mixxx::BeatsPointer BeatFactory::makePreferredBeats( if (version == BEAT_GRID_2_VERSION) { mixxx::audio::FramePos firstBeat = mixxx::audio::kStartFramePos; - double constBPM = BeatUtils::makeConstBpm(constantRegions, sampleRate, &firstBeat); + const mixxx::Bpm constBPM = BeatUtils::makeConstBpm( + constantRegions, sampleRate, &firstBeat); firstBeat = BeatUtils::adjustPhase(firstBeat, constBPM, sampleRate, beats); auto pGrid = mixxx::BeatGrid::makeBeatGrid( sampleRate, subVersion, constBPM, firstBeat); diff --git a/src/track/beatfactory.h b/src/track/beatfactory.h index 53b20345be7..b2b71b2a7d0 100644 --- a/src/track/beatfactory.h +++ b/src/track/beatfactory.h @@ -5,6 +5,7 @@ #include "audio/frame.h" #include "audio/types.h" #include "track/beats.h" +#include "track/bpm.h" class Track; @@ -17,7 +18,7 @@ class BeatFactory { const QByteArray& beatsSerialized); static mixxx::BeatsPointer makeBeatGrid( mixxx::audio::SampleRate sampleRate, - double dBpm, + mixxx::Bpm bpm, mixxx::audio::FramePos firstBeatFramePos); static QString getPreferredVersion(bool fixedTempo); diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index b6cce7435c4..7c5a59aa242 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -73,19 +73,20 @@ BeatGrid::BeatGrid(const BeatGrid& other) BeatsPointer BeatGrid::makeBeatGrid( audio::SampleRate sampleRate, const QString& subVersion, - double dBpm, + mixxx::Bpm bpm, mixxx::audio::FramePos firstBeatPos) { - if (dBpm < 0) { - dBpm = 0.0; + // FIXME: Should this be a debug assertion? + if (!bpm.hasValue()) { + return nullptr; } mixxx::track::io::BeatGrid grid; - grid.mutable_bpm()->set_bpm(dBpm); + grid.mutable_bpm()->set_bpm(bpm.getValue()); grid.mutable_first_beat()->set_frame_position( static_cast(firstBeatPos.value())); // Calculate beat length as sample offsets - double beatLength = (60.0 * sampleRate / dBpm) * kFrameSize; + double beatLength = (60.0 * sampleRate / bpm.getValue()) * kFrameSize; return BeatsPointer(new BeatGrid(sampleRate, subVersion, grid, beatLength)); } @@ -107,8 +108,9 @@ BeatsPointer BeatGrid::makeBeatGrid( } const BeatGridData* blob = reinterpret_cast(byteArray.constData()); const auto firstBeat = mixxx::audio::FramePos(blob->firstBeat); + const auto bpm = mixxx::Bpm(blob->bpm); - return makeBeatGrid(sampleRate, subVersion, blob->bpm, firstBeat); + return makeBeatGrid(sampleRate, subVersion, bpm, firstBeat); } QByteArray BeatGrid::toByteArray() const { @@ -305,7 +307,7 @@ BeatsPointer BeatGrid::translate(double dNumSamples) const { BeatsPointer BeatGrid::scale(enum BPMScale scale) const { mixxx::track::io::BeatGrid grid = m_grid; - double bpm = grid.bpm().bpm(); + auto bpm = mixxx::Bpm(grid.bpm().bpm()); switch (scale) { case DOUBLE: @@ -331,13 +333,14 @@ BeatsPointer BeatGrid::scale(enum BPMScale scale) const { return BeatsPointer(new BeatGrid(*this)); } - if (bpm > getMaxBpm()) { + // TODO: Check if we can remove getMaxBpm and replace it with Bpm::hasValue instead + if (bpm.getValue() > getMaxBpm()) { return BeatsPointer(new BeatGrid(*this)); } bpm = BeatUtils::roundBpmWithinRange(bpm - kBpmScaleRounding, bpm, bpm + kBpmScaleRounding); - grid.mutable_bpm()->set_bpm(bpm); - double beatLength = (60.0 * m_sampleRate / bpm) * kFrameSize; + grid.mutable_bpm()->set_bpm(bpm.getValue()); + double beatLength = (60.0 * m_sampleRate / bpm.getValue()) * kFrameSize; return BeatsPointer(new BeatGrid(*this, grid, beatLength)); } diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h index e32a49c9a72..5574fb075e9 100644 --- a/src/track/beatgrid.h +++ b/src/track/beatgrid.h @@ -3,6 +3,7 @@ #include "audio/frame.h" #include "proto/beats.pb.h" #include "track/beats.h" +#include "track/bpm.h" #define BEAT_GRID_1_VERSION "BeatGrid-1.0" #define BEAT_GRID_2_VERSION "BeatGrid-2.0" @@ -21,7 +22,7 @@ class BeatGrid final : public Beats { static BeatsPointer makeBeatGrid( audio::SampleRate sampleRate, const QString& subVersion, - double dBpm, + mixxx::Bpm bpm, mixxx::audio::FramePos firstBeatPos); static BeatsPointer makeBeatGrid( diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index b30ef51c2ed..e79dd1f0563 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -139,7 +139,7 @@ void scaleFourth(BeatList* pBeats) { } } -double calculateNominalBpm(const BeatList& beats, mixxx::audio::SampleRate sampleRate) { +mixxx::Bpm calculateNominalBpm(const BeatList& beats, mixxx::audio::SampleRate sampleRate) { QVector beatvect; beatvect.reserve(beats.size()); for (const auto& beat : beats) { @@ -149,7 +149,7 @@ double calculateNominalBpm(const BeatList& beats, mixxx::audio::SampleRate sampl } if (beatvect.size() < 2) { - return -1; + return mixxx::Bpm(); } return BeatUtils::calculateBpm(beatvect, mixxx::audio::SampleRate(sampleRate)); @@ -215,7 +215,7 @@ BeatsPointer BeatMap::makeBeatMap( audio::SampleRate sampleRate, const QString& subVersion, const QByteArray& byteArray) { - double nominalBpm = 0.0; + auto nominalBpm = mixxx::Bpm(); BeatList beatList; track::io::BeatMap map; @@ -229,7 +229,7 @@ BeatsPointer BeatMap::makeBeatMap( qDebug() << "ERROR: Could not parse BeatMap from QByteArray of size" << byteArray.size(); } - return BeatsPointer(new BeatMap(sampleRate, subVersion, beatList, nominalBpm)); + return BeatsPointer(new BeatMap(sampleRate, subVersion, beatList, nominalBpm.getValue())); } // static @@ -260,8 +260,8 @@ BeatsPointer BeatMap::makeBeatMap( beatList.append(beat); previousBeatPos = beatPos; } - double nominalBpm = calculateNominalBpm(beatList, sampleRate); - return BeatsPointer(new BeatMap(sampleRate, subVersion, beatList, nominalBpm)); + const auto nominalBpm = calculateNominalBpm(beatList, sampleRate); + return BeatsPointer(new BeatMap(sampleRate, subVersion, beatList, nominalBpm.getValue())); } QByteArray BeatMap::toByteArray() const { @@ -580,7 +580,9 @@ double BeatMap::getBpmAroundPosition(double curSample, int n) const { } } - return BeatUtils::calculateAverageBpm(numberOfBeats, m_sampleRate, lowerFrame, upperFrame); + return BeatUtils::calculateAverageBpm( + numberOfBeats, m_sampleRate, lowerFrame, upperFrame) + .getValue(); } BeatsPointer BeatMap::translate(double dNumSamples) const { @@ -652,7 +654,7 @@ BeatsPointer BeatMap::scale(enum BPMScale scale) const { return BeatsPointer(new BeatMap(*this)); } - double bpm = calculateNominalBpm(beats, m_sampleRate); + double bpm = calculateNominalBpm(beats, m_sampleRate).getValue(); return BeatsPointer(new BeatMap(*this, beats, bpm)); } diff --git a/src/track/beatutils.cpp b/src/track/beatutils.cpp index 83e4df76ab6..a744a56c229 100644 --- a/src/track/beatutils.cpp +++ b/src/track/beatutils.cpp @@ -6,7 +6,6 @@ #include #include -#include "track/bpm.h" #include "util/math.h" namespace { @@ -23,23 +22,23 @@ constexpr int kMinRegionBeatCount = 16; } // namespace -double BeatUtils::calculateAverageBpm(int numberOfBeats, +mixxx::Bpm BeatUtils::calculateAverageBpm(int numberOfBeats, mixxx::audio::SampleRate sampleRate, mixxx::audio::FramePos lowerFrame, mixxx::audio::FramePos upperFrame) { mixxx::audio::FrameDiff_t frames = upperFrame - lowerFrame; DEBUG_ASSERT(frames > 0); if (numberOfBeats < 1) { - return 0; + return mixxx::Bpm(); } - return 60.0 * numberOfBeats * sampleRate / frames; + return mixxx::Bpm(60.0 * numberOfBeats * sampleRate / frames); } -double BeatUtils::calculateBpm( +mixxx::Bpm BeatUtils::calculateBpm( const QVector& beats, mixxx::audio::SampleRate sampleRate) { if (beats.size() < 2) { - return 0; + return mixxx::Bpm(); } // If we don't have enough beats for our regular approach, just divide the # @@ -141,7 +140,7 @@ QVector BeatUtils::retrieveConstRegions( } // static -double BeatUtils::makeConstBpm( +mixxx::Bpm BeatUtils::makeConstBpm( const QVector& constantRegions, mixxx::audio::SampleRate sampleRate, mixxx::audio::FramePos* pFirstBeat) { @@ -178,7 +177,7 @@ double BeatUtils::makeConstBpm( if (longestRegionLength == 0) { // no betas, we default to - return 128; + return mixxx::Bpm(128); } int longestRegionNumberOfBeats = static_cast( @@ -300,13 +299,13 @@ double BeatUtils::makeConstBpm( // Create a const region region form the first beat of the first region to the last beat of the last region. - const double minRoundBpm = 60.0 * sampleRate / longestRegionBeatLengthMax; - const double maxRoundBpm = 60.0 * sampleRate / longestRegionBeatLengthMin; - const double centerBpm = 60.0 * sampleRate / longestRegionBeatLength; + const mixxx::Bpm minRoundBpm = mixxx::Bpm(60.0 * sampleRate / longestRegionBeatLengthMax); + const mixxx::Bpm maxRoundBpm = mixxx::Bpm(60.0 * sampleRate / longestRegionBeatLengthMin); + const mixxx::Bpm centerBpm = mixxx::Bpm(60.0 * sampleRate / longestRegionBeatLength); //qDebug() << "minRoundBpm" << minRoundBpm; //qDebug() << "maxRoundBpm" << maxRoundBpm; - const double roundBpm = roundBpmWithinRange(minRoundBpm, centerBpm, maxRoundBpm); + const mixxx::Bpm roundBpm = roundBpmWithinRange(minRoundBpm, centerBpm, maxRoundBpm); if (pFirstBeat) { // Move the first beat as close to the start of the track as we can. This is @@ -314,7 +313,7 @@ double BeatUtils::makeConstBpm( // bpm adjustments are made. // This is a temporary fix, ideally the anchor point for the BPM grid should // be the first proper downbeat, or perhaps the CUE point. - const double roundedBeatLength = 60.0 * sampleRate / roundBpm; + const double roundedBeatLength = 60.0 * sampleRate / roundBpm.getValue(); *pFirstBeat = mixxx::audio::FramePos( fmod(constantRegions[startRegionIndex].firstBeat.value(), roundedBeatLength)); @@ -323,9 +322,10 @@ double BeatUtils::makeConstBpm( } // static -double BeatUtils::roundBpmWithinRange(double minBpm, double centerBpm, double maxBpm) { +mixxx::Bpm BeatUtils::roundBpmWithinRange( + mixxx::Bpm minBpm, mixxx::Bpm centerBpm, mixxx::Bpm maxBpm) { // First try to snap to a full integer BPM - double snapBpm = round(centerBpm); + auto snapBpm = mixxx::Bpm(round(centerBpm.getValue())); if (snapBpm > minBpm && snapBpm < maxBpm) { // Success return snapBpm; @@ -336,23 +336,23 @@ double BeatUtils::roundBpmWithinRange(double minBpm, double centerBpm, double ma if (roundBpmWidth > 0.5) { // 0.5 BPM are only reasonable if the double value is not insane // or the 2/3 value is not too small. - if (centerBpm < 85.0) { + if (centerBpm < mixxx::Bpm(85.0)) { // this cane be actually up to 175 BPM // allow halve BPM values - return round(centerBpm * 2) / 2; - } else if (centerBpm > 127.0) { + return mixxx::Bpm(round(centerBpm.getValue() * 2) / 2); + } else if (centerBpm > mixxx::Bpm(127.0)) { // optimize for 2/3 going down to 85 - return round(centerBpm / 3 * 2) * 3 / 2; + return mixxx::Bpm(round(centerBpm.getValue() / 3 * 2) * 3 / 2); } } if (roundBpmWidth > 1.0 / 12) { // this covers all sorts of 1/2 2/3 and 3/4 multiplier - return round(centerBpm * 12) / 12; + return mixxx::Bpm(round(centerBpm.getValue() * 12) / 12); } else { // We are here if we have more that ~75 beats and ~30 s // try to snap to a 1/12 Bpm - snapBpm = round(centerBpm * 12) / 12; + snapBpm = mixxx::Bpm(round(centerBpm.getValue() * 12) / 12); if (snapBpm > minBpm && snapBpm < maxBpm) { // Success return snapBpm; @@ -384,10 +384,10 @@ QVector BeatUtils::getBeats( // static mixxx::audio::FramePos BeatUtils::adjustPhase( mixxx::audio::FramePos firstBeat, - double bpm, + mixxx::Bpm bpm, mixxx::audio::SampleRate sampleRate, const QVector& beats) { - const double beatLength = 60 * sampleRate / bpm; + const double beatLength = 60 * sampleRate / bpm.getValue(); const mixxx::audio::FramePos startOffset = mixxx::audio::FramePos(fmod(firstBeat.value(), beatLength)); mixxx::audio::FrameDiff_t offsetAdjust = 0; diff --git a/src/track/beatutils.h b/src/track/beatutils.h index 4b05451f29d..698c7848e5b 100644 --- a/src/track/beatutils.h +++ b/src/track/beatutils.h @@ -4,6 +4,7 @@ #include "audio/frame.h" #include "audio/types.h" +#include "track/bpm.h" #include "util/math.h" class BeatUtils { @@ -13,30 +14,31 @@ class BeatUtils { mixxx::audio::FrameDiff_t beatLength; }; - static double calculateBpm(const QVector& beats, + static mixxx::Bpm calculateBpm(const QVector& beats, mixxx::audio::SampleRate sampleRate); static QVector retrieveConstRegions( const QVector& coarseBeats, mixxx::audio::SampleRate sampleRate); - static double calculateAverageBpm(int numberOfBeats, + static mixxx::Bpm calculateAverageBpm(int numberOfBeats, mixxx::audio::SampleRate sampleRate, mixxx::audio::FramePos lowerFrame, mixxx::audio::FramePos upperFrame); - static double makeConstBpm( + static mixxx::Bpm makeConstBpm( const QVector& constantRegions, mixxx::audio::SampleRate sampleRate, mixxx::audio::FramePos* pFirstBeat); static mixxx::audio::FramePos adjustPhase( mixxx::audio::FramePos firstBeat, - double bpm, + mixxx::Bpm bpm, mixxx::audio::SampleRate sampleRate, const QVector& beats); static QVector getBeats(const QVector& constantRegions); - static double roundBpmWithinRange(double minBpm, double centerBpm, double maxBpm); + static mixxx::Bpm roundBpmWithinRange( + mixxx::Bpm minBpm, mixxx::Bpm centerBpm, mixxx::Bpm maxBpm); }; diff --git a/src/track/bpm.h b/src/track/bpm.h index 9ca47c6cb82..c56b44ecd6a 100644 --- a/src/track/bpm.h +++ b/src/track/bpm.h @@ -2,6 +2,7 @@ #include +#include "util/fpclassify.h" #include "util/math.h" namespace mixxx { @@ -37,13 +38,16 @@ class Bpm final { } static bool isValidValue(double value) { - return kValueMin < value; + return util_isfinite(value) && kValueMin < value; } bool hasValue() const { return isValidValue(m_value); } double getValue() const { + VERIFY_OR_DEBUG_ASSERT(hasValue()) { + return kValueUndefined; + } return m_value; } void setValue(double value) { @@ -68,6 +72,17 @@ class Bpm final { bool compareEq( const Bpm& bpm, Comparison cmp = Comparison::Default) const { + if (!hasValue() && !bpm.hasValue()) { + // Both values are invalid and thus equal. + return true; + } + + if (hasValue() != bpm.hasValue()) { + // One value is valid, one is not. + return false; + } + + // At this point both values are valid switch (cmp) { case Comparison::Integer: return Bpm::valueToInteger(getValue()) == Bpm::valueToInteger(bpm.getValue()); @@ -83,25 +98,95 @@ class Bpm final { return displayValueText(m_value); } + Bpm& operator+=(double increment) { + DEBUG_ASSERT(hasValue()); + m_value += increment; + return *this; + } + + Bpm& operator-=(double decrement) { + DEBUG_ASSERT(hasValue()); + m_value -= decrement; + return *this; + } + + Bpm& operator*=(double multiple) { + DEBUG_ASSERT(hasValue()); + m_value *= multiple; + return *this; + } + + Bpm& operator/=(double divisor) { + DEBUG_ASSERT(hasValue()); + m_value /= divisor; + return *this; + } + private: double m_value; }; -inline -bool operator==(const Bpm& lhs, const Bpm& rhs) { - return lhs.compareEq(rhs); +/// Bpm can be added to a double +inline Bpm operator+(Bpm bpm, double bpmDiff) { + return Bpm(bpm.getValue() + bpmDiff); } -inline -bool operator!=(const Bpm& lhs, const Bpm& rhs) { - return !(lhs == rhs); +/// Bpm can be subtracted from a double +inline Bpm operator-(Bpm bpm, double bpmDiff) { + return Bpm(bpm.getValue() - bpmDiff); } -inline -QDebug operator<<(QDebug dbg, const Bpm& arg) { - return dbg << arg.getValue(); +/// Two Bpm values can be subtracted to get a double +inline double operator-(Bpm bpm1, Bpm bpm2) { + return bpm1.getValue() - bpm2.getValue(); } +// Adding two Bpm is not allowed, because it makes no sense semantically. + +/// Bpm can be multiplied or divided by a double +inline Bpm operator*(Bpm bpm, double multiple) { + return Bpm(bpm.getValue() * multiple); +} + +inline Bpm operator/(Bpm bpm, double divisor) { + return Bpm(bpm.getValue() / divisor); +} + +inline bool operator==(Bpm bpm1, Bpm bpm2) { + if (!bpm1.hasValue() && !bpm2.hasValue()) { + return true; + } + return bpm1.hasValue() && bpm2.hasValue() && bpm1.getValue() == bpm2.getValue(); +} + +inline bool operator!=(Bpm bpm1, Bpm bpm2) { + return !(bpm1 == bpm2); +} + +inline bool operator<(Bpm bpm1, Bpm bpm2) { + return bpm1.getValue() < bpm2.getValue(); +} + +inline bool operator<=(Bpm bpm1, Bpm bpm2) { + return (bpm1 == bpm2) || bpm1 < bpm2; +} + +inline bool operator>(Bpm bpm1, Bpm bpm2) { + return bpm2 < bpm1; +} + +inline bool operator>=(Bpm bpm1, Bpm bpm2) { + return bpm2 <= bpm1; +} + +inline QDebug operator<<(QDebug dbg, Bpm arg) { + if (arg.hasValue()) { + dbg.nospace() << "Bpm(" << arg.getValue() << ")"; + } else { + dbg << "Bpm(Invalid)"; + } + return dbg; +} } Q_DECLARE_TYPEINFO(mixxx::Bpm, Q_MOVABLE_TYPE); diff --git a/src/track/track.cpp b/src/track/track.cpp index 6829658640f..159fae560f9 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -171,7 +171,8 @@ void Track::replaceMetadataFromSource( // Need to set BPM after sample rate since beat grid creation depends on // knowing the sample rate. Bug #1020438. auto beatsAndBpmModified = false; - if (!m_pBeats || !mixxx::Bpm::isValidValue(m_pBeats->getBpm())) { + if (importedBpm.hasValue() && + (!m_pBeats || !mixxx::Bpm::isValidValue(m_pBeats->getBpm()))) { // Only use the imported BPM if the current beat grid is either // missing or not valid! The BPM value in the metadata might be // imprecise (normalized or rounded), e.g. ID3v2 only supports @@ -338,7 +339,8 @@ mixxx::Bpm Track::getBpmWhileLocked() const { } bool Track::trySetBpmWhileLocked(double bpmValue) { - if (!mixxx::Bpm::isValidValue(bpmValue)) { + const auto bpm = mixxx::Bpm(bpmValue); + if (!bpm.hasValue()) { // If the user sets the BPM to an invalid value, we assume // they want to clear the beatgrid. return trySetBeatsWhileLocked(nullptr); @@ -346,23 +348,24 @@ bool Track::trySetBpmWhileLocked(double bpmValue) { // No beat grid available -> create and initialize double cue = m_record.getCuePoint().getPosition(); auto pBeats = BeatFactory::makeBeatGrid(getSampleRate(), - bpmValue, + bpm, mixxx::audio::FramePos::fromEngineSamplePos(cue)); return trySetBeatsWhileLocked(std::move(pBeats)); } else if ((m_pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM) && - m_pBeats->getBpm() != bpmValue) { + m_pBeats->getBpm() != bpm.getValue()) { // Continue with the regular cases if (kLogger.debugEnabled()) { kLogger.debug() << "Updating BPM:" << getLocation(); } - return trySetBeatsWhileLocked(m_pBeats->setBpm(bpmValue)); + return trySetBeatsWhileLocked(m_pBeats->setBpm(bpm.getValue())); } return false; } double Track::getBpm() const { const QMutexLocker lock(&m_qMutex); - return getBpmWhileLocked().getValue(); + const mixxx::Bpm bpm = getBpmWhileLocked(); + return bpm.hasValue() ? bpm.getValue() : mixxx::Bpm::kValueUndefined; } bool Track::trySetBpm(double bpmValue) {