2
// Created 7/5/2009 by RJ Ryan (rryan@mit.edu)
6
#include "controlobject.h"
7
#include "controlpushbutton.h"
9
#include "engine/enginebuffer.h"
10
#include "engine/enginestate.h"
11
#include "engine/bpmcontrol.h"
13
const int minBpm = 30;
14
const int maxInterval = (int)(1000.*(60./(CSAMPLE)minBpm));
15
const int filterLength = 5;
17
BpmControl::BpmControl(const char* _group,
18
ConfigObject<ConfigValue>* _config,
19
EngineState* pEngineState) :
20
EngineControl(_group, _config),
21
m_tapFilter(this, filterLength, maxInterval) {
22
m_pTrackWatcher = pEngineState->getTrackManager()->createTrackWatcher();
23
connect(m_pTrackWatcher, SIGNAL(beatsUpdated()),
24
this, SLOT(slotUpdatedTrackBeats()),
25
Qt::DirectConnection);
27
CallbackControlManager* pControlManager = pEngineState->getControlManager();
28
m_pPlayButton = pControlManager->getControl(ConfigKey(_group, "play"));
29
m_pRateSlider = pControlManager->getControl(ConfigKey(_group, "rate"));
30
connect(m_pRateSlider, SIGNAL(valueChanged(double)),
31
this, SLOT(slotRateChanged(double)),
32
Qt::DirectConnection);
33
connect(m_pRateSlider, SIGNAL(valueChangedFromEngine(double)),
34
this, SLOT(slotRateChanged(double)),
35
Qt::DirectConnection);
37
m_pRateRange = pControlManager->getControl(ConfigKey(_group, "rateRange"));
38
connect(m_pRateRange, SIGNAL(valueChanged(double)),
39
this, SLOT(slotRateChanged(double)),
40
Qt::DirectConnection);
41
connect(m_pRateRange, SIGNAL(valueChangedFromEngine(double)),
42
this, SLOT(slotRateChanged(double)),
43
Qt::DirectConnection);
45
m_pRateDir = pControlManager->getControl(ConfigKey(_group, "rate_dir"));
46
connect(m_pRateDir, SIGNAL(valueChanged(double)),
47
this, SLOT(slotRateChanged(double)),
48
Qt::DirectConnection);
49
connect(m_pRateDir, SIGNAL(valueChangedFromEngine(double)),
50
this, SLOT(slotRateChanged(double)),
51
Qt::DirectConnection);
53
m_pFileBpm = pControlManager->addControl(
54
new ControlObject(ConfigKey(_group, "file_bpm")), 1);
55
connect(m_pFileBpm, SIGNAL(valueChanged(double)),
56
this, SLOT(slotFileBpmChanged(double)),
57
Qt::DirectConnection);
59
m_pEngineBpm = pControlManager->addControl(
60
new ControlObject(ConfigKey(_group, "bpm")), 1);
61
connect(m_pEngineBpm, SIGNAL(valueChanged(double)),
62
this, SLOT(slotSetEngineBpm(double)),
63
Qt::DirectConnection);
65
m_pButtonTap = pControlManager->addControl(
66
new ControlPushButton(ConfigKey(_group, "bpm_tap")), 1);
67
connect(m_pButtonTap, SIGNAL(valueChanged(double)),
68
this, SLOT(slotBpmTap(double)),
69
Qt::DirectConnection);
71
// Beat sync (scale buffer tempo relative to tempo of other buffer)
72
m_pButtonSync = pControlManager->addControl(
73
new ControlPushButton(ConfigKey(_group, "beatsync")), 1);
74
connect(m_pButtonSync, SIGNAL(valueChanged(double)),
75
this, SLOT(slotControlBeatSync(double)),
76
Qt::DirectConnection);
78
m_pButtonSyncPhase = pControlManager->addControl(
79
new ControlPushButton(ConfigKey(_group, "beatsync_phase")), 1);
80
connect(m_pButtonSyncPhase, SIGNAL(valueChanged(double)),
81
this, SLOT(slotControlBeatSyncPhase(double)),
82
Qt::DirectConnection);
84
m_pButtonSyncTempo = pControlManager->addControl(
85
new ControlPushButton(ConfigKey(_group, "beatsync_tempo")), 1);
86
connect(m_pButtonSyncTempo, SIGNAL(valueChanged(double)),
87
this, SLOT(slotControlBeatSyncTempo(double)),
88
Qt::DirectConnection);
90
m_pTranslateBeats = pControlManager->addControl(
91
new ControlPushButton(ConfigKey(_group, "beats_translate_curpos")), 1);
92
connect(m_pTranslateBeats, SIGNAL(valueChanged(double)),
93
this, SLOT(slotBeatsTranslate(double)),
94
Qt::DirectConnection);
96
connect(&m_tapFilter, SIGNAL(tapped(double,int)),
97
this, SLOT(slotTapFilter(double,int)),
98
Qt::DirectConnection);
101
BpmControl::~BpmControl() {
104
delete m_pButtonSync;
105
delete m_pButtonSyncTempo;
106
delete m_pButtonSyncPhase;
108
delete m_pTranslateBeats;
109
delete m_pTrackWatcher;
112
double BpmControl::getBpm() {
113
return m_pEngineBpm->get();
116
void BpmControl::slotFileBpmChanged(double bpm) {
117
//qDebug() << this << "slotFileBpmChanged" << bpm;
118
// Adjust the file-bpm with the current setting of the rate to get the
120
double dRate = 1.0 + m_pRateDir->get() * m_pRateRange->get() * m_pRateSlider->get();
121
m_pEngineBpm->set(bpm * dRate);
124
void BpmControl::slotSetEngineBpm(double bpm) {
125
double filebpm = m_pFileBpm->get();
127
if (filebpm != 0.0) {
128
double newRate = bpm / filebpm - 1.0f;
129
newRate = math_max(-1.0f, math_min(1.0f, newRate));
130
m_pRateSlider->set(newRate * m_pRateDir->get());
134
void BpmControl::slotBpmTap(double v) {
140
void BpmControl::slotTapFilter(double averageLength, int numSamples) {
141
// averageLength is the average interval in milliseconds tapped over
142
// numSamples samples. Have to convert to BPM now:
144
if (averageLength <= 0)
150
// (60 seconds per minute) * (1000 milliseconds per second) / (X millis per
151
// beat) = Y beats/minute
152
double averageBpm = 60.0 * 1000.0 / averageLength;
153
m_pFileBpm->set(averageBpm);
154
slotFileBpmChanged(averageBpm);
157
void BpmControl::slotControlBeatSyncPhase(double v) {
163
void BpmControl::slotControlBeatSyncTempo(double v) {
169
void BpmControl::slotControlBeatSync(double v) {
173
// If the player is playing, and adjusting its tempo succeeded, adjust its
174
// phase so that it plays in sync.
175
if (syncTempo() && m_pPlayButton->get() > 0) {
180
bool BpmControl::syncTempo() {
181
EngineBuffer* pOtherEngineBuffer = getOtherEngineBuffer();
183
if(!pOtherEngineBuffer)
186
double fThisBpm = m_pEngineBpm->get();
187
//double fThisRate = m_pRateDir->get() * m_pRateSlider->get() * m_pRateRange->get();
188
double fThisFileBpm = m_pFileBpm->get();
190
double fOtherBpm = pOtherEngineBuffer->getBpm();
191
double fOtherRate = pOtherEngineBuffer->getRate();
192
double fOtherFileBpm = fOtherBpm / (1.0 + fOtherRate);
194
//qDebug() << "this" << "bpm" << fThisBpm << "filebpm" << fThisFileBpm << "rate" << fThisRate;
195
//qDebug() << "other" << "bpm" << fOtherBpm << "filebpm" << fOtherFileBpm << "rate" << fOtherRate;
197
////////////////////////////////////////////////////////////////////////////
198
// Rough proof of how syncing works -- rryan 3/2011
199
// ------------------------------------------------
201
// Let this and other denote this deck versus the sync-target deck.
203
// The goal is for this deck's effective BPM to equal the other decks.
205
// thisBpm = otherBpm
207
// The overall rate is the product of range, direction, and scale plus 1:
209
// rate = 1.0 + rateDir * rateRange * rateScale
211
// An effective BPM is the file-bpm times the rate:
213
// bpm = fileBpm * rate
215
// So our goal is to tweak thisRate such that this equation is true:
217
// thisFileBpm * (1.0 + thisRate) = otherFileBpm * (1.0 + otherRate)
219
// so rearrange this equation in terms of thisRate:
221
// thisRate = (otherFileBpm * (1.0 + otherRate)) / thisFileBpm - 1.0
223
// So the new rateScale to set is:
225
// thisRateScale = ((otherFileBpm * (1.0 + otherRate)) / thisFileBpm - 1.0) / (thisRateDir * thisRateRange)
227
if (fOtherBpm > 0.0 && fThisBpm > 0.0) {
228
// The desired rate is the other decks effective rate divided by this
229
// deck's file BPM. This gives us the playback rate that will produe an
230
// effective BPM equivalent to the other decks.
231
double fDesiredRate = fOtherBpm / fThisFileBpm;
233
// Test if this buffer's bpm is the double of the other one, and adjust
234
// the rate scale. I believe this is intended to account for our BPM
235
// algorithm sometimes finding double or half BPMs. This avoids drastic
238
float fFileBpmDelta = fabs(fThisFileBpm-fOtherFileBpm);
239
if (fabs(fThisFileBpm*2.0 - fOtherFileBpm) < fFileBpmDelta) {
241
} else if (fabs(fThisFileBpm - 2.0*fOtherFileBpm) < fFileBpmDelta) {
245
// Subtract the base 1.0, now fDesiredRate is the percentage
246
// increase/decrease in playback rate, not the playback rate.
249
// Ensure the rate is within resonable boundaries. Remember, this is the
250
// percent to scale the rate, not the rate itself. If fDesiredRate was -1,
251
// that would mean the deck would be completely stopped. If fDesiredRate
252
// is 1, that means it is playing at 2x speed. This limit enforces that
253
// we are scaled between 0.5x and 2x.
254
if (fDesiredRate < 1.0 && fDesiredRate > -0.5)
256
// Adjust the rateScale. We have to divide by the range and
257
// direction to get the correct rateScale.
258
fDesiredRate = fDesiredRate/(m_pRateRange->get() * m_pRateDir->get());
260
// And finally, set the slider
261
m_pRateSlider->set(fDesiredRate);
268
bool BpmControl::syncPhase() {
269
EngineBuffer* pOtherEngineBuffer = getOtherEngineBuffer();
270
TrackPointer otherTrack = pOtherEngineBuffer->getLoadedTrack();
271
BeatsPointer otherBeats = otherTrack ? otherTrack->getBeats() : BeatsPointer();
273
// If either track does not have beats, then we can't adjust the phase.
274
if (!m_pBeats || !otherBeats) {
278
// Get the file BPM of each song.
279
//double dThisBpm = m_pBeats->getBpm();
280
//double dOtherBpm = ControlObject::getControl(
281
//ConfigKey(pOtherEngineBuffer->getGroup(), "file_bpm"))->get();
283
// Get the current position of both decks
284
double dThisPosition = getCurrentSample();
285
double dOtherLength = ControlObject::getControl(
286
ConfigKey(pOtherEngineBuffer->getGroup(), "track_samples"))->get();
287
double dOtherPosition = dOtherLength * ControlObject::getControl(
288
ConfigKey(pOtherEngineBuffer->getGroup(), "visual_playposition"))->get();
290
double dThisPrevBeat = m_pBeats->findPrevBeat(dThisPosition);
291
double dThisNextBeat = m_pBeats->findNextBeat(dThisPosition);
293
if (dThisPrevBeat == -1 || dThisNextBeat == -1) {
297
// Protect against the case where we are sitting exactly on the beat.
298
if (dThisPrevBeat == dThisNextBeat) {
299
dThisNextBeat = m_pBeats->findNthBeat(dThisPosition, 2);
302
double dOtherPrevBeat = otherBeats->findPrevBeat(dOtherPosition);
303
double dOtherNextBeat = otherBeats->findNextBeat(dOtherPosition);
305
if (dOtherPrevBeat == -1 || dOtherNextBeat == -1) {
309
// Protect against the case where we are sitting exactly on the beat.
310
if (dOtherPrevBeat == dOtherNextBeat) {
311
dOtherNextBeat = otherBeats->findNthBeat(dOtherPosition, 2);
314
double dThisBeatLength = fabs(dThisNextBeat - dThisPrevBeat);
315
double dOtherBeatLength = fabs(dOtherNextBeat - dOtherPrevBeat);
316
double dOtherBeatFraction = (dOtherPosition - dOtherPrevBeat) / dOtherBeatLength;
319
bool this_near_next = dThisNextBeat - dThisPosition <= dThisPosition - dThisPrevBeat;
320
bool other_near_next = dOtherNextBeat - dOtherPosition <= dOtherPosition - dOtherPrevBeat;
322
// We want our beat fraction to be identical to theirs.
324
// If the two tracks have similar alignment, adjust phase is straight-
325
// forward. Use the same fraction for both beats, starting from the previous
326
// beat. But if This track is nearer to the next beat and the Other track
327
// is nearer to the previous beat, use This Next beat as the starting point
328
// for the phase. (ie, we pushed the sync button late). If This track
329
// is nearer to the previous beat, but the Other track is nearer to the
330
// next beat, we pushed the sync button early so use the double-previous
331
// beat as the basis for the adjustment.
333
// This makes way more sense when you're actually mixing.
335
// TODO(XXX) Revisit this logic once we move away from tempo-locked,
336
// infinite beatgrids because the assumption that findNthBeat(-2) always
337
// works will be wrong then.
339
if (this_near_next == other_near_next) {
340
dNewPlaypos = dThisPrevBeat + dOtherBeatFraction * dThisBeatLength;
341
} else if (this_near_next && !other_near_next) {
342
dNewPlaypos = dThisNextBeat + dOtherBeatFraction * dThisBeatLength;
343
} else { //!this_near_next && other_near_next
344
dThisPrevBeat = m_pBeats->findNthBeat(dThisPosition, -2);
345
dNewPlaypos = dThisPrevBeat + dOtherBeatFraction * dThisBeatLength;
348
emit(seekAbs(dNewPlaypos));
352
void BpmControl::slotRateChanged(double) {
353
double dFileBpm = m_pFileBpm->get();
354
slotFileBpmChanged(dFileBpm);
357
void BpmControl::trackLoaded(TrackPointer pTrack) {
359
trackUnloaded(m_pTrack);
364
m_pBeats = m_pTrack->getBeats();
365
m_pTrackWatcher->watchTrack(m_pTrack);
369
void BpmControl::trackUnloaded(TrackPointer pTrack) {
371
m_pTrackWatcher->unwatchTrack(m_pTrack);
377
void BpmControl::slotUpdatedTrackBeats() {
378
//qDebug() << "BpmControl::slotUpdatedTrackBeats()";
380
m_pBeats = m_pTrack->getBeats();
384
void BpmControl::slotBeatsTranslate(double v) {
385
if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) {
386
double currentSample = getCurrentSample();
387
double closestBeat = m_pBeats->findClosestBeat(currentSample);
388
int delta = currentSample - closestBeat;
389
if (delta % 2 != 0) {
392
m_pBeats->translate(delta);