-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Developer Guide Analysers
Mixxx analyses tracks in the background to detect their BPM, key, and
waveform data, and to measure ReplayGain and detect silence. This page
describes the analyser pipeline, the beat and key detector plug-in
interfaces, and the Beats data types that store results.
All analysers implement the same simple streaming interface:
class Analyzer {
public:
// Returns true if this track needs analysis; performs any
// initialization and returns false to skip.
virtual bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) = 0;
// Feed the next chunk of interleaved audio samples.
// Return false to abort early.
virtual bool processSamples(const CSAMPLE* pIn, SINT count) = 0;
// Commit results to the Track object after all samples have been fed.
virtual void storeResults(TrackPointer pTrack) = 0;
// Release any temporary memory regardless of success or failure.
virtual void cleanup() = 0;
};AnalyzerWithState is a thin RAII wrapper that tracks whether
initialize() returned true and calls cleanup() in the destructor if
the analyzer is still active.
| Class | What it computes |
|---|---|
AnalyzerBeats |
Beat positions (beat map or constant BPM grid) |
AnalyzerKey |
Musical key and key-change timeline |
AnalyzerWaveform |
Summary waveform data (stored in AnalysisDao) |
AnalyzerGain |
ReplayGain (EBU R128 integrated loudness via AnalyzerEbur128) |
AnalyzerSilence |
Intro/outro end-of-track silence detection |
TrackAnalysisScheduler is the public interface for the analysis
subsystem. Callers submit tracks via scheduleTrack() then call
resume(). The scheduler distributes tracks to a pool of
AnalyzerThread worker threads.
Library / PlayerManager
│
▼
TrackAnalysisScheduler
├── AnalyzerThread 0
├── AnalyzerThread 1
└── AnalyzerThread N (numWorkerThreads = nproc / 2, by default)
Each AnalyzerThread:
- Decodes the full audio of its assigned track via its own
SoundSourceinstance (independent of the playback caching reader). - Feeds decoded samples to each active
Analyzerin sequence viaprocessSamples(). - Calls
storeResults()on each analyser to commit the results to theTrackobject. - Saves the
Trackback to the database. - Emits
trackProgress()andfinished()signals back to the main thread.
The scheduler can be suspend()ed and resume()d (e.g. when playback
starts and CPU is needed) and stop()ped to discard the queue.
AnalyzerBeats delegates actual detection to an
AnalyzerBeatsPlugin. The plugin is selected by the user in
preferences (Preferences → Waveforms → Beat Detection). The available
plugins are discovered via AnalyzerBeats::availablePlugins().
Key preferences that control analysis behavior:
-
Re-analyze (
m_bPreferencesReanalyzeOldBpm) — whether to overwrite existing beat data. - Re-analyze imported — whether to re-analyze tracks that have BPM data imported from an external library.
-
Fixed tempo (
m_bPreferencesFixedTempo) — if true, the plugin is asked to produce a constant-BPM beat grid even if it detects tempo variation. - Fast analysis — use a faster but less accurate algorithm.
After all samples are fed, storeResults() calls
m_pPlugin->getBeats() to retrieve the detected beat frame positions
and stores them on the track.
Beat detection plugins live in
src/analyzer/plugins/
and implement AnalyzerBeatsPlugin:
class AnalyzerBeatsPlugin : public AnalyzerPlugin {
public:
virtual bool supportsBeatTracking() const = 0;
// Returns the overall average BPM (if fixed-tempo mode was used):
virtual mixxx::Bpm getBpm() const { return {}; }
// Returns the raw beat frame positions (variable-tempo mode):
virtual QVector<mixxx::audio::FramePos> getBeats() const { return {}; }
};src/analyzer/plugins/analyzerqueenmarybeats.h
Wraps the Queen Mary University of London beat tracker (from the Vamp
plug-in SDK). Performs onset detection followed by probabilistic beat
tracking. Returns a QVector<FramePos> of individual beat positions;
this produces a beat map (variable tempo). Identified by plugin id
"net.vamp-plugins.qm-vamp-plugins".
src/analyzer/plugins/analyzersoundtouchbeats.h
Uses the SoundTouch BPM detector. Returns only a single average BPM
value (not individual beat positions), so it always produces a
constant beat grid. Faster and lower memory than QueenMary, but
cannot model tempo variation.
AnalyzerKey delegates to an AnalyzerKeyPlugin. After analysis it
calls getKeyChanges() to obtain a KeyChangeList — a list of
(FramePos, ChromaticKey) pairs — and stores the dominant key and the
full change timeline on the track.
class AnalyzerKeyPlugin : public AnalyzerPlugin {
public:
virtual KeyChangeList getKeyChanges() const = 0;
};src/analyzer/plugins/analyzerqueenmarykey.h
Uses the QM key detector Vamp plugin. Returns a list of key changes over time, allowing Mixxx to display when a track modulates key.
src/analyzer/plugins/analyzerkeyfinder.h
Wraps the KeyFinder library. Generally considered more accurate than the QM key detector for modern music. Returns a single dominant key.
Beat data is stored on a Track object as a mixxx::Beats shared
pointer (TrackPointer → track->getBeats()).
mixxx::Beats is an immutable value type (created via factory
functions, modified by producing a new instance). It can represent
either a constant-tempo beat grid or a variable-tempo beat map:
A beat grid is fully described by a single BPM value and the position of one anchor beat. All other beat positions are derived by interpolation. Beat grids are compact to store and fast to query — any beat position can be computed in O(1).
Internally represented as: bpm + firstBeatPosition. Created when the
beat detector returns only a BPM value (SoundTouch mode, or when the
user explicitly sets a fixed BPM).
A beat map stores the explicit frame position of every detected beat. Queries like "find the nearest beat before frame N" require a binary search, but can represent tracks with tempo variation, live recordings, or imperfect metronomic timing.
Internally represented as a sorted QVector<FramePos>. Created when
QueenMary beat tracking returns individual beat positions.
Both representations expose the same Beats interface:
class Beats {
public:
// Frame position of the nearest beat at or after `position`.
std::optional<FramePos> nextBeat(FramePos position) const;
// Frame position of the nearest beat at or before `position`.
std::optional<FramePos> previousBeat(FramePos position) const;
// Frame position of the Nth beat relative to `position`.
// N=1 means the next beat, N=-1 means the previous beat.
std::optional<FramePos> nthBeat(FramePos position, int n) const;
// Fractional beat distance in [0.0, 1.0) from the previous beat
// to `position`.
double getBeatDistance(FramePos position) const;
// Average BPM over a range, or the full track if no range given:
std::optional<mixxx::Bpm> getBpm() const;
std::optional<mixxx::Bpm> getBpmInRange(
FramePos startPosition, FramePos endPosition) const;
// Iterate over all beats in a frame range:
BeatsIterator iterateFrom(FramePos startPosition) const;
};Beats are immutable. To translate, scale, or add/remove individual
beats you call factory methods that return a new BeatsPointer:
BeatsPointer translated(FrameDiff_t offset) const;
BeatsPointer scaled(Bpm newBpm) const;
BeatsPointer withoutLastBeat() const;
BeatsPointer withoutBeatAtPosition(FramePos position) const;
BeatsPointer withBeatAtPosition(FramePos position) const;Calling track->setBeats(newBeats) marks the track as dirty and
schedules a database save.
Mixxx is a free and open-source DJ software.
Manual
Hardware Compatibility
Reporting Bugs
Getting Involved
Contribution Guidelines
Coding Guidelines
Using Git
Developer Guide
Creating Skins
Contributing Mappings
Mixxx Controls
MIDI Scripting
Components JS
HID Scripting