Skip to content

Feature/baseline updates - #68

Closed
laurenceyoon wants to merge 9 commits into
pymatchmaker:developfrom
laurenceyoon:feature/baseline-updates
Closed

laurenceyoon wants to merge 9 commits into
pymatchmaker:developfrom
laurenceyoon:feature/baseline-updates

Conversation

@laurenceyoon

Copy link
Copy Markdown
Collaborator

Baseline fixes and two new audio baselines for the TISMIR 2026 benchmark reruns.

  • Audio features: take the modulus of complex CQT/STFT (the real dtype dropped the imaginary part).
  • Audio OPHMM: transitions follow Nakamura et al. (2016): forward-only band (D1=0, D2=2), a_{i,i+2}=1e-50, no flux boost, class default s_j=1e-100.
  • Audio OPHMM and SKF: chord durations in quarter notes; SKF score tempo in quarter-note BPM.
  • gt_from_match: GT onsets from float64 note_on instead of float32 onset_sec.
  • New: arzt_tempo (Arzt & Widmer 2010) and arzt_multi_fold (needs unfold_score=False).
  • run_examples.py: options for piece, files, unfolding (now off by default) and method kwargs.
  • dtw_loop.pyx: optional backpointers record the backward path used for arzt_tempo's tempo estimate; rebuild the Cython extension.
  • methods.yaml keeps audio OPHMM s_j: 0.0 on purpose (benchmark setting for performances without repeats).
  • Merge before matchmaker_benchmark feature/baseline-updates, which needs arzt_tempo in develop.

CQTProcessor, CQTSpectralFluxProcessor (OPHMM) and RawSpectrumProcessor (SKF)
passed dtype=np.float32 to librosa.cqt / librosa.stft. librosa 0.11 then returns a
real array, dropping the imaginary part, so np.abs gave |Re X| instead of |X|
(about 36% mean magnitude error). Compute the complex transform and cast its
modulus to float32. OPHMM's score templates already used the complex modulus.
The audio OPHMM used the symmetric +-3 transition table of the MIDI model
(Nakamura et al. 2014, Table 3, with the +1 entry mistyped as 0.94531 for
0.84531), filled the whole matrix with 1e-6 outside the band that the forward
step uses, boosted the exit probability by spectral flux, and defaulted to
s_j = 1e-5. Follow the audio paper instead (Sec. III-B, IV-B2): nbh(i) =
{j; 0 <= i - j <= 2} with a_{i,i+2} = 1e-50 and no backward moves, no mass
outside the band, no flux boost, and s_j = 1e-100 (Sec. IV-C).
build_chord_sequence measured note lengths in score beats divided by 4, which is
wrong whenever the beat is not a quarter note; use onset_quarter. The tempo read
from a score marking now honours the marking's unit (e.g. half = 100 is 200
quarter-note BPM).
arzt_tempo: frame-level Arzt OLTW with the simple tempo model of Arzt & Widmer
(SMC 2010), computed from a backward path (dtw_loop records backpointers).
arzt_multi_fold: for folded scores, one Arzt matcher per unfolded version of the
score, outputting the matcher with the lowest recent cost (adapted from Arzt,
Widmer & Dixon, ECAI 2008, and Widmer & Arzt, Vienna Talk 2010).
gt_from_match took performance onsets from note_array()'s onset_sec, which
partitura stores as float32. A MIDI follower stamps its updates with the
float64 onset it receives, so the GT time of the same onset could fall a few
tens of nanoseconds earlier, and the perf -> score lookup ("last position at
or before t") then missed the update made at that onset and scored the
previous event. In the eval fold about a third of MIDI onsets were scored one
event late this way; 6/8 scores, where one event is up to a beat, failed the
tracking criterion on paths that are correct. Use the PerformedPart notes'
note_on (float64) instead: GT and follower times now coincide exactly.
The bottom-level self-transitions took the chord durations from score beats
times 60 / tempo. The tempo is quarter-note BPM, but score beats follow the
time signature, so in 6/8 (eighth-note beats) every chord was expected to last
twice as long, and in 2/2 half as long. Use onset_quarter; the reported score
positions stay in beats.
@laurenceyoon
laurenceyoon requested a review from C-Suhit October 7, 2026 12:53
…notes

A .match file and the MIDI file a follower streams can store the same notes
at different tick resolutions ((n)ASAP: 480 vs 384 ppq), so their onsets differ
by up to half a tick. A GT time a fraction of a millisecond before the
follower's update was scored against the position before that update.
resolve_gt/gt_from_match take the streamed performance and use its onsets
when every matched note has the same id and pitch in it.
ParticleFilterMidiProcessor returned the middle of the polling window, which
precedes the onsets in the window's second half and follows those in its
first half. Use the first onset of the frame, as the other MIDI processors do.
@laurenceyoon
laurenceyoon marked this pull request as draft October 7, 2026 13:48
@laurenceyoon
laurenceyoon removed the request for review from C-Suhit October 7, 2026 13:48
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