diff --git a/docs/zynaddsubfx-pressure-survey.md b/docs/zynaddsubfx-pressure-survey.md index 6911a84..9894bb5 100644 --- a/docs/zynaddsubfx-pressure-survey.md +++ b/docs/zynaddsubfx-pressure-survey.md @@ -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 diff --git a/state/evidence.jsonl b/state/evidence.jsonl index e3b0fb7..2506161 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -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."} diff --git a/state/map.yaml b/state/map.yaml index 1ceb639..24afe02 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -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 diff --git a/state/spine.yaml b/state/spine.yaml index 60cdd43..0c46097 100644 --- a/state/spine.yaml +++ b/state/spine.yaml @@ -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." diff --git a/tests/AquaSynth.Dsl.Tests/ZynReferenceParityTests.cs b/tests/AquaSynth.Dsl.Tests/ZynReferenceParityTests.cs index 3fb1855..7410e1b 100644 --- a/tests/AquaSynth.Dsl.Tests/ZynReferenceParityTests.cs +++ b/tests/AquaSynth.Dsl.Tests/ZynReferenceParityTests.cs @@ -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 + { + "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 + { + $"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(); diff --git a/tools/zyn-reference/ZynVoiceReference.cpp b/tools/zyn-reference/ZynVoiceReference.cpp new file mode 100644 index 0000000..aa66ec1 --- /dev/null +++ b/tools/zyn-reference/ZynVoiceReference.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +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(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(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(¶meters, synthParams, nullptr, nullptr, false); + + const int total = std::max(1, static_cast(seconds * synth.samplerate)); + std::vector rendered(total); + std::vector left(synth.buffersize); + std::vector 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(rendered.size())); +} + +} + +int main(int argc, char** argv) +{ + if(argc < 3 || argc > 6) { + std::cerr << "Usage: ZynVoiceReference [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(seconds * synth.samplerate) + << " frequency=" << frequency + << " kit=" << kitIndex << "\n"; + + FFT_cleanup(); + return 0; +} diff --git a/tools/zyn-reference/build-zyn-reference.ps1 b/tools/zyn-reference/build-zyn-reference.ps1 index bd0e909..e3c38f7 100644 --- a/tools/zyn-reference/build-zyn-reference.ps1 +++ b/tools/zyn-reference/build-zyn-reference.ps1 @@ -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 "@ diff --git a/tools/zyn-reference/patches/0002-add-zyn-pad-reference-tool.patch b/tools/zyn-reference/patches/0002-add-zyn-pad-reference-tool.patch index 07cf823..c8c990b 100644 --- a/tools/zyn-reference/patches/0002-add-zyn-pad-reference-tool.patch +++ b/tools/zyn-reference/patches/0002-add-zyn-pad-reference-tool.patch @@ -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})