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
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
leads should prefer syrinx/acoustic source ownership with pressure/opening
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 float Gain { get; set; } = 1;
private bool SoftClip { get; set; } = true;
private float BeatSeconds { get; set; } = 0.5f;
private int BeatsPerBar { get; set; } = 4;
public SynthPatch Build()
{
@ -331,6 +333,22 @@ public static class PatchScript
FlushPendingOperatorGraph();
AddScaleCurve(fields, line);
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":
FlushPendingOperatorGraph();
AddControlSpline(fields, line);
@ -2570,7 +2588,7 @@ public static class PatchScript
private void AddScaleCurve(IReadOnlyDictionary<string, string> fields, int 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)
{
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);
}
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)
{
var name = SafeName(GetAny(fields, ["name", "n"], $"texture_{Voices.Count}"));
@ -3388,6 +3508,31 @@ public static class PatchScript
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(
int Line,
string Path,
@ -3517,8 +3662,12 @@ public static class PatchScript
"param" or "parameter" => "param",
"control_surface" or "surface" or "controlsurface" => "control_surface",
"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",
"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",
"phoneme_gesture" or "phone_gesture" or "ipa_gesture" or "phoneme" or "phone" => "phoneme_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();
}
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
{
"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":"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"}
{"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
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
`artifacts/parity/song-snippet-swarms/20260531T171802519`: each pass prepared
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);
}
[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]
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.",
"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.",
"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."
@ -1707,12 +1708,14 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
"voice-like lead -> syrinx/acoustic source ports with pressure, opening, and radiation motion",
"drums/transients -> subtractive body plus filtered noise skin plus pattern gate",
"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.",
"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(),
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)
{
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(
"music-role-syrinx-voice",
"syrinx/acoustic voice lead",

View file

@ -417,6 +417,11 @@ $agentSeedList
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.
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:
- 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.
@ -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.
- 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.
- 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:
- 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`.
- 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:
- 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.
@ -490,11 +504,13 @@ Instrument and DSL convention invention:
- 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 `.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.
Evidence contract:
- 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.
- 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`.
- 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.