#!/usr/bin/env python3 """IT BROKE — воспроизведение провала, а не рассказ о нём. Я опубликовал конкурс по обработке звука, где судья оценивает без ушей, и вместе с ним первую версию метрики. Она награждала починку, сделанную В ПРОТИВОПОЛОЖНУЮ СТОРОНУ, баллом лучше, чем бездействие. Запусти: uv run --with numpy --with scipy python broke_repro.py Сигнал синтетический, файлы не нужны, результат детерминирован (seed=7). """ import numpy as np from scipy.signal import stft def _mono(x): return x.mean(axis=1) if x.ndim>1 else x def lsd(a,b,sr,nffts=(2048,512,8192)): t=[] for n in nffts: A=np.abs(stft(_mono(a),fs=sr,nperseg=n,noverlap=n//2)[2])+1e-10 B=np.abs(stft(_mono(b),fs=sr,nperseg=n,noverlap=n//2)[2])+1e-10 m=min(A.shape[1],B.shape[1]) t.append(np.sqrt(np.mean((20*np.log10(A[:,:m])-20*np.log10(B[:,:m]))**2))) return float(np.mean(t)) def bands(x,sr,e=(20,120,500,2000,8000,20000)): X=np.abs(np.fft.rfft(_mono(x))); f=np.fft.rfftfreq(len(_mono(x)),1/sr) return np.array([10*np.log10(np.sum(X[(f>=lo)&(f1e-9 else 0.0) for k in a}; res[name]=rel print(f" {name:<20} худшая ось {max(rel.values()):6.3f} " + " ".join(f"{k} {v:.3f}" for k,v in rel.items())) r,w=res["починил правильно"],res["починил В МИНУС"] print("\n=== И ЧЕГО ПОЧИНКА НЕ ЗАКРЫЛА (это тоже часть отчёта)") print(f" по спектру и балансу правильная лучше: {r['спектр']:.3f}/{r['баланс']:.3f} против {w['спектр']:.3f}/{w['баланс']:.3f}") print(f" но по динамике хуже: {r['динамика']:.3f} против {w['динамика']:.3f} — наклон в минус случайно") print( " приблизил крест-фактор к эталонному, и это перевешивает.") print(f" среднее: правильная {np.mean(list(r.values())):.3f} против в минус {np.mean(list(w.values())):.3f}") print(f" худшая ось: правильная {max(r.values()):.3f} против в минус {max(w.values()):.3f}") print("\n ВЫВОД: обе версии метрики путают их, когда участник НЕ ТРОНУЛ доминирующую") print(" часть порчи (здесь пережатие). Разделение осей убрало произвольные веса,") print(" но не сделало метрику способной ранжировать двух одинаково неполных участников.") print(" Честная граница: судья годен отличать сделанное от несделанного, а не") print(" сравнивать две половинчатые починки между собой.") ########## ВЫВОД ЗАПУСКА (uv run --with numpy --with scipy python broke_repro.py) ########## порча: наклон +1.5 dB и пережатие 0.75; участник чинит только наклон === БЫЛО: одно число со взятыми с потолка весами ничего не делать 20.091 починил правильно 22.219 починил В МИНУС 17.590 <-- ЭТО ПРОВАЛ, если он <= 'ничего не делать' провал воспроизведён: True (порча наизнанку оценена не хуже бездействия) === СТАЛО: оси раздельно, нормировка на бездействие (1.0 = не тронул) ничего не делать худшая ось 1.000 спектр 1.000 баланс 1.000 динамика 1.000 починил правильно худшая ось 1.192 спектр 0.980 баланс 0.951 динамика 1.192 починил В МИНУС худшая ось 1.096 спектр 1.096 баланс 1.045 динамика 0.747 === И ЧЕГО ПОЧИНКА НЕ ЗАКРЫЛА (это тоже часть отчёта) по спектру и балансу правильная лучше: 0.980/0.951 против 1.096/1.045 но по динамике хуже: 1.192 против 0.747 — наклон в минус случайно приблизил крест-фактор к эталонному, и это перевешивает. среднее: правильная 1.041 против в минус 0.963 худшая ось: правильная 1.192 против в минус 1.096 ВЫВОД: обе версии метрики путают их, когда участник НЕ ТРОНУЛ доминирующую часть порчи (здесь пережатие). Разделение осей убрало произвольные веса, но не сделало метрику способной ранжировать двух одинаково неполных участников. Честная граница: судья годен отличать сделанное от несделанного, а не сравнивать две половинчатые починки между собой.