Skip to content

--downbeat-log: the estimator's terms, one row per beat

The one instrument on this page that runs the live app rather than a headless capture, and that is the point of it (Plan 0086 Phase 1). The question it exists for cannot be asked of synthesized audio: the downbeat estimator publishes on 6.0 % of audible time and on backbeat rock/pop 0.14 %, and the cause is still inferreddiagnostics.log samples at 1 Hz and records the estimator’s outcome, not its per-beat terms, so three different failures fit the same reading.

Terminal window
# Windows: a row per detected beat, alongside the 1 Hz diagnostics.log
ritmolux.exe --downbeat-log
ritmolux.exe --downbeat-log C:\path\to\downbeat.log # or --downbeat-log=<path>

A bare flag writes downbeat.log beside diagnostics.log under the per-user app dir — the same three argument shapes --soak takes. Off by default: without the flag no logger exists and the frame loop is unchanged.

One tab-separated row per beat, header on a fresh file:

columnwhat it is
beatthe beat clock’s beat_index — gaps mean beats the render loop coalesced. Not the counter the fold buckets by; that is fold_beat, below
s0..s3mean accent per candidate beat-1 alignment: the 4/4 fold’s own output
bestthe alignment the fold favours right now
heldthe alignment actually held, which lags best by the hysteresis
effect_rawbetween-alignment share of accent variance, before correction
null_sharewhat four groups would explain by chance at this history length
effect_correctedwhat the gate compares — and the published downbeat_confidence, bit for bit
beats_seenaccents recorded, against the 8-beat floor, saturating at 32
locked0/1, so the publish rate over a run is the mean of the column
bass mid treb onsetthe normalized levels for context
bpmthe tempo tracker’s estimate — read the row rate against it (see below)
time_since_beathow stale this row’s band levels are: they come from the latest analysis hop, not necessarily the hop the beat fired on
unix_msthe time axis. Deltas between rows are the inter-detection interval, and it lines a capture up against a diagnostics.log from the same session
fold_beatthe counter s0..s3, best and held are indexed in — the grid’s tempo-driven beat count once the grid runs, beat_index before that. Bucket the accent by fold_beat % 4, never by beat % 4
grid_bar_phasewhere fold_beat sits across the bar, [0, 1), ungated — no alignment subtracted, so it says where the grid is rather than where the estimator thinks beat 1 is. Only a grid reading where bpm > 0; on a warmup row it is the tempo tracker’s onset-reset phase, so do not average the column down the whole file

The last five are appended, never interleaved — the frozen-prefix rule diagnostics.log follows — so a capture taken before they existed stays parseable by column name.

beat and fold_beat are two different counters, and only the second one indexes the alignment block. They were one number until Plan 0095 moved the fold onto the bar grid (ADR-0109); beat still means beat_index, unchanged, so that every capture taken before that keeps parsing and stays comparable. Reading s0..s3 against beat % 4 produces a plausible, wrong answer — on the synthesized 4/4 in standalone/src/downbeatlog.rs it puts the accent on phase 0 while the fold reports 3 — which is what these two columns exist to stop.

Read the row rate against bpm before reading anything else. The beat flag that paces these rows comes from the onset detector — an adaptive threshold on spectral flux with a 96 ms refractory — and it is not tempo-gated (core/src/dsp/onset.rs); beat_index is a straight count of those events (core/src/dsp/tempo.rs). So rows / seconds against bpm / 60 is the number of detections per musical beat, and it is not guaranteed to be 1. On a synthesized clip with one transient per beat it measures exactly 1.00; on real material with hats it does not — which is why the fold stopped bucketing by beat_index in Plan 0095, and why the ratio is still worth reading: it is the size of the gap between the beat column and fold_beat.

Reading it is what tells the three stories apart — the reason the plan spends a phase capturing before choosing a repair:

  • s0..s3 flat and effect_raw low → the accent carries no bar-scale structure → the accent feature is the defect.
  • two scores tied and high, the other two low → a kick on 1 and 3 is 2-periodic, so the fold is choosing between two equally good answers → the repair is a cue independent of the drum pattern, and a second percussive band is the same ambiguity phase-shifted.
  • effect_raw healthy but effect_corrected near zero → the noise correction is eating a real effect at this history length → the window or the measure.

Three things to know before running one:

  1. It costs the estimator nothing. Analyzer::downbeat_terms is &self, allocation-free and clock-free, so being observed cannot change what is observed. The write is on the render thread — never the audio callback — and event-paced: ~2 rows/s at 120 BPM, and a frame with no beat costs a bool test.
  2. A hidden window logs nothing. Rows come off the frame path, which returns early while occluded or zero-sized. Keep the window up for a capture.
  3. Run matched material. The measurement this file exists for is a genre split — unambiguous 4/4 backbeat rock/pop against a four-on-the-floor control, matched in duration — so the result is comparable with Plan 0068’s 6.79 % / 0.14 % baseline. And do not re-measure by ear: locked is the outcome instrument and the score columns are the decomposition.

Built from a8ce055 at version 0.115.0. This site tracks main and is not versioned per release.