Skip to content

Fix QML crash on stem track load: null WaveformWidgetFactory - #17015

Open
nomiapps wants to merge 1 commit into
mixxxdj:mainfrom
nomiapps:fix-qml-stem-crash-main-v2
Open

Fix QML crash on stem track load: null WaveformWidgetFactory#17015
nomiapps wants to merge 1 commit into
mixxxdj:mainfrom
nomiapps:fix-qml-stem-crash-main-v2

Conversation

@nomiapps

@nomiapps nomiapps commented Sep 5, 2026

Copy link
Copy Markdown

Fixes #17011.

What happens

Loading any stem file in the QML UI (--qml / --new-ui) kills Mixxx. On a build with
MIXXX_DEBUG_ASSERTIONS_FATAL it terminates on the assert @Eve00000 captured in #17011:

DEBUG ASSERT: "m_instance" in function Singleton<WaveformWidgetFactory>::instance()
at src/util/singleton.h:22

with the stack, on the scene graph render thread:

allshader_sg::WaveformRenderBeat::preprocess+12
allshader_sg::WaveformRenderBeat::preprocessInner+179

On a release build there is no assert, so instance() returns nullptr, the very next
dereference runs, and it is an access violation in the same function instead.

Why

WaveformWidgetFactory::createInstance() has exactly one call site in the tree,
src/mixxxmainwindow.cpp, which is the QWidget main window. The QML UI never constructs
it, so the singleton is null for the whole session.

WaveformRenderBeat::preprocessInner() and WaveformRendererSlipMode::preprocessInner()
both do:

const bool splitStemTracks = isStemTrack &&
        WaveformWidgetFactory::instance()->isStemSplitTracks();

The dereference sits behind the isStemTrack && short-circuit, which is why the QML UI
looked healthy until the first stem track — plain tracks never evaluate the right-hand
side.

The fix

Resolve the factory once at construction, and null-check it at the use site.

The construction-time lookup asks isCreated() instead of calling instance() directly.
That matters: Singleton::instance() runs a VERIFY_OR_DEBUG_ASSERT(m_instance) of its
own, so merely asking for the pointer is fatal under MIXXX_DEBUG_ASSERTIONS_FATAL and
logs Singleton class has not been created yet, returning nullptr otherwise — once per
renderer, per session. dlgpreferences.cpp already guards the same singleton this way.

With the factory absent, splitStemTracks is false and the renderers draw the unsplit
beat grid — the behaviour the QML UI had before stem support existed. Nothing changes for
the QWidget UI, where the factory always exists.

Testing

Windows, RelWithDebInfo, -DQML=ON, mixxx --new-ui --developer with a .stem.mp4
passed on the command line so it loads straight into deck 1.

  • Before: two Singleton class has not been created yet, returning nullptr warnings on
    the render thread; on a fatal-assert build this is QML Latenight crashes on loading a stemfile (Win & Linux) #17011's crash.
  • After: stem decodes (SoundSourceSTEM / Track - Importing stem(s) info), waveform
    renderers come up for both decks, zero singleton warnings, no assert, still running
    after 40s.

Note for reviewers

In the QML UI this value is computed and then never used -- splitStemTracks only feeds the
slip beat renderer, which QML never builds (its beat renderer is fixed at
PositionSource::Play). That is why the crash surfaced on a plain non-slip renderer with no
interest in the answer.

An alternative would be to compute splitStemTracks inside the m_isSlipRenderer branch that
consumes it, removing the factory access by construction rather than by null check. Happy to
respin it that way if you prefer; as it stands the guard is the smaller, more backportable
change.

🤖 Generated with Claude Code

WaveformWidgetFactory::createInstance() has exactly one call site,
mixxxmainwindow.cpp, so under --qml / --new-ui the singleton is never
created. The allshader beat and slip-mode renderers dereference it in
preprocessInner() to read isStemSplitTracks(), but only behind an
isStemTrack && short-circuit -- so plain tracks were fine and loading
any stem file crashed on the scene graph render thread.

Resolve the factory once at construction and null-check it at the use
site. Ask isCreated() rather than calling instance() directly:
Singleton::instance() trips a VERIFY_OR_DEBUG_ASSERT of its own when
the singleton was never created, which terminates builds configured
with MIXXX_DEBUG_ASSERTIONS_FATAL and otherwise logs a warning for
every renderer. With the factory absent, splitStemTracks is false and
the renderers draw the unsplit beat grid, which is what the QML UI
showed before stem support existed.

Reported in mixxxdj#17011 as an assert at singleton.h:22 reached from
allshader_sg::WaveformRenderBeat::preprocessInner on a debug build.

Verified on Windows against main: mixxx --new-ui loading a .stem.mp4
into a deck now decodes the stems and runs with no assert and no
"Singleton class has not been created yet" warnings, where the same
build without this change logged one per affected renderer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Eve00000

Eve00000 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Loading any stem file in the QML UI (--qml / --new-ui) kills Mixxx.

The error/bug report was not about --new-ui but about the QML Latenight,
to enter the QML Latenight you need to start Mixxx with --developer command line option-> choose the QML Latenight Skin and restart Mixxx with --developer command line option.

Did you regenerate the error first before trying to fix it?

@xARSENICx

Copy link
Copy Markdown
Contributor

I already solved the issue in 38f421e. Sorry for late communication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QML Latenight crashes on loading a stemfile (Win & Linux)

3 participants