Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/engine/bpmcontrol.cpp

Go to the documentation of this file.
00001 // bpmcontrol.cpp
00002 // Created 7/5/2009 by RJ Ryan (rryan@mit.edu)
00003 
00004 #include "controlobject.h"
00005 #include "controlpushbutton.h"
00006 
00007 #include "engine/enginebuffer.h"
00008 #include "engine/bpmcontrol.h"
00009 
00010 const int minBpm = 30;
00011 const int maxInterval = (int)(1000.*(60./(CSAMPLE)minBpm));
00012 const int filterLength = 5;
00013 
00014 BpmControl::BpmControl(const char* _group,
00015                        ConfigObject<ConfigValue>* _config) :
00016         EngineControl(_group, _config),
00017         m_tapFilter(this, filterLength, maxInterval) {
00018     m_pPlayButton = ControlObject::getControl(ConfigKey(_group, "play"));
00019     m_pRateSlider = ControlObject::getControl(ConfigKey(_group, "rate"));
00020     connect(m_pRateSlider, SIGNAL(valueChanged(double)),
00021             this, SLOT(slotRateChanged(double)),
00022             Qt::DirectConnection);
00023     connect(m_pRateSlider, SIGNAL(valueChangedFromEngine(double)),
00024             this, SLOT(slotRateChanged(double)),
00025             Qt::DirectConnection);
00026 
00027     m_pRateRange = ControlObject::getControl(ConfigKey(_group, "rateRange"));
00028     connect(m_pRateRange, SIGNAL(valueChanged(double)),
00029             this, SLOT(slotRateChanged(double)),
00030             Qt::DirectConnection);
00031     connect(m_pRateRange, SIGNAL(valueChangedFromEngine(double)),
00032             this, SLOT(slotRateChanged(double)),
00033             Qt::DirectConnection);
00034 
00035     m_pRateDir = ControlObject::getControl(ConfigKey(_group, "rate_dir"));
00036     connect(m_pRateDir, SIGNAL(valueChanged(double)),
00037             this, SLOT(slotRateChanged(double)),
00038             Qt::DirectConnection);
00039     connect(m_pRateDir, SIGNAL(valueChangedFromEngine(double)),
00040             this, SLOT(slotRateChanged(double)),
00041             Qt::DirectConnection);
00042 
00043     m_pFileBpm = new ControlObject(ConfigKey(_group, "file_bpm"));
00044     connect(m_pFileBpm, SIGNAL(valueChanged(double)),
00045             this, SLOT(slotFileBpmChanged(double)),
00046             Qt::DirectConnection);
00047 
00048     m_pEngineBpm = new ControlObject(ConfigKey(_group, "bpm"));
00049     connect(m_pEngineBpm, SIGNAL(valueChanged(double)),
00050             this, SLOT(slotSetEngineBpm(double)),
00051             Qt::DirectConnection);
00052 
00053     m_pButtonTap = new ControlPushButton(ConfigKey(_group, "bpm_tap"));
00054     connect(m_pButtonTap, SIGNAL(valueChanged(double)),
00055             this, SLOT(slotBpmTap(double)),
00056             Qt::DirectConnection);
00057 
00058     // Beat sync (scale buffer tempo relative to tempo of other buffer)
00059     m_pButtonSync = new ControlPushButton(ConfigKey(_group, "beatsync"));
00060     connect(m_pButtonSync, SIGNAL(valueChanged(double)),
00061             this, SLOT(slotControlBeatSync(double)),
00062             Qt::DirectConnection);
00063 
00064     m_pButtonSyncPhase = new ControlPushButton(ConfigKey(_group, "beatsync_phase"));
00065     connect(m_pButtonSyncPhase, SIGNAL(valueChanged(double)),
00066             this, SLOT(slotControlBeatSyncPhase(double)),
00067             Qt::DirectConnection);
00068 
00069     m_pButtonSyncTempo = new ControlPushButton(ConfigKey(_group, "beatsync_tempo"));
00070     connect(m_pButtonSyncTempo, SIGNAL(valueChanged(double)),
00071             this, SLOT(slotControlBeatSyncTempo(double)),
00072             Qt::DirectConnection);
00073 
00074     m_pTranslateBeats = new ControlPushButton(
00075         ConfigKey(_group, "beats_translate_curpos"));
00076     connect(m_pTranslateBeats, SIGNAL(valueChanged(double)),
00077             this, SLOT(slotBeatsTranslate(double)),
00078             Qt::DirectConnection);
00079 
00080     connect(&m_tapFilter, SIGNAL(tapped(double,int)),
00081             this, SLOT(slotTapFilter(double,int)),
00082             Qt::DirectConnection);
00083 }
00084 
00085 BpmControl::~BpmControl() {
00086     delete m_pEngineBpm;
00087     delete m_pFileBpm;
00088     delete m_pButtonSync;
00089     delete m_pButtonSyncTempo;
00090     delete m_pButtonSyncPhase;
00091     delete m_pButtonTap;
00092     delete m_pTranslateBeats;
00093 }
00094 
00095 double BpmControl::getBpm() {
00096     return m_pEngineBpm->get();
00097 }
00098 
00099 void BpmControl::slotFileBpmChanged(double bpm) {
00100     //qDebug() << this << "slotFileBpmChanged" << bpm;
00101     // Adjust the file-bpm with the current setting of the rate to get the
00102     // engine BPM.
00103     double dRate = 1.0 + m_pRateDir->get() * m_pRateRange->get() * m_pRateSlider->get();
00104     m_pEngineBpm->set(bpm * dRate);
00105 }
00106 
00107 void BpmControl::slotSetEngineBpm(double bpm) {
00108     double filebpm = m_pFileBpm->get();
00109 
00110     if (filebpm != 0.0) {
00111         double newRate = bpm / filebpm - 1.0f;
00112         newRate = math_max(-1.0f, math_min(1.0f, newRate));
00113         m_pRateSlider->set(newRate * m_pRateDir->get());
00114     }
00115 }
00116 
00117 void BpmControl::slotBpmTap(double v) {
00118     if (v > 0) {
00119         m_tapFilter.tap();
00120     }
00121 }
00122 
00123 void BpmControl::slotTapFilter(double averageLength, int numSamples) {
00124     // averageLength is the average interval in milliseconds tapped over
00125     // numSamples samples.  Have to convert to BPM now:
00126 
00127     if (averageLength <= 0)
00128         return;
00129 
00130     if (numSamples < 4)
00131         return;
00132 
00133     // (60 seconds per minute) * (1000 milliseconds per second) / (X millis per
00134     // beat) = Y beats/minute
00135     double averageBpm = 60.0 * 1000.0 / averageLength;
00136     m_pFileBpm->set(averageBpm);
00137     slotFileBpmChanged(averageBpm);
00138 }
00139 
00140 void BpmControl::slotControlBeatSyncPhase(double v) {
00141     if (!v)
00142         return;
00143     syncPhase();
00144 }
00145 
00146 void BpmControl::slotControlBeatSyncTempo(double v) {
00147     if (!v)
00148         return;
00149     syncTempo();
00150 }
00151 
00152 void BpmControl::slotControlBeatSync(double v) {
00153     if (!v)
00154         return;
00155 
00156     // If the player is playing, and adjusting its tempo succeeded, adjust its
00157     // phase so that it plays in sync.
00158     if (syncTempo() && m_pPlayButton->get() > 0) {
00159         syncPhase();
00160     }
00161 }
00162 
00163 bool BpmControl::syncTempo() {
00164     EngineBuffer* pOtherEngineBuffer = getOtherEngineBuffer();
00165 
00166     if(!pOtherEngineBuffer)
00167         return false;
00168 
00169     double fThisBpm  = m_pEngineBpm->get();
00170     //double fThisRate = m_pRateDir->get() * m_pRateSlider->get() * m_pRateRange->get();
00171     double fThisFileBpm = m_pFileBpm->get();
00172 
00173     double fOtherBpm = pOtherEngineBuffer->getBpm();
00174     double fOtherRate = pOtherEngineBuffer->getRate();
00175     double fOtherFileBpm = fOtherBpm / (1.0 + fOtherRate);
00176 
00177     //qDebug() << "this" << "bpm" << fThisBpm << "filebpm" << fThisFileBpm << "rate" << fThisRate;
00178     //qDebug() << "other" << "bpm" << fOtherBpm << "filebpm" << fOtherFileBpm << "rate" << fOtherRate;
00179 
00181     // Rough proof of how syncing works -- rryan 3/2011
00182     // ------------------------------------------------
00183     //
00184     // Let this and other denote this deck versus the sync-target deck.
00185     //
00186     // The goal is for this deck's effective BPM to equal the other decks.
00187     //
00188     // thisBpm = otherBpm
00189     //
00190     // The overall rate is the product of range, direction, and scale plus 1:
00191     //
00192     // rate = 1.0 + rateDir * rateRange * rateScale
00193     //
00194     // An effective BPM is the file-bpm times the rate:
00195     //
00196     // bpm = fileBpm * rate
00197     //
00198     // So our goal is to tweak thisRate such that this equation is true:
00199     //
00200     // thisFileBpm * (1.0 + thisRate) = otherFileBpm * (1.0 + otherRate)
00201     //
00202     // so rearrange this equation in terms of thisRate:
00203     //
00204     // thisRate = (otherFileBpm * (1.0 + otherRate)) / thisFileBpm - 1.0
00205     //
00206     // So the new rateScale to set is:
00207     //
00208     // thisRateScale = ((otherFileBpm * (1.0 + otherRate)) / thisFileBpm - 1.0) / (thisRateDir * thisRateRange)
00209 
00210     if (fOtherBpm > 0.0 && fThisBpm > 0.0) {
00211         // The desired rate is the other decks effective rate divided by this
00212         // deck's file BPM. This gives us the playback rate that will produe an
00213         // effective BPM equivalent to the other decks.
00214         double fDesiredRate = fOtherBpm / fThisFileBpm;
00215 
00216         // Test if this buffers bpm is the double of the other one, and adjust
00217         // the rate scale. I believe this is intended to account for our BPM
00218         // algorithm sometimes finding double or half BPMs. This avoid drastic
00219         // scales.
00220         float fFileBpmDelta = fabs(fThisFileBpm-fOtherFileBpm);
00221         if (fabs(fThisFileBpm*2.0 - fOtherFileBpm) < fFileBpmDelta) {
00222             fDesiredRate /= 2.0;
00223         } else if (fabs(fThisFileBpm - 2.0*fOtherFileBpm) < fFileBpmDelta) {
00224             fDesiredRate *= 2.0;
00225         }
00226 
00227         // Subtract the base 1.0, now fDesiredRate is the percentage
00228         // increase/decrease in playback rate, not the playback rate.
00229         fDesiredRate -= 1.0;
00230 
00231         // Ensure the rate is within resonable boundaries. Remember, this is the
00232         // percent to scale the rate, not the rate itself. If fDesiredRate was -1,
00233         // that would mean the deck would be completely stopped. If fDesiredRate
00234         // is 1, that means it is playing at 2x speed. This limit enforces that
00235         // we are scaled between 0.5x and 2x.
00236         if (fDesiredRate < 1.0 && fDesiredRate > -0.5)
00237         {
00238             // Adjust the rateScale. We have to divide by the range and
00239             // direction to get the correct rateScale.
00240             fDesiredRate = fDesiredRate/(m_pRateRange->get() * m_pRateDir->get());
00241 
00242             // And finally, set the slider
00243             m_pRateSlider->set(fDesiredRate);
00244             return true;
00245         }
00246     }
00247     return false;
00248 }
00249 
00250 bool BpmControl::syncPhase() {
00251     EngineBuffer* pOtherEngineBuffer = getOtherEngineBuffer();
00252     TrackPointer otherTrack = pOtherEngineBuffer->getLoadedTrack();
00253     BeatsPointer otherBeats = otherTrack ? otherTrack->getBeats() : BeatsPointer();
00254 
00255     // If either track does not have beats, then we can't adjust the phase.
00256     if (!m_pBeats || !otherBeats) {
00257         return false;
00258     }
00259 
00260     // Get the file BPM of each song.
00261     //double dThisBpm = m_pBeats->getBpm();
00262     //double dOtherBpm = ControlObject::getControl(
00263     //ConfigKey(pOtherEngineBuffer->getGroup(), "file_bpm"))->get();
00264 
00265     // Get the current position of both decks
00266     double dThisPosition = getCurrentSample();
00267     double dOtherLength = ControlObject::getControl(
00268         ConfigKey(pOtherEngineBuffer->getGroup(), "track_samples"))->get();
00269     double dOtherPosition = dOtherLength * ControlObject::getControl(
00270         ConfigKey(pOtherEngineBuffer->getGroup(), "visual_playposition"))->get();
00271 
00272     double dThisPrevBeat = m_pBeats->findPrevBeat(dThisPosition);
00273     double dThisNextBeat = m_pBeats->findNextBeat(dThisPosition);
00274 
00275     if (dThisPrevBeat == -1 || dThisNextBeat == -1) {
00276         return false;
00277     }
00278 
00279     // Protect against the case where we are sitting exactly on the beat.
00280     if (dThisPrevBeat == dThisNextBeat) {
00281         dThisNextBeat = m_pBeats->findNthBeat(dThisPosition, 2);
00282     }
00283 
00284     double dOtherPrevBeat = otherBeats->findPrevBeat(dOtherPosition);
00285     double dOtherNextBeat = otherBeats->findNextBeat(dOtherPosition);
00286 
00287     if (dOtherPrevBeat == -1 || dOtherNextBeat == -1) {
00288         return false;
00289     }
00290 
00291     // Protect against the case where we are sitting exactly on the beat.
00292     if (dOtherPrevBeat == dOtherNextBeat) {
00293         dOtherNextBeat = otherBeats->findNthBeat(dOtherPosition, 2);
00294     }
00295 
00296     double dThisBeatLength = fabs(dThisNextBeat - dThisPrevBeat);
00297     double dOtherBeatLength = fabs(dOtherNextBeat - dOtherPrevBeat);
00298     double dOtherBeatFraction = (dOtherPosition - dOtherPrevBeat) / dOtherBeatLength;
00299 
00300     double dNewPlaypos;
00301     bool this_near_next = dThisNextBeat - dThisPosition <= dThisPosition - dThisPrevBeat;
00302     bool other_near_next = dOtherNextBeat - dOtherPosition <= dOtherPosition - dOtherPrevBeat;
00303 
00304     // We want our beat fraction to be identical to theirs.
00305 
00306     // If the two tracks have similar alignment, adjust phase is straight-
00307     // forward.  Use the same fraction for both beats, starting from the previous
00308     // beat.  But if This track is nearer to the next beat and the Other track
00309     // is nearer to the previous beat, use This Next beat as the starting point
00310     // for the phase. (ie, we pushed the sync button late).  If This track
00311     // is nearer to the previous beat, but the Other track is nearer to the
00312     // next beat, we pushed the sync button early so use the double-previous
00313     // beat as the basis for the adjustment.
00314     //
00315     // This makes way more sense when you're actually mixing.
00316     //
00317     // TODO(XXX) Revisit this logic once we move away from tempo-locked,
00318     // infinite beatgrids because the assumption that findNthBeat(-2) always
00319     // works will be wrong then.
00320 
00321     if (this_near_next == other_near_next) {
00322         dNewPlaypos = dThisPrevBeat + dOtherBeatFraction * dThisBeatLength;
00323     } else if (this_near_next && !other_near_next) {
00324         dNewPlaypos = dThisNextBeat + dOtherBeatFraction * dThisBeatLength;
00325     } else {  
00326         dThisPrevBeat = m_pBeats->findNthBeat(dThisPosition, -2);
00327         dNewPlaypos = dThisPrevBeat + dOtherBeatFraction * dThisBeatLength;
00328     }
00329 
00330     emit(seekAbs(dNewPlaypos));
00331     return true;
00332 }
00333 
00334 void BpmControl::slotRateChanged(double) {
00335     double dFileBpm = m_pFileBpm->get();
00336     slotFileBpmChanged(dFileBpm);
00337 }
00338 
00339 void BpmControl::trackLoaded(TrackPointer pTrack) {
00340     if (m_pTrack) {
00341         trackUnloaded(m_pTrack);
00342     }
00343 
00344     if (pTrack) {
00345         m_pTrack = pTrack;
00346         m_pBeats = m_pTrack->getBeats();
00347         connect(m_pTrack.data(), SIGNAL(beatsUpdated()),
00348                 this, SLOT(slotUpdatedTrackBeats()));
00349     }
00350 }
00351 
00352 void BpmControl::trackUnloaded(TrackPointer pTrack) {
00353     if (m_pTrack) {
00354         disconnect(m_pTrack.data(), SIGNAL(beatsUpdated()),
00355                    this, SLOT(slotUpdatedTrackBeats()));
00356     }
00357     m_pTrack.clear();
00358     m_pBeats.clear();
00359 }
00360 
00361 void BpmControl::slotUpdatedTrackBeats()
00362 {
00363     if (m_pTrack) {
00364         m_pBeats = m_pTrack->getBeats();
00365     }
00366 }
00367 
00368 void BpmControl::slotBeatsTranslate(double v) {
00369     if (v > 0 && m_pBeats && (m_pBeats->getCapabilities() & Beats::BEATSCAP_TRANSLATE)) {
00370         double currentSample = getCurrentSample();
00371         double closestBeat = m_pBeats->findClosestBeat(currentSample);
00372         int delta = currentSample - closestBeat;
00373         if (delta % 2 != 0) {
00374             delta--;
00375         }
00376         m_pBeats->translate(delta);
00377     }
00378 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines