Map Pink Trombone parity pressure
This commit is contained in:
parent
bb94924f5c
commit
a0eb78270b
8 changed files with 269 additions and 0 deletions
108
docs/pink-trombone-parity.md
Normal file
108
docs/pink-trombone-parity.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# Pink Trombone Parity Packet
|
||||
|
||||
## Objective
|
||||
|
||||
Use Pink Trombone as old-school speech parity pressure, not as a vague mascot
|
||||
for "more vocal knobs." The current AquaSynth speech-loss surface is a
|
||||
source/filter proxy. Pink Trombone is a moving vocal-tract waveguide. Those are
|
||||
not the same machine.
|
||||
|
||||
This packet exists so the mismatch stays inspectable.
|
||||
|
||||
## Reference Source
|
||||
|
||||
Primary source for this packet:
|
||||
|
||||
- `chdh/pink-trombone-mod`: MIT TypeScript modularization of Neil Thapen's Pink
|
||||
Trombone, <https://github.com/chdh/pink-trombone-mod>
|
||||
- Source files inspected:
|
||||
- `src/Synthesizer.ts`
|
||||
- `src/Glottis.ts`
|
||||
- `src/Tract.ts`
|
||||
- `src/TractShaper.ts`
|
||||
|
||||
The original interactive/programmatic Pink Trombone lineage is also visible in
|
||||
`zakaton/Pink-Trombone`, but that package is GPL-3.0. Treat it as reference
|
||||
pressure only unless a later license review says otherwise.
|
||||
|
||||
## Current Mechanism
|
||||
|
||||
Pink Trombone's source-readable graph:
|
||||
|
||||
```text
|
||||
frequency/tenseness/intensity
|
||||
-> LF-style glottal source plus aspiration noise
|
||||
-> 44-section main tract diameter array
|
||||
-> squared diameters become section areas
|
||||
-> adjacent section areas become reflection coefficients
|
||||
-> right/left traveling-wave delay-line state
|
||||
-> 28-section nasal branch through a velum junction
|
||||
-> lip output + nose output
|
||||
```
|
||||
|
||||
The synthesizer steps the tract twice per output sample. Tract shape changes are
|
||||
not static formants: the shaper slews section diameters toward target diameters,
|
||||
updates reflections at block boundaries, injects turbulence at constrictions,
|
||||
and emits a transient when a closure opens.
|
||||
|
||||
## AquaSynth Capability Map
|
||||
|
||||
Current AquaSynth can express:
|
||||
|
||||
- runtime parameters with stable paths;
|
||||
- oscillator/noise sources;
|
||||
- ADSR and staged rate/level envelopes;
|
||||
- low/high/band/notch filters;
|
||||
- static and frame-scanned formant banks;
|
||||
- patch-level and voice-local modulation;
|
||||
- compiled Faust control sweeps through `/speech/output/N`.
|
||||
|
||||
Current AquaSynth cannot express:
|
||||
|
||||
- a 44-cell bidirectional tract waveguide;
|
||||
- independent right/left traveling-wave state arrays;
|
||||
- a 28-cell nasal waveguide branch;
|
||||
- a three-way velum/nose junction;
|
||||
- per-section diameter targets as synthesis authority;
|
||||
- diameter-squared area reflection coefficients;
|
||||
- a subgraph that steps twice per output sample;
|
||||
- turbulence injected at a tract position into neighboring cells;
|
||||
- obstruction-state release transients;
|
||||
- exact Pink Trombone LF glottal coefficient updates.
|
||||
|
||||
## First Parity Rung
|
||||
|
||||
The first committed rung is structural, not audio:
|
||||
|
||||
- `PinkTromboneReference.ToReferencePatch()` records the source, tract features,
|
||||
and relevant controls.
|
||||
- `ReferenceRebuildCatalog.PinkTromboneRebuilds` contains the current
|
||||
AquaSynth source-filter proxy as a deliberately non-passing rebuild.
|
||||
- The proxy script parses and emits Faust, but its missing features explicitly
|
||||
name the waveguide authorities AquaSynth does not own.
|
||||
- `PinkTromboneReferenceDeclaresMissingWaveguideAuthority` prevents the proxy
|
||||
from being mistaken for real tract parity.
|
||||
|
||||
That is the correct failure for now. A failed exactness claim is better than a
|
||||
successful little lie with sliders.
|
||||
|
||||
## Next Cut
|
||||
|
||||
Do not add another formant workaround. The next coherent implementation is one
|
||||
of these:
|
||||
|
||||
1. Add a dedicated tract-DSP model to AquaSynth.Faust/Core: typed tract sections,
|
||||
diameter curves, reflection calculation, glottal source, nasal branch, and a
|
||||
Faust emitter for that structure.
|
||||
2. Add a test-only Pink Trombone oracle renderer and compare AquaSynth's first
|
||||
tract-DSP candidate against fixed vowel/constriction fixtures.
|
||||
|
||||
The new owner should be explainable as:
|
||||
|
||||
```text
|
||||
TractPlan owns section diameters and source/injection events so tract acoustics
|
||||
remain true during rendering.
|
||||
```
|
||||
|
||||
If that sentence becomes awkward, the design is probably drifting back into
|
||||
proxy mud.
|
||||
|
|
@ -608,6 +608,54 @@ public static class BuiltInScripts
|
|||
|
||||
public static readonly IReadOnlyList<string> ZynStyleNames = ["additive-lead", "pad-texture", "vocal-layer"];
|
||||
|
||||
public const string PinkTromboneProxy = """
|
||||
patch
|
||||
gain=0.82
|
||||
soft_clip=true
|
||||
|
||||
param name=frequency path=/pink/frequency default=140 min=10 max=600 step=0.01 unit=Hz rate=audio
|
||||
param name=intensity path=/pink/intensity default=0.7 min=0 max=1 step=0.001
|
||||
param name=tenseness path=/pink/tenseness default=0.6 min=0 max=1 step=0.001
|
||||
param name=tongue_index path=/pink/tongue/index default=12.9 min=0 max=44 step=0.001 unit=cell
|
||||
param name=tongue_diameter path=/pink/tongue/diameter default=2.43 min=0 max=4 step=0.001 unit=diameter
|
||||
param name=velum path=/pink/velum default=0.01 min=0.01 max=0.4 step=0.001 unit=diameter
|
||||
param name=constriction_index path=/pink/constriction/index default=32 min=0 max=44 step=0.001 unit=cell
|
||||
param name=constriction_diameter path=/pink/constriction/diameter default=1 min=-1 max=4 step=0.001 unit=diameter
|
||||
|
||||
defaults
|
||||
wave=saw
|
||||
freq=@/pink/frequency
|
||||
gain=@/pink/intensity
|
||||
attack=0.004
|
||||
sustain=0.18
|
||||
decay=0.08
|
||||
lpf=0.64
|
||||
lpf_q=@/pink/tenseness
|
||||
tremolo=0.02
|
||||
tremolo_hz=6
|
||||
formants=520:90:0.8,1250:170:1,2600:320:0.45
|
||||
formant_mix=0.42
|
||||
|
||||
voice
|
||||
|
||||
voice
|
||||
wave=noise
|
||||
freq=2500
|
||||
gain=0.04
|
||||
noise=0.8
|
||||
hpf=0.45
|
||||
lpf=0.7
|
||||
formants=1800:220:0.8,3600:480:0.55
|
||||
formant_mix=0.35
|
||||
|
||||
voice
|
||||
wave=sine
|
||||
freq=360
|
||||
gain=@/pink/velum
|
||||
formants=300:80:1,900:180:0.45
|
||||
formant_mix=0.55
|
||||
""";
|
||||
|
||||
public const string Dx7StyleAlgorithm32AdditiveOrgan = """
|
||||
patch
|
||||
gain=0.58
|
||||
|
|
@ -1075,6 +1123,7 @@ public static class BuiltInScripts
|
|||
foreach (var item in Classic808PrimitiveGolfScripts) yield return ("808", item.Name, item.Script);
|
||||
foreach (var item in FmBellPrimitiveGolfScripts) yield return ("fm-bell", item.Name, item.Script);
|
||||
foreach (var item in WobbleBassPrimitiveGolfScripts) yield return ("wobble-bass", item.Name, item.Script);
|
||||
yield return ("pink-trombone", "current-aquasynth-proxy", PinkTromboneProxy);
|
||||
foreach (var item in ReferenceRebuildCatalog.Dx7Rebuilds) yield return ("dx7", item.Name, item.Script);
|
||||
foreach (var item in ReferenceRebuildCatalog.ZynRebuilds) yield return ("zynaddsubfx", item.Name, item.Script);
|
||||
foreach (var item in AdvancedReferenceScripts) yield return ("advanced", item.Name, item.Script);
|
||||
|
|
|
|||
74
src/AquaSynth.Core/PinkTromboneReference.cs
Normal file
74
src/AquaSynth.Core/PinkTromboneReference.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
namespace AquaSynth.Dsl;
|
||||
|
||||
public static class PinkTromboneReference
|
||||
{
|
||||
public const string ReferenceId = "pink-trombone/modular-waveguide";
|
||||
|
||||
public static ReferencePatch ToReferencePatch() =>
|
||||
new(
|
||||
ReferenceId,
|
||||
"pink-trombone",
|
||||
"Pink Trombone modular waveguide",
|
||||
Source,
|
||||
Features,
|
||||
Parameters);
|
||||
|
||||
public static ReferenceSource Source { get; } = new(
|
||||
"source",
|
||||
"https://github.com/chdh/pink-trombone-mod",
|
||||
"MIT",
|
||||
"",
|
||||
"MIT TypeScript modularization of Neil Thapen's Pink Trombone, used as source-readable parity pressure.");
|
||||
|
||||
public static IReadOnlyList<ReferenceFeature> Features { get; } =
|
||||
[
|
||||
new("main_tract_waveguide_cells", "44", "Tract.n owns the main vocal tract section count."),
|
||||
new("nose_waveguide_cells", "28", "Tract.noseLength owns the nasal branch section count."),
|
||||
new("tract_sample_rate", "2x-audio-sample-rate", "Synthesizer steps the tract twice for each output sample."),
|
||||
new("glottal_source", "LF-style-normalized-waveform", "Glottis derives an LF-style waveform from frequency and tenseness."),
|
||||
new("glottal_reflection", "0.75", "The source end reflects the left-going tract wave while adding glottal output."),
|
||||
new("lip_reflection", "-0.85", "The lip end reflects the right-going tract wave and radiates output."),
|
||||
new("diameter_authority", "section-diameter-array", "A 44-element diameter array owns tract shape before reflection calculation."),
|
||||
new("reflection_formula", "(area[i-1]-area[i])/(area[i-1]+area[i])", "Reflection coefficients derive from adjacent squared diameters."),
|
||||
new("nose_junction", "three-way-velum-reflection", "The tract/nose junction uses left, right, and nose reflection coefficients from local areas."),
|
||||
new("turbulence_injection", "positioned-constriction-noise", "Frication noise is injected into neighboring tract cells based on constriction position and diameter."),
|
||||
new("closure_transients", "obstruction-release-impulse", "Opening a closure can add a decaying transient at the obstruction position."),
|
||||
new("tract_shape_motion", "diameter-target-slew", "TractShaper moves diameters toward target shape with position-dependent return speeds.")
|
||||
];
|
||||
|
||||
public static IReadOnlyList<PatchParameter> Parameters { get; } =
|
||||
[
|
||||
new("/pink/frequency", "Frequency", 140, 10, 600, 0.01f, "Hz", "audio", "Glottal target frequency."),
|
||||
new("/pink/intensity", "Intensity", 0.7f, 0, 1, 0.001f, "normalized", "control", "Glottal excitation intensity."),
|
||||
new("/pink/tenseness", "Tenseness", 0.6f, 0, 1, 0.001f, "normalized", "control", "LF waveform/turbulence voice-quality control."),
|
||||
new("/pink/tongue/index", "Tongue index", 12.9f, 0, 44, 0.001f, "cell", "control", "Tongue-body constriction center in tract-cell coordinates."),
|
||||
new("/pink/tongue/diameter", "Tongue diameter", 2.43f, 0, 4, 0.001f, "diameter", "control", "Tongue-body diameter target."),
|
||||
new("/pink/velum", "Velum", 0.01f, 0.01f, 0.4f, 0.001f, "diameter", "control", "Nasal branch opening diameter."),
|
||||
new("/pink/constriction/index", "Constriction index", 32, 0, 44, 0.001f, "cell", "control", "Fricative or closure constriction position."),
|
||||
new("/pink/constriction/diameter", "Constriction diameter", 1, -1, 4, 0.001f, "diameter", "control", "Fricative or closure constriction diameter.")
|
||||
];
|
||||
|
||||
public static ReferenceRebuild CurrentAquaSynthProxy() =>
|
||||
new(
|
||||
"pink-trombone/current-aquasynth-proxy",
|
||||
"pink-trombone",
|
||||
"Current AquaSynth source-filter proxy",
|
||||
ReferenceId,
|
||||
BuiltInScripts.PinkTromboneProxy,
|
||||
[
|
||||
new("runtime_controls", "/pink/frequency,/pink/intensity,/pink/tenseness,/pink/tongue/index,/pink/tongue/diameter,/pink/velum", "AquaSynth can expose stable runtime parameter paths."),
|
||||
new("glottal_pitch_pressure_proxy", "oscillator-frequency-plus-gain", "AquaSynth can drive pitch/loudness-like controls without recompiling."),
|
||||
new("turbulence_proxy", "noise-voice", "AquaSynth can emit a separate noise source for frication pressure."),
|
||||
new("formant_proxy", "static-resonant-bandpass-bank", "AquaSynth can sketch vowel/body color with formant filters.")
|
||||
],
|
||||
[
|
||||
new("main_tract_waveguide_cells", "44 bidirectional sections", "No AquaSynth DSL authority owns arrays of right/left traveling wave state per tract section."),
|
||||
new("nose_waveguide_cells", "28 bidirectional sections", "The current DSL has no nasal branch delay-line graph or velum-controlled three-way junction."),
|
||||
new("diameter_to_reflection_coefficients", "per-block area reflection update", "Formant filters do not preserve the section-area ownership or reflection math."),
|
||||
new("twice_rate_tract_stepping", "two tract steps per audio sample", "AquaSynth voice graphs do not express a subgraph with its own stepping rate."),
|
||||
new("positioned_turbulence_injection", "noise injected into neighboring cells", "The current noise field is source/color pressure, not a positioned tract injection event."),
|
||||
new("closure_release_transients", "obstruction release impulse", "AquaSynth envelopes can shape amplitude, but they do not observe obstruction state and emit release transients."),
|
||||
new("lf_glottal_exactness", "Pink Trombone LF-style coefficients", "The proxy uses ordinary oscillators/noise, not the LF coefficient path from tenseness.")
|
||||
],
|
||||
"This is a deliberately non-passing rebuild: it keeps the current source-filter proxy parseable while naming the tract-DSP authority AquaSynth lacks.");
|
||||
}
|
||||
|
|
@ -131,8 +131,18 @@ public static class ReferenceRebuildCatalog
|
|||
"This is the first thresholded audio parity rung. It proves the render/compare loop on a lawful fixture without pretending the DX7 internals are solved.")
|
||||
];
|
||||
|
||||
public static readonly IReadOnlyList<ReferenceRebuild> PinkTromboneRebuilds =
|
||||
[
|
||||
PinkTromboneReference.CurrentAquaSynthProxy()
|
||||
];
|
||||
|
||||
public static IEnumerable<ReferenceRebuild> All()
|
||||
{
|
||||
foreach (var rebuild in PinkTromboneRebuilds)
|
||||
{
|
||||
yield return rebuild;
|
||||
}
|
||||
|
||||
foreach (var rebuild in ZynRebuilds)
|
||||
{
|
||||
yield return rebuild;
|
||||
|
|
|
|||
|
|
@ -129,3 +129,4 @@
|
|||
{"date":"2026-05-24","topic":"cultmesh-speech-training-step","lesson":"AquaSynth can now run the first honest CultMesh-distributed speech training step: an authority trainer emits SpeechRenderRequest documents and a SpeechWorkerPayloadManifest, admits only CultMesh peers with valid speech-render-worker leases, records SpeechWorkerAdmissionReceipt documents for admitted/rejected peers, lets admitted workers produce SpeechRenderResult gradients, and applies those gradients centrally through SpeechBackpropagationPipeline. Workers still do not write checkpoints.","evidence":"Added GameCult.Mesh to AquaSynth.Core, added payload/admission/assignment/training-step types and store helpers in SpeechDistributedTraining.cs, added RunCultMeshTrainingStep, and added a test with two leased workers plus rejected unleased/read-replica peers. Focused verification: `dotnet test tests/AquaSynth.Dsl.Tests/AquaSynth.Dsl.Tests.csproj --filter \"SpeechDistributedTrainingTests\" -v minimal`: 3 passed. Remaining infrastructure gap: real remote artifact chunk transfer and process supervision belong to CultMesh, not an AquaSynth-local workaround."}
|
||||
{"date":"2026-05-25","topic":"vocal-tract-control-playground","lesson":"The current AquaSynth vocal-tract lane is touchable as a voice-patch target surface before it is a Pink Trombone-class tract simulation. A useful playground should expose the real VocalTractControlTarget knobs directly: these are the controls the learned speech driver predicts from phonetic and utterance input, not learned controls themselves. Keep the tract-ish visualization honest that the WebAudio renderer is a lab proxy rather than full waveguide anatomy.","evidence":"Added tools/vocal-tract-playground with static HTML/CSS/JS, a Node static server, WebAudio source/filter synthesis, tract canvas visualization, presets, and sliders for the 14 base voice-patch target controls plus six mel bands. Verified with `node --check` on app.js/serve.mjs, curl against http://localhost:5125/, and browser interaction: Play toggled audio, Ee preset updated controls and canvas, console errors were empty."}
|
||||
{"date":"2026-05-25","topic":"speech-loss-control-authority","lesson":"Exporting `/speech/output/N` controls is not proof that the speech training surface is usable. Every base VocalTractControlTarget output must route into audible DSP before gradient descent is trusted; epsilon keep-alive references are not control authority.","evidence":"The compiled Faust speech-loss surface previously used tongue body, tongue tip, lip aperture, lip rounding, and velum only in the final keep-controls epsilon. Routed tract controls into small resonant source-filter proxies, velum into a nasal tone, LFO depth into tremolo depth, and added tests that fail when base controls are only declared/kept alive. Focused verification: `dotnet test AquaSynth.sln --no-restore --filter FaustRenderedSpeechLossSurfaceTests` passed. Full `dotnet test AquaSynth.sln --no-restore` timed out twice at 120s and 300s without a result; orphaned dotnet workers were stopped."}
|
||||
{"date":"2026-05-25","topic":"pink-trombone-parity-boundary","lesson":"Pink Trombone parity must pressure AquaSynth at the tract-DSP authority, not through more source/filter proxy controls. The current DSL can expose parameters and formant/noise proxies, but it cannot exactly represent Pink Trombone's moving bidirectional waveguide graph: 44 main tract cells, 28 nasal cells, diameter-to-area reflection coefficients, velum junction, twice-rate tract stepping, positioned turbulence injection, closure transients, or exact LF glottal coefficient updates.","evidence":"Inspected `chdh/pink-trombone-mod` source (`Synthesizer.ts`, `Glottis.ts`, `Tract.ts`, `TractShaper.ts`) and added `docs/pink-trombone-parity.md`, `PinkTromboneReference`, a current non-passing AquaSynth proxy rebuild, and `PinkTromboneReferenceDeclaresMissingWaveguideAuthority` so the proxy cannot be mistaken for actual tract parity."}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ surfaces:
|
|||
reference_roadmap: docs/reference-synth-roadmap.md
|
||||
ipa_vocal_tract_roadmap: docs/ipa-vocal-tract-roadmap.md
|
||||
utterance_parity_research: docs/utterance-parity-research.md
|
||||
pink_trombone_parity: docs/pink-trombone-parity.md
|
||||
wet_voice_witness_fixture: tests/AquaSynth.Dsl.Tests/Fixtures/Speech/WetVoice01
|
||||
faust_toolchain_boundary: docs/faust-toolchain-boundary.md
|
||||
dx7_public_domain_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain
|
||||
|
|
@ -35,6 +36,7 @@ modules:
|
|||
model: src/AquaSynth.Core/Model.cs
|
||||
parser: src/AquaSynth.Core/PatchScript.cs
|
||||
vocal_tract_model: src/AquaSynth.Core/VocalTractModel.cs
|
||||
pink_trombone_reference: src/AquaSynth.Core/PinkTromboneReference.cs
|
||||
packed_neural_network: src/AquaSynth.Core/PackedNeuralNetwork.cs
|
||||
utterance_embedding_learning: src/AquaSynth.Core/UtteranceEmbeddingLearning.cs
|
||||
speech_backpropagation_pipeline: src/AquaSynth.Core/SpeechBackpropagationPipeline.cs
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ current_mechanism:
|
|||
zyn_pressure_survey: docs/zynaddsubfx-pressure-survey.md
|
||||
roadmap: docs/reference-synth-roadmap.md
|
||||
utterance_parity_research: docs/utterance-parity-research.md
|
||||
pink_trombone_parity: docs/pink-trombone-parity.md
|
||||
parameter_contracts: src/AquaSynth.Core/Model.cs
|
||||
|
||||
live_invariants:
|
||||
|
|
@ -109,6 +110,7 @@ live_invariants:
|
|||
- Do not hide missing graph structure behind unrelated voice fields.
|
||||
- The speech lane is the active center: IPA parity, utterance embeddings, synth-driver automation, rendered loss, and compiled Faust control sweeps take priority over new DX7/Zyn digestion.
|
||||
- Every learned speech arrow must stay inspectable: utterance metadata -> embedding, embedding plus phonetic features -> automation, automation -> rendered audio, rendered audio -> loss/gradient.
|
||||
- Pink Trombone parity is structural waveguide pressure, not permission to add more formant proxy knobs. Current AquaSynth cannot exactly express the 44-cell main tract, 28-cell nasal branch, diameter-to-reflection updates, twice-rate tract stepping, positioned turbulence injection, or closure transients through the ordinary `voice` DSL.
|
||||
- eSpeak NG is an early parity reference for timing, IPA coverage, and log-mel pressure, not an anatomy oracle or final target.
|
||||
- Do not expand into prosody, emotional context, personality, or broad Weksa expressivity until the tiny IPA utterance harness can show useful loss movement.
|
||||
- Speech training receipts must keep time as a witness: every request, render result, and checkpoint should record stage decision time, latency spent, latency budget, confidence, and artifact/model identifiers.
|
||||
|
|
|
|||
|
|
@ -288,6 +288,29 @@ public sealed class PatchScriptTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PinkTromboneReferenceDeclaresMissingWaveguideAuthority()
|
||||
{
|
||||
var reference = PinkTromboneReference.ToReferencePatch();
|
||||
var rebuild = Assert.Single(ReferenceRebuildCatalog.PinkTromboneRebuilds);
|
||||
var patch = PatchScript.Parse(rebuild.Script);
|
||||
var export = FaustEmitter.Emit(patch, new FaustExportOptions("pink_trombone_proxy"));
|
||||
|
||||
Assert.Equal("pink-trombone", rebuild.Family);
|
||||
Assert.Contains(reference.Features, feature => feature.Name == "main_tract_waveguide_cells" && feature.Value == "44");
|
||||
Assert.Contains(reference.Features, feature => feature.Name == "nose_waveguide_cells" && feature.Value == "28");
|
||||
Assert.Contains(reference.Features, feature => feature.Name == "tract_sample_rate" && feature.Value == "2x-audio-sample-rate");
|
||||
Assert.Contains(reference.Features, feature => feature.Name == "reflection_formula");
|
||||
Assert.Contains(reference.Parameters, parameter => parameter.Path == "/pink/tongue/index");
|
||||
Assert.Contains(reference.Parameters, parameter => parameter.Path == "/pink/constriction/diameter");
|
||||
Assert.Contains(rebuild.MatchedFeatures, feature => feature.Name == "formant_proxy");
|
||||
Assert.Contains(rebuild.MissingFeatures, feature => feature.Name == "main_tract_waveguide_cells");
|
||||
Assert.Contains(rebuild.MissingFeatures, feature => feature.Name == "diameter_to_reflection_coefficients");
|
||||
Assert.Contains(rebuild.MissingFeatures, feature => feature.Name == "nose_waveguide_cells");
|
||||
Assert.NotEmpty(patch.Parameters);
|
||||
Assert.Contains("process =", export.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZynReferenceRebuildsTrackFixtureFeaturePressure()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue