Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multithreaded analysis #1069

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c4aab97
Initial implementation of multithreaded analysis
JosepMaJAZ Dec 3, 2016
4a2a142
Fixed end detection and updated signals to show multiple thread progress
JosepMaJAZ Dec 3, 2016
c06ab24
bugfix: one division too much
JosepMaJAZ Dec 3, 2016
3c5c680
Inform the UI to update when stopping the manager
JosepMaJAZ Dec 3, 2016
90882d8
ups. forgot to change this when fixing the division
JosepMaJAZ Dec 3, 2016
0d28354
Fix abatch waveform analysis
JosepMaJAZ Dec 4, 2016
8b450dc
documentation of methods
JosepMaJAZ Dec 10, 2016
b581f88
Preferences option to set the max number of threads for analysis
JosepMaJAZ Dec 11, 2016
711a3f9
build fixes and changes from PR https://github.com/mixxxdj/mixxx/pull…
JosepMaJAZ Dec 11, 2016
569493e
second round of fixes
JosepMaJAZ Dec 11, 2016
b478ecc
renaming of background/foreground/batch to default/priority/force, ad…
JosepMaJAZ Dec 11, 2016
4bf1152
bugfixes related to changing maxthreads
JosepMaJAZ Dec 15, 2016
1613fb6
build fix, oups!
JosepMaJAZ Dec 16, 2016
6326a69
Merge branch 'master' into multithreaded-analysis
JosepMaJAZ Jan 18, 2017
0b06821
Merge branch 'master' into multithreaded-analysis
JosepMaJAZ Jan 21, 2017
fe99a7d
Merge branch 'master' into multithreaded-analysis
JosepMaJAZ Jan 21, 2017
cfb5542
leave one idle thread whenever there's a priority thread running. Thi…
JosepMaJAZ Jan 29, 2017
5410b84
merging to master
JosepMaJAZ Jan 29, 2017
bb09bb7
corrected VAMP_PATH multi-initialization. Corrected trackfinished cal…
JosepMaJAZ Jan 29, 2017
8a0e695
restart the paused default worker once all the priority workers finish
JosepMaJAZ Feb 12, 2017
2d7ab48
merge from master and bugfix on library.cpp (ouch! how did this work?…
JosepMaJAZ Apr 8, 2017
0d6cd9a
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Apr 9, 2017
39dc961
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ May 10, 2017
a6a7994
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ May 27, 2017
645648d
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Jun 2, 2017
c6f138f
merge with current master
JosepMaJAZ Sep 9, 2017
24ba937
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Sep 30, 2017
82efeea
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Oct 14, 2017
982ce5a
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Nov 19, 2017
7b09641
Several fixes and retouches. I believe this fixes the rare crashes th…
JosepMaJAZ Nov 19, 2017
a1c8aa1
linux compilation fixes plus removed calculation of threads from the …
JosepMaJAZ Nov 23, 2017
9fe94ff
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Nov 24, 2017
614d40f
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Dec 6, 2017
6da72ef
changes for sourceaudioapi v2 compatibility
JosepMaJAZ Dec 6, 2017
d010df7
merge remote branch and fix conflict
JosepMaJAZ Dec 14, 2017
2731050
Merge remote-tracking branch 'origin/master' into multithreaded-analysis
JosepMaJAZ Dec 21, 2017
a467944
build fix
JosepMaJAZ Dec 21, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,10 @@ def enabled_modules(build):
def enabled_imageformats(build):
qt5 = Qt.qt5_enabled(build)
qt_imageformats = [
'qgif', 'qico', 'qjpeg', 'qmng', 'qtga', 'qtiff', 'qsvg'
'qgif', 'qico', 'qjpeg', 'qtga', 'qtiff', 'qsvg'
]
if qt5:
qt_imageformats.extend(['qdds', 'qicns', 'qjp2', 'qwbmp', 'qwebp'])
qt_imageformats.extend(['qdds', 'qicns', 'qwbmp', 'qwebp'])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: These two changes were done for compatibility when building with QT5. If needed, they can be restored

return qt_imageformats

def satisfy(self):
Expand Down Expand Up @@ -737,7 +737,8 @@ def sources(self, build):
"engine/cachingreaderchunk.cpp",
"engine/cachingreaderworker.cpp",

"analyzer/analyzerqueue.cpp",
"analyzer/analyzermanager.cpp",
"analyzer/analyzerworker.cpp",
"analyzer/analyzerwaveform.cpp",
"analyzer/analyzergain.cpp",
"analyzer/analyzerebur128.cpp",
Expand Down
7 changes: 4 additions & 3 deletions src/analyzer/analyzerbeats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include "track/beatutils.h"
#include "track/track.h"

AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig)
AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool batch)
: m_pConfig(pConfig),
m_pVamp(NULL),
m_bPreferencesReanalyzeOldBpm(false),
Expand All @@ -27,7 +27,8 @@ AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig)
m_iSampleRate(0),
m_iTotalSamples(0),
m_iMinBpm(0),
m_iMaxBpm(9999) {
m_iMaxBpm(9999),
m_batch(batch) {
}

AnalyzerBeats::~AnalyzerBeats() {
Expand All @@ -38,7 +39,7 @@ bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSample
return false;
}

bool bPreferencesBeatDetectionEnabled = static_cast<bool>(
bool bPreferencesBeatDetectionEnabled = m_batch || static_cast<bool>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does "m_batch" overrides the preferences option?
Could it be renamed to "forceBeatDetectionEnabled"?

The name bPreferencesBeatDetectionEnabled does not fit anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know either why such option existed. I simply changed it to a constructor parameter rather than changing it on analysisfeature. I am open to talk about the need or meaning of it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok than I think m_batch should become m_forceBeatDetection and bPreferencesBeatDetectionEnabled should become beatDetectionEnabled.

m_pConfig->getValueString(
ConfigKey(BPM_CONFIG_KEY, BPM_DETECTION_ENABLED)).toInt());
if (!bPreferencesBeatDetectionEnabled) {
Expand Down
3 changes: 2 additions & 1 deletion src/analyzer/analyzerbeats.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class AnalyzerBeats: public Analyzer {
public:
AnalyzerBeats(UserSettingsPointer pConfig);
AnalyzerBeats(UserSettingsPointer pConfig, bool batch);
virtual ~AnalyzerBeats();

bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override;
Expand All @@ -40,6 +40,7 @@ class AnalyzerBeats: public Analyzer {

int m_iSampleRate, m_iTotalSamples;
int m_iMinBpm, m_iMaxBpm;
bool m_batch;
};

#endif /* ANALYZER_ANALYZERBEATS_H */
206 changes: 206 additions & 0 deletions src/analyzer/analyzermanager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#include "analyzer/analyzermanager.h"

#include <typeinfo>
#include <QThread>

#include <QtDebug>
#include <QMutexLocker>
#include <QListIterator>

#include "library/trackcollection.h"
#include "mixer/playerinfo.h"
#include "track/track.h"
#include "util/compatibility.h"
#include "util/event.h"
#include "util/timer.h"
#include "util/trace.h"

AnalyzerManager* AnalyzerManager::m_pAnalyzerManager = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the new nullprt keyword


//static
AnalyzerManager& AnalyzerManager::getInstance(UserSettingsPointer pConfig) {
if (!m_pAnalyzerManager) {
// There exists only one UserSettingsPointer in the app, so it doens't matter
// if we only assign it once.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand the comment.

It is untypical to pass a parameter in the getInstance() call.
We have decided not to have the user pointer as a singleton. Here it becomes one implicit.
I think the best here would be to either make UserSettingsPointer a singleton as well or remove the sigleton nature from AnalyzerManager;

Copy link
Contributor Author

@JosepMaJAZ JosepMaJAZ Dec 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation needs that there exists only one manager so that it can control the number of threads in execution. Since it is used from two different places (analysisfeature and playermanager), it cannot be an instance on those classes either.

Still, I think that I was wrong in using the getInstance() naming/model and should have been a MixxxMainWindow instance, where I have UserSettingsPointer.

I was busy enough trying to understand QThread and signals so I took the easiest path. Now I am open to change this if you give me the correct guidelines to do so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer this not become a singleton and instead pass it throughout to all the places it needs to go. Once #941 is in, there will be a better place for it in the Core class but for now it can go in the main window (src/mixxx.cpp).

m_pAnalyzerManager = new AnalyzerManager(pConfig);
}
int maxThreads = m_pAnalyzerManager->m_pConfig->getValue<int>(ConfigKey("[Library]", "MaxAnalysisThreads"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the use case, that some users (incuding me) have shared preferences on different machines.
I also think that this options exposes to much interns to the user. Is this option required at all?
Can't we just pick the ideal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the setting was supposed to be a safeguard, and still, there are more safeguards that should control your use case:

Concretely, giving the user the option to control the number of threads allows a way to reduce the usage of resources in case that the machine is not able to keep with the desired audio latency.
Also, the idealthreadcount should work on most places, but its API suggests that it can return -1 if it cannot detect it (i guess that's more for mobile platforms or some OS where the information cannot be retreived).

Finally, on the AnalyzerManager, there is a safety measure where it checks that the idealthreadcount is sane for the machine in use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid the average user does not even know what threads are. If we found out during testing that we have an performance issue, it is our job to tweak solve it.

Is there a use case to pick single thread analysis? If yes, a boolean option might be a better choice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't had any issue. I've tested it on an i7 (4 cores, 8 threads), a core 2 duo (2 cores, 2 threads), and a virtual machine with 2 cores assigned. None of them gave me issues when analyzing during playback using the idealthreadcount. On the virtual machine i needed to have a latency of 90ms, but that's a bug of virtualbox.
As for the user point of view, I've designed it in a way that the user can only reduce from the ideal thread count, so the only harm that he can cause is to take longer to analyze than what's needed (which is, in fact, the current situation).
In other words, if we get reports of users having more problems due to the multithreaded analysis, we have the option to tell them to reduce this setting until they are satisfied.

Copy link
Contributor

@Be-ing Be-ing Dec 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, single core CPUs are rather rare nowadays.

As for the user point of view, I've designed it in a way that the user can only reduce from the ideal thread count, so the only harm that he can cause is to take longer to analyze than what's needed (which is, in fact, the current situation).
In other words, if we get reports of users having more problems due to the multithreaded analysis, we have the option to tell them to reduce this setting until they are satisfied.

That makes sense. This could be useful, for example, if a user wants to analyze their library but also keep 1 CPU core unused by Mixxx for doing something else with their computer while Mixxx is analyzing.

if (maxThreads < 0 || maxThreads > 2 * QThread::idealThreadCount()) {
//Assume the value is incorrect, so fix it.
maxThreads = QThread::idealThreadCount();
m_pAnalyzerManager->m_pConfig->setValue<int>(ConfigKey("[Library]", "MaxAnalysisThreads"), maxThreads);
}
if (maxThreads == 0) {
maxThreads = QThread::idealThreadCount();
}
m_pAnalyzerManager->m_MaxThreads = maxThreads;
return *m_pAnalyzerManager;
}

AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) :
m_pConfig(pConfig),
m_batchTrackQueue(),
m_prioTrackQueue(),
m_backgroundWorkers(),
m_foregroundWorkers(),
m_pausedWorkers() {
}

AnalyzerManager::~AnalyzerManager() {
stop(true);
}

bool AnalyzerManager::isActive(bool includeForeground) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isActive(true) is hard to read at the caller.
how about add two calls? isActive() and isBackgroundWorkerActive()

//I don't count foregroundWorkers because
int total = (includeForeground ? m_foregroundWorkers.size() : 0) +
m_backgroundWorkers.size() + m_pausedWorkers.size();
return total > 0;
}

void AnalyzerManager::stop(bool shutdown) {
QListIterator<AnalyzerWorker*> it(m_backgroundWorkers);
while (it.hasNext()) {
AnalyzerWorker* worker = it.next();
worker->endProcess();
}
QListIterator<AnalyzerWorker*> it3(m_pausedWorkers);
while (it3.hasNext()) {
AnalyzerWorker* worker = it3.next();
worker->endProcess();
}
if (shutdown) {
QListIterator<AnalyzerWorker*> it2(m_foregroundWorkers);
while (it2.hasNext()) {
AnalyzerWorker* worker = it2.next();
worker->endProcess();
}
//TODO: ensure that they are all forcibly stopped.
}
}
// Analyze it with a foreground worker. (foreground as in interactive, i.e. not a batch worker).
void AnalyzerManager::analyseTrackNow(TrackPointer tio) {
if (m_batchTrackQueue.contains(tio)) {
m_batchTrackQueue.removeAll(tio);
}
if (!m_prioTrackQueue.contains(tio)) {
m_prioTrackQueue.append(tio);
if (m_foregroundWorkers.size() < m_MaxThreads) {
createNewWorker(false);
if (m_foregroundWorkers.size() + m_backgroundWorkers.size() > m_MaxThreads) {
AnalyzerWorker * backwork = m_backgroundWorkers.first();
backwork->pause();
//Ideally i would have done this on the slotPaused slot, but then i cannot
//ensure i won't call pause twice for the same worker.
m_pausedWorkers.append(backwork);
m_backgroundWorkers.removeAll(backwork);
}
}
}
}
// This is called from the GUI for batch analysis.
void AnalyzerManager::queueAnalyseTrack(TrackPointer tio) {
if (!m_batchTrackQueue.contains(tio)) {
m_batchTrackQueue.append(tio);
if (m_pausedWorkers.size() + m_backgroundWorkers.size() < m_MaxThreads) {
createNewWorker(true);
}
}
}

// This slot is called from the decks and samplers when the track is loaded.
void AnalyzerManager::slotAnalyseTrack(TrackPointer tio) {
analyseTrackNow(tio);
}


//slot
void AnalyzerManager::slotUpdateProgress(struct AnalyzerWorker::progress_info* progressInfo) {
if (progressInfo->current_track) {
progressInfo->current_track->setAnalyzerProgress(
progressInfo->track_progress);
if (progressInfo->track_progress == 1000) {
emit(trackDone(progressInfo->current_track));
}
progressInfo->current_track.clear();
}
emit(trackProgress(progressInfo->track_progress / 10));
if (progressInfo->track_progress == 1000) {
emit(trackFinished(m_backgroundWorkers.size() + m_batchTrackQueue.size() - 1));
}
progressInfo->sema.release();
}

void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) {
//The while loop is done in the event that the track which was added to the queue is no
//longer available.

//TODO: The old scan checked in isLoadedTrackWaiting for pTrack->getAnalyzerProgress()
// and either tried to load a previuos scan, or discarded the track if it had already been
// analyzed. I don't fully understand the scenario and I am not doing that right now.
TrackPointer track = TrackPointer();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

" = TrackPointer();" is redundant.

if (worker->isBatch()) {
while (!track && !m_batchTrackQueue.isEmpty()) {
track = m_batchTrackQueue.dequeue();
}
}
else {
while (!track && !m_prioTrackQueue.isEmpty()) {
track = m_prioTrackQueue.dequeue();
}
}
if (track) {
worker->nextTrack(track);
}
else {
worker->endProcess();
if (!m_pausedWorkers.isEmpty()) {
AnalyzerWorker* otherworker = m_pausedWorkers.first();
otherworker->resume();
m_pausedWorkers.removeOne(otherworker);
}
}
//Check if all queues are empty.
if (!isActive(false)) {
emit(queueEmpty());
}
}
void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) {
m_backgroundWorkers.removeAll(worker);
m_foregroundWorkers.removeAll(worker);
m_pausedWorkers.removeAll(worker);
}
void AnalyzerManager::slotPaused(AnalyzerWorker* worker) {
//No useful code to execute right now.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add Q_UNUSED(worker); to eliminate a warning

}
void AnalyzerManager::slotErrorString(QString errMsg) {
//TODO: This is currently unused.
qWarning() << "Testing with :" << errMsg;
}

AnalyzerWorker* AnalyzerManager::createNewWorker(bool batchJob) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this: bool priority

QThread* thread = new QThread();
AnalyzerWorker* worker = new AnalyzerWorker(m_pConfig, batchJob);
worker->moveToThread(thread);
//Auto startup and auto cleanup of worker and thread.
connect(thread, SIGNAL(started()), worker, SLOT(slotProcess()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
//Connect with manager.
connect(worker, SIGNAL(updateProgress(struct AnalyzerWorker::progress_info*)), this, SLOT(slotUpdateProgress(struct AnalyzerWorker::progress_info*)));
connect(worker, SIGNAL(waitingForNextTrack(AnalyzerWorker*)), this, SLOT(slotNextTrack(AnalyzerWorker*)));
connect(worker, SIGNAL(paused(AnalyzerWorker*)), this, SLOT(slotPaused(AnalyzerWorker*)));
connect(worker, SIGNAL(workerFinished(AnalyzerWorker*)), this, SLOT(slotWorkerFinished(AnalyzerWorker*)));
connect(worker, SIGNAL(error(QString)), this, SLOT(slotErrorString(QString)));
thread->start();
if (batchJob) {
m_backgroundWorkers.append(worker);
}
else {
m_foregroundWorkers.append(worker);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes a forground worker a "forground" worker?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is "priorityWorkers"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bad naming from my side, but I couldn't think of a better one at that time.
Strictly speaking, everything is run in background (i.e. the user does not need to wait for any of them to stop).
background, in this case, means that they are used for the job of the analysis queue (analysis feature of the library), while foreground means that they run an analysis by an immediate action of the user (just loaded a track).

Maybe I should have used worker and priorityworker. Take in mind that the priority is not the only difference, since there are some Mixxx settings that affect them in a different way. (namely the waveform creation and the bpm analysis)

}
return worker;
}



62 changes: 62 additions & 0 deletions src/analyzer/analyzermanager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#ifndef ANALYZER_ANALYZERMANAGER_H
#define ANALYZER_ANALYZERMANAGER_H

#include <QList>
#include <QQueue>

#include <vector>

#include "analyzer/analyzerworker.h"
#include "preferences/usersettings.h"
#include "track/track.h"

class TrackCollection;

class AnalyzerManager : public QObject {
Q_OBJECT

protected:
AnalyzerManager(UserSettingsPointer pConfig);

public:
static AnalyzerManager& getInstance(UserSettingsPointer pConfig);
virtual ~AnalyzerManager();

void stop(bool shutdown);
//This method might need to be protected an called only via slot.
void analyseTrackNow(TrackPointer tio);
void queueAnalyseTrack(TrackPointer tio);
bool isActive(bool includeForeground);


public slots:
// This slot is called from the decks and samplers when the track is loaded.
void slotAnalyseTrack(TrackPointer tio);
void slotUpdateProgress(struct AnalyzerWorker::progress_info*);
void slotNextTrack(AnalyzerWorker*);
void slotPaused(AnalyzerWorker*);
void slotWorkerFinished(AnalyzerWorker*);
void slotErrorString(QString);

signals:
void trackProgress(int progress);
void trackDone(TrackPointer track);
void trackFinished(int size);
void queueEmpty();
private:

AnalyzerWorker* createNewWorker(bool batchJob);

static AnalyzerManager* m_pAnalyzerManager;
UserSettingsPointer m_pConfig;
int m_MaxThreads;
// The processing queue and associated mutex
QQueue<TrackPointer> m_batchTrackQueue;
QQueue<TrackPointer> m_prioTrackQueue;

QList<AnalyzerWorker*> m_backgroundWorkers;
QList<AnalyzerWorker*> m_foregroundWorkers;
QList<AnalyzerWorker*> m_pausedWorkers;
};

#endif /* ANALYZER_ANALYZERMANAGER_H */
10 changes: 4 additions & 6 deletions src/analyzer/analyzerqueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,12 +439,12 @@ AnalyzerQueue* AnalyzerQueue::createDefaultAnalyzerQueue(
UserSettingsPointer pConfig, TrackCollection* pTrackCollection) {
AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection);

ret->addAnalyzer(new AnalyzerWaveform(pConfig));
ret->addAnalyzer(new AnalyzerWaveform(pConfig, false));
ret->addAnalyzer(new AnalyzerGain(pConfig));
ret->addAnalyzer(new AnalyzerEbur128(pConfig));
#ifdef __VAMP__
VampAnalyzer::initializePluginPaths();
ret->addAnalyzer(new AnalyzerBeats(pConfig));
ret->addAnalyzer(new AnalyzerBeats(pConfig, false));
ret->addAnalyzer(new AnalyzerKey(pConfig));
#endif

Expand All @@ -457,14 +457,12 @@ AnalyzerQueue* AnalyzerQueue::createAnalysisFeatureAnalyzerQueue(
UserSettingsPointer pConfig, TrackCollection* pTrackCollection) {
AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection);

if (pConfig->getValue<bool>(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) {
ret->addAnalyzer(new AnalyzerWaveform(pConfig));
}
ret->addAnalyzer(new AnalyzerWaveform(pConfig, true));
ret->addAnalyzer(new AnalyzerGain(pConfig));
ret->addAnalyzer(new AnalyzerEbur128(pConfig));
#ifdef __VAMP__
VampAnalyzer::initializePluginPaths();
ret->addAnalyzer(new AnalyzerBeats(pConfig));
ret->addAnalyzer(new AnalyzerBeats(pConfig, true));
ret->addAnalyzer(new AnalyzerKey(pConfig));
#endif

Expand Down
8 changes: 7 additions & 1 deletion src/analyzer/analyzerwaveform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
#include "track/track.h"
#include "waveform/waveformfactory.h"

AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig) :
AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig, bool batch) :
m_skipProcessing(false),
m_batch(batch),
m_pConfig(pConfig),
m_waveformData(nullptr),
m_waveformSummaryData(nullptr),
m_stride(0, 0),
Expand Down Expand Up @@ -104,6 +106,10 @@ bool AnalyzerWaveform::initialize(TrackPointer tio, int sampleRate, int totalSam
}

bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const {
if (m_batch && m_pConfig->getValue<bool>(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) {
return true;
}

ConstWaveformPointer pTrackWaveform = tio->getWaveform();
ConstWaveformPointer pTrackWaveformSummary = tio->getWaveformSummary();
ConstWaveformPointer pLoadedTrackWaveform;
Expand Down
Loading