64 lines
2.6 KiB
Python
64 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""rpp_setparam.py — rewrite PARAM values inside a REAPER .rpp soothe2 state chunk.
|
|
|
|
!!! CAVEAT (NOTES_LEVEL 22c): the XML <PARAM> list is a decorative UI-restore copy,
|
|
NOT the VST3 state source. Editing values here does NOT change plugin audio behaviour
|
|
(plugin falls back to defaults on any length mismatch). For real param changes use
|
|
setparam.lua (TrackFX_SetParam bridge). This tool is kept for format surgery only.
|
|
|
|
Block layout (joined from N consecutive base64 lines):
|
|
[u32 A=len(rest)][u32 ver=1]['VC2!'][u32 B=len(xml)][xml][tail: JUCEPrivateData...]
|
|
Both length fields MUST be updated when xml size changes, otherwise the plugin
|
|
silently rejects the state and falls back to defaults.
|
|
|
|
Usage:
|
|
python3 scripts/rpp_setparam.py in.rpp out.rpp depth=1.0 release=0.5
|
|
"""
|
|
import re, base64, struct, sys
|
|
|
|
|
|
def find_block(lines):
|
|
idx = [i for i, ln in enumerate(lines) if re.fullmatch(r"[A-Za-z0-9+/=]{40,}", ln.strip())]
|
|
start = None
|
|
for i in idx:
|
|
if base64.b64decode(lines[i].strip())[:1] == b"\x95":
|
|
start = i; break
|
|
if start is None:
|
|
raise SystemExit("no state block found")
|
|
end = start
|
|
while end + 1 < len(lines) and re.fullmatch(r"[A-Za-z0-9+/=]{40,}", lines[end + 1].strip()):
|
|
end += 1
|
|
return start, end
|
|
|
|
|
|
def main():
|
|
inp, outp = sys.argv[1], sys.argv[2]
|
|
sets = dict(kv.split("=", 1) for kv in sys.argv[3:])
|
|
lines = open(inp).read().splitlines()
|
|
start, end = find_block(lines)
|
|
buf = b"".join(base64.b64decode(lines[k].strip()) for k in range(start, end + 1))
|
|
a, ver, magic, blen = struct.unpack_from("<II4sI", buf, 0)
|
|
i = buf.find(b"<?xml")
|
|
head, xml, tail = buf[:i], buf[i:i + blen], buf[i + blen:]
|
|
for k, v in sets.items():
|
|
pat = f'<PARAM id="{k}" value="'
|
|
j = xml.find(pat.encode())
|
|
assert j >= 0, f"param {k} not found"
|
|
v0 = j + len(pat)
|
|
e = xml.index(b'"/>', v0)
|
|
print(f" {k}: {xml[v0:e].decode()} -> {v}")
|
|
xml = xml[:v0] + v.encode() + xml[e:]
|
|
new_blen = len(xml)
|
|
new_buf = struct.pack("<II4sI", a - blen + new_blen, ver, magic, new_blen) + xml + tail
|
|
ind = re.match(r"\s*", lines[start]).group(0)
|
|
width = max(len(lines[k]) - len(ind) for k in range(start, end + 1))
|
|
enc = base64.b64encode(new_buf).decode("ascii")
|
|
wrapped = [ind + enc[j:j + width] for j in range(0, len(enc), width)]
|
|
lines[start:end + 1] = wrapped
|
|
open(outp, "w").write("\n".join(lines) + "\n")
|
|
print(f"wrote {outp} (block {end-start+1}->{len(wrapped)} lines, xml {blen}->{new_blen})")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|