Skip to content

Developer Guide Analysers

Owen Williams edited this page Feb 22, 2026 · 1 revision

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.

The Analyzer Interface

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.

Concrete Analyzers

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 and AnalyzerThread

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:

  1. Decodes the full audio of its assigned track via its own SoundSource instance (independent of the playback caching reader).
  2. Feeds decoded samples to each active Analyzer in sequence via processSamples().
  3. Calls storeResults() on each analyser to commit the results to the Track object.
  4. Saves the Track back to the database.
  5. Emits trackProgress() and finished() 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.

Beat Analysis

AnalyzerBeats

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

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 {}; }
};

AnalyzerQueenMaryBeats

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".

AnalyzerSoundTouchBeats

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.

Key Analysis

AnalyzerKey

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.

Key Detection Plugins

class AnalyzerKeyPlugin : public AnalyzerPlugin {
  public:
    virtual KeyChangeList getKeyChanges() const = 0;
};

AnalyzerQueenMaryKey

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.

AnalyzerKeyFinder

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 Grid and Beat Map Types

Beat data is stored on a Track object as a mixxx::Beats shared pointer (TrackPointertrack->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:

Constant Beat Grid

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).

Beat Map

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.

Key Beats API

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;
};

Modifying Beats

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.

Clone this wiki locally