Skip to content

Commit

Permalink
feat: add downbeat support to beat map
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Holthuis <[email protected]>
  • Loading branch information
acolombier and Holzhaus committed Oct 23, 2024
1 parent fe9608f commit 9de4d5c
Show file tree
Hide file tree
Showing 10 changed files with 1,070 additions and 114 deletions.
6 changes: 6 additions & 0 deletions src/audio/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ QDebug operator<<(QDebug dbg, Bitrate arg) {
<< Bitrate::unit();
}

QDebug operator<<(QDebug dbg, BeatsPerBar arg) {
return dbg
<< static_cast<BeatsPerBar::value_t>(arg)
<< BeatsPerBar::unit();
}

} // namespace audio

} // namespace mixxx
47 changes: 47 additions & 0 deletions src/audio/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,50 @@ class Bitrate {

QDebug operator<<(QDebug dbg, Bitrate arg);

// The BeatsPerBar is measured in beats and provides information
// about the number expected within a single bar or phrase.
class BeatsPerBar {
public:
using value_t = std::uint32_t;

private:
// The default value is invalid and indicates a missing or unknown value.
static constexpr value_t kValueDefault = 4;

public:
static constexpr value_t kValueMin = 2; // lower bound (inclusive)
static constexpr value_t kValueMax = 32; // upper bound (inclusive)
static constexpr const char* unit() {
return "beats";
}

explicit constexpr BeatsPerBar(
value_t value = kValueDefault)
: m_value(value) {
}

constexpr bool isValid() const {
return m_value >= kValueMin && m_value <= kValueMax;
}

constexpr value_t value() const {
return m_value;
}
/*implicit*/ constexpr operator value_t() const {
return value();
}

BeatsPerBar operator+(std::int32_t increment) const {
DEBUG_ASSERT(isValid());
return BeatsPerBar(m_value + increment);
}

private:
value_t m_value;
};

QDebug operator<<(QDebug dbg, BeatsPerBar arg);

} // namespace audio

} // namespace mixxx
Expand All @@ -262,3 +306,6 @@ Q_DECLARE_METATYPE(mixxx::audio::SampleRate)

Q_DECLARE_TYPEINFO(mixxx::audio::Bitrate, Q_PRIMITIVE_TYPE);
Q_DECLARE_METATYPE(mixxx::audio::Bitrate)

Q_DECLARE_TYPEINFO(mixxx::audio::BeatsPerBar, Q_PRIMITIVE_TYPE);
Q_DECLARE_METATYPE(mixxx::audio::BeatsPerBar)
220 changes: 199 additions & 21 deletions src/engine/controls/bpmcontrol.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "engine/controls/bpmcontrol.h"

#include <chrono>

#include "control/controlencoder.h"
#include "control/controllinpotmeter.h"
#include "control/controlproxy.h"
Expand All @@ -13,6 +15,8 @@
#include "util/logger.h"
#include "util/math.h"

using namespace std::literals;

namespace {
const mixxx::Logger kLogger("BpmControl");

Expand All @@ -22,8 +26,8 @@ constexpr double kBpmRangeMax = 200.0;
constexpr double kBpmRangeStep = 1.0;
constexpr double kBpmRangeSmallStep = 0.1;

constexpr double kBpmAdjustMin = kBpmRangeMin;
constexpr double kBpmAdjustStep = 0.01;
constexpr std::chrono::milliseconds kBpmAdjustRepeatInterval = 50ms;
constexpr std::chrono::milliseconds kBpmAdjustTimeBeforeRepeat = 500ms;
constexpr double kBpmTapRounding = 1 / 12.0;

// Maximum allowed interval between beats (calculated from kBpmTapMin).
Expand Down Expand Up @@ -86,6 +90,17 @@ BpmControl::BpmControl(const QString& group,
this,
&BpmControl::slotAdjustBeatsFaster,
Qt::DirectConnection);
m_pAdjustBeatsMuchFaster = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_adjust_much_faster"), false);
m_pAdjustBeatsMuchFaster->setKbdRepeatable(true);
connect(
m_pAdjustBeatsMuchFaster.get(),
&ControlObject::valueChanged,
this,
[this](double v) {
slotAdjustBeatsFaster(v * 10);
},
Qt::DirectConnection);
m_pAdjustBeatsSlower = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_adjust_slower"), false);
m_pAdjustBeatsSlower->setKbdRepeatable(true);
Expand All @@ -94,6 +109,17 @@ BpmControl::BpmControl(const QString& group,
this,
&BpmControl::slotAdjustBeatsSlower,
Qt::DirectConnection);
m_pAdjustBeatsMuchSlower = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_adjust_much_slower"), false);
m_pAdjustBeatsMuchSlower->setKbdRepeatable(true);
connect(
m_pAdjustBeatsMuchSlower.get(),
&ControlObject::valueChanged,
this,
[this](double v) {
slotAdjustBeatsSlower(v * 10);
},
Qt::DirectConnection);
m_pTranslateBeatsEarlier = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_translate_earlier"), false);
m_pTranslateBeatsEarlier->setKbdRepeatable(true);
Expand All @@ -117,6 +143,34 @@ BpmControl::BpmControl(const QString& group,
this,
&BpmControl::slotTranslateBeatsMove,
Qt::DirectConnection);
m_pBeatsSetMarker = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_set_change_marker"), false);
connect(m_pBeatsSetMarker.get(),
&ControlObject::valueChanged,
this,
&BpmControl::slotBeatsSetMarker,
Qt::DirectConnection);
m_pBeatsRemoveMarker = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_remove_marker"), false);
connect(m_pBeatsRemoveMarker.get(),
&ControlObject::valueChanged,
this,
&BpmControl::slotBeatsRemoveMarker,
Qt::DirectConnection);
m_pBeatsBarCountUp = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_increase_bar_length"), false);
connect(m_pBeatsBarCountUp.get(),
&ControlObject::valueChanged,
this,
&BpmControl::slotBeatsBarCountUp,
Qt::DirectConnection);
m_pBeatsBarCountDown = std::make_unique<ControlPushButton>(
ConfigKey(group, "beats_decrease_bar_length"), false);
connect(m_pBeatsBarCountDown.get(),
&ControlObject::valueChanged,
this,
&BpmControl::slotBeatsBarCountDown,
Qt::DirectConnection);

m_pBeatsHalve = std::make_unique<ControlPushButton>(ConfigKey(group, "beats_set_halve"), false);
connect(m_pBeatsHalve.get(),
Expand Down Expand Up @@ -264,7 +318,33 @@ mixxx::Bpm BpmControl::getBpm() const {
return mixxx::Bpm(m_pEngineBpm->get());
}

void BpmControl::adjustBeatsBpm(double deltaBpm) {
void BpmControl::clearActionRepeater() {
m_repeatOperation.stop();
m_repeatOperation.disconnect();
return;
}

void BpmControl::activateActionRepeater(const std::function<void()>& callback) {
VERIFY_OR_DEBUG_ASSERT(callback) {
clearActionRepeater();
return;
}

if (m_repeatOperation.isActive()) {
m_repeatOperation.setInterval(kBpmAdjustRepeatInterval);
} else {
m_repeatOperation.disconnect();
connect(&m_repeatOperation, &QTimer::timeout, this, callback);
m_repeatOperation.start(kBpmAdjustTimeBeforeRepeat);
}
}

void BpmControl::slotAdjustBeatsFaster(double v) {
if (v <= 0) {
clearActionRepeater();
return;
}

const TrackPointer pTrack = getEngineBuffer()->getLoadedTrack();
if (!pTrack) {
return;
Expand All @@ -274,44 +354,68 @@ void BpmControl::adjustBeatsBpm(double deltaBpm) {
return;
}

const mixxx::Bpm bpm = pBeats->getBpmInRange(
mixxx::audio::kStartFramePos, frameInfo().trackEndPosition);
// FIXME: calling bpm.value() without checking bpm.isValid()
const auto centerBpm = mixxx::Bpm(math_max(kBpmAdjustMin, bpm.value() + deltaBpm));
mixxx::Bpm adjustedBpm = BeatUtils::roundBpmWithinRange(
centerBpm - kBpmAdjustStep / 2, centerBpm, centerBpm + kBpmAdjustStep / 2);
const auto newBeats = pBeats->trySetBpm(adjustedBpm);
if (!newBeats) {
return;
activateActionRepeater([this, v]() {
slotAdjustBeatsFaster(v);
});

const auto adjustedBeats =
pBeats->tryAdjustTempo(frameInfo().currentPosition,
v > 1 ? mixxx::Beats::TempoAdjustment::MuchFaster
: mixxx::Beats::TempoAdjustment::Faster);
if (adjustedBeats) {
pTrack->trySetBeats(*adjustedBeats);
}
pTrack->trySetBeats(*newBeats);
}

void BpmControl::slotAdjustBeatsFaster(double v) {
void BpmControl::slotAdjustBeatsSlower(double v) {
if (v <= 0) {
clearActionRepeater();
return;
}
adjustBeatsBpm(kBpmAdjustStep);
}

void BpmControl::slotAdjustBeatsSlower(double v) {
if (v <= 0) {
const TrackPointer pTrack = getEngineBuffer()->getLoadedTrack();
if (!pTrack) {
return;
}
adjustBeatsBpm(-kBpmAdjustStep);
const mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (!pBeats) {
return;
}

activateActionRepeater([this, v]() {
slotAdjustBeatsSlower(v);
});

const auto adjustedBeats =
pBeats->tryAdjustTempo(frameInfo().currentPosition,
v > 1 ? mixxx::Beats::TempoAdjustment::MuchSlower
: mixxx::Beats::TempoAdjustment::Slower);
if (adjustedBeats) {
pTrack->trySetBeats(*adjustedBeats);
}
}

void BpmControl::slotTranslateBeatsEarlier(double v) {
if (v <= 0) {
clearActionRepeater();
return;
}

activateActionRepeater([this]() {
slotTranslateBeatsEarlier(1);
});
slotTranslateBeatsMove(-1);
}

void BpmControl::slotTranslateBeatsLater(double v) {
if (v <= 0) {
clearActionRepeater();
return;
}

activateActionRepeater([this]() {
slotTranslateBeatsLater(1);
});
slotTranslateBeatsMove(1);
}

Expand All @@ -330,7 +434,7 @@ void BpmControl::slotTranslateBeatsMove(double v) {
const double sampleOffset = frameInfo().sampleRate * v * 0.01;
const mixxx::audio::FrameDiff_t frameOffset =
sampleOffset / mixxx::kEngineChannelOutputCount;
const auto translatedBeats = pBeats->tryTranslate(frameOffset);
const auto translatedBeats = pBeats->tryTranslate(frameOffset, frameInfo().currentPosition);
if (translatedBeats) {
pTrack->trySetBeats(*translatedBeats);
}
Expand All @@ -349,6 +453,80 @@ void BpmControl::slotBeatsUndoAdjustment(double v) {
m_pBeatsUndoPossible->forceSet(pTrack->canUndoBeatsChange());
}

void BpmControl::slotBeatsSetMarker(double v) {
if (v <= 0) {
return;
}
const TrackPointer pTrack = getEngineBuffer()->getLoadedTrack();
if (!pTrack) {
return;
}
const mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (!pBeats) {
return;
}

const auto modifiedBeats = pBeats->trySetMarker(frameInfo().currentPosition);
if (modifiedBeats) {
pTrack->trySetBeats(*modifiedBeats);
}
}

void BpmControl::slotBeatsBarCountUp(double v) {
if (v <= 0) {
return;
}
const TrackPointer pTrack = getEngineBuffer()->getLoadedTrack();
if (!pTrack) {
return;
}
const mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (!pBeats) {
return;
}

const auto modifiedBeats = pBeats->tryAdjustMarkerBarCount(frameInfo().currentPosition, 1);
if (modifiedBeats) {
pTrack->trySetBeats(*modifiedBeats);
}
}
void BpmControl::slotBeatsBarCountDown(double v) {
if (v <= 0) {
return;
}
const TrackPointer pTrack = getEngineBuffer()->getLoadedTrack();
if (!pTrack) {
return;
}
const mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (!pBeats) {
return;
}

const auto modifiedBeats = pBeats->tryAdjustMarkerBarCount(frameInfo().currentPosition, -1);
if (modifiedBeats) {
pTrack->trySetBeats(*modifiedBeats);
}
}
void BpmControl::slotBeatsRemoveMarker(double v) {
if (v <= 0) {
return;
}
const TrackPointer pTrack = getEngineBuffer()->getLoadedTrack();
if (!pTrack) {
return;
}
const mixxx::BeatsPointer pBeats = pTrack->getBeats();
if (!pBeats) {
return;
}

const auto modifiedBeats = pBeats->tryRemoveMarker(frameInfo().currentPosition);
if (modifiedBeats) {
pTrack->trySetBeats(*modifiedBeats);
}
}

void BpmControl::slotBpmTap(double v) {
if (v > 0) {
m_bpmTapFilter.tap();
Expand Down Expand Up @@ -1169,7 +1347,7 @@ void BpmControl::slotBeatsTranslate(double v) {
const auto currentPosition = frameInfo().currentPosition.toLowerFrameBoundary();
const auto closestBeat = pBeats->findClosestBeat(currentPosition);
const mixxx::audio::FrameDiff_t frameOffset = currentPosition - closestBeat;
const auto translatedBeats = pBeats->tryTranslate(frameOffset);
const auto translatedBeats = pBeats->tryTranslate(frameOffset, currentPosition);
if (translatedBeats) {
pTrack->trySetBeats(*translatedBeats);
}
Expand Down
Loading

0 comments on commit 9de4d5c

Please sign in to comment.