Add Zyn formant parity fixtures

This commit is contained in:
Meta Vens 2026-05-17 20:52:34 +01:00
commit 2461821907
8 changed files with 349 additions and 5 deletions

View file

@ -60,6 +60,29 @@ semantics such as Zyn harmonic profile shaping, bandwidth/profile behavior, and
filter envelope/LFO lowering. These are listening and parity fixtures, not
library candidates.
## Upstream Formant Listening Batch
`ZynVoiceReferenceRendererSurveysUpstreamFormantFixturesWhenBuilt` renders a
small GPL ADD/formant batch through `ZynVoiceReference` when the reference
renderer is built:
- `Companion/0056-Wide Bass.xiz`
- `Companion/0121-Ghost Ensemble.xiz`
- `Companion/0119-Ghost Choir.xiz`
The batch intentionally requires multi-frame formant motion so the first workout
does not get hijacked by drum-kit envelope/source failures. Each run writes Zyn
reference WAVs, AquaSynth candidate WAVs, `candidate.aqua`, and `report.txt`
under `artifacts/parity/zyn-upstream-formant-fixtures/`. The first baseline
scores are poor but useful: Wide Bass `0.280990`, Ghost Ensemble `0.312813`,
and Ghost Choir `0.363644`. The shared missing features are exact Zyn
filter-position-driven vowel motion, formant smoothing/clearness, and ADD voice
source detail.
This is now ready for the real parity workout. The fixtures are expected to
scream. The point is that they scream in a chorus instead of letting one wounded
patch write the architecture.
## Pressure Conclusions
- **Named layers are real.** Anonymous AquaSynth voices can sketch small Zyn

View file

@ -101,5 +101,6 @@
{"date":"2026-05-17","topic":"package-versioning","lesson":"Use normal SemVer prerelease labels for AquaSynth packages. Do not encode the repo name into the version suffix; `0.1.0-alpha.2` is a real prerelease version, while `0.1.0-aquasynth.2` is project metadata cosplay.","evidence":"Renamed AquaSynth.Core and AquaSynth.Faust package versions from `0.1.0-aquasynth.2` to `0.1.0-alpha.2` before repacking Aquarium's local feed."}
{"date":"2026-05-17","topic":"solution-format","lesson":"AquaSynth uses a normal Visual Studio `.sln` solution because Rider does not reliably like `.slnx`. Keep tests, state, and docs looking for `AquaSynth.sln`, not `AquaSynth.slnx`.","evidence":"Generated `AquaSynth.sln` with `dotnet new sln --format sln`, added Core/Faust/tests projects, removed `AquaSynth.slnx`, and replaced solution references across tests/state."}
{"date":"2026-05-17","topic":"zyn-formant-motion","lesson":"AquaSynth now owns a first vowel-frame motion surface instead of treating Zyn formants as static filters or loose counted pressure. Voice `vowels=` stores `|`-separated formant banks and `vowel_hz` scans them; Zyn `FORMANT_FILTER` sequences can seed this, while exact Zyn cutoff-position driving, formant slowness, and vowel clearness remain named pressure.","evidence":"Added `FormantFrame`, voice `FormantFrames`/`FormantFrameRateHz`, parser support for `vowels=`/`vowel_hz`, Faust cyclic frame blending, and `ZynInstrumentReader.RebuildFirstFormantMotionAsAquaSynthScript`. `UpstreamWideBassExportsFormantMotionWorkoutWhenAvailable` exercises `Companion/0056-Wide Bass.xiz`; focused PatchScript/ZynInstrument tests passed 73/73 and non-reference tests passed 103/103."}
{"date":"2026-05-17","topic":"zyn-formant-parity-fixtures","lesson":"The first formant-motion parity workout needs multiple multi-frame ADD/formant fixtures and a real Zyn ADD note oracle. Avoid letting drum-kit envelope/source failures masquerade as formant evidence. Use Wide Bass, Ghost Ensemble, and Ghost Choir as the initial chorus; all currently fail enough to be useful.","evidence":"Added test-only `tools/zyn-reference/ZynVoiceReference.cpp`, wired it into the MSYS reference build, and added `ZynVoiceReferenceRendererSurveysUpstreamFormantFixturesWhenBuilt`. The batch writes ignored artifacts under `artifacts/parity/zyn-upstream-formant-fixtures/`; baseline scores were Wide Bass 0.280990, Ghost Ensemble 0.312813, Ghost Choir 0.363644. The native Zyn reference build succeeded and the focused formant fixture test passed."}

View file

@ -22,6 +22,7 @@ surfaces:
zyn_upstream_instrument_bank: external/zynaddsubfx/instruments
zyn_reference_build_script: tools/zyn-reference/build-zyn-reference.ps1
zyn_pad_reference_renderer: tools/zyn-reference/ZynPadReference.cpp
zyn_voice_reference_renderer: tools/zyn-reference/ZynVoiceReference.cpp
zyn_reference_build_patches: tools/zyn-reference/patches
zyn_project_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored
core_package: AquaSynth.Core 0.1.0-alpha.2

View file

@ -114,7 +114,7 @@ reference_targets:
priority: 2
purpose: "Additive, subtractive, PAD, formant, kit, and layered-instrument pressure test."
source: "https://github.com/zynaddsubfx/zynaddsubfx"
status: "Initial ZynInstrumentReader reads plain or gzip-compressed .xiz XML from project-authored fixtures and classifies active ADD/SUB/PAD engines, enabled kit items, layering, envelopes, free envelopes, LFOs, filters, formants, and effects as ReferencePatch features. `ZynInstrumentSurvey.CoverageDirectory` now classifies observed Zyn features into handled/counted/unknown buckets and writes `artifacts/parity/zyn-upstream-bank-survey/coverage.md` from the upstream GPL bank. First AquaSynth rebuilds exist for additive lead, PAD texture, and vocal/formant layer and now use named `layer` declarations. Additive rebuilds can use `harmonics` banks attached to layers, PAD rebuilds can use `spectrum` PADsynth-style FFT wavetable banks attached to layers, and PAD/vocal rebuilds use staged voice `env=rl` envelopes through layer defaults. Voices can now use `vowels=` formant frames plus `vowel_hz`; `ZynInstrumentReader.RebuildFirstFormantMotionAsAquaSynthScript` seeds that surface from Zyn `FORMANT_FILTER` vowel sequences for Wide Bass-style workouts while keeping exact Zyn position-driver/smoothing as named missing features. `ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript` now emits per-preset PAD `.aqua` rebuilds for enabled PAD kit items with OSCIL harmonic expansion, oracle-reported table base frequency, PAD detune, Zyn PAD profile/bandwidth/position semantics, static/filter-envelope low-pass mapping, low-pass note tracking, explicit low-pass Q, logarithmic Zyn envelope times, and calibrated PAD output gain. ZynAddSubFX source is pinned under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825 for test-only parity work, and `tools/zyn-reference/build-zyn-reference.ps1` now builds an ignored patched source copy with MSYS2/MinGW, proves `PadNoteTest`, and builds `ZynPadReference`, including a test-only `harmonics` mode for dumping normalized OscilGen amplitudes before PAD generation. The optional PAD reports emit note and source-table WAV/listening artifacts. Latest accepted upstream PAD pressure has DoublePadBass close enough by ear with log-mel 0.247748 / score 0.650029 after Q/order cascade, while Ghost Ensemble improves to log-mel 0.369402 / score 0.57229. The remaining Ghost gap is no longer first-32 OscilGen harmonic extraction; oracle harmonic dumps match those ratios. The upstream GPL instrument bank is initialized at `external/zynaddsubfx/instruments` commit c5c912131b31df5fdf372d2f06a25aaf2375837f; survey tests parse 1318/1318 .xiz files, write a pressure report and coverage map, and render a five-patch upstream PAD listening batch under ignored artifacts."
status: "Initial ZynInstrumentReader reads plain or gzip-compressed .xiz XML from project-authored fixtures and classifies active ADD/SUB/PAD engines, enabled kit items, layering, envelopes, free envelopes, LFOs, filters, formants, and effects as ReferencePatch features. `ZynInstrumentSurvey.CoverageDirectory` now classifies observed Zyn features into handled/counted/unknown buckets and writes `artifacts/parity/zyn-upstream-bank-survey/coverage.md` from the upstream GPL bank. First AquaSynth rebuilds exist for additive lead, PAD texture, and vocal/formant layer and now use named `layer` declarations. Additive rebuilds can use `harmonics` banks attached to layers, PAD rebuilds can use `spectrum` PADsynth-style FFT wavetable banks attached to layers, and PAD/vocal rebuilds use staged voice `env=rl` envelopes through layer defaults. Voices can now use `vowels=` formant frames plus `vowel_hz`; `ZynInstrumentReader.RebuildFirstFormantMotionAsAquaSynthScript` seeds that surface from Zyn `FORMANT_FILTER` vowel sequences for Wide Bass-style workouts while keeping exact Zyn position-driver/smoothing as named missing features. `ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript` now emits per-preset PAD `.aqua` rebuilds for enabled PAD kit items with OSCIL harmonic expansion, oracle-reported table base frequency, PAD detune, Zyn PAD profile/bandwidth/position semantics, static/filter-envelope low-pass mapping, low-pass note tracking, explicit low-pass Q, logarithmic Zyn envelope times, and calibrated PAD output gain. ZynAddSubFX source is pinned under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825 for test-only parity work, and `tools/zyn-reference/build-zyn-reference.ps1` now builds an ignored patched source copy with MSYS2/MinGW, proves `PadNoteTest`, and builds `ZynPadReference`, including a test-only `harmonics` mode for dumping normalized OscilGen amplitudes before PAD generation, plus `ZynVoiceReference` for ADD note reference renders. The optional PAD reports emit note and source-table WAV/listening artifacts. The optional formant batch renders Wide Bass, Ghost Ensemble, and Ghost Choir under `artifacts/parity/zyn-upstream-formant-fixtures`; initial scores are 0.280990, 0.312813, and 0.363644, which makes the next workout real rather than flattering. Latest accepted upstream PAD pressure has DoublePadBass close enough by ear with log-mel 0.247748 / score 0.650029 after Q/order cascade, while Ghost Ensemble improves to log-mel 0.369402 / score 0.57229. The remaining Ghost gap is no longer first-32 OscilGen harmonic extraction; oracle harmonic dumps match those ratios. The upstream GPL instrument bank is initialized at `external/zynaddsubfx/instruments` commit c5c912131b31df5fdf372d2f06a25aaf2375837f; survey tests parse 1318/1318 .xiz files, write a pressure report and coverage map, and render a five-patch upstream PAD listening batch under ignored artifacts."
first_slice:
- "Use the Zyn PAD oracle report to isolate source-table, playback/gain, envelope, and fixture-mapping mismatch before further Zyn DSL golf."
- "Use Wide Bass formant-frame output as the foothold before attempting exact Zyn formant cutoff-position driving."

View file

@ -358,6 +358,146 @@ public sealed class ZynReferenceParityTests
await File.WriteAllLinesAsync(Path.Combine(artifactDir, $"survey-{Timestamp()}.txt"), report);
}
[Fact]
public async Task ZynVoiceReferenceRendererSurveysUpstreamFormantFixturesWhenBuilt()
{
var root = RepositoryRoot();
var bash = @"C:\msys64\usr\bin\bash.exe";
var renderer = Path.Combine(root, "artifacts", "zyn-reference-build-msys", "src", "Tests", "ZynVoiceReference.exe");
var bankRoot = Path.Combine(root, "external", "zynaddsubfx", "instruments", "banks");
if (!File.Exists(bash) || !File.Exists(renderer) || !Directory.Exists(bankRoot))
{
return;
}
var selected = Directory.GetFiles(bankRoot, "*.xiz", SearchOption.AllDirectories)
.Select(file => new
{
File = file,
Instrument = ZynInstrumentReader.ParseFile(file),
Rebuild = TryRebuildFormantMotion(file)
})
.Where(item => item.Instrument.KitItems.Any(kit =>
kit.Enabled &&
kit.Engines.Contains(ZynEngine.AddSynth) &&
kit.FormantFilterCount > 0) &&
item.Rebuild is not null &&
item.Rebuild.MatchedFeatures.Any(feature =>
feature.Name == "formant_frames" &&
int.TryParse(feature.Value, out var frameCount) &&
frameCount > 1))
.Select(item => new
{
item.File,
item.Instrument,
Rebuild = item.Rebuild!,
Score = ZynInstrumentSurvey.ComplexityScore(item.Instrument.Features())
})
.OrderByDescending(item => item.Score)
.ThenBy(item => Path.GetRelativePath(bankRoot, item.File), StringComparer.OrdinalIgnoreCase)
.Take(3)
.ToArray();
Assert.True(selected.Length >= 2, "The Zyn formant parity workout needs at least two upstream ADD/formant fixtures.");
var artifactDir = Path.Combine(root, "artifacts", "parity", "zyn-upstream-formant-fixtures");
Directory.CreateDirectory(artifactDir);
var report = new List<string>
{
"Zyn upstream GPL formant fixture survey",
"source: https://github.com/zynaddsubfx/instruments",
"license: GPL test/development corpus via upstream ZynAddSubFX instruments submodule",
$"renderer: {renderer}"
};
foreach (var selectedItem in selected)
{
var input = selectedItem.File;
var relative = Path.GetRelativePath(bankRoot, input);
var rebuild = selectedItem.Rebuild;
var runDir = Path.Combine(artifactDir, RunFolderName(Path.GetFileNameWithoutExtension(input)));
Directory.CreateDirectory(runDir);
var referenceRaw = Path.Combine(runDir, "reference-zyn.f32");
var referenceWav = Path.Combine(runDir, "reference-zyn.wav");
var candidateWav = Path.Combine(runDir, "candidate-aquasynth.wav");
var reference = await RunAsync(bash, ["-lc", ZynVoiceCommand(root, input, referenceRaw, rebuild.KitItemId, "110", "1.8")]);
Assert.Equal(0, reference.ExitCode);
var referenceSamples = await ReadFloat32Async(referenceRaw);
Assert.True(referenceSamples.Length > 0);
WriteWav(referenceWav, referenceSamples, 44100, 0.9f);
await File.WriteAllTextAsync(Path.Combine(runDir, "candidate.aqua"), rebuild.Script);
var candidate = await FaustCompiler.RenderAsync(
FaustEmitter.EmitScript(rebuild.Script, new FaustExportOptions("zyn_formant_candidate")).Source,
new FaustRenderOptions(DurationSeconds: 1.8f));
var features = AudioAnalyzer.AnalyzeAudio(referenceSamples).Features;
var runReport = new List<string>
{
$"fixture: {relative}",
$"instrument: {selectedItem.Instrument.Name}",
$"input: {input}",
$"kit_index: {rebuild.KitItemId}",
$"candidate: generated Zyn formant-motion rebuild",
$"reference_stdout: {reference.Stdout.Trim()}",
$"peak: {Peak(referenceSamples):0.######}",
$"rms: {Rms(referenceSamples):0.######}",
$"duration: {features.DurationSeconds:0.######}",
$"centroid: {features.SpectralCentroidHz:0.######}"
};
runReport.AddRange(rebuild.MatchedFeatures.Select(feature => $"matched_{feature.Name}: {feature.Value}"));
runReport.AddRange(rebuild.MissingFeatures.Select(feature => $"missing_{feature.Name}: {feature.Value} - {feature.Notes}"));
report.Add("");
report.Add($"fixture: {relative}");
report.Add($"instrument: {selectedItem.Instrument.Name}");
report.Add($"kit_index: {rebuild.KitItemId}");
report.Add($"reference_stdout: {reference.Stdout.Trim()}");
report.Add($"peak: {Peak(referenceSamples):0.######}");
report.Add($"rms: {Rms(referenceSamples):0.######}");
report.Add($"duration: {features.DurationSeconds:0.######}");
report.Add($"centroid: {features.SpectralCentroidHz:0.######}");
report.Add($"artifact_dir: {runDir}");
report.AddRange(rebuild.MatchedFeatures.Select(feature => $"matched_{feature.Name}: {feature.Value}"));
report.AddRange(rebuild.MissingFeatures.Select(feature => $"missing_{feature.Name}: {feature.Value} - {feature.Notes}"));
if (candidate is { Samples.Length: > 0 })
{
WriteWav(candidateWav, candidate.Samples, candidate.SampleRate, 0.9f);
var comparison = AudioAnalyzer.CompareAudio(referenceSamples, candidate.Samples);
report.Add($"log_mel_distance: {comparison.LogMelDistance:0.######}");
report.Add($"envelope_distance: {comparison.EnvelopeDistance:0.######}");
report.Add($"rms_ratio: {comparison.RmsRatio:0.######}");
report.Add($"centroid_ratio: {comparison.CentroidRatio:0.######}");
report.Add($"score: {comparison.Score:0.######}");
runReport.Add($"log_mel_distance: {comparison.LogMelDistance:0.######}");
runReport.Add($"envelope_distance: {comparison.EnvelopeDistance:0.######}");
runReport.Add($"rms_ratio: {comparison.RmsRatio:0.######}");
runReport.Add($"centroid_ratio: {comparison.CentroidRatio:0.######}");
runReport.Add($"score: {comparison.Score:0.######}");
}
await File.WriteAllLinesAsync(Path.Combine(runDir, "report.txt"), runReport);
}
await File.WriteAllLinesAsync(Path.Combine(artifactDir, $"survey-{Timestamp()}.txt"), report);
}
private static ZynFormantMotionRebuild? TryRebuildFormantMotion(string path)
{
try
{
return ZynInstrumentReader.RebuildFirstFormantMotionAsAquaSynthScript(path);
}
catch (ArgumentException)
{
return null;
}
}
private static string RepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
@ -412,6 +552,18 @@ public sealed class ZynReferenceParityTests
..modeArguments
]);
private static string ZynVoiceCommand(string root, string input, string output, int kitIndex, string frequency, string seconds) =>
string.Join(' ', [
"export PATH=/mingw64/bin:/usr/bin:/bin:$PATH;",
$"cd {BashQuote(ToMsysPath(root))};",
"./artifacts/zyn-reference-build-msys/src/Tests/ZynVoiceReference.exe",
BashQuote(ToMsysPath(input)),
BashQuote(ToMsysPath(output)),
kitIndex.ToString(System.Globalization.CultureInfo.InvariantCulture),
frequency,
seconds
]);
private static string SanitizeArtifactName(string name)
{
var chars = name.Select(ch => char.IsLetterOrDigit(ch) ? char.ToLowerInvariant(ch) : '-').ToArray();

View file

@ -0,0 +1,149 @@
/*
Test-only AquaSynth ZynAddSubFX ADD voice oracle.
This file links against the pinned ZynAddSubFX reference source in an ignored
artifact build. It is development/parity tooling, not AquaSynth runtime code.
*/
#include "../globals.h"
#include "../DSP/FFTwrapper.h"
#include "../Misc/Allocator.h"
#include "../Misc/Time.h"
#include "../Misc/Util.h"
#include "../Misc/XMLwrapper.h"
#include "../Params/ADnoteParameters.h"
#include "../Params/Controller.h"
#include "../Synth/ADnote.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace zyn;
namespace {
bool enterInstrument(XMLwrapper& xml)
{
if(xml.enterbranch("INSTRUMENT"))
return true;
if(xml.enterbranch("MASTER")) {
if(xml.enterbranch("PART", 0) && xml.enterbranch("INSTRUMENT"))
return true;
}
return false;
}
int parseInt(const char* text, const char* name)
{
char* end = nullptr;
const long value = std::strtol(text, &end, 10);
if(end == text || *end != '\0') {
std::cerr << "Invalid " << name << ": " << text << "\n";
std::exit(2);
}
return static_cast<int>(value);
}
void writeFloat32(const std::string& path, const float* samples, int count)
{
std::ofstream output(path, std::ios::binary);
if(!output) {
std::cerr << "Could not open output file: " << path << "\n";
std::exit(2);
}
output.write(reinterpret_cast<const char*>(samples), sizeof(float) * count);
if(!output) {
std::cerr << "Could not write output file: " << path << "\n";
std::exit(2);
}
}
void renderAddNote(
ADnoteParameters& parameters,
const SYNTH_T& synth,
const AbsTime& time,
const std::string& path,
float frequency,
float seconds)
{
Alloc memory;
Controller controller(synth, &time);
const float log2Frequency = std::log2(frequency);
SynthParams synthParams{memory, controller, synth, time, 120, nullptr, log2Frequency, false, prng()};
ADnote note(&parameters, synthParams, nullptr, nullptr, false);
const int total = std::max(1, static_cast<int>(seconds * synth.samplerate));
std::vector<float> rendered(total);
std::vector<float> left(synth.buffersize);
std::vector<float> right(synth.buffersize);
int written = 0;
while(written < total) {
std::fill(left.begin(), left.end(), 0.0f);
std::fill(right.begin(), right.end(), 0.0f);
note.noteout(left.data(), right.data());
const int count = std::min(synth.buffersize, total - written);
for(int i = 0; i < count; ++i)
rendered[written + i] = 0.5f * (left[i] + right[i]);
written += count;
}
writeFloat32(path, rendered.data(), static_cast<int>(rendered.size()));
}
}
int main(int argc, char** argv)
{
if(argc < 3 || argc > 6) {
std::cerr << "Usage: ZynVoiceReference <input.xiz> <output.f32> [kit-index] [frequency-hz] [seconds]\n";
return 2;
}
const std::string inputPath = argv[1];
const std::string outputPath = argv[2];
const int kitIndex = argc >= 4 ? parseInt(argv[3], "kit index") : 0;
const float frequency = argc >= 5 ? std::strtof(argv[4], nullptr) : 110.0f;
const float seconds = argc >= 6 ? std::strtof(argv[5], nullptr) : 1.8f;
SYNTH_T synth;
synth.samplerate = 44100;
synth.buffersize = 256;
synth.oscilsize = 1024;
synth.alias(false);
AbsTime time(synth);
FFTwrapper fft(synth.oscilsize);
ADnoteParameters parameters(synth, &fft, &time);
XMLwrapper xml;
if(xml.loadXMLfile(inputPath) != 0) {
std::cerr << "Could not load Zyn instrument: " << inputPath << "\n";
return 2;
}
if(!enterInstrument(xml) ||
!xml.enterbranch("INSTRUMENT_KIT") ||
!xml.enterbranch("INSTRUMENT_KIT_ITEM", kitIndex) ||
!xml.enterbranch("ADD_SYNTH_PARAMETERS")) {
std::cerr << "Could not find ADD_SYNTH_PARAMETERS for kit item " << kitIndex
<< " in " << inputPath << "\n";
return 2;
}
parameters.getfromXML(xml);
renderAddNote(parameters, synth, time, outputPath, frequency, seconds);
std::cout << "mode=add-note samples=" << static_cast<int>(seconds * synth.samplerate)
<< " frequency=" << frequency
<< " kit=" << kitIndex << "\n";
FFT_cleanup();
return 0;
}

View file

@ -36,7 +36,8 @@ $repoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\.."))
$zynRoot = Join-Path $repoRoot "external\zynaddsubfx"
$workSource = [System.IO.Path]::GetFullPath((Join-Path $repoRoot $SourceDir))
$patchRoot = Join-Path $PSScriptRoot "patches"
$referenceSource = Join-Path $PSScriptRoot "ZynPadReference.cpp"
$padReferenceSource = Join-Path $PSScriptRoot "ZynPadReference.cpp"
$voiceReferenceSource = Join-Path $PSScriptRoot "ZynVoiceReference.cpp"
$bash = Join-Path $MsysRoot "usr\bin\bash.exe"
if (-not (Test-Path $bash)) {
@ -62,7 +63,8 @@ if ($LASTEXITCODE -gt 7) {
throw "Failed to copy Zyn source to $workSource"
}
Copy-Item -LiteralPath $referenceSource -Destination (Join-Path $workSource "src\Tests\ZynPadReference.cpp") -Force
Copy-Item -LiteralPath $padReferenceSource -Destination (Join-Path $workSource "src\Tests\ZynPadReference.cpp") -Force
Copy-Item -LiteralPath $voiceReferenceSource -Destination (Join-Path $workSource "src\Tests\ZynVoiceReference.cpp") -Force
foreach ($patchPath in Get-ChildItem -LiteralPath $patchRoot -Filter "*.patch" | Sort-Object Name | Select-Object -ExpandProperty FullName) {
$applyCheck = Invoke-GitProbe @("-C", $workSource, "apply", "--check", $patchPath)
@ -84,7 +86,19 @@ foreach ($patchPath in Get-ChildItem -LiteralPath $patchRoot -Filter "*.patch" |
$repoUnix = Convert-ToMsysPath $repoRoot
$sourceUnix = Convert-ToMsysPath $workSource
$buildUnix = Convert-ToMsysPath (Join-Path $repoRoot $BuildDir)
$buildPath = [System.IO.Path]::GetFullPath((Join-Path $repoRoot $BuildDir))
$artifactRoot = [System.IO.Path]::GetFullPath((Join-Path $repoRoot "artifacts"))
$cachePath = Join-Path $buildPath "CMakeCache.txt"
if ((Test-Path $cachePath) -and
(Select-String -LiteralPath $cachePath -SimpleMatch $workSource -Quiet) -eq $false) {
if (-not $buildPath.StartsWith($artifactRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to clear non-artifact Zyn build directory: $buildPath"
}
Remove-Item -LiteralPath $buildPath -Recurse -Force
}
$buildUnix = Convert-ToMsysPath $buildPath
$script = @"
set -euo pipefail
export PATH=/mingw64/bin:/usr/bin:/bin:`$PATH
@ -98,6 +112,7 @@ cmake -S "$sourceUnix" -B "$buildUnix" -G Ninja \
-DWerror=OFF
cmake --build "$buildUnix" --target PadNoteTest -j 4
cmake --build "$buildUnix" --target ZynPadReference -j 4
cmake --build "$buildUnix" --target ZynVoiceReference -j 4
ctest --test-dir "$buildUnix" -R PadNoteTest --output-on-failure
"@

View file

@ -2,12 +2,15 @@ diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt
index 871a0457..567cb3f9 100644
--- a/src/Tests/CMakeLists.txt
+++ b/src/Tests/CMakeLists.txt
@@ -25,6 +25,9 @@ set(test_lib zynaddsubfx_core ${GUI_LIBRARIES} ${ZLIB_LIBRARY} ${FFTW3F_LIBRARIE
@@ -25,6 +25,12 @@ set(test_lib zynaddsubfx_core ${GUI_LIBRARIES} ${ZLIB_LIBRARY} ${FFTW3F_LIBRARIE
message(STATUS "Linking tests with: ${test_lib}")
+add_executable(ZynPadReference ZynPadReference.cpp)
+target_link_libraries(ZynPadReference ${test_lib})
+
+add_executable(ZynVoiceReference ZynVoiceReference.cpp)
+target_link_libraries(ZynVoiceReference ${test_lib})
+
quick_test(AdNoteTest ${test_lib})
quick_test(AllocatorTest ${test_lib})