#!/usr/bin/env python3 """Independent checks of the optional JUCE CLI's file and rendering contracts.""" from pathlib import Path import hashlib import math import struct import subprocess import sys import tempfile binary = str(Path(sys.argv[1]).resolve()) def run(*args, expected=0): result = subprocess.run([binary, *map(str, args)], capture_output=True, text=True) assert result.returncode == expected, (args, result.returncode, result.stdout, result.stderr) return result def wav(path): data = path.read_bytes() assert data[:4] == b"RIFF" and data[8:12] == b"WAVE" chunks = {} cursor = 12 while cursor + 8 <= len(data): kind, size = struct.unpack_from("<4sI", data, cursor) chunks[kind] = data[cursor + 8: cursor + 8 + size] cursor += 8 + size + size % 2 code, channels, rate, _, _, bits = struct.unpack_from(" .005 return len(samples) // channels def vlq(value): result = [value & 127] while value >> 7: value >>= 7 result.insert(0, 128 | (value & 127)) return bytes(result) def track(data): return b"MTrk" + struct.pack(">I", len(data)) + data with tempfile.TemporaryDirectory(prefix="xtc-juce-accept-") as temporary: root = Path(temporary) output = root / "demo.wav" run("--render", output) assert wav(output) == 216000 # one-second chord + .5 s hold + 3 s tail digest = hashlib.sha256(output.read_bytes()).digest() run("--render", output, expected=1) assert hashlib.sha256(output.read_bytes()).digest() == digest midi = root / "tempo.mid" header = b"MThd" + struct.pack(">IHHH", 6, 1, 2, 480) tempo = b"\x00\xff\x51\x03\x07\xa1\x20" + vlq(480) + b"\xff\x51\x03\x0f\x42\x40" + b"\x00\xff\x2f\x00" notes = b"\x00\x90\x3c\x64" + vlq(960) + b"\x80\x3c\x00\x00\xff\x2f\x00" midi.write_bytes(header + track(tempo) + track(notes)) imported = root / "imported.wav" run("--render", imported, "--midi", midi) assert wav(imported) == 240000 # .5 s at 120 BPM + 1 s at 60 BPM + .5 + 3 missing_output = root / "failed.wav" run("--render", missing_output, "--midi", root / "absent.mid", expected=1) midi.write_bytes(b"not midi") run("--render", missing_output, "--midi", midi, expected=1) # Dense MIDI is rejected instead of publishing a partially dropped render. dense = b"\x00\x90\x3c\x64" * 257 + vlq(960) + b"\x80\x3c\x00\x00\xff\x2f\x00" midi.write_bytes(b"MThd" + struct.pack(">IHHH", 6, 0, 1, 480) + track(dense)) run("--render", missing_output, "--midi", midi, expected=1) assert not missing_output.exists() and not list(root.glob(".xtc-juce-*")) png = root / "editor.png" run("--snapshot", png) contents = png.read_bytes() assert contents[:8] == b"\x89PNG\r\n\x1a\n" assert struct.unpack_from(">II", contents, 16) == (1560, 980) png_digest = hashlib.sha256(contents).digest() run("--snapshot", png, expected=1) assert hashlib.sha256(png.read_bytes()).digest() == png_digest run("--invalid", expected=2) print("JUCE examples: float WAV, tempo-map MIDI, no-clobber, failure cleanup and actual editor render/reopen passed.")