Gate song swarm on self-iteration evidence
This commit is contained in:
parent
e13fdbc21b
commit
8250afc39a
7 changed files with 348 additions and 17 deletions
|
|
@ -34,6 +34,7 @@ Composition sugar:
|
|||
|
||||
```aqua
|
||||
meter bpm=128 beats=4
|
||||
song_form name=club unit=bar sections=intro:0-4:.35,build:4-8:.85,drop:8-16:1,break:16-20:.5,build2:20-24:.85,final_drop:24-32:1 energy=0:.2,4:.55,8:1,16:.4,24:1,32:.2
|
||||
sequence name=kick pattern=x...x...x...x... step=.125 high=.9 low=0
|
||||
scale name=hook path=/seq/lead/freq root=220 scale=minor degrees=0,2,3,5,3,2 step=.25
|
||||
chords name=pad root=220 scale=minor progression=0,5,3,4 voicing=0,2,4 paths=/chords/pad/root,/chords/pad/third,/chords/pad/fifth step=bar
|
||||
|
|
@ -44,6 +45,12 @@ 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`.
|
||||
|
||||
`song_form` owns section gates and whole-track energy. Use it when a patch needs
|
||||
intro/build/drop/break/final-drop structure instead of hand-written bar gates.
|
||||
Each section becomes a bindable path such as `/form/club/drop`, and the optional
|
||||
energy contour becomes `/form/club/energy`. It does not replace lane-specific
|
||||
mixing; it gives those lanes a real song-form spine to bind against.
|
||||
|
||||
Reusable production abstractions:
|
||||
|
||||
```aqua
|
||||
|
|
|
|||
98
patches/advanced/electro-drop-demo.aqua
Normal file
98
patches/advanced/electro-drop-demo.aqua
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# electro-drop-demo
|
||||
# Family: advanced
|
||||
# Source: AquaSynth song-swarm synthesis demo
|
||||
# Notes: Electro/complextro demo using song-curriculum sugar, layered bass chops, build tension, and a drop.
|
||||
|
||||
patch
|
||||
gain=0.68
|
||||
duration=64
|
||||
soft_clip=true
|
||||
|
||||
meter bpm=128 beats=4
|
||||
|
||||
# Section authority: intro -> build -> drop -> break -> build2 -> final_drop.
|
||||
song_form name=electro unit=bar
|
||||
sections=intro:0-4:.35,build:4-8:.85,drop:8-16:1,break:16-20:.55,build2:20-24:.85,final_drop:24-32:1
|
||||
energy=0:.22,4:.55,7.5:.92,8:1,16:.42,20:.62,23.5:.95,24:1,32:.18
|
||||
|
||||
sub_kick_grid name=main
|
||||
kick=x...x...x...x...
|
||||
snare=....x.......x...
|
||||
hat=x.x.x.xx.x.x.xxx.
|
||||
step=.117187
|
||||
kick_freq=50
|
||||
kick_decay=.11
|
||||
kick_lpf=.52
|
||||
kick_pitch_ramp=-4.2
|
||||
kick_skin_freq=4200
|
||||
kick_skin_decay=.018
|
||||
kick_skin_hpf=.78
|
||||
kick_skin_lpf=.22
|
||||
snare_freq=205
|
||||
snare_decay=.08
|
||||
snare_skin_freq=6200
|
||||
snare_skin_decay=.045
|
||||
snare_skin_hpf=.80
|
||||
snare_skin_lpf=.20
|
||||
hat_gain=.08
|
||||
hat_freq=11200
|
||||
hat_decay=.018
|
||||
hat_hpf=.92
|
||||
hat_lpf=.13
|
||||
|
||||
dust_hat pattern=x..x.x..x..x.x.. step=.117187 gain=.012 sustain=64
|
||||
|
||||
bass_response name=sub
|
||||
root=55
|
||||
progression=0,5,3,4
|
||||
pattern=x..x.x...x..xx..
|
||||
step=.117187
|
||||
scene=0:.04,8:.10,16:.18,24:.14,32:.03,40:.14,56:.20,64:.08
|
||||
wave=triangle
|
||||
lpf=.30
|
||||
fm=1.9
|
||||
fmi=.24
|
||||
|
||||
chords name=drop_prog root=110 scale=minor progression=0,5,3,4 voicing=0,2,4 paths=/chords/drop/root,/chords/drop/third,/chords/drop/fifth step=bar
|
||||
scale name=hook path=/seq/hook/freq root=440 scale=minor degrees=0,2,3,5,7,5,3,2,0,-2,0,3 step=.234375
|
||||
|
||||
sequence name=hook_gate pattern=x.x.xx..x..x.xx. step=.117187 high=.32 low=0
|
||||
sequence name=bass_a pattern=x...x..x....x.x. step=.117187 high=.36 low=0
|
||||
sequence name=bass_b pattern=..x...x.xx...x.. step=.117187 high=.28 low=0
|
||||
sequence name=bass_c pattern=...x.xx...x..x.. step=.117187 high=.22 low=0
|
||||
sequence name=stab_gate pattern=x...x...xx..x... step=.117187 high=.34 low=0
|
||||
sequence name=fill_gate pattern=............xxxx step=.117187 high=.24 low=0
|
||||
|
||||
mix name=lead points=0:.02,8:.05,14:.16,16:.24,24:.16,32:.04,40:.15,48:.26,56:.32,64:.08
|
||||
mix name=pad points=0:.025,8:.06,15:.12,16:.025,28:.03,32:.10,40:.035,48:.07,64:.025
|
||||
mix name=air points=0:.006,8:.014,15:.04,16:.004,32:.025,40:.01,47:.05,48:.006,64:.004
|
||||
section_rise name=riser start=8 peak=15.5 end=16 low=0 high=.25
|
||||
section_rise name=second_riser start=40 peak=47.5 end=48 low=0 high=.31
|
||||
|
||||
additive_wash name=wash root=@/chords/drop/root partials=1:.06,2:.025,3:.014,5:.008 scene=0:.02,8:.06,15:.10,16:.02,32:.08,40:.035,48:.08,64:.02 lpf=.54
|
||||
layer name=halo engine=pad gain=@/mix/pad/gain env=rl rates=.55,.85,1.25,1.8 levels=.9,.95,.78,0 gate=4
|
||||
spectrum layer=halo root=110 freq=220 spread=.009 pad_mode=bandwidth pad_bandwidth=560 pad_bwscale=2 partials=1:.07,1.5:.03,2:.022,3:.012
|
||||
|
||||
texture name=air_wash role=air gain=@/mix/air/gain sustain=64
|
||||
texture name=codec_spray role=codec pattern=..x...x...x..x.. step=.117187 gain=.006 sustain=64
|
||||
|
||||
# Explicit transient reinforcements. The sugar owns the kit; these make it read.
|
||||
voice name=kick_sub_punch wave=sine freq=46 gain=@/seq/main/kick env=ad attack=.001 decay=.075 sustain=64 pitch_ramp=-5.8 lpf=.28 drive=.05
|
||||
voice name=snare_crack wave=noise freq=7200 gain=@/seq/main/snare env=ad attack=.001 decay=.032 sustain=64 hpf=.88 lpf=.16 bpf=.72 bpf_q=3.5 drive=.09
|
||||
voice name=hat_needle wave=noise freq=12500 gain=@/seq/main/hat env=ad attack=.001 decay=.012 sustain=64 hpf=.94 lpf=.10 bpf=.80 bpf_q=4.5
|
||||
|
||||
voice name=hook_front wave=saw freq=@/seq/hook/freq gain=@/mix/lead/gain sustain=64 decay=.22 lpf=.58 bpf=.42 bpf_q=2.1 fm=2.8 fmi=.22 drive=.12
|
||||
voice name=hook_edge wave=square freq=@/seq/hook/freq gain=@/seq/hook_gate sustain=64 decay=.09 hpf=.18 lpf=.68 duty=.38 fm=4.2 fmi=.18 fold=.05
|
||||
|
||||
# Complextro cut-ups: separate timbres take turns instead of one polite bass patch.
|
||||
voice name=bass_chop_a wave=saw freq=@/chords/drop/root gain=@/seq/bass_a sustain=64 decay=.10 lpf=.24 fm=3.2 fmi=.65 drive=.22 fold=.08
|
||||
voice name=bass_chop_b wave=square freq=@/chords/drop/fifth gain=@/seq/bass_b sustain=64 decay=.08 lpf=.32 hpf=.05 fm=5.4 fmi=.46 duty=.31 drive=.18
|
||||
voice name=bass_chop_c wave=triangle freq=@/chords/drop/third gain=@/seq/bass_c sustain=64 decay=.12 bpf=.48 bpf_q=2.8 fm=1.6 fmi=.72 drive=.14
|
||||
voice name=stab wave=saw freq=@/chords/drop/fifth gain=@/seq/stab_gate attack=.001 sustain=.05 decay=.16 lpf=.62 hpf=.08 fm=2.2 fmi=.24 drive=.2
|
||||
voice name=turnaround_fill wave=square freq=@/seq/hook/freq gain=@/seq/fill_gate attack=.001 sustain=.035 decay=.09 hpf=.3 lpf=.72 duty=.42 fm=7.2 fmi=.38
|
||||
|
||||
voice name=drop_weight wave=sine freq=55 gain=@/form/electro/drop attack=.001 sustain=.12 decay=.28 pitch_ramp=-.8 lpf=.34 drive=.08
|
||||
voice name=final_drop_weight wave=triangle freq=55 gain=@/form/electro/final_drop attack=.001 sustain=.16 decay=.34 lpf=.38 fm=1.1 fmi=.18 drive=.12
|
||||
voice name=build_riser wave=saw freq=220 gain=@/mix/riser/gain attack=.01 sustain=64 decay=.1 pitch_ramp=2.4 lpf=.72 hpf=.24 fm=1.2 fmi=.18
|
||||
voice name=second_build_riser wave=saw freq=165 gain=@/mix/second_riser/gain attack=.01 sustain=64 decay=.1 pitch_ramp=2.9 lpf=.78 hpf=.22 fm=1.6 fmi=.22
|
||||
voice name=break_pluck wave=triangle freq=@/chords/drop/third gain=@/form/electro/break attack=.004 sustain=.09 decay=.34 lpf=.44 hpf=.02 fm=.8 fmi=.12
|
||||
|
|
@ -37,6 +37,11 @@ entries:
|
|||
name: "aurora-pad"
|
||||
source: "BuiltInScripts.ReferenceScripts"
|
||||
notes: "Stock reference patch exported from the in-code catalog."
|
||||
- path: patches/advanced/electro-drop-demo.aqua
|
||||
family: advanced
|
||||
name: "electro-drop-demo"
|
||||
source: "AquaSynth song-swarm synthesis demo"
|
||||
notes: "Electro/complextro demo with intro/build/drop section gates, layered bass chops, additive wash, shaped texture, and mix automation."
|
||||
- path: patches/advanced/additive-pad.aqua
|
||||
family: advanced
|
||||
name: "additive-pad"
|
||||
|
|
|
|||
|
|
@ -349,6 +349,10 @@ public static class PatchScript
|
|||
FlushPendingOperatorGraph();
|
||||
AddMixAutomation(fields, line);
|
||||
break;
|
||||
case "song_form":
|
||||
FlushPendingOperatorGraph();
|
||||
AddSongForm(fields, line);
|
||||
break;
|
||||
case "drumkit":
|
||||
FlushPendingOperatorGraph();
|
||||
AddSubKickGrid(fields, line);
|
||||
|
|
@ -2748,6 +2752,67 @@ public static class PatchScript
|
|||
("mode", GetAny(fields, ["mode"], "blend"))), line);
|
||||
}
|
||||
|
||||
private void AddSongForm(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var name = SafeName(GetAny(fields, ["name", "n"], "song"));
|
||||
var unit = GetAny(fields, ["unit", "time"], "seconds");
|
||||
var unitScale = unit.Equals("bar", StringComparison.OrdinalIgnoreCase) ||
|
||||
unit.Equals("bars", StringComparison.OrdinalIgnoreCase) ||
|
||||
unit.Equals("measure", StringComparison.OrdinalIgnoreCase) ||
|
||||
unit.Equals("measures", StringComparison.OrdinalIgnoreCase)
|
||||
? BeatSeconds * BeatsPerBar
|
||||
: 1f;
|
||||
var sections = ParseSongSections(RequiredAny(fields, ["sections", "form", "arrangement"], line), unitScale, line);
|
||||
if (sections.Count == 0)
|
||||
{
|
||||
throw new PatchScriptException(line, "song_form needs at least one section");
|
||||
}
|
||||
|
||||
var lengthSeconds = GetFloat(fields, line, sections.Max(section => section.EndSeconds), "duration", "length", "end");
|
||||
foreach (var section in sections)
|
||||
{
|
||||
if (section.EndSeconds <= section.StartSeconds)
|
||||
{
|
||||
throw new PatchScriptException(line, $"song_form section `{section.Name}` must end after it starts");
|
||||
}
|
||||
|
||||
var path = $"/form/{name}/{section.Name}";
|
||||
EnsureParameter(path, section.StartSeconds <= 0 ? section.High : section.Low, 0, Math.Max(1, section.High), 0.001f, "", line);
|
||||
var sectionPoints = new List<(float Time, float Value)>
|
||||
{
|
||||
(0f, section.StartSeconds <= 0 ? section.High : section.Low)
|
||||
};
|
||||
AddUniquePoint(sectionPoints, Math.Max(0, section.StartSeconds), section.High);
|
||||
AddUniquePoint(sectionPoints, section.EndSeconds, section.Low);
|
||||
AddUniquePoint(sectionPoints, Math.Max(section.EndSeconds, lengthSeconds), section.Low);
|
||||
var points = PointsText(sectionPoints);
|
||||
AddControlCurve(SyntheticFields(
|
||||
("name", $"{name}_{section.Name}"),
|
||||
("path", path),
|
||||
("points", points),
|
||||
("interp", "hold"),
|
||||
("loop", GetAny(fields, ["loop"], "false")),
|
||||
("mode", GetAny(fields, ["mode"], "blend"))), line);
|
||||
}
|
||||
|
||||
if (TryGetAny(fields, ["energy", "energy_points", "contour"], out var energyText))
|
||||
{
|
||||
var path = GetAny(fields, ["energy_path"], $"/form/{name}/energy");
|
||||
var points = unitScale == 1f
|
||||
? energyText
|
||||
: PointsText(ParseControlCurvePoints(energyText, line).Select(point => (point.TimeSeconds * unitScale, point.Value)));
|
||||
var values = ParseControlCurvePoints(points, line).Select(point => point.Value).ToArray();
|
||||
EnsureParameter(path, values.Length == 0 ? 0 : values[0], 0, Math.Max(1, values.DefaultIfEmpty(1).Max() * 1.25f), 0.001f, "", line);
|
||||
AddControlCurve(SyntheticFields(
|
||||
("name", $"{name}_energy"),
|
||||
("path", path),
|
||||
("points", points),
|
||||
("interp", GetAny(fields, ["energy_interp", "interp"], "linear")),
|
||||
("loop", GetAny(fields, ["loop"], "false")),
|
||||
("mode", GetAny(fields, ["mode"], "blend"))), line);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddNoiseTexture(IReadOnlyDictionary<string, string> fields, int line)
|
||||
{
|
||||
var name = SafeName(GetAny(fields, ["name", "n"], $"texture_{Voices.Count}"));
|
||||
|
|
@ -3755,6 +3820,45 @@ public static class PatchScript
|
|||
return ParseFloat(value, line);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<SongSectionSpec> ParseSongSections(string text, float unitScale, int line)
|
||||
{
|
||||
var sections = new List<SongSectionSpec>();
|
||||
foreach (var rawToken in text.Split([',', '|'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
{
|
||||
var token = rawToken.Replace("..", "-", StringComparison.Ordinal);
|
||||
var parts = token.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length < 2 || parts.Length > 4)
|
||||
{
|
||||
throw new PatchScriptException(line, $"song_form section `{rawToken}` must be name:start-end[:high[:low]]");
|
||||
}
|
||||
|
||||
var range = parts[1].Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (range.Length != 2)
|
||||
{
|
||||
throw new PatchScriptException(line, $"song_form section `{rawToken}` must use a start-end range");
|
||||
}
|
||||
|
||||
var start = ParseFloat(range[0], line) * unitScale;
|
||||
var end = ParseFloat(range[1], line) * unitScale;
|
||||
var high = parts.Length >= 3 ? ParseFloat(parts[2], line) : 1f;
|
||||
var low = parts.Length >= 4 ? ParseFloat(parts[3], line) : 0f;
|
||||
sections.Add(new SongSectionSpec(SafeName(parts[0]), start, end, high, low));
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
private static void AddUniquePoint(List<(float Time, float Value)> points, float time, float value)
|
||||
{
|
||||
if (points.Count > 0 && Math.Abs(points[^1].Time - time) < 0.000001f)
|
||||
{
|
||||
points[^1] = (time, value);
|
||||
return;
|
||||
}
|
||||
|
||||
points.Add((time, value));
|
||||
}
|
||||
|
||||
private sealed record PendingOperatorGraph(
|
||||
int Line,
|
||||
string Path,
|
||||
|
|
@ -3772,6 +3876,7 @@ public static class PatchScript
|
|||
}
|
||||
|
||||
private sealed record ParsedEnvelope(Envelope Envelope, float GateSeconds, RateLevelEnvelope? RateLevelEnvelope = null);
|
||||
private sealed record SongSectionSpec(string Name, float StartSeconds, float EndSeconds, float High, float Low);
|
||||
}
|
||||
|
||||
private static readonly (string[] Keys, ModTarget Target)[] ModTargets =
|
||||
|
|
@ -3890,6 +3995,7 @@ 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",
|
||||
"song_form" or "songform" or "arrangement" or "arrange" or "sections" or "section_map" or "section-map" => "song_form",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -273,4 +273,5 @@
|
|||
{"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-01T05:20:00+01:00","topic":"song-form-sugar-and-electro-demo","claim":"AquaSynth now has `song_form` arrangement sugar that owns named section gates and an optional whole-track energy contour, lowering into visible `/form/<name>/<section>` and `/form/<name>/energy` parameters plus control curves. The new `patches/advanced/electro-drop-demo.aqua` uses it with `sub_kick_grid`, `bass_response`, `additive_wash`, `dust_hat`, `section_rise`, scale/chord/mix lanes, and complextro-style alternating bass timbres to make a structured electro demo with build, drop, break, second build, and final drop.","evidence":"src/AquaSynth.Core/PatchScript.cs; tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs; docs/music-production-knowledge/aqua-dsp-usage-manual.md; patches/advanced/electro-drop-demo.aqua; dotnet test tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter \"ParserSupportsSongFormSugar|ParserSupportsProductionAbstractionSugar|PatchLibraryScriptsParseAndExportFaust\": passed"}
|
||||
{"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"}
|
||||
|
|
|
|||
|
|
@ -193,6 +193,31 @@ public sealed class PatchScriptTests
|
|||
Assert.True(patch.Voices.Count >= 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParserSupportsSongFormSugar()
|
||||
{
|
||||
var patch = PatchScript.Parse("""
|
||||
meter bpm=120 beats=4
|
||||
song_form name=electro unit=bar sections=intro:0-2:.35,build:2-4:.8,drop:4-8:1,break:8-10:.5,final_drop:10-14:1 energy=0:.25,2:.55,4:1,8:.35,10:.75,14:.2
|
||||
voice wave=saw freq=220 gain=@/form/electro/drop sustain=1 decay=.2
|
||||
""");
|
||||
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/form/electro/intro");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/form/electro/drop");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/form/electro/final_drop");
|
||||
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/form/electro/energy");
|
||||
var drop = Assert.Single(patch.ControlCurves, curve => curve.Name == "electro_drop");
|
||||
Assert.Equal(ControlCurveInterpolation.Hold, drop.Interpolation);
|
||||
Assert.Equal(8f, drop.Points[1].TimeSeconds, 3);
|
||||
Assert.Equal(16f, drop.Points[2].TimeSeconds, 3);
|
||||
Assert.Equal(1f, drop.Points[1].Value, 3);
|
||||
Assert.Equal(0f, drop.Points[2].Value, 3);
|
||||
var energy = Assert.Single(patch.ControlCurves, curve => curve.Name == "electro_energy");
|
||||
Assert.Equal(4f, energy.Points[1].TimeSeconds, 3);
|
||||
Assert.Equal(8f, energy.Points[2].TimeSeconds, 3);
|
||||
Assert.Equal(1f, energy.Points[2].Value, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParserSupportsVoiceVowelFrames()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ function Start-CodexAgentJob {
|
|||
[string]$LogPath,
|
||||
[string]$CodexCommand,
|
||||
[string]$CodexModel,
|
||||
[string]$JobName
|
||||
[string]$JobName,
|
||||
[string]$SandboxMode = "workspace-write",
|
||||
[string[]]$WritableDirs = @()
|
||||
)
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $OutputPath) | Out-Null
|
||||
|
|
@ -68,11 +70,14 @@ function Start-CodexAgentJob {
|
|||
$args = @(
|
||||
"exec",
|
||||
"--cd", $RepoRoot,
|
||||
"--full-auto",
|
||||
"--sandbox", $SandboxMode,
|
||||
"--output-last-message", $OutputPath,
|
||||
"--config", 'shell_environment_policy.inherit="all"',
|
||||
"--add-dir", (Join-Path (Split-Path -Parent $RepoRoot) "CultLib")
|
||||
)
|
||||
foreach ($dir in $WritableDirs) {
|
||||
$args += @("--add-dir", $dir)
|
||||
}
|
||||
if ($CodexModel.Length -gt 0) {
|
||||
$args += @("--model", $CodexModel)
|
||||
}
|
||||
|
|
@ -153,6 +158,67 @@ function Assert-SongCandidates {
|
|||
}
|
||||
}
|
||||
|
||||
function Assert-AgentSelfIterationEvidence {
|
||||
param(
|
||||
[string]$PassDirectory,
|
||||
[int]$AgentsPerPass,
|
||||
[int]$SongsPerAgent,
|
||||
[int]$IterationsPerTarget
|
||||
)
|
||||
|
||||
$failures = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($agent in 1..$AgentsPerPass) {
|
||||
$agentId = "agent-{0:00}" -f $agent
|
||||
$agentDir = Join-Path $PassDirectory $agentId
|
||||
$iterationRoot = Join-Path $agentDir "iterations"
|
||||
$spectrogramReview = Join-Path $agentDir "spectrogram-review.md"
|
||||
if (!(Test-Path -LiteralPath $spectrogramReview)) {
|
||||
$failures.Add("$agentId missing spectrogram-review.md")
|
||||
}
|
||||
else {
|
||||
$reviewText = Get-Content -LiteralPath $spectrogramReview -Raw
|
||||
foreach ($song in 1..$SongsPerAgent) {
|
||||
$songId = "target-{0:00}" -f $song
|
||||
foreach ($attempt in 1..$IterationsPerTarget) {
|
||||
$attemptId = "attempt-{0:00}" -f $attempt
|
||||
if ($reviewText -notmatch [regex]::Escape($songId) -or $reviewText -notmatch [regex]::Escape($attemptId)) {
|
||||
$failures.Add("$agentId spectrogram-review.md does not name $songId $attemptId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($reviewText -notmatch "Visible mismatch|mismatch" -or
|
||||
$reviewText -notmatch "Responsible lane|owner lane|Responsible lanes" -or
|
||||
$reviewText -notmatch "Still failed|Residual|failed") {
|
||||
$failures.Add("$agentId spectrogram-review.md lacks mismatch/owner/failure review language")
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($song in 1..$SongsPerAgent) {
|
||||
$songId = "target-{0:00}" -f $song
|
||||
foreach ($attempt in 1..$IterationsPerTarget) {
|
||||
$attemptId = "attempt-{0:00}" -f $attempt
|
||||
$attemptDir = Join-Path (Join-Path $iterationRoot $attemptId) $songId
|
||||
$summary = Get-ChildItem -LiteralPath $attemptDir -Filter "summary.csv" -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($null -eq $summary) {
|
||||
$failures.Add("$agentId $songId $attemptId missing summary.csv")
|
||||
continue
|
||||
}
|
||||
|
||||
$renderFailure = Get-ChildItem -LiteralPath $attemptDir -Filter "render-failure.txt" -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
$candidateLogMel = Get-ChildItem -LiteralPath $attemptDir -Filter "candidate-logmel-spectrogram.csv" -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($null -eq $renderFailure -and $null -eq $candidateLogMel) {
|
||||
$failures.Add("$agentId $songId $attemptId has summary.csv but no candidate log-mel spectrogram or render-failure.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
throw "Self-iteration evidence incomplete:`n$($failures -join "`n")"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SongSources {
|
||||
param(
|
||||
[string]$Source,
|
||||
|
|
@ -369,6 +435,17 @@ Invoke-LoggedProcess `
|
|||
"300"
|
||||
) `
|
||||
-LogPath (Join-Path $logRoot "music-knowledge-seed-index.log")
|
||||
Invoke-LoggedProcess `
|
||||
-FilePath "dotnet" `
|
||||
-ArgumentList @(
|
||||
"build",
|
||||
$workerProject,
|
||||
"--no-restore",
|
||||
"--disable-build-servers",
|
||||
"-p:UseSharedCompilation=false",
|
||||
"-p:BuildInParallel=false"
|
||||
) `
|
||||
-LogPath (Join-Path $logRoot "worker-prebuild.log")
|
||||
$latestMusicKnowledgeStore = $musicKnowledgeStore
|
||||
for ($pass = 1; $pass -le $Passes; $pass++) {
|
||||
$passId = "pass-{0:000}" -f $pass
|
||||
|
|
@ -501,7 +578,7 @@ for ($pass = 1; $pass -le $Passes; $pass++) {
|
|||
$prompt = @"
|
||||
You are an AquaSynth producer-apprenticeship worker.
|
||||
|
||||
The dataset is on trial. Your patches, producer briefs, listening journals, failures, and evaluator scores become curriculum evidence in CultCache and the vector database. Write useful studio knowledge and reusable reasoning, not just a one-off trick that happens to flatter one metric.
|
||||
The dataset is on trial. Your patches, producer briefs, listening journals, spectrogram reviews, failures, and self-critiques become curriculum evidence in CultCache and the vector database. Write useful studio knowledge and reusable reasoning, not a one-off trick that flatters a scalar metric.
|
||||
|
||||
Challenge:
|
||||
$challengeList
|
||||
|
|
@ -536,19 +613,20 @@ Generalization:
|
|||
|
||||
Producer evidence:
|
||||
- Write $agentDir/producer-brief.md before patching. For each target, include the artistic reading, likely genre/tempo feel, emotional/energy contour, section map, instrument role map, mix priorities, and the exact challenge artifacts you are trusting.
|
||||
- Write $agentDir/listening-journal.md during self-iteration. For every attempt, record what you expected, what the evaluator/audio facts said, what sounded alive, what sounded fake or static, and the exact revision you made.
|
||||
- Write $agentDir/listening-journal.md during self-iteration. For every attempt, record what you expected, what the rendered audio and spectrogram evidence showed, what sounded alive, what sounded fake or static, and the exact revision you made.
|
||||
- Write $agentDir/spectrogram-review.md during self-iteration. For every target and every attempt, compare the target log-mel evidence against the candidate log-mel evidence directly. Name the visible mismatch in time and band/register terms, the Aqua owner lane responsible, the edit you made for the next attempt, and what still failed after reviewing the result.
|
||||
- Write $agentDir/aqua-gap-ledger.md. List missing primitives, syntax sugar, control surfaces, analysis views, or renderer features that made the production harder. Each gap needs current workaround, evidence path, and whether it blocks composition or only polish.
|
||||
- Write $agentDir/studio-lesson.md at the end. This is the compact lesson for the next musician: keep/cut verdicts, transferable production ideas, rejected tricks, and which AquaSynth abstractions should be mined next.
|
||||
- The evaluator records producer_musicianship_score, required_studio_docs_present, template_loop_risk, noise_percussion_risk, composition_section_score, and aqua_gap_count. Candidates with missing studio evidence, stock-loop risk, or noise-percussion risk are failure pressure, not curriculum exemplars.
|
||||
- The analysis worker records producer_musicianship_score, required_studio_docs_present, template_loop_risk, noise_percussion_risk, composition_section_score, and aqua_gap_count. These are warning lights only. Candidates with missing studio evidence, stock-loop risk, or noise-percussion risk are failure pressure, not curriculum exemplars.
|
||||
|
||||
Self-iteration loop:
|
||||
- For each target, run exactly $IterationsPerTarget local attempts before publishing the final .aqua file.
|
||||
- Each attempt must write a candidate under the private iteration root at attempt-NN/<target-id>/patch, run the scoring worker against that one target, inspect the rendered candidate evidence, and revise the next attempt.
|
||||
- Scoring command pattern:
|
||||
dotnet run --project "$workerProject" -- song-score --patch-root "<attempt-patch-dir>" --challenge "<challenge-json>" --artifact-root "<attempt-dir>" --batch-id "<agent-id>-<target-id>-attempt-NN" --store "<attempt-dir>/iteration-results.cc" --hypothesizer "$agentId-iteration"
|
||||
- After each score, read the attempt's evaluator-report.md and summary.csv. If the attempt rendered, also read audio/comparison.txt, audio/candidate-analysis.md, audio/candidate-logmel-band-stats.csv, audio/candidate-rms-envelope-autocorr.csv, and audio/candidate-whitened-spectral-autocorr.csv. If the attempt is render-failed, do not try to read missing candidate audio analysis files; treat the parse/lowering/render failure as negative syntax evidence and fix that before changing the composition.
|
||||
- Use those candidate-side facts the same way you use target facts: compare spectral bands, envelope autocorrelation, whitened spectral autocorrelation, centroid/RMS, instrument-role metrics, and chip-distress risk. Move timings, filters, gains, sources, roles, and patterns between attempts.
|
||||
- The evaluator records song-continuity metrics: candidate_motion_coverage, motion_coverage_ratio, candidate_first_second_energy_share, first_second_energy_excess, candidate_tail_energy_share, and mode_collapse_risk. A patch that plays a short phrase in the first second and then coasts on low-motion texture/noise is failed song evidence even if its RMS or log-mel score looks tolerable.
|
||||
- Each attempt must write a candidate under the private iteration root at attempt-NN/<target-id>/patch, run the analysis worker against that one target, inspect the rendered candidate evidence, write the spectrogram review entry, and revise the next attempt.
|
||||
- Analysis command pattern:
|
||||
dotnet run --no-build --project "$workerProject" -- song-score --patch-root "<attempt-patch-dir>" --challenge "<challenge-json>" --artifact-root "<attempt-dir>" --batch-id "<agent-id>-<target-id>-attempt-NN" --store "<attempt-dir>/iteration-results.cc" --hypothesizer "$agentId-iteration"
|
||||
- After each analysis run, read the attempt's evaluator-report.md and summary.csv as machine telemetry, not judgment. If the attempt rendered, also read audio/comparison.txt, audio/candidate-analysis.md, audio/candidate-logmel-spectrogram.csv, audio/candidate-logmel-band-stats.csv, audio/candidate-rms-envelope-autocorr.csv, and audio/candidate-whitened-spectral-autocorr.csv. Compare these with the target analysis_report, log_mel_spectrogram_csv, log_mel_band_stats_csv, rms_envelope_csv, and autocorrelation artifacts named in challenge.md.
|
||||
- Use those candidate-side facts the same way you use target facts: directly compare log-mel energy by time window and band/register, then use envelope autocorrelation, whitened spectral autocorrelation, centroid/RMS, instrument-role metrics, and chip-distress risk as supporting clues. Move timings, filters, gains, sources, roles, and patterns between attempts.
|
||||
- The analysis worker records song-continuity metrics: candidate_motion_coverage, motion_coverage_ratio, candidate_first_second_energy_share, first_second_energy_excess, candidate_tail_energy_share, and mode_collapse_risk. A patch that plays a short phrase in the first second and then coasts on low-motion texture/noise is failed song evidence even if its RMS or log-mel score looks tolerable.
|
||||
- If an attempt has mode_collapse_risk >= .45, the next attempt must add later-section motifs or eventful motion across the target's full duration. Do not merely raise the noise bed or pad gain; that is hiding, not composing.
|
||||
- If an attempt has high template_loop_risk or noise_percussion_risk, rebuild the role ownership before touching level: drums need pitched body plus filtered skin and target-specific gates; texture needs band limits and musical motion.
|
||||
- Keep intermediate candidates inside the private iteration root; publish only one final .aqua per target to the official target patch output directory.
|
||||
|
|
@ -622,12 +700,13 @@ Instrument and DSL convention invention:
|
|||
- The `.aqua` candidate must use today's implemented syntax only: `pattern`, `scale`, `param`, `curve`, and ordinary patch/voice syntax are legal. Proposed future sugar is evidence for the next DSL cut, not magic accepted by the parser.
|
||||
- Today's implemented composition syntax also includes `meter`, `sequence`, `chords`, and `mix`. Prefer those over raw `param`/`curve` boilerplate when writing composition-scale structure.
|
||||
- Legal oscillator wave= values are sine, square, saw, triangle, and noise only. Use square plus filters/envelopes for pulse-like tone; pulse is not accepted syntax.
|
||||
- You do not own the AquaSynth source tree during this job. Do not edit `src/`, `tests/`, `docs/`, `patches/library.yaml`, or shared repo state. Write only under your assigned artifact/report/patch directories. Proposed DSL/source changes belong in `abstraction-ledger.md` and `aqua-gap-ledger.md`.
|
||||
|
||||
Evidence contract:
|
||||
- Read every assigned challenge report, target music intelligence report, $preEvidence, and $preMusicEvidence.
|
||||
- Write $agentDir/hypotheses.md with: per-target reference features, shared cross-target invariants, cited analysis artifacts, tempo/rhythm plan, register/scale plan, scene voices, synthesis owners, invented instrument roles, expected metric movement on assigned targets, known risks.
|
||||
- Include a `Composition Map` section in $agentDir/hypotheses.md: meter/time signature, progression or tonal center, instrument lanes, section events after the opening, automation/mix moves, and which evaluator metrics should prove the arrangement did not collapse.
|
||||
- Also write $agentDir/producer-brief.md, $agentDir/listening-journal.md, $agentDir/aqua-gap-ledger.md, and $agentDir/studio-lesson.md. These reports are now curriculum admission evidence, not decorative paperwork.
|
||||
- Include a `Composition Map` section in $agentDir/hypotheses.md: meter/time signature, progression or tonal center, instrument lanes, section events after the opening, automation/mix moves, and which visible spectrogram regions should prove the arrangement did not collapse.
|
||||
- Also write $agentDir/producer-brief.md, $agentDir/listening-journal.md, $agentDir/spectrogram-review.md, $agentDir/aqua-gap-ledger.md, and $agentDir/studio-lesson.md. These reports are now curriculum admission evidence, not decorative paperwork.
|
||||
- Write exactly one `.aqua` file under each target patch output directory named `<agent-id>__<family>__<hypothesis>.aqua`.
|
||||
- Include at least three scene voices or layers in the patch: a primary voice/body, a rhythmic drum/transient/noise role, and a pad/bed/recording-color role. Prefer syrinx for the primary voice when it is voice-like, subtractive for the drum/transient, and additive/PAD or shaped texture for the bed.
|
||||
- The target must have full-form continuity: distinct musical events or motif mutations after the first second, with audible activity distributed across the beginning, middle, and ending. A one-second riff followed by gently textured pink noise is mode collapse and should not be submitted.
|
||||
|
|
@ -644,11 +723,18 @@ Return a short final summary naming the patch and report.
|
|||
-LogPath $logPath `
|
||||
-CodexCommand $CodexCommand `
|
||||
-CodexModel $CodexModel `
|
||||
-JobName "$passId-$agentId"
|
||||
-JobName "$passId-$agentId" `
|
||||
-SandboxMode "read-only" `
|
||||
-WritableDirs @($agentDir, $agentPatchDir)
|
||||
}
|
||||
|
||||
Wait-CodexJobs -Jobs $jobs
|
||||
Assert-SongCandidates -PatchDirectory $patchRoot -ExpectedCount ($AgentsPerPass * $SongsPerAgent)
|
||||
Assert-AgentSelfIterationEvidence `
|
||||
-PassDirectory $passDir `
|
||||
-AgentsPerPass $AgentsPerPass `
|
||||
-SongsPerAgent $SongsPerAgent `
|
||||
-IterationsPerTarget $IterationsPerTarget
|
||||
|
||||
for ($agent = 1; $agent -le $AgentsPerPass; $agent++) {
|
||||
$agentId = "agent-{0:00}" -f $agent
|
||||
|
|
@ -694,8 +780,8 @@ Your only job is to distill musician model output from this phase into actionabl
|
|||
Read:
|
||||
- pass directory: $passDir
|
||||
- proposed patches: $patchRoot
|
||||
- evaluator reports and summary CSV files under each agent target directory
|
||||
- producer-brief.md, listening-journal.md, aqua-gap-ledger.md, studio-lesson.md, abstraction-ledger.md, instrument-conventions.md from musician agents
|
||||
- analysis telemetry reports and summary CSV files under each agent target directory
|
||||
- producer-brief.md, listening-journal.md, spectrogram-review.md, aqua-gap-ledger.md, studio-lesson.md, abstraction-ledger.md, instrument-conventions.md from musician agents
|
||||
- playlist report: $(Join-Path $passDir "top-scoring-candidates.md")
|
||||
- prior Qdrant music knowledge injection: $preMusicEvidence
|
||||
|
||||
|
|
@ -713,6 +799,7 @@ Format knowledge-curation.md as compact entries. Each entry must include:
|
|||
|
||||
Admission standard:
|
||||
- Keep only lessons with evidence and a clear next action.
|
||||
- Treat musician spectrogram-review.md files as the primary evidence for what they saw, tried, and failed to achieve. Metrics may corroborate a lesson, but they must not be the lesson.
|
||||
- Prefer composition, arrangement, sound design, mixing, and AquaSynth sugar lessons that transfer across targets.
|
||||
- Reject generic advice, duplicate role-name slogans, and anything that merely restates the prompt.
|
||||
- If the phase learned mostly negative evidence, write negative entries with precise failure signatures.
|
||||
|
|
@ -727,7 +814,9 @@ Return a short final note naming knowledge-curation.md.
|
|||
-LogPath (Join-Path $logRoot "$passId-knowledge-curator.log") `
|
||||
-CodexCommand $CodexCommand `
|
||||
-CodexModel $CodexModel `
|
||||
-JobName "$passId-knowledge-curator"
|
||||
-JobName "$passId-knowledge-curator" `
|
||||
-SandboxMode "read-only" `
|
||||
-WritableDirs @($curatorDir)
|
||||
Wait-CodexJobs -Jobs @($curatorJob)
|
||||
if (!(Test-Path -LiteralPath (Join-Path $curatorDir "knowledge-curation.md")) -or
|
||||
((Get-Item -LiteralPath (Join-Path $curatorDir "knowledge-curation.md")).Length -lt 64)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue