Merge pull request #926 from got-feedBack/feat/tuning-midis-followups

tuningMidis follow-ups: NaN/Inf guard in freqs_to_midis + v3 badge adopts exact midis
This commit is contained in:
Byron Gamatos
2026-07-13 14:20:58 +02:00
committed by GitHub
3 changed files with 18 additions and 4 deletions
+4 -2
View File
@@ -101,14 +101,16 @@ def open_midis_to_freqs(midis: list[int], reference_pitch: float = DEFAULT_REFER
def freqs_to_midis(freqs: list[float], reference_pitch: float = DEFAULT_REFERENCE_PITCH) -> list[int] | None: def freqs_to_midis(freqs: list[float], reference_pitch: float = DEFAULT_REFERENCE_PITCH) -> list[int] | None:
"""Return absolute open-string MIDI notes for frequencies at the supplied """Return absolute open-string MIDI notes for frequencies at the supplied
A4 reference — the inverse of open_midis_to_freqs. None if any entry is A4 reference — the inverse of open_midis_to_freqs. None if any entry is
non-numeric or non-positive (a provider could hand us anything).""" non-numeric, non-finite, or non-positive (a provider could hand us
anything; NaN/Infinity would otherwise raise inside int(round(...)) and
500 the /api/tunings endpoint)."""
out: list[int] = [] out: list[int] = []
for f in freqs: for f in freqs:
try: try:
f = float(f) f = float(f)
except (TypeError, ValueError): except (TypeError, ValueError):
return None return None
if f <= 0: if not math.isfinite(f) or f <= 0:
return None return None
out.append(int(round(69 + 12 * math.log2(f / reference_pitch)))) out.append(int(round(69 + 12 * math.log2(f / reference_pitch))))
return out return out
+11 -2
View File
@@ -120,11 +120,20 @@
if (!r.ok) return; if (!r.ok) return;
const data = await r.json(); const data = await r.json();
_tuningsByKey = data.tunings || {}; _tuningsByKey = data.tunings || {};
// Build TUNING_NOTE from the first (lowest) string frequency of each tuning. // Build TUNING_NOTE from the lowest string of each tuning. Prefer the
// exact integer midis the server now sends (tuningMidis, #829) — the
// frequency path reconstructs the note via log2 against a hardcoded
// 440 and can land a semitone off at non-440 reference pitches.
// Frequencies remain the fallback for older cached responses.
const midisByKey = data.tuningMidis || {};
TUNING_NOTE = {}; TUNING_NOTE = {};
for (const key of Object.keys(_tuningsByKey)) { for (const key of Object.keys(_tuningsByKey)) {
for (const [name, freqs] of Object.entries(_tuningsByKey[key])) { for (const [name, freqs] of Object.entries(_tuningsByKey[key])) {
if (!(name in TUNING_NOTE) && Array.isArray(freqs) && freqs.length > 0) { if (name in TUNING_NOTE) continue;
const midis = midisByKey[key] && midisByKey[key][name];
if (Array.isArray(midis) && midis.length > 0 && Number.isFinite(midis[0])) {
TUNING_NOTE[name] = NOTE_NAMES[((midis[0] % 12) + 12) % 12];
} else if (Array.isArray(freqs) && freqs.length > 0) {
TUNING_NOTE[name] = _freqToNote(freqs[0]); TUNING_NOTE[name] = _freqToNote(freqs[0]);
} }
} }
+3
View File
@@ -275,4 +275,7 @@ def test_freqs_to_midis_rejects_garbage():
from tunings import freqs_to_midis from tunings import freqs_to_midis
assert freqs_to_midis([82.41, 0]) is None # non-positive assert freqs_to_midis([82.41, 0]) is None # non-positive
assert freqs_to_midis([82.41, "x"]) is None # non-numeric assert freqs_to_midis([82.41, "x"]) is None # non-numeric
assert freqs_to_midis([float("nan")]) is None # non-finite (would raise in int(round(...)))
assert freqs_to_midis([float("inf")]) is None # non-finite
assert freqs_to_midis([float("-inf")]) is None # non-finite
assert freqs_to_midis([]) == [] # vacuously fine assert freqs_to_midis([]) == [] # vacuously fine