1 #include "engine/sync/enginesync.h"
2 
3 #include <QStringList>
4 
5 #include "engine/channels/enginechannel.h"
6 #include "engine/enginebuffer.h"
7 #include "engine/sync/internalclock.h"
8 #include "util/assert.h"
9 #include "util/logger.h"
10 
11 namespace {
12 const mixxx::Logger kLogger("EngineSync");
13 } // namespace
14 
EngineSync(UserSettingsPointer pConfig)15 EngineSync::EngineSync(UserSettingsPointer pConfig)
16         : BaseSyncableListener(pConfig) {
17 }
18 
~EngineSync()19 EngineSync::~EngineSync() {
20 }
21 
pickMaster(Syncable * enabling_syncable)22 Syncable* EngineSync::pickMaster(Syncable* enabling_syncable) {
23     // If there is an explicit master, and it is playing, keep it.
24     if (m_pMasterSyncable && m_pMasterSyncable->getSyncMode() == SYNC_MASTER_EXPLICIT) {
25         return m_pMasterSyncable;
26     }
27 
28     Syncable* first_stopped_deck = nullptr;
29     Syncable* first_playing_deck = nullptr;
30     int stopped_deck_count = 0;
31     int playing_deck_count = 0;
32 
33     for (const auto& pSyncable : qAsConst(m_syncables)) {
34         if (pSyncable->getBaseBpm() <= 0.0) {
35             continue;
36         }
37 
38         if (pSyncable != enabling_syncable) {
39             if (!pSyncable->getChannel()->isPrimaryDeck()) {
40                 continue;
41             }
42             if (!pSyncable->isSynchronized()) {
43                 continue;
44             }
45         }
46 
47         if (pSyncable->isPlaying() && pSyncable->isAudible()) {
48             if (playing_deck_count == 0) {
49                 first_playing_deck = pSyncable;
50             }
51             playing_deck_count++;
52         } else {
53             if (stopped_deck_count == 0) {
54                 first_stopped_deck = pSyncable;
55             }
56             stopped_deck_count++;
57         }
58     }
59 
60     if (playing_deck_count == 1) {
61         return first_playing_deck;
62     } else if (playing_deck_count > 1) {
63         return m_pInternalClock;
64     }
65 
66     // No valid playing sync decks
67     if (stopped_deck_count == 1) {
68         return first_stopped_deck;
69     } else if (stopped_deck_count > 1) {
70         return m_pInternalClock;
71     }
72 
73     // No valid stopped sync decks
74     return nullptr;
75 }
76 
requestSyncMode(Syncable * pSyncable,SyncMode mode)77 void EngineSync::requestSyncMode(Syncable* pSyncable, SyncMode mode) {
78     if (kLogger.traceEnabled()) {
79         kLogger.trace() << "EngineSync::requestSyncMode" << pSyncable->getGroup() << mode;
80     }
81     // Based on the call hierarchy I don't think this is possible. (Famous last words.)
82     VERIFY_OR_DEBUG_ASSERT(pSyncable) {
83         return;
84     }
85 
86     if (mode == SYNC_MASTER_EXPLICIT) {
87         // Note: we enable master unconditionally. If it has no valid
88         // tempo, the tempo of the old master remains until we know better
89         activateMaster(pSyncable, true);
90         if (pSyncable->getBaseBpm() > 0) {
91             setMasterParams(pSyncable, pSyncable->getBeatDistance(),
92                             pSyncable->getBaseBpm(), pSyncable->getBpm());
93         }
94     } else if (mode == SYNC_FOLLOWER ||
95             mode == SYNC_MASTER_SOFT ||
96             pSyncable == m_pInternalClock) {
97         // Note: SYNC_MASTER_SOFT and SYNC_FOLLOWER cannot be set explicitly,
98         // they are calculated by pickMaster.
99         // Internal clock cannot be disabled, it is always listening
100         if (m_pMasterSyncable == pSyncable) {
101             // This Syncable was master before. Hand off.
102             m_pMasterSyncable = nullptr;
103             pSyncable->setSyncMode(SYNC_FOLLOWER);
104         }
105         Syncable* newMaster = pickMaster(pSyncable);
106         if (newMaster) {
107             activateMaster(newMaster, false);
108         }
109         if (pSyncable != newMaster) {
110             activateFollower(pSyncable);
111         }
112     } else {
113         deactivateSync(pSyncable);
114     }
115     checkUniquePlayingSyncable();
116 }
117 
findBpmMatchTarget(Syncable * requester)118 Syncable* EngineSync::findBpmMatchTarget(Syncable* requester) {
119     Syncable* pStoppedTarget = nullptr;
120 
121     for (const auto& pOtherSyncable : qAsConst(m_syncables)) {
122         if (pOtherSyncable == requester) {
123             continue;
124         }
125         // Skip non-master decks, like preview decks.
126         if (!pOtherSyncable->getChannel()->isMasterEnabled()) {
127             continue;
128         }
129         if (!pOtherSyncable->getChannel()->isPrimaryDeck()) {
130             continue;
131         }
132         if (pOtherSyncable->getBaseBpm() == 0.0) {
133             continue;
134         }
135 
136         // If the other deck is playing we stop looking immediately. Otherwise continue looking
137         // for a playing deck with bpm > 0.0.
138         if (pOtherSyncable->isPlaying() && pOtherSyncable->isAudible()) {
139             return pOtherSyncable;
140         }
141 
142         // The target is not playing. If this is the first one we have seen,
143         // record it. If we never find a playing target, we'll return
144         // this one as a fallback.
145         if (!pStoppedTarget && !requester->isPlaying()) {
146             pStoppedTarget = pOtherSyncable;
147         }
148     }
149 
150     return pStoppedTarget;
151 }
152 
requestEnableSync(Syncable * pSyncable,bool bEnabled)153 void EngineSync::requestEnableSync(Syncable* pSyncable, bool bEnabled) {
154     if (kLogger.traceEnabled()) {
155         kLogger.trace() << "EngineSync::requestEnableSync " << pSyncable->getGroup() << bEnabled;
156     }
157     // Sync disable request, hand off to a different function
158     if (!bEnabled) {
159         // Already disabled?  Do nothing.
160         if (!pSyncable->isSynchronized()) {
161             return;
162         }
163         deactivateSync(pSyncable);
164         return;
165     }
166 
167     // Already enabled?  Do nothing.
168     if (pSyncable->isSynchronized()) {
169         return;
170     }
171 
172     // Now go through and possible pick a new master deck if we can find one.
173     Syncable* newMaster = pickMaster(pSyncable);
174 
175     // The syncable that will be used to initialize the master params, if needed
176     Syncable* pParamsSyncable = nullptr;
177 
178     if (newMaster == m_pInternalClock) {
179         // This happens if we had a single master before
180         if (m_pMasterSyncable != m_pInternalClock) {
181             if (pSyncable->isPlaying()) {
182                 // We are already playing, only change speed if
183                 // the old master is playing as well
184                 if (m_pMasterSyncable && m_pMasterSyncable->isPlaying()) {
185                     // Adopt value from the old master if it is still playing
186                     pParamsSyncable = m_pMasterSyncable;
187                 } else {
188                     DEBUG_ASSERT(pSyncable->getBaseBpm() > 0);
189                     pParamsSyncable = pSyncable;
190                 }
191             } else {
192                 if (m_pMasterSyncable) {
193                     pParamsSyncable = m_pMasterSyncable;
194                 } else {
195                     // Can this ever happen?
196                     DEBUG_ASSERT(false);
197                     pParamsSyncable = pSyncable;
198                 }
199             }
200         }
201     } else if (newMaster == pSyncable) {
202         // There is no other synced deck. If any other deck is playing we will
203         // match the first available bpm -- sync won't be enabled on these decks,
204         // otherwise there would have been a master.
205         pParamsSyncable = findBpmMatchTarget(pSyncable);
206         if (pParamsSyncable == nullptr) {
207             // We weren't able to find anything to match to, so set ourselves as the
208             // target.  That way we'll use our own params when we setMasterParams below.
209             pParamsSyncable = pSyncable;
210         }
211     } else if (newMaster) {
212         // This happens if this Deck has no valid BPM
213         // avoid that other decks are adjusted
214         pParamsSyncable = newMaster;
215     } else {
216         // This happens if there is no other SyncDeck around and this has no valid BPM
217         // nothing to do.
218     }
219 
220     if (newMaster != nullptr && newMaster != m_pMasterSyncable) {
221         activateMaster(newMaster, false);
222     }
223 
224     if (newMaster != pSyncable) {
225         pSyncable->setSyncMode(SYNC_FOLLOWER);
226     }
227 
228     if (pParamsSyncable != nullptr) {
229         setMasterParams(pParamsSyncable,
230                 pParamsSyncable->getBeatDistance(),
231                 pParamsSyncable->getBaseBpm(),
232                 pParamsSyncable->getBpm());
233         pSyncable->setInstantaneousBpm(pParamsSyncable->getBpm());
234         if (pParamsSyncable != pSyncable) {
235             pSyncable->requestSync();
236         }
237     }
238 }
239 
notifyPlayingAudible(Syncable * pSyncable,bool playingAudible)240 void EngineSync::notifyPlayingAudible(Syncable* pSyncable, bool playingAudible) {
241     if (kLogger.traceEnabled()) {
242         kLogger.trace() << "EngineSync::notifyPlayingAudible"
243                         << pSyncable->getGroup() << playingAudible;
244     }
245     // For now we don't care if the deck is now playing or stopping.
246     if (!pSyncable->isSynchronized()) {
247         return;
248     }
249 
250     // similar to enablesync -- we pick a new master and maybe reinit.
251     Syncable* newMaster = pickMaster(playingAudible ? pSyncable : nullptr);
252 
253     if (newMaster != nullptr && newMaster != m_pMasterSyncable) {
254         activateMaster(newMaster, false);
255         setMasterParams(newMaster, newMaster->getBeatDistance(), newMaster->getBaseBpm(), newMaster->getBpm());
256     }
257 
258     pSyncable->requestSync();
259 }
260 
notifyScratching(Syncable * pSyncable,bool scratching)261 void EngineSync::notifyScratching(Syncable* pSyncable, bool scratching) {
262     // No special behavior for now.
263     Q_UNUSED(pSyncable);
264     Q_UNUSED(scratching);
265 }
266 
notifyBpmChanged(Syncable * pSyncable,double bpm)267 void EngineSync::notifyBpmChanged(Syncable* pSyncable, double bpm) {
268     if (kLogger.traceEnabled()) {
269         kLogger.trace() << "EngineSync::notifyBpmChanged" << pSyncable->getGroup() << bpm;
270     }
271 
272     // Master Base BPM shouldn't be updated for every random deck that twiddles
273     // the rate.
274     setMasterBpm(pSyncable, bpm);
275 }
276 
requestBpmUpdate(Syncable * pSyncable,double bpm)277 void EngineSync::requestBpmUpdate(Syncable* pSyncable, double bpm) {
278     if (kLogger.traceEnabled()) {
279         kLogger.trace() << "EngineSync::requestBpmUpdate" << pSyncable->getGroup() << bpm;
280     }
281 
282     double mbaseBpm = 0.0;
283     double mbpm = 0.0;
284     double beatDistance = 0.0;
285     if (m_pMasterSyncable) {
286         mbaseBpm = m_pMasterSyncable->getBaseBpm();
287         mbpm = m_pMasterSyncable->getBpm();
288         beatDistance = m_pMasterSyncable->getBeatDistance();
289     }
290 
291     if (mbaseBpm != 0.0 && mbpm != 0.0) {
292         // resync to current master
293         pSyncable->setMasterParams(beatDistance, mbaseBpm, mbpm);
294     } else {
295         // There is no other master, adopt this bpm as master
296         pSyncable->setMasterParams(0.0, 0.0, bpm);
297     }
298 }
299 
notifyInstantaneousBpmChanged(Syncable * pSyncable,double bpm)300 void EngineSync::notifyInstantaneousBpmChanged(Syncable* pSyncable, double bpm) {
301     if (kLogger.traceEnabled()) {
302         kLogger.trace() << "EngineSync::notifyInstantaneousBpmChanged" << pSyncable->getGroup() << bpm;
303     }
304     if (!isMaster(pSyncable->getSyncMode())) {
305         return;
306     }
307 
308     // Do not update the master rate slider because instantaneous changes are
309     // not user visible.
310     setMasterInstantaneousBpm(pSyncable, bpm);
311 }
312 
notifyBeatDistanceChanged(Syncable * pSyncable,double beat_distance)313 void EngineSync::notifyBeatDistanceChanged(Syncable* pSyncable, double beat_distance) {
314     if (kLogger.traceEnabled()) {
315         kLogger.trace() << "EngineSync::notifyBeatDistanceChanged" << pSyncable->getGroup() << beat_distance;
316     }
317     if (!isMaster(pSyncable->getSyncMode())) {
318         return;
319     }
320 
321     setMasterBeatDistance(pSyncable, beat_distance);
322 }
323 
activateFollower(Syncable * pSyncable)324 void EngineSync::activateFollower(Syncable* pSyncable) {
325     if (pSyncable == nullptr) {
326         qWarning() << "WARNING: Logic Error: Called activateFollower on a nullptr Syncable.";
327         return;
328     }
329 
330     pSyncable->setSyncMode(SYNC_FOLLOWER);
331     pSyncable->setMasterParams(masterBeatDistance(), masterBaseBpm(), masterBpm());
332     pSyncable->setInstantaneousBpm(masterBpm());
333 }
334 
activateMaster(Syncable * pSyncable,bool explicitMaster)335 void EngineSync::activateMaster(Syncable* pSyncable, bool explicitMaster) {
336     VERIFY_OR_DEBUG_ASSERT(pSyncable) {
337         qWarning() << "WARNING: Logic Error: Called activateMaster on a nullptr Syncable.";
338         return;
339     }
340     if (kLogger.traceEnabled()) {
341         kLogger.trace() << "EngineSync::activateMaster: "
342                         << pSyncable->getGroup() << "explicit? "
343                         << explicitMaster;
344     }
345 
346     if (m_pMasterSyncable == pSyncable) {
347         // Already master, update the explicit State.
348         if (explicitMaster) {
349             if (m_pMasterSyncable->getSyncMode() != SYNC_MASTER_EXPLICIT) {
350                 m_pMasterSyncable->setSyncMode(SYNC_MASTER_EXPLICIT);
351             } else if (m_pMasterSyncable->getSyncMode() != SYNC_MASTER_SOFT) {
352                 m_pMasterSyncable->setSyncMode(SYNC_MASTER_SOFT);
353             } else {
354                 DEBUG_ASSERT(!"Logic Error: m_pMasterSyncable is a syncable that does not think it is master.");
355             }
356         }
357         // nothing else to do
358         return;
359     }
360 
361     // If a channel is master, disable it.
362     Syncable* pOldChannelMaster = m_pMasterSyncable;
363 
364     m_pMasterSyncable = nullptr;
365     if (pOldChannelMaster) {
366         pOldChannelMaster->setSyncMode(SYNC_FOLLOWER);
367     }
368 
369     //qDebug() << "Setting up master " << pSyncable->getGroup();
370     m_pMasterSyncable = pSyncable;
371     if (explicitMaster) {
372         pSyncable->setSyncMode(SYNC_MASTER_EXPLICIT);
373     } else {
374         pSyncable->setSyncMode(SYNC_MASTER_SOFT);
375     }
376     pSyncable->setMasterParams(masterBeatDistance(), masterBaseBpm(), masterBpm());
377     pSyncable->setInstantaneousBpm(masterBpm());
378 
379     if (m_pMasterSyncable != m_pInternalClock) {
380         activateFollower(m_pInternalClock);
381     }
382 
383     // It is up to callers of this function to initialize bpm and beat_distance
384     // if necessary.
385 }
386 
deactivateSync(Syncable * pSyncable)387 void EngineSync::deactivateSync(Syncable* pSyncable) {
388     if (kLogger.traceEnabled()) {
389         kLogger.trace() << "EngineSync::deactivateSync" << pSyncable->getGroup();
390     }
391     bool wasMaster = isMaster(pSyncable->getSyncMode());
392     if (wasMaster) {
393         m_pMasterSyncable = nullptr;
394     }
395 
396     // Notifications happen after-the-fact.
397     pSyncable->setSyncMode(SYNC_NONE);
398 
399     bool bSyncDeckExists = syncDeckExists();
400 
401     if (pSyncable != m_pInternalClock && !bSyncDeckExists) {
402         // Deactivate the internal clock if there are no more sync decks left.
403         m_pMasterSyncable = nullptr;
404         m_pInternalClock->setSyncMode(SYNC_NONE);
405     }
406 
407     Syncable* newMaster = pickMaster(nullptr);
408     if (newMaster != nullptr && m_pMasterSyncable != newMaster) {
409         activateMaster(newMaster, false);
410     }
411 }
412 
pickNonSyncSyncTarget(EngineChannel * pDontPick) const413 Syncable* EngineSync::pickNonSyncSyncTarget(EngineChannel* pDontPick) const {
414     // First choice: the sync master, if it's a deck
415     if (m_pMasterSyncable &&
416             m_pMasterSyncable->getChannel() &&
417             m_pMasterSyncable->getChannel() != pDontPick) {
418         return m_pMasterSyncable;
419     }
420 
421     Syncable* pFirstPlayingDeck = nullptr;
422     Syncable* pFirstNonplayingDeck = nullptr;
423     foreach (Syncable* pSyncable, m_syncables) {
424         EngineChannel* pChannel = pSyncable->getChannel();
425         // Exclude non-decks
426         if (pChannel == nullptr || pChannel == pDontPick) {
427             continue;
428         }
429 
430         // Only consider channels that have a track loaded, are in the master
431         // mix, and are primary decks.
432         if (pChannel->isActive() && pChannel->isMasterEnabled() && pChannel->isPrimaryDeck()) {
433             EngineBuffer* pBuffer = pChannel->getEngineBuffer();
434             if (pBuffer && pBuffer->getBpm() > 0) {
435                 if (pBuffer->getSpeed() != 0.0) {
436                     if (pSyncable->getSyncMode() != SYNC_NONE) {
437                         // Second choice: first playing sync deck
438                         return pSyncable;
439                     }
440                     if (pFirstPlayingDeck == nullptr) {
441                         pFirstPlayingDeck = pSyncable;
442                     }
443                 } else if (pFirstNonplayingDeck == nullptr) {
444                     pFirstNonplayingDeck = pSyncable;
445                 }
446             }
447         }
448     }
449     if (pFirstPlayingDeck) {
450         // Third choice: first playing non-sync deck
451         return pFirstPlayingDeck;
452     }
453 
454     // No playing decks have a BPM. Go with the first deck that was stopped but
455     // had a BPM.
456     return pFirstNonplayingDeck;
457 }
458 
otherSyncedPlaying(const QString & group)459 bool EngineSync::otherSyncedPlaying(const QString& group) {
460     bool othersInSync = false;
461     for (Syncable* theSyncable : qAsConst(m_syncables)) {
462         bool isSynchonized = theSyncable->isSynchronized();
463         if (theSyncable->getGroup() == group) {
464             if (!isSynchonized) {
465                 return false;
466             }
467             continue;
468         }
469         if (theSyncable->isPlaying() && isSynchonized) {
470             othersInSync = true;
471         }
472     }
473     return othersInSync;
474 }
475