diff --git a/lib/tunings.py b/lib/tunings.py index 2099cdb..c5d34ed 100644 --- a/lib/tunings.py +++ b/lib/tunings.py @@ -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: """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 - 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] = [] for f in freqs: try: f = float(f) except (TypeError, ValueError): return None - if f <= 0: + if not math.isfinite(f) or f <= 0: return None out.append(int(round(69 + 12 * math.log2(f / reference_pitch)))) return out diff --git a/static/v3/badges.js b/static/v3/badges.js index 7e49f1b..f36cf84 100644 --- a/static/v3/badges.js +++ b/static/v3/badges.js @@ -120,11 +120,20 @@ if (!r.ok) return; const data = await r.json(); _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 = {}; for (const key of Object.keys(_tuningsByKey)) { 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]); } } diff --git a/tests/test_tunings.py b/tests/test_tunings.py index ae08d6a..70b310c 100644 --- a/tests/test_tunings.py +++ b/tests/test_tunings.py @@ -275,4 +275,7 @@ def test_freqs_to_midis_rejects_garbage(): from tunings import freqs_to_midis 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([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