Skip to content

Commit ac3ec7f

Browse files
committed
feat(QML): use waveform overview cache
1 parent 712e58d commit ac3ec7f

7 files changed

Lines changed: 343 additions & 45 deletions

File tree

res/qml/Library/TrackList.qml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,13 +329,9 @@ Rectangle {
329329
colorLow: Theme.green
330330
colorMid: Theme.blue
331331
renderer: Mixxx.WaveformOverview.Renderer.Filtered
332-
track: null // Lazy loaded
332+
trackUrl: file_url
333333
implicitHeight: 30
334334
implicitWidth: 30
335-
336-
Component.onCompleted: {
337-
waveformOverview.track = TableView.view.model?.getTrackByRow(row)
338-
}
339335
}
340336
}
341337
DelegateChoice {

src/coreservices.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,10 +627,19 @@ void CoreServices::initialize(QApplication* pApp) {
627627
m_pRecordingManager.get());
628628

629629
OverviewCache* pOverviewCache = OverviewCache::createInstance(pConfig, m_pDbConnectionPool);
630+
pOverviewCache->setTrackDAO(
631+
&m_pTrackCollectionManager->internalCollection()->getTrackDAO());
630632
connect(&(m_pTrackCollectionManager->internalCollection()->getTrackDAO()),
631633
&TrackDAO::waveformSummaryUpdated,
632634
pOverviewCache,
633635
&OverviewCache::onTrackSummaryChanged);
636+
// Forward per-track analyzer progress to OverviewCache so that
637+
// OverviewCache clients (e.g. QmlWaveformOverview) can repaint
638+
// the partial waveform during analysis.
639+
connect(m_pPlayerManager.get(),
640+
&PlayerManager::trackAnalyzerProgress,
641+
pOverviewCache,
642+
&OverviewCache::onTrackAnalysisProgress);
634643

635644
// Binding the PlayManager to the Library may already trigger
636645
// loading of tracks which requires that the GlobalTrackCache has

src/library/dao/trackdao.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC
143143
friend class LibraryScanner;
144144
friend class TrackCollection;
145145
friend class TrackAnalysisScheduler;
146+
friend class OverviewCache;
146147

147148
QList<TrackId> resolveTrackIds(
148149
const QStringList& pathList,

src/library/overviewcache.cpp

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
#include <QFutureWatcher>
44
#include <QPixmapCache>
5+
#include <QPointer>
56
#include <QSqlDatabase>
67
#include <QtConcurrentRun>
78

89
#include "library/dao/analysisdao.h"
10+
#include "library/dao/trackdao.h"
911
#include "moc_overviewcache.cpp"
1012
#include "util/db/dbconnectionpooled.h"
1113
#include "util/db/dbconnectionpooler.h"
@@ -40,8 +42,12 @@ OverviewCache::OverviewCache(UserSettingsPointer pConfig,
4042
m_pDbConnectionPool(std::move(pDbConnectionPool)) {
4143
}
4244

43-
void OverviewCache::onTrackAnalysisProgress(TrackId trackId, AnalyzerProgress analyzerProgress) {
44-
if (analyzerProgress < 1.0) {
45+
void OverviewCache::onTrackAnalysisProgress(TrackId trackId, AnalyzerProgress progress) {
46+
// Always forward the raw progress so listeners that need to draw
47+
// the partial waveform during analysis (e.g. QmlWaveformOverview)
48+
// can repaint on every tick.
49+
emit analyzerProgress(trackId, progress);
50+
if (progress < 1.0) {
4551
return;
4652
}
4753
m_tracksWithoutOverview.remove(trackId);
@@ -238,6 +244,21 @@ void OverviewCache::requestWaveformSummary(TrackId trackId, const QObject* pRequ
238244

239245
m_currentlyLoadingWaveform.insert(trackId);
240246

247+
// The async load carries `pRequester` verbatim and will eventually
248+
// dispatch the result back to it. If `pRequester` is destroyed while
249+
// the load is still in flight, its `slotWaveformSummaryReady` is
250+
// auto-disconnected by Qt, no one adopts the loaded waveform, and
251+
// `m_currentlyLoadingWaveform` would only be cleared when the
252+
// background job finishes — meanwhile other requesters interested in
253+
// the same track are silently blocked. Drop the pending entry as
254+
// soon as the requester goes away so a new load can be started by
255+
// any other listener still alive.
256+
if (pRequester) {
257+
QObject::connect(pRequester, &QObject::destroyed, this, [this, trackId] {
258+
m_currentlyLoadingWaveform.remove(trackId);
259+
});
260+
}
261+
241262
QFutureWatcher<FutureWaveformResult>* watcher =
242263
new QFutureWatcher<FutureWaveformResult>(this);
243264
QFuture<FutureWaveformResult> future = QtConcurrent::run(
@@ -253,6 +274,35 @@ void OverviewCache::requestWaveformSummary(TrackId trackId, const QObject* pRequ
253274
watcher->setFuture(future);
254275
}
255276

277+
void OverviewCache::requestWaveformSummary(
278+
const QString& trackLocation, const QObject* pRequester) {
279+
if (trackLocation.isEmpty()) {
280+
return;
281+
}
282+
if (!m_pTrackDAO) {
283+
// Without a TrackDAO we cannot translate a file location into
284+
// a TrackId. The TrackId-based overload is the only available
285+
// path in that case.
286+
return;
287+
}
288+
289+
// Resolve the location into a TrackId on the GUI thread. The
290+
// query is a single indexed lookup on `track_locations.location`,
291+
// which is essentially instantaneous, so doing it on the GUI
292+
// thread is preferable to introducing an extra round-trip through
293+
// the worker pool and lets us reuse the existing TrackId-based
294+
// dedup logic and async machinery. `TrackDAO` is owned by
295+
// `TrackCollection` and lives on the GUI thread, so its
296+
// pre-initialised `QSqlDatabase` connection is valid here.
297+
const TrackId trackId = m_pTrackDAO->getTrackIdByLocation(trackLocation);
298+
if (!trackId.isValid()) {
299+
return;
300+
}
301+
302+
// Delegate to the existing TrackId-based load path.
303+
requestWaveformSummary(trackId, pRequester);
304+
}
305+
256306
// static
257307
OverviewCache::FutureWaveformResult OverviewCache::prepareWaveformSummary(
258308
const UserSettingsPointer pConfig,
@@ -276,9 +326,18 @@ OverviewCache::FutureWaveformResult OverviewCache::prepareWaveformSummary(
276326
analysisDao.getAnalysesForTrackByType(
277327
trackId, AnalysisDao::AnalysisType::TYPE_WAVESUMMARY);
278328

279-
if (!analyses.isEmpty()) {
280-
result.pWaveform = ConstWaveformPointer(
281-
WaveformFactory::loadWaveformFromAnalysis(analyses.first()));
329+
// Only load analyses whose version is compatible with the current
330+
// format, mirroring the logic in `AnalyzerWaveform::shouldAnalyze`.
331+
// Loading an incompatible (e.g. `VC_REMOVE`) waveform would yield a
332+
// `Waveform` with invalid data, producing a garbled overview.
333+
for (const AnalysisDao::AnalysisInfo& analysis : std::as_const(analyses)) {
334+
const WaveformFactory::VersionClass vc =
335+
WaveformFactory::waveformSummaryVersionToVersionClass(analysis.version);
336+
if (vc == WaveformFactory::VC_USE) {
337+
result.pWaveform = ConstWaveformPointer(
338+
WaveformFactory::loadWaveformFromAnalysis(analysis));
339+
break;
340+
}
282341
}
283342

284343
return result;
@@ -292,5 +351,15 @@ void OverviewCache::waveformSummaryPrepared() {
292351

293352
m_currentlyLoadingWaveform.remove(res.trackId);
294353

354+
// The async job captured `pRequester` as a raw pointer; it may have
355+
// been deleted on the GUI thread while the load was in flight.
356+
// Re-check on the GUI thread before handing the (possibly dangling)
357+
// pointer to listeners, so they don't compare against `this` with
358+
// a dangling address.
359+
QPointer<QObject> requesterGuard(const_cast<QObject*>(res.requester));
360+
if (requesterGuard.isNull()) {
361+
return;
362+
}
363+
295364
emit waveformSummaryReady(res.requester, res.trackId, res.pWaveform);
296365
}

src/library/overviewcache.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,31 @@
33
#include <QSqlDatabase>
44

55
#include "analyzer/analyzerprogress.h"
6+
#include "analyzer/trackanalysisscheduler.h"
67
#include "preferences/usersettings.h"
78
#include "track/trackid.h"
89
#include "util/db/dbconnectionpool.h"
910
#include "util/singleton.h"
1011
#include "waveform/overviewtype.h"
1112
#include "waveform/waveform.h"
1213

14+
class TrackDAO;
1315
class WaveformSignalColors;
1416

1517
class OverviewCache : public QObject, public Singleton<OverviewCache> {
1618
Q_OBJECT
1719
public:
1820
void onTrackSummaryChanged(TrackId);
1921

22+
/// Inject the library's `TrackDAO`. Used by the
23+
/// `requestWaveformSummary(QString)` overload to resolve a
24+
/// track file location into a `TrackId` through the library
25+
/// database. Must be called once after `createInstance`, before
26+
/// any location-based request is made.
27+
void setTrackDAO(TrackDAO* pTrackDAO) {
28+
m_pTrackDAO = pTrackDAO;
29+
}
30+
2031
QPixmap requestCachedOverview(
2132
mixxx::OverviewType type,
2233
TrackId trackId,
@@ -37,6 +48,17 @@ class OverviewCache : public QObject, public Singleton<OverviewCache> {
3748
/// load for that track is already pending.
3849
void requestWaveformSummary(TrackId trackId, const QObject* pRequester);
3950

51+
/// Variant of `requestWaveformSummary` that resolves the track by
52+
/// its file location rather than its `TrackId`. The location is
53+
/// translated to a `TrackId` through the library database (a
54+
/// single indexed query on the GUI thread) and then the existing
55+
/// `TrackId`-based load path is reused. This is useful for tracks
56+
/// that are not in the database (no valid `TrackId`) but whose
57+
/// file URL is known, e.g. for tracks shown in browse/external
58+
/// table views. If no track matches `trackLocation` the call is
59+
/// a no-op.
60+
void requestWaveformSummary(const QString& trackLocation, const QObject* pRequester);
61+
4062
struct FutureResult {
4163
FutureResult()
4264
: requester(nullptr) {
@@ -72,6 +94,15 @@ class OverviewCache : public QObject, public Singleton<OverviewCache> {
7294

7395
void overviewChanged(TrackId);
7496

97+
/// Emitted on every analyzer progress update for `trackId`,
98+
/// including partial progress while analysis is in flight.
99+
/// Connect to this signal to repaint waveform overview
100+
/// visualizations during analysis (the `Track` itself only emits
101+
/// `waveformSummaryUpdated` once at the start and once at the
102+
/// end of analysis, so it can't drive progressive drawing on its
103+
/// own).
104+
void analyzerProgress(TrackId trackId, AnalyzerProgress analyzerProgress);
105+
75106
void waveformSummaryReady(
76107
const QObject* pRequester,
77108
TrackId trackId,
@@ -102,6 +133,11 @@ class OverviewCache : public QObject, public Singleton<OverviewCache> {
102133
UserSettingsPointer m_pConfig;
103134
mixxx::DbConnectionPoolPtr m_pDbConnectionPool;
104135

136+
/// Borrowed pointer to the library's `TrackDAO` (lives on the GUI
137+
/// thread, owned by `TrackCollection`). Used to resolve track file
138+
/// locations to `TrackId`s in `requestWaveformSummary(QString)`.
139+
TrackDAO* m_pTrackDAO = nullptr;
140+
105141
QSet<TrackId> m_currentlyLoading;
106142
QSet<TrackId> m_currentlyLoadingWaveform;
107143
QSet<TrackId> m_tracksWithoutOverview;

0 commit comments

Comments
 (0)