Redirect song curriculum toward composition

This commit is contained in:
Meta Vens 2026-05-31 22:16:28 +01:00
commit 378d8e1fb3
7 changed files with 242 additions and 4 deletions

View file

@ -348,6 +348,16 @@ first-second energy share, tail energy share, and mode-collapse risk; distillati
should treat high collapse risk as negative pressure even when log-mel similarity should treat high collapse risk as negative pressure even when log-mel similarity
looks superficially acceptable. looks superficially acceptable.
Composition structure is now part of song-scene authorship. Sound roles answer
"what is playing"; composition roles answer "why does time keep mattering."
Future song agents should declare meter/time signature, chord or tonal
progression, note-degree sampling, instrument-lane gates, section events,
composition-scale automation, and mix movement before polishing timbre. The
current Strudel-ish sugar is intentionally small: `meter` stores the beat grid;
`sequence` owns lane gates; `scale` owns sampled note degrees; `chords` owns
progression/voicing lanes; and `mix` owns gain automation. Each lowers to
ordinary `param` and `curve` records, so the realtime truth remains inspectable.
Song-scene instrument roles are scored as curriculum evidence. Voice-like Song-scene instrument roles are scored as curriculum evidence. Voice-like
leads should prefer syrinx/acoustic source ownership with pressure/opening leads should prefer syrinx/acoustic source ownership with pressure/opening
motion and radiation shaping; percussion should prefer subtractive pitched body motion and radiation shaping; percussion should prefer subtractive pitched body

View file

@ -99,6 +99,8 @@ public static class PatchScript
private Playback Playback { get; set; } = new(); private Playback Playback { get; set; } = new();
private float Gain { get; set; } = 1; private float Gain { get; set; } = 1;
private bool SoftClip { get; set; } = true; private bool SoftClip { get; set; } = true;
private float BeatSeconds { get; set; } = 0.5f;
private int BeatsPerBar { get; set; } = 4;
public SynthPatch Build() public SynthPatch Build()
{ {
@ -331,6 +333,22 @@ public static class PatchScript
FlushPendingOperatorGraph(); FlushPendingOperatorGraph();
AddScaleCurve(fields, line); AddScaleCurve(fields, line);
break; break;
case "meter":
FlushPendingOperatorGraph();
AddMeter(fields, line);
break;
case "sequence":
FlushPendingOperatorGraph();
AddSequencePattern(fields, line);
break;
case "chords":
FlushPendingOperatorGraph();
AddChordProgression(fields, line);
break;
case "mix":
FlushPendingOperatorGraph();
AddMixAutomation(fields, line);
break;
case "control_spline": case "control_spline":
FlushPendingOperatorGraph(); FlushPendingOperatorGraph();
AddControlSpline(fields, line); AddControlSpline(fields, line);
@ -2570,7 +2588,7 @@ public static class PatchScript
private void AddScaleCurve(IReadOnlyDictionary<string, string> fields, int line) private void AddScaleCurve(IReadOnlyDictionary<string, string> fields, int line)
{ {
var parameterPath = Required(fields, "path", line); var parameterPath = Required(fields, "path", line);
var stepSeconds = GetFloat(fields, line, GetFloat(fields, line, 0.25f, "beat"), "step", "step_seconds", "dt"); var stepSeconds = GetFloat(fields, line, GetFloat(fields, line, BeatSeconds / 2, "beat"), "step", "step_seconds", "dt");
if (stepSeconds <= 0) if (stepSeconds <= 0)
{ {
throw new PatchScriptException(line, "scale step must be greater than zero"); throw new PatchScriptException(line, "scale step must be greater than zero");
@ -2608,6 +2626,108 @@ public static class PatchScript
("depth", GetAny(fields, ["depth", "amount", "mix"], "1"))), line); ("depth", GetAny(fields, ["depth", "amount", "mix"], "1"))), line);
} }
private void AddMeter(IReadOnlyDictionary<string, string> fields, int line)
{
var bpm = GetFloat(fields, line, 60f / BeatSeconds, "bpm", "tempo");
if (bpm <= 0)
{
throw new PatchScriptException(line, "meter bpm must be greater than zero");
}
BeatSeconds = 60f / bpm;
BeatsPerBar = Math.Max(1, GetInt(fields, line, BeatsPerBar, "beats", "beats_per_bar", "numerator"));
}
private void AddSequencePattern(IReadOnlyDictionary<string, string> fields, int line)
{
var name = SafeName(RequiredAny(fields, ["name", "n", "voice", "lane", "instrument"], line));
var path = GetAny(fields, ["path", "gate_path"], $"/seq/{name}");
var stepSeconds = GetMusicalStepSeconds(fields, line, BeatSeconds / 4, "step", "step_seconds", "dt");
AddPatternCurve(SyntheticFields(
("name", GetAny(fields, ["curve", "curve_name"], $"{name}_seq")),
("path", path),
("pattern", RequiredAny(fields, ["pattern", "pat", "steps"], line)),
("step", stepSeconds.ToString(CultureInfo.InvariantCulture)),
("high", GetAny(fields, ["high", "on", "hit", "gain"], "1")),
("low", GetAny(fields, ["low", "off", "rest"], "0")),
("loop", GetAny(fields, ["loop"], "true"))), line);
}
private void AddChordProgression(IReadOnlyDictionary<string, string> fields, int line)
{
var name = SafeName(GetAny(fields, ["name", "n"], "chords"));
var root = GetFloat(fields, line, 220, "root", "freq", "base");
var scaleIntervals = TryGetAny(fields, ["scale_intervals", "intervals"], out var scaleIntervalsText)
? ParseLooseIntList(scaleIntervalsText, line)
: ScaleIntervals(GetAny(fields, ["scale", "mode"], "minor"));
var progression = TryGetAny(fields, ["progression", "prog", "roots"], out var progressionText)
? ParseLooseIntList(progressionText, line)
: [0, 5, 3, 4];
var voicing = TryGetAny(fields, ["voicing", "degrees"], out var voicingText)
? ParseLooseIntList(voicingText, line)
: [0, 2, 4];
if (progression.Count == 0 || voicing.Count == 0)
{
throw new PatchScriptException(line, "chords need progression and voicing degrees");
}
var stepSeconds = GetMusicalStepSeconds(fields, line, BeatSeconds * BeatsPerBar, "step", "bar", "bar_seconds");
var paths = TryGetAny(fields, ["paths"], out var pathsText)
? ParseNameList(pathsText)
: voicing.Select((_, index) => $"/chords/{name}/{index}").ToArray();
if (paths.Count != voicing.Count)
{
throw new PatchScriptException(line, "chords paths count must match voicing count");
}
for (var voiceIndex = 0; voiceIndex < voicing.Count; voiceIndex++)
{
var values = progression
.Select(rootDegree => ChordToneFrequency(root, scaleIntervals, rootDegree, voicing[voiceIndex]))
.ToArray();
var path = paths[voiceIndex];
EnsureParameter(path, values[0], values.Min() * 0.5f, values.Max() * 2f, 0.01f, "Hz", line);
AddControlCurve(SyntheticFields(
("name", $"{name}_{voiceIndex}"),
("path", path),
("points", PointsText(values.Select((value, index) => (index * stepSeconds, value)))),
("interp", "hold"),
("loop", GetAny(fields, ["loop"], "true")),
("mode", GetAny(fields, ["curve_mode"], "blend"))), line);
}
}
private void AddMixAutomation(IReadOnlyDictionary<string, string> fields, int line)
{
var name = SafeName(RequiredAny(fields, ["name", "n", "lane", "voice", "bus"], line));
var path = GetAny(fields, ["path"], $"/mix/{name}/gain");
if (TryGetAny(fields, ["pattern", "pat", "steps"], out var pattern))
{
AddPatternCurve(SyntheticFields(
("name", $"{name}_mix"),
("path", path),
("pattern", pattern),
("step", GetMusicalStepSeconds(fields, line, BeatSeconds, "step", "step_seconds", "dt").ToString(CultureInfo.InvariantCulture)),
("high", GetAny(fields, ["high", "on"], GetAny(fields, ["gain", "g"], "1"))),
("low", GetAny(fields, ["low", "off"], "0")),
("loop", GetAny(fields, ["loop"], "true"))), line);
return;
}
var points = TryGetAny(fields, ["points", "pts", "values"], out var pointsText)
? pointsText
: $"0:{GetAny(fields, ["gain", "g", "default"], "1")}";
var values = ParseControlCurvePoints(points, line).Select(point => point.Value).ToArray();
EnsureParameter(path, values.Length == 0 ? 1 : values[0], 0, Math.Max(1, values.DefaultIfEmpty(1).Max() * 2), 0.001f, "", line);
AddControlCurve(SyntheticFields(
("name", $"{name}_mix"),
("path", path),
("points", points),
("interp", GetAny(fields, ["interp", "interpolation"], "linear")),
("loop", GetAny(fields, ["loop"], "false")),
("mode", GetAny(fields, ["mode"], "blend"))), line);
}
private void AddNoiseTexture(IReadOnlyDictionary<string, string> fields, int line) private void AddNoiseTexture(IReadOnlyDictionary<string, string> fields, int line)
{ {
var name = SafeName(GetAny(fields, ["name", "n"], $"texture_{Voices.Count}")); var name = SafeName(GetAny(fields, ["name", "n"], $"texture_{Voices.Count}"));
@ -3388,6 +3508,31 @@ public static class PatchScript
return parameters; return parameters;
} }
private float GetMusicalStepSeconds(IReadOnlyDictionary<string, string> fields, int line, float fallback, params string[] keys)
{
if (!TryGetAny(fields, keys, out var value))
{
return fallback;
}
if (value.Equals("bar", StringComparison.OrdinalIgnoreCase) || value.Equals("measure", StringComparison.OrdinalIgnoreCase))
{
return BeatSeconds * BeatsPerBar;
}
if (value.EndsWith("bar", StringComparison.OrdinalIgnoreCase))
{
return BeatSeconds * BeatsPerBar * ParseFloat(value[..^3], line);
}
if (value.EndsWith("beat", StringComparison.OrdinalIgnoreCase))
{
return BeatSeconds * ParseFloat(value[..^4], line);
}
return ParseFloat(value, line);
}
private sealed record PendingOperatorGraph( private sealed record PendingOperatorGraph(
int Line, int Line,
string Path, string Path,
@ -3517,8 +3662,12 @@ public static class PatchScript
"param" or "parameter" => "param", "param" or "parameter" => "param",
"control_surface" or "surface" or "controlsurface" => "control_surface", "control_surface" or "surface" or "controlsurface" => "control_surface",
"curve" or "control_curve" or "automation" => "curve", "curve" or "control_curve" or "automation" => "curve",
"pattern" or "pat" or "seq" or "sequence" => "pattern", "pattern" or "pat" or "seq" => "pattern",
"scale" or "notes" or "melody" => "scale", "scale" or "notes" or "melody" => "scale",
"meter" or "time" or "time_signature" or "timesig" => "meter",
"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",
"control_spline" or "spline" or "gesture_spline" or "gesturespline" => "control_spline", "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", "phoneme_gesture" or "phone_gesture" or "ipa_gesture" or "phoneme" or "phone" => "phoneme_gesture",
"gesture" or "gesture_group" or "gesturegroup" => "gesture", "gesture" or "gesture_group" or "gesturegroup" => "gesture",
@ -3817,6 +3966,14 @@ public static class PatchScript
return intervals.Select(interval => root * MathF.Pow(2f, interval / 12f)).ToArray(); return intervals.Select(interval => root * MathF.Pow(2f, interval / 12f)).ToArray();
} }
private static float ChordToneFrequency(float root, IReadOnlyList<int> scaleIntervals, int rootDegree, int voicingDegree)
{
var degree = rootDegree + voicingDegree;
var octave = MathF.Floor(degree / (float)scaleIntervals.Count);
var wrapped = ((degree % scaleIntervals.Count) + scaleIntervals.Count) % scaleIntervals.Count;
return root * MathF.Pow(2f, (scaleIntervals[wrapped] + octave * 12f) / 12f);
}
private static IReadOnlyList<int> ScaleIntervals(string scale) => scale.ToLowerInvariant() switch private static IReadOnlyList<int> ScaleIntervals(string scale) => scale.ToLowerInvariant() switch
{ {
"major" or "ionian" => [0, 2, 4, 5, 7, 9, 11, 12], "major" or "ionian" => [0, 2, 4, 5, 7, 9, 11, 12],

View file

@ -262,3 +262,4 @@
{"date":"2026-05-31","area":"music-production-knowledge-cultcache","summary":"Fresh music curriculum stores should be distilled from official rendered evidence, not copied from hot trial stores. `music-distill` now writes paged `MusicProductionKnowledgeDocument` records: one master quality standard, reusable scene-role doctrine for syrinx voice, subtractive drums, additive/PAD beds, and shaped texture, quarantined render-failure pressure, ranked rendered candidate patterns, and abstraction-ledger syntax-sugar pressure. Explorer commands `music-search` and `music-show` make the new store inspectable by fresh agents before patch authoring.","verification":"dotnet build E:\\Projects\\AquaSynth\\tools\\IpaTrialWorker\\IpaTrialWorker.csproj --no-restore: passed; dotnet test E:\\Projects\\AquaSynth\\tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter CultCache: 7 passed; music-distill over artifacts\\parity\\song-snippet-swarms\\20260531T121228337 wrote 37 records from 28 official rows; music-search syrinx/drums and music-show quality-standard succeeded","evidence":"src/AquaSynth.Core/IpaTrialResults.cs; tools/IpaTrialWorker/Program.cs; artifacts/parity/music-knowledge/20260531T121228337/music-production-knowledge.cc; artifacts/parity/music-knowledge/20260531T121228337/distillation-report.md; artifacts/parity/music-knowledge/20260531T121228337/show-quality-standard.md"} {"date":"2026-05-31","area":"music-production-knowledge-cultcache","summary":"Fresh music curriculum stores should be distilled from official rendered evidence, not copied from hot trial stores. `music-distill` now writes paged `MusicProductionKnowledgeDocument` records: one master quality standard, reusable scene-role doctrine for syrinx voice, subtractive drums, additive/PAD beds, and shaped texture, quarantined render-failure pressure, ranked rendered candidate patterns, and abstraction-ledger syntax-sugar pressure. Explorer commands `music-search` and `music-show` make the new store inspectable by fresh agents before patch authoring.","verification":"dotnet build E:\\Projects\\AquaSynth\\tools\\IpaTrialWorker\\IpaTrialWorker.csproj --no-restore: passed; dotnet test E:\\Projects\\AquaSynth\\tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter CultCache: 7 passed; music-distill over artifacts\\parity\\song-snippet-swarms\\20260531T121228337 wrote 37 records from 28 official rows; music-search syrinx/drums and music-show quality-standard succeeded","evidence":"src/AquaSynth.Core/IpaTrialResults.cs; tools/IpaTrialWorker/Program.cs; artifacts/parity/music-knowledge/20260531T121228337/music-production-knowledge.cc; artifacts/parity/music-knowledge/20260531T121228337/distillation-report.md; artifacts/parity/music-knowledge/20260531T121228337/show-quality-standard.md"}
{"date":"2026-05-31","area":"song-corpus-three-pass-curriculum","summary":"A three-pass 10-second corpus curriculum run across D:\\Music\\Reinier completed with 25 official assignments per pass. Pass 1 produced 25 official candidates, 23 rendered, 2 render failures, best cosine 0.299361, mean cosine 0.162788, mean score 0.198594, and 34 promoted music knowledge records. Pass 2 produced 25 official candidates, 20 rendered, 5 render failures, best cosine 0.351068, mean cosine 0.137059, mean score 0.170592, and 56 promoted music knowledge records. Pass 3 produced 25 official candidates, 15 rendered, 10 render failures, best cosine 0.304571, mean cosine 0.115347, mean score 0.128224, and 58 promoted music knowledge records. The learned signal is mixed: knowledge injection increased structured artifacts and reusable abstraction ledgers, but later-pass render failure and score degradation must be treated as quality pressure, not progress.","verification":"pass-001/pass-002/pass-003 each have 25 challenge.json files, 25 official candidates, 25 official summary.csv files, top-scoring-candidates.m3u, distillation-report.md, semantic-search-after.md, vector-index-after.md, music-production-knowledge.cc, music-production-knowledge-report.md, and music-production-quality-standard.md. Pass 3 was manually finished after external Codex jobs failed by promoting existing attempt-05 patches and running official scoring/promotion with IpaTrialWorker.exe.","evidence":"artifacts/parity/song-snippet-swarms/20260531T171802519/pass-001/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-002/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/top-scoring-candidates.m3u; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/distillation-report.md"} {"date":"2026-05-31","area":"song-corpus-three-pass-curriculum","summary":"A three-pass 10-second corpus curriculum run across D:\\Music\\Reinier completed with 25 official assignments per pass. Pass 1 produced 25 official candidates, 23 rendered, 2 render failures, best cosine 0.299361, mean cosine 0.162788, mean score 0.198594, and 34 promoted music knowledge records. Pass 2 produced 25 official candidates, 20 rendered, 5 render failures, best cosine 0.351068, mean cosine 0.137059, mean score 0.170592, and 56 promoted music knowledge records. Pass 3 produced 25 official candidates, 15 rendered, 10 render failures, best cosine 0.304571, mean cosine 0.115347, mean score 0.128224, and 58 promoted music knowledge records. The learned signal is mixed: knowledge injection increased structured artifacts and reusable abstraction ledgers, but later-pass render failure and score degradation must be treated as quality pressure, not progress.","verification":"pass-001/pass-002/pass-003 each have 25 challenge.json files, 25 official candidates, 25 official summary.csv files, top-scoring-candidates.m3u, distillation-report.md, semantic-search-after.md, vector-index-after.md, music-production-knowledge.cc, music-production-knowledge-report.md, and music-production-quality-standard.md. Pass 3 was manually finished after external Codex jobs failed by promoting existing attempt-05 patches and running official scoring/promotion with IpaTrialWorker.exe.","evidence":"artifacts/parity/song-snippet-swarms/20260531T171802519/pass-001/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-002/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/top-scoring-candidates.m3u; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/distillation-report.md"}
{"date":"2026-05-31","area":"song-mode-collapse-evaluator","summary":"The corpus run exposed a scoring blind spot: agents could write a short opening phrase and coast for the remaining clip on low-motion texture/noise while still receiving weak-but-ranked evidence. `song-score` now emits song-continuity metrics for active coverage, motion coverage, first-second energy share, tail energy share, and mode-collapse risk; high collapse risk changes verdict to `weak-mode-collapse`, lowers distillation ranking, and is carried into music knowledge failure modes. Swarm prompts now require later-section motifs/events and explicitly reject one-second riff plus nine seconds of texture as submitted song evidence.","verification":"dotnet build E:\\Projects\\AquaSynth\\tools\\IpaTrialWorker\\IpaTrialWorker.csproj --no-restore: passed; dotnet test E:\\Projects\\AquaSynth\\tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter CultCache: 7 passed; rescoring pass-003 agent_02__retrievedmesh__target_04_a05 produced verdict weak-mode-collapse, candidate_first_second_energy_share 0.883627, candidate_motion_coverage 0.573295, mode_collapse_risk 1","evidence":"tools/IpaTrialWorker/Program.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md; artifacts/parity/song-continuity-smoke/20260531Tmodecollapse-v2/continuity-smoke/summary.csv"} {"date":"2026-05-31","area":"song-mode-collapse-evaluator","summary":"The corpus run exposed a scoring blind spot: agents could write a short opening phrase and coast for the remaining clip on low-motion texture/noise while still receiving weak-but-ranked evidence. `song-score` now emits song-continuity metrics for active coverage, motion coverage, first-second energy share, tail energy share, and mode-collapse risk; high collapse risk changes verdict to `weak-mode-collapse`, lowers distillation ranking, and is carried into music knowledge failure modes. Swarm prompts now require later-section motifs/events and explicitly reject one-second riff plus nine seconds of texture as submitted song evidence.","verification":"dotnet build E:\\Projects\\AquaSynth\\tools\\IpaTrialWorker\\IpaTrialWorker.csproj --no-restore: passed; dotnet test E:\\Projects\\AquaSynth\\tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter CultCache: 7 passed; rescoring pass-003 agent_02__retrievedmesh__target_04_a05 produced verdict weak-mode-collapse, candidate_first_second_energy_share 0.883627, candidate_motion_coverage 0.573295, mode_collapse_risk 1","evidence":"tools/IpaTrialWorker/Program.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md; artifacts/parity/song-continuity-smoke/20260531Tmodecollapse-v2/continuity-smoke/summary.csv"}
{"timestamp":"2026-05-31T22:25:00+01:00","topic":"composition-curriculum-redirect","claim":"The song CultCache run learned transferable sound-production role owners, but not composition. Next passes should require meter, progression or tonal center, note-degree sampling, instrument-lane gates, section events, composition-scale automation, and mix movement. AquaSynth now has small Strudel-ish sugar for `meter`, `sequence`, `chords`, and `mix`, all lowering to visible `param`/`curve` owners alongside existing `pattern` and `scale`.","evidence":"Trawl of pass-003 music-production quality standard, distillation report, and abstraction ledgers showed 56 of 75 official candidates had mode-collapse risk while ledgers repeatedly converged on role owners and sugar candidates. Added parser/test coverage for composition-scale sugar and redirected the song swarm prompt toward Composition Map evidence."}

View file

@ -15,6 +15,16 @@ Current slice:
and the swarm prompt now requires later-section motifs/events instead of and the swarm prompt now requires later-section motifs/events instead of
hiding behind noise or pads. hiding behind noise or pads.
- Follow-up trawl found real generalizable sound-production knowledge but weak
composition: stable role owners for syrinx/acoustic leads, subtractive drums,
additive/PAD beds, shaped texture/air/codec, and strong negative pressure
against mode collapse. The redirect is composition-first: next passes must
publish a Composition Map with meter/time signature, progression or tonal
center, note-degree sampling, instrument-lane gates, section events,
composition-scale automation, and mix movement. Parser sugar now includes
`meter`, `sequence`, `chords`, and `mix`, lowering to ordinary `param` and
`curve` owners alongside existing `pattern` and `scale`.
- Completed a three-pass corpus song curriculum run under - Completed a three-pass corpus song curriculum run under
`artifacts/parity/song-snippet-swarms/20260531T171802519`: each pass prepared `artifacts/parity/song-snippet-swarms/20260531T171802519`: each pass prepared
25 ten-second targets from `D:\Music\Reinier` for five agents with five 25 ten-second targets from `D:\Music\Reinier` for five agents with five

View file

@ -99,6 +99,34 @@ public sealed class PatchScriptTests
Assert.Equal(622.254f, curve.Points[3].Value, 3); Assert.Equal(622.254f, curve.Points[3].Value, 3);
} }
[Fact]
public void ParserSupportsCompositionScaleSugar()
{
var patch = PatchScript.Parse("""
meter bpm=120 beats=4
sequence name=kick pattern=x..x step=0.5 high=0.8 low=0
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
mix name=pad points=0:.18,4:.28,8:.16
voice wave=sine freq=@/chords/pad/root gain=@/mix/pad/gain sustain=10 decay=.2
voice wave=sine freq=@/chords/pad/third gain=@/mix/pad/gain sustain=10 decay=.2
voice wave=sine freq=@/chords/pad/fifth gain=@/mix/pad/gain sustain=10 decay=.2
voice wave=sine freq=58 gain=@/seq/kick sustain=.05 decay=.2
""");
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/seq/kick");
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/mix/pad/gain");
Assert.Contains(patch.Parameters, parameter => parameter.Path == "/chords/pad/root" && parameter.Unit == "Hz");
Assert.Contains(patch.ControlCurves, curve => curve.Name == "kick_seq" && curve.Interpolation == ControlCurveInterpolation.Hold);
var root = Assert.Single(patch.ControlCurves, curve => curve.Name == "pad_0");
Assert.Equal(220f, root.Points[0].Value, 3);
Assert.Equal(349.228f, root.Points[1].Value, 3);
Assert.Equal(293.665f, root.Points[2].Value, 3);
Assert.Equal(329.628f, root.Points[3].Value, 3);
Assert.Equal([0f, 2f, 4f, 6f], root.Points.Select(point => point.TimeSeconds).ToArray());
var mix = Assert.Single(patch.ControlCurves, curve => curve.Name == "pad_mix");
Assert.Equal([.18f, .28f, .16f], mix.Points.Select(point => point.Value).ToArray());
}
[Fact] [Fact]
public void ParserSupportsShapedNoiseTextureSugar() public void ParserSupportsShapedNoiseTextureSugar()
{ {

View file

@ -1699,6 +1699,7 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
[ [
"Admit a candidate as reusable knowledge only when it renders, has explicit instrument-role ownership, and states what production job it owns.", "Admit a candidate as reusable knowledge only when it renders, has explicit instrument-role ownership, and states what production job it owns.",
"Reject one-phrase-plus-texture arrangements as failure pressure; continuity across the clip is part of the production contract.", "Reject one-phrase-plus-texture arrangements as failure pressure; continuity across the clip is part of the production contract.",
"Require an explicit composition map: meter, progression or tonal center, instrument lanes, section events, composition-scale automation, and mix movement.",
"Prefer compact role abstractions over one-off target mimicry; a reusable owner sentence is more valuable than a small metric bump.", "Prefer compact role abstractions over one-off target mimicry; a reusable owner sentence is more valuable than a small metric bump.",
"Keep failed renders as failure-mode pressure only; do not let syntax wreckage dominate retrieval.", "Keep failed renders as failure-mode pressure only; do not let syntax wreckage dominate retrieval.",
"Store full artifacts on disk and cite them; CultCache curriculum records should carry bounded summaries, metrics, and transfer rules." "Store full artifacts on disk and cite them; CultCache curriculum records should carry bounded summaries, metrics, and transfer rules."
@ -1707,12 +1708,14 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
"voice-like lead -> syrinx/acoustic source ports with pressure, opening, and radiation motion", "voice-like lead -> syrinx/acoustic source ports with pressure, opening, and radiation motion",
"drums/transients -> subtractive body plus filtered noise skin plus pattern gate", "drums/transients -> subtractive body plus filtered noise skin plus pattern gate",
"pads/beds -> additive/PAD layer, harmonics, and spectrum banks", "pads/beds -> additive/PAD layer, harmonics, and spectrum banks",
"recording color -> texture role with band limits and gates, not full-duration raw noise" "recording color -> texture role with band limits and gates, not full-duration raw noise",
"composition form -> `meter`, `sequence`, `chords`, `scale`, `pattern`, and `mix` sugar lowering to visible `param`/`curve` owners"
], ],
[ [
"Single-file stores or raw spectrogram payloads are not curriculum; distill to paged CultCache records.", "Single-file stores or raw spectrogram payloads are not curriculum; distill to paged CultCache records.",
"Metric winners with unclear role ownership should be pressure, not doctrine.", "Metric winners with unclear role ownership should be pressure, not doctrine.",
"High cosine with high mode-collapse risk is a trap: it copies a moment, not a song." "High cosine with high mode-collapse risk is a trap: it copies a moment, not a song.",
"Instrument timbre without phrase, progression, lane entrances, and mix movement is sound design, not composition."
], ],
rows.Select(row => row.TrialId).Take(24).ToArray(), rows.Select(row => row.TrialId).Take(24).ToArray(),
rendered.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(24).ToArray(), rendered.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(24).ToArray(),
@ -1767,6 +1770,19 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
static IEnumerable<MusicProductionKnowledgeDocument> MusicRoleDocuments(IReadOnlyList<SongSummaryRow> rendered, string created) static IEnumerable<MusicProductionKnowledgeDocument> MusicRoleDocuments(IReadOnlyList<SongSummaryRow> rendered, string created)
{ {
yield return RoleDocument(
"music-role-composition-form",
"composition form, lanes, and mix motion",
"The composition role owns meter, chord/progression movement, note sampling, instrument-lane gates, section events, automation, and mix motion so a ten-second clip behaves like arranged music rather than a timbre demo.",
[
"Start every song candidate with `meter` and at least one progression or tonal-center declaration.",
"Use `sequence`/`pattern` for lane gates and `scale`/`chords` for pitch material before hand-writing isolated frequencies.",
"Add section events after the opening and use `mix` or ordinary curves for lane entrances, drops, swells, and exits."
],
["meter", "sequence", "pattern", "scale", "chords", "progression", "mix", "curve"],
rendered.Where(row => row.ModeCollapseRisk < .45f).ToArray(),
created);
yield return RoleDocument( yield return RoleDocument(
"music-role-syrinx-voice", "music-role-syrinx-voice",
"syrinx/acoustic voice lead", "syrinx/acoustic voice lead",

View file

@ -417,6 +417,11 @@ $agentSeedList
Goal: Goal:
Write exactly one parseable AquaSynth `.aqua` patch for each frozen $targetKind reference assigned to you. This is scene-audio parity, not IPA articulation. You may use ordinary voices, FM, AM/tremolo, filters, formants, noise layers, acoustic vocal/syrinx primitives, additive/PAD layers, curves, and helper voices. Write exactly one parseable AquaSynth `.aqua` patch for each frozen $targetKind reference assigned to you. This is scene-audio parity, not IPA articulation. You may use ordinary voices, FM, AM/tremolo, filters, formants, noise layers, acoustic vocal/syrinx primitives, additive/PAD layers, curves, and helper voices.
Composition objective:
- The previous corpus learned useful sound-production roles but collapsed into short phrases plus texture. Your job is now composition parity: declare meter, tonal center or progression, instrument lanes, section events, automation, and mix motion before polishing timbre.
- Every final patch must include a composition spine using today's implemented sugar: `meter`, `sequence`, `chords` or `scale`, and `mix` where appropriate. These lower to ordinary `param` and `curve` owners; they are not hidden magic.
- Treat the target as arranged music across time. There should be distinct musical events or motif mutations after the first second, preferably near seconds 2, 4, 6, and 8 for ten-second clips.
Generalization: Generalization:
- You own $SongsPerAgent target patches, and your report must compare all assigned targets and state what reusable scene/instrument knowledge transfers between them. - You own $SongsPerAgent target patches, and your report must compare all assigned targets and state what reusable scene/instrument knowledge transfers between them.
- The patches may share instrument roles, texture roles, control idioms, and DSL conventions, but each patch should fit its own target's tempo/register/spectral evidence. - The patches may share instrument roles, texture roles, control idioms, and DSL conventions, but each patch should fit its own target's tempo/register/spectral evidence.
@ -446,6 +451,7 @@ Reusable abstraction mining:
- `keep/cut verdict`: whether this abstraction should be mined into DSL sugar, kept as a stock patch idiom, or discarded. - `keep/cut verdict`: whether this abstraction should be mined into DSL sugar, kept as a stock patch idiom, or discarded.
- Prefer abstractions that transfer across multiple assigned songs. A one-off trick is allowed only if you label it as target-specific and say why it should not become syntax. - Prefer abstractions that transfer across multiple assigned songs. A one-off trick is allowed only if you label it as target-specific and say why it should not become syntax.
- Do not propose sugar that hides ownership. The future shorthand must still lower into visible syrinx, subtractive, additive/PAD, texture, pattern, scale, or curve owners. - Do not propose sugar that hides ownership. The future shorthand must still lower into visible syrinx, subtractive, additive/PAD, texture, pattern, scale, or curve owners.
- Composition abstractions are now first-class sugar candidates. Prefer reusable names like `metered_progression`, `hook_degrees`, `lane_sequence`, `section_rise`, `drop_fill`, `mix_scene`, or better target-grounded names. Each one must say which `meter`, `sequence`, `chords`, `scale`, `mix`, `param`, and `curve` lines it lowers into.
Noise and texture: Noise and texture:
- Do not model background or recording color as a full-duration raw `voice wave=noise` bed. That produces static white-noise wash and will be treated as broken subtractive synthesis, not scene modeling. - Do not model background or recording color as a full-duration raw `voice wave=noise` bed. That produces static white-noise wash and will be treated as broken subtractive synthesis, not scene modeling.
@ -472,6 +478,14 @@ Rhythm and tempo:
- bind parameters into voices or primitives, for example `gain=@/seq/kick`, `tremolo_hz=<tempo_bpm/60>`, `noise=@/seq/hiss`, `freq=@/seq/pitch`. - bind parameters into voices or primitives, for example `gain=@/seq/kick`, `tremolo_hz=<tempo_bpm/60>`, `noise=@/seq/hiss`, `freq=@/seq/pitch`.
- Do not invent unimplemented syntax like `s("bd sn")` inside the `.aqua` candidate. You may propose it in `instrument-conventions.md`. - Do not invent unimplemented syntax like `s("bd sn")` inside the `.aqua` candidate. You may propose it in `instrument-conventions.md`.
Composition sugar available today:
- Set the meter before sequencing: `meter bpm=<tempo_bpm> beats=4`.
- Sequence any instrument lane without hand-declaring the gate parameter: `sequence name=kick pattern=x..x step=<beat_seconds> high=.8 low=0`, then bind `gain=@/seq/kick`.
- Declare chord/progression control lanes for pads or bass: `chords name=prog root=<root_hz> scale=minor progression=0,5,3,4 voicing=0,2,4 paths=/chords/prog/root,/chords/prog/third,/chords/prog/fifth step=bar`, then bind voices to those paths.
- Use `scale` for sampled note degrees inside the register: `scale name=hook path=/seq/pitch root=<root_hz> scale=minor degrees=0,2,4,2,5,4 step=<beat_seconds/2>`.
- Use `mix` for composition-scale lane motion: `mix name=pad points=0:.12,2:.18,4:.08,6:.24,8:.16`, then bind `gain=@/mix/pad/gain`.
- These are Strudel-ish authoring conveniences, but the lowered truth remains inspectable parameters and curves.
Register and scale: Register and scale:
- The challenge report includes `dominant_hz`, `register_low_hz`, `register_high_hz`, `root_note`, `suggested_scale`, and `scale_frequencies_hz`. - The challenge report includes `dominant_hz`, `register_low_hz`, `register_high_hz`, `root_note`, `suggested_scale`, and `scale_frequencies_hz`.
- Songs tend to pick a register and stay there. Treat those bounds as the default melodic playground unless your hypothesis explicitly tests octave spread. - Songs tend to pick a register and stay there. Treat those bounds as the default melodic playground unless your hypothesis explicitly tests octave spread.
@ -490,11 +504,13 @@ Instrument and DSL convention invention:
- any Strudel-like sugar that would make the patch shorter; - any Strudel-like sugar that would make the patch shorter;
- the exact lowering into today's AquaSynth syntax using explicit `voice`, `texture`, `pattern`, `scale`, `param`, and `curve` lines. - the exact lowering into today's AquaSynth syntax using explicit `voice`, `texture`, `pattern`, `scale`, `param`, and `curve` lines.
- 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. - 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. - 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.
Evidence contract: Evidence contract:
- Read every assigned challenge report and $preEvidence. - Read every assigned challenge report and $preEvidence.
- 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 both targets, known risks. - 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 both 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.
- Write exactly one `.aqua` file under each target patch output directory named `<agent-id>__<family>__<hypothesis>.aqua`. - 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. - 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 ten-second clip must have full-form continuity: at least four 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 nine seconds of gently textured pink noise is mode collapse and should not be submitted. - The ten-second clip must have full-form continuity: at least four 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 nine seconds of gently textured pink noise is mode collapse and should not be submitted.