hum2song contour encoder

A melody-contour encoder for query by melody: humming, whistling, and singing with wrong or nonsense lyrics. It maps a key-normalized pitch track (F0) to one L2-normalized 256-d vector, so a query and a song's vocal melody can be compared by cosine similarity. Lyrics and timbre never reach the model.

Paper: "Whistle and Wrong Lyrics: Synthetic Queries Are Not Enough for Real-World Query-by-Melody", Luigi Rivolta, 2026 (arXiv, cs.SD). The arXiv link will be added here once the paper is announced.

Code: https://github.com/luiginotmario/hum2song

Files

File What it is
e2b/model.safetensors, e2b/best.pt, e2b/config.json E2b, the main model in the paper and the live model for open retrieval
whistle_closedset_ft/model.safetensors, whistle_closedset_ft/best.pt, whistle_closedset_ft/config.json Closed-set fine-tune of E2b on real MLEnd whistles (paper Section "closed set", experiment D-044, all train performers, seed 0). Experiment weights only, for the 8 MLEnd songs

The .pt files are what the repo's load_checkpoint reads (model state_dict, config, step; no optimizer state). The .safetensors files hold the same weights.

Architecture

Input is key-normalized pitch plus a voicing flag at 20 ms frames. A convolutional front end halves the frame rate; a 6-layer pre-norm transformer (d=256, 4 heads) and a masked attention pool give one L2-normalized 256-d vector. About 5.2M parameters, one tower for queries and songs.

Training

  • E2b: symmetric InfoNCE on HumTrans hum-to-MIDI pairs, then fine-tuned with the CLEWS loss (Serrà et al.) on cover-to-original windows from CHAD. Query F0 comes from RMVPE; whistles use a spectral-peak tracker.
  • Whistle closed-set fine-tune: starts from E2b; InfoNCE with same-song masking on MLEnd Hums and Whistles whistle-to-hum and hum-to-hum pairs from the 136 train performers only; 300 steps, LR 3e-5; checkpoint selected on validation performers. Train, validation and test performers are disjoint, but all splits use the same 8 songs.

Training-data provenance: HumTrans (hums with MIDI labels), CHAD (cover and original song fragments), MLEnd Hums and Whistles (hums and whistles of 8 songs). No song audio or datasets are distributed here; check each dataset's own terms before reuse.

Results (from the paper)

MLEnd test performers, 339 real whistles from 26 performers, whistle-to-hums query-by-example over the 8 songs (chance top-1 0.125). 95% CIs are bootstrap over clips.

Model Top-1 Top-3
E2b (zero-shot) 0.631 [0.581, 0.681] (214/339) 0.770
Whistle closed-set fine-tune 0.673 [0.622, 0.723] (228/339) 0.820

Performer-bootstrap CIs are wide (E2b [0.500, 0.753], fine-tune [0.536, 0.801]). The fine-tune is closed-set on known songs; it is not a claim about new songs.

E2b on hums: HumTrans test top-1 / top-10 0.996 / 1.000; MIR-QBSH (4,431 queries, 48 MIDI songs) 0.949 / 0.990.

Limitations

  • Synthetic data did not transfer. Three generations of synthetic whistles and wrong-lyrics singing (physics-based, a commercial neural music model, and open-source neural singing plus a DDSP whistle with a learned human-error model) raised synthetic-query retrieval but never beat E2b on real whistles. The best open-source run reached 0.619 (paired difference -0.012, 95% CI [-0.038, +0.015]). Real queries are the missing step.
  • The real-whistle benchmark has only 8 songs and amateur performers; one seed per run.
  • The whistle fine-tune is tuned to MLEnd's 8 songs. For open retrieval use E2b.
  • Melody-only: it cannot use lyrics, and songs without a clear vocal melody are hard.

Usage

Clone the code and install the training package (training/), then:

from pathlib import Path
import numpy as np, torch
from huggingface_hub import hf_hub_download
from hum2song.contour.train import load_checkpoint
from hum2song.contour.features import rmvpe_contour
from hum2song.contour.evaluate import embed_contours, query_contour

ckpt = hf_hub_download("luigir5/hum2song", "e2b/best.pt")
model, config, step = load_checkpoint(Path(ckpt), torch.device("cpu"))

# track: (2, frames) F0 in Hz and confidence at 10 ms, e.g. from RMVPE
# (hum2song.contour.trackers) or the whistle spectral-peak tracker.
t = np.arange(400) * 0.01
track = np.stack([440 * 2 ** (np.floor(t) / 12), np.ones_like(t)])
contour = rmvpe_contour(track)
emb = embed_contours(model, [query_contour(contour)], torch.device("cpu"))[0]
print(emb.shape)  # (256,)

Licence

Weights: CC BY 4.0. Code: see the GitHub repository.

Citation

@misc{rivolta2026whistle,
  title  = {Whistle and Wrong Lyrics: Synthetic Queries Are Not Enough for Real-World Query-by-Melody},
  author = {Rivolta, Luigi},
  year   = {2026},
  note   = {arXiv preprint, cs.SD}
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support