Add PAD spectral cloud DSL surface
This commit is contained in:
parent
bfd1e19e5b
commit
ff43eef5e3
11 changed files with 212 additions and 23 deletions
|
|
@ -120,10 +120,11 @@ First deliverable:
|
|||
effects.
|
||||
- Rebuild one additive lead, one PAD texture, and one formant/vocal patch. The
|
||||
first rebuild catalog entries now parse/export and record the first Zyn
|
||||
missing abstractions. Named layers, additive harmonic-bank syntax, and staged
|
||||
rate/level envelopes for normal voices now exist; PAD-style spectral source
|
||||
generation, arbitrary Zyn free-envelope point curves, fuller kit/effect
|
||||
routing, and richer formant/vowel morphing remain pressure.
|
||||
missing abstractions. Named layers, additive harmonic-bank syntax, PAD
|
||||
spectral-cloud syntax, and staged rate/level envelopes for normal voices now
|
||||
exist; exact PADsynth wavetable generation, arbitrary Zyn free-envelope point
|
||||
curves, fuller kit/effect routing, and richer formant/vowel morphing remain
|
||||
pressure.
|
||||
|
||||
Do not vendor the upstream Zyn instrument-bank submodule until its preset
|
||||
license/provenance is explicit enough for this repo. Use project-authored
|
||||
|
|
@ -160,6 +161,18 @@ This promotes the envelope authority already used by operator graphs into the
|
|||
voice path. It is enough to express layered PAD/vocal onset and release shapes,
|
||||
but it is not yet a Zyn free-envelope point editor.
|
||||
|
||||
The fourth response is `spectrum`: PAD layers can declare a source-level partial
|
||||
cloud that lowers to detuned voice pairs:
|
||||
|
||||
```text
|
||||
layer name=pad_low engine=pad
|
||||
spectrum layer=pad_low root=130.8128 spread=.012 partials=1:.07,1.5:.052
|
||||
```
|
||||
|
||||
This gives PAD texture targets a reusable source authority without claiming
|
||||
exact PADsynth FFT/wavetable generation. `root`, `spread`, and `partials` are
|
||||
authoring-time graph shape, not runtime controls.
|
||||
|
||||
Sources:
|
||||
|
||||
- [ZynAddSubFX](https://github.com/zynaddsubfx/zynaddsubfx)
|
||||
|
|
|
|||
|
|
@ -98,3 +98,15 @@ voice layer=pad freq=220
|
|||
That gives PAD and vocal layers asymmetric contour authority without returning
|
||||
to anonymous ADSR piles. Arbitrary Zyn free-envelope point curves remain
|
||||
pressure; the staged surface is the first coherent rung, not the whole ladder.
|
||||
|
||||
PAD layers now have a spectral-cloud source command:
|
||||
|
||||
```text
|
||||
layer name=pad_low engine=pad
|
||||
spectrum layer=pad_low root=130.8128 spread=.012 partials=1:.07,1.5:.052
|
||||
```
|
||||
|
||||
The command records PAD-like partial intent under the layer and lowers it to
|
||||
detuned voice pairs. This is not a PADsynth wavetable generator. It is the
|
||||
smallest honest source authority needed before exact PAD synthesis earns its
|
||||
own machinery.
|
||||
|
|
|
|||
|
|
@ -832,9 +832,8 @@ public static class BuiltInScripts
|
|||
layer name=pad_high engine=pad min_key=48 max_key=108 gain=0.044 vibrato=0.009 vibrato_hz=0.11 env=rl rates=0.42,0.58,0.9,1.25 levels=1,0.84,0.72,0 curves=lin,exp,lin,lin gate=1.9
|
||||
layer name=air engine=add min_key=0 max_key=127 gain=0.018 wave=noise noise=0.82 env=rl rates=0.8,0.6,1.1,1.1 levels=1,0.82,0.55,0 curves=lin,lin,exp,lin gate=2.5 lpf=0.36 hpf=0.22
|
||||
|
||||
voice layer=pad_low freq=130.8128
|
||||
voice layer=pad_low freq=196 gain=0.052 vibrato=0.012 vibrato_hz=0.13
|
||||
voice layer=pad_high freq=261.6256 wave=triangle
|
||||
spectrum layer=pad_low root=130.8128 spread=0.012 partials=1:0.07,1.5:0.052
|
||||
spectrum layer=pad_high root=261.6256 spread=0.009 partials=1:0.044 wave=triangle
|
||||
voice layer=air freq=1800
|
||||
""";
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,12 @@ public sealed record HarmonicBank(
|
|||
float RootFrequencyHz,
|
||||
IReadOnlyList<HarmonicPartial> Partials);
|
||||
|
||||
public sealed record SpectralBank(
|
||||
string LayerName,
|
||||
float RootFrequencyHz,
|
||||
float SpreadRatio,
|
||||
IReadOnlyList<HarmonicPartial> Partials);
|
||||
|
||||
public sealed record ReferencePatch(
|
||||
string Id,
|
||||
string Family,
|
||||
|
|
@ -236,6 +242,7 @@ public sealed record SynthPatch
|
|||
public IReadOnlyList<Voice> Voices { get; init; } = Array.Empty<Voice>();
|
||||
public IReadOnlyList<PatchLayer> Layers { get; init; } = Array.Empty<PatchLayer>();
|
||||
public IReadOnlyList<HarmonicBank> HarmonicBanks { get; init; } = Array.Empty<HarmonicBank>();
|
||||
public IReadOnlyList<SpectralBank> SpectralBanks { get; init; } = Array.Empty<SpectralBank>();
|
||||
public IReadOnlyList<OperatorGraph> OperatorGraphs { get; init; } = Array.Empty<OperatorGraph>();
|
||||
public IReadOnlyList<ControlLane> Controls { get; init; } = Array.Empty<ControlLane>();
|
||||
public IReadOnlyList<PatchParameter> Parameters { get; init; } = Array.Empty<PatchParameter>();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public static class PatchScript
|
|||
private readonly List<ParameterBinding> _parameterBindings = [];
|
||||
private readonly List<PatchLayer> _layers = [];
|
||||
private readonly List<HarmonicBank> _harmonicBanks = [];
|
||||
private readonly List<SpectralBank> _spectralBanks = [];
|
||||
private readonly Dictionary<string, string> _defaults = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, Dictionary<string, string>> _layerDefaults = new(StringComparer.OrdinalIgnoreCase);
|
||||
private PendingOperatorGraph? _pendingOperatorGraph;
|
||||
|
|
@ -56,6 +57,7 @@ public static class PatchScript
|
|||
Voices = Voices,
|
||||
Layers = _layers,
|
||||
HarmonicBanks = _harmonicBanks,
|
||||
SpectralBanks = _spectralBanks,
|
||||
OperatorGraphs = _operatorGraphs,
|
||||
Controls = _controls,
|
||||
Parameters = _parameters,
|
||||
|
|
@ -103,6 +105,10 @@ public static class PatchScript
|
|||
FlushPendingOperatorGraph();
|
||||
AddHarmonicBank(fields, line);
|
||||
break;
|
||||
case "spectrum":
|
||||
FlushPendingOperatorGraph();
|
||||
AddSpectralBank(fields, line);
|
||||
break;
|
||||
case "voice":
|
||||
FlushPendingOperatorGraph();
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(fields, line), Voices.Count, line));
|
||||
|
|
@ -188,6 +194,79 @@ public static class PatchScript
|
|||
}
|
||||
}
|
||||
|
||||
private void AddSpectralBank(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var layerName = Required(fields, "layer", line);
|
||||
if (!_layerDefaults.ContainsKey(layerName))
|
||||
{
|
||||
throw new PatchScriptException(line, $"unknown layer `{layerName}`");
|
||||
}
|
||||
|
||||
var rootFrequency = GetFloat(fields, line, 440, "root", "base", "freq", "frequency");
|
||||
if (rootFrequency <= 0)
|
||||
{
|
||||
throw new PatchScriptException(line, "spectral root frequency must be greater than zero");
|
||||
}
|
||||
|
||||
var spread = GetFloat(fields, line, 0, "spread", "width", "detune");
|
||||
if (spread is < 0 or >= 1)
|
||||
{
|
||||
throw new PatchScriptException(line, "spectral spread must be at least zero and less than one");
|
||||
}
|
||||
|
||||
var partials = ParseHarmonicPartials(
|
||||
GetAny(fields, ["partials", "bank", "tones"], ""),
|
||||
line);
|
||||
if (partials.Count == 0)
|
||||
{
|
||||
throw new PatchScriptException(line, "spectrum needs at least one partial");
|
||||
}
|
||||
|
||||
_spectralBanks.Add(new SpectralBank(layerName, rootFrequency, spread, partials));
|
||||
|
||||
var sharedFields = Without(fields,
|
||||
"layer",
|
||||
"root",
|
||||
"base",
|
||||
"freq",
|
||||
"frequency",
|
||||
"spread",
|
||||
"width",
|
||||
"detune",
|
||||
"partials",
|
||||
"bank",
|
||||
"tones",
|
||||
"gain",
|
||||
"g");
|
||||
foreach (var partial in partials)
|
||||
{
|
||||
if (spread == 0)
|
||||
{
|
||||
AddSpectralVoice(sharedFields, layerName, rootFrequency * partial.Ratio, partial.Gain, line);
|
||||
continue;
|
||||
}
|
||||
|
||||
AddSpectralVoice(sharedFields, layerName, rootFrequency * partial.Ratio * (1 - spread), partial.Gain * 0.5f, line);
|
||||
AddSpectralVoice(sharedFields, layerName, rootFrequency * partial.Ratio * (1 + spread), partial.Gain * 0.5f, line);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSpectralVoice(
|
||||
IReadOnlyDictionary<string, string> sharedFields,
|
||||
string layerName,
|
||||
float frequency,
|
||||
float gain,
|
||||
int line)
|
||||
{
|
||||
var voiceFields = new Dictionary<string, string>(sharedFields, StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["layer"] = layerName,
|
||||
["freq"] = F(frequency),
|
||||
["gain"] = F(gain)
|
||||
};
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(voiceFields, line), Voices.Count, line));
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExpandVoiceFields(Dictionary<string, string> fields, int line)
|
||||
{
|
||||
var expanded = new Dictionary<string, string>(_defaults, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -982,6 +1061,7 @@ public static class PatchScript
|
|||
"def" or "t" or "template" => "template",
|
||||
"layer" or "kit" => "layer",
|
||||
"harmonics" or "partials" or "drawbars" => "harmonics",
|
||||
"spectrum" or "spectral" or "padsource" or "pad_source" => "spectrum",
|
||||
"v" or "voice" => "voice",
|
||||
"opgraph" or "ops" or "operators" => "opgraph",
|
||||
"operator" or "op" => "operator",
|
||||
|
|
|
|||
|
|
@ -42,16 +42,17 @@ public static class ReferenceRebuildCatalog
|
|||
[
|
||||
new("engine_pad", "1", "Approximated as slow layered oscillators plus an air/noise layer."),
|
||||
new("named_layers", "pad_low,pad_high,air", "Layer declarations separate PAD body from air/noise texture."),
|
||||
new("pad_spectral_source", "2 spectral banks / 3 partials", "PAD layers declare spectral partial clouds before lowering to detuned Aquarium voices."),
|
||||
new("filter_count", "1", "Represented with low-pass/high-pass field sites."),
|
||||
new("free_envelope_count", "1", "Approximated with staged voice rate/level envelopes and macro spread."),
|
||||
new("voice_rate_level_envelope", "3", "PAD body and air layers use staged rate/level envelopes attached through layer defaults."),
|
||||
new("slow_modulation", "patch_lfo", "The rebuild adds slow patch modulation because PAD texture wants motion even though the minimal fixture only marks PAD/filter/free-envelope pressure.")
|
||||
],
|
||||
[
|
||||
new("pad_spectral_source", "PADsynth wavetable/spectral generation", "Aquarium has no PAD-style spectral source; this rebuild is a texture approximation."),
|
||||
new("padsynth_wavetable_exactness", "PADsynth FFT/wavetable generation", "Aquarium has spectral source syntax, but it lowers to detuned voices rather than generating a PADsynth wavetable."),
|
||||
new("free_envelope_shape", "arbitrary Zyn envelope points", "Aquarium has staged voice envelopes now, but not arbitrary point/free-mode Zyn envelope curves.")
|
||||
],
|
||||
"This is pressure, not a claim of PAD parity. The current voice DSL can sketch the texture, but PAD-style spectral generation deserves its own source authority if repeated targets need it."),
|
||||
"This is pressure, not a claim of PADsynth parity. Aquarium can now state a layered spectral cloud, while exact PAD wavetable generation remains separate pressure."),
|
||||
new(
|
||||
"zyn/project-vocal-layer/aquarium",
|
||||
"zynaddsubfx",
|
||||
|
|
|
|||
|
|
@ -63,3 +63,4 @@
|
|||
{"kind":"layer_language_golf","lesson":"The first Zyn-driven language-golf abstraction is `layer`: named layer declarations own engine tags, optional MIDI key range metadata, default gain, and effect-send labels, while lowering to ordinary Aquarium voices for now. The abstraction generalizes across additive, PAD-texture, and vocal/formant rebuilds and gives future additive-bank, PAD-source, free-envelope, and formant-motion syntax a coherent owner. It deliberately does not implement those later features yet.","source":"Zyn named layer DSL slice on 2026-05-14"}
|
||||
{"kind":"harmonic_bank_language_golf","lesson":"Additive `harmonics` syntax now records authored ratio/gain partial banks under named layers and lowers them to ordinary Aquarium voices. This generalizes additive lead and future drawbar/partial-bank targets without hiding Zyn ADDsynth phase, bandwidth, oscillator shaping, or free-envelope semantics behind a fake parity claim.","source":"Zyn harmonic-bank DSL slice on 2026-05-14"}
|
||||
{"kind":"voice_rate_level_envelopes","lesson":"The existing readable `env=rl rates=... levels=... curves=... gate=...` contour now applies to normal voices and therefore to named layer defaults, not only operator graphs. This gives PAD and vocal Zyn rebuilds staged non-ADSR onset/release shape while preserving the invariant that Note owns gate timing and envelope data owns level shape. Arbitrary Zyn free-envelope point curves remain missing pressure.","source":"Zyn staged voice envelope DSL slice on 2026-05-14"}
|
||||
{"kind":"pad_spectral_cloud","lesson":"PAD-style layers now have `spectrum layer=... root=... spread=... partials=ratio:gain,...`, recorded as `SpectralBank` and lowered to detuned voice pairs. This gives PAD texture rebuilds a source-level partial-cloud authority without claiming exact PADsynth FFT/wavetable generation; roots/spreads/partials are authoring-time graph shape, not runtime controls.","source":"Zyn PAD spectral-cloud DSL slice on 2026-05-14"}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"structured development .aqua patch library",
|
||||
"ZynAddSubFX .xiz feature extraction with project-authored fixtures",
|
||||
"Zyn additive harmonic-bank DSL surface",
|
||||
"Zyn PAD spectral-cloud DSL surface",
|
||||
"staged rate/level envelopes for normal voices",
|
||||
"thresholded DX7 rendered parity test",
|
||||
"reference rebuild catalog",
|
||||
|
|
@ -83,9 +84,9 @@
|
|||
"next": "Use the now-validated algorithm-8 route scale, transpose lowering, and fixed-frequency lowering as fixed inputs while isolating remaining community-voice timbre mismatch, then expand to broader public-domain banks."
|
||||
},
|
||||
"zyn_reference_path": {
|
||||
"status": "Initial ZynInstrumentReader parses plain XML and gzip-compressed .xiz-shaped XML from project-authored fixtures into ZynInstrument records and ReferencePatch features. First Zyn-style Aquarium rebuilds exist for additive lead, PAD texture, and vocal/formant layer. `ZynInstrumentSurvey` can rank .xiz directories by feature pressure. Named `layer` declarations provide the first Zyn-driven DSL abstraction, additive `harmonics` banks attach to those layers, and staged `env=rl` envelopes now work on normal voices/layer defaults.",
|
||||
"lesson": "Start Zyn as structural classification, not translation. Upstream Zyn presets live in a separate bank submodule and should not be vendored until license/provenance is explicit. The hardened reader now parses the observed upstream instrument bank at commit e9f64a9: 1358/1358 .xiz files. The worst target is `olivers-100/0032-Drum Kit.xiz`, with 16 enabled kit items, mixed ADD/SUB/PAD engines, 106 envelopes, 25 free envelopes, 73 LFOs, 37 filters, and 3 formant filters. The first rebuilds show Aquarium can sketch small Zyn fixtures with existing voices/macros/formants/noise, and the upstream survey correctly pushed named kit/layer routing first. `layer` owns name, engine tag, optional key range metadata, default gain, and effect-send labels while lowering to ordinary voices. `harmonics` owns authored ratio/gain partial banks under those layers and lowers them to voices for inspectable Faust output. Staged `env=rl` now applies to voices as well as operators, preserving Note as gate authority and envelope data as level-shape authority. Aquarium still does not claim Zyn ADDsynth phase/bandwidth details, PAD spectral sources, arbitrary free-envelope point curves, or richer formant morphing.",
|
||||
"next": "Choose the next syntax that attaches to named layers: PAD spectral source or richer formant morphing. Prove one across more than one target."
|
||||
"status": "Initial ZynInstrumentReader parses plain XML and gzip-compressed .xiz-shaped XML from project-authored fixtures into ZynInstrument records and ReferencePatch features. First Zyn-style Aquarium rebuilds exist for additive lead, PAD texture, and vocal/formant layer. `ZynInstrumentSurvey` can rank .xiz directories by feature pressure. Named `layer` declarations provide the first Zyn-driven DSL abstraction; additive `harmonics`, PAD `spectrum`, and staged `env=rl` voice envelopes now attach to those layers.",
|
||||
"lesson": "Start Zyn as structural classification, not translation. Upstream Zyn presets live in a separate bank submodule and should not be vendored until license/provenance is explicit. The hardened reader now parses the observed upstream instrument bank at commit e9f64a9: 1358/1358 .xiz files. The worst target is `olivers-100/0032-Drum Kit.xiz`, with 16 enabled kit items, mixed ADD/SUB/PAD engines, 106 envelopes, 25 free envelopes, 73 LFOs, 37 filters, and 3 formant filters. The first rebuilds show Aquarium can sketch small Zyn fixtures with existing voices/macros/formants/noise, and the upstream survey correctly pushed named kit/layer routing first. `layer` owns name, engine tag, optional key range metadata, default gain, and effect-send labels while lowering to ordinary voices. `harmonics` owns authored ratio/gain partial banks under those layers. `spectrum` owns PAD-style root/spread/partial clouds under those layers and lowers partials to detuned voice pairs. Staged `env=rl` now applies to voices as well as operators, preserving Note as gate authority and envelope data as level-shape authority. Aquarium still does not claim Zyn ADDsynth phase/bandwidth details, exact PADsynth FFT/wavetable generation, arbitrary free-envelope point curves, or richer formant morphing.",
|
||||
"next": "Choose the next syntax that attaches to named layers: richer formant/vowel morphing. Prove it across more than one target."
|
||||
},
|
||||
"note_and_envelope_model": {
|
||||
"rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.",
|
||||
|
|
@ -100,7 +101,7 @@
|
|||
},
|
||||
"last_verified": {
|
||||
"command": "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore",
|
||||
"result": "91 passed; staged voice envelope DSL added without regressing DX7 parity",
|
||||
"commit": "pending Zyn staged voice envelopes"
|
||||
"result": "93 passed; PAD spectral-cloud DSL added without regressing DX7 parity",
|
||||
"commit": "pending Zyn PAD spectral clouds"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,25 @@ Doctrine update:
|
|||
|
||||
Completed this slice:
|
||||
|
||||
- Added PAD spectral-cloud syntax attached to named layers:
|
||||
`spectrum layer=pad_low root=130.8128 spread=.012 partials=1:.07,1.5:.052`.
|
||||
- The parser preserves each bank as `SpectralBank` model data, then lowers
|
||||
each partial to detuned voice pairs. `root`, `spread`, and `partials` are
|
||||
authoring-time graph shape, not runtime controls.
|
||||
- Converted the Zyn PAD texture rebuild from individual PAD body voices to two
|
||||
spectral banks under `pad_low` and `pad_high`; the air/noise layer remains a
|
||||
normal voice.
|
||||
- This is not exact PADsynth FFT/wavetable generation. It is a source-level
|
||||
partial-cloud authority for readable PAD rebuilds.
|
||||
- Focused verification passed:
|
||||
`dotnet test tests\AquariumSynth.Dsl.Tests\AquariumSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~SpectralBank|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure|FullyQualifiedName~BuiltInReferenceScriptsParseAndExportFaust"`:
|
||||
4 passed.
|
||||
- Full verification with bundled Python/dexed-py:
|
||||
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
|
||||
93 passed.
|
||||
|
||||
Previous slice:
|
||||
|
||||
- Promoted staged rate/level envelopes to normal voices and layer defaults.
|
||||
Voice fields now accept `env=rl rates=... levels=... curves=... gate=...`;
|
||||
the model stores `Voice.RateLevelEnvelope`, and Faust emission uses
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
project: AquariumSynthCSharp
|
||||
spine_version: 1
|
||||
updated: 2026-05-14T20:45:00+01:00
|
||||
updated: 2026-05-14T21:10:00+01:00
|
||||
|
||||
prime_directive: >
|
||||
Build coherent synth machinery. References exist to reveal missing structure,
|
||||
|
|
@ -76,6 +76,7 @@ live_invariants:
|
|||
- Named `layer` declarations own Zyn-style kit/layer identity: engine tag, optional key range metadata, default gain, and effect-send labels. They lower to ordinary voices for now; do not smuggle PAD/free-envelope/effect behavior into them.
|
||||
- Additive `harmonics` declarations own authored ratio/gain partial banks under named layers. They lower to ordinary voices for now; do not pretend this covers Zyn ADDsynth phase, bandwidth, oscillator shaping, or free-envelope semantics.
|
||||
- Normal voices may use staged `env=rl` rate/level envelopes. Notes still own gate timing; envelopes own level shape. This is not arbitrary Zyn free-envelope point editing.
|
||||
- PAD `spectrum` declarations own authored partial clouds under named layers. They lower to detuned voice pairs for now; do not pretend this covers exact PADsynth FFT/wavetable generation.
|
||||
- Structured `.aqua` stock lives under `patches/` as development source material. Every library patch must parse/export, and the patch library must stay out of the NuGet package unless intentionally promoted.
|
||||
- Project-authored Dexed patches may be generated inside tests for calibration; do not ship them in the NuGet package or treat them as runtime dependencies.
|
||||
- Harsh calibration probes belong in tests and ignored artifacts, not in the patch library unless they are musically useful stock.
|
||||
|
|
@ -97,10 +98,10 @@ 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. First Aquarium 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, and PAD/vocal rebuilds now use staged voice `env=rl` envelopes through layer defaults. A non-vendored upstream bank survey parsed 1358/1358 .xiz files and identified named kit/layer routing as the first owner abstraction."
|
||||
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. First Aquarium 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` partial-cloud banks attached to layers, and PAD/vocal rebuilds use staged voice `env=rl` envelopes through layer defaults. A non-vendored upstream bank survey parsed 1358/1358 .xiz files and identified named kit/layer routing as the first owner abstraction."
|
||||
first_slice:
|
||||
- "Use named layers as the owner before promoting PAD spectral source or richer formant morphing syntax."
|
||||
- "Additive harmonic banks and staged voice envelopes now exist; prove any next syntax across more than one target."
|
||||
- "Use named layers as the owner before promoting richer formant morphing syntax."
|
||||
- "Additive harmonic banks, PAD spectral clouds, and staged voice envelopes now exist; prove any next syntax across more than one target."
|
||||
- "Do not add all remaining pressures at once."
|
||||
faust:
|
||||
priority: 3
|
||||
|
|
@ -132,11 +133,10 @@ next_agent_start_here:
|
|||
`src/AquariumSynth.Dsl/ZynInstrument.cs` with project-authored fixtures
|
||||
under `tests/AquariumSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored`.
|
||||
Named `layer` declarations own source identity, additive `harmonics` banks
|
||||
attach to those layers, and staged `env=rl` envelopes now work on normal
|
||||
voices/layer defaults. Next pressure is to choose the next syntax that
|
||||
attaches to named layers: PAD spectral source or richer formant morphing.
|
||||
Do one, prove it across more than one target, and leave DX7 alone unless a
|
||||
regression test fails.
|
||||
attach to those layers, PAD `spectrum` banks attach to those layers, and
|
||||
staged `env=rl` envelopes now work on normal voices/layer defaults. Next
|
||||
pressure is richer formant/vowel morphing. Prove it across more than one
|
||||
target, and leave DX7 alone unless a regression test fails.
|
||||
must_read:
|
||||
- AGENTS.md
|
||||
- docs/audio-doctrine.md
|
||||
|
|
@ -149,6 +149,16 @@ next_agent_start_here:
|
|||
- "dotnet test AquariumSynthCSharp.slnx --no-restore"
|
||||
|
||||
completed_slices:
|
||||
- date: 2026-05-14
|
||||
summary: >
|
||||
Added PAD spectral-cloud syntax for named layers. `spectrum
|
||||
layer=pad_low root=130.8128 spread=.012 partials=1:.07,1.5:.052`
|
||||
records authored PAD partial intent as `SpectralBank`, then lowers each
|
||||
partial to detuned voice pairs for inspectable Faust output. The Zyn PAD
|
||||
texture rebuild now uses two spectral banks under `pad_low` and
|
||||
`pad_high`, while exact PADsynth FFT/wavetable generation remains missing
|
||||
pressure.
|
||||
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 93 passed"
|
||||
- date: 2026-05-14
|
||||
summary: >
|
||||
Promoted staged rate/level envelopes from operator graphs to normal
|
||||
|
|
|
|||
|
|
@ -259,6 +259,10 @@ public sealed class PatchScriptTests
|
|||
Assert.NotEmpty(rebuild.MatchedFeatures);
|
||||
Assert.NotEmpty(rebuild.MissingFeatures);
|
||||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "named_layers");
|
||||
if (rebuild.ReferenceId == "zyn/project/pad-texture")
|
||||
{
|
||||
Assert.NotEmpty(patch.SpectralBanks);
|
||||
}
|
||||
if (rebuild.ReferenceId is "zyn/project/pad-texture" or "zyn/project/vocal-layer")
|
||||
{
|
||||
Assert.Contains(patch.Voices, voice => voice.RateLevelEnvelope is not null);
|
||||
|
|
@ -381,6 +385,48 @@ public sealed class PatchScriptTests
|
|||
Assert.Contains("rl4_env(0.1, 1, 0, 0.2, 0.8, 1, 0.3, 0.5, 1, 0.4, 0, 0, patch_param_0)", export.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpectralBankSyntaxExpandsPadPartialCloud()
|
||||
{
|
||||
var patch = PatchScript.Parse("""
|
||||
layer name=pad engine=pad gain=.08 wave=saw env=rl rates=.1,.2,.3,.4 levels=1,.8,.5,0 gate=.9
|
||||
spectrum layer=pad root=100 spread=.01 partials=1:.08,1.5:.04
|
||||
""");
|
||||
var export = FaustEmitter.Emit(patch);
|
||||
|
||||
var bank = Assert.Single(patch.SpectralBanks);
|
||||
Assert.Equal("pad", bank.LayerName);
|
||||
Assert.Equal(100, bank.RootFrequencyHz);
|
||||
Assert.Equal(.01f, bank.SpreadRatio, 5);
|
||||
Assert.Equal(2, bank.Partials.Count);
|
||||
Assert.Equal(4, patch.Voices.Count);
|
||||
Assert.All(patch.Voices, voice => Assert.Equal("pad", voice.Layer?.Name));
|
||||
Assert.All(patch.Voices, voice => Assert.NotNull(voice.RateLevelEnvelope));
|
||||
Assert.Equal([99, 101, 148.5f, 151.5f], patch.Voices.Select(voice => voice.Oscillator.FrequencyHz).ToArray());
|
||||
Assert.Equal(.04f, patch.Voices[0].Gain, 5);
|
||||
Assert.Equal(.04f, patch.Voices[1].Gain, 5);
|
||||
Assert.Equal(.02f, patch.Voices[2].Gain, 5);
|
||||
Assert.Equal(.02f, patch.Voices[3].Gain, 5);
|
||||
Assert.Contains("process =", export.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpectralBankSyntaxRejectsUnknownLayerOrBadSpread()
|
||||
{
|
||||
Assert.Throws<PatchScriptException>(() =>
|
||||
PatchScript.Parse("spectrum layer=missing root=110 partials=1:.5"));
|
||||
|
||||
Assert.Throws<PatchScriptException>(() => PatchScript.Parse("""
|
||||
layer name=pad
|
||||
spectrum layer=pad root=110 spread=-.01 partials=1:.5
|
||||
"""));
|
||||
|
||||
Assert.Throws<PatchScriptException>(() => PatchScript.Parse("""
|
||||
layer name=pad
|
||||
spectrum layer=pad root=110 spread=1 partials=1:.5
|
||||
"""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dx7Algorithm32RebuildMatchesAdditiveCarrierShape()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue