File size: 2,833 Bytes
11330a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 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}")