"""Constructed track/channel fixtures; no audio renderer or DAW measurements."""
import struct,pathlib,json,csv
r=pathlib.Path(__file__).parent
def vlq(n):
 out=[n&127];n>>=7
 while n:out.insert(0,(n&127)|128);n>>=7
 return bytes(out)
def track(name,notes):
 b=b'\x00\xff\x03'+vlq(len(name))+name.encode();last=0
 for tick,ch,key,on in notes:
  b+=vlq(tick-last)+bytes([(144 if on else 128)+ch-1,key,80 if on else 0]);last=tick
 b+=b'\x00\xff\x2f\x00';return b'MTrk'+struct.pack('>I',len(b))+b
def build(fmt,tracks):return b'MThd'+struct.pack('>IHHH',6,fmt,len(tracks),480)+b''.join(track(name,events) for name,events in tracks)
a=[(0,1,60,True),(120,1,60,False)];b1=[(240,1,67,True),(360,1,67,False)];b2=[(240,2,67,True),(360,2,67,False)]
fixtures={'two-tracks-one-channel.mid':(1,[('A',a),('B',b1)]),'one-track-two-channels.mid':(0,[('Together',a+b2)]),'two-tracks-two-channels.mid':(1,[('A',a),('B',b2)]),'renamed-tracks.mid':(1,[('Banana',a),('Apple',b2)])}
def parse(data):
 fmt,count,division=struct.unpack('>HHH',data[8:14]);p=14;events=[];names=[]
 for t in range(count):
  assert data[p:p+4]==b'MTrk';size=struct.unpack('>I',data[p+4:p+8])[0];p+=8;end=p+size;tick=0
  def readvlq():
   nonlocal p
   n=0
   while True:
    v=data[p];p+=1;n=n*128+(v&127)
    if v<128:return n
  while p<end:
   tick+=readvlq();s=data[p];p+=1
   if s==255:
    kind=data[p];p+=1;n=readvlq();v=data[p:p+n];p+=n
    if kind==3:names.append(v.decode())
    continue
   assert s>>4 in (8,9);key,velocity=data[p:p+2];p+=2
   events.append({'tick':tick,'track':t+1,'channel':(s&15)+1,'key':key,'kind':'on' if s>>4==9 and velocity else 'off','velocity':velocity})
 return {'format':fmt,'tracks':count,'division':division,'names':names,'events':sorted(events,key=lambda e:e['tick'])}
results=[];rows=[]
for name,(fmt,tracks) in fixtures.items():
 data=build(fmt,tracks);(r/name).write_bytes(data);v=parse(data);ons=[e for e in v['events'] if e['kind']=='on']
 assert [(e['tick'],e['key']) for e in ons]==[(0,60),(240,67)]
 rows.append({'file':name,'tracks':v['tracks'],'eventChannels':','.join(map(str,sorted({e['channel'] for e in ons}))),'channel1NoteOns':sum(e['channel']==1 for e in ons),'channel2NoteOns':sum(e['channel']==2 for e in ons)})
 results.append({'file':name,**v})
def stream(v):return [{k:x for k,x in e.items() if k!='track'} for e in v['events']]
assert stream(results[1])==stream(results[2])==stream(results[3]);assert [x['channel2NoteOns'] for x in rows]==[0,1,1,1]
(r/'results.json').write_text(json.dumps({'scope':'Constructed SMF track grouping and explicit event channels, not DAW playback','cases':results},indent=2)+'\n',encoding='utf-8')
with (r/'observations.csv').open('w',newline='',encoding='utf-8') as f:
 w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
print(json.dumps(rows))
