S-KEY β musical key detection (ONNX, audio β key)
ONNX export of S-KEY, Deezer's key detector (harmonic VQT + ChromaNet),
packaged for the musetric packages/ai runtime (onnxruntime-web
on the wasm execution provider).
Unlike this project's chord and separation exports, this graph is self-contained: feature extraction, the network and the reference runner's postprocessing are all baked in, so the host feeds raw audio and reads back 24 numbers. At 0.33 MB it needs no WebGPU host and runs directly in Node.
audio [1, N] @ 22050 Hz mono, peak-normalized
-> nnAudio harmonic VQT (log-amplitude HCQT) [1, 1, 99, T]
-> crop 84 bins [1, 1, 84, T]
-> ChromaNet (ConvNeXt stack -> chroma -> 24) [1, 24] (softmax'd)
-> mean over batch -> softmax [24]
The whole track is fed at once; ChromaNet's time pooling collapses it to a single
key. Two ops that cannot export with a dynamic time axis β an affine-free
layer_norm whose normalized_shape would carry T, and AdaptiveAvgPool2d
over a dynamic width β are swapped at export time for numerically identical
equivalents, and the vendored CropCQT is inlined as a slice (the reference
always crops at transpose = 0). The graph therefore carries no
approximation: it reproduces the reference implementation's key and confidence
up to float noise (~1e-6).
Intended uses & limitations
Intended:
- Global key estimation for a music track, as a stage in an audio pipeline.
- Client/edge inference through
onnxruntime-web; the graph is small enough for the wasm EP.
Out of scope:
- Key changes β time pooling returns one key for whatever you feed it. Segment the audio host-side if you need modulation tracking.
- Anything outside the 24 major/minor classes (modal, atonal, ambiguous).
- Use in other training frameworks β this is an inference-only export.
Limitations:
- The graph has no resampler and no normalizer. Input must be 22050 Hz mono
and peak-normalized (divide by
max|x|), matching the referenceload_audio. confidenceis not calibrated. The reference runner softmaxes an already softmaxed vector, which squeezes all 24 classes toward the1/24 = 0.0417floor and leaves the winner at only ~0.05β0.10. It is reproduced faithfully for parity, so treat it as a display value: useargmaxfor the decision, andtop1 - top2if you need a margin (worst case 8.8e-3 over our 20-track set).- Memory scales with track length β
Tis dynamic and the whole track is one forward pass. - The decoder is part of the contract. Peak-normalization cancels gain
differences, but resamplers still differ:
ffmpegversustorchaudiomovesconfidenceby ~1e-4 (argmax unaffected β see Validation). - Validate on the material fed in production. For this pipeline that is the instrumental stem, not the full mix and not a vocal stem: an isolated vocal yields the relative minor as often as the key.
- Training-data provenance of the upstream checkpoint is not documented here.
How to use
argmax(probs) indexes config.json's keyMap ("A Major", "C minor", β¦);
the probability at that index is the confidence.
import * as ort from 'onnxruntime-web';
const session = await ort.InferenceSession.create('skey.onnx', {
executionProviders: ['wasm'],
});
// audio: Float32Array, whole track, 22050 Hz mono, peak-normalized.
const { probs } = await session.run({
audio: new ort.Tensor('float32', audio, [1, audio.length]),
});
// probs.data: Float32Array(24) -> argmax -> keyMap index
See the musetric packages/ai host code (key/analyzeKey.node.ts) for the
decode + peak-normalize + argmax pipeline around the session.
Files
| File | Size | SHA256 |
|---|---|---|
skey.onnx |
338,482 B | 5113c1378c1007c8559fcb767593366ba9794397b060535eb80a113db50530fc |
config.json |
712 B | 20be1e139e1b05dea4bae2e2dde717d593c10c30bb38b300aeedc6693be88a52 |
config.json records the I/O contract, the sample rate, the mono/peak-normalize
flags and the 24-entry keyMap that gives argmax its meaning.
Signature β float32 weights, opset ai.onnx 17:
| Tensor | Type | Shape | Meaning |
|---|---|---|---|
audio (in) |
float32 | [1, samples] |
whole track, 22050 Hz mono, peak-normalized; samples is dynamic |
probs (out) |
float32 | [24] |
key probabilities; argmax indexes keyMap |
Validation
This export vs the PyTorch run_skey reference pipeline, key argmax:
| Metric | Value |
|---|---|
| 20 instrumental stems (the production input) | 20/20 |
| 20 full mixes | 20/20 |
| 15 isolated lead-vocal stems | 15/15 |
| degenerate outputs | 0 |
Agreement is exact because the graph substitutes nothing: it reuses the same nnAudio VQT the reference uses, and the export-time op swaps are mathematically identical. Parity therefore does not depend on the material β unlike an export that stands a different transform in for the reference's.
End to end, the shipped Node path (ffmpeg decode + peak-normalize + wasm EP) against the Python CLI it replaces, over the same 20 instrumental stems:
| Metric | Value |
|---|---|
{root, mode} β the user-visible key |
20/20 identical |
confidence β max abs difference |
9.08e-05 |
worst top1 - top2 margin |
8.81e-3 |
The residual ~1e-4 is the decoder, not the graph: torchaudio and ffmpeg
resample to 22050 Hz with different filters. It sits ~100Γ below the model's
worst decision margin, so the key is unaffected.
Re-run the parity gate on the exact published bytes before relying on it.
Source & lineage
Code license and weight license are separate; ONNX conversion does not change the weight license. Documented only as far as it is verifiable.
- Architecture: harmonic VQT (
harmonics=[1],fmin=27.5,n_bins=99,bins_per_octave=12, log-amplitude) β crop to 84 bins β ChromaNet: seven time-downsampling + ConvNeXt blocks, an octave pool to 12 chroma, a global time pool, a 1Γ1 classifier and a batch norm, softmaxed to 24 classes. Small β the whole graph is 0.33 MB in float32. - Reference implementation and weights:
deezer/skey, MIT (per itsLICENSE, Copyright (c) 2019-present, Deezer SA). - Checkpoint:
skey/models/skey.pt@ commit918b83d2β 765,465 B, git blob7ae0e9f2d226dcd2d34a65707dac5012b603e2caβ fetched at export time fromraw.githubusercontent.com. The URL is pinned to that commit, so the fetch does not follow a moving branch. - Vendored code: the inference subset lives under
musetric_toolkit/key_audio/skeyin musetric-toolkit; see itsthirdPartyNotices.md. - Export tooling:
scripts/onnx/skeyin musetric-toolkit. - Host runtime:
packages/aiinmusetric.
This export preserves the upstream MIT license; we do not claim authorship of the original weights.
License
MIT, inherited from the upstream weights.
- Downloads last month
- 31