Generalize PAD spectrum syntax
This commit is contained in:
parent
31e9390195
commit
5c972443bc
10 changed files with 169 additions and 141 deletions
|
|
@ -184,6 +184,13 @@ PAD engine parity across all harmonic profiles, randomness controls, or
|
|||
multi-table pitch-zone behavior. `root`, `spread`, and `partials` are
|
||||
authoring-time table shape, not runtime controls.
|
||||
|
||||
The canonical Aquarium spelling for richer spectral-table shaping is neutral:
|
||||
`pad_mode`, `pad_bandwidth`, `pad_bwscale`, `pad_profile`, and `pad_position`.
|
||||
The Zyn importer may populate those fields from Zyn's PAD controls, and the
|
||||
parser still accepts older `zyn_*` names in generated parity artifacts, but
|
||||
authored or newly generated `.aqua` should not use reference-synth names for
|
||||
general DSP intent.
|
||||
|
||||
Sources:
|
||||
|
||||
- [ZynAddSubFX](https://github.com/zynaddsubfx/zynaddsubfx)
|
||||
|
|
|
|||
|
|
@ -210,19 +210,19 @@ public sealed record HarmonicBank(
|
|||
public enum PadSpectrumMode
|
||||
{
|
||||
Generic,
|
||||
ZynBandwidth,
|
||||
ZynDiscrete,
|
||||
ZynContinuous
|
||||
Bandwidth,
|
||||
Discrete,
|
||||
Continuous
|
||||
}
|
||||
|
||||
public enum ZynProfileBaseType
|
||||
public enum PadProfileBaseType
|
||||
{
|
||||
Gaussian = 0,
|
||||
Square = 1,
|
||||
DoubleExponential = 2
|
||||
}
|
||||
|
||||
public enum ZynProfileAmplitudeType
|
||||
public enum PadProfileAmplitudeType
|
||||
{
|
||||
Off = 0,
|
||||
Gaussian = 1,
|
||||
|
|
@ -230,7 +230,7 @@ public enum ZynProfileAmplitudeType
|
|||
Flat = 3
|
||||
}
|
||||
|
||||
public enum ZynProfileAmplitudeMode
|
||||
public enum PadProfileAmplitudeMode
|
||||
{
|
||||
Sum = 0,
|
||||
Mult = 1,
|
||||
|
|
@ -238,14 +238,14 @@ public enum ZynProfileAmplitudeMode
|
|||
Div2 = 3
|
||||
}
|
||||
|
||||
public enum ZynProfileHalf
|
||||
public enum PadProfileHalf
|
||||
{
|
||||
Full = 0,
|
||||
Upper = 1,
|
||||
Lower = 2
|
||||
}
|
||||
|
||||
public enum ZynHarmonicPositionType
|
||||
public enum PadHarmonicPositionType
|
||||
{
|
||||
Harmonic = 0,
|
||||
ShiftUp = 1,
|
||||
|
|
@ -257,22 +257,22 @@ public enum ZynHarmonicPositionType
|
|||
Shift = 7
|
||||
}
|
||||
|
||||
public sealed record ZynHarmonicProfile(
|
||||
ZynProfileBaseType BaseType = ZynProfileBaseType.Gaussian,
|
||||
public sealed record PadHarmonicProfile(
|
||||
PadProfileBaseType BaseType = PadProfileBaseType.Gaussian,
|
||||
int BaseParameter = 80,
|
||||
int FrequencyMultiplier = 0,
|
||||
int ModulatorParameter = 0,
|
||||
int ModulatorFrequency = 30,
|
||||
int Width = 127,
|
||||
ZynProfileAmplitudeType AmplitudeType = ZynProfileAmplitudeType.Off,
|
||||
ZynProfileAmplitudeMode AmplitudeMode = ZynProfileAmplitudeMode.Sum,
|
||||
PadProfileAmplitudeType AmplitudeType = PadProfileAmplitudeType.Off,
|
||||
PadProfileAmplitudeMode AmplitudeMode = PadProfileAmplitudeMode.Sum,
|
||||
int AmplitudeParameter1 = 80,
|
||||
int AmplitudeParameter2 = 64,
|
||||
bool AutoScale = true,
|
||||
ZynProfileHalf Half = ZynProfileHalf.Full);
|
||||
PadProfileHalf Half = PadProfileHalf.Full);
|
||||
|
||||
public sealed record ZynHarmonicPosition(
|
||||
ZynHarmonicPositionType Type = ZynHarmonicPositionType.Harmonic,
|
||||
public sealed record PadHarmonicPosition(
|
||||
PadHarmonicPositionType Type = PadHarmonicPositionType.Harmonic,
|
||||
int Parameter1 = 0,
|
||||
int Parameter2 = 0,
|
||||
int Parameter3 = 0);
|
||||
|
|
@ -281,14 +281,14 @@ public sealed record PadSpectrumProfile(
|
|||
PadSpectrumMode Mode = PadSpectrumMode.Generic,
|
||||
int Bandwidth = 500,
|
||||
int BandwidthScale = 0,
|
||||
ZynHarmonicProfile ZynProfile = null!,
|
||||
ZynHarmonicPosition ZynPosition = null!)
|
||||
PadHarmonicProfile HarmonicProfile = null!,
|
||||
PadHarmonicPosition HarmonicPosition = null!)
|
||||
{
|
||||
public static PadSpectrumProfile Generic { get; } = new();
|
||||
|
||||
public ZynHarmonicProfile ZynProfile { get; init; } = ZynProfile ?? new();
|
||||
public PadHarmonicProfile HarmonicProfile { get; init; } = HarmonicProfile ?? new();
|
||||
|
||||
public ZynHarmonicPosition ZynPosition { get; init; } = ZynPosition ?? new();
|
||||
public PadHarmonicPosition HarmonicPosition { get; init; } = HarmonicPosition ?? new();
|
||||
}
|
||||
|
||||
public sealed record SpectralBank(
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ internal static class PadSynthWaveform
|
|||
{
|
||||
return bank.Profile.Mode == PadSpectrumMode.Generic
|
||||
? GenericAmplitudeSpectrum(bank, tableSize)
|
||||
: ZynAmplitudeSpectrum(bank, tableSize);
|
||||
: ProfiledAmplitudeSpectrum(bank, tableSize);
|
||||
}
|
||||
|
||||
private static double[] GenericAmplitudeSpectrum(SpectralBank bank, int tableSize)
|
||||
|
|
@ -97,18 +97,18 @@ internal static class PadSynthWaveform
|
|||
return amplitudes;
|
||||
}
|
||||
|
||||
private static double[] ZynAmplitudeSpectrum(SpectralBank bank, int tableSize)
|
||||
private static double[] ProfiledAmplitudeSpectrum(SpectralBank bank, int tableSize)
|
||||
{
|
||||
var amplitudes = new double[tableSize / 2 + 1];
|
||||
var harmonics = NormalizeHarmonics(bank.Partials);
|
||||
var profile = ZynProfile(bank.Profile.ZynProfile);
|
||||
if (bank.Profile.Mode == PadSpectrumMode.ZynBandwidth)
|
||||
var profile = HarmonicProfile(bank.Profile.HarmonicProfile);
|
||||
if (bank.Profile.Mode == PadSpectrumMode.Bandwidth)
|
||||
{
|
||||
AddZynBandwidthSpectrum(amplitudes, harmonics, profile, bank, tableSize);
|
||||
AddProfiledBandwidthSpectrum(amplitudes, harmonics, profile, bank, tableSize);
|
||||
return amplitudes;
|
||||
}
|
||||
|
||||
AddZynPeakSpectrum(amplitudes, harmonics, bank, tableSize, bank.Profile.Mode == PadSpectrumMode.ZynContinuous);
|
||||
AddProfiledPeakSpectrum(amplitudes, harmonics, bank, tableSize, bank.Profile.Mode == PadSpectrumMode.Continuous);
|
||||
return amplitudes;
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ internal static class PadSynthWaveform
|
|||
.ToArray();
|
||||
}
|
||||
|
||||
private static double[] ZynProfile(ZynHarmonicProfile profile)
|
||||
private static double[] HarmonicProfile(PadHarmonicProfile profile)
|
||||
{
|
||||
const int profileSize = 512;
|
||||
const int superSample = 16;
|
||||
|
|
@ -148,11 +148,11 @@ internal static class PadSynthWaveform
|
|||
continue;
|
||||
}
|
||||
|
||||
if (profile.Half == ZynProfileHalf.Upper && x > 0.5)
|
||||
if (profile.Half == PadProfileHalf.Upper && x > 0.5)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (profile.Half == ZynProfileHalf.Lower && x < 0.5)
|
||||
if (profile.Half == PadProfileHalf.Lower && x < 0.5)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -164,12 +164,12 @@ internal static class PadSynthWaveform
|
|||
|
||||
var value = profile.BaseType switch
|
||||
{
|
||||
ZynProfileBaseType.Square => Math.Exp(-(x * x) * basePar) < 0.4 ? 0.0 : 1.0,
|
||||
ZynProfileBaseType.DoubleExponential => Math.Exp(-Math.Abs(x) * Math.Sqrt(basePar)),
|
||||
PadProfileBaseType.Square => Math.Exp(-(x * x) * basePar) < 0.4 ? 0.0 : 1.0,
|
||||
PadProfileBaseType.DoubleExponential => Math.Exp(-Math.Abs(x) * Math.Sqrt(basePar)),
|
||||
_ => Math.Exp(-(x * x) * basePar)
|
||||
};
|
||||
|
||||
value = ApplyZynAmplitudeMultiplier(value, originalX, ampPar1, ampPar2, profile);
|
||||
value = ApplyProfileAmplitudeMultiplier(value, originalX, ampPar1, ampPar2, profile);
|
||||
smp[i / superSample] += value / superSample;
|
||||
}
|
||||
|
||||
|
|
@ -185,49 +185,49 @@ internal static class PadSynthWaveform
|
|||
return smp;
|
||||
}
|
||||
|
||||
private static double ApplyZynAmplitudeMultiplier(
|
||||
private static double ApplyProfileAmplitudeMultiplier(
|
||||
double value,
|
||||
double originalX,
|
||||
double ampPar1,
|
||||
double ampPar2,
|
||||
ZynHarmonicProfile profile)
|
||||
PadHarmonicProfile profile)
|
||||
{
|
||||
if (profile.AmplitudeType == ZynProfileAmplitudeType.Off)
|
||||
if (profile.AmplitudeType == PadProfileAmplitudeType.Off)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var amp = profile.AmplitudeType switch
|
||||
{
|
||||
ZynProfileAmplitudeType.Gaussian => Math.Exp(-(originalX * originalX) * 10.0 * ampPar1),
|
||||
ZynProfileAmplitudeType.Sine => 0.5 * (1.0 + Math.Cos(Math.PI * originalX * Math.Sqrt(ampPar1 * 4.0 + 1.0))),
|
||||
ZynProfileAmplitudeType.Flat => 1.0 / (Math.Pow(originalX * (ampPar1 * 2.0 + 0.8), 14.0) + 1.0),
|
||||
PadProfileAmplitudeType.Gaussian => Math.Exp(-(originalX * originalX) * 10.0 * ampPar1),
|
||||
PadProfileAmplitudeType.Sine => 0.5 * (1.0 + Math.Cos(Math.PI * originalX * Math.Sqrt(ampPar1 * 4.0 + 1.0))),
|
||||
PadProfileAmplitudeType.Flat => 1.0 / (Math.Pow(originalX * (ampPar1 * 2.0 + 0.8), 14.0) + 1.0),
|
||||
_ => 1.0
|
||||
};
|
||||
|
||||
return profile.AmplitudeMode switch
|
||||
{
|
||||
ZynProfileAmplitudeMode.Mult => value * (amp * (1.0 - ampPar2) + ampPar2),
|
||||
ZynProfileAmplitudeMode.Div1 => value / (amp + Math.Pow(ampPar2, 4.0) * 20.0 + 0.0001),
|
||||
ZynProfileAmplitudeMode.Div2 => amp / (value + Math.Pow(ampPar2, 4.0) * 20.0 + 0.0001),
|
||||
PadProfileAmplitudeMode.Mult => value * (amp * (1.0 - ampPar2) + ampPar2),
|
||||
PadProfileAmplitudeMode.Div1 => value / (amp + Math.Pow(ampPar2, 4.0) * 20.0 + 0.0001),
|
||||
PadProfileAmplitudeMode.Div2 => amp / (value + Math.Pow(ampPar2, 4.0) * 20.0 + 0.0001),
|
||||
_ => amp * (1.0 - ampPar2) + value * ampPar2
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddZynBandwidthSpectrum(
|
||||
private static void AddProfiledBandwidthSpectrum(
|
||||
double[] amplitudes,
|
||||
IReadOnlyList<HarmonicPartial> harmonics,
|
||||
double[] profile,
|
||||
SpectralBank bank,
|
||||
int tableSize)
|
||||
{
|
||||
var bandwidthCents = ZynBandwidthCents(bank.Profile.Bandwidth);
|
||||
var bandwidthScale = ZynBandwidthScalePower(bank.Profile.BandwidthScale);
|
||||
var bandwidthAdjust = Math.Max(0.001, ZynProfileBandwidthAdjust(bank.Profile.ZynProfile, profile));
|
||||
var bandwidthCents = PadBandwidthCents(bank.Profile.Bandwidth);
|
||||
var bandwidthScale = PadBandwidthScalePower(bank.Profile.BandwidthScale);
|
||||
var bandwidthAdjust = Math.Max(0.001, HarmonicProfileBandwidthAdjust(bank.Profile.HarmonicProfile, profile));
|
||||
var spectrumSize = tableSize / 2;
|
||||
foreach (var harmonic in harmonics)
|
||||
{
|
||||
var realRatio = ZynHarmonicRatio(harmonic.Ratio, bank.Profile.ZynPosition);
|
||||
var realRatio = PadHarmonicRatio(harmonic.Ratio, bank.Profile.HarmonicPosition);
|
||||
var realFrequency = realRatio * bank.RootFrequencyHz;
|
||||
if (realFrequency > SampleRate * 0.49999 || realFrequency < 20 || harmonic.Gain < 0.0001f)
|
||||
{
|
||||
|
|
@ -274,7 +274,7 @@ internal static class PadSynthWaveform
|
|||
}
|
||||
}
|
||||
|
||||
private static void AddZynPeakSpectrum(
|
||||
private static void AddProfiledPeakSpectrum(
|
||||
double[] amplitudes,
|
||||
IReadOnlyList<HarmonicPartial> harmonics,
|
||||
SpectralBank bank,
|
||||
|
|
@ -285,7 +285,7 @@ internal static class PadSynthWaveform
|
|||
var previousGain = 0.0;
|
||||
foreach (var harmonic in harmonics)
|
||||
{
|
||||
var frequency = ZynHarmonicRatio(harmonic.Ratio, bank.Profile.ZynPosition) * bank.RootFrequencyHz;
|
||||
var frequency = PadHarmonicRatio(harmonic.Ratio, bank.Profile.HarmonicPosition) * bank.RootFrequencyHz;
|
||||
var bin = (int)Math.Round(frequency / SampleRate * tableSize);
|
||||
if (bin <= 0 || bin >= amplitudes.Length)
|
||||
{
|
||||
|
|
@ -307,13 +307,13 @@ internal static class PadSynthWaveform
|
|||
}
|
||||
}
|
||||
|
||||
private static double ZynBandwidthCents(int bandwidth)
|
||||
private static double PadBandwidthCents(int bandwidth)
|
||||
{
|
||||
var result = Math.Pow(Math.Max(0, bandwidth) / 1000.0, 1.1);
|
||||
return Math.Pow(10.0, result * 4.0) * 0.25;
|
||||
}
|
||||
|
||||
private static double ZynBandwidthScalePower(int scale) => scale switch
|
||||
private static double PadBandwidthScalePower(int scale) => scale switch
|
||||
{
|
||||
1 => 0.0,
|
||||
2 => 0.25,
|
||||
|
|
@ -325,7 +325,7 @@ internal static class PadSynthWaveform
|
|||
_ => 1.0
|
||||
};
|
||||
|
||||
private static double ZynProfileBandwidthAdjust(ZynHarmonicProfile harmonicProfile, IReadOnlyList<double> profile)
|
||||
private static double HarmonicProfileBandwidthAdjust(PadHarmonicProfile harmonicProfile, IReadOnlyList<double> profile)
|
||||
{
|
||||
if (!harmonicProfile.AutoScale)
|
||||
{
|
||||
|
|
@ -345,7 +345,7 @@ internal static class PadSynthWaveform
|
|||
return 1.0;
|
||||
}
|
||||
|
||||
private static double ZynHarmonicRatio(float harmonicNumber, ZynHarmonicPosition position)
|
||||
private static double PadHarmonicRatio(float harmonicNumber, PadHarmonicPosition position)
|
||||
{
|
||||
var n = Math.Max(1.0, harmonicNumber);
|
||||
var n0 = n - 1.0;
|
||||
|
|
@ -353,13 +353,13 @@ internal static class PadSynthWaveform
|
|||
var par2 = position.Parameter2 / 255.0;
|
||||
var result = position.Type switch
|
||||
{
|
||||
ZynHarmonicPositionType.ShiftUp => ZynShiftUp(n, n0, par1, par2),
|
||||
ZynHarmonicPositionType.ShiftDown => ZynShiftDown(n, n0, par1, par2),
|
||||
ZynHarmonicPositionType.PowerUp => Math.Pow(n0 / (par1 * 100.0 + 1.0), 1.0 - par2 * 0.8) * (par1 * 100.0 + 1.0) + 1.0,
|
||||
ZynHarmonicPositionType.PowerDown => n0 * (1.0 - par1) + Math.Pow(n0 * 0.1, par2 * 3.0 + 1.0) * par1 * 10.0 + 1.0,
|
||||
ZynHarmonicPositionType.Sine => n0 + Math.Sin(n0 * par2 * par2 * Math.PI * 0.999) * Math.Sqrt(par1) * 2.0 + 1.0,
|
||||
ZynHarmonicPositionType.Power => n0 * Math.Pow(1.0 + par1 * Math.Pow(n0 * 0.8, Math.Pow(par2 * 2.0, 2.0) + 0.1), Math.Pow(par2 * 2.0, 2.0) + 0.1) + 1.0,
|
||||
ZynHarmonicPositionType.Shift => (n + position.Parameter1 / 255.0) / (position.Parameter1 / 255.0 + 1.0),
|
||||
PadHarmonicPositionType.ShiftUp => PadShiftUp(n, n0, par1, par2),
|
||||
PadHarmonicPositionType.ShiftDown => PadShiftDown(n, n0, par1, par2),
|
||||
PadHarmonicPositionType.PowerUp => Math.Pow(n0 / (par1 * 100.0 + 1.0), 1.0 - par2 * 0.8) * (par1 * 100.0 + 1.0) + 1.0,
|
||||
PadHarmonicPositionType.PowerDown => n0 * (1.0 - par1) + Math.Pow(n0 * 0.1, par2 * 3.0 + 1.0) * par1 * 10.0 + 1.0,
|
||||
PadHarmonicPositionType.Sine => n0 + Math.Sin(n0 * par2 * par2 * Math.PI * 0.999) * Math.Sqrt(par1) * 2.0 + 1.0,
|
||||
PadHarmonicPositionType.Power => n0 * Math.Pow(1.0 + par1 * Math.Pow(n0 * 0.8, Math.Pow(par2 * 2.0, 2.0) + 0.1), Math.Pow(par2 * 2.0, 2.0) + 0.1) + 1.0,
|
||||
PadHarmonicPositionType.Shift => (n + position.Parameter1 / 255.0) / (position.Parameter1 / 255.0 + 1.0),
|
||||
_ => n
|
||||
};
|
||||
var forced = position.Parameter3 / 255.0;
|
||||
|
|
@ -367,13 +367,13 @@ internal static class PadSynthWaveform
|
|||
return Math.Max(0.0001, rounded + (1.0 - forced) * (result - rounded));
|
||||
}
|
||||
|
||||
private static double ZynShiftUp(double n, double n0, double par1, double par2)
|
||||
private static double PadShiftUp(double n, double n0, double par1, double par2)
|
||||
{
|
||||
var threshold = par2 * par2 * 100.0 + 1.0;
|
||||
return n < threshold ? n : 1.0 + n0 + (n0 - threshold + 1.0) * par1 * 8.0;
|
||||
}
|
||||
|
||||
private static double ZynShiftDown(double n, double n0, double par1, double par2)
|
||||
private static double PadShiftDown(double n, double n0, double par1, double par2)
|
||||
{
|
||||
var threshold = par2 * par2 * 100.0 + 1.0;
|
||||
return n < threshold ? n : 1.0 + n0 - (n0 - threshold + 1.0) * par1 * 0.90;
|
||||
|
|
@ -483,22 +483,22 @@ internal static class PadSynthWaveform
|
|||
AddInt((int)bank.Profile.Mode);
|
||||
AddInt(bank.Profile.Bandwidth);
|
||||
AddInt(bank.Profile.BandwidthScale);
|
||||
AddInt((int)bank.Profile.ZynProfile.BaseType);
|
||||
AddInt(bank.Profile.ZynProfile.BaseParameter);
|
||||
AddInt(bank.Profile.ZynProfile.FrequencyMultiplier);
|
||||
AddInt(bank.Profile.ZynProfile.ModulatorParameter);
|
||||
AddInt(bank.Profile.ZynProfile.ModulatorFrequency);
|
||||
AddInt(bank.Profile.ZynProfile.Width);
|
||||
AddInt((int)bank.Profile.ZynProfile.AmplitudeType);
|
||||
AddInt((int)bank.Profile.ZynProfile.AmplitudeMode);
|
||||
AddInt(bank.Profile.ZynProfile.AmplitudeParameter1);
|
||||
AddInt(bank.Profile.ZynProfile.AmplitudeParameter2);
|
||||
AddInt(bank.Profile.ZynProfile.AutoScale ? 1 : 0);
|
||||
AddInt((int)bank.Profile.ZynProfile.Half);
|
||||
AddInt((int)bank.Profile.ZynPosition.Type);
|
||||
AddInt(bank.Profile.ZynPosition.Parameter1);
|
||||
AddInt(bank.Profile.ZynPosition.Parameter2);
|
||||
AddInt(bank.Profile.ZynPosition.Parameter3);
|
||||
AddInt((int)bank.Profile.HarmonicProfile.BaseType);
|
||||
AddInt(bank.Profile.HarmonicProfile.BaseParameter);
|
||||
AddInt(bank.Profile.HarmonicProfile.FrequencyMultiplier);
|
||||
AddInt(bank.Profile.HarmonicProfile.ModulatorParameter);
|
||||
AddInt(bank.Profile.HarmonicProfile.ModulatorFrequency);
|
||||
AddInt(bank.Profile.HarmonicProfile.Width);
|
||||
AddInt((int)bank.Profile.HarmonicProfile.AmplitudeType);
|
||||
AddInt((int)bank.Profile.HarmonicProfile.AmplitudeMode);
|
||||
AddInt(bank.Profile.HarmonicProfile.AmplitudeParameter1);
|
||||
AddInt(bank.Profile.HarmonicProfile.AmplitudeParameter2);
|
||||
AddInt(bank.Profile.HarmonicProfile.AutoScale ? 1 : 0);
|
||||
AddInt((int)bank.Profile.HarmonicProfile.Half);
|
||||
AddInt((int)bank.Profile.HarmonicPosition.Type);
|
||||
AddInt(bank.Profile.HarmonicPosition.Parameter1);
|
||||
AddInt(bank.Profile.HarmonicPosition.Parameter2);
|
||||
AddInt(bank.Profile.HarmonicPosition.Parameter3);
|
||||
foreach (var partial in bank.Partials)
|
||||
{
|
||||
AddFloat(partial.Ratio);
|
||||
|
|
|
|||
|
|
@ -852,21 +852,21 @@ public static class PatchScript
|
|||
|
||||
private static PadSpectrumProfile ParsePadSpectrumProfile(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var hasZynFields =
|
||||
var hasPadProfileFields =
|
||||
HasAny(fields,
|
||||
"pad_mode", "zyn_mode",
|
||||
"pad_bandwidth", "zyn_bandwidth", "bandwidth",
|
||||
"pad_bwscale", "zyn_bwscale", "bwscale",
|
||||
"pad_profile", "zyn_profile",
|
||||
"pad_position", "zyn_position");
|
||||
if (!hasZynFields)
|
||||
if (!hasPadProfileFields)
|
||||
{
|
||||
return PadSpectrumProfile.Generic;
|
||||
}
|
||||
|
||||
var mode = TryGetAny(fields, ["pad_mode", "zyn_mode"], out var modeText)
|
||||
? ParsePadSpectrumMode(modeText, line)
|
||||
: PadSpectrumMode.ZynBandwidth;
|
||||
: PadSpectrumMode.Bandwidth;
|
||||
var bandwidth = GetInt(fields, line, 500, "pad_bandwidth", "zyn_bandwidth", "bandwidth");
|
||||
var bandwidthScale = GetInt(fields, line, 0, "pad_bwscale", "zyn_bwscale", "bwscale");
|
||||
if (bandwidth < 0) throw new PatchScriptException(line, "PAD bandwidth must be zero or greater");
|
||||
|
|
@ -875,31 +875,31 @@ public static class PatchScript
|
|||
Math.Clamp(bandwidth, 0, 1000),
|
||||
Math.Clamp(bandwidthScale, 0, 7),
|
||||
TryGetAny(fields, ["pad_profile", "zyn_profile"], out var profileText)
|
||||
? ParseZynHarmonicProfile(profileText, line)
|
||||
: new ZynHarmonicProfile(),
|
||||
? ParsePadHarmonicProfile(profileText, line)
|
||||
: new PadHarmonicProfile(),
|
||||
TryGetAny(fields, ["pad_position", "zyn_position"], out var positionText)
|
||||
? ParseZynHarmonicPosition(positionText, line)
|
||||
: new ZynHarmonicPosition());
|
||||
? ParsePadHarmonicPosition(positionText, line)
|
||||
: new PadHarmonicPosition());
|
||||
}
|
||||
|
||||
private static PadSpectrumMode ParsePadSpectrumMode(string value, int line) => value.ToLowerInvariant() switch
|
||||
{
|
||||
"generic" => PadSpectrumMode.Generic,
|
||||
"bandwidth" or "zyn" or "zyn_bandwidth" => PadSpectrumMode.ZynBandwidth,
|
||||
"discrete" or "other" => PadSpectrumMode.ZynDiscrete,
|
||||
"continuous" => PadSpectrumMode.ZynContinuous,
|
||||
"bandwidth" or "zyn" or "zyn_bandwidth" => PadSpectrumMode.Bandwidth,
|
||||
"discrete" or "other" => PadSpectrumMode.Discrete,
|
||||
"continuous" => PadSpectrumMode.Continuous,
|
||||
_ => throw new PatchScriptException(line, $"unknown PAD spectrum mode `{value}`")
|
||||
};
|
||||
|
||||
private static ZynHarmonicProfile ParseZynHarmonicProfile(string value, int line)
|
||||
private static PadHarmonicProfile ParsePadHarmonicProfile(string value, int line)
|
||||
{
|
||||
var pieces = value.Split(':', StringSplitOptions.TrimEntries);
|
||||
if (pieces.Length != 12)
|
||||
{
|
||||
throw new PatchScriptException(line, "Zyn harmonic profile needs 12 colon-separated fields");
|
||||
throw new PatchScriptException(line, "PAD harmonic profile needs 12 colon-separated fields");
|
||||
}
|
||||
|
||||
return new ZynHarmonicProfile(
|
||||
return new PadHarmonicProfile(
|
||||
ParseProfileBaseType(pieces[0], line),
|
||||
ParseByteish(pieces[1], line),
|
||||
ParseByteish(pieces[2], line),
|
||||
|
|
@ -914,15 +914,15 @@ public static class PatchScript
|
|||
ParseProfileHalf(pieces[11], line));
|
||||
}
|
||||
|
||||
private static ZynHarmonicPosition ParseZynHarmonicPosition(string value, int line)
|
||||
private static PadHarmonicPosition ParsePadHarmonicPosition(string value, int line)
|
||||
{
|
||||
var pieces = value.Split(':', StringSplitOptions.TrimEntries);
|
||||
if (pieces.Length != 4)
|
||||
{
|
||||
throw new PatchScriptException(line, "Zyn harmonic position needs 4 colon-separated fields");
|
||||
throw new PatchScriptException(line, "PAD harmonic position needs 4 colon-separated fields");
|
||||
}
|
||||
|
||||
return new ZynHarmonicPosition(
|
||||
return new PadHarmonicPosition(
|
||||
ParseHarmonicPositionType(pieces[0], line),
|
||||
ParseByteish(pieces[1], line, 255),
|
||||
ParseByteish(pieces[2], line, 255),
|
||||
|
|
@ -934,56 +934,56 @@ public static class PatchScript
|
|||
var parsed = ParseInt(value, line);
|
||||
if (parsed < 0 || parsed > max)
|
||||
{
|
||||
throw new PatchScriptException(line, $"Zyn parameter `{value}` is outside 0..{max}");
|
||||
throw new PatchScriptException(line, $"PAD profile parameter `{value}` is outside 0..{max}");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private static ZynProfileBaseType ParseProfileBaseType(string value, int line) => value.ToLowerInvariant() switch
|
||||
private static PadProfileBaseType ParseProfileBaseType(string value, int line) => value.ToLowerInvariant() switch
|
||||
{
|
||||
"0" or "gauss" or "gaussian" => ZynProfileBaseType.Gaussian,
|
||||
"1" or "square" or "rect" or "rectangular" => ZynProfileBaseType.Square,
|
||||
"2" or "double" or "double_exp" or "doubleexponential" => ZynProfileBaseType.DoubleExponential,
|
||||
_ => throw new PatchScriptException(line, $"unknown Zyn profile base type `{value}`")
|
||||
"0" or "gauss" or "gaussian" => PadProfileBaseType.Gaussian,
|
||||
"1" or "square" or "rect" or "rectangular" => PadProfileBaseType.Square,
|
||||
"2" or "double" or "double_exp" or "doubleexponential" => PadProfileBaseType.DoubleExponential,
|
||||
_ => throw new PatchScriptException(line, $"unknown PAD profile base type `{value}`")
|
||||
};
|
||||
|
||||
private static ZynProfileAmplitudeType ParseProfileAmplitudeType(string value, int line) => value.ToLowerInvariant() switch
|
||||
private static PadProfileAmplitudeType ParseProfileAmplitudeType(string value, int line) => value.ToLowerInvariant() switch
|
||||
{
|
||||
"0" or "off" or "none" => ZynProfileAmplitudeType.Off,
|
||||
"1" or "gauss" or "gaussian" => ZynProfileAmplitudeType.Gaussian,
|
||||
"2" or "sine" => ZynProfileAmplitudeType.Sine,
|
||||
"3" or "flat" => ZynProfileAmplitudeType.Flat,
|
||||
_ => throw new PatchScriptException(line, $"unknown Zyn profile amplitude type `{value}`")
|
||||
"0" or "off" or "none" => PadProfileAmplitudeType.Off,
|
||||
"1" or "gauss" or "gaussian" => PadProfileAmplitudeType.Gaussian,
|
||||
"2" or "sine" => PadProfileAmplitudeType.Sine,
|
||||
"3" or "flat" => PadProfileAmplitudeType.Flat,
|
||||
_ => throw new PatchScriptException(line, $"unknown PAD profile amplitude type `{value}`")
|
||||
};
|
||||
|
||||
private static ZynProfileAmplitudeMode ParseProfileAmplitudeMode(string value, int line) => value.ToLowerInvariant() switch
|
||||
private static PadProfileAmplitudeMode ParseProfileAmplitudeMode(string value, int line) => value.ToLowerInvariant() switch
|
||||
{
|
||||
"0" or "sum" => ZynProfileAmplitudeMode.Sum,
|
||||
"1" or "mult" or "multiply" => ZynProfileAmplitudeMode.Mult,
|
||||
"2" or "div1" => ZynProfileAmplitudeMode.Div1,
|
||||
"3" or "div2" => ZynProfileAmplitudeMode.Div2,
|
||||
_ => throw new PatchScriptException(line, $"unknown Zyn profile amplitude mode `{value}`")
|
||||
"0" or "sum" => PadProfileAmplitudeMode.Sum,
|
||||
"1" or "mult" or "multiply" => PadProfileAmplitudeMode.Mult,
|
||||
"2" or "div1" => PadProfileAmplitudeMode.Div1,
|
||||
"3" or "div2" => PadProfileAmplitudeMode.Div2,
|
||||
_ => throw new PatchScriptException(line, $"unknown PAD profile amplitude mode `{value}`")
|
||||
};
|
||||
|
||||
private static ZynProfileHalf ParseProfileHalf(string value, int line) => value.ToLowerInvariant() switch
|
||||
private static PadProfileHalf ParseProfileHalf(string value, int line) => value.ToLowerInvariant() switch
|
||||
{
|
||||
"0" or "full" => ZynProfileHalf.Full,
|
||||
"1" or "upper" => ZynProfileHalf.Upper,
|
||||
"2" or "lower" => ZynProfileHalf.Lower,
|
||||
_ => throw new PatchScriptException(line, $"unknown Zyn profile half `{value}`")
|
||||
"0" or "full" => PadProfileHalf.Full,
|
||||
"1" or "upper" => PadProfileHalf.Upper,
|
||||
"2" or "lower" => PadProfileHalf.Lower,
|
||||
_ => throw new PatchScriptException(line, $"unknown PAD profile half `{value}`")
|
||||
};
|
||||
|
||||
private static ZynHarmonicPositionType ParseHarmonicPositionType(string value, int line) => value.ToLowerInvariant() switch
|
||||
private static PadHarmonicPositionType ParseHarmonicPositionType(string value, int line) => value.ToLowerInvariant() switch
|
||||
{
|
||||
"0" or "harmonic" => ZynHarmonicPositionType.Harmonic,
|
||||
"1" or "shiftu" or "shift_up" => ZynHarmonicPositionType.ShiftUp,
|
||||
"2" or "shiftl" or "shift_down" => ZynHarmonicPositionType.ShiftDown,
|
||||
"3" or "poweru" or "power_up" => ZynHarmonicPositionType.PowerUp,
|
||||
"4" or "powerl" or "power_down" => ZynHarmonicPositionType.PowerDown,
|
||||
"5" or "sine" => ZynHarmonicPositionType.Sine,
|
||||
"6" or "power" => ZynHarmonicPositionType.Power,
|
||||
"7" or "shift" => ZynHarmonicPositionType.Shift,
|
||||
_ => throw new PatchScriptException(line, $"unknown Zyn harmonic position type `{value}`")
|
||||
"0" or "harmonic" => PadHarmonicPositionType.Harmonic,
|
||||
"1" or "shiftu" or "shift_up" => PadHarmonicPositionType.ShiftUp,
|
||||
"2" or "shiftl" or "shift_down" => PadHarmonicPositionType.ShiftDown,
|
||||
"3" or "poweru" or "power_up" => PadHarmonicPositionType.PowerUp,
|
||||
"4" or "powerl" or "power_down" => PadHarmonicPositionType.PowerDown,
|
||||
"5" or "sine" => PadHarmonicPositionType.Sine,
|
||||
"6" or "power" => PadHarmonicPositionType.Power,
|
||||
"7" or "shift" => PadHarmonicPositionType.Shift,
|
||||
_ => throw new PatchScriptException(line, $"unknown PAD harmonic position type `{value}`")
|
||||
};
|
||||
|
||||
private ParsedEnvelope ParseRateLevelEnvelope(IReadOnlyDictionary<string, string> fields, int line, string fieldPath)
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ public static class ZynInstrumentReader
|
|||
var filterQText = lowPass < 1 ? $" lpf_q={F(lowPassQ)}" : "";
|
||||
var highPassText = highPass > 0 ? $" hpf={F(highPass)} hpf_order={highPassOrder}" : "";
|
||||
script.AppendLine($"layer name={safeName} engine=pad gain={F(layerGain)} lpf={F(lowPass)}{filterQText} lpf_order={lowPassOrder}{highPassText}{filterEnvelopeText}{highPassEnvelopeText} 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}");
|
||||
script.AppendLine($"spectrum layer={safeName} root={F(tableRootFrequencyHz)} freq={F(noteFrequencyHz)} spread=0 pad_mode={mode} pad_bandwidth={bandwidth} pad_bwscale={bandwidthScale} pad_profile={profile} pad_position={position} partials={partialText}");
|
||||
}
|
||||
|
||||
private static ZynKitItem ParseKitItem(XElement item)
|
||||
|
|
|
|||
|
|
@ -89,3 +89,4 @@
|
|||
{"date":"2026-05-15","topic":"zyn-organ-highpass-selected-root","lesson":"Organ Choir's coherent octave blast needs real Zyn PAD high-pass ownership and selected table roots for OscilGen shift-base sources, but selected roots must not become the global default. The source rule is narrow: keep sample-0 roots unless the PAD OscilGen base function is the shift source (`base_function=7`) and the oracle provides the selected note table root.","evidence":"Organ Choir kit0 has analog HP2 with stages and FILTER_ENVELOPE, while kit1 uses base_function=7. Mapping hpf_order/hpf_env and using the selected 622.25 Hz table root only for kit1 moved the candidate's dominant peak to 522.67 Hz with 261 Hz at -1.29 dB, close to Zyn's 523.33 Hz lead and 261 Hz at -2.23 dB. Latest Organ log-mel is 0.499122 / score 0.51966, while accepted Soft Pad, Ghost, and DoublePadBass remain on sample-0 roots; global selected roots had regressed DoublePadBass."}
|
||||
{"date":"2026-05-15","topic":"zyn-organ-basefunc-mod-adaptive","lesson":"Organ Choir's violin-vs-wind mismatch was OscilGen source-table ownership, not pitch, envelope, or gain. Zyn `base_function_modulation` warps the phase before base-function sampling, and `adaptive_harmonics` remaps the prepared oscillator spectrum at the PAD table root; both must happen before PAD bandwidth/profile spreading.","evidence":"Before the fix, Aqua kit1 partials formed a smooth ramp (h3 -3.5 dB, h4 -5.1 dB, h8 -10.2 dB relative to h1) while Zyn's harmonic dump was sparse (h3 -27.1 dB, h4 -70.3 dB, h8 -50.2 dB). Applying base_function_modulation=1 and adaptive_harmonics=1 moved kit1 table energy to 400-800 Hz 0.2713 vs Zyn 0.2780 and 8-16 kHz 0.1471 vs Zyn 0.1355; Organ log-mel improved to 0.471483 and score to 0.541933 without changing accepted DoublePadBass, Ghost, or Soft Pad."}
|
||||
{"date":"2026-05-15","topic":"zyn-pad-ear-vs-logmel","lesson":"For sustained Zyn PAD fixtures, log-mel distance can remain pessimistic after the perceptual target has been reached. Keep it as a regression and triage signal, but do not keep grinding accepted fixtures solely to chase a DX7-like numeric threshold when listening and structural source-table evidence agree.","evidence":"After mapping selected roots, HP filter motion, Q/order cascades, one-based OscilGen filter indices, base-function phase modulation, and adaptive harmonics, the user accepted DoublePadBass, Ghost Ensemble, Soft Pad, Organ Choir, and the other recent community PAD fixtures by ear. Organ Choir still reports log-mel 0.471483 despite direct kit1 table energy matching the Zyn body/air balance and the listener judging all fixtures equivalent."}
|
||||
{"date":"2026-05-16","topic":"pad-syntax-generalization","lesson":"Reference-synth names must not become canonical Aquarium syntax or public patch-model vocabulary. Importers may translate Zyn controls, and parser aliases may keep old parity artifacts readable, but generated/authored `.aqua` should use Aquarium-native `pad_*` terms for general spectral-table intent.","evidence":"Zyn PAD parity had produced generated scripts with `zyn_mode`, `zyn_bandwidth`, `zyn_bwscale`, `zyn_profile`, and `zyn_position`, plus public model names like `ZynHarmonicProfile`. The generalization pass changed generated scripts to `pad_mode`, `pad_bandwidth`, `pad_bwscale`, `pad_profile`, and `pad_position`, renamed the public profile/position records and enums to PAD-native names, preserved legacy `zyn_*` parser aliases, and passed `dotnet test AquariumSynthCSharp.slnx --no-restore` with 108 tests."}
|
||||
|
|
|
|||
|
|
@ -59,9 +59,11 @@ Current slice:
|
|||
`PadSynthWaveform` uses the Zyn PAD profile/bandwidth formulas for those
|
||||
banks and keeps the old generic PADsynth path for ordinary `spectrum`.
|
||||
- `ZynInstrumentReader.RebuildFirstPadAsAquariumScript` now emits readable
|
||||
`zyn_mode=`, `zyn_bandwidth=`, `zyn_bwscale=`, `zyn_profile=...`, and
|
||||
`zyn_position=...` fields, and maps Zyn PAD volume with the source
|
||||
exponential gain curve instead of the old tiny linear `/500` guess.
|
||||
neutral `pad_mode=`, `pad_bandwidth=`, `pad_bwscale=`, `pad_profile=...`,
|
||||
and `pad_position=...` fields, and maps Zyn PAD volume with the source
|
||||
exponential gain curve instead of the old tiny linear `/500` guess. The
|
||||
parser still accepts older `zyn_*` aliases for parity artifacts, but authored
|
||||
`.aqua` should use Aquarium-native PAD terms.
|
||||
- The translator now expands a useful subset of Zyn OscilGen base-function
|
||||
harmonic content plus spectrum adjustment before feeding PAD synthesis. This
|
||||
is a coherent source-table owner, but current parity is mixed: it improves
|
||||
|
|
|
|||
|
|
@ -80,7 +80,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 and split table root from playback pitch: `root` is source/base frequency, while `freq` is the note played by the spectral voice. They generate PADsynth-style FFT wavetables with frequency-domain spreading and deterministic random phase, then emit Faust table readers before ordinary voice treatment. Zyn-fed PAD banks may additionally carry `zyn_mode`, `zyn_bandwidth`, `zyn_bwscale`, `zyn_profile`, and `zyn_position`; those fields belong to the spectral table generator, not to parity-test side channels. Do not pretend this covers every Zyn OscilGen modulation/waveshaping/filter/adaptive-harmonic path yet.
|
||||
- PAD `spectrum` declarations own authored partial clouds under named layers and split table root from playback pitch: `root` is source/base frequency, while `freq` is the note played by the spectral voice. They generate PADsynth-style FFT wavetables with frequency-domain spreading and deterministic random phase, then emit Faust table readers before ordinary voice treatment. Richer PAD table shaping uses neutral `pad_mode`, `pad_bandwidth`, `pad_bwscale`, `pad_profile`, and `pad_position` syntax. The parser may accept older `zyn_*` parity-artifact aliases, but generated/authored `.aqua` should not expose reference-synth names for general DSP intent. Do not pretend this covers every Zyn OscilGen modulation/waveshaping/filter/adaptive-harmonic path yet.
|
||||
- Zyn OscilGen oscillator filters use one-based harmonic indices: first harmonic is `1`, not `0`. Preserve that index when translating filter types or spectral peaks move to the wrong partial.
|
||||
- Zyn PAD analog high-pass filters are filter authority too: HP1/HP2 type, stages, and FILTER_ENVELOPE motion lower through `hpf_order` and `hpf_env=rl`, not ad hoc layer gain or brightness compensation.
|
||||
- Zyn PAD selected table roots are not a global default in Aquarium lowering. Keep sample-0 roots unless a source rule earns the selected root; currently OscilGen shift-base sources (`base_function=7`) may use the selected note table root because Organ Choir proved sample-0 roots leave the octave voice misaligned.
|
||||
|
|
|
|||
|
|
@ -441,7 +441,27 @@ public sealed class PatchScriptTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void SpectralBankParsesZynPadProfileFields()
|
||||
public void SpectralBankParsesPadProfileFields()
|
||||
{
|
||||
var patch = PatchScript.Parse("""
|
||||
layer name=pad engine=pad gain=.08
|
||||
spectrum layer=pad root=77.7813 freq=261.6256 pad_mode=bandwidth pad_bandwidth=485 pad_bwscale=3 pad_profile=gaussian:99:8:12:55:127:sine:mult:80:20:yes:full pad_position=sine:20:40:60 partials=1:.08,2:.04
|
||||
""");
|
||||
|
||||
var bank = Assert.Single(patch.SpectralBanks);
|
||||
Assert.Equal(PadSpectrumMode.Bandwidth, bank.Profile.Mode);
|
||||
Assert.Equal(485, bank.Profile.Bandwidth);
|
||||
Assert.Equal(3, bank.Profile.BandwidthScale);
|
||||
Assert.Equal(PadProfileBaseType.Gaussian, bank.Profile.HarmonicProfile.BaseType);
|
||||
Assert.Equal(8, bank.Profile.HarmonicProfile.FrequencyMultiplier);
|
||||
Assert.Equal(PadProfileAmplitudeType.Sine, bank.Profile.HarmonicProfile.AmplitudeType);
|
||||
Assert.Equal(PadProfileAmplitudeMode.Mult, bank.Profile.HarmonicProfile.AmplitudeMode);
|
||||
Assert.Equal(PadHarmonicPositionType.Sine, bank.Profile.HarmonicPosition.Type);
|
||||
Assert.Equal(60, bank.Profile.HarmonicPosition.Parameter3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpectralBankStillParsesLegacyZynPadProfileFields()
|
||||
{
|
||||
var patch = PatchScript.Parse("""
|
||||
layer name=pad engine=pad gain=.08
|
||||
|
|
@ -449,15 +469,11 @@ public sealed class PatchScriptTests
|
|||
""");
|
||||
|
||||
var bank = Assert.Single(patch.SpectralBanks);
|
||||
Assert.Equal(PadSpectrumMode.ZynBandwidth, bank.Profile.Mode);
|
||||
Assert.Equal(PadSpectrumMode.Bandwidth, bank.Profile.Mode);
|
||||
Assert.Equal(485, bank.Profile.Bandwidth);
|
||||
Assert.Equal(3, bank.Profile.BandwidthScale);
|
||||
Assert.Equal(ZynProfileBaseType.Gaussian, bank.Profile.ZynProfile.BaseType);
|
||||
Assert.Equal(8, bank.Profile.ZynProfile.FrequencyMultiplier);
|
||||
Assert.Equal(ZynProfileAmplitudeType.Sine, bank.Profile.ZynProfile.AmplitudeType);
|
||||
Assert.Equal(ZynProfileAmplitudeMode.Mult, bank.Profile.ZynProfile.AmplitudeMode);
|
||||
Assert.Equal(ZynHarmonicPositionType.Sine, bank.Profile.ZynPosition.Type);
|
||||
Assert.Equal(60, bank.Profile.ZynPosition.Parameter3);
|
||||
Assert.Equal(PadProfileAmplitudeType.Sine, bank.Profile.HarmonicProfile.AmplitudeType);
|
||||
Assert.Equal(PadHarmonicPositionType.Sine, bank.Profile.HarmonicPosition.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -159,11 +159,13 @@ public sealed class ZynInstrumentTests
|
|||
Assert.Equal(0, rebuild.KitItemId);
|
||||
Assert.Contains("spectrum", rebuild.Script);
|
||||
Assert.Contains("root=77.7813", rebuild.Script);
|
||||
Assert.Contains("zyn_profile=", rebuild.Script);
|
||||
Assert.Contains("zyn_position=", rebuild.Script);
|
||||
Assert.Contains("pad_profile=", rebuild.Script);
|
||||
Assert.Contains("pad_position=", rebuild.Script);
|
||||
Assert.DoesNotContain("zyn_profile=", rebuild.Script);
|
||||
Assert.DoesNotContain("zyn_position=", rebuild.Script);
|
||||
var bank = Assert.Single(patch.SpectralBanks);
|
||||
Assert.Equal(77.7813f, bank.RootFrequencyHz, precision: 4);
|
||||
Assert.Equal(PadSpectrumMode.ZynBandwidth, bank.Profile.Mode);
|
||||
Assert.Equal(PadSpectrumMode.Bandwidth, bank.Profile.Mode);
|
||||
Assert.Contains(bank.Partials, partial => Math.Abs(partial.Ratio - 2) < 0.0001f && partial.Gain > 0);
|
||||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "pad_oscillator_harmonics");
|
||||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "pad_harmonic_profile");
|
||||
|
|
|
|||
Loading…
Reference in a new issue