Map Zyn PAD detune and filter order
This commit is contained in:
parent
85245363b0
commit
01eea5eccf
10 changed files with 145 additions and 4 deletions
|
|
@ -224,9 +224,10 @@ public static class FaustEmitter
|
|||
var hpf = $"clip01({parameters.Expression(OwnerField(ownerPath, "filter/hpf"), voice.Filter.HighPass)} * (1.0 + {parameters.Expression(OwnerField(ownerPath, "filter/hpf_ramp"), voice.Filter.HighPassRamp)} * age * 2.0) + patch_mod_hpf + {hpfMod})";
|
||||
var hasResonance = voice.Filter.LowPassResonance > 0 || parameters.IsBound(OwnerField(ownerPath, "filter/resonance"));
|
||||
var resonance = parameters.Expression(OwnerField(ownerPath, "filter/resonance"), voice.Filter.LowPassResonance);
|
||||
var lowPassOrder = Math.Clamp(voice.Filter.LowPassOrder, 1, 12);
|
||||
var lowpass = hasResonance
|
||||
? $"fi.resonlp(max(20.0, {lpf} * 18000.0), 0.7 + clip01({resonance}) * 18.0, 1.0)"
|
||||
: $"fi.lowpass(1, max(20.0, {lpf} * 18000.0))";
|
||||
: $"fi.lowpass({lowPassOrder}, max(20.0, {lpf} * 18000.0))";
|
||||
|
||||
source.AppendLine($"{name}_freq = {frequency};");
|
||||
source.AppendLine($"{name}_osc = {oscillator};");
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ public sealed record Filter(
|
|||
float LowPass = 1,
|
||||
float LowPassRamp = 0,
|
||||
float LowPassResonance = 0,
|
||||
int LowPassOrder = 1,
|
||||
float HighPass = 0,
|
||||
float HighPassRamp = 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -441,6 +441,7 @@ public static class PatchScript
|
|||
GetBoundFloat(fields, line, 1, OwnerField(ownerPath, "filter/lpf"), "lpf", "l"),
|
||||
GetBoundFloat(fields, line, 0, OwnerField(ownerPath, "filter/lpf_ramp"), "lpf_ramp", "lr"),
|
||||
GetBoundFloat(fields, line, 0, OwnerField(ownerPath, "filter/resonance"), "resonance", "res"),
|
||||
GetBoundInt(fields, line, 1, OwnerField(ownerPath, "filter/lpf_order"), "lpf_order", "lpfo"),
|
||||
GetBoundFloat(fields, line, 0, OwnerField(ownerPath, "filter/hpf"), "hpf", "h"),
|
||||
GetBoundFloat(fields, line, 0, OwnerField(ownerPath, "filter/hpf_ramp"), "hpf_ramp", "hr")),
|
||||
Phaser = new Phaser(
|
||||
|
|
@ -707,6 +708,24 @@ public static class PatchScript
|
|||
params string[] keys) =>
|
||||
TryGetAny(fields, keys, out var value) ? ParseBoundFloat(value, line, fallback, fieldPath) : fallback;
|
||||
|
||||
private int GetBoundInt(
|
||||
IReadOnlyDictionary<string, string> fields,
|
||||
int line,
|
||||
int fallback,
|
||||
string fieldPath,
|
||||
params string[] keys)
|
||||
{
|
||||
if (!TryGetAny(fields, keys, out var value))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
if (value.StartsWith('@'))
|
||||
{
|
||||
throw new PatchScriptException(line, $"parameter binding is not supported for integer field `{fieldPath}`");
|
||||
}
|
||||
return ParseInt(value, line);
|
||||
}
|
||||
|
||||
private float ParseBoundFloat(string value, int line, float fallback, string fieldPath)
|
||||
{
|
||||
if (!value.StartsWith('@'))
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ public sealed record SfxrParams
|
|||
Math.Clamp(LpfFreq, 0, 1),
|
||||
Math.Clamp(LpfRamp, -1, 1),
|
||||
Math.Clamp(LpfResonance, 0, 1),
|
||||
1,
|
||||
Math.Clamp(HpfFreq, 0, 1),
|
||||
Math.Clamp(HpfRamp, -1, 1)),
|
||||
Phaser = new Phaser(
|
||||
|
|
|
|||
|
|
@ -249,6 +249,8 @@ public static class ZynInstrumentReader
|
|||
MathF.Pow(0.1f, 3.0f * (1.0f - volume / 96.0f)),
|
||||
0.001f,
|
||||
1.5f);
|
||||
var frequency = pad.Element("FREQUENCY_PARAMETERS");
|
||||
var noteFrequencyHz = ZynPadPlaybackFrequency(frequency, playbackFrequencyHz);
|
||||
var attack = EnvelopeTime(envelope, "A_dt", 0.28f);
|
||||
var decay = EnvelopeTime(envelope, "D_dt", 0.42f);
|
||||
var release = EnvelopeTime(envelope, "R_dt", 1.2f);
|
||||
|
|
@ -259,6 +261,7 @@ public static class ZynInstrumentReader
|
|||
var position = ZynPositionText(pad.Element("HARMONIC_POSITION"));
|
||||
var filter = pad.Element("FILTER_PARAMETERS");
|
||||
var lowPass = ZynPadLowPass(filter);
|
||||
var lowPassOrder = ZynPadLowPassOrder(filter);
|
||||
var safeName = includeKitSuffix ? $"{safeBaseName}_{kitId}" : safeBaseName;
|
||||
var partialText = string.Join(",", harmonics.Select(partial => $"{F(partial.Ratio)}:{F(partial.Gain)}"));
|
||||
|
||||
|
|
@ -267,6 +270,7 @@ public static class ZynInstrumentReader
|
|||
matched.Add(new($"{featurePrefix}oscillator_base_function", IntParam(oscillator, "base_function", 0).ToString(CultureInfo.InvariantCulture), "Expanded Zyn OscilGen base function into harmonic partials before PAD synthesis."));
|
||||
matched.Add(new($"{featurePrefix}oscillator_spectrum_adjust", $"{IntParam(oscillator, "spectrum_adjust_type", 0)}:{IntParam(oscillator, "spectrum_adjust_par", 64)}", "Applied Zyn OscilGen spectrum adjustment to generated harmonic partials."));
|
||||
matched.Add(new($"{featurePrefix}table_root", F(tableRootFrequencyHz), "Root frequency comes from the Zyn oracle generated sample basefreq."));
|
||||
matched.Add(new($"{featurePrefix}note_frequency", F(noteFrequencyHz), "Mapped Zyn PAD coarse/fine detune into Aquarium table playback frequency."));
|
||||
matched.Add(new($"{featurePrefix}volume", volume.ToString(CultureInfo.InvariantCulture), "Mapped PAD amplitude volume into Aquarium layer gain."));
|
||||
matched.Add(new($"{featurePrefix}bandwidth", bandwidth.ToString(CultureInfo.InvariantCulture), "Mapped Zyn PAD bandwidth into Aquarium spectral table generation."));
|
||||
matched.Add(new($"{featurePrefix}bandwidth_scale", bandwidthScale.ToString(CultureInfo.InvariantCulture), "Mapped Zyn PAD bandwidth scaling exponent selection."));
|
||||
|
|
@ -275,14 +279,15 @@ public static class ZynInstrumentReader
|
|||
if (lowPass < 1)
|
||||
{
|
||||
matched.Add(new($"{featurePrefix}filter_lpf", F(lowPass), "Mapped static Zyn PAD global low-pass frequency into Aquarium layer filtering."));
|
||||
matched.Add(new($"{featurePrefix}filter_lpf_order", lowPassOrder.ToString(CultureInfo.InvariantCulture), "Mapped Zyn analog LP1/LP2 and stage count into Aquarium low-pass order."));
|
||||
}
|
||||
if (filter is not null)
|
||||
{
|
||||
missing.Add(new($"{featurePrefix}filter_modulation", "present", "Filter envelope/LFO lowering remains separate pressure."));
|
||||
}
|
||||
|
||||
script.AppendLine($"layer name={safeName} engine=pad gain={F(layerGain)} lpf={F(lowPass)} env=rl rates={F(attack)},{F(decay)},1,{F(release)} levels=1,1,1,0 curves=lin,lin,lin,lin gate=1.5");
|
||||
script.AppendLine($"spectrum layer={safeName} root={F(tableRootFrequencyHz)} freq={F(playbackFrequencyHz)} spread=0 zyn_mode={mode} zyn_bandwidth={bandwidth} zyn_bwscale={bandwidthScale} zyn_profile={profile} zyn_position={position} partials={partialText}");
|
||||
script.AppendLine($"layer name={safeName} engine=pad gain={F(layerGain)} lpf={F(lowPass)} lpf_order={lowPassOrder} env=rl rates={F(attack)},{F(decay)},1,{F(release)} levels=1,1,1,0 curves=lin,lin,lin,lin gate=1.5");
|
||||
script.AppendLine($"spectrum layer={safeName} root={F(tableRootFrequencyHz)} freq={F(noteFrequencyHz)} spread=0 zyn_mode={mode} zyn_bandwidth={bandwidth} zyn_bwscale={bandwidthScale} zyn_profile={profile} zyn_position={position} partials={partialText}");
|
||||
}
|
||||
|
||||
private static ZynKitItem ParseKitItem(XElement item)
|
||||
|
|
@ -825,6 +830,87 @@ public static class ZynInstrumentReader
|
|||
return Math.Clamp(cutoffHz / 18000.0f, 20.0f / 18000.0f, 1f);
|
||||
}
|
||||
|
||||
private static int ZynPadLowPassOrder(XElement? filter)
|
||||
{
|
||||
if (filter is null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var category = DescendantIntParam(filter, "category", 0);
|
||||
var type = DescendantIntParam(filter, "type", 0);
|
||||
if (category != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var poles = type switch
|
||||
{
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
_ => 1
|
||||
};
|
||||
return Math.Clamp(poles * (DescendantIntParam(filter, "stages", 0) + 1), 1, 12);
|
||||
}
|
||||
|
||||
private static float ZynPadPlaybackFrequency(XElement? frequency, float playbackFrequencyHz)
|
||||
{
|
||||
var detuneCents = ZynDetuneCents(
|
||||
IntParam(frequency, "detune_type", 1),
|
||||
IntParam(frequency, "coarse_detune", 0),
|
||||
IntParam(frequency, "detune", 8192));
|
||||
return playbackFrequencyHz * MathF.Pow(2.0f, detuneCents / 1200.0f);
|
||||
}
|
||||
|
||||
private static float ZynDetuneCents(int type, int coarseDetune, int fineDetune)
|
||||
{
|
||||
var octave = coarseDetune / 1024;
|
||||
if (octave >= 8)
|
||||
{
|
||||
octave -= 16;
|
||||
}
|
||||
|
||||
var coarse = coarseDetune % 1024;
|
||||
if (coarse > 512)
|
||||
{
|
||||
coarse -= 1024;
|
||||
}
|
||||
|
||||
var fine = fineDetune - 8192;
|
||||
float coarseCents;
|
||||
float fineCents;
|
||||
switch (type)
|
||||
{
|
||||
case 2:
|
||||
coarseCents = MathF.Abs(coarse * 10.0f);
|
||||
fineCents = MathF.Abs(fine / 8192.0f) * 10.0f;
|
||||
break;
|
||||
case 3:
|
||||
coarseCents = MathF.Abs(coarse * 100.0f);
|
||||
fineCents = MathF.Pow(10.0f, MathF.Abs(fine / 8192.0f) * 3.0f) / 10.0f - 0.1f;
|
||||
break;
|
||||
case 4:
|
||||
coarseCents = MathF.Abs(coarse * 701.95500087f);
|
||||
fineCents = (MathF.Pow(2.0f, MathF.Abs(fine / 8192.0f) * 12.0f) - 1.0f) / 4095.0f * 1200.0f;
|
||||
break;
|
||||
default:
|
||||
coarseCents = MathF.Abs(coarse * 50.0f);
|
||||
fineCents = MathF.Abs(fine / 8192.0f) * 35.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fineDetune < 8192)
|
||||
{
|
||||
fineCents = -fineCents;
|
||||
}
|
||||
if (coarse < 0)
|
||||
{
|
||||
coarseCents = -coarseCents;
|
||||
}
|
||||
|
||||
return octave * 1200.0f + coarseCents + fineCents;
|
||||
}
|
||||
|
||||
private static int IntParam(XElement? root, string name, int fallback)
|
||||
{
|
||||
var value = root?.Elements("par")
|
||||
|
|
|
|||
|
|
@ -80,3 +80,4 @@
|
|||
{"date":"2026-05-15","topic":"zyn-pad-kit-layering","lesson":"Do not treat first enabled PAD kit item as a Zyn instrument when kit_mode is multi-kit. Multi-kit PAD presets need per-kit table roots and layered Aqua spectrum banks, or parity artifacts compare against an amputated preset.","evidence":"DoublePadBass has kit_mode=1 with enabled PAD kit items 0 and 1. Rebuilding/summing both moved log-mel from about 0.655 to 0.493 and generated doublepadbass_0 plus doublepadbass_1. Raising the volume cap to 3.0 worsened log-mel to 0.521 and was cut."}
|
||||
{"date":"2026-05-15","topic":"zyn-ghost-base-function","lesson":"A direct-harmonic readability gate must not erase source identity. Ghost Ensemble needed power-base OscilGen expansion even without downstream oscillator stages; otherwise it collapsed toward a generic two-partial pad.","evidence":"Ghost Ensemble has base_function=4 with two explicit harmonics. Expanding power-base multi-harmonic sources changed the candidate from two partials to 32 partials and improved score to 0.446977, with log-mel 0.402789."}
|
||||
{"date":"2026-05-15","topic":"zyn-pad-static-filter","lesson":"Zyn PAD static global filter frequency is audible layer-balance authority, not optional decoration. Read nested FILTER params under FILTER_PARAMETERS and lower old Zyn cutoff values through the exponential FilterParams curve, not a linear freq/127 guess.","evidence":"DoublePadBass kit0 filter freq=12 and kit1 freq=61. Linear mapping gave lpf=0.094488/0.480315 and still sounded like all kazoo. Zyn's curve maps them to lpf=0.003325/0.047225, improving log-mel to 0.334546 and making the generated patch deep-bass-first. Filter envelopes/LFOs and level recovery remain missing pressure."}
|
||||
{"date":"2026-05-15","topic":"zyn-pad-detune-and-filter-order","lesson":"Zyn PAD note frequency is not just the requested note plus table root; FREQUENCY_PARAMETERS coarse/fine detune are live playback authority, and analog LP2 needs a steeper low-pass than Aquarium's old one-pole default.","evidence":"DoublePadBass has coarse_detune=15360, which Zyn getdetune wraps from octave 15 to -1 octave. Emitting spectrum freq=130.8128 instead of 261.6256 and lpf_order=2 moved DoublePadBass log-mel from 0.334546 after static cutoff mapping to 0.259298; remaining gap is RMS/envelope."}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
"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`, PAD `spectrum`, and staged `env=rl` voice envelopes now attach to those layers. Zyn PAD `spectrum` banks can now carry explicit `zyn_mode`, `zyn_bandwidth`, `zyn_bwscale`, `zyn_profile`, and `zyn_position` table-generation fields, and `PadSynthWaveform` lowers those through Zyn-style profile/bandwidth math. The pinned Zyn source now builds as a test-only MSYS2/MinGW oracle through `tools/zyn-reference/build-zyn-reference.ps1`, which patches an ignored artifact source copy, proves `PadNoteTest`, and builds `ZynPadReference`.",
|
||||
"lesson": "Start Zyn as structural classification, then move into explicit scoped rebuilds. Upstream Zyn presets live in a nested GPL instrument-bank submodule and may be used as test-only parity/pressure feedstock once provenance is recorded. The ZynAddSubFX engine source is pinned as GPL test/development material under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825; use it as a parity oracle only, never as Aquarium runtime or NuGet package content. Zyn build fixes belong in `tools/zyn-reference/patches/` and apply to ignored artifact copies, not the clean pinned submodule. The PAD oracle now renders both generated PAD sample tables and note audio through Zyn `PADnoteParameters`/`PADnote`; optional parity artifacts live under `artifacts/parity/zyn-pad-reference` and `artifacts/parity/zyn-pad-fixtures`. The first project-authored PAD comparison proved the harness and showed Aquarium `spectrum` was not a close Zyn PAD emulation. User listening identified Zyn as pure wineglass and Aquarium as angry bees. Cutting unearned air/noise/drive/soft-clip from the rebuild, adding interpolated wavetable reads, and raising Aquarium spectral tables to Zyn's 131072-frame PAD size improved the report. A later pitch/drone pass split PAD table root from playback note frequency: `spectrum root=...` owns table/base frequency and `freq=...` owns the played note. Using Zyn's reported sample basefreq 77.7813 while playing 261.6256, removing gratuitous spread/vibrato, collapsing two same-note PAD voices into one coherent spectral table, and trimming gain improved the report to log-mel 0.35669, envelope 0.347572, RMS ratio 1.001704, centroid ratio 1.004072, score 0.6211. The first PAD fixture survey shows `pad-pure` and the old `pad-texture` render identically through Zyn, so the filter/category XML in `pad-texture` is not live pressure for this oracle path. `pad-harmonic` changes the source table and note output through explicit OSCIL harmonics, making it the next real PAD reference rung. The remaining gap is source-table/profile and envelope semantics, not more decorative patch layers. The report now also emits source-table WAVs for listening. The hardened reader parses the upstream GPL instrument bank at commit c5c912131b31df5fdf372d2f06a25aaf2375837f: 1318/1318 .xiz files; optional tests write a ranked report and render five upstream PAD listening fixtures under ignored artifacts. `RebuildFirstPadAsAquariumScript` now generates first-pass per-preset PAD `.aqua` candidates from the first enabled PAD kit item, OSCIL harmonic magnitudes, oracle-reported table base frequency, basic volume, and simple rate/level envelope scaffolding while reporting missing bandwidth/profile/filter semantics. 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 named layers and generated table base frequency; playback `freq` remains note authority. 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, full Zyn PAD harmonic-profile/randomness/pitch-zone behavior, arbitrary free-envelope point curves, or richer formant morphing.",
|
||||
"next": "Continue Zyn PAD parity from the sampled OscilGen stage-order, multi-kit PAD, and Zyn-curve static global-filter paths. Ghost Ensemble now expands its power-base multi-harmonic OscilGen source instead of collapsing into a generic two-partial pad, and DoublePadBass now rebuilds both enabled PAD kit items with per-kit static low-pass filters using Zyn's exponential old-cutoff curve. DoublePadBass is now deep-bass-first but under-loud; remaining pressure is filter envelope/level recovery. Do not claim Zyn PAD parity until broader OscilGen filtering/waveshaping/adaptive-harmonic behavior and note/global filter envelope/LFO ownership are mapped or deliberately excluded."
|
||||
"next": "Continue Zyn PAD parity from the sampled OscilGen stage-order, multi-kit PAD, Zyn-curve static global-filter, low-pass-order, and PAD frequency-detune paths. Ghost Ensemble now expands its power-base multi-harmonic OscilGen source instead of collapsing into a generic two-partial pad. DoublePadBass now rebuilds both enabled PAD kit items, maps Zyn analog LP2 to `lpf_order=2`, and applies Zyn `getdetune` semantics so `coarse_detune=15360` plays one octave down (`freq=130.8128` instead of `261.6256`). Latest DoublePadBass log-mel is 0.259298; remaining pressure is mostly loudness/envelope recovery, especially filter envelope/LFO behavior. Do not claim Zyn PAD parity until broader OscilGen filtering/waveshaping/adaptive-harmonic behavior and note/global filter envelope/LFO ownership are mapped or deliberately excluded."
|
||||
},
|
||||
"note_and_envelope_model": {
|
||||
"rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.",
|
||||
|
|
|
|||
|
|
@ -105,6 +105,15 @@ Current user-ear correction:
|
|||
`doublepadbass_0 lpf=0.003325` and `doublepadbass_1 lpf=0.047225`. The
|
||||
character should now be deep-bass-first; remaining pressure is filter
|
||||
envelope/level recovery, not more static high-band brightness.
|
||||
- User still heard Aqua as kazoo-first. The next missing authority was not
|
||||
brightness but pitch: DoublePadBass PAD frequency params set
|
||||
`coarse_detune=15360`, and Zyn's `getdetune` wraps octave `15` to `-1`.
|
||||
Aqua had ignored PAD coarse/fine detune and played both spectral tables at
|
||||
`261.6256 Hz`; it now emits `freq=130.8128` for both enabled kit layers.
|
||||
- The same pass added explicit `lpf_order` so Zyn analog `LP2` filters lower to
|
||||
Faust `fi.lowpass(2, ...)` instead of pretending all low-passes are first
|
||||
order. Latest DoublePadBass: log-mel `0.259298`, score `0.370218`, centroid
|
||||
ratio `1.027115`; remaining pressure is loudness/envelope recovery.
|
||||
|
||||
Completed this slice:
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,19 @@ next_agent_start_here:
|
|||
- "dotnet test AquariumSynthCSharp.slnx --no-restore"
|
||||
|
||||
completed_slices:
|
||||
- date: 2026-05-15
|
||||
summary: >
|
||||
Mapped two missing Zyn PAD authorities exposed by DoublePadBass listening:
|
||||
analog low-pass slope and PAD frequency detune. Aquarium now has a general
|
||||
`lpf_order` field which emits Faust `fi.lowpass(order, ...)`; Zyn analog
|
||||
`LP2` filters lower to `lpf_order=2` and include Zyn stage count. The Zyn
|
||||
PAD importer also applies Zyn `getdetune` semantics to
|
||||
`FREQUENCY_PARAMETERS`, so DoublePadBass `coarse_detune=15360` wraps to
|
||||
one octave down and generated `spectrum` playback moves from `261.6256`
|
||||
to `130.8128`. DoublePadBass log-mel moved from `0.334546` after static
|
||||
cutoff mapping to `0.259298`; the remaining gap is mostly envelope/RMS,
|
||||
not wrong pitch or high-band balance.
|
||||
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 103 passed"
|
||||
- date: 2026-05-15
|
||||
summary: >
|
||||
Mapped static Zyn PAD global low-pass filters into generated Aqua layers
|
||||
|
|
|
|||
|
|
@ -656,6 +656,16 @@ public sealed class PatchScriptTests
|
|||
Assert.Empty(export.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VoiceLowPassOrderSelectsFaustFilterOrder()
|
||||
{
|
||||
var patch = PatchScript.Parse("v w=saw f=80 lpf=.1 lpf_order=2");
|
||||
var export = FaustEmitter.Emit(patch);
|
||||
|
||||
Assert.Equal(2, patch.Voices[0].Filter.LowPassOrder);
|
||||
Assert.Contains("fi.lowpass(2, max(20.0,", export.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VoiceEnvelopeUsesStandardAdsrAndNoteGate()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue