Add project-authored DX7 parity probes

This commit is contained in:
Meta Vens 2026-05-14 05:50:18 +01:00
commit d7ff704489
6 changed files with 275 additions and 20 deletions

View file

@ -61,6 +61,9 @@ math is still fake in the places that matter perceptually.
- Render fixed-output operators with varied DX7 EG levels and rates.
- Fit the rate/level envelope amplitude curve instead of assuming
`level / 99`.
- Current cut lesson: reusing the operator output-level amplitude curve for
EG levels improves some envelope metrics but fails the hard PRC log-mel
target. Do not land it without isolated envelope parity.
7. **Summed/cascaded modulation**
- Render project-authored algorithm-8 stacks that isolate `6 -> 5 -> 3`
@ -68,8 +71,8 @@ math is still fake in the places that matter perceptually.
- Replace hard `PRC SYNTH1` route probe logic with a topology-aware DX7
route rule: isolated direct branches keep the two-op scale, while
summed/cascaded branches use the summed-modulator scale.
- A hard-target calibrated summed-modulator scale exists; project-authored
isolated stack sweeps should still tighten it before broad DX7 import.
- A project-authored algorithm-8 summed-stack parity test now backs the
topology-aware summed-modulator scale.
8. **Hard target replay**
- Re-run `PRC SYNTH1`.

View file

@ -34,3 +34,5 @@
{"kind":"dx7_feedback_scale","lesson":"Isolated Dexed feedback sweeps fit a nonlinear Aquarium feedback table: 0, .01, .02, .05, .10, .19, .38, .66 for DX7 feedback values 0-7. PRC feedback value 5 was already accidentally close via feedback*0.04; the table moves that knowledge into Dx7SysEx.OperatorFeedbackAmount.","source":"DX7 feedback calibration rung on 2026-05-14"}
{"kind":"dx7_detune_and_com","lesson":"PRC exposed two separate DX7 facts: ratio-mode detune must lower into operator frequency ratios, and ROM COM output compensation must be applied relative to the algorithm-32 six-output baseline used by the carrier-amplitude calibration. This cut the PRC carrier scale table; the remaining cascaded-route constant is still a fenced hard-target scar needing summed-modulator calibration.","source":"DX7 PRC parity rung on 2026-05-14"}
{"kind":"dx7_topology_aware_route_index","lesson":"The PRC hard target proved DX7 route index cannot key only on ROM edge kind: a direct edge feeding a later summed branch behaves like part of the cascaded/summed stack, not like the isolated two-op branch. `Dx7SysEx.OperatorRouteIndex(topology, edge)` now owns this distinction; PRC no longer has a private route function.","source":"DX7 summed/cascaded route lowering on 2026-05-14"}
{"kind":"dx7_project_authored_patch_rendering","lesson":"Project-authored Dexed patches can be generated directly inside tests from explicit operator specs. Use this for lawful calibration rungs instead of squeezing every DX7 fact out of public-domain hard patches.","source":"DX7 project-authored summed-stack parity on 2026-05-14"}
{"kind":"dx7_envelope_curve_cut","lesson":"Reusing the operator output amplitude curve for DX7 EG levels is not ready: it can improve isolated envelope shape metrics with shorter timing, but it failed the hard PRC log-mel target. Keep EG levels linear until isolated envelope parity passes without regressing PRC.","source":"DX7 envelope level experiment on 2026-05-14"}

View file

@ -16,6 +16,7 @@
"reference patch contracts",
"test-only public-domain DX7 fixtures",
"test-only dexed-py reference renderer",
"test-only project-authored Dexed patch renderer",
"ignored listening WAV parity artifacts",
"thresholded DX7 rendered parity test",
"reference rebuild catalog",
@ -67,9 +68,9 @@
"rule": "Add DSL abstractions only when reference rebuilds prove the missing invariant. Do not consume references that cannot be backed by parity tests; parsing alone is inventory, not evidence of expressivity."
},
"dx7_reference_path": {
"status": "DX7 SysEx parser extracts voice edit buffers, packed voices, packed 32-voice banks, six operators, envelopes, algorithm, feedback, oscillator sync, LFO, transpose, voice name, checksum validation, ReferencePatch features, algorithm topology from the reverse-engineered OPS algorithm ROM table, envelope-to-ADSR approximations, measured carrier output amplitude, isolated two-op modulation route scale, calibrated feedback amount table, ratio detune lowering, ROM COM output compensation, topology-aware route lowering, staged operator envelopes, two Aquarium DSL rebuild attempts, first-class OperatorGraph support, public-domain SysEx fixture coverage, dexed-py reference rendering, Aquarium candidate rendering through Faust-generated C#, one thresholded rendered parity test for analog1.syx voice 13, a hard PRC SYNTH1 parity probe with listening WAV artifacts and log-mel <= 0.255, and renderable delayed operator feedback.",
"lesson": "Algorithm 32's additive six-carrier shape maps cleanly to six Aquarium voices. Algorithm 8 justified OperatorGraph: operators, modulation edges, carriers, and feedback belong to a graph authority, not Voice fields. The first rendered parity rung is behavioral and sine-like, not exact DX7 operator execution. A harder algorithm-8 target exposed that feedback must be delayed recursion in Faust, not a direct cyclic self-reference. Static DX7 output-level approximation helped import/rebuild math but was mathematically wrong for carrier amplitude. A single-carrier Dexed sweep shows carrier amplitude follows 2^((outputLevel - 99) / 8). Isolated two-op DX7 FM maps full-level modulation to Aquarium route index 6.275, while summed/cascaded algorithm-8 branches need a lower topology-aware route scale. Feedback now lowers through a fitted table instead of PRC-local constants. Ratio-mode detune belongs in operator frequency lowering. ROM COM values carry output-count compensation; because the amplitude curve was fitted against algorithm 32, DX7 carriers scale by 6/(COM+1). Staged operator envelopes now let Aquarium express four-rate/four-level contours in readable DSL.",
"next": "Follow docs/dx7-calibration-plan.md: calibrate envelope level curve and independently sweep summed/cascaded modulation before adding new DSL surface or tuning PRC constants."
"status": "DX7 SysEx parser extracts voice edit buffers, packed voices, packed 32-voice banks, six operators, envelopes, algorithm, feedback, oscillator sync, LFO, transpose, voice name, checksum validation, ReferencePatch features, algorithm topology from the reverse-engineered OPS algorithm ROM table, envelope-to-ADSR approximations, measured carrier output amplitude, isolated two-op modulation route scale, calibrated feedback amount table, ratio detune lowering, ROM COM output compensation, topology-aware route lowering, staged operator envelopes, two Aquarium DSL rebuild attempts, first-class OperatorGraph support, public-domain SysEx fixture coverage, dexed-py reference rendering, project-authored Dexed patch rendering, Aquarium candidate rendering through Faust-generated C#, one thresholded rendered parity test for analog1.syx voice 13, a hard PRC SYNTH1 parity probe with listening WAV artifacts and log-mel <= 0.255, project-authored algorithm-8 summed-stack parity, and renderable delayed operator feedback.",
"lesson": "Algorithm 32's additive six-carrier shape maps cleanly to six Aquarium voices. Algorithm 8 justified OperatorGraph: operators, modulation edges, carriers, and feedback belong to a graph authority, not Voice fields. The first rendered parity rung is behavioral and sine-like, not exact DX7 operator execution. A harder algorithm-8 target exposed that feedback must be delayed recursion in Faust, not a direct cyclic self-reference. Static DX7 output-level approximation helped import/rebuild math but was mathematically wrong for carrier amplitude. A single-carrier Dexed sweep shows carrier amplitude follows 2^((outputLevel - 99) / 8). Isolated two-op DX7 FM maps full-level modulation to Aquarium route index 6.275, while summed/cascaded algorithm-8 branches need a lower topology-aware route scale. Feedback now lowers through a fitted table instead of PRC-local constants. Ratio-mode detune belongs in operator frequency lowering. ROM COM values carry output-count compensation; because the amplitude curve was fitted against algorithm 32, DX7 carriers scale by 6/(COM+1). Project-authored Dexed patch rendering now gives lawful calibration targets. Reusing the operator output curve for EG levels was tested and cut because it failed hard-target log-mel. Staged operator envelopes now let Aquarium express four-rate/four-level contours in readable DSL.",
"next": "Follow docs/dx7-calibration-plan.md: solve the DX7 envelope level/time curve with isolated project-authored parity before adding new DSL surface or tuning PRC constants."
},
"note_and_envelope_model": {
"rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.",
@ -84,7 +85,7 @@
},
"last_verified": {
"command": "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore",
"result": "60 passed; latest focused PRC log-mel 0.24476814, score 0.5708394",
"commit": "pending DX7 topology-aware route lowering"
"result": "61 passed; project-authored summed stack and PRC passed",
"commit": "pending project-authored DX7 patch renderer"
}
}

View file

@ -15,6 +15,22 @@ Doctrine update:
Completed this slice:
- Added test-only project-authored Dexed patch rendering through
`DexedPyRenderer.RenderPatchAsync`. It builds a `dexed.Patch` from explicit
operator specs and renders it without adding any shipped fixture or package
surface.
- Added an independent project-authored algorithm-8 summed-stack parity test.
This backs the topology-aware route lowering outside the public-domain PRC
patch, so `SummedOperatorModulationRouteIndex` is no longer only PRC-shaped
evidence.
- Cut the attempted DX7 EG exponential level curve again. On an isolated
envelope target, exponential EG levels plus a shorter timing scale improved
shape versus linear levels, but PRC still failed log-mel (`0.2857573` at a
tuned timing scale). The live lowering stays linear for EG levels until an
isolated envelope target passes without harming the hard target.
Previous slice:
- Moved the remaining PRC cascaded-route scalar into a topology-aware DX7 route
helper. `Dx7SysEx.OperatorRouteIndex(topology, edge)` now keeps the isolated
two-op route scale (`6.275`) for standalone direct branches and uses the

View file

@ -1,6 +1,6 @@
project: AquariumSynthCSharp
spine_version: 1
updated: 2026-05-14T04:34:00+01:00
updated: 2026-05-14T05:00:00+01:00
prime_directive: >
Build coherent synth machinery. References exist to reveal missing structure,
@ -57,6 +57,7 @@ live_invariants:
- Recompile only for graph shape changes; vary declared parameters through the hosted DSP control API.
- Reference fixtures must preserve provenance, license status, source hash, and feature pressure.
- Public-domain reference fixtures and dexed-py helpers are test/development material only; the NuGet package must ship only AquariumSynth.Dsl.
- 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.
- Listening WAV artifacts belong under ignored `artifacts/`; keep them out of NuGet and commits unless explicitly promoted as fixtures.
- Do not consume external synth targets that cannot be backed by parity tests. Parsing is inventory; reproducing behavior through terse, readable Aquarium DSL is the proof.
- Audio metrics are evidence, not final judgment.
@ -68,7 +69,7 @@ reference_targets:
priority: 1
purpose: "Structured FM/operator graph pressure test."
source: "https://github.com/asb2m10/dexed"
status: "DX7 SysEx parser, public-domain SysEx fixture, dexed-py reference rendering, first thresholded rendered parity test for analog1.syx voice 13, hard PRC SYNTH1 probe with listening WAV artifacts and log-mel <= 0.255, renderable delayed operator feedback, DX7 effective operator-level approximation, measured carrier output amplitude, isolated route index, feedback table, ratio detune lowering, ROM COM output compensation, staged operator envelopes, algorithm topology extraction, envelope-to-ADSR approximation, two Aquarium DSL rebuild attempts, first-class operator graph support, and Aquarium candidate rendering through Faust-generated C# exist."
status: "DX7 SysEx parser, public-domain SysEx fixture, dexed-py reference rendering, project-authored Dexed patch rendering, first thresholded rendered parity test for analog1.syx voice 13, hard PRC SYNTH1 probe with listening WAV artifacts and log-mel <= 0.255, project-authored algorithm-8 summed-stack parity, renderable delayed operator feedback, DX7 effective operator-level approximation, measured carrier output amplitude, isolated route index, feedback table, ratio detune lowering, ROM COM output compensation, staged operator envelopes, algorithm topology extraction, envelope-to-ADSR approximation, two Aquarium DSL rebuild attempts, first-class operator graph support, and Aquarium candidate rendering through Faust-generated C# exist."
first_slice:
- "Add a harder public-domain DX7 parity target that audibly needs operator routing."
zynaddsubfx:
@ -106,12 +107,13 @@ next_agent_start_here:
recommended_first_task: >
Listen to the latest generated PRC SYNTH1 WAVs under
`artifacts/parity/dx7-prc-synth1`. Continue the ladder in
`docs/dx7-calibration-plan.md` with envelope level curve and isolated
summed/cascaded modulation sweeps. Single-carrier amplitude, isolated
`docs/dx7-calibration-plan.md` with envelope level curve calibration.
Single-carrier amplitude, isolated
two-op modulation index, feedback, ratio detune, ROM COM output
compensation, and topology-aware route lowering are now fitted or mapped;
the summed route scale is still hard-target calibrated rather than
independently swept.
a project-authored algorithm-8 stack test now backs the summed route scale
outside PRC. The attempted exponential EG level curve was cut because it
still failed PRC log-mel.
must_read:
- AGENTS.md
- docs/audio-doctrine.md
@ -124,6 +126,15 @@ next_agent_start_here:
- "dotnet test AquariumSynthCSharp.slnx --no-restore"
completed_slices:
- date: 2026-05-14
summary: >
Added test-only project-authored Dexed patch rendering and an independent
algorithm-8 summed-stack parity test. This backs topology-aware summed
route lowering outside the public-domain PRC hard target. An attempted
exponential DX7 EG level curve was tested and cut: it improved some
envelope metrics but failed PRC log-mel, so EG levels remain linear until
isolated envelope parity can pass without harming PRC.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|Dx7SysExTests\": passed"
- date: 2026-05-14
summary: >
Moved the remaining PRC cascaded-route scalar into

View file

@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Text.Json;
using System.Text;
namespace AquariumSynth.Dsl.Tests;
@ -97,7 +98,12 @@ public sealed class Dx7ReferenceParityTests
var voice = bank.Voices[17];
Assert.Equal("PRC SYNTH1", voice.Name);
var script = PrcSynth1ProbeScript(voice);
var script = Dx7VoiceProbeScript(
voice,
graphName: "dx7_prc_synth1_probe",
graphGain: 0.39f,
envelopeScale: 0.62f,
gateSeconds: null);
var candidateSource = FaustEmitter.EmitScript(script);
var candidate = await FaustCompiler.RenderAsync(
candidateSource.Source,
@ -126,6 +132,55 @@ public sealed class Dx7ReferenceParityTests
Assert.True(comparison.Score >= 0.40f, $"{ParityReport(comparison)}{Environment.NewLine}artifacts: {artifactDir}");
}
[Fact]
public async Task ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled()
{
var spec = new DexedPatchSpec(
"AQ ALG8SUM",
Algorithm: 8,
Feedback: 0,
Operators:
[
SilentOperator(1),
SilentOperator(2),
FullOperator(3, outputLevel: 99, coarse: 1),
FullOperator(4, outputLevel: 88, coarse: 1),
FullOperator(5, outputLevel: 86, coarse: 2),
FullOperator(6, outputLevel: 82, coarse: 4)
]);
var reference = await DexedPyRenderer.RenderPatchAsync(
spec,
noteDurationSeconds: 0.45f,
renderDurationSeconds: 0.7f);
if (reference is null)
{
return;
}
var script = Dx7VoiceProbeScript(
VoiceFromSpec(spec),
graphName: "dx7_algorithm8_summed_stack_probe",
graphGain: 0.39f,
envelopeScale: 0.62f,
gateSeconds: 0.45f);
var candidateSource = FaustEmitter.EmitScript(script);
var candidate = await FaustCompiler.RenderAsync(
candidateSource.Source,
new FaustRenderOptions(DurationSeconds: 0.7f));
if (candidate is null)
{
return;
}
var comparison = new AudioAnalyzer(new AudioAnalysisConfig(SampleRate: reference.SampleRate))
.Compare(reference.Samples, candidate.Samples);
Assert.True(comparison.LogMelDistance <= 0.31f, ParityReport(comparison));
Assert.True(comparison.Score >= 0.35f, ParityReport(comparison));
}
private static string FixturePath(params string[] parts) =>
Path.Combine([AppContext.BaseDirectory, "Fixtures", .. parts]);
@ -141,7 +196,12 @@ public sealed class Dx7ReferenceParityTests
return Path.Combine([root, "artifacts", .. parts]);
}
private static string PrcSynth1ProbeScript(Dx7Voice voice)
private static string Dx7VoiceProbeScript(
Dx7Voice voice,
string graphName,
float graphGain,
float envelopeScale,
float? gateSeconds)
{
var topology = Dx7SysEx.AlgorithmTopology(voice.Algorithm);
var builder = new StringBuilder();
@ -150,16 +210,16 @@ public sealed class Dx7ReferenceParityTests
builder.AppendLine(" soft_clip=false");
builder.AppendLine();
builder.AppendLine("opgraph");
builder.AppendLine(" name=dx7_prc_synth1_probe");
builder.AppendLine($" name={graphName}");
builder.AppendLine(" freq=261.6256");
builder.AppendLine(" gain=0.39");
builder.AppendLine($" gain={F(graphGain)}");
builder.AppendLine();
foreach (var op in voice.Operators.OrderByDescending(op => op.Number))
{
var level = Dx7SysEx.ApproximateOperatorLevel(op).LinearLevel *
Dx7SysEx.OperatorOutputCompensation(topology, op.Number);
var envelope = ScaledEnvelope(Dx7SysEx.ApproximateRateLevelEnvelope(op.Envelope), 0.62f);
var envelope = ScaledEnvelope(Dx7SysEx.ApproximateRateLevelEnvelope(op.Envelope), envelopeScale, gateSeconds);
builder.AppendLine($"operator name=op{op.Number}");
builder.AppendLine($" ratio={F(Dx7SysEx.OperatorFrequencyRatio(op))}");
builder.AppendLine($" level={F(level)}");
@ -187,7 +247,49 @@ public sealed class Dx7ReferenceParityTests
return builder.ToString();
}
private static Dx7RateLevelEnvelopeApproximation ScaledEnvelope(Dx7RateLevelEnvelopeApproximation approximation, float scale)
private static Dx7Voice VoiceFromSpec(DexedPatchSpec spec) =>
new(
spec.Name,
spec.Operators.Select(op =>
new Dx7Operator(
op.Number,
new Dx7Envelope(
op.Rates[0],
op.Rates[1],
op.Rates[2],
op.Rates[3],
op.Levels[0],
op.Levels[1],
op.Levels[2],
op.Levels[3]),
BreakPoint: 39,
LeftDepth: 0,
RightDepth: 0,
LeftCurve: 0,
RightCurve: 0,
RateScaling: 0,
AmplitudeModulationSensitivity: 0,
KeyVelocitySensitivity: 0,
OutputLevel: op.OutputLevel,
FrequencyMode: op.FrequencyMode == 0 ? Dx7FrequencyMode.Ratio : Dx7FrequencyMode.Fixed,
FrequencyCoarse: op.FrequencyCoarse,
FrequencyFine: op.FrequencyFine,
Detune: op.Detune))
.ToArray(),
new Dx7PitchEnvelope(new Dx7Envelope(99, 99, 99, 99, 50, 50, 50, 50)),
spec.Algorithm,
spec.Feedback,
OscillatorSync: true,
new Dx7Lfo(35, 0, 0, 0, KeySync: true, Dx7LfoWaveform.Sine, PitchModulationSensitivity: 3),
Transpose: 24);
private static DexedOperatorSpec SilentOperator(int number) =>
FullOperator(number, outputLevel: 0, coarse: 1);
private static DexedOperatorSpec FullOperator(int number, int outputLevel, int coarse) =>
new(number, outputLevel, FrequencyCoarse: coarse, FrequencyFine: 0, Detune: 7, FrequencyMode: 0, Rates: [99, 99, 99, 99], Levels: [99, 99, 99, 0]);
private static Dx7RateLevelEnvelopeApproximation ScaledEnvelope(Dx7RateLevelEnvelopeApproximation approximation, float scale, float? gateSeconds)
{
var envelope = approximation.Envelope;
var scaled = new RateLevelEnvelope(
@ -202,7 +304,7 @@ public sealed class Dx7ReferenceParityTests
return approximation with
{
Envelope = scaled,
GateSeconds = Math.Max((scaled.Rate1Seconds + scaled.Rate2Seconds + scaled.Rate3Seconds) * 0.68f, 0.02f)
GateSeconds = gateSeconds ?? Math.Max((scaled.Rate1Seconds + scaled.Rate2Seconds + scaled.Rate3Seconds) * 0.68f, 0.02f)
};
}
@ -280,6 +382,22 @@ public sealed class Dx7ReferenceParityTests
internal sealed record DexedPyRender(float[] Samples, int SampleRate, string Stdout, string Stderr);
internal sealed record DexedPatchSpec(
string Name,
int Algorithm,
int Feedback,
IReadOnlyList<DexedOperatorSpec> Operators);
internal sealed record DexedOperatorSpec(
int Number,
int OutputLevel,
int FrequencyCoarse,
int FrequencyFine,
int Detune,
int FrequencyMode,
IReadOnlyList<int> Rates,
IReadOnlyList<int> Levels);
internal static class DexedPyRenderer
{
public static async Task<DexedPyRender?> RenderAsync(
@ -335,6 +453,59 @@ internal static class DexedPyRenderer
}
}
public static async Task<DexedPyRender?> RenderPatchAsync(
DexedPatchSpec patch,
int midiNote = 60,
int velocity = 100,
float noteDurationSeconds = 1,
float renderDurationSeconds = 1.5f,
int sampleRate = 44100,
CancellationToken cancellationToken = default)
{
var python = await FindPythonWithDexedAsync(cancellationToken);
if (python is null) return null;
var tempDir = Path.Combine(Path.GetTempPath(), $"aquarium-dx7-patch-render-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
try
{
var scriptPath = Path.Combine(tempDir, "render_project_dx7.py");
var specPath = Path.Combine(tempDir, "patch.json");
var outputPath = Path.Combine(tempDir, "reference.f32");
await File.WriteAllTextAsync(scriptPath, PatchScriptSource(), cancellationToken);
await File.WriteAllTextAsync(specPath, JsonSerializer.Serialize(patch), cancellationToken);
var result = await RunAsync(
python.FileName,
[
.. python.PrefixArguments,
scriptPath,
specPath,
outputPath,
midiNote.ToStringInvariant(),
velocity.ToStringInvariant(),
noteDurationSeconds.ToStringInvariant(),
renderDurationSeconds.ToStringInvariant(),
sampleRate.ToStringInvariant()
],
cancellationToken);
if (result.ExitCode != 0 || !File.Exists(outputPath))
{
return new DexedPyRender([], sampleRate, result.Stdout, result.Stderr);
}
var bytes = await File.ReadAllBytesAsync(outputPath, cancellationToken);
var samples = new float[bytes.Length / sizeof(float)];
Buffer.BlockCopy(bytes, 0, samples, 0, samples.Length * sizeof(float));
return new DexedPyRender(samples, sampleRate, result.Stdout, result.Stderr);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
private static async Task<PythonCommand?> FindPythonWithDexedAsync(CancellationToken cancellationToken)
{
var configuredPython = Environment.GetEnvironmentVariable("AQUARIUM_DX7_PYTHON");
@ -389,6 +560,57 @@ internal static class DexedPyRenderer
np.asarray(audio, dtype=np.float32).tofile(output_path)
""";
private static string PatchScriptSource() =>
"""
import json
import sys
import numpy as np
from dexed import Patch, DexedSynth
spec_path = sys.argv[1]
output_path = sys.argv[2]
midi_note = int(sys.argv[3])
velocity = int(sys.argv[4])
note_duration = float(sys.argv[5])
render_duration = float(sys.argv[6])
sample_rate = int(sys.argv[7])
with open(spec_path, "r", encoding="utf-8") as f:
spec = json.load(f)
patch = Patch(spec["Name"])
patch.algorithm = int(spec["Algorithm"]) - 1
patch.feedback = int(spec["Feedback"])
patch.transpose = 24
for op in patch.op:
op.output_level = 0
op.envelope.rates = [99, 99, 99, 99]
op.envelope.levels = [99, 99, 99, 0]
op.frequency_coarse = 1
op.frequency_fine = 0
op.frequency_mode = 0
op.detune = 7
for op_spec in spec["Operators"]:
op = patch.op[int(op_spec["Number"]) - 1]
op.output_level = int(op_spec["OutputLevel"])
op.frequency_coarse = int(op_spec["FrequencyCoarse"])
op.frequency_fine = int(op_spec["FrequencyFine"])
op.frequency_mode = int(op_spec["FrequencyMode"])
op.detune = int(op_spec["Detune"])
op.envelope.rates = [int(v) for v in op_spec["Rates"]]
op.envelope.levels = [int(v) for v in op_spec["Levels"]]
synth = DexedSynth(sample_rate=sample_rate)
synth.load_patch(patch)
audio = synth.render(
midi_note=midi_note,
velocity=velocity,
note_duration=note_duration,
render_duration=render_duration)
np.asarray(audio, dtype=np.float32).tofile(output_path)
""";
private static async Task<(int ExitCode, string Stdout, string Stderr)> RunAsync(
string fileName,
IReadOnlyList<string> arguments,