1 #include "library/analysisfeature.h"
2
3 #include <QtDebug>
4
5 #include "controllers/keyboard/keyboardeventfilter.h"
6 #include "library/dlganalysis.h"
7 #include "library/library.h"
8 #include "library/librarytablemodel.h"
9 #include "library/trackcollection.h"
10 #include "moc_analysisfeature.cpp"
11 #include "sources/soundsourceproxy.h"
12 #include "util/debug.h"
13 #include "util/dnd.h"
14 #include "util/logger.h"
15 #include "widget/wlibrary.h"
16
17 namespace {
18
19 const mixxx::Logger kLogger("AnalysisFeature");
20
21 const QString kViewName = QStringLiteral("Analysis");
22
23 // Utilize all available cores for batch analysis of tracks
24 const int kNumberOfAnalyzerThreads = math_max(1, QThread::idealThreadCount());
25
26 inline
numberOfAnalyzerThreads()27 int numberOfAnalyzerThreads() {
28 return kNumberOfAnalyzerThreads;
29 }
30
31 inline
getAnalyzerModeFlags(const UserSettingsPointer & pConfig)32 AnalyzerModeFlags getAnalyzerModeFlags(
33 const UserSettingsPointer& pConfig) {
34 // Always enable at least BPM detection for batch analysis, even if disabled
35 // in the config for ad-hoc analysis of tracks.
36 // NOTE(uklotzde, 2018-12-26): The previous comment just states the status-quo
37 // of the existing code. We should rethink the configuration of analyzers when
38 // refactoring/redesigning the analyzer framework.
39 int modeFlags = AnalyzerModeFlags::WithBeats | AnalyzerModeFlags::LowPriority;
40 if (pConfig->getValue<bool>(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"), true)) {
41 modeFlags |= AnalyzerModeFlags::WithWaveform;
42 }
43 return static_cast<AnalyzerModeFlags>(modeFlags);
44 }
45
46 } // anonymous namespace
47
AnalysisFeature(Library * pLibrary,UserSettingsPointer pConfig)48 AnalysisFeature::AnalysisFeature(
49 Library* pLibrary,
50 UserSettingsPointer pConfig)
51 : LibraryFeature(pLibrary, pConfig),
52 m_baseTitle(tr("Analyze")),
53 m_icon(":/images/library/ic_library_prepare.svg"),
54 m_pTrackAnalysisScheduler(TrackAnalysisScheduler::NullPointer()),
55 m_pAnalysisView(nullptr),
56 m_title(m_baseTitle) {
57 }
58
resetTitle()59 void AnalysisFeature::resetTitle() {
60 m_title = m_baseTitle;
61 emit featureIsLoading(this, false);
62 }
63
setTitleProgress(int currentTrackNumber,int totalTracksCount)64 void AnalysisFeature::setTitleProgress(int currentTrackNumber, int totalTracksCount) {
65 m_title = QString("%1 (%2 / %3)")
66 .arg(m_baseTitle,
67 QString::number(currentTrackNumber),
68 QString::number(totalTracksCount));
69 emit featureIsLoading(this, false);
70 }
71
bindLibraryWidget(WLibrary * libraryWidget,KeyboardEventFilter * keyboard)72 void AnalysisFeature::bindLibraryWidget(WLibrary* libraryWidget,
73 KeyboardEventFilter* keyboard) {
74 m_pAnalysisView = new DlgAnalysis(libraryWidget,
75 m_pConfig,
76 m_pLibrary);
77 connect(m_pAnalysisView,
78 &DlgAnalysis::loadTrack,
79 this,
80 &AnalysisFeature::loadTrack);
81 connect(m_pAnalysisView,
82 &DlgAnalysis::loadTrackToPlayer,
83 this,
84 [=](TrackPointer track, const QString& group) {
85 emit loadTrackToPlayer(track, group, false);
86 });
87 connect(m_pAnalysisView,
88 &DlgAnalysis::analyzeTracks,
89 this,
90 &AnalysisFeature::analyzeTracks);
91 connect(m_pAnalysisView,
92 &DlgAnalysis::stopAnalysis,
93 this,
94 &AnalysisFeature::stopAnalysis);
95
96 connect(m_pAnalysisView,
97 &DlgAnalysis::trackSelected,
98 this,
99 &AnalysisFeature::trackSelected);
100
101 connect(this,
102 &AnalysisFeature::analysisActive,
103 m_pAnalysisView,
104 &DlgAnalysis::slotAnalysisActive);
105
106 m_pAnalysisView->installEventFilter(keyboard);
107
108 // Let the DlgAnalysis know whether or not analysis is active.
109 emit analysisActive(static_cast<bool>(m_pTrackAnalysisScheduler));
110
111 libraryWidget->registerView(kViewName, m_pAnalysisView);
112 }
113
getChildModel()114 TreeItemModel* AnalysisFeature::getChildModel() {
115 return &m_childModel;
116 }
117
refreshLibraryModels()118 void AnalysisFeature::refreshLibraryModels() {
119 if (m_pAnalysisView) {
120 m_pAnalysisView->onShow();
121 }
122 }
123
activate()124 void AnalysisFeature::activate() {
125 //qDebug() << "AnalysisFeature::activate()";
126 emit switchToView(kViewName);
127 if (m_pAnalysisView) {
128 emit restoreSearch(m_pAnalysisView->currentSearch());
129 }
130 emit enableCoverArtDisplay(true);
131 }
132
analyzeTracks(const QList<TrackId> & trackIds)133 void AnalysisFeature::analyzeTracks(const QList<TrackId>& trackIds) {
134 if (!m_pTrackAnalysisScheduler) {
135 const int numAnalyzerThreads = numberOfAnalyzerThreads();
136 kLogger.info()
137 << "Starting analysis using"
138 << numAnalyzerThreads
139 << "analyzer threads";
140 m_pTrackAnalysisScheduler = TrackAnalysisScheduler::createInstance(
141 m_pLibrary,
142 numAnalyzerThreads,
143 m_pConfig,
144 getAnalyzerModeFlags(m_pConfig));
145
146 connect(m_pTrackAnalysisScheduler.get(),
147 &TrackAnalysisScheduler::progress,
148 m_pAnalysisView,
149 &DlgAnalysis::onTrackAnalysisSchedulerProgress);
150 connect(m_pTrackAnalysisScheduler.get(),
151 &TrackAnalysisScheduler::finished,
152 m_pAnalysisView,
153 &DlgAnalysis::onTrackAnalysisSchedulerFinished);
154 connect(m_pTrackAnalysisScheduler.get(),
155 &TrackAnalysisScheduler::progress,
156 this,
157 &AnalysisFeature::onTrackAnalysisSchedulerProgress);
158 connect(m_pTrackAnalysisScheduler.get(),
159 &TrackAnalysisScheduler::finished,
160 this,
161 &AnalysisFeature::onTrackAnalysisSchedulerFinished);
162
163 emit analysisActive(true);
164 }
165
166 if (m_pTrackAnalysisScheduler->scheduleTracksById(trackIds) > 0) {
167 resumeAnalysis();
168 }
169 }
170
suspendAnalysis()171 void AnalysisFeature::suspendAnalysis() {
172 if (!m_pTrackAnalysisScheduler) {
173 return; // inactive
174 }
175 kLogger.info() << "Suspending analysis";
176 m_pTrackAnalysisScheduler->suspend();
177 }
178
resumeAnalysis()179 void AnalysisFeature::resumeAnalysis() {
180 if (!m_pTrackAnalysisScheduler) {
181 return; // inactive
182 }
183 kLogger.info() << "Resuming analysis";
184 m_pTrackAnalysisScheduler->resume();
185 }
186
stopAnalysis()187 void AnalysisFeature::stopAnalysis() {
188 if (!m_pTrackAnalysisScheduler) {
189 return; // inactive
190 }
191 kLogger.info() << "Stopping analysis";
192 m_pTrackAnalysisScheduler->stop();
193 }
194
onTrackAnalysisSchedulerProgress(AnalyzerProgress,int currentTrackNumber,int totalTracksCount)195 void AnalysisFeature::onTrackAnalysisSchedulerProgress(
196 AnalyzerProgress /*currentTrackProgress*/,
197 int currentTrackNumber,
198 int totalTracksCount) {
199 // Ignore any delayed progress updates after the analysis
200 // has already been stopped.
201 if (!m_pTrackAnalysisScheduler) {
202 return; // inactive
203 }
204 if (totalTracksCount > 0) {
205 setTitleProgress(currentTrackNumber, totalTracksCount);
206 } else {
207 resetTitle();
208 }
209 }
210
onTrackAnalysisSchedulerFinished()211 void AnalysisFeature::onTrackAnalysisSchedulerFinished() {
212 if (!m_pTrackAnalysisScheduler) {
213 return; // already inactive
214 }
215 kLogger.info() << "Finishing analysis";
216 if (m_pTrackAnalysisScheduler) {
217 // Free resources by abandoning the queue after the batch analysis
218 // has completed. Batch analysis are not started very frequently
219 // during a session and should be avoided while performing live.
220 // If the user decides to start a new batch analysis the setup costs
221 // for creating the queue with its worker threads are acceptable.
222 m_pTrackAnalysisScheduler.reset();
223 }
224 resetTitle();
225 emit analysisActive(false);
226 }
227
dropAccept(const QList<QUrl> & urls,QObject * pSource)228 bool AnalysisFeature::dropAccept(const QList<QUrl>& urls, QObject* pSource) {
229 QList<TrackId> trackIds = m_pLibrary->trackCollection().resolveTrackIdsFromUrls(urls,
230 !pSource);
231 analyzeTracks(trackIds);
232 return trackIds.size() > 0;
233 }
234
dragMoveAccept(const QUrl & url)235 bool AnalysisFeature::dragMoveAccept(const QUrl& url) {
236 return SoundSourceProxy::isUrlSupported(url);
237 }
238