JUCE SDK integration.
This commit is contained in:
parent
8e09f58944
commit
adcbcdfd5e
16 changed files with 1057 additions and 0 deletions
80
tests/integration/test_juce_examples.py
Normal file
80
tests/integration/test_juce_examples.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/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("<HHIIHH", chunks[b"fmt "])
|
||||
assert code in (3, 0xFFFE) and channels == 2 and rate == 48000 and bits == 32
|
||||
if code == 0xFFFE:
|
||||
assert struct.unpack_from("<I", chunks[b"fmt "], 24)[0] == 3
|
||||
samples = struct.unpack("<" + "f" * (len(chunks[b"data"]) // 4), chunks[b"data"])
|
||||
assert all(math.isfinite(x) for x in samples)
|
||||
assert max(map(abs, samples)) > .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.")
|
||||
30
tests/integration/test_juce_plugin.c
Normal file
30
tests/integration/test_juce_plugin.c
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* Exercise the JUCE-built VST3 through xtc's existing C host, not JUCE's host. */
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
xtc_plugin_spec spec;
|
||||
xtc_plugin *plugin=NULL;
|
||||
xtc_midi_event events[]={{13,XTC_MIDI_NOTE_ON,0,69,100},{200,XTC_MIDI_ALL_OFF,0,0,0}};
|
||||
float data[256]; size_t i, block; float peak=0;
|
||||
assert(argc==2 && argv[1][0]=='/');
|
||||
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST3,argv[1],0,48000,&spec)==XTC_OK);
|
||||
assert(spec.instrument && spec.inputs==0 && spec.format==XTC_PLUGIN_VST3);
|
||||
assert(xtc_plugin_create(&spec,48000,256,&plugin)==XTC_OK);
|
||||
assert(xtc_plugin_process_midi(plugin,events,2,data,256)==XTC_OK);
|
||||
for(i=0;i<13;++i)assert(data[i]==0);
|
||||
for(i=13;i<200;++i){assert(isfinite(data[i]));if(fabsf(data[i])>peak)peak=fabsf(data[i]);}
|
||||
assert(peak>.005f);
|
||||
/* The C VST3 host maps ALL_OFF to individual note-offs, allowing release. */
|
||||
for(i=200;i<256;++i)assert(isfinite(data[i]));
|
||||
for(block=0;block<200;++block) {
|
||||
assert(xtc_plugin_process_midi(plugin,NULL,0,data,256)==XTC_OK);
|
||||
for(i=0;i<256;++i)assert(isfinite(data[i]));
|
||||
}
|
||||
for(i=0;i<256;++i)assert(data[i]==0);
|
||||
xtc_plugin_destroy(plugin);
|
||||
puts("JUCE VST3 loaded through the xtc C host: identity, mono negotiation, MIDI offsets, note-off release and eventual silence passed.");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue