Skip to content

Show the pitch track and notes filling in while recording - #12

Open
novikov-alexander wants to merge 6 commits into
sonic-visualiser:defaultfrom
novikov-alexander:alnovi/recordPreview
Open

novikov-alexander wants to merge 6 commits into
sonic-visualiser:defaultfrom
novikov-alexander:alnovi/recordPreview

Conversation

@novikov-alexander

Copy link
Copy Markdown

An off-by-default option, in the Analysis menu, that fills in the pitch
track and notes as a recording is made, so the performer can see
something as they go.

It cannot change Tony's output. Everything it adds is removed again when
recording stops, and the recordCompleted path is untouched:
analyseNow() → analyseExistingFile() removes both layers and re-runs
the full analysis exactly as today, and MainWindowBase's refreshModel
still fires. The worst case is a wrong-looking preview that is deleted a
moment later.

Notes on the implementation:

  • Transform output models come from ModelTransformerFactory rather than
    Document, so they belong to the preview: no layer is created for them,
    they are not registered with the document, and nothing reaches the undo
    history. (Document::addLayerToView and removeLayerFromView both push
    undoable commands holding a raw Layer *, so creating and destroying
    transient layers is not safe.)
  • Each pass re-analyses a short tail of the previous one and replaces the
    preview's contents there, rather than merging. Notes have duration and
    would otherwise be cut in two at a region boundary; because the preview
    owns everything it draws, replacing a region is a delete and an add,
    with no heuristic choosing between overlapping notes.
  • pYIN's two outputs disagree about frame origin: smoothedpitchtrack
    carries the host's block timestamps, while notes derives timestamps
    from a frame index counting from zero. Handled explicitly where they are
    read, with a range filter as a backstop either way.
  • The new setting is registered in Analyser::getAnalysisSettings() so it
    flows through the same single-source-of-truth map as the others.

Adds a tony-main test target for main/, run by meson test alongside
the svcore suites. 11 files changed, +1238 −3, of which around 240 are
tests.

The first commit is the macOS/Qt build fix submitted separately as #11; it
will drop out on rebase once that lands, or I can remove it now.

This supersedes the approach in #6.

novikov-alexander and others added 6 commits August 6, 2026 17:33
Qt's qyieldcpu.h calls the ACLE intrinsic __yield() under
__has_builtin(__yield) without including <arm_acle.h>. Apple clang 21
reports the builtin as available but makes the resulting implicit
declaration an error, so every translation unit reaching
<QtCore/qatomic.h> fails to compile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pure functions deciding which region of a growing recording to analyse
next, and discarding transform output that falls outside the region
asked for. Adjacent, non-overlapping, monotone ranges, so results can be
concatenated with nothing to merge. Adds a test target for main/, run by
meson test alongside the svcore suites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Off by default, from a new item in the Analysis menu. When enabled, the
pitch track is filled in as the recording proceeds, one region at a
time, so that a performer can see something as they go.

This is only ever a preview: everything it adds is removed again when
recording stops, and the pitch and note layers are then regenerated in
full from the completed audio as they always were. The recordCompleted
path is untouched.

The transform output model comes from ModelTransformerFactory rather
than Document, so it belongs to us: no layer is created for it, it is
not registered with the document, and nothing reaches the undo history.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
recordStatusChanged(true) is emitted from startRecording(), before
record() has closed the old session, created the new document, set the
main model and built the analysis layers. Starting the preview there
attached it to the session about to be discarded, so fileClosed()
abandoned it and nothing was ever drawn.

The first duration update arrives 200ms later, by which time all of that
has happened, so start there instead. One attempt per recording, so a
failure is not retried on every update.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Notes have duration, so a region boundary falling inside one would cut
it in two. Each pass now reaches back over a short tail of the previous
one and replaces the preview's contents there, giving the note tracker
the surrounding audio so it can emit the note whole.

Replacing rather than merging is possible because everything drawn is
the preview's own: it is a delete followed by an add, with no heuristic
deciding between overlapping notes.

Both outputs come from a single run of the plugin. Note frames need
shifting and pitch frames do not, because pYIN's notes output derives
its timestamps from a frame index counting from zero while its
smoothedpitchtrack output carries the host's block timestamps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each pass removed what it had previously drawn in the revisited region
with a stable_partition across the whole record of added events. That
record grows at around 170 events a second, and Event holds QStrings, so
the cost was quadratic in the length of the take: after several minutes
the passes were doing enough work on the GUI thread to make the
application progressively less responsive.

The record is sorted by frame, because each pass prunes from its region
start and then appends that region in order, and the region start only
moves forward. So the cut point can be found by binary search.

Also make the completion connection queued: completionChanged is emitted
from the transform's own thread and the handler releases the model that
emitted it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant