# 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}")