7c22ae1f02
- remove diagnostic canaries from audio hot path (crackle fix) and stuck C5 note raw-MIDI test (Vst2Instrument::init noteOff) - split native bridge into daw_vst_bridge (VSTi) + fx_vst_bridge (VST FX) - VST3: processData prepare before setActive, zero input buses, accept both .vstpreset and bridge-state blob in RenderJob presets - native_render/vst_engine: bridge-exe discovery + render_fx_chain + /fx-gui - delete old install/ engine binaries (install via sidecar+resources now) - add BUILD_LAYOUT.md, build_selfcheck, tools/ diagnostics
97 lines
3.7 KiB
Python
97 lines
3.7 KiB
Python
# SHM injection test for live-flag fix: verify transportStopped guard drops
|
|
# non-live note-ons but passes live ones (reserved[0]=1).
|
|
# Layout from SharedMemoryIPC.h (verified): base=2064 bytes, midiQueue 64x12 @2064,
|
|
# midiQueueCount @2832, controlQueue 8x1040 @2836, controlQueueCount @11156.
|
|
import ctypes, sys, time, os
|
|
|
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
|
|
APP_PID = sys.argv[1] if len(sys.argv) > 1 else '9712'
|
|
SHM_NAME = f'SonicForge_DAW_IPC_{APP_PID}'
|
|
|
|
OFF_MQ = 2064
|
|
OFF_MQ_COUNT = 2832
|
|
OFF_CQ = 2836
|
|
OFF_CQ_COUNT = 11156
|
|
MIDI_ENTRY = 12
|
|
CTRL_ENTRY = 1040
|
|
|
|
k32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
|
k32.OpenFileMappingA.restype = ctypes.c_void_p
|
|
k32.OpenFileMappingA.argtypes = [ctypes.c_uint32, ctypes.c_int, ctypes.c_char_p]
|
|
k32.MapViewOfFile.restype = ctypes.c_void_p
|
|
k32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_size_t]
|
|
k32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
|
|
k32.CloseHandle.argtypes = [ctypes.c_void_p]
|
|
|
|
def get_shm():
|
|
h = k32.OpenFileMappingA(0x0002, 0, SHM_NAME.encode()) # FILE_MAP_WRITE first
|
|
if not h:
|
|
h = k32.OpenFileMappingA(0x0004, 0, SHM_NAME.encode()) # fallback FILE_MAP_READ
|
|
if not h:
|
|
err = ctypes.get_last_error()
|
|
print(f'FATAL: cannot open SHM {SHM_NAME} (err={err})')
|
|
sys.exit(1)
|
|
ptr = k32.MapViewOfFile(h, 0x0002, 0, 0, 0) # FILE_MAP_WRITE
|
|
if not ptr:
|
|
print(f'FATAL: MapViewOfFile err={ctypes.get_last_error()}')
|
|
sys.exit(1)
|
|
return h, ptr
|
|
|
|
def w32(ptr, off, val):
|
|
ctypes.c_uint32.from_address(ptr + off).value = val
|
|
|
|
def send_ctrl(ptr, type_, arg0, arg1=0, channel=0):
|
|
idx = ctypes.c_uint32.from_address(ptr + OFF_CQ_COUNT).value
|
|
if idx >= 8:
|
|
print('FATAL: control queue full')
|
|
sys.exit(1)
|
|
base = ptr + OFF_CQ + idx * CTRL_ENTRY
|
|
ctypes.c_uint32.from_address(base + 0).value = type_
|
|
ctypes.c_uint32.from_address(base + 4).value = arg0
|
|
ctypes.c_uint32.from_address(base + 8).value = arg1
|
|
ctypes.c_uint32.from_address(base + 12).value = channel
|
|
ctypes.c_uint32.from_address(ptr + OFF_CQ_COUNT).value = idx + 1
|
|
print(f'sent ctrl type={type_} arg0={arg0}')
|
|
|
|
def send_midi(ptr, cmd, channel, pitch, velocity, live, data2=0, data3=0):
|
|
idx = ctypes.c_uint32.from_address(ptr + OFF_MQ_COUNT).value
|
|
if idx >= 64:
|
|
print('FATAL: midi queue full')
|
|
sys.exit(1)
|
|
base = ptr + OFF_MQ + idx * MIDI_ENTRY
|
|
ctypes.c_uint8.from_address(base + 0).value = cmd
|
|
ctypes.c_uint8.from_address(base + 1).value = channel
|
|
ctypes.c_uint8.from_address(base + 2).value = pitch
|
|
ctypes.c_uint8.from_address(base + 3).value = velocity
|
|
ctypes.c_uint8.from_address(base + 4).value = data2
|
|
ctypes.c_uint8.from_address(base + 5).value = data3
|
|
ctypes.c_uint8.from_address(base + 6).value = 1 if live else 0 # reserved[0] = live flag
|
|
ctypes.c_uint8.from_address(base + 7).value = 0
|
|
ctypes.c_uint32.from_address(base + 8).value = 0 # sampleOffset
|
|
ctypes.c_uint32.from_address(ptr + OFF_MQ_COUNT).value = idx + 1
|
|
print(f'sent midi cmd=0x{cmd:x} ch={channel} pitch={pitch} vel={velocity} live={live}')
|
|
|
|
h, ptr = get_shm()
|
|
print(f'SHM {SHM_NAME} mapped at {ptr:#x}')
|
|
|
|
# 1) transport STOP -> guard arms
|
|
send_ctrl(ptr, 3, 0) # TRANSPORT STOP
|
|
time.sleep(1.0)
|
|
# 2) non-live NOTE_ON -> must be dropped (no [midi] ON log)
|
|
send_midi(ptr, 0x9, 0, 60, 100, live=0)
|
|
time.sleep(1.0)
|
|
# 3) live NOTE_ON -> must pass
|
|
send_midi(ptr, 0x9, 0, 60, 100, live=1)
|
|
time.sleep(1.0)
|
|
# 4) non-live NOTE_ON again -> dropped (sanity)
|
|
send_midi(ptr, 0x9, 0, 62, 90, live=0)
|
|
time.sleep(1.0)
|
|
# 5) restore PLAY so app continues working
|
|
send_ctrl(ptr, 3, 1)
|
|
time.sleep(0.5)
|
|
|
|
k32.UnmapViewOfFile(ptr)
|
|
k32.CloseHandle(h)
|
|
print('DONE')
|