diff --git a/CMakeLists.txt b/CMakeLists.txt index 6246f78af9c..44dfdf79d94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1508,6 +1508,7 @@ add_executable(mixxx-test src/test/enginemicrophonetest.cpp src/test/enginesynctest.cpp src/test/fileinfo_test.cpp + src/test/frametest.cpp src/test/globaltrackcache_test.cpp src/test/hotcuecontrol_test.cpp src/test/imageutils_test.cpp diff --git a/src/analyzer/analyzerbeats.cpp b/src/analyzer/analyzerbeats.cpp index 4317d616bea..14c91c2566d 100644 --- a/src/analyzer/analyzerbeats.cpp +++ b/src/analyzer/analyzerbeats.cpp @@ -219,7 +219,7 @@ void AnalyzerBeats::storeResults(TrackPointer pTrack) { mixxx::BeatsPointer pBeats; if (m_pPlugin->supportsBeatTracking()) { - QVector beats = m_pPlugin->getBeats(); + QVector beats = m_pPlugin->getBeats(); QHash extraVersionInfo = getExtraVersionInfo( m_pluginId, m_bPreferencesFastAnalysis); pBeats = BeatFactory::makePreferredBeats( @@ -232,7 +232,7 @@ void AnalyzerBeats::storeResults(TrackPointer pTrack) { } else { float bpm = m_pPlugin->getBpm(); qDebug() << "AnalyzerBeats plugin detected constant BPM: " << bpm; - pBeats = BeatFactory::makeBeatGrid(m_sampleRate, bpm, 0.0f); + pBeats = BeatFactory::makeBeatGrid(m_sampleRate, bpm, mixxx::audio::kStartFramePos); } pTrack->trySetBeats(pBeats); diff --git a/src/analyzer/plugins/analyzerplugin.h b/src/analyzer/plugins/analyzerplugin.h index 18298a98a85..cc6ec54b849 100644 --- a/src/analyzer/plugins/analyzerplugin.h +++ b/src/analyzer/plugins/analyzerplugin.h @@ -2,6 +2,7 @@ #include +#include "audio/frame.h" #include "track/beats.h" #include "track/keys.h" #include "util/types.h" @@ -71,8 +72,8 @@ class AnalyzerBeatsPlugin : public AnalyzerPlugin { virtual float getBpm() const { return 0.0f; } - virtual QVector getBeats() const { - return QVector(); + virtual QVector getBeats() const { + return {}; } }; diff --git a/src/analyzer/plugins/analyzerqueenmarybeats.cpp b/src/analyzer/plugins/analyzerqueenmarybeats.cpp index 47ba8a13479..7ee5ba5d871 100644 --- a/src/analyzer/plugins/analyzerqueenmarybeats.cpp +++ b/src/analyzer/plugins/analyzerqueenmarybeats.cpp @@ -22,12 +22,12 @@ constexpr float kStepSecs = 0.01161f; // results in 43 Hz @ 44.1 kHz / 47 Hz @ 48 kHz / 47 Hz @ 96 kHz constexpr int kMaximumBinSizeHz = 50; // Hz -DFConfig makeDetectionFunctionConfig(int stepSize, int windowSize) { +DFConfig makeDetectionFunctionConfig(int stepSizeFrames, int windowSize) { // These are the defaults for the VAMP beat tracker plugin we used in Mixxx // 2.0. DFConfig config; config.DFType = DF_COMPLEXSD; - config.stepSize = stepSize; + config.stepSize = stepSizeFrames; config.frameLength = windowSize; config.dbRise = 3; config.adaptiveWhitening = false; @@ -41,7 +41,7 @@ DFConfig makeDetectionFunctionConfig(int stepSize, int windowSize) { AnalyzerQueenMaryBeats::AnalyzerQueenMaryBeats() : m_iSampleRate(0), m_windowSize(0), - m_stepSize(0) { + m_stepSizeFrames(0) { } AnalyzerQueenMaryBeats::~AnalyzerQueenMaryBeats() { @@ -50,14 +50,14 @@ AnalyzerQueenMaryBeats::~AnalyzerQueenMaryBeats() { bool AnalyzerQueenMaryBeats::initialize(int samplerate) { m_detectionResults.clear(); m_iSampleRate = samplerate; - m_stepSize = static_cast(m_iSampleRate * kStepSecs); + m_stepSizeFrames = static_cast(m_iSampleRate * kStepSecs); m_windowSize = MathUtilities::nextPowerOfTwo(m_iSampleRate / kMaximumBinSizeHz); m_pDetectionFunction = std::make_unique( - makeDetectionFunctionConfig(m_stepSize, m_windowSize)); - qDebug() << "input sample rate is " << m_iSampleRate << ", step size is " << m_stepSize; + makeDetectionFunctionConfig(m_stepSizeFrames, m_windowSize)); + qDebug() << "input sample rate is " << m_iSampleRate << ", step size is " << m_stepSizeFrames; m_helper.initialize( - m_windowSize, m_stepSize, [this](double* pWindow, size_t) { + m_windowSize, m_stepSizeFrames, [this](double* pWindow, size_t) { // TODO(rryan) reserve? m_detectionResults.push_back( m_pDetectionFunction->processTimeDomain(pWindow)); @@ -97,7 +97,7 @@ bool AnalyzerQueenMaryBeats::finalize() { beatPeriod.push_back(0.0); } - TempoTrackV2 tt(m_iSampleRate, m_stepSize); + TempoTrackV2 tt(m_iSampleRate, m_stepSizeFrames); tt.calculateBeatPeriod(df, beatPeriod, tempi); std::vector beats; @@ -105,9 +105,10 @@ bool AnalyzerQueenMaryBeats::finalize() { m_resultBeats.reserve(static_cast(beats.size())); for (size_t i = 0; i < beats.size(); ++i) { - // we add the halve m_stepSize here, because the beat + // we add the halve m_stepSizeFrames here, because the beat // is detected between the two samples. - double result = (beats.at(i) * m_stepSize) + m_stepSize / 2; + const auto result = mixxx::audio::FramePos( + (beats.at(i) * m_stepSizeFrames) + m_stepSizeFrames / 2); m_resultBeats.push_back(result); } diff --git a/src/analyzer/plugins/analyzerqueenmarybeats.h b/src/analyzer/plugins/analyzerqueenmarybeats.h index 5dc70ae85c9..59dc7542753 100644 --- a/src/analyzer/plugins/analyzerqueenmarybeats.h +++ b/src/analyzer/plugins/analyzerqueenmarybeats.h @@ -40,7 +40,7 @@ class AnalyzerQueenMaryBeats : public AnalyzerBeatsPlugin { return true; } - QVector getBeats() const override { + QVector getBeats() const override { return m_resultBeats; } @@ -49,9 +49,9 @@ class AnalyzerQueenMaryBeats : public AnalyzerBeatsPlugin { DownmixAndOverlapHelper m_helper; int m_iSampleRate; int m_windowSize; - int m_stepSize; + int m_stepSizeFrames; std::vector m_detectionResults; - QVector m_resultBeats; + QVector m_resultBeats; }; } // namespace mixxx diff --git a/src/audio/frame.h b/src/audio/frame.h new file mode 100644 index 00000000000..5b22ee458a1 --- /dev/null +++ b/src/audio/frame.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include + +#include "engine/engine.h" +#include "util/fpclassify.h" + +namespace mixxx { +namespace audio { +/// FrameDiff_t can be used to store the difference in position between +/// two frames and to store the length of a segment of track in terms of frames. +typedef double FrameDiff_t; + +/// FramePos defines the position of a frame in a track +/// with respect to a fixed origin, i.e. start of the track. +class FramePos final { + public: + typedef double value_t; + static constexpr value_t kStartValue = 0; + static constexpr value_t kInvalidValue = std::numeric_limits::quiet_NaN(); + + constexpr FramePos() + : m_framePosition(kInvalidValue) { + } + + constexpr explicit FramePos(value_t framePosition) + : m_framePosition(framePosition) { + } + + static constexpr FramePos fromEngineSamplePos(double engineSamplePos) { + return FramePos(engineSamplePos / mixxx::kEngineChannelCount); + } + + constexpr double toEngineSamplePos() const { + return value() * mixxx::kEngineChannelCount; + } + + bool isValid() const { + return !util_isnan(m_framePosition) && !util_isinf(m_framePosition); + } + + void setValue(value_t framePosition) { + m_framePosition = framePosition; + } + + constexpr value_t value() const { + return m_framePosition; + } + + bool isFractional() const { + DEBUG_ASSERT(isValid()); + value_t integerPart; + return std::modf(value(), &integerPart) != 0; + } + + /// Return position rounded to the next lower full frame position, without + /// the fractional part. + [[nodiscard]] FramePos toLowerFrameBoundary() const { + return FramePos(std::floor(value())); + } + + FramePos& operator+=(FrameDiff_t increment) { + m_framePosition += increment; + return *this; + } + + FramePos& operator-=(FrameDiff_t decrement) { + m_framePosition -= decrement; + return *this; + } + + FramePos& operator*=(double multiple) { + m_framePosition *= multiple; + return *this; + } + + FramePos& operator/=(double divisor) { + m_framePosition /= divisor; + return *this; + } + + private: + value_t m_framePosition; +}; + +/// FramePos can be added to a FrameDiff_t +inline FramePos operator+(FramePos framePos, FrameDiff_t frameDiff) { + return FramePos(framePos.value() + frameDiff); +} + +/// FramePos can be subtracted from a FrameDiff_t +inline FramePos operator-(FramePos framePos, FrameDiff_t frameDiff) { + return FramePos(framePos.value() - frameDiff); +} + +/// Two FramePos can be subtracted to get a FrameDiff_t +inline FrameDiff_t operator-(FramePos framePos1, FramePos framePos2) { + return framePos1.value() - framePos2.value(); +} + +// Adding two FramePos is not allowed since every FramePos shares a common +// reference or origin i.e. the start of the track. + +/// FramePos can be multiplied or divided by a double +inline FramePos operator*(FramePos framePos, double multiple) { + return FramePos(framePos.value() * multiple); +} + +inline FramePos operator/(FramePos framePos, double divisor) { + return FramePos(framePos.value() / divisor); +} + +inline bool operator<(FramePos frame1, FramePos frame2) { + return frame1.value() < frame2.value(); +} + +inline bool operator<=(FramePos frame1, FramePos frame2) { + return frame1.value() <= frame2.value(); +} + +inline bool operator>(FramePos frame1, FramePos frame2) { + return frame1.value() > frame2.value(); +} + +inline bool operator>=(FramePos frame1, FramePos frame2) { + return frame1.value() >= frame2.value(); +} + +inline bool operator==(FramePos frame1, FramePos frame2) { + return frame1.value() == frame2.value(); +} + +inline bool operator!=(FramePos frame1, FramePos frame2) { + return !(frame1.value() == frame2.value()); +} + +inline QDebug operator<<(QDebug dbg, FramePos arg) { + dbg << arg.value(); + return dbg; +} + +constexpr FramePos kInvalidFramePos = FramePos(FramePos::kInvalidValue); +constexpr FramePos kStartFramePos = FramePos(FramePos::kStartValue); +} // namespace audio +} // namespace mixxx + +Q_DECLARE_TYPEINFO(mixxx::audio::FramePos, Q_MOVABLE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::FramePos); diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 99344e42a42..5a7f004c139 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -1255,7 +1255,8 @@ bool setTrackBeats(const QSqlRecord& record, const int column, } } else { // Load a temorary beat grid without offset that will be replaced by the analyzer. - const auto pBeats = BeatFactory::makeBeatGrid(pTrack->getSampleRate(), bpm, 0.0); + const auto pBeats = BeatFactory::makeBeatGrid( + pTrack->getSampleRate(), bpm, mixxx::audio::kStartFramePos); pTrack->trySetBeats(pBeats); } return false; diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index a4f8ce93722..9e94a003aed 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -581,8 +581,11 @@ void DlgTrackInfo::slotBpmConstChanged(int state) { // The cue point should be set on a beat, so this seems // to be a good alternative CuePosition cue = m_pLoadedTrack->getCuePoint(); - m_pBeatsClone = BeatFactory::makeBeatGrid( - m_pLoadedTrack->getSampleRate(), spinBpm->value(), cue.getPosition()); + m_pBeatsClone = + BeatFactory::makeBeatGrid(m_pLoadedTrack->getSampleRate(), + spinBpm->value(), + mixxx::audio::FramePos::fromEngineSamplePos( + cue.getPosition())); } else { m_pBeatsClone.clear(); } @@ -618,7 +621,9 @@ void DlgTrackInfo::slotSpinBpmValueChanged(double value) { if (!m_pBeatsClone) { CuePosition cue = m_pLoadedTrack->getCuePoint(); m_pBeatsClone = BeatFactory::makeBeatGrid( - m_pLoadedTrack->getSampleRate(), value, cue.getPosition()); + m_pLoadedTrack->getSampleRate(), + value, + mixxx::audio::FramePos::fromEngineSamplePos(cue.getPosition())); } double oldValue = m_pBeatsClone->getBpm(); diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 956c48317e2..9b0be2200fa 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -925,7 +925,7 @@ void readAnalyze(TrackPointer track, static_cast( (*section)->body()); - QVector beats; + QVector beats; for (std::vector::iterator beat = beatGridTag->beats()->begin(); @@ -936,7 +936,7 @@ void readAnalyze(TrackPointer track, if (time < 1) { time = 1; } - beats << (sampleRateKhz * static_cast(time)); + beats << mixxx::audio::FramePos(sampleRateKhz * static_cast(time)); } const auto pBeats = mixxx::BeatMap::makeBeatMap( diff --git a/src/mixxxapplication.cpp b/src/mixxxapplication.cpp index cf859aedbd9..5d21deedfcb 100644 --- a/src/mixxxapplication.cpp +++ b/src/mixxxapplication.cpp @@ -4,6 +4,7 @@ #include #include +#include "audio/frame.h" #include "audio/types.h" #include "control/controlproxy.h" #include "library/trackset/crate/crateid.h" @@ -97,6 +98,7 @@ void MixxxApplication::registerMetaTypes() { qRegisterMetaType("mixxx::cache_key_t"); qRegisterMetaType("mixxx::Bpm"); qRegisterMetaType("mixxx::Duration"); + qRegisterMetaType("mixxx::audio::FramePos"); qRegisterMetaType>("std::optional"); qRegisterMetaType("mixxx::FileInfo"); } diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp index 63bfea76f01..ed6776279e3 100644 --- a/src/test/beatgridtest.cpp +++ b/src/test/beatgridtest.cpp @@ -29,7 +29,10 @@ TEST(BeatGridTest, Scale) { double bpm = 60.0; pTrack->trySetBpm(bpm); - auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), bpm, 0); + auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), + QString(), + bpm, + mixxx::audio::kStartFramePos); EXPECT_DOUBLE_EQ(bpm, pGrid->getBpm()); pGrid = pGrid->scale(Beats::DOUBLE); @@ -60,7 +63,10 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) { pTrack->trySetBpm(bpm); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; - auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), bpm, 0); + auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), + QString(), + bpm, + mixxx::audio::kStartFramePos); // Pretend we're on the 20th beat; double position = beatLength * 20; @@ -99,7 +105,10 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { pTrack->trySetBpm(bpm); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; - auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), bpm, 0); + auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), + QString(), + bpm, + mixxx::audio::kStartFramePos); // Pretend we're just before the 20th beat. const double kClosestBeat = 20 * beatLength; @@ -140,7 +149,10 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) { pTrack->trySetBpm(bpm); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; - auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), bpm, 0); + auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), + QString(), + bpm, + mixxx::audio::kStartFramePos); // Pretend we're just before the 20th beat. const double kClosestBeat = 20 * beatLength; @@ -181,7 +193,10 @@ TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) { pTrack->trySetBpm(bpm); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; - auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), QString(), bpm, 0); + auto pGrid = BeatGrid::makeBeatGrid(pTrack->getSampleRate(), + QString(), + bpm, + mixxx::audio::kStartFramePos); // Pretend we're half way between the 20th and 21st beat double previousBeat = beatLength * 20.0; diff --git a/src/test/beatmaptest.cpp b/src/test/beatmaptest.cpp index f307cbb525b..04d2495dbb3 100644 --- a/src/test/beatmaptest.cpp +++ b/src/test/beatmaptest.cpp @@ -23,7 +23,7 @@ class BeatMapTest : public testing::Test { mixxx::Duration::fromSeconds(180)); } - double getBeatLengthFrames(double bpm) { + mixxx::audio::FrameDiff_t getBeatLengthFrames(double bpm) { return (60.0 * m_iSampleRate / bpm); } @@ -31,10 +31,10 @@ class BeatMapTest : public testing::Test { return getBeatLengthFrames(bpm) * m_iFrameSize; } - QVector createBeatVector(double first_beat, - unsigned int num_beats, - double beat_length) { - QVector beats; + QVector createBeatVector(mixxx::audio::FramePos first_beat, + unsigned int num_beats, + mixxx::audio::FrameDiff_t beat_length) { + QVector beats; for (unsigned int i = 0; i < num_beats; ++i) { beats.append(first_beat + i * beat_length); } @@ -49,11 +49,12 @@ class BeatMapTest : public testing::Test { TEST_F(BeatMapTest, Scale) { const double bpm = 60.0; m_pTrack->trySetBpm(bpm); - double beatLengthFrames = getBeatLengthFrames(bpm); - double startOffsetFrames = 7; + mixxx::audio::FrameDiff_t beatLengthFrames = getBeatLengthFrames(bpm); + const auto startOffsetFrames = mixxx::audio::FramePos(7); const int numBeats = 100; // Note beats must be in frames, not samples. - QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); + QVector beats = + createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pMap = BeatMap::makeBeatMap(m_pTrack->getSampleRate(), QString(), beats); EXPECT_DOUBLE_EQ(bpm, pMap->getBpm()); @@ -79,13 +80,14 @@ TEST_F(BeatMapTest, Scale) { TEST_F(BeatMapTest, TestNthBeat) { const double bpm = 60.0; m_pTrack->trySetBpm(bpm); - double beatLengthFrames = getBeatLengthFrames(bpm); - double startOffsetFrames = 7; + mixxx::audio::FrameDiff_t beatLengthFrames = getBeatLengthFrames(bpm); + const auto startOffsetFrames = mixxx::audio::FramePos(7); double beatLengthSamples = getBeatLengthSamples(bpm); - double startOffsetSamples = startOffsetFrames * 2; + const double startOffsetSamples = startOffsetFrames.toEngineSamplePos(); const int numBeats = 100; // Note beats must be in frames, not samples. - QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); + QVector beats = + createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pMap = BeatMap::makeBeatMap(m_pTrack->getSampleRate(), QString(), beats); // Check edge cases @@ -111,13 +113,14 @@ TEST_F(BeatMapTest, TestNthBeat) { TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) { const double bpm = 60.0; m_pTrack->trySetBpm(bpm); - double beatLengthFrames = getBeatLengthFrames(bpm); - double startOffsetFrames = 7; + mixxx::audio::FrameDiff_t beatLengthFrames = getBeatLengthFrames(bpm); + const auto startOffsetFrames = mixxx::audio::FramePos(7); double beatLengthSamples = getBeatLengthSamples(bpm); - double startOffsetSamples = startOffsetFrames * 2; + const double startOffsetSamples = startOffsetFrames.toEngineSamplePos(); const int numBeats = 100; // Note beats must be in frames, not samples. - QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); + QVector beats = + createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pMap = BeatMap::makeBeatMap(m_pTrack->getSampleRate(), QString(), beats); // Pretend we're on the 20th beat; @@ -153,13 +156,14 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) { TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { const double bpm = 60.0; m_pTrack->trySetBpm(bpm); - double beatLengthFrames = getBeatLengthFrames(bpm); - double startOffsetFrames = 7; + mixxx::audio::FrameDiff_t beatLengthFrames = getBeatLengthFrames(bpm); + const auto startOffsetFrames = mixxx::audio::FramePos(7); double beatLengthSamples = getBeatLengthSamples(bpm); - double startOffsetSamples = startOffsetFrames * 2; + const double startOffsetSamples = startOffsetFrames.toEngineSamplePos(); const int numBeats = 100; // Note beats must be in frames, not samples. - QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); + QVector beats = + createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pMap = BeatMap::makeBeatMap(m_pTrack->getSampleRate(), QString(), beats); // Pretend we're just before the 20th beat; @@ -197,13 +201,14 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) { const double bpm = 60.0; m_pTrack->trySetBpm(bpm); - double beatLengthFrames = getBeatLengthFrames(bpm); - double startOffsetFrames = 7; + mixxx::audio::FrameDiff_t beatLengthFrames = getBeatLengthFrames(bpm); + const auto startOffsetFrames = mixxx::audio::FramePos(7); double beatLengthSamples = getBeatLengthSamples(bpm); - double startOffsetSamples = startOffsetFrames * 2; + const double startOffsetSamples = startOffsetFrames.toEngineSamplePos(); const int numBeats = 100; // Note beats must be in frames, not samples. - QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); + QVector beats = + createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pMap = BeatMap::makeBeatMap(m_pTrack->getSampleRate(), QString(), beats); // Pretend we're just after the 20th beat; @@ -242,13 +247,14 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) { TEST_F(BeatMapTest, TestNthBeatWhenNotOnBeat) { const double bpm = 60.0; m_pTrack->trySetBpm(bpm); - double beatLengthFrames = getBeatLengthFrames(bpm); - double startOffsetFrames = 7; + mixxx::audio::FrameDiff_t beatLengthFrames = getBeatLengthFrames(bpm); + const auto startOffsetFrames = mixxx::audio::FramePos(7); double beatLengthSamples = getBeatLengthSamples(bpm); - double startOffsetSamples = startOffsetFrames * 2; + const double startOffsetSamples = startOffsetFrames.toEngineSamplePos(); const int numBeats = 100; // Note beats must be in frames, not samples. - QVector beats = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); + QVector beats = + createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pMap = BeatMap::makeBeatMap(m_pTrack->getSampleRate(), QString(), beats); // Pretend we're half way between the 20th and 21st beat @@ -286,10 +292,10 @@ TEST_F(BeatMapTest, TestBpmAround) { m_pTrack->trySetBpm(filebpm); const int numBeats = 64; - QVector beats; - double beat_pos = 0; + QVector beats; + mixxx::audio::FramePos beat_pos = mixxx::audio::kStartFramePos; for (unsigned int i = 0, bpm=60; i < numBeats; ++i, ++bpm) { - double beat_length = getBeatLengthFrames(bpm); + const mixxx::audio::FrameDiff_t beat_length = getBeatLengthFrames(bpm); beats.append(beat_pos); beat_pos += beat_length; } @@ -309,7 +315,8 @@ TEST_F(BeatMapTest, TestBpmAround) { pMap->getBpmAroundPosition(65 * approx_beat_length, 4)); // Try a really, really short track - beats = createBeatVector(10, 3, getBeatLengthFrames(filebpm)); + constexpr auto startFramePos = mixxx::audio::FramePos(10); + beats = createBeatVector(startFramePos, 3, getBeatLengthFrames(filebpm)); pMap = BeatMap::makeBeatMap(m_pTrack->getSampleRate(), QString(), beats); EXPECT_DOUBLE_EQ(filebpm, pMap->getBpmAroundPosition(1 * approx_beat_length, 4)); } diff --git a/src/test/beatstranslatetest.cpp b/src/test/beatstranslatetest.cpp index dbbdefdb8c1..b95d681ef5a 100644 --- a/src/test/beatstranslatetest.cpp +++ b/src/test/beatstranslatetest.cpp @@ -8,16 +8,16 @@ 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; + constexpr auto firstBeat = mixxx::audio::kStartFramePos; auto grid1 = mixxx::BeatGrid::makeBeatGrid( m_pTrack1->getSampleRate(), QString(), bpm, firstBeat); m_pTrack1->trySetBeats(grid1); - ASSERT_DOUBLE_EQ(firstBeat, grid1->findClosestBeat(0)); + ASSERT_DOUBLE_EQ(firstBeat.value(), grid1->findClosestBeat(0)); auto grid2 = mixxx::BeatGrid::makeBeatGrid( m_pTrack2->getSampleRate(), QString(), bpm, firstBeat); m_pTrack2->trySetBeats(grid2); - ASSERT_DOUBLE_EQ(firstBeat, grid2->findClosestBeat(0)); + ASSERT_DOUBLE_EQ(firstBeat.value(), grid2->findClosestBeat(0)); // Seek deck 1 forward a bit. const double delta = 2222.0; diff --git a/src/test/bpmcontrol_test.cpp b/src/test/bpmcontrol_test.cpp index 997edf79ec2..35053cb56ae 100644 --- a/src/test/bpmcontrol_test.cpp +++ b/src/test/bpmcontrol_test.cpp @@ -37,7 +37,8 @@ TEST_F(BpmControlTest, BeatContext_BeatGrid) { const int kFrameSize = 2; const double expectedBeatLength = (60.0 * sampleRate / bpm) * kFrameSize; - const mixxx::BeatsPointer pBeats = BeatFactory::makeBeatGrid(pTrack->getSampleRate(), bpm, 0); + const mixxx::BeatsPointer pBeats = BeatFactory::makeBeatGrid( + pTrack->getSampleRate(), bpm, mixxx::audio::kStartFramePos); // On a beat. double prevBeat, nextBeat, beatLength, beatPercentage; diff --git a/src/test/enginesynctest.cpp b/src/test/enginesynctest.cpp index 42f4df402d7..c361c667e80 100644 --- a/src/test/enginesynctest.cpp +++ b/src/test/enginesynctest.cpp @@ -244,9 +244,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->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 124, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 124, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonMasterSync1 = @@ -276,11 +278,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->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 124, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 124, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); - mixxx::BeatsPointer pBeats3 = BeatFactory::makeBeatGrid(m_pTrack3->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats3 = BeatFactory::makeBeatGrid( + m_pTrack3->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack3->trySetBeats(pBeats3); auto pButtonMasterSync1 = @@ -310,7 +315,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->getSampleRate(), 80, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 80, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonMasterSync1 = std::make_unique(m_sGroup1, "sync_mode"); @@ -336,10 +342,12 @@ TEST_F(EngineSyncTest, DisableInternalMasterWhilePlaying) { EXPECT_TRUE(isExplicitMaster(m_sInternalClockGroup)); // Make sure both decks are playing. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 80, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 80, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "play"))->set(1.0); ProcessBuffer(); @@ -355,13 +363,15 @@ TEST_F(EngineSyncTest, DisableInternalMasterWhilePlaying) { TEST_F(EngineSyncTest, DisableSyncOnMaster) { // Channel 1 follower, channel 2 master. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set deck two to explicit master. auto pButtonSyncMaster2 = @@ -399,7 +409,8 @@ TEST_F(EngineSyncTest, InternalMasterSetFollowerSliderMoves) { pButtonMasterSyncInternal->slotSet(1); // Set the file bpm of channel 1 to 80 bpm. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 80, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 80, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonMasterSync1 = @@ -417,7 +428,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->getSampleRate(), 80, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 80, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -428,7 +440,8 @@ TEST_F(EngineSyncTest, AnySyncDeckSliderStays) { ControlObject::getControl(ConfigKey(m_sInternalClockGroup, "bpm")) ->get()); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 100, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); @@ -452,11 +465,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->getSampleRate(), 100, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 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); @@ -522,10 +537,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->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 150, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -603,7 +620,8 @@ TEST_F(EngineSyncTest, RateChangeTest) { ProcessBuffer(); // Set the file bpm of channel 1 to 160bpm. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_DOUBLE_EQ( 160.0, ControlObject::get(ConfigKey(m_sGroup1, "file_bpm"))); @@ -622,7 +640,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->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ( 120.0, ControlObject::get(ConfigKey(m_sGroup2, "file_bpm"))); @@ -644,13 +663,15 @@ TEST_F(EngineSyncTest, RateChangeTestWeirdOrder) { ProcessBuffer(); // Set the file bpm of channel 1 to 160bpm. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set the rate slider of channel 1 to 1.2. @@ -668,13 +689,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->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ( 120.0, ControlObject::get(ConfigKey(m_sGroup2, "file_bpm"))); @@ -714,11 +737,13 @@ TEST_F(EngineSyncTest, FollowerRateChange) { ProcessBuffer(); // Set the file bpm of channel 1 to 160bpm. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set the rate slider of channel 1 to 1.2. @@ -760,13 +785,15 @@ TEST_F(EngineSyncTest, InternalRateChangeTest) { EXPECT_TRUE(isFollower(m_sGroup2)); // Set the file bpm of channel 1 to 160bpm. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ(120.0, ControlObject::getControl(ConfigKey(m_sGroup2, "file_bpm"))->get()); @@ -812,9 +839,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->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonMasterSync1 = @@ -856,7 +885,8 @@ TEST_F(EngineSyncTest, EnableOneDeckInitsMaster) { ProcessBuffer(); // Set up the deck to play. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -883,7 +913,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->getSampleRate(), 140, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "rate")) ->set(getRateSliderValue(1.0)); @@ -911,9 +942,11 @@ TEST_F(EngineSyncTest, MomentarySyncAlgorithmTwo) { m_pConfig->set(ConfigKey("[BPM]", "sync_lock_algorithm"), ConfigValue(EngineSync::PREFER_LOCK_BPM)); - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "play"))->set(1.0); @@ -931,7 +964,8 @@ TEST_F(EngineSyncTest, MomentarySyncAlgorithmTwo) { 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, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -1121,7 +1155,8 @@ TEST_F(EngineSyncTest, EnableOneDeckSliderUpdates) { auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -1148,11 +1183,13 @@ TEST_F(EngineSyncTest, SyncToNonSyncDeck) { auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "rate")) ->set(getRateSliderValue(1.0)); @@ -1232,11 +1269,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->getSampleRate(), 100, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 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); @@ -1306,7 +1345,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->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); pButtonSyncEnabled1->set(1.0); ProcessBuffer(); @@ -1331,7 +1371,8 @@ TEST_F(EngineSyncTest, EjectTrackSyncRemains) { m_pMixerDeck1->loadFakeTrack(false, 128.0); EXPECT_DOUBLE_EQ(128.0, ControlObject::getControl(ConfigKey(m_sGroup1, "bpm"))->get()); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 135, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 135, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); pButtonSyncEnabled2->set(1.0); ProcessBuffer(); @@ -1351,14 +1392,16 @@ 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, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 100, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); pButtonSyncEnabled1->set(1.0); ProcessBuffer(); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); @@ -1369,7 +1412,8 @@ TEST_F(EngineSyncTest, FileBpmChangesDontAffectMaster) { EXPECT_TRUE(isFollower(m_sGroup2)); // Update the master's beats -- update the internal clock - pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 160, 0.0); + pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_DOUBLE_EQ( 160.0, ControlObject::get(ConfigKey(m_sInternalClockGroup, "bpm"))); @@ -1377,7 +1421,8 @@ TEST_F(EngineSyncTest, FileBpmChangesDontAffectMaster) { EXPECT_TRUE(isSoftMaster(m_sGroup1)); // Update follower beats -- don't update internal clock. - pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 140, 0.0); + pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); EXPECT_DOUBLE_EQ( 160.0, ControlObject::get(ConfigKey(m_sInternalClockGroup, "bpm"))); @@ -1389,7 +1434,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->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 160, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ProcessBuffer(); ControlObject::getControl(ConfigKey(m_sGroup1, "play"))->set(1.0); @@ -1403,7 +1449,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->getSampleRate(), 0, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 0, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -1412,7 +1459,8 @@ TEST_F(EngineSyncTest, ZeroBPMRateAdjustIgnored) { ->set(getRateSliderValue(1.0)); ProcessBuffer(); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 120, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); @@ -1450,7 +1498,8 @@ TEST_F(EngineSyncTest, DISABLED_BeatDistanceBeforeStart) { // If the start position is before zero, we should still initialize the beat distance // correctly. Unfortunately, this currently doesn't work. - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::set(ConfigKey(m_sGroup1, "playposition"), -.05); ControlObject::getControl(ConfigKey(m_sGroup1, "sync_mode")) @@ -1466,9 +1515,11 @@ TEST_F(EngineSyncTest, DISABLED_BeatDistanceBeforeStart) { 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, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 160, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Make Channel2 master to weed out any channel ordering issues. @@ -1514,9 +1565,11 @@ TEST_F(EngineSyncTest, ZeroLatencyRateChangeNoQuant) { 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, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 160, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0); @@ -1567,9 +1620,11 @@ TEST_F(EngineSyncTest, ZeroLatencyRateChangeQuant) { 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, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 160, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 160, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "quantize"))->set(1.0); @@ -1621,9 +1676,11 @@ TEST_F(EngineSyncTest, ZeroLatencyRateDiffQuant) { // need to check. The Sync feature is unfortunately brittle. // This test exercises https://bugs.launchpad.net/mixxx/+bug/1884324 TEST_F(EngineSyncTest, ActivatingSyncDoesNotCauseDrifting) { - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 150, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 150, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 150, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 150, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(0.0); @@ -1665,9 +1722,11 @@ TEST_F(EngineSyncTest, ActivatingSyncDoesNotCauseDrifting) { } TEST_F(EngineSyncTest, HalfDoubleBpmTest) { - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 70, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 70, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 140, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Mixxx will choose the first playing deck to be master. Let's start deck 2 first. @@ -1762,9 +1821,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->getSampleRate(), 80, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 80, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 175, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 175, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "rate")) ->set(getRateSliderValue(1.0)); @@ -1885,9 +1946,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->getSampleRate(), 70, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 70, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 140, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0); @@ -1906,9 +1969,11 @@ TEST_F(EngineSyncTest, HalfDoubleInternalClockTest) { } namespace { -QVector createBeatVector( - double first_beat, unsigned int num_beats, double beat_length) { - QVector beats; +QVector createBeatVector( + mixxx::audio::FramePos first_beat, + unsigned int num_beats, + mixxx::audio::FrameDiff_t beat_length) { + QVector beats; for (unsigned int i = 0; i < num_beats; ++i) { beats.append(first_beat + i * beat_length); } @@ -1918,16 +1983,16 @@ QVector createBeatVector( TEST_F(EngineSyncTest, HalfDoubleConsistency) { // half-double matching should be consistent - double beatLengthFrames = 60.0 * 44100 / 90.0; - double startOffsetFrames = 0; + mixxx::audio::FrameDiff_t beatLengthFrames = 60.0 * 44100 / 90.0; + constexpr auto startOffsetFrames = mixxx::audio::kStartFramePos; const int numBeats = 100; - QVector beats1 = + QVector beats1 = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pBeats1 = mixxx::BeatMap::makeBeatMap(m_pTrack1->getSampleRate(), QString(), beats1); m_pTrack1->trySetBeats(pBeats1); beatLengthFrames = 60.0 * 44100 / 145.0; - QVector beats2 = + QVector beats2 = createBeatVector(startOffsetFrames, numBeats, beatLengthFrames); auto pBeats2 = mixxx::BeatMap::makeBeatMap(m_pTrack2->getSampleRate(), QString(), beats2); m_pTrack2->trySetBeats(pBeats2); @@ -1958,9 +2023,11 @@ TEST_F(EngineSyncTest, HalfDoubleEachOther) { // Confirm that repeated sync with both decks leads to the same // Half/Double decision. // This test demonstrates https://bugs.launchpad.net/mixxx/+bug/1921962 - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 144, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 144, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 105, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 105, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Threshold 1.414 sqrt(2); @@ -1985,7 +2052,8 @@ TEST_F(EngineSyncTest, HalfDoubleEachOther) { // 105 / 75 = 1.40 // expect 75 BPM - mixxx::BeatsPointer pBeats1b = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 150, 0.0); + mixxx::BeatsPointer pBeats1b = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 150, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1b); EXPECT_DOUBLE_EQ(150.0, @@ -2006,7 +2074,8 @@ 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, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); EXPECT_EQ( 130.0, m_pEngineSync->getSyncableForGroup(m_sGroup1)->getBaseBpm()); @@ -2018,14 +2087,16 @@ TEST_F(EngineSyncTest, SyncPhaseToPlayingNonSyncDeck) { auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(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->getSampleRate(), 100, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); // Set the sync deck playing with nothing else active. @@ -2108,7 +2179,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->getSampleRate(), 140, 0.0); + mixxx::BeatsPointer pBeats3 = BeatFactory::makeBeatGrid( + m_pTrack3->getSampleRate(), 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); @@ -2153,9 +2225,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->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "quantize"))->set(1.0); @@ -2207,9 +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, 0.0); + m_pTrack1->getSampleRate(), kDivisibleBpm, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup2, "sync_enabled"))->set(1); @@ -2287,9 +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, 0.0); + m_pTrack1->getSampleRate(), kDivisibleBpm, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "sync_master"))->set(1); @@ -2339,9 +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, 0.0); + m_pTrack1->getSampleRate(), kDivisibleBpm, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::getControl(ConfigKey(m_sGroup1, "sync_master"))->set(1); @@ -2386,7 +2463,8 @@ TEST_F(EngineSyncTest, MasterUserTweakPreservedInMasterChange) { } TEST_F(EngineSyncTest, MasterBpmNeverZero) { - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -2401,7 +2479,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->getSampleRate(), 0.0, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 0.0, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); auto pButtonSyncEnabled1 = std::make_unique(m_sGroup1, "sync_enabled"); @@ -2420,11 +2499,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->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ProcessBuffer(); @@ -2516,7 +2597,8 @@ TEST_F(EngineSyncTest, QuantizeImpliesSyncPhase) { TEST_F(EngineSyncTest, SeekStayInPhase) { ControlObject::set(ConfigKey(m_sGroup1, "quantize"), 1.0); - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -2538,7 +2620,8 @@ TEST_F(EngineSyncTest, SeekStayInPhase) { ControlObject::set(ConfigKey(m_sGroup1, "play"), 0.0); ProcessBuffer(); - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -2560,7 +2643,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->getSampleRate(), 128, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 128, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); m_pTrack2->trySetBeats(mixxx::BeatsPointer()); @@ -2578,11 +2662,13 @@ TEST_F(EngineSyncTest, SyncWithoutBeatgrid) { } TEST_F(EngineSyncTest, QuantizeHotCueActivate) { - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 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, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 100, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); @@ -2643,7 +2729,8 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) { auto pButtonSyncEnabled2 = std::make_unique(m_sGroup2, "sync_enabled"); // set beatgrid for deck 1 - mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid(m_pTrack1->getSampleRate(), 130, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 130, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); pButtonSyncEnabled1->set(1.0); ControlObject::set(ConfigKey(m_sGroup1, "play"), 1.0); @@ -2676,7 +2763,8 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) { EXPECT_TRUE(isFollower(m_sGroup2)); // Load a new beatgrid during playing, this happens when the analyser is finished. - mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 140, 0.0); + mixxx::BeatsPointer pBeats2 = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 140, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2); ProcessBuffer(); @@ -2700,7 +2788,8 @@ TEST_F(EngineSyncTest, ChangeBeatGrid) { EXPECT_TRUE(isSoftMaster(m_sGroup2)); // Load a new beatgrid again, this happens when the user adjusts the beatgrid - mixxx::BeatsPointer pBeats2n = BeatFactory::makeBeatGrid(m_pTrack2->getSampleRate(), 75, 0.0); + mixxx::BeatsPointer pBeats2n = BeatFactory::makeBeatGrid( + m_pTrack2->getSampleRate(), 75, mixxx::audio::kStartFramePos); m_pTrack2->trySetBeats(pBeats2n); ProcessBuffer(); @@ -2715,7 +2804,8 @@ 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, 0.0); + mixxx::BeatsPointer pBeats1 = BeatFactory::makeBeatGrid( + m_pTrack1->getSampleRate(), 120, mixxx::audio::kStartFramePos); m_pTrack1->trySetBeats(pBeats1); constexpr auto kSampleRate = mixxx::audio::SampleRate(44100); @@ -2723,8 +2813,11 @@ TEST_F(EngineSyncTest, BeatMapQuantizePlay) { auto pBeats2 = mixxx::BeatMap::makeBeatMap(kSampleRate, QString(), // Add two beats at 120 Bpm - QVector({static_cast(kSampleRate) / 2, - static_cast(kSampleRate)})); + QVector( + {mixxx::audio::FramePos( + static_cast(kSampleRate) / 2), + mixxx::audio::FramePos( + static_cast(kSampleRate))})); m_pTrack2->trySetBeats(pBeats2); ControlObject::set(ConfigKey(m_sGroup1, "quantize"), 1.0); diff --git a/src/test/frametest.cpp b/src/test/frametest.cpp new file mode 100644 index 00000000000..a567c93c0d2 --- /dev/null +++ b/src/test/frametest.cpp @@ -0,0 +1,37 @@ +#include + +#include + +#include "audio/frame.h" + +class FrameTest : public testing::Test { +}; + +TEST_F(FrameTest, TestFramePosValid) { + EXPECT_TRUE(mixxx::audio::FramePos(100).isValid()); + EXPECT_TRUE(mixxx::audio::FramePos(2000).isValid()); + EXPECT_TRUE(mixxx::audio::FramePos(-1).isValid()); + EXPECT_TRUE(mixxx::audio::FramePos(99999).isValid()); + EXPECT_TRUE(mixxx::audio::FramePos(-99999).isValid()); + EXPECT_TRUE(mixxx::audio::FramePos(128.5).isValid()); + EXPECT_TRUE(mixxx::audio::FramePos(135.67).isValid()); + // Denormals + EXPECT_TRUE(mixxx::audio::FramePos(0.0).isValid()); + EXPECT_TRUE(mixxx::audio::FramePos(std::numeric_limits::min() / 2.0).isValid()); + EXPECT_FALSE(mixxx::audio::FramePos(std::numeric_limits::infinity()).isValid()); + // NaN + EXPECT_FALSE(mixxx::audio::FramePos().isValid()); + EXPECT_FALSE(mixxx::audio::FramePos(std::numeric_limits::quiet_NaN()).isValid()); + EXPECT_FALSE(mixxx::audio::FramePos(std::numeric_limits::signaling_NaN()).isValid()); +} + +TEST_F(FrameTest, TestFramePosFractional) { + EXPECT_FALSE(mixxx::audio::FramePos(100).isFractional()); + EXPECT_FALSE(mixxx::audio::FramePos(2000).isFractional()); + EXPECT_FALSE(mixxx::audio::FramePos(0).isFractional()); + EXPECT_FALSE(mixxx::audio::FramePos(-1).isFractional()); + EXPECT_FALSE(mixxx::audio::FramePos(99999).isFractional()); + EXPECT_FALSE(mixxx::audio::FramePos(-99999).isFractional()); + EXPECT_TRUE(mixxx::audio::FramePos(128.5).isFractional()); + EXPECT_TRUE(mixxx::audio::FramePos(135.67).isFractional()); +} diff --git a/src/test/seratobeatgridtest.cpp b/src/test/seratobeatgridtest.cpp index 2f507d4451f..7895fde0bb2 100644 --- a/src/test/seratobeatgridtest.cpp +++ b/src/test/seratobeatgridtest.cpp @@ -116,7 +116,8 @@ TEST_F(SeratoBeatGridTest, SerializeBeatgrid) { constexpr double bpm = 120.0; const auto sampleRate = mixxx::audio::SampleRate(44100); EXPECT_EQ(sampleRate.isValid(), true); - const auto pBeats = mixxx::BeatGrid::makeBeatGrid(sampleRate, QString("Test"), bpm, 0); + const auto pBeats = mixxx::BeatGrid::makeBeatGrid( + sampleRate, QString("Test"), bpm, mixxx::audio::kStartFramePos); const auto signalInfo = mixxx::audio::SignalInfo(mixxx::audio::ChannelCount(2), sampleRate); const auto duration = mixxx::Duration::fromSeconds(300); @@ -135,12 +136,12 @@ TEST_F(SeratoBeatGridTest, SerializeBeatMap) { const auto sampleRate = mixxx::audio::SampleRate(44100); const auto signalInfo = mixxx::audio::SignalInfo(mixxx::audio::ChannelCount(2), sampleRate); const auto duration = mixxx::Duration::fromSeconds(300); - const double framesPerMinute = signalInfo.getSampleRate() * 60; - const double framesPerBeat = framesPerMinute / bpm; - const double initialFrameOffset = framesPerBeat / 2; + const mixxx::audio::FrameDiff_t framesPerMinute = signalInfo.getSampleRate() * 60; + const mixxx::audio::FrameDiff_t framesPerBeat = framesPerMinute / bpm; + const mixxx::audio::FrameDiff_t initialFrameOffset = framesPerBeat / 2; - QVector beatPositionsFrames; - double beatPositionFrames = initialFrameOffset; + QVector beatPositionsFrames; + mixxx::audio::FramePos beatPositionFrames = mixxx::audio::FramePos(initialFrameOffset); constexpr int kNumBeats120BPM = 4; qInfo() << "Step 1: Add" << kNumBeats120BPM << "beats at 100 bpm to the beatgrid"; @@ -172,11 +173,13 @@ TEST_F(SeratoBeatGridTest, SerializeBeatMap) { mixxx::SeratoBeatsImporter beatsImporter( seratoBeatGrid.nonTerminalMarkers(), seratoBeatGrid.terminalMarker()); - QVector importedBeatPositionsFrames = + const QVector importedBeatPositionsFrames = beatsImporter.importBeatsAndApplyTimingOffset(timingOffsetMillis, signalInfo); ASSERT_EQ(beatPositionsFrames.size(), importedBeatPositionsFrames.size()); for (int i = 0; i < beatPositionsFrames.size(); i++) { - EXPECT_NEAR(beatPositionsFrames[i], importedBeatPositionsFrames[i], kEpsilon); + EXPECT_NEAR(beatPositionsFrames[i].value(), + importedBeatPositionsFrames[i].value(), + kEpsilon); } } @@ -220,11 +223,13 @@ TEST_F(SeratoBeatGridTest, SerializeBeatMap) { mixxx::SeratoBeatsImporter beatsImporter( seratoBeatGrid.nonTerminalMarkers(), seratoBeatGrid.terminalMarker()); - QVector importedBeatPositionsFrames = + const QVector importedBeatPositionsFrames = beatsImporter.importBeatsAndApplyTimingOffset(timingOffsetMillis, signalInfo); ASSERT_EQ(beatPositionsFrames.size(), importedBeatPositionsFrames.size()); for (int i = 0; i < beatPositionsFrames.size(); i++) { - EXPECT_NEAR(beatPositionsFrames[i], importedBeatPositionsFrames[i], kEpsilon); + EXPECT_NEAR(beatPositionsFrames[i].value(), + importedBeatPositionsFrames[i].value(), + kEpsilon); } } @@ -278,11 +283,13 @@ TEST_F(SeratoBeatGridTest, SerializeBeatMap) { mixxx::SeratoBeatsImporter beatsImporter( seratoBeatGrid.nonTerminalMarkers(), seratoBeatGrid.terminalMarker()); - QVector importedBeatPositionsFrames = + const QVector importedBeatPositionsFrames = beatsImporter.importBeatsAndApplyTimingOffset(timingOffsetMillis, signalInfo); ASSERT_EQ(beatPositionsFrames.size(), importedBeatPositionsFrames.size()); for (int i = 0; i < beatPositionsFrames.size(); i++) { - EXPECT_NEAR(beatPositionsFrames[i], importedBeatPositionsFrames[i], kEpsilon); + EXPECT_NEAR(beatPositionsFrames[i].value(), + importedBeatPositionsFrames[i].value(), + kEpsilon); } } } diff --git a/src/track/beatfactory.cpp b/src/track/beatfactory.cpp index 6f7fa038522..26c4a8801f2 100644 --- a/src/track/beatfactory.cpp +++ b/src/track/beatfactory.cpp @@ -34,8 +34,8 @@ mixxx::BeatsPointer BeatFactory::loadBeatsFromByteArray( mixxx::BeatsPointer BeatFactory::makeBeatGrid( mixxx::audio::SampleRate sampleRate, double dBpm, - double dFirstBeatSample) { - return mixxx::BeatGrid::makeBeatGrid(sampleRate, QString(), dBpm, dFirstBeatSample); + mixxx::audio::FramePos firstBeatFramePos) { + return mixxx::BeatGrid::makeBeatGrid(sampleRate, QString(), dBpm, firstBeatFramePos); } // static @@ -77,7 +77,7 @@ QString BeatFactory::getPreferredSubVersion( } mixxx::BeatsPointer BeatFactory::makePreferredBeats( - const QVector& beats, + const QVector& beats, const QHash& extraVersionInfo, bool fixedTempo, mixxx::audio::SampleRate sampleRate) { @@ -85,8 +85,8 @@ mixxx::BeatsPointer BeatFactory::makePreferredBeats( const QString subVersion = getPreferredSubVersion(extraVersionInfo); #ifdef DEBUG_PRINT_BEATS - for (double beat : beats) { - qDebug().noquote() << QString::number(beat, 'g', 8); + for (mixxx::audio::FramePos beat : beats) { + qDebug().noquote() << QString::number(beat.value(), 'g', 8); } #endif @@ -95,20 +95,20 @@ mixxx::BeatsPointer BeatFactory::makePreferredBeats( #ifdef DEBUG_PRINT_BEATS for (auto& region : constantRegions) { - qDebug().noquote() << QString::number(region.firstBeat, 'g', 8) + qDebug().noquote() << QString::number(region.firstBeat.value(), 'g', 8) << QString::number(region.beatLength, 'g', 8); } #endif if (version == BEAT_GRID_2_VERSION) { - double firstBeat = 0; + mixxx::audio::FramePos firstBeat = mixxx::audio::kStartFramePos; double constBPM = BeatUtils::makeConstBpm(constantRegions, sampleRate, &firstBeat); firstBeat = BeatUtils::adjustPhase(firstBeat, constBPM, sampleRate, beats); auto pGrid = mixxx::BeatGrid::makeBeatGrid( - sampleRate, subVersion, constBPM, firstBeat * 2); + sampleRate, subVersion, constBPM, firstBeat); return pGrid; } else if (version == BEAT_MAP_VERSION) { - QVector ironedBeats = BeatUtils::getBeats(constantRegions); + QVector ironedBeats = BeatUtils::getBeats(constantRegions); auto pBeatMap = mixxx::BeatMap::makeBeatMap(sampleRate, subVersion, ironedBeats); return pBeatMap; } else { diff --git a/src/track/beatfactory.h b/src/track/beatfactory.h index 6d0fa57eb65..53b20345be7 100644 --- a/src/track/beatfactory.h +++ b/src/track/beatfactory.h @@ -2,6 +2,7 @@ #include +#include "audio/frame.h" #include "audio/types.h" #include "track/beats.h" @@ -17,7 +18,7 @@ class BeatFactory { static mixxx::BeatsPointer makeBeatGrid( mixxx::audio::SampleRate sampleRate, double dBpm, - double dFirstBeatSample); + mixxx::audio::FramePos firstBeatFramePos); static QString getPreferredVersion(bool fixedTempo); @@ -25,7 +26,7 @@ class BeatFactory { const QHash& extraVersionInfo); static mixxx::BeatsPointer makePreferredBeats( - const QVector& beats, + const QVector& beats, const QHash& extraVersionInfo, bool fixedTempo, mixxx::audio::SampleRate sampleRate); diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index a5c85cc2d38..b6cce7435c4 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -74,7 +74,7 @@ BeatsPointer BeatGrid::makeBeatGrid( audio::SampleRate sampleRate, const QString& subVersion, double dBpm, - double dFirstBeatSample) { + mixxx::audio::FramePos firstBeatPos) { if (dBpm < 0) { dBpm = 0.0; } @@ -83,7 +83,7 @@ BeatsPointer BeatGrid::makeBeatGrid( grid.mutable_bpm()->set_bpm(dBpm); grid.mutable_first_beat()->set_frame_position( - static_cast(dFirstBeatSample / kFrameSize)); + static_cast(firstBeatPos.value())); // Calculate beat length as sample offsets double beatLength = (60.0 * sampleRate / dBpm) * kFrameSize; @@ -106,9 +106,9 @@ BeatsPointer BeatGrid::makeBeatGrid( return BeatsPointer(new BeatGrid(sampleRate, QString(), grid, 0)); } const BeatGridData* blob = reinterpret_cast(byteArray.constData()); + const auto firstBeat = mixxx::audio::FramePos(blob->firstBeat); - // We serialize into frame offsets but use sample offsets at runtime - return makeBeatGrid(sampleRate, subVersion, blob->bpm, blob->firstBeat * kFrameSize); + return makeBeatGrid(sampleRate, subVersion, blob->bpm, firstBeat); } QByteArray BeatGrid::toByteArray() const { diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h index 8397fd7afdb..e32a49c9a72 100644 --- a/src/track/beatgrid.h +++ b/src/track/beatgrid.h @@ -1,5 +1,6 @@ #pragma once +#include "audio/frame.h" #include "proto/beats.pb.h" #include "track/beats.h" @@ -21,7 +22,7 @@ class BeatGrid final : public Beats { audio::SampleRate sampleRate, const QString& subVersion, double dBpm, - double dFirstBeatSample); + mixxx::audio::FramePos firstBeatPos); static BeatsPointer makeBeatGrid( audio::SampleRate sampleRate, diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index f68af39bfcf..b30ef51c2ed 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -20,15 +20,26 @@ using mixxx::track::io::Beat; namespace { -constexpr int kFrameSize = 2; constexpr int kMinNumberOfBeats = 2; // a map needs at least two beats to have a tempo -inline double samplesToFrames(const double samples) { - return floor(samples / kFrameSize); +inline Beat beatFromFramePos(mixxx::audio::FramePos beatPosition) { + DEBUG_ASSERT(beatPosition.isValid()); + // Because the protobuf Beat object stores integers internally, all + // fractional positions are lost. + DEBUG_ASSERT(!beatPosition.isFractional()); + Beat beat; + beat.set_frame_position(static_cast(beatPosition.value())); + return beat; +} + +inline Beat beatFromEngineSamplePos(double beatPositionSamples) { + return beatFromFramePos( + mixxx::audio::FramePos::fromEngineSamplePos(beatPositionSamples) + .toLowerFrameBoundary()); } -inline double framesToSamples(const int frames) { - return frames * kFrameSize; +inline mixxx::audio::FrameDiff_t frameDiffFromSampleDiff(double sampleDiff) { + return std::floor(sampleDiff / mixxx::kEngineChannelCount); } bool BeatLessThan(const Beat& beat1, const Beat& beat2) { @@ -129,11 +140,11 @@ void scaleFourth(BeatList* pBeats) { } double calculateNominalBpm(const BeatList& beats, mixxx::audio::SampleRate sampleRate) { - QVector beatvect; + QVector beatvect; beatvect.reserve(beats.size()); for (const auto& beat : beats) { if (beat.enabled()) { - beatvect.append(beat.frame_position()); + beatvect.append(mixxx::audio::FramePos(beat.frame_position())); } } @@ -164,12 +175,12 @@ class BeatMapIterator : public BeatIterator { } double next() override { - double beat = framesToSamples(m_currentBeat->frame_position()); + const auto beat = mixxx::audio::FramePos(m_currentBeat->frame_position()); ++m_currentBeat; while (m_currentBeat != m_endBeat && !m_currentBeat->enabled()) { ++m_currentBeat; } - return beat; + return beat.toEngineSamplePos(); } private: @@ -225,23 +236,29 @@ BeatsPointer BeatMap::makeBeatMap( BeatsPointer BeatMap::makeBeatMap( audio::SampleRate sampleRate, const QString& subVersion, - const QVector& beats) { + const QVector& beats) { BeatList beatList; - double previous_beatpos = -1; - Beat beat; + mixxx::audio::FramePos previousBeatPos = mixxx::audio::kInvalidFramePos; - 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(static_cast(beatpos)); - beatList.append(beat); - previous_beatpos = beatpos; + for (const mixxx::audio::FramePos& originalBeatPos : beats) { + VERIFY_OR_DEBUG_ASSERT(originalBeatPos.isValid()) { + qWarning() << "BeatMap::makeBeatMap: Beats is invalid, discarding beat"; + continue; + } + + // Do not accept fractional frames. + const auto beatPos = mixxx::audio::FramePos(std::floor(originalBeatPos.value())); + if (previousBeatPos.isValid() && beatPos <= previousBeatPos) { + qWarning() << "BeatMap::makeBeatMap: Beats not in increasing " + "order, discarding beat " + << beatPos; + continue; } + + Beat beat = beatFromFramePos(beatPos); + beatList.append(beat); + previousBeatPos = beatPos; } double nominalBpm = calculateNominalBpm(beatList, sampleRate); return BeatsPointer(new BeatMap(sampleRate, subVersion, beatList, nominalBpm)); @@ -302,9 +319,7 @@ double BeatMap::findNthBeat(double dSamples, int n) const { return -1; } - Beat beat; - // Reduce sample offset to a frame offset. - beat.set_frame_position(static_cast(samplesToFrames(dSamples))); + Beat beat = beatFromEngineSamplePos(dSamples); // it points at the first occurrence of beat or the next largest beat BeatList::const_iterator it = @@ -359,7 +374,7 @@ double BeatMap::findNthBeat(double dSamples, int n) const { } if (n == 1) { // Return a sample offset - return framesToSamples(next_beat->frame_position()); + return mixxx::audio::FramePos(next_beat->frame_position()).toEngineSamplePos(); } --n; } @@ -368,7 +383,9 @@ double BeatMap::findNthBeat(double dSamples, int n) const { if (previous_beat->enabled()) { if (n == -1) { // Return a sample offset - return framesToSamples(previous_beat->frame_position()); + return mixxx::audio::FramePos( + previous_beat->frame_position()) + .toEngineSamplePos(); } ++n; } @@ -392,9 +409,7 @@ bool BeatMap::findPrevNextBeats(double dSamples, return false; } - Beat beat; - // Reduce sample offset to a frame offset. - beat.set_frame_position(static_cast(samplesToFrames(dSamples))); + Beat beat = beatFromEngineSamplePos(dSamples); // it points at the first occurrence of beat or the next largest beat BeatList::const_iterator it = @@ -450,13 +465,16 @@ bool BeatMap::findPrevNextBeats(double dSamples, if (!next_beat->enabled()) { continue; } - *dpNextBeatSamples = framesToSamples(next_beat->frame_position()); + *dpNextBeatSamples = mixxx::audio::FramePos(next_beat->frame_position()) + .toEngineSamplePos(); break; } if (previous_beat != m_beats.end()) { for (; true; --previous_beat) { if (previous_beat->enabled()) { - *dpPrevBeatSamples = framesToSamples(previous_beat->frame_position()); + *dpPrevBeatSamples = + mixxx::audio::FramePos(previous_beat->frame_position()) + .toEngineSamplePos(); break; } @@ -476,10 +494,8 @@ std::unique_ptr BeatMap::findBeats(double startSample, double stop return std::unique_ptr(); } - Beat startBeat, stopBeat; - startBeat.set_frame_position( - static_cast(samplesToFrames(startSample))); - stopBeat.set_frame_position(static_cast(samplesToFrames(stopSample))); + Beat startBeat = beatFromEngineSamplePos(startSample); + Beat stopBeat = beatFromEngineSamplePos(stopSample); BeatList::const_iterator curBeat = std::lower_bound(m_beats.constBegin(), m_beats.constEnd(), @@ -523,25 +539,30 @@ double BeatMap::getBpmAroundPosition(double curSample, int n) const { // 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 lowerSample = findNthBeat(curSample, -n); + mixxx::audio::FramePos lowerFrame; if (lowerSample == -1) { - lowerSample = framesToSamples(m_beats.first().frame_position()); + lowerFrame = mixxx::audio::FramePos(m_beats.first().frame_position()); + } else { + lowerFrame = mixxx::audio::FramePos::fromEngineSamplePos(lowerSample); } // If we hit the end of the beat map, recalculate the lower bound. - double upperSample = findNthBeat(lowerSample, n * 2); + const double upperSample = findNthBeat(lowerSample, n * 2); + mixxx::audio::FramePos upperFrame; if (upperSample == -1) { - upperSample = framesToSamples(m_beats.last().frame_position()); - lowerSample = findNthBeat(upperSample, n * -2); + upperFrame = mixxx::audio::FramePos(m_beats.last().frame_position()); + lowerSample = findNthBeat(upperFrame.toEngineSamplePos(), n * -2); // Super edge-case -- the track doesn't have n beats! Do the best // we can. if (lowerSample == -1) { - lowerSample = framesToSamples(m_beats.first().frame_position()); + lowerFrame = mixxx::audio::FramePos(m_beats.first().frame_position()); + } else { + lowerFrame = mixxx::audio::FramePos::fromEngineSamplePos(lowerSample); } + } else { + upperFrame = mixxx::audio::FramePos::fromEngineSamplePos(upperSample); } - double lowerFrame = samplesToFrames(lowerSample); - double upperFrame = samplesToFrames(upperSample); - VERIFY_OR_DEBUG_ASSERT(lowerFrame < upperFrame) { return -1; } @@ -550,7 +571,7 @@ double BeatMap::getBpmAroundPosition(double curSample, int n) const { int numberOfBeats = 0; for (const auto& beat : m_beats) { - double pos = beat.frame_position() + kFrameEpsilon; + const auto pos = mixxx::audio::FramePos(beat.frame_position() + kFrameEpsilon); if (pos > upperFrame) { break; } @@ -569,12 +590,15 @@ BeatsPointer BeatMap::translate(double dNumSamples) const { } BeatList beats = m_beats; - double dNumFrames = samplesToFrames(dNumSamples); + const mixxx::audio::FrameDiff_t offset = frameDiffFromSampleDiff(dNumSamples); for (BeatList::iterator it = beats.begin(); it != beats.end();) { - double newpos = it->frame_position() + dNumFrames; - if (newpos >= 0) { - it->set_frame_position(static_cast(newpos)); + const auto oldPosition = mixxx::audio::FramePos(it->frame_position()); + mixxx::audio::FramePos newPosition = oldPosition + offset; + + // FIXME: Don't we allow negative positions? + if (newPosition >= mixxx::audio::kStartFramePos) { + it->set_frame_position(static_cast(newPosition.value())); ++it; } else { it = beats.erase(it); diff --git a/src/track/beatmap.h b/src/track/beatmap.h index a51e313609d..7215bfcaad4 100644 --- a/src/track/beatmap.h +++ b/src/track/beatmap.h @@ -7,6 +7,7 @@ #pragma once +#include "audio/frame.h" #include "proto/beats.pb.h" #include "track/beats.h" @@ -31,7 +32,7 @@ class BeatMap final : public Beats { static BeatsPointer makeBeatMap( audio::SampleRate sampleRate, const QString& subVersion, - const QVector& beats); + const QVector& beats); Beats::CapabilitiesFlags getCapabilities() const override { return BEATSCAP_TRANSLATE | BEATSCAP_SCALE | BEATSCAP_ADDREMOVE | diff --git a/src/track/beatsimporter.h b/src/track/beatsimporter.h index 0b58b374334..402889fba24 100644 --- a/src/track/beatsimporter.h +++ b/src/track/beatsimporter.h @@ -3,6 +3,7 @@ #include #include +#include "audio/frame.h" #include "audio/streaminfo.h" namespace mixxx { @@ -17,7 +18,7 @@ class BeatsImporter { /// Determines the timing offset and returns a Vector of frame positions /// to use as input for the BeatMap constructor - virtual QVector importBeatsAndApplyTimingOffset( + virtual QVector importBeatsAndApplyTimingOffset( const QString& filePath, const audio::StreamInfo& streamInfo) = 0; }; diff --git a/src/track/beatutils.cpp b/src/track/beatutils.cpp index 418ba9adb13..83e4df76ab6 100644 --- a/src/track/beatutils.cpp +++ b/src/track/beatutils.cpp @@ -25,9 +25,9 @@ constexpr int kMinRegionBeatCount = 16; double BeatUtils::calculateAverageBpm(int numberOfBeats, mixxx::audio::SampleRate sampleRate, - double lowerFrame, - double upperFrame) { - double frames = upperFrame - lowerFrame; + mixxx::audio::FramePos lowerFrame, + mixxx::audio::FramePos upperFrame) { + mixxx::audio::FrameDiff_t frames = upperFrame - lowerFrame; DEBUG_ASSERT(frames > 0); if (numberOfBeats < 1) { return 0; @@ -36,7 +36,7 @@ double BeatUtils::calculateAverageBpm(int numberOfBeats, } double BeatUtils::calculateBpm( - const QVector& beats, + const QVector& beats, mixxx::audio::SampleRate sampleRate) { if (beats.size() < 2) { return 0; @@ -54,7 +54,7 @@ double BeatUtils::calculateBpm( } QVector BeatUtils::retrieveConstRegions( - const QVector& coarseBeats, + const QVector& coarseBeats, mixxx::audio::SampleRate sampleRate) { // The QM Beat detector has a step size of 512 frames @ 44100 Hz. This means that // Single beats have has a jitter of +- 12 ms around the actual position. @@ -78,23 +78,23 @@ QVector BeatUtils::retrieveConstRegions( return constantRegions; } - double maxPhaseError = kMaxSecsPhaseError * sampleRate; - double maxPhaseErrorSum = kMaxSecsPhaseErrorSum * sampleRate; + const mixxx::audio::FrameDiff_t maxPhaseError = kMaxSecsPhaseError * sampleRate; + const mixxx::audio::FrameDiff_t maxPhaseErrorSum = kMaxSecsPhaseErrorSum * sampleRate; int leftIndex = 0; int rightIndex = coarseBeats.size() - 1; while (leftIndex < coarseBeats.size() - 1) { DEBUG_ASSERT(rightIndex > leftIndex); - double meanBeatLength = + mixxx::audio::FrameDiff_t meanBeatLength = (coarseBeats[rightIndex] - coarseBeats[leftIndex]) / (rightIndex - leftIndex); int outliersCount = 0; - double ironedBeat = coarseBeats[leftIndex]; - double phaseErrorSum = 0; + mixxx::audio::FramePos ironedBeat = coarseBeats[leftIndex]; + mixxx::audio::FrameDiff_t phaseErrorSum = 0; int i = leftIndex + 1; for (; i <= rightIndex; ++i) { ironedBeat += meanBeatLength; - double phaseError = ironedBeat - coarseBeats[i]; + const mixxx::audio::FrameDiff_t phaseError = ironedBeat - coarseBeats[i]; phaseErrorSum += phaseError; if (fabs(phaseError) > maxPhaseError) { outliersCount++; @@ -112,15 +112,17 @@ QVector BeatUtils::retrieveConstRegions( if (i > rightIndex) { // Verify that the first an the last beat are not correction beats in the same direction // This would bend meanBeatLength unfavorably away from the optimum. - double regionBorderError = 0; + mixxx::audio::FrameDiff_t regionBorderError = 0; if (rightIndex > leftIndex + 2) { - double firstBeatLength = coarseBeats[leftIndex + 1] - coarseBeats[leftIndex]; - double lastBeatLength = coarseBeats[rightIndex] - coarseBeats[rightIndex - 1]; + const mixxx::audio::FrameDiff_t firstBeatLength = + coarseBeats[leftIndex + 1] - coarseBeats[leftIndex]; + const mixxx::audio::FrameDiff_t lastBeatLength = + coarseBeats[rightIndex] - coarseBeats[rightIndex - 1]; regionBorderError = fabs(firstBeatLength + lastBeatLength - (2 * meanBeatLength)); } if (regionBorderError < maxPhaseError / 2) { // We have found a constant enough region. - double firstBeat = coarseBeats[leftIndex]; + const mixxx::audio::FramePos firstBeat = coarseBeats[leftIndex]; // store the regions for the later stages constantRegions.append({firstBeat, meanBeatLength}); // continue with the next region. @@ -142,7 +144,7 @@ QVector BeatUtils::retrieveConstRegions( double BeatUtils::makeConstBpm( const QVector& constantRegions, mixxx::audio::SampleRate sampleRate, - double* pFirstBeat) { + mixxx::audio::FramePos* pFirstBeat) { // We assume here the track was recorded with an unhear-able static metronome. // This metronome is likely at a full BPM. // The track may has intros, outros and bridges without detectable beats. @@ -161,10 +163,11 @@ double BeatUtils::makeConstBpm( // this functions are based on frames. int midRegionIndex = 0; - double longestRegionLength = 0; - double longestRegionBeatLength = 0; + mixxx::audio::FrameDiff_t longestRegionLength = 0; + mixxx::audio::FrameDiff_t longestRegionBeatLength = 0; for (int i = 0; i < constantRegions.size() - 1; ++i) { - double length = constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat; + mixxx::audio::FrameDiff_t length = + constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat; if (length > longestRegionLength) { longestRegionLength = length; longestRegionBeatLength = constantRegions[i].beatLength; @@ -180,34 +183,38 @@ double BeatUtils::makeConstBpm( int longestRegionNumberOfBeats = static_cast( (longestRegionLength / longestRegionBeatLength) + 0.5); - double longestRegionBeatLengthMin = longestRegionBeatLength - + mixxx::audio::FrameDiff_t longestRegionBeatLengthMin = longestRegionBeatLength - ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats); - double longestRegionBeatLengthMax = longestRegionBeatLength + + mixxx::audio::FrameDiff_t longestRegionBeatLengthMax = longestRegionBeatLength + ((kMaxSecsPhaseError * sampleRate) / longestRegionNumberOfBeats); int startRegionIndex = midRegionIndex; // Find a region at the beginning of the track with a similar tempo and phase for (int i = 0; i < midRegionIndex; ++i) { - const double length = constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat; + const mixxx::audio::FrameDiff_t length = + constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat; const int numberOfBeats = static_cast((length / constantRegions[i].beatLength) + 0.5); if (numberOfBeats < kMinRegionBeatCount) { // Request short regions, too unstable. continue; } - const double thisRegionBeatLengthMin = constantRegions[i].beatLength - + const mixxx::audio::FrameDiff_t thisRegionBeatLengthMin = constantRegions[i].beatLength - ((kMaxSecsPhaseError * sampleRate) / numberOfBeats); - const double thisRegionBeatLengthMax = constantRegions[i].beatLength + + const mixxx::audio::FrameDiff_t thisRegionBeatLengthMax = constantRegions[i].beatLength + ((kMaxSecsPhaseError * sampleRate) / numberOfBeats); // check if the tempo of the longest region is part of the rounding range of this region if (longestRegionBeatLength > thisRegionBeatLengthMin && longestRegionBeatLength < thisRegionBeatLengthMax) { // Now check if both regions are at the same phase. - const double newLongestRegionLength = constantRegions[midRegionIndex + 1].firstBeat - + const mixxx::audio::FrameDiff_t newLongestRegionLength = + constantRegions[midRegionIndex + 1].firstBeat - constantRegions[i].firstBeat; - double beatLengthMin = math_max(longestRegionBeatLengthMin, thisRegionBeatLengthMin); - double beatLengthMax = math_min(longestRegionBeatLengthMax, thisRegionBeatLengthMax); + mixxx::audio::FrameDiff_t beatLengthMin = math_max( + longestRegionBeatLengthMin, thisRegionBeatLengthMin); + mixxx::audio::FrameDiff_t beatLengthMax = math_min( + longestRegionBeatLengthMax, thisRegionBeatLengthMax); const int maxNumberOfBeats = static_cast(round(newLongestRegionLength / beatLengthMin)); @@ -219,7 +226,7 @@ double BeatUtils::makeConstBpm( continue; } const int numberOfBeats = minNumberOfBeats; - const double newBeatLength = newLongestRegionLength / numberOfBeats; + const mixxx::audio::FrameDiff_t newBeatLength = newLongestRegionLength / numberOfBeats; if (newBeatLength > longestRegionBeatLengthMin && newBeatLength < longestRegionBeatLengthMax) { longestRegionLength = newLongestRegionLength; @@ -237,23 +244,27 @@ double BeatUtils::makeConstBpm( // Find a region at the end of the track with similar tempo and phase for (int i = constantRegions.size() - 2; i > midRegionIndex; --i) { - const double length = constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat; + const mixxx::audio::FrameDiff_t length = + constantRegions[i + 1].firstBeat - constantRegions[i].firstBeat; const int numberOfBeats = static_cast((length / constantRegions[i].beatLength) + 0.5); if (numberOfBeats < kMinRegionBeatCount) { continue; } - const double thisRegionBeatLengthMin = constantRegions[i].beatLength - + const mixxx::audio::FrameDiff_t thisRegionBeatLengthMin = constantRegions[i].beatLength - ((kMaxSecsPhaseError * sampleRate) / numberOfBeats); - const double thisRegionBeatLengthMax = constantRegions[i].beatLength + + const mixxx::audio::FrameDiff_t thisRegionBeatLengthMax = constantRegions[i].beatLength + ((kMaxSecsPhaseError * sampleRate) / numberOfBeats); if (longestRegionBeatLength > thisRegionBeatLengthMin && longestRegionBeatLength < thisRegionBeatLengthMax) { // Now check if both regions are at the same phase. - const double newLongestRegionLength = constantRegions[i + 1].firstBeat - + const mixxx::audio::FrameDiff_t newLongestRegionLength = + constantRegions[i + 1].firstBeat - constantRegions[startRegionIndex].firstBeat; - double minBeatLength = math_max(longestRegionBeatLengthMin, thisRegionBeatLengthMin); - double maxBeatLength = math_min(longestRegionBeatLengthMax, thisRegionBeatLengthMax); + mixxx::audio::FrameDiff_t minBeatLength = math_max( + longestRegionBeatLengthMin, thisRegionBeatLengthMin); + mixxx::audio::FrameDiff_t maxBeatLength = math_min( + longestRegionBeatLengthMax, thisRegionBeatLengthMax); const int maxNumberOfBeats = static_cast(round(newLongestRegionLength / minBeatLength)); @@ -265,7 +276,7 @@ double BeatUtils::makeConstBpm( continue; } const int numberOfBeats = minNumberOfBeats; - const double newBeatLength = newLongestRegionLength / numberOfBeats; + const mixxx::audio::FrameDiff_t newBeatLength = newLongestRegionLength / numberOfBeats; if (newBeatLength > longestRegionBeatLengthMin && newBeatLength < longestRegionBeatLengthMax) { longestRegionLength = newLongestRegionLength; @@ -304,8 +315,9 @@ double BeatUtils::makeConstBpm( // 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; - *pFirstBeat = fmod(constantRegions[startRegionIndex].firstBeat, - roundedBeatLength); + *pFirstBeat = mixxx::audio::FramePos( + fmod(constantRegions[startRegionIndex].firstBeat.value(), + roundedBeatLength)); } return roundBpm; } @@ -352,10 +364,11 @@ double BeatUtils::roundBpmWithinRange(double minBpm, double centerBpm, double ma } // static -QVector BeatUtils::getBeats(const QVector& constantRegions) { - QVector beats; +QVector BeatUtils::getBeats( + const QVector& constantRegions) { + QVector beats; for (int i = 0; i < constantRegions.size() - 1; ++i) { - double beat = constantRegions[i].firstBeat; + mixxx::audio::FramePos beat = constantRegions[i].firstBeat; constexpr double epsilon = 100; // Protection against tiny beats due rounding while (beat < constantRegions[i + 1].firstBeat - epsilon) { beats.append(beat); @@ -369,17 +382,18 @@ QVector BeatUtils::getBeats(const QVector& const } // static -double BeatUtils::adjustPhase( - double firstBeat, +mixxx::audio::FramePos BeatUtils::adjustPhase( + mixxx::audio::FramePos firstBeat, double bpm, mixxx::audio::SampleRate sampleRate, - const QVector& beats) { + const QVector& beats) { const double beatLength = 60 * sampleRate / bpm; - const double startOffset = fmod(firstBeat, beatLength); - double offsetAdjust = 0; + const mixxx::audio::FramePos startOffset = + mixxx::audio::FramePos(fmod(firstBeat.value(), beatLength)); + mixxx::audio::FrameDiff_t offsetAdjust = 0; double offsetAdjustCount = 0; for (const auto& beat : beats) { - double offset = fmod(beat - startOffset, beatLength); + mixxx::audio::FrameDiff_t offset = fmod(beat - startOffset, beatLength); if (offset > beatLength / 2) { offset -= beatLength; } diff --git a/src/track/beatutils.h b/src/track/beatutils.h index 5a4df420e1d..4b05451f29d 100644 --- a/src/track/beatutils.h +++ b/src/track/beatutils.h @@ -2,40 +2,41 @@ #include +#include "audio/frame.h" #include "audio/types.h" #include "util/math.h" class BeatUtils { public: struct ConstRegion { - double firstBeat; - double beatLength; + mixxx::audio::FramePos firstBeat; + mixxx::audio::FrameDiff_t beatLength; }; - static double calculateBpm(const QVector& beats, + static double calculateBpm(const QVector& beats, mixxx::audio::SampleRate sampleRate); static QVector retrieveConstRegions( - const QVector& coarseBeats, + const QVector& coarseBeats, mixxx::audio::SampleRate sampleRate); static double calculateAverageBpm(int numberOfBeats, mixxx::audio::SampleRate sampleRate, - double lowerFrame, - double upperFrame); + mixxx::audio::FramePos lowerFrame, + mixxx::audio::FramePos upperFrame); static double makeConstBpm( const QVector& constantRegions, mixxx::audio::SampleRate sampleRate, - double* pFirstBeat); + mixxx::audio::FramePos* pFirstBeat); - static double adjustPhase( - double firstBeat, + static mixxx::audio::FramePos adjustPhase( + mixxx::audio::FramePos firstBeat, double bpm, mixxx::audio::SampleRate sampleRate, - const QVector& beats); + const QVector& beats); - static QVector getBeats(const QVector& constantRegions); + static QVector getBeats(const QVector& constantRegions); static double roundBpmWithinRange(double minBpm, double centerBpm, double maxBpm); }; diff --git a/src/track/serato/beatsimporter.cpp b/src/track/serato/beatsimporter.cpp index 96725283d5f..364408107ad 100644 --- a/src/track/serato/beatsimporter.cpp +++ b/src/track/serato/beatsimporter.cpp @@ -22,7 +22,7 @@ bool SeratoBeatsImporter::isEmpty() const { return !m_pTerminalMarker; }; -QVector SeratoBeatsImporter::importBeatsAndApplyTimingOffset( +QVector SeratoBeatsImporter::importBeatsAndApplyTimingOffset( const QString& filePath, const audio::StreamInfo& streamInfo) { const audio::SignalInfo& signalInfo = streamInfo.getSignalInfo(); const double timingOffsetMillis = SeratoTags::guessTimingOffsetMillis( @@ -31,13 +31,13 @@ QVector SeratoBeatsImporter::importBeatsAndApplyTimingOffset( return importBeatsAndApplyTimingOffset(timingOffsetMillis, signalInfo); } -QVector SeratoBeatsImporter::importBeatsAndApplyTimingOffset( +QVector SeratoBeatsImporter::importBeatsAndApplyTimingOffset( double timingOffsetMillis, const audio::SignalInfo& signalInfo) { VERIFY_OR_DEBUG_ASSERT(!isEmpty()) { return {}; } - QVector beats; + QVector beats; double beatPositionMillis = 0; // Calculate beat positions for non-terminal markers for (int i = 0; i < m_nonTerminalMarkers.size(); ++i) { @@ -62,8 +62,8 @@ QVector SeratoBeatsImporter::importBeatsAndApplyTimingOffset( beats.reserve(beats.size() + pMarker->beatsTillNextMarker()); for (quint32 j = 0; j < pMarker->beatsTillNextMarker(); ++j) { - beats.append(signalInfo.millis2frames( - beatPositionMillis + timingOffsetMillis)); + beats.append(mixxx::audio::FramePos(signalInfo.millis2frames( + beatPositionMillis + timingOffsetMillis))); beatPositionMillis += beatLengthMillis; } } @@ -99,8 +99,8 @@ QVector SeratoBeatsImporter::importBeatsAndApplyTimingOffset( // Now fill the range with beats until the end is reached. Add a half beat // length, to make sure that the last beat is actually included. while (beatPositionMillis <= (rangeEndBeatPositionMillis + beatLengthMillis / 2)) { - beats.append(signalInfo.millis2frames( - beatPositionMillis + timingOffsetMillis)); + beats.append(mixxx::audio::FramePos(signalInfo.millis2frames( + beatPositionMillis + timingOffsetMillis))); beatPositionMillis += beatLengthMillis; } diff --git a/src/track/serato/beatsimporter.h b/src/track/serato/beatsimporter.h index 71a0ee7e00c..26c3ec5450a 100644 --- a/src/track/serato/beatsimporter.h +++ b/src/track/serato/beatsimporter.h @@ -18,14 +18,14 @@ class SeratoBeatsImporter : public BeatsImporter { ~SeratoBeatsImporter() override = default; bool isEmpty() const override; - QVector importBeatsAndApplyTimingOffset( + QVector importBeatsAndApplyTimingOffset( const QString& filePath, const audio::StreamInfo& streamInfo) override; private: FRIEND_TEST(SeratoBeatGridTest, SerializeBeatMap); - QVector importBeatsAndApplyTimingOffset( + QVector importBeatsAndApplyTimingOffset( double timingOffsetMillis, const audio::SignalInfo& signalInfo); QList m_nonTerminalMarkers; diff --git a/src/track/track.cpp b/src/track/track.cpp index 15e3d495b06..6829658640f 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -345,7 +345,9 @@ bool Track::trySetBpmWhileLocked(double bpmValue) { } else if (!m_pBeats) { // No beat grid available -> create and initialize double cue = m_record.getCuePoint().getPosition(); - auto pBeats = BeatFactory::makeBeatGrid(getSampleRate(), bpmValue, cue); + auto pBeats = BeatFactory::makeBeatGrid(getSampleRate(), + bpmValue, + mixxx::audio::FramePos::fromEngineSamplePos(cue)); return trySetBeatsWhileLocked(std::move(pBeats)); } else if ((m_pBeats->getCapabilities() & mixxx::Beats::BEATSCAP_SETBPM) && m_pBeats->getBpm() != bpmValue) {