Skip to content

Commit 6f4b0e9

Browse files
committed
CmrtGroupingService/TrackDAO/WTrackMenu: Add Make Canonical CMRT manual re-election.
1 parent 9f0d56a commit 6f4b0e9

6 files changed

Lines changed: 168 additions & 24 deletions

File tree

src/library/dao/trackdao.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "library/library_prefs.h"
2323
#include "library/queryutil.h"
2424
#include "moc_trackdao.cpp"
25+
#include "musicbrainz/cmrtgroupingservice.h"
2526
#include "sources/soundsourceproxy.h"
2627
#include "track/beats.h"
2728
#include "track/globaltrackcache.h"
@@ -1864,6 +1865,11 @@ TrackPointer TrackDAO::getCmrtCanonicalTrack(TrackId trackId, double* pOffsetSec
18641865
return pCanonical;
18651866
}
18661867

1868+
bool TrackDAO::promoteCmrtCanonical(TrackId trackId) const {
1869+
mixxx::CmrtGroupingService groupingService(m_fingerprintDao, m_pConfig);
1870+
return groupingService.promoteToCanonical(trackId);
1871+
}
1872+
18671873
void TrackDAO::applyCmrtOverlayToLoadedTrack(const TrackPointer& pTrack) const {
18681874
applyCmrtOverlayIfConfigured(pTrack);
18691875
}

src/library/dao/trackdao.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC
124124

125125
TrackPointer getCmrtCanonicalTrack(TrackId trackId, double* pOffsetSeconds) const;
126126

127+
// the same re-election CmrtGroupingService::handleMatchedCandidate()
128+
// already runs automatically when a fresh AcoustID match's quality score
129+
// beats the existing canonical, just triggered by user choice
130+
bool promoteCmrtCanonical(TrackId trackId) const;
131+
127132
bool clearMusicBrainzData(TrackId trackId) const;
128133

129134
signals:

src/musicbrainz/cmrtgroupingservice.cpp

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "analyzer/qualityscorer.h"
77
#include "library/library_prefs.h"
88
#include "track/globaltrackcache.h"
9+
#include "track/track.h"
910

1011
namespace 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+
157200
void 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
}

src/musicbrainz/cmrtgroupingservice.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class CmrtGroupingService {
2222

2323
void processTrack(TrackId trackId, const QString& musicbrainzRecordingId);
2424

25+
bool promoteToCanonical(TrackId trackId);
26+
2527
private:
2628
void assignToExistingGroup(TrackId trackId,
2729
int groupId,

src/widget/wtrackmenu.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ void WTrackMenu::createMenus() {
240240
if (featureIsEnabled(Feature::Analyze)) {
241241
m_pAnalyzeMenu = make_parented<QMenu>(this);
242242
m_pAnalyzeMenu->setTitle(tr("Analyze"));
243+
// Required to show CMRT-disabled tooltip.
244+
m_pAnalyzeMenu->setToolTipsVisible(true);
243245
}
244246

245247
if (featureIsEnabled(Feature::SearchRelated)) {
@@ -610,6 +612,13 @@ void WTrackMenu::createActions() {
610612
&QAction::triggered,
611613
this,
612614
&WTrackMenu::slotAnalyzeFingerprint);
615+
616+
m_pMakeCmrtCanonicalAction =
617+
make_parented<QAction>(tr("Make Canonical (CMRT)"), this);
618+
connect(m_pMakeCmrtCanonicalAction,
619+
&QAction::triggered,
620+
this,
621+
&WTrackMenu::slotMakeCmrtCanonical);
613622
}
614623

615624
// This action is only usable when m_deckGroup is set. That is true only
@@ -792,6 +801,8 @@ void WTrackMenu::setupActions() {
792801
m_pAnalyzeMenu->addAction(m_pReanalyzeVarBpmAction);
793802
m_pAnalyzeMenu->addSeparator();
794803
m_pAnalyzeMenu->addAction(m_pAnalyzeFingerprintAction.get());
804+
m_pAnalyzeMenu->addSeparator();
805+
m_pAnalyzeMenu->addAction(m_pMakeCmrtCanonicalAction);
795806
addMenu(m_pAnalyzeMenu);
796807
}
797808

@@ -936,6 +947,32 @@ void WTrackMenu::copyOrAddCmrtHotcuesForSelection(bool replaceExisting) {
936947
}
937948
}
938949

950+
void WTrackMenu::slotMakeCmrtCanonical() {
951+
promoteSelectionToCmrtCanonical();
952+
}
953+
954+
void WTrackMenu::promoteSelectionToCmrtCanonical() {
955+
const TrackIdList trackIds = getTrackIds();
956+
if (trackIds.isEmpty()) {
957+
return;
958+
}
959+
960+
TrackDAO& trackDao = m_pLibrary->trackCollectionManager()
961+
->internalCollection()
962+
->getTrackDAO();
963+
964+
bool anyPromoted = false;
965+
for (const TrackId& id : std::as_const(trackIds)) {
966+
if (trackDao.promoteCmrtCanonical(id)) {
967+
anyPromoted = true;
968+
}
969+
}
970+
971+
if (anyPromoted) {
972+
emit m_pLibrary->trackCollectionManager()->cmrtDataChanged();
973+
}
974+
}
975+
939976
int WTrackMenu::getCommonTrackRating() const {
940977
VERIFY_OR_DEBUG_ASSERT(!isEmpty()) {
941978
return 0;
@@ -1261,6 +1298,24 @@ void WTrackMenu::updateMenus() {
12611298
// https://github.com/mixxxdj/mixxx/pull/10931#issuecomment-1262559750
12621299
m_pReanalyzeConstBpmAction->setVisible(!useFixedTempo);
12631300
m_pReanalyzeVarBpmAction->setVisible(useFixedTempo);
1301+
1302+
const bool anyOverlayActive = anySelectedTrackUsesCmrtOverlay();
1303+
const bool anyPromotable =
1304+
!anyOverlayActive && anySelectedTrackIsCmrtMember();
1305+
m_pMakeCmrtCanonicalAction->setEnabled(anyPromotable);
1306+
m_pMakeCmrtCanonicalAction->setToolTip(anyPromotable
1307+
? tr("Make the selected track(s) the new canonical track(s) of their "
1308+
"CMRT groups.\n"
1309+
"Offsets of other members in the group are recalculated with "
1310+
"respect to the new canonical track.")
1311+
: anyOverlayActive
1312+
? tr("Disabled while \"Use CMRT Data\" is "
1313+
"checked. Uncheck it first.")
1314+
: tr("Disabled: the selected track(s) are "
1315+
"either already the canonical (CMRT) "
1316+
"track for their group, or aren't "
1317+
"linked to a CMRT group with another "
1318+
"track to compare against."));
12641319
}
12651320

12661321
const auto applyCmrtRestriction = [cmrtActive, &cmrtDisabledTooltip](QAction* pAction) {

src/widget/wtrackmenu.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class WTrackMenu : public QMenu {
176176
void slotSortHotcuesByPosition(HotcueSortMode sortMode);
177177
void slotCopyCmrtHotcues();
178178
void slotAddCmrtHotcues();
179+
void slotMakeCmrtCanonical();
179180

180181
// Info and metadata
181182
void slotUpdateReplayGainFromPregain();
@@ -265,6 +266,7 @@ class WTrackMenu : public QMenu {
265266
// Whether any currently selected track is a non-canonical member of
266267
// a CMRT group (use: has a canonical track to copy/add hotcues from).
267268
bool anySelectedTrackIsCmrtMember() const;
269+
void promoteSelectionToCmrtCanonical();
268270
void lockBpm(bool lock);
269271

270272
#ifdef __STEM__
@@ -399,6 +401,7 @@ class WTrackMenu : public QMenu {
399401
parented_ptr<QAction> m_pSortHotcuesByPositionCompressAction{};
400402
parented_ptr<QAction> m_pCopyCmrtHotcuesAction{};
401403
parented_ptr<QAction> m_pAddCmrtHotcuesAction{};
404+
parented_ptr<QAction> m_pMakeCmrtCanonicalAction{};
402405

403406
const UserSettingsPointer m_pConfig;
404407
Library* const m_pLibrary;

0 commit comments

Comments
 (0)