v0.53.0: Kaleidostrobe, the 2002 iTunes visualizer, as the fourth style

Reverse-engineered from its PowerPC binary and checked against the original
running under emulation: textures byte for byte and every triangle, over
3,600 frames. 25 fps like the plugin; its colour maps and wave images are
extracted from its own code; tests/kaleidostrobe_golden.npz was recorded
from the original.
This commit is contained in:
2026-10-14 12:02:22 -07:00
parent 6178f5d0f7
commit e38c751e39
21 changed files with 2109 additions and 3 deletions
+68
View File
@@ -0,0 +1,68 @@
"""Drive the emulated plugin through VisualPluginHandler like iTunes did."""
import struct, sys
sys.path.insert(0, __file__.rsplit('/', 1)[0])
from emu import KS
def fourcc(s): return struct.unpack('>I', s.encode())[0]
HANDLER = '_VisualPluginHandler'
class Plugin:
def __init__(self, seed=1, rect=(0, 0, 480, 640), ticks=1000):
k = self.k = KS(seed)
k.ticks = ticks
k.port_rect = rect
self.msg = k.alloc(0x100)
self.port = k.alloc(0x100)
self.render = k.alloc(0x802)
# init
k.call(HANDLER, fourcc('init'), self.msg, 0)
self.pd = k.r32(self.msg + 0x18)
# show: port, rect, options
k.w32(self.msg, self.port)
k.u.mem_write(self.msg + 4, struct.pack('>4h', *rect))
k.w32(self.msg + 0xc, 0)
k.call(HANDLER, fourcc('show'), self.msg, self.pd)
# play
k.w32(self.msg, 0); k.w32(self.msg + 4, 0)
k.call(HANDLER, fourcc('vply'), self.msg, self.pd)
def frame(self, wave=None, spec=None, dt_us=40001, dticks=None):
"""One 'vrnd'. wave/spec: 2x512 uint8 lists (or None for silence)."""
k = self.k
k.micro += dt_us
k.ticks += dticks if dticks is not None else round(dt_us * 60 / 1e6)
data = bytearray(0x802)
data[0] = 2
data[0x401] = 2
if wave is not None:
data[1:0x401] = bytes(wave[0]) + bytes(wave[1])
else:
data[1:0x401] = bytes([128]) * 1024
if spec is not None:
data[0x402:0x802] = bytes(spec[0]) + bytes(spec[1])
k.u.mem_write(self.render, bytes(data))
k.w32(self.msg, self.render)
k.w32(self.msg + 4, 0)
start = len(k.gl)
k.call(HANDLER, fourcc('vrnd'), self.msg, self.pd)
return k.gl[start:]
if __name__ == '__main__':
import random
p = Plugin()
print('pd', hex(p.pd), 'gl calls during show:', len(p.k.gl))
for name, args in p.k.gl[:40]:
print(' ', name, args)
rnd = random.Random(0)
for i in range(3):
wave = [[rnd.randrange(64, 192) for _ in range(512)] for _ in range(2)]
spec = [[rnd.randrange(0, 200) for _ in range(512)] for _ in range(2)]
calls = p.frame(wave, spec)
print('frame', i, len(calls))
from collections import Counter
print(Counter(n for n, a in calls))
for c in calls[:60]:
print(' ', c)