diff --git a/docs/audio-doctrine.md b/docs/audio-doctrine.md index e69b8ac..e461cf3 100644 --- a/docs/audio-doctrine.md +++ b/docs/audio-doctrine.md @@ -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 diff --git a/src/AquaSynth.Core/PatchScript.cs b/src/AquaSynth.Core/PatchScript.cs index fee8c50..b403685 100644 --- a/src/AquaSynth.Core/PatchScript.cs +++ b/src/AquaSynth.Core/PatchScript.cs @@ -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 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 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 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 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 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 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 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 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 ScaleIntervals(string scale) => scale.ToLowerInvariant() switch { "major" or "ionian" => [0, 2, 4, 5, 7, 9, 11, 12], diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 6f65ed1..dfedb5b 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -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."} diff --git a/state/scratch.md b/state/scratch.md index 3260f41..9dc3909 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -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 diff --git a/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs b/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs index 8f838a3..25b25d0 100644 --- a/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs +++ b/tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs @@ -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() { diff --git a/tools/IpaTrialWorker/Program.cs b/tools/IpaTrialWorker/Program.cs index cae3783..7837e26 100644 --- a/tools/IpaTrialWorker/Program.cs +++ b/tools/IpaTrialWorker/Program.cs @@ -1699,6 +1699,7 @@ static IEnumerable 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 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 BuildMusicKnowledgeDocument static IEnumerable MusicRoleDocuments(IReadOnlyList 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", diff --git a/tools/run-song-snippet-trial-swarm.ps1 b/tools/run-song-snippet-trial-swarm.ps1 index fae5479..3ac1b97 100644 --- a/tools/run-song-snippet-trial-swarm.ps1 +++ b/tools/run-song-snippet-trial-swarm.ps1 @@ -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=`, `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= beats=4`. +- Sequence any instrument lane without hand-declaring the gate parameter: `sequence name=kick pattern=x..x step= high=.8 low=0`, then bind `gain=@/seq/kick`. +- Declare chord/progression control lanes for pads or bass: `chords name=prog root= 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= scale=minor degrees=0,2,4,2,5,4 step=`. +- 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 `____.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.