66#include " analyzer/qualityscorer.h"
77#include " library/library_prefs.h"
88#include " track/globaltrackcache.h"
9+ #include " track/track.h"
910
1011namespace mixxx {
1112
@@ -154,6 +155,48 @@ void CmrtGroupingService::processTrack(
154155 createNewGroup (trackId, *pFpRow, qualityScore);
155156}
156157
158+ bool CmrtGroupingService::promoteToCanonical (TrackId trackId) {
159+ if (sDebugCmrtGroupingService ) {
160+ qDebug () << " CmrtGroupingService -> [promoteToCanonical] -> entry"
161+ << " trackId:" << trackId;
162+ }
163+
164+ auto pMember = m_fingerprintDao.getCmrtMemberByTrackId (trackId);
165+ if (!pMember) {
166+ if (sDebugCmrtGroupingService ) {
167+ qDebug () << " CmrtGroupingService -> [promoteToCanonical] -> "
168+ " not a cmrt_members row, skipping"
169+ << " trackId:" << trackId;
170+ }
171+ return false ;
172+ }
173+
174+ auto pGroup = m_fingerprintDao.getCmrtGroup (pMember->groupId );
175+ if (!pGroup) {
176+ qWarning () << " CmrtGroupingService -> [promoteToCanonical] -> "
177+ " member row references missing group"
178+ << pMember->groupId << " for track" << trackId;
179+ return false ;
180+ }
181+
182+ if (pGroup->canonicalTrackId == trackId) {
183+ if (sDebugCmrtGroupingService ) {
184+ qDebug () << " CmrtGroupingService -> [promoteToCanonical] -> "
185+ " trackId is already canonical, skipping"
186+ << trackId;
187+ }
188+ return false ;
189+ }
190+
191+ replaceCanonical (pMember->groupId ,
192+ pGroup->canonicalTrackId ,
193+ trackId,
194+ pMember->offsetFromCanonical ,
195+ pMember->qualityScore ,
196+ pMember->matchScore );
197+ return true ;
198+ }
199+
157200void CmrtGroupingService::assignToExistingGroup (
158201 TrackId trackId,
159202 int groupId,
@@ -307,15 +350,26 @@ void CmrtGroupingService::replaceCanonical(int groupId,
307350 // 1. Point the group at the new canonical track.
308351 m_fingerprintDao.updateCanonicalTrack (groupId, newCanonicalId);
309352
310- // 2. Add the new track as a member at offset 0 and mark it canonical.
311- CmrtMember newMember;
312- newMember.groupId = groupId;
313- newMember.trackId = newCanonicalId;
314- newMember.offsetFromCanonical = 0.0 ;
315- newMember.qualityScore = newCanonicalQualityScore;
316- newMember.addedAt = QDateTime::currentDateTimeUtc ();
317- m_fingerprintDao.addCmrtMember (newMember);
318- m_fingerprintDao.updateCmrtGroupTrackCount (groupId, +1 );
353+ // 2. If already a member, update its offset to 0 and mark it canonical.
354+ // If not, add the new track as a member at offset 0 and mark it canonical.
355+ bool newCanonicalWasUsingCmrtData = false ;
356+ if (auto pExistingMember = m_fingerprintDao.getCmrtMemberByTrackId (newCanonicalId)) {
357+ newCanonicalWasUsingCmrtData = pExistingMember->useCmrtData ;
358+ m_fingerprintDao.updateMemberOffset (newCanonicalId, 0.0 );
359+ if (newCanonicalWasUsingCmrtData) {
360+ m_fingerprintDao.updateMemberUseCmrtData (newCanonicalId, false );
361+ }
362+ } else {
363+ // 2
364+ CmrtMember newMember;
365+ newMember.groupId = groupId;
366+ newMember.trackId = newCanonicalId;
367+ newMember.offsetFromCanonical = 0.0 ;
368+ newMember.qualityScore = newCanonicalQualityScore;
369+ newMember.addedAt = QDateTime::currentDateTimeUtc ();
370+ m_fingerprintDao.addCmrtMember (newMember);
371+ m_fingerprintDao.updateCmrtGroupTrackCount (groupId, +1 );
372+ }
319373
320374 if (auto pNewMeta = m_fingerprintDao.getFingerprintMetadata (newCanonicalId)) {
321375 pNewMeta->cmrtGroupId = groupId;
@@ -343,6 +397,13 @@ void CmrtGroupingService::replaceCanonical(int groupId,
343397 const QVector<quint32> newCanonicalFp =
344398 chromaBytesToVector (m_fingerprintDao.loadChromaFile (newCanonicalId));
345399
400+ // Looked up once -- every overlaying member below hands the same
401+ // pointer to applyCmrtOverlay(). Null if the new canonical isn't
402+ // currently loaded anywhere, in which case there's nothing live to
403+ // push the update onto (handled per-member below).
404+ const TrackPointer pNewCanonicalTrack =
405+ GlobalTrackCacheLocker ().lookupTrackById (newCanonicalId);
406+
346407 for (const CmrtMember& member : existingMembers) {
347408 if (member.trackId == oldCanonicalId || member.trackId == newCanonicalId) {
348409 continue ;
@@ -359,28 +420,40 @@ void CmrtGroupingService::replaceCanonical(int groupId,
359420 pMemberMeta->cmrtOffsetSeconds = offsetSeconds;
360421 m_fingerprintDao.saveFingerprintMetadata (*pMemberMeta);
361422 }
423+
424+ if (!member.useCmrtData ) {
425+ continue ;
426+ }
427+ // This member's *live* Track object (if it has one -- e.g.
428+ // loaded on a deck) is still overlaying the demoted
429+ // oldCanonicalId, and will keep doing so Push the new canonical
430+ // the freshly-recalculated offset onto it directly instead.
431+ if (TrackPointer pOverlayingMember =
432+ GlobalTrackCacheLocker ().lookupTrackById (member.trackId )) {
433+ if (pNewCanonicalTrack) {
434+ pOverlayingMember->applyCmrtOverlay (pNewCanonicalTrack,
435+ offsetSeconds,
436+ pOverlayingMember->getSampleRate ());
437+ } else {
438+ GlobalTrackCacheLocker ().purgeTrackId (member.trackId );
439+ }
440+ }
441+ // Not currently loaded anywhere: nothing live to refresh. The
442+ // next getTrackById() picks it up naturally, since
443+ // applyCmrtOverlayIfConfigured() always reads current DB state.
362444 }
363445
364446 if (sDebugCmrtGroupingService ) {
365447 qDebug () << " CmrtGroupingService -> [replaceCanonical] -> done, recalculated"
366448 << existingMembers.size () << " other member offset(s)" ;
367449 }
368450
369- // any member with use_cmrt_data=true was overlaying
370- // (via Track::applyCmrtOverlay()) a TrackPointer to the now-demoted
371- // oldCanonicalId. GlobalTrackCacheLocker().purgeTrackId() on each
372- // overlaying member forces the next getTrackById() to reconstruct the
373- // Track from scratch, which re-runs applyCmrtOverlayIfConfigured()
374- // against the new canonical
375- for (const CmrtMember& member : existingMembers) {
376- if (member.useCmrtData ) {
377- GlobalTrackCacheLocker ().purgeTrackId (member.trackId );
378- }
379- }
380- if (newMember.useCmrtData ) {
381- // Shouldn't normally be true for a track that just became
382- // canonical (there's nothing to overlay onto itself), but purge
383- // defensively in case the flag was set before this election ran.
451+ if (newCanonicalWasUsingCmrtData) {
452+ // newCanonicalId's own overlay flag was just cleared in step 2
453+ // above -- purge it too so a live, already-loaded Track object
454+ // for it (if any) reconstructs on next load with its own real
455+ // cues/beats instead of the stale overlay snapshot it was
456+ // showing back when it was still a member.
384457 GlobalTrackCacheLocker ().purgeTrackId (newCanonicalId);
385458 }
386459}
0 commit comments