Add Zyn formant parity fixtures
This commit is contained in:
parent
788e72e177
commit
2461821907
8 changed files with 349 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
149
tools/zyn-reference/ZynVoiceReference.cpp
Normal file
149
tools/zyn-reference/ZynVoiceReference.cpp
Normal 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(¶meters, 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
"@
|
||||
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
Loading…
Reference in a new issue