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