Skip to content

Commit

Permalink
Beats: Make return value of mutation methods optional
Browse files Browse the repository at this point in the history
This allows the caller to handle the failure case more efficiently, e.g.
by exiting early if nothing changes.

If the caller does not care wether the operation worked and resulted in
changes or not and simply needs a valid beats pointer, the following
expression can be used:

    pBeats->scale(...).value_or(pBeats)
  • Loading branch information
Holzhaus committed Oct 14, 2021
1 parent 908245a commit bfb9cc7
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 61 deletions.
36 changes: 28 additions & 8 deletions src/engine/controls/bpmcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ void BpmControl::adjustBeatsBpm(double deltaBpm) {
const auto centerBpm = mixxx::Bpm(math_max(kBpmAdjustMin, bpm.value() + deltaBpm));
mixxx::Bpm adjustedBpm = BeatUtils::roundBpmWithinRange(
centerBpm - kBpmAdjustStep / 2, centerBpm, centerBpm + kBpmAdjustStep / 2);
pTrack->trySetBeats(pBeats->setBpm(adjustedBpm));
const auto newBeats = pBeats->setBpm(adjustedBpm);
if (!newBeats) {
return;
}
pTrack->trySetBeats(*newBeats);
}

void BpmControl::slotAdjustBeatsFaster(double v) {
Expand Down Expand Up @@ -191,7 +195,10 @@ void BpmControl::slotTranslateBeatsEarlier(double v) {
if (pBeats) {
const double sampleOffset = frameInfo().sampleRate * -0.01;
const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount;
pTrack->trySetBeats(pBeats->translate(frameOffset));
const auto translatedBeats = pBeats->translate(frameOffset);
if (translatedBeats) {
pTrack->trySetBeats(*translatedBeats);
}
}
}

Expand All @@ -208,7 +215,10 @@ void BpmControl::slotTranslateBeatsLater(double v) {
// TODO(rryan): Track::frameInfo is possibly inaccurate!
const double sampleOffset = frameInfo().sampleRate * 0.01;
const mixxx::audio::FrameDiff_t frameOffset = sampleOffset / mixxx::kEngineChannelCount;
pTrack->trySetBeats(pBeats->translate(frameOffset));
const auto translatedBeats = pBeats->translate(frameOffset);
if (translatedBeats) {
pTrack->trySetBeats(*translatedBeats);
}
}
}

Expand Down Expand Up @@ -245,7 +255,11 @@ void BpmControl::slotTapFilter(double averageLength, int numSamples) {
averageBpm = BeatUtils::roundBpmWithinRange(averageBpm - kBpmTabRounding,
averageBpm,
averageBpm + kBpmTabRounding);
pTrack->trySetBeats(pBeats->setBpm(averageBpm));
const auto newBeats = pBeats->setBpm(averageBpm);
if (!newBeats) {
return;
}
pTrack->trySetBeats(*newBeats);
}

// static
Expand Down Expand Up @@ -961,8 +975,11 @@ void BpmControl::slotBeatsTranslate(double v) {
if (pBeats) {
const auto currentPosition = frameInfo().currentPosition.toLowerFrameBoundary();
const auto closestBeat = pBeats->findClosestBeat(currentPosition);
const mixxx::audio::FrameDiff_t delta = currentPosition - closestBeat;
pTrack->trySetBeats(pBeats->translate(delta));
const mixxx::audio::FrameDiff_t frameOffset = currentPosition - closestBeat;
const auto translatedBeats = pBeats->translate(frameOffset);
if (translatedBeats) {
pTrack->trySetBeats(*translatedBeats);
}
}
}

Expand All @@ -980,8 +997,11 @@ void BpmControl::slotBeatsTranslateMatchAlignment(double v) {
// otherwise it will always return 0 if sync lock is active.
m_dUserOffset.setValue(0.0);

const mixxx::audio::FrameDiff_t frameOffset = getPhaseOffset(frameInfo().currentPosition);
pTrack->trySetBeats(pBeats->translate(-frameOffset));
const mixxx::audio::FrameDiff_t frameOffset = -getPhaseOffset(frameInfo().currentPosition);
const auto translatedBeats = pBeats->translate(frameOffset);
if (translatedBeats) {
pTrack->trySetBeats(*translatedBeats);
}
}
}

Expand Down
28 changes: 15 additions & 13 deletions src/library/dlgtrackinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,33 +520,35 @@ void DlgTrackInfo::clear() {
}

void DlgTrackInfo::slotBpmDouble() {
m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::BpmScale::Double);
updateSpinBpmFromBeats();
slotBpmScale(mixxx::Beats::BpmScale::Double);
}

void DlgTrackInfo::slotBpmHalve() {
m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::BpmScale::Halve);
updateSpinBpmFromBeats();
slotBpmScale(mixxx::Beats::BpmScale::Halve);
}

void DlgTrackInfo::slotBpmTwoThirds() {
m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::BpmScale::TwoThirds);
updateSpinBpmFromBeats();
slotBpmScale(mixxx::Beats::BpmScale::TwoThirds);
}

void DlgTrackInfo::slotBpmThreeFourth() {
m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::BpmScale::ThreeFourths);
updateSpinBpmFromBeats();
slotBpmScale(mixxx::Beats::BpmScale::ThreeFourths);
}

void DlgTrackInfo::slotBpmFourThirds() {
m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::BpmScale::FourThirds);
updateSpinBpmFromBeats();
slotBpmScale(mixxx::Beats::BpmScale::FourThirds);
}

void DlgTrackInfo::slotBpmThreeHalves() {
m_pBeatsClone = m_pBeatsClone->scale(mixxx::Beats::BpmScale::ThreeHalves);
updateSpinBpmFromBeats();
slotBpmScale(mixxx::Beats::BpmScale::ThreeHalves);
}

void DlgTrackInfo::slotBpmScale(mixxx::Beats::BpmScale bpmScale) {
const auto scaledBeats = m_pBeatsClone->scale(bpmScale);
if (scaledBeats) {
m_pBeatsClone = *scaledBeats;
updateSpinBpmFromBeats();
}
}

void DlgTrackInfo::slotBpmClear() {
Expand Down Expand Up @@ -610,7 +612,7 @@ void DlgTrackInfo::slotSpinBpmValueChanged(double value) {
if (oldValue == bpm) {
return;
}
m_pBeatsClone = m_pBeatsClone->setBpm(bpm);
m_pBeatsClone = m_pBeatsClone->setBpm(bpm).value_or(m_pBeatsClone);
}

updateSpinBpmFromBeats();
Expand Down
1 change: 1 addition & 0 deletions src/library/dlgtrackinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo {
void slotBpmThreeFourth();
void slotBpmFourThirds();
void slotBpmThreeHalves();
void slotBpmScale(mixxx::Beats::BpmScale bpmScale);
void slotBpmClear();
void slotBpmConstChanged(int state);
void slotBpmTap(double averageLength, int numSamples);
Expand Down
12 changes: 6 additions & 6 deletions src/test/beatgridtest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ TEST(BeatGridTest, Scale) {
mixxx::Bpm(bpm));

EXPECT_DOUBLE_EQ(bpm.value(), pGrid->getBpm().value());
pGrid = pGrid->scale(Beats::BpmScale::Double);
pGrid = *pGrid->scale(Beats::BpmScale::Double);
EXPECT_DOUBLE_EQ(2 * bpm.value(), pGrid->getBpm().value());

pGrid = pGrid->scale(Beats::BpmScale::Halve);
pGrid = *pGrid->scale(Beats::BpmScale::Halve);
EXPECT_DOUBLE_EQ(bpm.value(), pGrid->getBpm().value());

pGrid = pGrid->scale(Beats::BpmScale::TwoThirds);
pGrid = *pGrid->scale(Beats::BpmScale::TwoThirds);
EXPECT_DOUBLE_EQ(bpm.value() * 2 / 3, pGrid->getBpm().value());

pGrid = pGrid->scale(Beats::BpmScale::ThreeHalves);
pGrid = *pGrid->scale(Beats::BpmScale::ThreeHalves);
EXPECT_DOUBLE_EQ(bpm.value(), pGrid->getBpm().value());

pGrid = pGrid->scale(Beats::BpmScale::ThreeFourths);
pGrid = *pGrid->scale(Beats::BpmScale::ThreeFourths);
EXPECT_DOUBLE_EQ(bpm.value() * 3 / 4, pGrid->getBpm().value());

pGrid = pGrid->scale(Beats::BpmScale::FourThirds);
pGrid = *pGrid->scale(Beats::BpmScale::FourThirds);
EXPECT_DOUBLE_EQ(bpm.value(), pGrid->getBpm().value());
}

Expand Down
12 changes: 6 additions & 6 deletions src/test/beatmaptest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,22 @@ TEST_F(BeatMapTest, Scale) {
auto pMap = Beats::fromBeatPositions(m_pTrack->getSampleRate(), beats);

EXPECT_DOUBLE_EQ(bpm.value(), pMap->getBpm().value());
pMap = pMap->scale(Beats::BpmScale::Double);
pMap = *pMap->scale(Beats::BpmScale::Double);
EXPECT_DOUBLE_EQ(2 * bpm.value(), pMap->getBpm().value());

pMap = pMap->scale(Beats::BpmScale::Halve);
pMap = *pMap->scale(Beats::BpmScale::Halve);
EXPECT_DOUBLE_EQ(bpm.value(), pMap->getBpm().value());

pMap = pMap->scale(Beats::BpmScale::TwoThirds);
pMap = *pMap->scale(Beats::BpmScale::TwoThirds);
EXPECT_DOUBLE_EQ(bpm.value() * 2 / 3, pMap->getBpm().value());

pMap = pMap->scale(Beats::BpmScale::ThreeHalves);
pMap = *pMap->scale(Beats::BpmScale::ThreeHalves);
EXPECT_DOUBLE_EQ(bpm.value(), pMap->getBpm().value());

pMap = pMap->scale(Beats::BpmScale::ThreeFourths);
pMap = *pMap->scale(Beats::BpmScale::ThreeFourths);
EXPECT_DOUBLE_EQ(bpm.value() * 3 / 4, pMap->getBpm().value());

pMap = pMap->scale(Beats::BpmScale::FourThirds);
pMap = *pMap->scale(Beats::BpmScale::FourThirds);
EXPECT_DOUBLE_EQ(bpm.value(), pMap->getBpm().value());
}

Expand Down
17 changes: 9 additions & 8 deletions src/track/beatgrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ mixxx::Bpm BeatGrid::getBpmAroundPosition(audio::FramePos position, int n) const
return getBpm();
}

BeatsPointer BeatGrid::translate(audio::FrameDiff_t offset) const {
std::optional<BeatsPointer> BeatGrid::translate(audio::FrameDiff_t offset) const {
VERIFY_OR_DEBUG_ASSERT(isValid()) {
return clonePointer();
return std::nullopt;
}

mixxx::track::io::BeatGrid grid = m_grid;
Expand All @@ -275,9 +275,9 @@ BeatsPointer BeatGrid::translate(audio::FrameDiff_t offset) const {
return std::make_shared<BeatGrid>(MakeSharedTag{}, *this, grid, m_beatLengthFrames);
}

BeatsPointer BeatGrid::scale(BpmScale scale) const {
std::optional<BeatsPointer> BeatGrid::scale(BpmScale scale) const {
VERIFY_OR_DEBUG_ASSERT(isValid()) {
return clonePointer();
return std::nullopt;
}

mixxx::track::io::BeatGrid grid = m_grid;
Expand All @@ -304,11 +304,12 @@ BeatsPointer BeatGrid::scale(BpmScale scale) const {
break;
default:
DEBUG_ASSERT(!"scale value invalid");
return clonePointer();
return std::nullopt;
}

if (!bpm.isValid()) {
return clonePointer();
qWarning() << "BeatGrid: Scaling would result in invalid BPM!";
return std::nullopt;
}

bpm = BeatUtils::roundBpmWithinRange(bpm - kBpmScaleRounding, bpm, bpm + kBpmScaleRounding);
Expand All @@ -317,9 +318,9 @@ BeatsPointer BeatGrid::scale(BpmScale scale) const {
return std::make_shared<BeatGrid>(MakeSharedTag{}, *this, grid, beatLengthFrames);
}

BeatsPointer BeatGrid::setBpm(mixxx::Bpm bpm) const {
std::optional<BeatsPointer> BeatGrid::setBpm(mixxx::Bpm bpm) const {
VERIFY_OR_DEBUG_ASSERT(bpm.isValid()) {
return clonePointer();
return std::nullopt;
}

mixxx::track::io::BeatGrid grid = m_grid;
Expand Down
6 changes: 3 additions & 3 deletions src/track/beatgrid.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ class BeatGrid final : public Beats {
// Beat mutations
////////////////////////////////////////////////////////////////////////////

BeatsPointer translate(audio::FrameDiff_t offset) const override;
BeatsPointer scale(BpmScale scale) const override;
BeatsPointer setBpm(mixxx::Bpm bpm) const override;
std::optional<BeatsPointer> translate(audio::FrameDiff_t offset) const override;
std::optional<BeatsPointer> scale(BpmScale scale) const override;
std::optional<BeatsPointer> setBpm(mixxx::Bpm bpm) const override;

////////////////////////////////////////////////////////////////////////////
// Hidden constructors
Expand Down
14 changes: 7 additions & 7 deletions src/track/beatmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,9 +532,9 @@ mixxx::Bpm BeatMap::getBpmAroundPosition(audio::FramePos position, int n) const
numberOfBeats, m_sampleRate, lowerFrame, upperFrame);
}

BeatsPointer BeatMap::translate(audio::FrameDiff_t offset) const {
std::optional<BeatsPointer> BeatMap::translate(audio::FrameDiff_t offset) const {
if (!isValid()) {
return clonePointer();
return std::nullopt;
}

BeatList beats = m_beats;
Expand All @@ -555,9 +555,9 @@ BeatsPointer BeatMap::translate(audio::FrameDiff_t offset) const {
return std::make_shared<BeatMap>(MakeSharedTag{}, *this, beats, m_nominalBpm);
}

BeatsPointer BeatMap::scale(BpmScale scale) const {
std::optional<BeatsPointer> BeatMap::scale(BpmScale scale) const {
VERIFY_OR_DEBUG_ASSERT(isValid()) {
return clonePointer();
return std::nullopt;
}

BeatList beats = m_beats;
Expand Down Expand Up @@ -596,16 +596,16 @@ BeatsPointer BeatMap::scale(BpmScale scale) const {
break;
default:
DEBUG_ASSERT(!"scale value invalid");
return clonePointer();
return std::nullopt;
}

mixxx::Bpm bpm = calculateNominalBpm(beats, m_sampleRate);
return std::make_shared<BeatMap>(MakeSharedTag{}, *this, beats, bpm);
}

BeatsPointer BeatMap::setBpm(mixxx::Bpm bpm) const {
std::optional<BeatsPointer> BeatMap::setBpm(mixxx::Bpm bpm) const {
VERIFY_OR_DEBUG_ASSERT(bpm.isValid()) {
return clonePointer();
return std::nullopt;
}

const auto firstBeatPosition = mixxx::audio::FramePos(m_beats.first().frame_position());
Expand Down
6 changes: 3 additions & 3 deletions src/track/beatmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ class BeatMap final : public Beats {
// Beat mutations
////////////////////////////////////////////////////////////////////////////

BeatsPointer translate(audio::FrameDiff_t offset) const override;
BeatsPointer scale(BpmScale scale) const override;
BeatsPointer setBpm(mixxx::Bpm bpm) const override;
std::optional<BeatsPointer> translate(audio::FrameDiff_t offset) const override;
std::optional<BeatsPointer> scale(BpmScale scale) const override;
std::optional<BeatsPointer> setBpm(mixxx::Bpm bpm) const override;

////////////////////////////////////////////////////////////////////////////
// Hidden constructors
Expand Down
16 changes: 13 additions & 3 deletions src/track/beats.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <QString>
#include <QVector>
#include <memory>
#include <optional>

#include "audio/frame.h"
#include "audio/types.h"
Expand Down Expand Up @@ -167,13 +168,22 @@ class Beats : private std::enable_shared_from_this<Beats> {
/// Translate all beats in the song by `offset` frames. Beats that lie
/// before the start of the track or after the end of the track are *not*
/// removed.
virtual BeatsPointer translate(audio::FrameDiff_t offset) const = 0;
//
/// Returns a pointer to the modified beats object, or `nullopt` on
/// failure.
virtual std::optional<BeatsPointer> translate(audio::FrameDiff_t offset) const = 0;

/// Scale the position of every beat in the song by `scale`.
virtual BeatsPointer scale(BpmScale scale) const = 0;
//
/// Returns a pointer to the modified beats object, or `nullopt` on
/// failure.
virtual std::optional<BeatsPointer> scale(BpmScale scale) const = 0;

/// Adjust the beats so the global average BPM matches `bpm`.
virtual BeatsPointer setBpm(mixxx::Bpm bpm) const = 0;
//
/// Returns a pointer to the modified beats object, or `nullopt` on
/// failure.
virtual std::optional<BeatsPointer> setBpm(mixxx::Bpm bpm) const = 0;

protected:
/// Type tag for making public constructors of derived classes inaccessible.
Expand Down
10 changes: 7 additions & 3 deletions src/track/track.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,14 @@ bool Track::trySetBpmWhileLocked(mixxx::Bpm bpm) {
return trySetBeatsWhileLocked(std::move(pBeats));
} else if (m_pBeats->getBpm() != bpm) {
// Continue with the regular cases
if (kLogger.debugEnabled()) {
kLogger.debug() << "Updating BPM:" << getLocation();
const auto newBeats = m_pBeats->setBpm(bpm);
if (newBeats) {
if (kLogger.debugEnabled()) {
kLogger.debug() << "Updating BPM:" << getLocation();
}

return trySetBeatsWhileLocked(*newBeats);
}
return trySetBeatsWhileLocked(m_pBeats->setBpm(bpm));
}
return false;
}
Expand Down
6 changes: 5 additions & 1 deletion src/widget/wtrackmenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,11 @@ class ScaleBpmTrackPointerOperation : public mixxx::TrackPointerOperation {
if (!pBeats) {
return;
}
pTrack->trySetBeats(pBeats->scale(m_bpmScale));
const auto scaledBeats = pBeats->scale(m_bpmScale);
if (!scaledBeats) {
return;
}
pTrack->trySetBeats(*scaledBeats);
}

const mixxx::Beats::BpmScale m_bpmScale;
Expand Down

0 comments on commit bfb9cc7

Please sign in to comment.