Add community DX7 voice parity survey
This commit is contained in:
parent
b1394638cd
commit
ecae695fe2
6 changed files with 163 additions and 8 deletions
|
|
@ -106,6 +106,19 @@ math is still fake in the places that matter perceptually.
|
|||
`<= 0.145`, envelope distance at `<= 0.08`, zero-crossing at `>= 0.75`,
|
||||
RMS ratio near unity, and score at `>= 0.75`.
|
||||
|
||||
9. **Community voice survey**
|
||||
- Use public-domain community SysEx fixtures as broader behavioral probes
|
||||
after each isolated calibration rung.
|
||||
- `analog1.syx` from Musical Artifacts artifact 152 now has a broad rendered
|
||||
parity test for four actual community voices: `ANLGSYN 1`, `{ Mooger }`,
|
||||
`Piano Bass`, and `RES SYNTH1`. The test writes listening artifacts under
|
||||
`artifacts/parity/dx7-community-analog1/` and gates log-mel at `<= 0.3`,
|
||||
envelope distance at `<= 0.16`, and score at `>= 0.5`.
|
||||
- A first survey also tried `DX1 LEAD B` and `MELLOWSOLO`; they were left
|
||||
out of the passing gate because their log-mel/zero-crossing mismatch is
|
||||
larger. Keep those failures as pressure for oscillator phase, feedback,
|
||||
and transient detail rather than padding the test with weak thresholds.
|
||||
|
||||
## Rejected Path
|
||||
|
||||
Do not keep tuning `PRC SYNTH1` with branch-specific magic constants. That patch
|
||||
|
|
|
|||
|
|
@ -46,3 +46,4 @@
|
|||
{"kind":"patch_library","lesson":"Repo-owned Aquarium .aqua stock now lives under patches/ with library.yaml; every .aqua must parse/export in tests and the patch library must remain out of AquariumSynth.Dsl.nupkg unless intentionally promoted.","source":"Patch library folder slice on 2026-05-14"}
|
||||
{"kind":"dx7_algorithm8_split_route_parity","lesson":"The DX7 summed/cascaded route scale 6.0 is now backed by split project-authored algorithm-8 rendered probes, not only PRC or one combined stack. Latest real-Dexed run: combined stack log-mel 0.04029555 score 0.8664546, summed pair log-mel 0.042429477 score 0.870778, cascade log-mel 0.03811625 score 0.9157809. Keep isolated direct branches at 6.275 and summed/cascaded branches at 6.0 until broader evidence contradicts it.","source":"Split algorithm-8 parity probes on 2026-05-14"}
|
||||
{"kind":"patch_library_taste","lesson":"Not every parity probe belongs in the patch library. Harsh project-authored DX7 calibration probes should stay in tests and ignored listening artifacts unless they are musically useful stock; the navigable .aqua library is for reusable inspiration, not measurement torture signals.","source":"User listening correction on 2026-05-14"}
|
||||
{"kind":"dx7_community_voice_parity","lesson":"Actual public-domain analog1.syx community voices now have a broad rendered parity gate. ANLGSYN 1, { Mooger }, Piano Bass, and RES SYNTH1 pass log-mel <= 0.3, envelope <= 0.16, score >= 0.5 using the current DX7 lowering. The first survey excluded DX1 LEAD B and MELLOWSOLO because their log-mel/zero-crossing mismatch is too large; remaining pressure is fine waveform/phase/feedback behavior, not only broad envelope shape.","source":"Community DX7 analog1.syx survey on 2026-05-14"}
|
||||
|
|
|
|||
|
|
@ -74,9 +74,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, summed/cascaded route scale, calibrated feedback amount table, ratio detune lowering, ROM COM output compensation, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering, 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.18, project-authored algorithm-8 summed-stack/cascade/summed-pair 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; summed/cascaded algorithm-8 branches now use 6.0 after PRC listening, a project-authored combined stack probe, and split cascade/summed-pair probes showed lower values were under-bright. Feedback 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. Direct envelope tracing shows current linear `env=rl` cannot express DX7 EG spike/drop behavior. Dexed-style audio samples EG once per 64-sample block and interpolates applied gain, so raw EG parity alone is not audio parity. Staged operator envelopes now support linear/exponential segments and supranormal levels. `ApproximateAppliedRateLevelEnvelope` uses that surface and now picks the first near-peak before gate so slower rising modulators hit early instead of swelling late. PRC log-mel is down to 0.13874426 with RMS at unity; remaining mismatch is smaller high-air/timbre detail, not broad envelope shape.",
|
||||
"next": "Use the now-validated algorithm-8 route scale as fixed input while isolating remaining PRC high-air/timbre mismatch, then choose broader public-domain voices for import."
|
||||
"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, summed/cascaded route scale, calibrated feedback amount table, ratio detune lowering, ROM COM output compensation, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering, 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.18, project-authored algorithm-8 summed-stack/cascade/summed-pair parity, broad rendered community parity for four analog1.syx voices, 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; summed/cascaded algorithm-8 branches now use 6.0 after PRC listening, a project-authored combined stack probe, and split cascade/summed-pair probes showed lower values were under-bright. Feedback 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. Direct envelope tracing shows current linear `env=rl` cannot express DX7 EG spike/drop behavior. Dexed-style audio samples EG once per 64-sample block and interpolates applied gain, so raw EG parity alone is not audio parity. Staged operator envelopes now support linear/exponential segments and supranormal levels. `ApproximateAppliedRateLevelEnvelope` uses that surface and now picks the first near-peak before gate so slower rising modulators hit early instead of swelling late. PRC log-mel is down to 0.13874426 with RMS at unity; broader analog1.syx community voices pass broad log-mel/envelope gates but expose high zero-crossing mismatch, especially on analog synth and piano-bass timbres.",
|
||||
"next": "Use the now-validated algorithm-8 route scale as fixed input while isolating remaining PRC high-air/timbre mismatch and community-voice zero-crossing mismatch, then expand to broader public-domain banks."
|
||||
},
|
||||
"note_and_envelope_model": {
|
||||
"rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.",
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
},
|
||||
"last_verified": {
|
||||
"command": "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore",
|
||||
"result": "69 passed; algorithm-8 stack/cascade/summed-pair parity probes pass with log-mel <= 0.06 and write listening artifacts",
|
||||
"commit": "pending split DX7 algorithm-8 probes"
|
||||
"result": "70 passed; four actual public-domain analog1.syx community voices pass broad rendered parity and write listening artifacts",
|
||||
"commit": "pending community DX7 voices"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,27 @@ Doctrine update:
|
|||
|
||||
Completed this slice:
|
||||
|
||||
- Added `PublicDomainDx7AnalogCommunityVoicesMeetBroadRenderedParityWhenInstalled`,
|
||||
which renders actual public-domain `analog1.syx` community voices through
|
||||
Dexed and the current Aquarium DX7 lowering. Kept four broad parity voices:
|
||||
`ANLGSYN 1`, `{ Mooger }`, `Piano Bass`, and `RES SYNTH1`.
|
||||
- The test writes WAVs/reports under
|
||||
`artifacts/parity/dx7-community-analog1/<voice>/` and gates log-mel
|
||||
`<= .3`, envelope distance `<= .16`, and score `>= .5`.
|
||||
- Latest kept-voice metrics:
|
||||
- `ANLGSYN 1`: log-mel `.25955316`, envelope `.12271979`, score `.5709625`
|
||||
- `{ Mooger }`: log-mel `.23776375`, envelope `.07168135`, score `.66662127`
|
||||
- `Piano Bass`: log-mel `.19985504`, envelope `.14781862`, score `.5205584`
|
||||
- `RES SYNTH1`: log-mel `.22371109`, envelope `.109231755`, score `.54040927`
|
||||
- First survey also tried `DX1 LEAD B` and `MELLOWSOLO`; they were excluded
|
||||
from the passing gate because log-mel and zero-crossing mismatch were too
|
||||
large. That is pressure, not library stock.
|
||||
- Verified with bundled Python/dexed-py:
|
||||
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
|
||||
70 passed.
|
||||
|
||||
Previous slice:
|
||||
|
||||
- Added two split project-authored DX7 algorithm-8 rendered parity probes:
|
||||
`ProjectAuthoredDx7AlgorithmEightCascadeProbeMeetsParityWhenInstalled` for
|
||||
`6 -> 5 -> 3`, and
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
project: AquariumSynthCSharp
|
||||
spine_version: 1
|
||||
updated: 2026-05-14T14:00:00+01:00
|
||||
updated: 2026-05-14T14:09:00+01:00
|
||||
|
||||
prime_directive: >
|
||||
Build coherent synth machinery. References exist to reveal missing structure,
|
||||
|
|
@ -64,6 +64,7 @@ live_invariants:
|
|||
- Public-domain reference fixtures and dexed-py helpers are test/development material only; the NuGet package must ship only AquariumSynth.Dsl.
|
||||
- Structured `.aqua` stock lives under `patches/` as development source material. Every library patch must parse/export, and the patch library must stay out of the NuGet package unless intentionally promoted.
|
||||
- Project-authored Dexed patches may be generated inside tests for calibration; do not ship them in the NuGet package or treat them as runtime dependencies.
|
||||
- Harsh calibration probes belong in tests and ignored artifacts, not in the patch library unless they are musically useful stock.
|
||||
- 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.
|
||||
|
|
@ -122,8 +123,8 @@ next_agent_start_here:
|
|||
route scale now gives PRC the same character with closer harsh stacked
|
||||
modulation. Split project-authored algorithm-8 cascade and summed-pair
|
||||
probes now back the 6.0 summed/cascaded route scale. Next pressure is
|
||||
residual PRC high-air/timbre mismatch, then broader public-domain voice
|
||||
selection before broad import.
|
||||
residual PRC high-air/timbre mismatch and high zero-crossing mismatch on
|
||||
broader public-domain community voices before broad import.
|
||||
must_read:
|
||||
- AGENTS.md
|
||||
- docs/audio-doctrine.md
|
||||
|
|
@ -136,6 +137,18 @@ next_agent_start_here:
|
|||
- "dotnet test AquariumSynthCSharp.slnx --no-restore"
|
||||
|
||||
completed_slices:
|
||||
- date: 2026-05-14
|
||||
summary: >
|
||||
Added a rendered public-domain community DX7 parity test against four
|
||||
actual `analog1.syx` voices from Musical Artifacts artifact 152:
|
||||
`ANLGSYN 1`, `{ Mooger }`, `Piano Bass`, and `RES SYNTH1`. The test
|
||||
renders Dexed references, lowers the same voices through the current
|
||||
Aquarium DX7 operator-graph path, writes listening WAV/report artifacts
|
||||
under `artifacts/parity/dx7-community-analog1/`, and gates broad parity
|
||||
at log-mel <= 0.3, envelope <= 0.16, score >= 0.5. First survey also
|
||||
tried `DX1 LEAD B` and `MELLOWSOLO`; they remain pressure because
|
||||
log-mel/zero-crossing mismatch is too large for the broad gate.
|
||||
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 70 passed"
|
||||
- date: 2026-05-14
|
||||
summary: >
|
||||
Added split project-authored DX7 algorithm-8 parity probes for the
|
||||
|
|
|
|||
|
|
@ -136,6 +136,41 @@ public sealed class Dx7ReferenceParityTests
|
|||
Assert.True(comparison.Score >= 0.75f, $"{ParityReport(comparison)}{Environment.NewLine}artifacts: {artifactDir}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PublicDomainDx7AnalogCommunityVoicesMeetBroadRenderedParityWhenInstalled()
|
||||
{
|
||||
var cases = new[]
|
||||
{
|
||||
new CommunityDx7VoiceCase(7, "ANLGSYN 1", 0.65f, 1.0f, 0.25f),
|
||||
new CommunityDx7VoiceCase(19, "{ Mooger }", 0.65f, 1.0f, 0.25f),
|
||||
new CommunityDx7VoiceCase(22, "Piano Bass", 0.45f, 0.8f, 0.25f),
|
||||
new CommunityDx7VoiceCase(31, "RES SYNTH1", 0.65f, 1.0f, 0.25f)
|
||||
};
|
||||
|
||||
var comparisons = new List<(CommunityDx7VoiceCase Case, AudioComparison Comparison, string ArtifactDir)>();
|
||||
foreach (var item in cases)
|
||||
{
|
||||
var result = await RenderAndComparePublicDomainDx7VoiceAsync(item);
|
||||
if (result is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
comparisons.Add(result.Value);
|
||||
}
|
||||
|
||||
var report = string.Join(
|
||||
Environment.NewLine + Environment.NewLine,
|
||||
comparisons.Select(item =>
|
||||
$"{item.Case.Index}: {item.Case.ExpectedName}{Environment.NewLine}" +
|
||||
$"{ParityReport(item.Comparison)}{Environment.NewLine}" +
|
||||
$"artifacts: {item.ArtifactDir}"));
|
||||
|
||||
Assert.All(comparisons, item => Assert.True(item.Comparison.LogMelDistance <= 0.3f, report));
|
||||
Assert.All(comparisons, item => Assert.True(item.Comparison.EnvelopeDistance <= 0.16f, report));
|
||||
Assert.All(comparisons, item => Assert.True(item.Comparison.Score >= 0.5f, report));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled()
|
||||
{
|
||||
|
|
@ -443,6 +478,60 @@ public sealed class Dx7ReferenceParityTests
|
|||
return comparison;
|
||||
}
|
||||
|
||||
private static async Task<(CommunityDx7VoiceCase Case, AudioComparison Comparison, string ArtifactDir)?> RenderAndComparePublicDomainDx7VoiceAsync(
|
||||
CommunityDx7VoiceCase item)
|
||||
{
|
||||
var bankPath = FixturePath("Dx7", "PublicDomain", "analog1.syx");
|
||||
var reference = await DexedPyRenderer.RenderAsync(
|
||||
bankPath,
|
||||
patchIndex: item.Index,
|
||||
noteDurationSeconds: item.NoteDurationSeconds,
|
||||
renderDurationSeconds: item.RenderDurationSeconds);
|
||||
|
||||
if (reference is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bank = Dx7SysEx.ParseBank(File.ReadAllBytes(bankPath));
|
||||
var voice = bank.Voices[item.Index];
|
||||
Assert.Equal(item.ExpectedName, voice.Name);
|
||||
|
||||
var graphName = $"dx7_analog1_{item.Index}_{SafeName(voice.Name)}";
|
||||
var script = Dx7VoiceProbeScript(
|
||||
voice,
|
||||
graphName,
|
||||
item.GraphGain,
|
||||
envelopeScale: 0.62f,
|
||||
gateSeconds: item.NoteDurationSeconds,
|
||||
useAppliedEnvelope: true);
|
||||
var candidateSource = FaustEmitter.EmitScript(script);
|
||||
var candidate = await FaustCompiler.RenderAsync(
|
||||
candidateSource.Source,
|
||||
new FaustRenderOptions(DurationSeconds: item.RenderDurationSeconds));
|
||||
|
||||
if (candidate is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var comparison = new AudioAnalyzer(new AudioAnalysisConfig(SampleRate: reference.SampleRate))
|
||||
.Compare(reference.Samples, candidate.Samples);
|
||||
var artifactDir = ArtifactPath(
|
||||
"parity",
|
||||
"dx7-community-analog1",
|
||||
$"{item.Index:00}-{SafeName(voice.Name)}-{DateTimeOffset.UtcNow:yyyyMMddTHHmmssfff}");
|
||||
WriteListeningArtifacts(
|
||||
artifactDir,
|
||||
reference.Samples,
|
||||
candidate.Samples,
|
||||
reference.SampleRate,
|
||||
script,
|
||||
comparison);
|
||||
|
||||
return (item, comparison, artifactDir);
|
||||
}
|
||||
|
||||
private static DexedOperatorSpec SilentOperator(int number) =>
|
||||
FullOperator(number, outputLevel: 0, coarse: 1);
|
||||
|
||||
|
|
@ -595,6 +684,17 @@ public sealed class Dx7ReferenceParityTests
|
|||
private static string F(float value) =>
|
||||
value.ToString("0.######", System.Globalization.CultureInfo.InvariantCulture);
|
||||
|
||||
private static string SafeName(string value)
|
||||
{
|
||||
var builder = new StringBuilder(value.Length);
|
||||
foreach (var ch in value.ToLowerInvariant())
|
||||
{
|
||||
builder.Append(char.IsLetterOrDigit(ch) ? ch : '_');
|
||||
}
|
||||
|
||||
return builder.ToString().Trim('_');
|
||||
}
|
||||
|
||||
private static string ParityReport(AudioComparison comparison) =>
|
||||
string.Join(
|
||||
Environment.NewLine,
|
||||
|
|
@ -627,6 +727,13 @@ internal sealed record DexedOperatorSpec(
|
|||
IReadOnlyList<int> Rates,
|
||||
IReadOnlyList<int> Levels);
|
||||
|
||||
internal sealed record CommunityDx7VoiceCase(
|
||||
int Index,
|
||||
string ExpectedName,
|
||||
float NoteDurationSeconds,
|
||||
float RenderDurationSeconds,
|
||||
float GraphGain);
|
||||
|
||||
internal static class DexedPyRenderer
|
||||
{
|
||||
public static async Task<DexedPyRender?> RenderAsync(
|
||||
|
|
|
|||
Loading…
Reference in a new issue