#include "engine/controls/bpmcontrol.h"
#include <QStringList>
#include "control/controllinpotmeter.h"
#include "control/controlobject.h"
#include "control/controlproxy.h"
#include "control/controlpushbutton.h"
#include "engine/channels/enginechannel.h"
#include "engine/enginebuffer.h"
#include "engine/enginemaster.h"
#include "moc_bpmcontrol.cpp"
#include "track/track.h"
#include "util/assert.h"
#include "util/duration.h"
#include "util/logger.h"
#include "util/math.h"
#include "waveform/visualplayposition.h"
namespace {
const mixxx::Logger kLogger("BpmControl");
constexpr double kBpmRangeMin = 1.0;
// TODO(XXX): Change to mixxx::Bpm::kValueMax? This would affect mappings!
constexpr double kBpmRangeMax = 200.0;
constexpr double kBpmRangeStep = 1.0;
constexpr double kBpmRangeSmallStep = 0.1;
constexpr double kBpmAdjustMin = kBpmRangeMin;
constexpr double kBpmAdjustStep = 0.01;
// Maximum allowed interval between beats (calculated from kBpmTapMin).
constexpr double kBpmTapMin = 30.0;
const mixxx::Duration kBpmTapMaxInterval = mixxx::Duration::fromMillis(
static_cast<qint64>(1000.0 * (60.0 / kBpmTapMin)));
constexpr int kBpmTapFilterLength = 5;
// The local_bpm is calculated forward and backward this number of beats, so
// the actual number of beats is this x2.
constexpr int kLocalBpmSpan = 4;
constexpr SINT kSamplesPerFrame = 2;
}
BpmControl::BpmControl(const QString& group,
UserSettingsPointer pConfig)
: EngineControl(group, pConfig),
m_tapFilter(this, kBpmTapFilterLength, kBpmTapMaxInterval),
m_dSyncInstantaneousBpm(0.0),
m_dLastSyncAdjustment(1.0),
m_dUserTweakingSync(false) {
m_dSyncTargetBeatDistance.setValue(0.0);
m_dUserOffset.setValue(0.0);
m_pPlayButton = new ControlProxy(group, "play", this);
m_pReverseButton = new ControlProxy(group, "reverse", this);
m_pRateRatio = new ControlProxy(group, "rate_ratio", this);
m_pRateRatio->connectValueChanged(this, &BpmControl::slotUpdateEngineBpm,
Qt::DirectConnection);
m_pQuantize = ControlObject::getControl(group, "quantize");
m_pPrevBeat.reset(new ControlProxy(group, "beat_prev"));
m_pNextBeat.reset(new ControlProxy(group, "beat_next"));
m_pLoopEnabled = new ControlProxy(group, "loop_enabled", this);
m_pLoopStartPosition = new ControlProxy(group, "loop_start_position", this);
m_pLoopEndPosition = new ControlProxy(group, "loop_end_position", this);
m_pLocalBpm = new ControlObject(ConfigKey(group, "local_bpm"));
m_pAdjustBeatsFaster = new ControlPushButton(ConfigKey(group, "beats_adjust_faster"), false);
connect(m_pAdjustBeatsFaster, &ControlObject::valueChanged,
this, &BpmControl::slotAdjustBeatsFaster,
Qt::DirectConnection);
m_pAdjustBeatsSlower = new ControlPushButton(ConfigKey(group, "beats_adjust_slower"), false);
connect(m_pAdjustBeatsSlower, &ControlObject::valueChanged,
this, &BpmControl::slotAdjustBeatsSlower,
Qt::DirectConnection);
m_pTranslateBeatsEarlier = new ControlPushButton(ConfigKey(group, "beats_translate_earlier"), false);
connect(m_pTranslateBeatsEarlier, &ControlObject::valueChanged,
this, &BpmControl::slotTranslateBeatsEarlier,
Qt::DirectConnection);
m_pTranslateBeatsLater = new ControlPushButton(ConfigKey(group, "beats_translate_later"), false);
connect(m_pTranslateBeatsLater, &ControlObject::valueChanged,
this, &BpmControl::slotTranslateBeatsLater,
Qt::DirectConnection);
// Pick a wide range (kBpmRangeMin to kBpmRangeMax) and allow out of bounds sets. This lets you
// map a soft-takeover MIDI knob to the BPM. This also creates bpm_up and
// bpm_down controls.
// bpm_up / bpm_down steps by kBpmRangeStep
// bpm_up_small / bpm_do