refactor(bridge): split fx bridge, stabilize VST3 host chain, drop old install engines
- 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
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# Test VST2 host: create SHM, spawn bridge (stdout->file), LOAD_INSTRUMENT (VST2),
|
||||
# noteOn, read ring audio energy. Verify [Vst2Instrument] loaded log.
|
||||
import ctypes, subprocess, sys, time, os, tempfile
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||
SHM_NAME = 'SonicForge_DAW_IPC_V2TEST'
|
||||
SHM_SIZE = 30000
|
||||
PLUGIN = r'C:\Program Files\Steinberg\VSTPlugins\DUNE 3.dll'
|
||||
BRIDGE = r'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe'
|
||||
|
||||
k32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
||||
k32.CreateFileMappingA.restype = ctypes.c_void_p
|
||||
k32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, 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]
|
||||
|
||||
h = k32.CreateFileMappingA(ctypes.c_void_p(-1), None, 0x04, 0, SHM_SIZE, SHM_NAME.encode())
|
||||
if not h:
|
||||
print('FATAL CreateFileMappingA', ctypes.get_last_error()); sys.exit(1)
|
||||
ptr = k32.MapViewOfFile(h, 0x02, 0, 0, 0)
|
||||
if not ptr:
|
||||
print('FATAL MapViewOfFile', ctypes.get_last_error()); sys.exit(1)
|
||||
|
||||
def w32(off, val): ctypes.c_uint32.from_address(ptr + off).value = val
|
||||
def r32(off): return ctypes.c_uint32.from_address(ptr + off).value
|
||||
def energy(off, n=256):
|
||||
arr = (ctypes.c_float * n).from_address(ptr + off)
|
||||
return sum(abs(v) for v in arr)
|
||||
|
||||
OFF_MQ = 2064; OFF_MQ_COUNT = 2832
|
||||
OFF_CQ = 2836; OFF_CQ_COUNT = 11156
|
||||
OFF_HEARTBEAT = 11164
|
||||
MIDI_ENTRY = 12; CTRL_ENTRY = 1040
|
||||
OFF_RING_L = 11172; OFF_RING_R = 11172 + 8192
|
||||
OFF_RING_MASK = 11168
|
||||
|
||||
def send_ctrl(type_, arg0, arg1, channel, path=b''):
|
||||
idx = r32(OFF_CQ_COUNT)
|
||||
if idx >= 8: print('FATAL ctrl 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
|
||||
if path:
|
||||
ctypes.memmove(base + 16, path, len(path))
|
||||
ctypes.c_char.from_address(base + 16 + len(path)).value = b'\0'
|
||||
w32(OFF_CQ_COUNT, idx + 1)
|
||||
print(f'sent ctrl type={type_} arg0={arg0} arg1={arg1} ch={channel}')
|
||||
|
||||
def send_midi(cmd, channel, pitch, velocity, live=1, d2=0, d3=0):
|
||||
idx = r32(OFF_MQ_COUNT)
|
||||
if idx >= 64: print('FATAL midi 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 = d2
|
||||
ctypes.c_uint8.from_address(base + 5).value = d3
|
||||
ctypes.c_uint8.from_address(base + 6).value = 1 if live else 0
|
||||
ctypes.c_uint32.from_address(base + 8).value = 0
|
||||
w32(OFF_MQ_COUNT, idx + 1)
|
||||
print(f'sent midi cmd=0x{cmd:x} ch={channel} p={pitch} v={velocity}')
|
||||
|
||||
env = dict(os.environ)
|
||||
env['SF_SHM_NAME'] = SHM_NAME
|
||||
env['SF_SAMPLE_RATE'] = '44100'
|
||||
env['SF_BLOCK_SIZE'] = '256'
|
||||
env['SF_PARENT_PID'] = str(os.getpid())
|
||||
empty_state = os.path.join(tempfile.gettempdir(), 'sf_v2test_state.json')
|
||||
open(empty_state, 'w').write('{}')
|
||||
env['SF_STATE_FILE'] = empty_state
|
||||
|
||||
bout = tempfile.NamedTemporaryFile(delete=False, suffix='.log', mode='w', encoding='utf-8')
|
||||
bout_path = bout.name
|
||||
bout.close()
|
||||
with open(bout_path, 'w', encoding='utf-8') as f:
|
||||
proc = subprocess.Popen([BRIDGE, '--shm', SHM_NAME], env=env,
|
||||
stdout=f, stderr=subprocess.STDOUT)
|
||||
print('bridge pid', proc.pid)
|
||||
time.sleep(2.5)
|
||||
|
||||
def tail_bridge(tag):
|
||||
with open(bout_path, encoding='utf-8', errors='replace') as f:
|
||||
lines = f.read().splitlines()
|
||||
print(f'--- bridge {tag} ({len(lines)} lines) ---')
|
||||
for l in lines[-50:]:
|
||||
print(l)
|
||||
|
||||
# LOAD VST2 (type enum: VST2=1)
|
||||
send_ctrl(2, 1, len(PLUGIN.encode()), 0, PLUGIN.encode())
|
||||
print('proc alive after load:', proc.poll() is None)
|
||||
time.sleep(2.0)
|
||||
tail_bridge('after LOAD')
|
||||
|
||||
# TRANSPORT PLAY so plugins gating DSP on transport start producing sound
|
||||
send_ctrl(3, 1, 0, 0)
|
||||
time.sleep(0.3)
|
||||
# CC7 volume max (some presets default to 0 volume)
|
||||
send_midi(0xB, 0, 7, 127)
|
||||
time.sleep(0.2)
|
||||
|
||||
send_midi(0x9, 0, 60, 100)
|
||||
for t in (0.5, 1.0, 2.0, 4.0):
|
||||
time.sleep(t)
|
||||
print('energy at +', t, ':', round(max(sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_L + s * 1024)) + sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_R + s * 1024)) for s in range(8)), 3))
|
||||
|
||||
mask = r32(OFF_RING_MASK) or 7
|
||||
wi = r32(4)
|
||||
slots = [(wi - i) & mask for i in range(mask + 1)]
|
||||
energies = []
|
||||
for s in slots:
|
||||
e = energy(OFF_RING_L + s * 1024) + energy(OFF_RING_R + s * 1024)
|
||||
energies.append(e)
|
||||
print('ringMask', mask, 'writeIndex', wi, 'heartbeat', r32(OFF_HEARTBEAT))
|
||||
print('slot energies (L+R):', [round(e, 3) for e in energies])
|
||||
print('max energy:', round(max(energies), 3))
|
||||
|
||||
send_midi(0x8, 0, 60, 0)
|
||||
time.sleep(0.5)
|
||||
tail_bridge('tail')
|
||||
|
||||
proc.terminate()
|
||||
time.sleep(1)
|
||||
if proc.poll() is None:
|
||||
proc.kill()
|
||||
k32.UnmapViewOfFile(ptr); k32.CloseHandle(h)
|
||||
print('--- grep Vst2Instrument in log ---')
|
||||
with open(bout_path, encoding='utf-8', errors='replace') as f:
|
||||
hits = [l.rstrip() for l in f if 'Vst2Instrument' in l]
|
||||
for l in hits[-20:]:
|
||||
print(l)
|
||||
os.remove(bout_path)
|
||||
print('done')
|
||||
Reference in New Issue
Block a user