notepilot / scripts /note_correspondence.py
jasonmdong's picture
Sync from GitHub via hub-sync
11330a4 verified
Raw History Blame Contribute Delete
2.83 kB
# Note-by-note: what was PLAYED vs what the detector SAW vs what REGISTERED,
# and for every mismatch, which of the three failures it was.
import json, sys, bisect, collections, statistics
NAMES=['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
def pn(m): return f"{NAMES[m%12]}{m//12-1}"
d=json.load(open(sys.argv[1])); evs=d['events']
keys=[(e['t'], e['midi'], e.get('name')) for e in evs if e.get('type')=='input.key' and e.get('midi')]
notes=[(e['t'], e.get('midi')) for e in evs if e.get('type')=='mic.note' and e.get('midi')]
notes.sort(); nts=[n[0] for n in notes]
fr=[e for e in evs if e.get('type')=='follow.frame']
fr.sort(key=lambda e: e.get('t',0)); fts=[e.get('t',0) for e in fr]
holds=collections.defaultdict(list)
for e in evs:
if e.get('type')=='follow.hold': holds[round(e.get('t',0)/100)].append(e.get('reason'))
WIN=800
rows=[]; why=collections.Counter()
for t,m,name in keys:
i=bisect.bisect_left(nts,t); j=bisect.bisect_left(nts,t+WIN)
got=[p for _,p in notes[i:j]]
hit = m in got
a=bisect.bisect_left(fts,t); b=bisect.bisect_left(fts,t+WIN)
seg=fr[a:b]
peaks=[e.get('peak') for e in seg if isinstance(e.get('peak'),int)]
pmax=max((e.get('p') or 0) for e in seg) if seg else 0
evmax=max((e.get('ev') or 0) for e in seg) if seg else 0
gated=sum(1 for e in seg if not e.get('inf'))
ownrow=[]
for e in seg:
sal=e.get('sal')
if sal and 0 <= m-33 < len(sal): ownrow.append(sal[m-33])
own=max(ownrow) if ownrow else 0
med=statistics.median([statistics.median(e['sal']) for e in seg if e.get('sal')]) if seg else 0
sawit = any(p==m for p in peaks)
hs=[]
for k in range(round(t/100), round((t+WIN)/100)+1): hs += holds.get(k,[])
top = collections.Counter(hs).most_common(1)
if hit: cls='OK'
elif not seg: cls='no frames'
elif gated == len(seg): cls='UNDER GATE (level)'
elif own <= med: cls='NOT HEARD (no energy at its pitch)'
elif sawit: cls='HEARD, REFUSED (peak was the right note)'
else: cls='HEARD SOMETHING ELSE (peak != your note)'
if not hit: why[cls]+=1
rows.append((t,name,cls,pn(max(set(peaks),key=peaks.count)) if peaks else '-',round(pmax,2),round(evmax,1),round(own),round(med),top[0][0] if top else ''))
print(f"PLAYED {len(keys)} notes; detected {sum(1 for r in rows if r[2]=='OK')} ({100*sum(1 for r in rows if r[2]=='OK')/len(rows):.0f}%)\n")
print("WHY EACH MISS HAPPENED")
for k,v in why.most_common(): print(f" {v:4} {k}")
print(f"\n{'time':>8} {'played':>7} {'peak':>6} {'p':>5} {'ev':>6} {'ownRow':>7} {'median':>7} verdict / hold")
for t,name,cls,peak,pmax,evmax,own,med,hold in rows[:40]:
flag='' if cls=='OK' else ' *'
print(f"{t/1000:8.2f} {str(name):>7} {peak:>6} {pmax:5.2f} {evmax:6.1f} {own:7} {med:7} {cls}{(' / '+hold) if hold else ''}{flag}")