#!/usr/bin/env python3
"""Compare every exported SCC-1 event and delta with original driver instructions.

Intercepts MIDI byte output before hardware polling, with unity master volume.
Does not emulate a sound card or the periodic interrupt scheduler.
"""
from pathlib import Path
import json,struct
from unicorn import Uc,UC_ARCH_X86,UC_MODE_16,UC_HOOK_CODE
from unicorn import x86_const as r
from extract_audio import ROOT,OUT,vlq
DRIVER=(ROOT/'analysis/resources/00_034.bin').read_bytes()
checks=[]
for n in range(39,65,5):
    source=(ROOT/f'analysis/resources/00_{n:03}.bin').read_bytes()
    info=json.loads((OUT/f'variants/group-{(n-39)//5+1}-scc1-events.json').read_text());events=0;delays=0
    for tr in info['tracks']:
        cpu=Uc(UC_ARCH_X86,UC_MODE_16);cpu.mem_map(0,0x100000)
        cpu.mem_write(0x10000,DRIVER);cpu.mem_write(0x14000,source)
        for name,value in [('CS',0x1000),('DS',0x1000),('ES',0x1000),('SS',0x5000),('DI',0x8a)]:cpu.reg_write(getattr(r,'UC_X86_REG_'+name),value)
        cpu.mem_write(0x10219,b'\0\1');cpu.mem_write(0x10223,bytes([info['ppqn'],info['initial_bpm']]))
        cpu.mem_write(0x10094,b'\1');cpu.mem_write(0x10090,struct.pack('<H',0x4000+tr['start']))
        midi=[];phase=['event']
        def hook(c,a,size,data):
            ip=a-0x10000
            if phase[0]=='event' and ip in (0x325,0x332):c.emu_stop();return
            if ip in (0x796,0x79f):
                midi.append(c.reg_read(r.UC_X86_REG_AX)&255)
                sp=c.reg_read(r.UC_X86_REG_SP);dest=int.from_bytes(c.mem_read(0x50000+sp,2),'little')
                c.reg_write(r.UC_X86_REG_SP,sp+2);c.reg_write(r.UC_X86_REG_IP,dest)
        cpu.hook_add(UC_HOOK_CODE,hook)
        def run(entry,si):
            cpu.reg_write(r.UC_X86_REG_SI,0x4000+si);cpu.reg_write(r.UC_X86_REG_SP,0xfff0)
            cpu.mem_write(0x5fff0,b'\0\xff');cpu.emu_start(0x10000+entry,0x1ff00,count=10000)
        previous=tr['start'];tick=0
        for cmd in tr['commands']:
            phase[0]='delay';delta,end=vlq(source,previous);run(0x5fe,previous)
            actual=cpu.reg_read(r.UC_X86_REG_BX)+(cpu.reg_read(r.UC_X86_REG_DX)<<16)
            assert actual==delta and cpu.reg_read(r.UC_X86_REG_SI)==0x4000+end
            tick+=delta;assert tick==cmd['tick'];delays+=1
            phase[0]='event';midi.clear();run(0x31a,cmd['offset'])
            assert midi==cmd['midi'],(n,cmd,midi)
            expected=tr['loop_offset'] if cmd['opcode']==0x98 else cmd['end']
            assert cpu.reg_read(r.UC_X86_REG_SI)==0x4000+expected,(n,cmd,hex(cpu.reg_read(r.UC_X86_REG_SI)))
            if cmd['opcode']==0x93:assert cpu.mem_read(0x10224,1)[0]==source[cmd['offset']+1]
            events+=1;previous=cmd['end']
    checks.append(dict(resource=n,event_commands=events,delta_decodes=delays,match=True))
report=dict(driver='00_034',scope=__doc__,checks=checks)
(OUT/'variants/scc1-verification.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report,indent=2))
