Add production sugar for music agents
This commit is contained in:
parent
89e6ee10bf
commit
e13fdbc21b
4 changed files with 308 additions and 2 deletions
|
|
@ -18,8 +18,13 @@ AquaSynth technique instead of copying weak prior patches.
|
|||
- Treat every sound as a role owner. Leads, bass, drums, pads, and texture need
|
||||
separate control surfaces so the next agent can adjust them without surgery.
|
||||
- Use reference analysis as constraints: tempo/autocorrelation for gates,
|
||||
register/root/scale for pitch lanes, band stats for filtering, and RMS/motion
|
||||
coverage for arrangement density.
|
||||
register/root/scale for pitch lanes, and log-mel spectrogram structure for
|
||||
filter/register/lane balance. RMS and motion are warning lights, not goals to
|
||||
golf.
|
||||
- Between patch attempts, compare target and candidate log-mel images/features
|
||||
directly: where is energy missing, smeared, too bright, too static, or
|
||||
entering at the wrong time? Change the owner lane that explains that visual
|
||||
mismatch before touching global gain.
|
||||
- Write the simplest patch that makes ownership inspectable. Do not hide a song
|
||||
in one giant voice or one full-duration noise bed.
|
||||
|
||||
|
|
@ -39,6 +44,21 @@ The sugar lowers into visible `param` and `curve` owners. Bind those paths from
|
|||
voices with `gain=@/seq/kick`, `freq=@/seq/lead/freq`, or
|
||||
`gain=@/mix/pad/gain`.
|
||||
|
||||
Reusable production abstractions:
|
||||
|
||||
```aqua
|
||||
sub_kick_grid name=kit kick=x...x..x snare=....x... hat=x.x.xx.x step=.125
|
||||
dust_hat pattern=x..x.x.. step=.125 gain=.045 sustain=30
|
||||
bass_response name=bass root=110 progression=0,5,3,4 pattern=x..x.x.. scene=0:.16,8:.24,16:.18
|
||||
additive_wash name=pad root=@/chords/bass_prog/root partials=1:.12,2:.07,3:.04 scene=0:.05,8:.14,16:.08
|
||||
section_rise name=lead start=0 peak=12 end=24 low=.04 high=.18
|
||||
```
|
||||
|
||||
These are not hidden instruments. They lower into visible `sequence`, `texture`,
|
||||
`chords`, `mix`, `voice`, `layer`, and `harmonics` owners. Use them when the
|
||||
role is actually present in the reference; rewrite their patterns and scene
|
||||
points from the target's log-mel/onset evidence.
|
||||
|
||||
Voice-like leads:
|
||||
|
||||
```aqua
|
||||
|
|
@ -67,6 +87,11 @@ filtered noise only for click, skin, dust, or air, and gates from target tempo.
|
|||
Noise alone is a placeholder unless it has band limits, a gate, and a musical
|
||||
job.
|
||||
|
||||
`sub_kick_grid` is the reusable drum shorthand. It owns the subtractive drum
|
||||
family: kick body, kick skin, snare body, snare skin, and hat tick, plus
|
||||
tempo-derived lane gates. Keep the owner names but rewrite `kick`, `snare`,
|
||||
`hat`, and `step` per target; copied lane strings are template pressure.
|
||||
|
||||
Additive/PAD beds:
|
||||
|
||||
```aqua
|
||||
|
|
@ -79,6 +104,11 @@ Pads own harmonic field and emotional bed. Use harmonic banks and slow
|
|||
brightness/gain motion. Do not let a pad occupy every band for the full target
|
||||
unless the reference really does.
|
||||
|
||||
`additive_wash` is the reusable harmonic-bed shorthand. It owns an additive/PAD
|
||||
layer, harmonic partials, and a mix scene. Use it when the log-mel target shows
|
||||
stable harmonic bed energy or section swells; do not use it to wallpaper over a
|
||||
missing bass/drum/lead owner.
|
||||
|
||||
Texture and recording color:
|
||||
|
||||
```aqua
|
||||
|
|
@ -91,6 +121,23 @@ Texture owns band-limited color, not arrangement. It should support a scene with
|
|||
motion, gates, or slow modulation. A static broadband noise voice is failure
|
||||
pressure.
|
||||
|
||||
`dust_hat` is the reusable high-band texture shorthand. It owns gated spectral
|
||||
dust, not percussion by itself. If the target spectrogram shows high-band ticks,
|
||||
use `dust_hat`; if it shows drum body, use `sub_kick_grid` first.
|
||||
|
||||
Bass and section motion:
|
||||
|
||||
```aqua
|
||||
bass_response name=bass root=110 progression=0,5,3,4 pattern=x..x.x.. scene=0:.14,8:.24,16:.16
|
||||
section_rise name=lead start=0 peak=16 end=30 low=.04 high=.2
|
||||
```
|
||||
|
||||
`bass_response` owns low-mid call/answer motion: a bass gate, chord root lane,
|
||||
mix scene, driven bass body, and short pulse layer. Use it when log-mel bands
|
||||
show low/mid movement between downbeats. `section_rise` owns one lane's
|
||||
macro-energy contour; use it to place entrances, lifts, drops, and endings
|
||||
where the target spectrogram actually changes.
|
||||
|
||||
## Production Checklist
|
||||
|
||||
- Producer brief: target feel, energy contour, meter, tonal center, section map,
|
||||
|
|
@ -98,6 +145,9 @@ pressure.
|
|||
- First attempt: prove parsing, duration coverage, and audible lane ownership.
|
||||
- Later attempts: change one hypothesis at a time: timing, register, role
|
||||
assignment, filter band, envelope, or section/mix motion.
|
||||
- Before chasing a scalar metric, inspect target-vs-candidate log-mel evidence.
|
||||
Ask which visible region is wrong, which owner controls that region, and what
|
||||
single owner edit would test the hypothesis.
|
||||
- Listening journal: write what sounded alive, fake, static, missing, or too
|
||||
generic after every render.
|
||||
- Gap ledger: name missing AquaSynth primitives or sugar only when a concrete
|
||||
|
|
@ -107,6 +157,8 @@ pressure.
|
|||
|
||||
- Naming `kick`, `hat`, `pad`, or `syrinx` without audible role behavior.
|
||||
- Copying one stock drum grid across unrelated targets.
|
||||
- Golfing RMS or another scalar while the spectrogram says the wrong lane,
|
||||
register, or section is being edited.
|
||||
- Letting texture keep the tail non-silent while composition dies.
|
||||
- Hand-writing isolated frequencies when the reference suggests a tonal center
|
||||
or progression.
|
||||
|
|
|
|||
|
|
@ -349,6 +349,26 @@ public static class PatchScript
|
|||
FlushPendingOperatorGraph();
|
||||
AddMixAutomation(fields, line);
|
||||
break;
|
||||
case "drumkit":
|
||||
FlushPendingOperatorGraph();
|
||||
AddSubKickGrid(fields, line);
|
||||
break;
|
||||
case "dust_hat":
|
||||
FlushPendingOperatorGraph();
|
||||
AddDustHat(fields, line);
|
||||
break;
|
||||
case "bass_response":
|
||||
FlushPendingOperatorGraph();
|
||||
AddBassResponse(fields, line);
|
||||
break;
|
||||
case "additive_wash":
|
||||
FlushPendingOperatorGraph();
|
||||
AddAdditiveWash(fields, line);
|
||||
break;
|
||||
case "section_rise":
|
||||
FlushPendingOperatorGraph();
|
||||
AddSectionRise(fields, line);
|
||||
break;
|
||||
case "control_spline":
|
||||
FlushPendingOperatorGraph();
|
||||
AddControlSpline(fields, line);
|
||||
|
|
@ -2781,6 +2801,205 @@ public static class PatchScript
|
|||
("drive", GetAny(fields, ["drive"], role.Drive.ToString(CultureInfo.InvariantCulture)))), line), VoicePath(Voices.Count), line));
|
||||
}
|
||||
|
||||
private void AddSubKickGrid(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var name = SafeName(GetAny(fields, ["name", "n"], "drums"));
|
||||
var stepSeconds = GetMusicalStepSeconds(fields, line, BeatSeconds / 4, "step", "step_seconds", "dt");
|
||||
var kickPath = GetAny(fields, ["kick_path"], $"/seq/{name}/kick");
|
||||
var snarePath = GetAny(fields, ["snare_path"], $"/seq/{name}/snare");
|
||||
var hatPath = GetAny(fields, ["hat_path"], $"/seq/{name}/hat");
|
||||
var kickPattern = GetAny(fields, ["kick", "kick_pattern"], "x...x...x...x...");
|
||||
var snarePattern = GetAny(fields, ["snare", "snare_pattern"], "....x.......x...");
|
||||
var hatPattern = GetAny(fields, ["hat", "hat_pattern"], "x.x.x.x.x.x.x.x.");
|
||||
|
||||
AddPatternCurve(SyntheticFields(
|
||||
("name", $"{name}_kick"),
|
||||
("path", kickPath),
|
||||
("pattern", kickPattern),
|
||||
("step", F(stepSeconds)),
|
||||
("high", GetAny(fields, ["kick_gain", "kg"], "1")),
|
||||
("low", "0"),
|
||||
("loop", GetAny(fields, ["loop"], "true"))), line);
|
||||
AddPatternCurve(SyntheticFields(
|
||||
("name", $"{name}_snare"),
|
||||
("path", snarePath),
|
||||
("pattern", snarePattern),
|
||||
("step", F(stepSeconds)),
|
||||
("high", GetAny(fields, ["snare_gain", "sg"], ".8")),
|
||||
("low", "0"),
|
||||
("loop", GetAny(fields, ["loop"], "true"))), line);
|
||||
AddPatternCurve(SyntheticFields(
|
||||
("name", $"{name}_hat"),
|
||||
("path", hatPath),
|
||||
("pattern", hatPattern),
|
||||
("step", F(stepSeconds)),
|
||||
("high", GetAny(fields, ["hat_gain", "hg"], ".18")),
|
||||
("low", GetAny(fields, ["hat_rest", "hr"], ".02")),
|
||||
("loop", GetAny(fields, ["loop"], "true"))), line);
|
||||
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(SyntheticFields(
|
||||
("name", $"{name}_kick_body"),
|
||||
("wave", "sine"),
|
||||
("freq", GetAny(fields, ["kick_freq", "kf"], "54")),
|
||||
("gain", "@" + kickPath),
|
||||
("env", "ad"),
|
||||
("attack", GetAny(fields, ["kick_attack"], ".002")),
|
||||
("decay", GetAny(fields, ["kick_decay"], ".14")),
|
||||
("lpf", GetAny(fields, ["kick_lpf"], ".74")),
|
||||
("pitch_ramp", GetAny(fields, ["kick_pitch_ramp"], "-2.2"))), line), VoicePath(Voices.Count), line));
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(SyntheticFields(
|
||||
("name", $"{name}_kick_skin"),
|
||||
("wave", "noise"),
|
||||
("freq", GetAny(fields, ["kick_skin_freq"], "2600")),
|
||||
("gain", "@" + kickPath),
|
||||
("env", "ad"),
|
||||
("attack", GetAny(fields, ["skin_attack"], ".001")),
|
||||
("decay", GetAny(fields, ["kick_skin_decay"], ".035")),
|
||||
("hpf", GetAny(fields, ["kick_skin_hpf"], ".55")),
|
||||
("lpf", GetAny(fields, ["kick_skin_lpf"], ".35"))), line), VoicePath(Voices.Count), line));
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(SyntheticFields(
|
||||
("name", $"{name}_snare_body"),
|
||||
("wave", "triangle"),
|
||||
("freq", GetAny(fields, ["snare_freq", "sf"], "185")),
|
||||
("gain", "@" + snarePath),
|
||||
("env", "ad"),
|
||||
("attack", GetAny(fields, ["snare_attack"], ".001")),
|
||||
("decay", GetAny(fields, ["snare_decay"], ".11")),
|
||||
("lpf", GetAny(fields, ["snare_lpf"], ".55"))), line), VoicePath(Voices.Count), line));
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(SyntheticFields(
|
||||
("name", $"{name}_snare_skin"),
|
||||
("wave", "noise"),
|
||||
("freq", GetAny(fields, ["snare_skin_freq"], "3400")),
|
||||
("gain", "@" + snarePath),
|
||||
("env", "ad"),
|
||||
("attack", GetAny(fields, ["skin_attack"], ".001")),
|
||||
("decay", GetAny(fields, ["snare_skin_decay"], ".08")),
|
||||
("hpf", GetAny(fields, ["snare_skin_hpf"], ".62")),
|
||||
("lpf", GetAny(fields, ["snare_skin_lpf"], ".42"))), line), VoicePath(Voices.Count), line));
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(SyntheticFields(
|
||||
("name", $"{name}_hat_tick"),
|
||||
("wave", "noise"),
|
||||
("freq", GetAny(fields, ["hat_freq", "hf"], "9200")),
|
||||
("gain", "@" + hatPath),
|
||||
("env", "ad"),
|
||||
("attack", GetAny(fields, ["hat_attack"], ".001")),
|
||||
("decay", GetAny(fields, ["hat_decay"], ".035")),
|
||||
("hpf", GetAny(fields, ["hat_hpf"], ".88")),
|
||||
("lpf", GetAny(fields, ["hat_lpf"], ".23"))), line), VoicePath(Voices.Count), line));
|
||||
}
|
||||
|
||||
private void AddDustHat(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var merged = SyntheticFields(
|
||||
("name", GetAny(fields, ["name", "n"], "dust_hat")),
|
||||
("role", "dust"),
|
||||
("pattern", RequiredAny(fields, ["pattern", "pat", "steps"], line)),
|
||||
("step", GetAny(fields, ["step", "step_seconds", "dt"], F(BeatSeconds / 4))),
|
||||
("gain", GetAny(fields, ["gain", "g"], ".05")),
|
||||
("sustain", GetAny(fields, ["sustain", "duration", "hold"], "30")));
|
||||
Merge(merged, Without(fields, "name", "n", "role", "kind", "type", "pattern", "pat", "steps", "step", "step_seconds", "dt", "gain", "g", "sustain", "duration", "hold"));
|
||||
AddNoiseTexture(merged, line);
|
||||
}
|
||||
|
||||
private void AddBassResponse(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var name = SafeName(GetAny(fields, ["name", "n"], "bass"));
|
||||
var gatePath = GetAny(fields, ["gate_path"], $"/seq/{name}/gate");
|
||||
var mixPath = GetAny(fields, ["mix_path", "path"], $"/mix/{name}/gain");
|
||||
var chordName = SafeName(GetAny(fields, ["chords", "prog_name"], $"{name}_prog"));
|
||||
var chordRootPath = GetAny(fields, ["root_path", "freq_path"], $"/chords/{chordName}/root");
|
||||
|
||||
AddSequencePattern(SyntheticFields(
|
||||
("name", $"{name}_gate"),
|
||||
("path", gatePath),
|
||||
("pattern", GetAny(fields, ["pattern", "pat", "gate"], "x..x.x...x..xx..")),
|
||||
("step", GetAny(fields, ["step", "step_seconds", "dt"], F(BeatSeconds / 4))),
|
||||
("high", GetAny(fields, ["gate_high", "high"], ".6")),
|
||||
("low", GetAny(fields, ["gate_low", "low"], ".05")),
|
||||
("loop", GetAny(fields, ["loop"], "true"))), line);
|
||||
AddChordProgression(SyntheticFields(
|
||||
("name", chordName),
|
||||
("root", GetAny(fields, ["root", "freq", "base"], "110")),
|
||||
("scale", GetAny(fields, ["scale", "mode"], "minor")),
|
||||
("progression", GetAny(fields, ["progression", "prog"], "0,5,3,4")),
|
||||
("voicing", "0"),
|
||||
("paths", chordRootPath),
|
||||
("step", GetAny(fields, ["chord_step", "bar"], "bar"))), line);
|
||||
AddMixAutomation(SyntheticFields(
|
||||
("name", name),
|
||||
("path", mixPath),
|
||||
("points", GetAny(fields, ["scene", "points", "pts"], "0:.18,4:.25,8:.14,12:.3,16:.18"))), line);
|
||||
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(SyntheticFields(
|
||||
("name", $"{name}_body"),
|
||||
("wave", GetAny(fields, ["wave", "w"], "triangle")),
|
||||
("freq", "@" + chordRootPath),
|
||||
("gain", "@" + mixPath),
|
||||
("attack", GetAny(fields, ["attack", "a"], ".002")),
|
||||
("sustain", GetAny(fields, ["sustain", "s"], ".09")),
|
||||
("decay", GetAny(fields, ["decay", "d"], ".22")),
|
||||
("lpf", GetAny(fields, ["lpf"], ".34")),
|
||||
("fm", GetAny(fields, ["fm"], "1.8")),
|
||||
("fmi", GetAny(fields, ["fmi"], ".22")),
|
||||
("tremolo_hz", GetAny(fields, ["tremolo_hz", "th"], F(1 / Math.Max(BeatSeconds, 0.001f)))),
|
||||
("tremolo", GetAny(fields, ["tremolo", "tr"], ".08"))), line), VoicePath(Voices.Count), line));
|
||||
Voices.Add(ParseVoice(ExpandVoiceFields(SyntheticFields(
|
||||
("name", $"{name}_pulse"),
|
||||
("wave", GetAny(fields, ["pulse_wave"], "sine")),
|
||||
("freq", "@" + chordRootPath),
|
||||
("gain", "@" + gatePath),
|
||||
("env", "ad"),
|
||||
("attack", GetAny(fields, ["pulse_attack"], ".001")),
|
||||
("decay", GetAny(fields, ["pulse_decay"], ".09")),
|
||||
("lpf", GetAny(fields, ["pulse_lpf"], ".4"))), line), VoicePath(Voices.Count), line));
|
||||
}
|
||||
|
||||
private void AddAdditiveWash(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var name = SafeName(GetAny(fields, ["name", "n"], "wash"));
|
||||
var gainPath = GetAny(fields, ["mix_path", "path"], $"/mix/{name}/gain");
|
||||
AddMixAutomation(SyntheticFields(
|
||||
("name", name),
|
||||
("path", gainPath),
|
||||
("points", GetAny(fields, ["scene", "points", "pts"], "0:.08,4:.18,8:.12,12:.24,16:.08"))), line);
|
||||
if (!_layerDefaults.ContainsKey(name))
|
||||
{
|
||||
AddLayer(SyntheticFields(
|
||||
("name", name),
|
||||
("engine", GetAny(fields, ["engine", "e"], "add")),
|
||||
("gain", "@" + gainPath),
|
||||
("attack", GetAny(fields, ["attack", "a"], ".5")),
|
||||
("sustain", GetAny(fields, ["sustain", "s"], "30")),
|
||||
("decay", GetAny(fields, ["decay", "d"], "1.5"))), line);
|
||||
}
|
||||
|
||||
AddHarmonicBank(SyntheticFields(
|
||||
("layer", name),
|
||||
("root", GetAny(fields, ["root", "freq", "base"], "220")),
|
||||
("partials", GetAny(fields, ["partials", "bank"], "1:.12,2:.06,3:.035,5:.018")),
|
||||
("wave", GetAny(fields, ["wave", "w"], "sine")),
|
||||
("attack", GetAny(fields, ["attack", "a"], ".5")),
|
||||
("sustain", GetAny(fields, ["sustain", "s"], "30")),
|
||||
("decay", GetAny(fields, ["decay", "d"], "1.5")),
|
||||
("lpf", GetAny(fields, ["lpf"], ".62"))), line);
|
||||
}
|
||||
|
||||
private void AddSectionRise(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var name = SafeName(RequiredAny(fields, ["name", "n", "lane", "voice", "bus"], line));
|
||||
var start = GetFloat(fields, line, 0, "start", "from");
|
||||
var peak = GetFloat(fields, line, start + BeatSeconds * BeatsPerBar * 2, "peak", "at");
|
||||
var end = GetFloat(fields, line, peak + BeatSeconds * BeatsPerBar * 2, "end", "to");
|
||||
var low = GetFloat(fields, line, .08f, "low", "base");
|
||||
var high = GetFloat(fields, line, .24f, "high", "top");
|
||||
AddMixAutomation(SyntheticFields(
|
||||
("name", name),
|
||||
("path", GetAny(fields, ["path"], $"/mix/{name}/gain")),
|
||||
("points", GetAny(fields, ["points", "pts"], $"{F(start)}:{F(low)},{F(peak)}:{F(high)},{F(end)}:{F(low)}")),
|
||||
("interp", GetAny(fields, ["interp", "interpolation"], "linear")),
|
||||
("loop", GetAny(fields, ["loop"], "false"))), line);
|
||||
}
|
||||
|
||||
private void EnsureParameter(string path, float defaultValue, float min, float max, float step, string unit, int line)
|
||||
{
|
||||
if (_parameters.Any(parameter => parameter.Path.Equals(path, StringComparison.OrdinalIgnoreCase)))
|
||||
|
|
@ -3671,6 +3890,11 @@ public static class PatchScript
|
|||
"sequence" or "voice_seq" or "lane" or "instrument_seq" or "instrument_sequence" => "sequence",
|
||||
"chord" or "chords" or "progression" or "chord_progression" => "chords",
|
||||
"mix" or "mixer" or "mix_bus" or "mixbus" => "mix",
|
||||
"drumkit" or "drum_kit" or "sub_kick_grid" or "sub-kick-grid" => "drumkit",
|
||||
"dust_hat" or "dust-hat" or "spectral_dust_hat" or "spectral-dust-hat" => "dust_hat",
|
||||
"bass_response" or "bass-response" or "call_response_bass" or "call-response-bass" => "bass_response",
|
||||
"additive_wash" or "additive-wash" or "additive_wash_bed" or "additive-wash-bed" or "wash_bed" => "additive_wash",
|
||||
"section_rise" or "section-rise" or "mix_scene" or "mix-scene" => "section_rise",
|
||||
"control_spline" or "spline" or "gesture_spline" or "gesturespline" => "control_spline",
|
||||
"phoneme_gesture" or "phone_gesture" or "ipa_gesture" or "phoneme" or "phone" => "phoneme_gesture",
|
||||
"gesture" or "gesture_group" or "gesturegroup" => "gesture",
|
||||
|
|
|
|||
|
|
@ -273,3 +273,4 @@
|
|||
{"timestamp":"2026-06-01T02:33:00+01:00","topic":"learned-only-music-knowledge-show-standard","claim":"A cumulative music-production knowledge loop should not fail when `music-distill --learned-only true` omits the seed `music-production-quality-standard-v1` document. The pass-end standard snapshot is display-only: it may show the canonical seed record when present, but if learned-only output only contains current curator/manual/failure/abstraction documents it must write an index-backed pass snapshot and continue the swarm.","evidence":"tools/run-song-snippet-trial-swarm.ps1; artifacts/parity/song-snippet-swarms/20260601T010706781/launcher stderr showed `No music knowledge document found for music-production-quality-standard-v1` after pass-001 despite successful learned-only distillation"}
|
||||
{"timestamp":"2026-06-01T04:23:00+01:00","topic":"five-pass-song-swarm-composition-run","claim":"A 5-pass, 2-agent, 2-task, 30-second song swarm over the Reinier hierarchy completed end-to-end with post-pass curator/distill/index promotion after every pass. The cumulative learned-only music knowledge loop stayed alive through all five passes. Official candidates improved from pass-001 top score 0.432376 to pass-004 top score 0.471762 and pass-005 top score 0.468977, with low mode-collapse risk around 0.07 and motion coverage around 0.638. Remaining pressure is producer discipline and syntax hygiene: official verdicts were mostly `pressure`, with two official render failures and one weak mode-collapse; one failure came from agent-authored newline loss gluing `soft_clip=true` to `meter` as `truemeter`.","evidence":"artifacts/parity/song-snippet-swarms/20260601T012948062; pass-005/top-scoring-candidates.md; pass-004/top-scoring-candidates.md; pass-*/knowledge-curator/knowledge-curation.md; pass-*/music-production-knowledge-report.md"}
|
||||
{"timestamp":"2026-06-01T04:40:00+01:00","topic":"music-agent-orchestration-generalization","claim":"The durable lesson from song-agent and orchestration work is now summarized as a report: agents learn only when role ownership, audible continuity, render failures, studio reasoning, curation, storage, and retrieval are all explicit evidence paths. The mistakes to stop repeating are syntax attendance as musicianship, texture as arrangement, metrics as ears, weak winners as doctrine, invisible targets, non-retrievable memory, hidden parser/render faults, monolithic trial stores, and proposed sugar masquerading as live syntax.","evidence":"docs/music-agent-orchestration-lessons.md; state/map.yaml"}
|
||||
{"timestamp":"2026-06-01T04:43:51+01:00","topic":"log-mel-steered-production-sugar","claim":"Music agents should steer patch revisions by directly comparing target and candidate log-mel spectrogram/features between attempts, then editing the lane owner that explains the visible mismatch. RMS, cosine, and motion metrics are warning lights, not optimization targets. Aqua DSL now includes reusable production sugar for subtractive drum grids, gated dust hats, bass response lanes, additive wash beds, and section-rise mix automation, and the Aqua DSP manual teaches those abstractions as inspectable owners rather than hidden instruments.","evidence":"src/AquaSynth.Core/PatchScript.cs; tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs; docs/music-production-knowledge/aqua-dsp-usage-manual.md"}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,35 @@ public sealed class PatchScriptTests
|
|||
Assert.Contains("/textures/dust_hat/gate", faust);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParserSupportsProductionAbstractionSugar()
|
||||
{
|
||||
var patch = PatchScript.Parse("""
|
||||
meter bpm=140 beats=4
|
||||
sub_kick_grid name=kit kick=x...x..x snare=....x... hat=x.x.xx.x step=.107 kick_freq=52
|
||||
dust_hat pattern=x..x.x.. step=.107 gain=.045 sustain=30
|
||||
bass_response name=bass root=110 progression=0,5,3,4 pattern=x..x.x.. scene=0:.16,8:.24,16:.18
|
||||
additive_wash name=pad root=@/chords/bass_prog/root partials=1:.12,2:.07,3:.04 scene=0:.05,8:.14,16:.08
|
||||
section_rise name=lead start=0 peak=12 end=24 low=.04 high=.18
|
||||
voice name=lead wave=saw freq=330 gain=@/mix/lead/gain sustain=.12 decay=.18
|
||||
""");
|
||||
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/seq/kit/kick");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/seq/kit/snare");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/seq/kit/hat");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/textures/dust_hat/gate");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/seq/bass/gate");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/mix/bass/gain");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/mix/pad/gain");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/mix/lead/gain");
|
||||
Assert.Contains(patch.ControlCurves, curve => curve.Name == "kit_kick" && curve.Interpolation == ControlCurveInterpolation.Hold);
|
||||
Assert.Contains(patch.ControlCurves, curve => curve.Name == "bass_mix");
|
||||
Assert.Contains(patch.ControlCurves, curve => curve.Name == "lead_mix");
|
||||
Assert.Contains(patch.Layers, layer => layer.Name == "pad" && layer.Engine == "add");
|
||||
Assert.Contains(patch.HarmonicBanks, bank => bank.LayerName == "pad");
|
||||
Assert.True(patch.Voices.Count >= 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParserSupportsVoiceVowelFrames()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue