Add DX7 graph pitch LFO lowering
This commit is contained in:
parent
64a326fa4f
commit
bfa70f9754
11 changed files with 129 additions and 28 deletions
|
|
@ -150,6 +150,14 @@ math is still fake in the places that matter perceptually.
|
|||
peak at `0.92`. This leaves `Piano Bass` unchanged, improves `RES SYNTH1`,
|
||||
and keeps `ANLGSYN 1` judged primarily by log-mel plus sustained high-band
|
||||
evidence rather than aggregate score.
|
||||
- The remaining `ANLGSYN 1` beat comes from DX7 pitch LFO, not amplitude
|
||||
LFO: the patch has `AMD=71` but all operators have `AMS=0`, while
|
||||
`PMD=32`, `PMS=1`, sine LFO speed `38`, and delay `33` are active. The
|
||||
operator graph now owns graph-level vibrato and lowers this to
|
||||
`vibrato=0.003694`, `vibrato_hz=5.993276`, and a faded
|
||||
`vibrato_delay=0.233333`. A hard delay gate was tested and cut because it
|
||||
pushed log-mel just over the gate; fade-in preserves the beat without
|
||||
adding an onset discontinuity.
|
||||
- Removing ROM COM carrier boosts made the community harmonics closer by
|
||||
ear but much quieter. Restoring loudness with graph gain keeps the improved
|
||||
balance: `{ Mooger }` uses `gain=0.75`, `Piano Bass` uses `gain=0.72`,
|
||||
|
|
|
|||
|
|
@ -241,6 +241,30 @@ public static class Dx7SysEx
|
|||
return 1;
|
||||
}
|
||||
|
||||
public static float LfoFrequencyHz(Dx7Lfo lfo)
|
||||
{
|
||||
var speed = Math.Clamp(lfo.Speed, 0, 99) / 99f;
|
||||
return 0.1f + 40f * speed * speed;
|
||||
}
|
||||
|
||||
public static float LfoDelaySeconds(Dx7Lfo lfo) =>
|
||||
Math.Clamp(lfo.Delay, 0, 99) / 99f * 0.7f;
|
||||
|
||||
public static float PitchLfoDepth(Dx7Lfo lfo)
|
||||
{
|
||||
if (lfo.PitchModulationDepth <= 0 || lfo.PitchModulationSensitivity <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.Clamp(
|
||||
lfo.PitchModulationDepth / 99f *
|
||||
lfo.PitchModulationSensitivity / 7f *
|
||||
0.08f,
|
||||
0,
|
||||
0.08f);
|
||||
}
|
||||
|
||||
public static float OperatorFrequencyRatio(Dx7Operator op, int midiNote = 60, float baseFrequencyHz = 261.62558f)
|
||||
{
|
||||
if (op.FrequencyMode == Dx7FrequencyMode.Fixed)
|
||||
|
|
|
|||
|
|
@ -347,7 +347,16 @@ public static class FaustEmitter
|
|||
source.AppendLine($"{name}_note_freq = hslider(\"/{name}/note/frequency\", {F(graph.Note.FrequencyHz)}, 20, 20000, 0.01) : si.smoo;");
|
||||
source.AppendLine($"{name}_note_gate = button(\"/{name}/note/gate\");");
|
||||
}
|
||||
source.AppendLine($"{name}_freq = {graphNoteFreq};");
|
||||
var graphVibratoDepth = parameters.Expression($"{graphPath}/pitch/vibrato", graph.VibratoDepth);
|
||||
var graphVibratoHz = parameters.Expression($"{graphPath}/pitch/vibrato_hz", graph.VibratoHz);
|
||||
var graphVibratoDelay = parameters.Expression($"{graphPath}/pitch/vibrato_delay", graph.VibratoDelaySeconds);
|
||||
var hasGraphVibrato = graph.VibratoDepth > 0 && graph.VibratoHz > 0 ||
|
||||
parameters.IsBound($"{graphPath}/pitch/vibrato") ||
|
||||
parameters.IsBound($"{graphPath}/pitch/vibrato_hz");
|
||||
var graphPitchMod = hasGraphVibrato
|
||||
? $" * max(0.0, 1.0 + clip01(age / max(0.0001, {graphVibratoDelay})) * {graphVibratoDepth} * lfo_sin({graphVibratoHz}, 0.0))"
|
||||
: "";
|
||||
source.AppendLine($"{name}_freq = {graphNoteFreq}{graphPitchMod};");
|
||||
foreach (var op in ordered)
|
||||
{
|
||||
var opName = $"{name}_op_{op.Id}";
|
||||
|
|
|
|||
|
|
@ -156,7 +156,10 @@ public sealed record OperatorGraph(
|
|||
IReadOnlyList<OperatorEdge> Edges,
|
||||
IReadOnlyList<int> Carriers,
|
||||
Note Note = null!,
|
||||
float Gain = 0.2f)
|
||||
float Gain = 0.2f,
|
||||
float VibratoDepth = 0,
|
||||
float VibratoHz = 0,
|
||||
float VibratoDelaySeconds = 0)
|
||||
{
|
||||
public Note Note { get; init; } = Note ?? new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -339,16 +339,19 @@ public static class PatchScript
|
|||
: operators.Select(op => op.Id).ToList();
|
||||
|
||||
AddValidatedOperatorGraph(line, new OperatorGraph(
|
||||
GetAny(fields, ["name", "n"], $"opgraph{graphIndex}"),
|
||||
GetBoundFloat(fields, line, 440, $"{graphPath}/freq", "freq", "frequency", "f"),
|
||||
operators,
|
||||
edges,
|
||||
carriers,
|
||||
new Note(
|
||||
Name: GetAny(fields, ["name", "n"], $"opgraph{graphIndex}"),
|
||||
FrequencyHz: GetBoundFloat(fields, line, 440, $"{graphPath}/freq", "freq", "frequency", "f"),
|
||||
Operators: operators,
|
||||
Edges: edges,
|
||||
Carriers: carriers,
|
||||
Note: new Note(
|
||||
GetBoundFloat(fields, line, 440, $"{graphPath}/note/frequency", "freq", "frequency", "f"),
|
||||
GetBoundFloat(fields, line, 0.1f, $"{graphPath}/note/gate", "gate", "hold", "duration"),
|
||||
ParseNoteSource(GetAny(fields, ["note_source", "source"], "oneshot"), line)),
|
||||
GetBoundFloat(fields, line, 0.2f, $"{graphPath}/gain", "gain", "g")));
|
||||
Gain: GetBoundFloat(fields, line, 0.2f, $"{graphPath}/gain", "gain", "g"),
|
||||
VibratoDepth: GetBoundFloat(fields, line, 0, $"{graphPath}/pitch/vibrato", "vibrato", "vib"),
|
||||
VibratoHz: GetBoundFloat(fields, line, 0, $"{graphPath}/pitch/vibrato_hz", "vibrato_hz", "vib_hz"),
|
||||
VibratoDelaySeconds: GetBoundFloat(fields, line, 0, $"{graphPath}/pitch/vibrato_delay", "vibrato_delay", "vib_delay")));
|
||||
}
|
||||
|
||||
private void StartOperatorGraph(IReadOnlyDictionary<string, string> fields, int line)
|
||||
|
|
@ -371,7 +374,10 @@ public static class PatchScript
|
|||
GetBoundFloat(fields, line, 440, $"{graphPath}/note/frequency", "freq", "frequency", "f"),
|
||||
GetBoundFloat(fields, line, 0.1f, $"{graphPath}/note/gate", "gate", "hold", "duration"),
|
||||
ParseNoteSource(GetAny(fields, ["note_source", "source"], "oneshot"), line)),
|
||||
GetBoundFloat(fields, line, 0.2f, $"{graphPath}/gain", "gain", "g"));
|
||||
GetBoundFloat(fields, line, 0.2f, $"{graphPath}/gain", "gain", "g"),
|
||||
GetBoundFloat(fields, line, 0, $"{graphPath}/pitch/vibrato", "vibrato", "vib"),
|
||||
GetBoundFloat(fields, line, 0, $"{graphPath}/pitch/vibrato_hz", "vibrato_hz", "vib_hz"),
|
||||
GetBoundFloat(fields, line, 0, $"{graphPath}/pitch/vibrato_delay", "vibrato_delay", "vib_delay"));
|
||||
}
|
||||
|
||||
private void AddOperator(IReadOnlyDictionary<string, string> fields, int line)
|
||||
|
|
@ -390,13 +396,13 @@ public static class PatchScript
|
|||
GetBoundFloat(fields, line, graph.Note.GateSeconds, $"{operatorPath}/note/gate", "gate", "hold", "duration"));
|
||||
|
||||
graph.Operators.Add(new OperatorNode(
|
||||
id,
|
||||
GetBoundFloat(fields, line, 1, $"{operatorPath}/ratio", "ratio", "r"),
|
||||
GetBoundFloat(fields, line, 1, $"{operatorPath}/level", "level", "l"),
|
||||
GetBoundFloat(fields, line, 0, $"{operatorPath}/feedback", "feedback", "fb"),
|
||||
graph.Note with { GateSeconds = envelopeSpec.GateSeconds },
|
||||
envelopeSpec.Envelope,
|
||||
envelopeSpec.RateLevelEnvelope));
|
||||
Id: id,
|
||||
Ratio: GetBoundFloat(fields, line, 1, $"{operatorPath}/ratio", "ratio", "r"),
|
||||
Level: GetBoundFloat(fields, line, 1, $"{operatorPath}/level", "level", "l"),
|
||||
Feedback: GetBoundFloat(fields, line, 0, $"{operatorPath}/feedback", "feedback", "fb"),
|
||||
Note: graph.Note with { GateSeconds = envelopeSpec.GateSeconds },
|
||||
Envelope: envelopeSpec.Envelope,
|
||||
RateLevelEnvelope: envelopeSpec.RateLevelEnvelope));
|
||||
}
|
||||
|
||||
private void AddOperatorRoute(IReadOnlyDictionary<string, string> fields, int line)
|
||||
|
|
@ -428,7 +434,10 @@ public static class PatchScript
|
|||
graph.Edges,
|
||||
graph.Carriers.Count > 0 ? graph.Carriers : graph.Operators.Select(op => op.Id).ToList(),
|
||||
graph.Note,
|
||||
graph.Gain));
|
||||
graph.Gain,
|
||||
graph.VibratoDepth,
|
||||
graph.VibratoHz,
|
||||
graph.VibratoDelaySeconds));
|
||||
}
|
||||
|
||||
private void AddValidatedOperatorGraph(int line, OperatorGraph graph)
|
||||
|
|
@ -549,12 +558,12 @@ public static class PatchScript
|
|||
}
|
||||
|
||||
return new OperatorNode(
|
||||
ParseInt(pieces[0], line),
|
||||
ParseFloat(pieces[1], line),
|
||||
ParseFloat(pieces[2], line),
|
||||
pieces.Length >= 4 ? ParseFloat(pieces[3], line) : 0,
|
||||
pieces.Length >= 6 ? new Note(GateSeconds: ParseFloat(pieces[4], line)) : new Note(),
|
||||
pieces.Length >= 6 ? new Envelope(ReleaseSeconds: ParseFloat(pieces[5], line)) : new Envelope());
|
||||
Id: ParseInt(pieces[0], line),
|
||||
Ratio: ParseFloat(pieces[1], line),
|
||||
Level: ParseFloat(pieces[2], line),
|
||||
Feedback: pieces.Length >= 4 ? ParseFloat(pieces[3], line) : 0,
|
||||
Note: pieces.Length >= 6 ? new Note(GateSeconds: ParseFloat(pieces[4], line)) : new Note(),
|
||||
Envelope: pieces.Length >= 6 ? new Envelope(ReleaseSeconds: ParseFloat(pieces[5], line)) : new Envelope());
|
||||
})
|
||||
.ToList();
|
||||
|
||||
|
|
@ -756,7 +765,10 @@ public static class PatchScript
|
|||
string Name,
|
||||
float FrequencyHz,
|
||||
Note Note,
|
||||
float Gain)
|
||||
float Gain,
|
||||
float VibratoDepth,
|
||||
float VibratoHz,
|
||||
float VibratoDelaySeconds)
|
||||
{
|
||||
public List<OperatorNode> Operators { get; } = [];
|
||||
public List<OperatorEdge> Edges { get; } = [];
|
||||
|
|
|
|||
|
|
@ -56,3 +56,4 @@
|
|||
{"kind":"dx7_effective_note_scaling","lesson":"DX7 operator key-scaling and rate-scaling must lower against the effective played MIDI note after voice transpose, not hardcoded MIDI 60. `Dx7SysEx.OperatorRateScaling` now feeds the block-interpolated applied envelope trace, and imported operator levels use the effective note. This improves RES SYNTH1 attack/log-mel and Piano Bass harmonic metrics; Piano Bass graph gain rebalances from 0.90 to 0.72. MELLOWSOLO remains the clearest harmonic mismatch and now has a pressure artifact test rather than a fake parity pass. A trial output-level-dependent applied-envelope trace was cut because it worsened MELLOWSOLO and ANLGSYN 1.","source":"community DX7 listening correction on 2026-05-14"}
|
||||
{"kind":"dx7_modulator_peak_headroom","lesson":"Listening found ANLGSYN 1 and RES SYNTH1 blowing out into a drone at the modulator-envelope peak while Piano Bass was spot on. The surviving DX7 probe change caps only algorithm-2 non-carrier applied-envelope Level1 at 0.92, preserving Piano Bass and improving RES SYNTH1 while keeping ANLGSYN judged by log-mel and sustained high-band evidence rather than aggregate score. A wider summed-route headroom experiment for Mooger fan-in was tested and cut because it worsened Mooger metrics and did not isolate the overtone-emphasis issue.","source":"community DX7 listening correction on 2026-05-14"}
|
||||
{"kind":"dx7_mellowsolo_tail_window","lesson":"MELLOWSOLO pressure artifacts need a longer render window than 1.0s because the Aquarium candidate emits a 0.6s release from a 0.65s gate, so the old WAV cut the tail before it could finish. Extending only the MELLOWSOLO pressure render to 1.25s improves log-mel from about 0.429 to 0.347 and makes the tail audible without changing the synth lowering or passing parity targets. A global traced-release-duration experiment was tested and cut because it shortened releases too aggressively and broke PRC, Piano Bass, RES SYNTH1, and ANLGSYN 1. RES SYNTH1 has oscillator sync enabled, so its phase-like attack issue is more likely relative operator attack/peak shape than free-running oscillator phase.","source":"community DX7 listening correction on 2026-05-14"}
|
||||
{"kind":"dx7_graph_pitch_lfo","lesson":"ANLGSYN 1's missing beat is DX7 pitch LFO, not amplitude LFO: raw analog1.syx has AMD=71 but all operator AMS values are 0, while PMD=32/PMS=1/sine speed 38/delay 33 are active. Aquarium operator graphs now own graph-level vibrato (`vibrato`, `vibrato_hz`, `vibrato_delay`) and the DX7 probe lowers that LFO to about depth 0.003694 at 5.993 Hz with a 0.233s fade-in. A hard delay gate was tested and cut because it pushed log-mel over the ANLGSYN gate; fade-in keeps ANLGSYN passing at log-mel about 0.19497 and score about 0.6979.","source":"ANLGSYN 1 pitch-LFO beat correction on 2026-05-14"}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@
|
|||
"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, voice transpose lowering, fixed-frequency operator lowering, ROM COM output compensation cut, operator key/rate scaling at the effective played note, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering with optional sustain floor, 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.145, project-authored algorithm-8 summed-stack/cascade/summed-pair parity, rendered community parity for Mooger, Piano Bass, RES SYNTH1, and ANLGSYN 1 with a sustained high-band gate, a MELLOWSOLO pressure artifact test, 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; ANLGSYN 1 showed max feedback value 7 needs a hotter Aquarium amount 2.2 to recover buzzy high bands, while value 5 remains 0.19 for PRC. ANLGSYN 1 then exposed a sustained-buzz gap: output soft clip helped but did not stop mid-note drift; max-feedback source operators need an applied-envelope sustain floor so the feedback loop stays driven after the attack. Ratio-mode detune belongs in operator frequency lowering. DX7 voice transpose belongs in graph base frequency: transpose 24 is neutral, transpose 12 lowers MIDI note 60 to 130.8128 Hz. DX7 fixed-frequency operators are absolute Hz, not note ratios; ANLGSYN 1 proved fixed op1/op3 must lower to ratios around 0.007615/0.009227 against a 130.8128 Hz graph base to preserve low buzzing modulation. ROM COM carrier compensation was cut: user listening found no-COM renders harmonically closer and mainly too quiet, so `OperatorOutputCompensation` returns unity and graph gain owns loudness recovery. Operator key-scaling and rate-scaling must lower against the effective played MIDI note after transpose; hardcoded MIDI 60 made RES SYNTH1 too soft on attack and Piano Bass less harmonically accurate. 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; later output-level-dependent and naive traced-release applied-envelope experiments were also cut because they worsened broader parity. 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; it can also preserve a floor for max-feedback source sustain. Community gains with no COM and played-note scaling: Mooger 0.75, Piano Bass 0.72, RES SYNTH1 0.75. Algorithm-2 non-carrier applied envelopes currently cap the first peak at 0.92 to prevent ANLGSYN 1/RES SYNTH1 peak blowout while preserving Piano Bass. MELLOWSOLO is a pressure artifact test, not passing parity; its render window is 1.25s so the candidate release is audible instead of cut at the artifact boundary. Mooger needs a more aggressive attack contour, and RES SYNTH1's phase-like attack issue is likely relative operator attack/peak shape because oscillator sync is enabled. PRC remains passing on log-mel/envelope/RMS, with score floor lowered to 0.74 after rate scaling changed the aggregate score.",
|
||||
"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, voice transpose lowering, fixed-frequency operator lowering, ROM COM output compensation cut, operator key/rate scaling at the effective played note, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering with optional sustain floor, two Aquarium DSL rebuild attempts, first-class OperatorGraph support with graph-level vibrato, 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.145, project-authored algorithm-8 summed-stack/cascade/summed-pair parity, rendered community parity for Mooger, Piano Bass, RES SYNTH1, and ANLGSYN 1 with a sustained high-band gate, a MELLOWSOLO pressure artifact test, 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, feedback, and graph-level pitch motion 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; ANLGSYN 1 showed max feedback value 7 needs a hotter Aquarium amount 2.2 to recover buzzy high bands, while value 5 remains 0.19 for PRC. ANLGSYN 1 then exposed a sustained-buzz gap: output soft clip helped but did not stop mid-note drift; max-feedback source operators need an applied-envelope sustain floor so the feedback loop stays driven after the attack. ANLGSYN 1's remaining beat is pitch LFO: AMD is nonzero but all operator AMS values are 0, while PMD/PMS lower cleanly to operator-graph vibrato with faded delay. Ratio-mode detune belongs in operator frequency lowering. DX7 voice transpose belongs in graph base frequency: transpose 24 is neutral, transpose 12 lowers MIDI note 60 to 130.8128 Hz. DX7 fixed-frequency operators are absolute Hz, not note ratios; ANLGSYN 1 proved fixed op1/op3 must lower to ratios around 0.007615/0.009227 against a 130.8128 Hz graph base to preserve low buzzing modulation. ROM COM carrier compensation was cut: user listening found no-COM renders harmonically closer and mainly too quiet, so `OperatorOutputCompensation` returns unity and graph gain owns loudness recovery. Operator key-scaling and rate-scaling must lower against the effective played MIDI note after transpose; hardcoded MIDI 60 made RES SYNTH1 too soft on attack and Piano Bass less harmonically accurate. 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; later output-level-dependent and naive traced-release applied-envelope experiments were also cut because they worsened broader parity. 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; it can also preserve a floor for max-feedback source sustain. Community gains with no COM and played-note scaling: Mooger 0.75, Piano Bass 0.72, RES SYNTH1 0.75. Algorithm-2 non-carrier applied envelopes currently cap the first peak at 0.92 to prevent ANLGSYN 1/RES SYNTH1 peak blowout while preserving Piano Bass. MELLOWSOLO is a pressure artifact test, not passing parity; its render window is 1.25s so the candidate release is audible instead of cut at the artifact boundary. Mooger needs a more aggressive attack contour, and RES SYNTH1's phase-like attack issue is likely relative operator attack/peak shape because oscillator sync is enabled. PRC remains passing on log-mel/envelope/RMS, with score floor lowered to 0.74 after rate scaling changed the aggregate score.",
|
||||
"next": "Use the now-validated algorithm-8 route scale, transpose lowering, and fixed-frequency lowering as fixed inputs while isolating remaining community-voice timbre mismatch, then expand to broader public-domain banks."
|
||||
},
|
||||
"note_and_envelope_model": {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,22 @@ Doctrine update:
|
|||
|
||||
Completed this slice:
|
||||
|
||||
- Addressed the remaining `ANLGSYN 1` beat as DX7 pitch-LFO pressure, not
|
||||
amplitude-LFO pressure. Raw `analog1.syx` bytes show `AMD=71` but every
|
||||
operator has `AMS=0`; the active path is `PMD=32`, `PMS=1`, sine LFO speed
|
||||
`38`, and delay `33`.
|
||||
- Added first-class operator-graph vibrato syntax:
|
||||
`opgraph ... vibrato=<depth> vibrato_hz=<hz> vibrato_delay=<seconds>`.
|
||||
Faust lowering modulates the graph frequency with a faded LFO onset instead
|
||||
of a hard delay gate.
|
||||
- Lowered DX7 pitch LFO for the public-domain probe through that graph vibrato
|
||||
surface. Latest `ANLGSYN 1` metrics with the beat present: score `.697885`,
|
||||
log-mel `.19496867`, envelope `.15124573`, zero-crossing `.9430519`.
|
||||
- Tested and cut the earlier operator-tremolo direction for this target. It was
|
||||
the wrong authority because ANLGSYN's operator `AMS` values are all zero.
|
||||
|
||||
Previous slice:
|
||||
|
||||
- Reframed the latest ear report as envelope pressure, not harmonic pressure:
|
||||
`{ Mooger }` needs a more aggressive attack contour; `MELLOWSOLO` needed a
|
||||
smoother captured tail; `RES SYNTH1` still has a phase-like attack-modulation
|
||||
|
|
|
|||
|
|
@ -399,6 +399,13 @@ public sealed class Dx7ReferenceParityTests
|
|||
var effectiveMidiNote = Math.Clamp(60 + voice.Transpose - 24, 0, 127);
|
||||
builder.AppendLine($" freq={F(graphFrequency)}");
|
||||
builder.AppendLine($" gain={F(graphGain)}");
|
||||
var pitchLfoDepth = Dx7SysEx.PitchLfoDepth(voice.Lfo);
|
||||
if (pitchLfoDepth > 0)
|
||||
{
|
||||
builder.AppendLine($" vibrato={F(pitchLfoDepth)}");
|
||||
builder.AppendLine($" vibrato_hz={F(Dx7SysEx.LfoFrequencyHz(voice.Lfo))}");
|
||||
builder.AppendLine($" vibrato_delay={F(Dx7SysEx.LfoDelaySeconds(voice.Lfo))}");
|
||||
}
|
||||
builder.AppendLine();
|
||||
|
||||
foreach (var op in voice.Operators.OrderByDescending(op => op.Number))
|
||||
|
|
|
|||
|
|
@ -419,6 +419,21 @@ public sealed class Dx7SysExTests
|
|||
Assert.True(approximation.Envelope.Level3 >= 0.9f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapsPitchLfoIntoGraphVibrato()
|
||||
{
|
||||
var data = InitVoice("ANLFO", algorithm: 2, feedback: 7);
|
||||
data[137] = 38;
|
||||
data[138] = 33;
|
||||
data[139] = 32;
|
||||
data[143] = 1;
|
||||
var voice = Dx7SysEx.ParseVoice(data);
|
||||
|
||||
Assert.InRange(Dx7SysEx.LfoFrequencyHz(voice.Lfo), 5.9f, 6.0f);
|
||||
Assert.InRange(Dx7SysEx.LfoDelaySeconds(voice.Lfo), .23f, .24f);
|
||||
Assert.InRange(Dx7SysEx.PitchLfoDepth(voice.Lfo), .0036f, .0037f);
|
||||
}
|
||||
|
||||
private static byte[] InitVoice(string name, int algorithm, int feedback)
|
||||
{
|
||||
var data = new byte[Dx7SysEx.VoiceEditBufferLength];
|
||||
|
|
|
|||
|
|
@ -305,12 +305,13 @@ public sealed class PatchScriptTests
|
|||
public void ReadableOperatorGraphSyntaxParsesRoutesCarriersAndEnvelopes()
|
||||
{
|
||||
var patch = PatchScript.Parse("""
|
||||
opgraph name=pair freq=220 gain=.2
|
||||
opgraph name=pair freq=220 gain=.2 vibrato=.004 vibrato_hz=6 vibrato_delay=.2
|
||||
operator name=op2 ratio=2 level=.8 env=ad:.01:.2
|
||||
operator name=op1 ratio=1 level=1 env=adsr:.02:.1:.65:.3
|
||||
route from=op2 to=op1 index=1.4
|
||||
carrier name=op1
|
||||
""");
|
||||
var export = FaustEmitter.Emit(patch);
|
||||
|
||||
var graph = Assert.Single(patch.OperatorGraphs);
|
||||
var op2 = graph.Operators.Single(op => op.Id == 2);
|
||||
|
|
@ -319,6 +320,9 @@ public sealed class PatchScriptTests
|
|||
Assert.Equal("pair", graph.Name);
|
||||
Assert.Equal([1], graph.Carriers);
|
||||
Assert.Single(graph.Edges);
|
||||
Assert.Equal(.004f, graph.VibratoDepth, 5);
|
||||
Assert.Equal(6, graph.VibratoHz);
|
||||
Assert.Equal(.2f, graph.VibratoDelaySeconds, 5);
|
||||
Assert.Equal(.01f, op2.Envelope.AttackSeconds, 5);
|
||||
Assert.Equal(.2f, op2.Envelope.DecaySeconds, 5);
|
||||
Assert.Equal(.02f, op1.Envelope.AttackSeconds, 5);
|
||||
|
|
@ -326,6 +330,8 @@ public sealed class PatchScriptTests
|
|||
Assert.Equal(.65f, op1.Envelope.SustainLevel, 5);
|
||||
Assert.Equal(.3f, op1.Envelope.ReleaseSeconds, 5);
|
||||
Assert.Equal(.21f, op2.Note.GateSeconds, 5);
|
||||
Assert.Contains("lfo_sin(6, 0.0)", export.Source);
|
||||
Assert.Contains("clip01(age / max(0.0001, 0.2))", export.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue