1 #pragma once
2 
3 #include <vector>
4 
5 #include "analyzer/analyzer.h"
6 #include "analyzer/analyzerprogress.h"
7 #include "preferences/usersettings.h"
8 #include "rigtorp/SPSCQueue.h"
9 #include "sources/audiosource.h"
10 #include "track/track_decl.h"
11 #include "track/trackid.h"
12 #include "util/db/dbconnectionpool.h"
13 #include "util/memory.h"
14 #include "util/performancetimer.h"
15 #include "util/samplebuffer.h"
16 #include "util/workerthread.h"
17 
18 enum AnalyzerModeFlags {
19     None = 0x00,
20     WithBeats = 0x01,
21     WithWaveform = 0x02,
22     LowPriority = 0x04,
23     All = WithBeats | WithWaveform,
24 };
25 
26 enum class AnalyzerThreadState {
27     Void,
28     Idle,
29     Busy,
30     Done,
31     Exit,
32 };
33 
34 Q_DECLARE_TYPEINFO(AnalyzerThreadState, Q_MOVABLE_TYPE);
35 Q_DECLARE_METATYPE(AnalyzerThreadState);
36 
37 // Atomic control values are used for transferring data between the
38 // host and the worker thread, e.g. the next track to be analyzed or
39 // the current analyzer progress that can be read independent of any
40 // progress signal
41 //
42 // The frequency of progress signal is limited to avoid flooding the
43 // signal queued connection between the internal worker thread and
44 // the host, which might otherwise cause unresponsiveness of the host.
45 class AnalyzerThread : public WorkerThread {
46     Q_OBJECT
47 
48   public:
49     typedef std::unique_ptr<AnalyzerThread, void (*)(AnalyzerThread*)> Pointer;
50     // Subclass that provides a default constructor and nothing else
51     class NullPointer : public Pointer {
52       public:
53         NullPointer();
54     };
55 
56     static Pointer createInstance(
57             int id,
58             mixxx::DbConnectionPoolPtr dbConnectionPool,
59             UserSettingsPointer pConfig,
60             AnalyzerModeFlags modeFlags);
61 
62     /*private*/ AnalyzerThread(
63             int id,
64             mixxx::DbConnectionPoolPtr dbConnectionPool,
65             UserSettingsPointer pConfig,
66             AnalyzerModeFlags modeFlags);
67     ~AnalyzerThread() override = default;
68 
id()69     int id() const {
70         return m_id;
71     }
72 
73     // Submits the next track to the worker thread without
74     // blocking. This is only allowed after a progress() signal
75     // with state Idle has been received to avoid overwriting
76     // a previously sent track that has not been received by the
77     // worker thread, yet.
78     bool submitNextTrack(TrackPointer nextTrack);
79 
80   signals:
81     // Use a single signal for progress updates to ensure that all signals
82     // are queued and received in the same order as emitted from the internal
83     // worker thread. Different signals would otherwise end up in different
84     // queued connections which are processed in an undefined order!
85     // TODO(uklotzde): Encapsulate all signal parameters into an
86     // AnalyzerThreadProgress object and register it as a new meta type.
87     void progress(int threadId, AnalyzerThreadState threadState, TrackId trackId, AnalyzerProgress trackProgress);
88 
89   protected:
90     void doRun() override;
91 
92     TryFetchWorkItemsResult tryFetchWorkItems() override;
93 
94   private:
95     /////////////////////////////////////////////////////////////////////////
96     // Immutable values and pointers (objects are thread-safe)
97     const int m_id;
98     const mixxx::DbConnectionPoolPtr m_dbConnectionPool;
99     const UserSettingsPointer m_pConfig;
100     const AnalyzerModeFlags m_modeFlags;
101 
102     /////////////////////////////////////////////////////////////////////////
103     // Thread-safe atomic values
104 
105     // There is only one consumer (namely the worker thread) and one producer
106     // (the host thread) for this value. A single value is written and then
107     // read so a lock-free FIFO with the minimum capacity is sufficient for
108     // safely exchanging data between two threads.
109     // NOTE(uklotzde, 2018-01-04): Ideally we would use std::atomic<TrackPointer>,
110     // for this purpose, which will become available in C++20.
111     rigtorp::SPSCQueue<TrackPointer> m_nextTrack;
112 
113     /////////////////////////////////////////////////////////////////////////
114     // Thread local: Only used in the constructor/destructor and within
115     // run() by the worker thread.
116 
117     std::vector<AnalyzerWithState> m_analyzers;
118 
119     mixxx::SampleBuffer m_sampleBuffer;
120 
121     TrackPointer m_currentTrack;
122 
123     AnalyzerThreadState m_emittedState;
124 
125     PerformanceTimer m_lastBusyProgressEmittedTimer;
126 
127     enum class AnalysisResult {
128         Pending,
129         Finished,
130         Cancelled,
131     };
132     AnalysisResult analyzeAudioSource(
133             const mixxx::AudioSourcePointer& audioSource);
134 
135     // Blocks the worker thread until a next track becomes available
136     TrackPointer receiveNextTrack();
137 
138     // Conditionally emit a progress() signal while busy (frequency is limited)
139     void emitBusyProgress(AnalyzerProgress busyProgress);
140 
141     // Unconditionally emits a progress() signal when done
142     void emitDoneProgress(AnalyzerProgress doneProgress);
143 
144     // Unconditionally emits any kind of progress() signal if not current track is present
145     void emitProgress(AnalyzerThreadState state);
146 
147     // Unconditionally emits any kind of progress() signal
148     void emitProgress(AnalyzerThreadState state, TrackId trackId, AnalyzerProgress trackProgress);
149 };
150