Score song continuity collapse risk
This commit is contained in:
parent
f11d5ce070
commit
5e0ad2ddd0
5 changed files with 367 additions and 24 deletions
|
|
@ -287,11 +287,13 @@ static async Task SongScoreAsync(Dictionary<string, string> options)
|
|||
analyzer);
|
||||
artifacts.AddRange(candidateAnalysisArtifacts.Select(ArtifactFromAnalysis));
|
||||
var comparison = analyzer.Compare(referenceSamples, matched);
|
||||
var continuity = AnalyzeSongContinuity(comparison);
|
||||
metrics.AddRange(SongComparisonMetrics(comparison));
|
||||
verdict = SongVerdict(comparison, instrumentProfile);
|
||||
evaluation = SongEvaluationSentence(challenge, comparison, verdict, instrumentProfile);
|
||||
metrics.AddRange(SongContinuityMetrics(continuity));
|
||||
verdict = SongVerdict(comparison, instrumentProfile, continuity);
|
||||
evaluation = SongEvaluationSentence(challenge, comparison, verdict, instrumentProfile, continuity);
|
||||
var comparisonPath = Path.Combine(audioDir, "comparison.txt");
|
||||
await File.WriteAllTextAsync(comparisonPath, SongComparisonReport(challenge, candidate, comparison, verdict, instrumentProfile), Encoding.UTF8);
|
||||
await File.WriteAllTextAsync(comparisonPath, SongComparisonReport(challenge, candidate, comparison, verdict, instrumentProfile, continuity), Encoding.UTF8);
|
||||
artifacts.Add(Artifact("comparison-report", comparisonPath));
|
||||
}
|
||||
}
|
||||
|
|
@ -1685,6 +1687,7 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
|
|||
.OrderByDescending(row => MusicSignalScore(row))
|
||||
.ToArray();
|
||||
var failed = rows.Where(row => row.Verdict.Equals("render-failed", StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
var collapsed = rows.Where(row => row.ModeCollapseRisk >= .45f).ToArray();
|
||||
|
||||
yield return new MusicProductionKnowledgeDocument(
|
||||
"music-production-quality-standard-v1",
|
||||
|
|
@ -1695,6 +1698,7 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
|
|||
"Fresh agents should retrieve role owners, transfer rules, failure modes, and AquaSynth patterns before writing patches. New runs must add compact, evidence-backed documents, not whole spectrogram dumps or undigested trial chatter.",
|
||||
[
|
||||
"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.",
|
||||
"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,14 +1711,16 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
|
|||
],
|
||||
[
|
||||
"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."
|
||||
],
|
||||
rows.Select(row => row.TrialId).Take(24).ToArray(),
|
||||
rendered.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(24).ToArray(),
|
||||
[
|
||||
new SpeechScoreMetric("input_trial_count", rows.Length, 0),
|
||||
new SpeechScoreMetric("rendered_trial_count", rendered.Length, 0),
|
||||
new SpeechScoreMetric("render_failed_count", failed.Length, 0)
|
||||
new SpeechScoreMetric("render_failed_count", failed.Length, 0),
|
||||
new SpeechScoreMetric("mode_collapsed_count", collapsed.Length, 0)
|
||||
],
|
||||
[artifactRoot],
|
||||
created);
|
||||
|
|
@ -1740,7 +1746,7 @@ static IEnumerable<MusicProductionKnowledgeDocument> BuildMusicKnowledgeDocument
|
|||
$"music-candidate-{SafeName(row.CandidateId)}-{StableUuid(row.TrialId)[..8]}",
|
||||
"candidate-pattern",
|
||||
row.CandidateId,
|
||||
row.LogMelCosine >= .12f || row.AudioScore >= .22f ? "strong-pressure" : "weak-pressure",
|
||||
row.ModeCollapseRisk >= .45f ? "failure-pressure" : row.LogMelCosine >= .12f || row.AudioScore >= .22f ? "strong-pressure" : "weak-pressure",
|
||||
$"Candidate `{row.CandidateId}` owns a reusable rendered song-pattern witness for `{row.ReferenceId}`.",
|
||||
CandidateKnowledgeSummary(row, profile, analysisExcerpt),
|
||||
CandidateTransferRules(row, profile),
|
||||
|
|
@ -1848,6 +1854,7 @@ static IEnumerable<MusicProductionKnowledgeDocument> MusicFailureDocuments(
|
|||
string created,
|
||||
string artifactRoot)
|
||||
{
|
||||
var collapsed = all.Where(row => row.ModeCollapseRisk >= .45f).ToArray();
|
||||
yield return new MusicProductionKnowledgeDocument(
|
||||
"music-failure-rendered-syntax-pressure",
|
||||
"failure-mode",
|
||||
|
|
@ -1870,6 +1877,40 @@ static IEnumerable<MusicProductionKnowledgeDocument> MusicFailureDocuments(
|
|||
],
|
||||
[artifactRoot],
|
||||
created);
|
||||
|
||||
if (collapsed.Length == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new MusicProductionKnowledgeDocument(
|
||||
"music-failure-mode-collapse-pressure",
|
||||
"failure-mode",
|
||||
"one-phrase-plus-texture candidates are not song exemplars",
|
||||
"failure-pressure",
|
||||
"The mode-collapse failure record owns evidence that a rendered candidate can satisfy static audio metrics while failing song continuity.",
|
||||
$"This run produced {collapsed.Length} official candidates with mode-collapse risk >= 0.45. These candidates transfer only as negative pressure unless their abstractions explicitly add later motifs, section changes, or eventful motion.",
|
||||
[
|
||||
"Do not promote candidates that spend most musical energy in the first second and coast on low-motion texture.",
|
||||
"Require motif mutations or distinct musical events after seconds 2, 4, 6, and 8 for ten-second curriculum clips.",
|
||||
"Use motion coverage and first-second energy share beside cosine; similarity to a moment is not similarity to the song."
|
||||
],
|
||||
["mode_collapse_risk", "candidate_motion_coverage", "candidate_first_second_energy_share", "motif mutation", "section event"],
|
||||
[
|
||||
"front-loaded musical energy",
|
||||
"low-motion pink-noise or pad tail",
|
||||
"high cosine that copies only the opening phrase"
|
||||
],
|
||||
collapsed.Select(row => row.TrialId).Take(20).ToArray(),
|
||||
collapsed.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(20).ToArray(),
|
||||
[
|
||||
new SpeechScoreMetric("mode_collapsed_count", collapsed.Length, 0),
|
||||
new SpeechScoreMetric("input_trial_count", all.Count, 0),
|
||||
new SpeechScoreMetric("max_mode_collapse_risk", collapsed.Select(row => row.ModeCollapseRisk).DefaultIfEmpty(0).Max(), 0),
|
||||
new SpeechScoreMetric("mean_first_second_energy_share", collapsed.Select(row => row.CandidateFirstSecondEnergyShare).DefaultIfEmpty(0).Average(), 0)
|
||||
],
|
||||
ExistingPaths(collapsed.Select(row => row.SummaryPath).Concat([artifactRoot]).Take(24)),
|
||||
created);
|
||||
}
|
||||
|
||||
static IEnumerable<MusicProductionKnowledgeDocument> MusicAbstractionLedgerDocuments(string artifactRoot, string created)
|
||||
|
|
@ -2060,7 +2101,7 @@ static IEnumerable<SongSummaryRow> ReadSongSummaryRows(string path)
|
|||
return column >= 0 && column < parts.Length ? parts[column] : "";
|
||||
}
|
||||
|
||||
float Float(string name) => float.TryParse(Value(name), NumberStyles.Float, CultureInfo.InvariantCulture, out var value) ? value : 0;
|
||||
float Float(string name, float fallback = 0) => float.TryParse(Value(name), NumberStyles.Float, CultureInfo.InvariantCulture, out var value) ? value : fallback;
|
||||
yield return new SongSummaryRow(
|
||||
Value("trial_id"),
|
||||
Value("candidate_id"),
|
||||
|
|
@ -2079,6 +2120,15 @@ static IEnumerable<SongSummaryRow> ReadSongSummaryRows(string path)
|
|||
Float("instrument_voice_syrinx"),
|
||||
Float("instrument_drum_subtractive"),
|
||||
Float("instrument_pad_additive"),
|
||||
Float("candidate_active_coverage", 1),
|
||||
Float("active_coverage_ratio", 1),
|
||||
Float("candidate_motion_coverage", .75f),
|
||||
Float("motion_coverage_ratio", 1),
|
||||
Float("candidate_first_second_energy_share"),
|
||||
Float("first_second_energy_excess"),
|
||||
Float("candidate_tail_energy_share", .2f),
|
||||
Float("tail_energy_ratio", 1),
|
||||
Float("mode_collapse_risk"),
|
||||
path);
|
||||
}
|
||||
}
|
||||
|
|
@ -2088,6 +2138,8 @@ static float MusicSignalScore(SongSummaryRow row) =>
|
|||
+ row.AudioScore
|
||||
+ row.MusicalInstrumentScore * .35f
|
||||
- row.ChipDistressRisk * .5f
|
||||
- row.ModeCollapseRisk * .9f
|
||||
- MathF.Max(0, .55f - row.CandidateMotionCoverage) * .35f
|
||||
- (row.Verdict.Equals("render-failed", StringComparison.OrdinalIgnoreCase) ? 100f : 0f);
|
||||
|
||||
static bool IsOfficialMusicArtifactPath(string path)
|
||||
|
|
@ -2118,13 +2170,22 @@ static SpeechScoreMetric[] RowMetrics(SongSummaryRow row) =>
|
|||
new("articulation_score", row.ArticulationScore, 1),
|
||||
new("musical_instrument_score", row.MusicalInstrumentScore, 1),
|
||||
new("chip_distress_risk", row.ChipDistressRisk, 1),
|
||||
new("candidate_active_coverage", row.CandidateActiveCoverage, 1),
|
||||
new("active_coverage_ratio", row.ActiveCoverageRatio, 1),
|
||||
new("candidate_motion_coverage", row.CandidateMotionCoverage, 1),
|
||||
new("motion_coverage_ratio", row.MotionCoverageRatio, 1),
|
||||
new("candidate_first_second_energy_share", row.CandidateFirstSecondEnergyShare, 1),
|
||||
new("first_second_energy_excess", row.FirstSecondEnergyExcess, 1),
|
||||
new("candidate_tail_energy_share", row.CandidateTailEnergyShare, 1),
|
||||
new("tail_energy_ratio", row.TailEnergyRatio, 1),
|
||||
new("mode_collapse_risk", row.ModeCollapseRisk, 1),
|
||||
new("rms_ratio", row.RmsRatio, 0),
|
||||
new("centroid_ratio", row.CentroidRatio, 0),
|
||||
new("zero_crossing_ratio", row.ZeroCrossingRatio, 0)
|
||||
];
|
||||
|
||||
static string CandidateKnowledgeSummary(SongSummaryRow row, SongInstrumentProfile profile, string analysisExcerpt) =>
|
||||
$"Rendered candidate `{row.CandidateId}` against `{row.ReferenceId}` with cosine {row.LogMelCosine:0.######}, score {row.AudioScore:0.######}, instrument score {row.MusicalInstrumentScore:0.###}, chip risk {row.ChipDistressRisk:0.###}. Instrument profile: {profile.Summary}. Candidate analysis excerpt: {TrimForReport(analysisExcerpt, 900)}";
|
||||
$"Rendered candidate `{row.CandidateId}` against `{row.ReferenceId}` with cosine {row.LogMelCosine:0.######}, score {row.AudioScore:0.######}, instrument score {row.MusicalInstrumentScore:0.###}, chip risk {row.ChipDistressRisk:0.###}, motion coverage {row.CandidateMotionCoverage:0.###}, first-second energy share {row.CandidateFirstSecondEnergyShare:0.###}, collapse risk {row.ModeCollapseRisk:0.###}. Instrument profile: {profile.Summary}. Candidate analysis excerpt: {TrimForReport(analysisExcerpt, 900)}";
|
||||
|
||||
static string[] CandidateTransferRules(SongSummaryRow row, SongInstrumentProfile profile)
|
||||
{
|
||||
|
|
@ -2134,6 +2195,7 @@ static string[] CandidateTransferRules(SongSummaryRow row, SongInstrumentProfile
|
|||
if (profile.HasAdditivePad) rules.Add("Transfer the bed role as additive/PAD harmonic material with slow control motion.");
|
||||
if (profile.HasTexture) rules.Add("Transfer texture as a shaped role with band limits, gates, and motion.");
|
||||
if (row.RmsRatio < .35f) rules.Add("Candidate is quiet against the reference; normalize loudness after role selection.");
|
||||
if (row.ModeCollapseRisk >= .45f) rules.Add("Do not transfer the arrangement as-is: it concentrates too much musical action near the opening and must be rewritten with distributed sections.");
|
||||
if (rules.Count == 0) rules.Add("Use as weak pressure only; demand a clearer role owner before promoting this pattern.");
|
||||
return rules.ToArray();
|
||||
}
|
||||
|
|
@ -2153,6 +2215,9 @@ static string[] CandidateFailureModes(SongSummaryRow row)
|
|||
if (row.RmsRatio < .35f) modes.Add("under-loud candidate can hide arrangement ideas behind weak level");
|
||||
if (row.ZeroCrossingRatio > 8f) modes.Add("excess zero-crossing ratio suggests noisy or too-bright material");
|
||||
if (row.ChipDistressRisk > 0) modes.Add("chip-distress risk must be justified as deliberate style before transfer");
|
||||
if (row.ModeCollapseRisk >= .45f) modes.Add("mode collapse: short opening phrase followed by low-motion texture or noise");
|
||||
if (row.CandidateFirstSecondEnergyShare >= .35f) modes.Add("front-loaded energy; require later motifs or section changes before promotion");
|
||||
if (row.CandidateMotionCoverage > 0 && row.CandidateMotionCoverage < .45f) modes.Add("low musical motion coverage across the ten-second target");
|
||||
return modes.Count == 0 ? ["still pressure, not mastered parity"] : modes.ToArray();
|
||||
}
|
||||
|
||||
|
|
@ -2470,6 +2535,11 @@ static IEnumerable<string> TransferRulesFrom(IReadOnlyList<IpaTrialResult> selec
|
|||
yield return "Normalize loudness after role selection; high cosine can still hide bad RMS balance.";
|
||||
}
|
||||
|
||||
if (selected.Any(result => MetricValue(result, "mode_collapse_risk") >= .45f))
|
||||
{
|
||||
yield return "Reject one-phrase-plus-texture arrangements as reusable song knowledge; distribute motifs, rhythmic events, and harmonic motion across the full clip.";
|
||||
}
|
||||
|
||||
yield return "Use `texture` for background/recording noise and reserve raw `wave=noise` for short gated transients with narrow filters.";
|
||||
yield return "When the scene wants a singing or creature voice, start with a syrinx/acoustic voice role and modulate pressure/opening/radiation before falling back to ordinary formant oscillators.";
|
||||
yield return "When the scene wants a bed or pad, start with additive/PAD banks (`layer`, `harmonics`, `spectrum`) instead of stacked simple waves.";
|
||||
|
|
@ -2504,6 +2574,12 @@ static IEnumerable<string> FailurePatternsFrom(IReadOnlyList<IpaTrialResult> all
|
|||
{
|
||||
yield return "Low instrument-role coverage is a scoring smell: require at least one owned role such as syrinx voice, subtractive drums, additive/PAD bed, or shaped texture.";
|
||||
}
|
||||
|
||||
var collapsed = allResults.Count(result => MetricValue(result, "mode_collapse_risk") >= .45f);
|
||||
if (collapsed > 0)
|
||||
{
|
||||
yield return $"{collapsed} trials show mode-collapse risk: a short opening phrase followed by low-motion texture/noise. Keep them as negative pressure unless later sections carry motifs or eventful motion.";
|
||||
}
|
||||
}
|
||||
|
||||
static float ScoreSort(IpaTrialResult result)
|
||||
|
|
@ -2511,7 +2587,9 @@ static float ScoreSort(IpaTrialResult result)
|
|||
var cosine = MetricValue(result, "log_mel_cosine");
|
||||
var instrument = MetricValue(result, "musical_instrument_score");
|
||||
var chipRisk = MetricValue(result, "chip_distress_risk");
|
||||
return cosine + instrument * .10f - chipRisk * .15f;
|
||||
var collapseRisk = MetricValue(result, "mode_collapse_risk");
|
||||
var motionCoverage = MetricValue(result, "candidate_motion_coverage");
|
||||
return cosine + instrument * .10f + motionCoverage * .08f - chipRisk * .15f - collapseRisk * .25f;
|
||||
}
|
||||
|
||||
static string Metric(IpaTrialResult result, string name) =>
|
||||
|
|
@ -3361,6 +3439,22 @@ static IReadOnlyList<SpeechScoreMetric> SongComparisonMetrics(AudioComparison co
|
|||
new("speech_band_ratio", comparison.Articulation.SpeechBandRatio, .05f)
|
||||
];
|
||||
|
||||
static IReadOnlyList<SpeechScoreMetric> SongContinuityMetrics(SongContinuityProfile profile) =>
|
||||
[
|
||||
new("candidate_active_coverage", profile.CandidateActiveCoverage, .08f),
|
||||
new("target_active_coverage", profile.TargetActiveCoverage, 0),
|
||||
new("active_coverage_ratio", profile.ActiveCoverageRatio, .08f),
|
||||
new("candidate_motion_coverage", profile.CandidateMotionCoverage, .12f),
|
||||
new("target_motion_coverage", profile.TargetMotionCoverage, 0),
|
||||
new("motion_coverage_ratio", profile.MotionCoverageRatio, .12f),
|
||||
new("candidate_first_second_energy_share", profile.CandidateFirstSecondEnergyShare, .10f),
|
||||
new("target_first_second_energy_share", profile.TargetFirstSecondEnergyShare, 0),
|
||||
new("first_second_energy_excess", profile.FirstSecondEnergyExcess, .10f),
|
||||
new("candidate_tail_energy_share", profile.CandidateTailEnergyShare, .05f),
|
||||
new("tail_energy_ratio", profile.TailEnergyRatio, .05f),
|
||||
new("mode_collapse_risk", profile.ModeCollapseRisk, .15f)
|
||||
];
|
||||
|
||||
static IReadOnlyList<SpeechScoreMetric> SongInstrumentMetrics(SongInstrumentProfile profile) =>
|
||||
[
|
||||
new("instrument_voice_syrinx", profile.HasSyrinxVoice ? 1 : 0, .05f),
|
||||
|
|
@ -3436,8 +3530,13 @@ static int CountOccurrences(string haystack, string needle)
|
|||
return count;
|
||||
}
|
||||
|
||||
static string SongVerdict(AudioComparison comparison, SongInstrumentProfile profile)
|
||||
static string SongVerdict(AudioComparison comparison, SongInstrumentProfile profile, SongContinuityProfile continuity)
|
||||
{
|
||||
if (continuity.ModeCollapseRisk >= .60f)
|
||||
{
|
||||
return "weak-mode-collapse";
|
||||
}
|
||||
|
||||
if (profile.ChipDistressRisk >= .75f && comparison.LogMelCosineSimilarity < .60f)
|
||||
{
|
||||
return "weak";
|
||||
|
|
@ -3456,6 +3555,179 @@ static string SongVerdict(AudioComparison comparison, SongInstrumentProfile prof
|
|||
return "weak";
|
||||
}
|
||||
|
||||
static SongContinuityProfile AnalyzeSongContinuity(AudioComparison comparison)
|
||||
{
|
||||
var durationSeconds = Math.Max(1e-3f, comparison.Candidate.Features.DurationSeconds);
|
||||
var targetActive = ActiveCoverage(comparison.Reference.RmsEnvelope);
|
||||
var candidateActive = ActiveCoverage(comparison.Candidate.RmsEnvelope);
|
||||
var targetMotion = MotionCoverage(comparison.Reference.RmsEnvelope, comparison.Reference.LogMelSpectrogram);
|
||||
var candidateMotion = MotionCoverage(comparison.Candidate.RmsEnvelope, comparison.Candidate.LogMelSpectrogram);
|
||||
var targetFirst = EnergyShare(comparison.Reference.RmsEnvelope, comparison.Reference.Features.DurationSeconds, 0, 1);
|
||||
var candidateFirst = EnergyShare(comparison.Candidate.RmsEnvelope, durationSeconds, 0, 1);
|
||||
var targetTail = EnergyShare(comparison.Reference.RmsEnvelope, comparison.Reference.Features.DurationSeconds, MathF.Min(1, comparison.Reference.Features.DurationSeconds * .20f), comparison.Reference.Features.DurationSeconds);
|
||||
var candidateTail = EnergyShare(comparison.Candidate.RmsEnvelope, durationSeconds, MathF.Min(1, durationSeconds * .20f), durationSeconds);
|
||||
var activeRatio = SafeMetricRatio(candidateActive, targetActive);
|
||||
var motionRatio = SafeMetricRatio(candidateMotion, targetMotion);
|
||||
var firstExcess = MathF.Max(0, candidateFirst - MathF.Max(.20f, targetFirst * 1.6f));
|
||||
var tailRatio = SafeMetricRatio(candidateTail, targetTail);
|
||||
var collapseRisk = Math.Clamp(
|
||||
firstExcess * 1.6f +
|
||||
MathF.Max(0, .72f - candidateMotion) * .85f +
|
||||
MathF.Max(0, .65f - motionRatio) * .55f +
|
||||
MathF.Max(0, .55f - candidateActive) * .40f +
|
||||
MathF.Max(0, .50f - tailRatio) * .25f,
|
||||
0,
|
||||
1);
|
||||
return new SongContinuityProfile(
|
||||
targetActive,
|
||||
candidateActive,
|
||||
activeRatio,
|
||||
targetMotion,
|
||||
candidateMotion,
|
||||
motionRatio,
|
||||
targetFirst,
|
||||
candidateFirst,
|
||||
firstExcess,
|
||||
candidateTail,
|
||||
tailRatio,
|
||||
collapseRisk);
|
||||
}
|
||||
|
||||
static float ActiveCoverage(IReadOnlyList<float> envelope)
|
||||
{
|
||||
if (envelope.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var peak = envelope.Max();
|
||||
var threshold = Math.Max(peak * .08f, 1e-5f);
|
||||
return envelope.Count(value => value >= threshold) / (float)envelope.Count;
|
||||
}
|
||||
|
||||
static float MotionCoverage(IReadOnlyList<float> envelope, Spectrogram spectrogram)
|
||||
{
|
||||
var envelopeFlux = FrameFlux(envelope);
|
||||
var spectralFlux = SpectralFluxSeries(spectrogram);
|
||||
var frames = Math.Max(envelopeFlux.Length, spectralFlux.Length);
|
||||
if (frames == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var envelopeThreshold = AdaptiveFluxThreshold(envelopeFlux);
|
||||
var spectralThreshold = AdaptiveFluxThreshold(spectralFlux);
|
||||
var moving = 0;
|
||||
for (var index = 0; index < frames; index++)
|
||||
{
|
||||
var env = ResampledAt(envelopeFlux, index, frames);
|
||||
var spec = ResampledAt(spectralFlux, index, frames);
|
||||
if (env >= envelopeThreshold || spec >= spectralThreshold)
|
||||
{
|
||||
moving++;
|
||||
}
|
||||
}
|
||||
|
||||
return moving / (float)frames;
|
||||
}
|
||||
|
||||
static float[] FrameFlux(IReadOnlyList<float> values)
|
||||
{
|
||||
if (values.Count < 2)
|
||||
{
|
||||
return [0];
|
||||
}
|
||||
|
||||
var flux = new float[values.Count - 1];
|
||||
for (var index = 1; index < values.Count; index++)
|
||||
{
|
||||
flux[index - 1] = MathF.Abs(values[index] - values[index - 1]);
|
||||
}
|
||||
|
||||
return flux;
|
||||
}
|
||||
|
||||
static float[] SpectralFluxSeries(Spectrogram spectrogram)
|
||||
{
|
||||
if (spectrogram.Frames < 2)
|
||||
{
|
||||
return [0];
|
||||
}
|
||||
|
||||
var flux = new float[spectrogram.Frames - 1];
|
||||
for (var frame = 1; frame < spectrogram.Frames; frame++)
|
||||
{
|
||||
var sum = 0f;
|
||||
for (var band = 0; band < spectrogram.Bands; band++)
|
||||
{
|
||||
sum += MathF.Abs(spectrogram.At(frame, band) - spectrogram.At(frame - 1, band));
|
||||
}
|
||||
|
||||
flux[frame - 1] = sum / Math.Max(1, spectrogram.Bands);
|
||||
}
|
||||
|
||||
return flux;
|
||||
}
|
||||
|
||||
static float AdaptiveFluxThreshold(IReadOnlyList<float> values)
|
||||
{
|
||||
if (values.Count == 0)
|
||||
{
|
||||
return float.PositiveInfinity;
|
||||
}
|
||||
|
||||
var sorted = values.Order().ToArray();
|
||||
var median = sorted[sorted.Length / 2];
|
||||
var high = sorted[Math.Clamp((int)MathF.Round((sorted.Length - 1) * .75f), 0, sorted.Length - 1)];
|
||||
return Math.Max(1e-6f, median + (high - median) * .35f);
|
||||
}
|
||||
|
||||
static float EnergyShare(IReadOnlyList<float> envelope, float durationSeconds, float startSeconds, float endSeconds)
|
||||
{
|
||||
if (envelope.Count == 0 || durationSeconds <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var total = envelope.Sum(value => value * value);
|
||||
if (total <= float.Epsilon)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var start = Math.Clamp((int)MathF.Floor(startSeconds / durationSeconds * envelope.Count), 0, envelope.Count - 1);
|
||||
var end = Math.Clamp((int)MathF.Ceiling(endSeconds / durationSeconds * envelope.Count), start + 1, envelope.Count);
|
||||
var segment = 0f;
|
||||
for (var index = start; index < end; index++)
|
||||
{
|
||||
segment += envelope[index] * envelope[index];
|
||||
}
|
||||
|
||||
return segment / total;
|
||||
}
|
||||
|
||||
static float SafeMetricRatio(float candidate, float reference) =>
|
||||
reference <= float.Epsilon ? (candidate <= float.Epsilon ? 1 : 10) : candidate / reference;
|
||||
|
||||
static float ResampledAt(IReadOnlyList<float> values, int index, int outputLength)
|
||||
{
|
||||
if (values.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (values.Count == 1 || outputLength <= 1)
|
||||
{
|
||||
return values[0];
|
||||
}
|
||||
|
||||
var position = index / (float)(outputLength - 1) * (values.Count - 1);
|
||||
var left = Math.Clamp((int)MathF.Floor(position), 0, values.Count - 1);
|
||||
var right = Math.Clamp(left + 1, 0, values.Count - 1);
|
||||
var t = position - left;
|
||||
return values[left] * (1 - t) + values[right] * t;
|
||||
}
|
||||
|
||||
static float SongConfidence(IReadOnlyList<SpeechScoreMetric> metrics)
|
||||
{
|
||||
var cosine = metrics.FirstOrDefault(metric => metric.Name == "log_mel_cosine")?.Value ?? 0;
|
||||
|
|
@ -3463,8 +3735,8 @@ static float SongConfidence(IReadOnlyList<SpeechScoreMetric> metrics)
|
|||
return Math.Clamp((cosine + score) * .5f, 0, 1);
|
||||
}
|
||||
|
||||
static string SongEvaluationSentence(SongChallenge challenge, AudioComparison comparison, string verdict, SongInstrumentProfile profile) =>
|
||||
$"Song snippet `{challenge.ChallengeId}` is `{verdict}`: logMelCosine={comparison.LogMelCosineSimilarity:0.0000}, logMelDistance={comparison.LogMelDistance:0.0000}, score={comparison.Score:0.0000}, rmsRatio={comparison.RmsRatio:0.0000}, centroidRatio={comparison.CentroidRatio:0.0000}. Instrument profile: {profile.Summary}.";
|
||||
static string SongEvaluationSentence(SongChallenge challenge, AudioComparison comparison, string verdict, SongInstrumentProfile profile, SongContinuityProfile continuity) =>
|
||||
$"Song snippet `{challenge.ChallengeId}` is `{verdict}`: logMelCosine={comparison.LogMelCosineSimilarity:0.0000}, logMelDistance={comparison.LogMelDistance:0.0000}, score={comparison.Score:0.0000}, rmsRatio={comparison.RmsRatio:0.0000}, centroidRatio={comparison.CentroidRatio:0.0000}, activeCoverage={continuity.CandidateActiveCoverage:0.0000}, motionCoverage={continuity.CandidateMotionCoverage:0.0000}, firstSecondEnergyShare={continuity.CandidateFirstSecondEnergyShare:0.0000}, modeCollapseRisk={continuity.ModeCollapseRisk:0.0000}. Instrument profile: {profile.Summary}.";
|
||||
|
||||
static string SongAnalysisReport(SongChallenge challenge, AudioAnalysis analysis)
|
||||
{
|
||||
|
|
@ -3571,7 +3843,7 @@ static string SongChallengeReport(SongChallenge challenge) =>
|
|||
whitened_spectral_autocorr_csv: `{challenge.Artifacts?.WhitenedSpectralAutocorrCsv ?? ""}`
|
||||
""";
|
||||
|
||||
static string SongComparisonReport(SongChallenge challenge, IpaTrialScriptCandidate candidate, AudioComparison comparison, string verdict, SongInstrumentProfile profile) =>
|
||||
static string SongComparisonReport(SongChallenge challenge, IpaTrialScriptCandidate candidate, AudioComparison comparison, string verdict, SongInstrumentProfile profile, SongContinuityProfile continuity) =>
|
||||
$"""
|
||||
candidate={candidate.CandidateId}
|
||||
challenge={challenge.ChallengeId}
|
||||
|
|
@ -3585,6 +3857,15 @@ static string SongComparisonReport(SongChallenge challenge, IpaTrialScriptCandid
|
|||
zeroCrossingRatio={comparison.ZeroCrossingRatio:0.######}
|
||||
articulation={comparison.Articulation.ArticulationScore:0.######}
|
||||
speechBandRatio={comparison.Articulation.SpeechBandRatio:0.######}
|
||||
candidateActiveCoverage={continuity.CandidateActiveCoverage:0.######}
|
||||
activeCoverageRatio={continuity.ActiveCoverageRatio:0.######}
|
||||
candidateMotionCoverage={continuity.CandidateMotionCoverage:0.######}
|
||||
motionCoverageRatio={continuity.MotionCoverageRatio:0.######}
|
||||
candidateFirstSecondEnergyShare={continuity.CandidateFirstSecondEnergyShare:0.######}
|
||||
firstSecondEnergyExcess={continuity.FirstSecondEnergyExcess:0.######}
|
||||
candidateTailEnergyShare={continuity.CandidateTailEnergyShare:0.######}
|
||||
tailEnergyRatio={continuity.TailEnergyRatio:0.######}
|
||||
modeCollapseRisk={continuity.ModeCollapseRisk:0.######}
|
||||
instrumentVoiceSyrinx={(profile.HasSyrinxVoice ? 1 : 0)}
|
||||
instrumentDrumSubtractive={(profile.HasSubtractiveDrums ? 1 : 0)}
|
||||
instrumentPadAdditive={(profile.HasAdditivePad ? 1 : 0)}
|
||||
|
|
@ -3596,7 +3877,7 @@ static string SongComparisonReport(SongChallenge challenge, IpaTrialScriptCandid
|
|||
static string SongSummaryCsv(IReadOnlyList<IpaTrialResult> results)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("trial_id,candidate_id,reference_id,verdict,log_mel_cosine,log_mel_distance,audio_score,envelope_distance,rms_ratio,centroid_ratio,zero_crossing_ratio,articulation_score,musical_instrument_score,chip_distress_risk,instrument_voice_syrinx,instrument_drum_subtractive,instrument_pad_additive");
|
||||
builder.AppendLine("trial_id,candidate_id,reference_id,verdict,log_mel_cosine,log_mel_distance,audio_score,envelope_distance,rms_ratio,centroid_ratio,zero_crossing_ratio,articulation_score,musical_instrument_score,chip_distress_risk,instrument_voice_syrinx,instrument_drum_subtractive,instrument_pad_additive,candidate_active_coverage,active_coverage_ratio,candidate_motion_coverage,motion_coverage_ratio,candidate_first_second_energy_share,first_second_energy_excess,candidate_tail_energy_share,tail_energy_ratio,mode_collapse_risk");
|
||||
foreach (var result in results)
|
||||
{
|
||||
builder.Append(Escape(result.TrialId)).Append(',');
|
||||
|
|
@ -3615,7 +3896,16 @@ static string SongSummaryCsv(IReadOnlyList<IpaTrialResult> results)
|
|||
builder.Append(Metric(result, "chip_distress_risk")).Append(',');
|
||||
builder.Append(Metric(result, "instrument_voice_syrinx")).Append(',');
|
||||
builder.Append(Metric(result, "instrument_drum_subtractive")).Append(',');
|
||||
builder.AppendLine(Metric(result, "instrument_pad_additive"));
|
||||
builder.Append(Metric(result, "instrument_pad_additive")).Append(',');
|
||||
builder.Append(Metric(result, "candidate_active_coverage")).Append(',');
|
||||
builder.Append(Metric(result, "active_coverage_ratio")).Append(',');
|
||||
builder.Append(Metric(result, "candidate_motion_coverage")).Append(',');
|
||||
builder.Append(Metric(result, "motion_coverage_ratio")).Append(',');
|
||||
builder.Append(Metric(result, "candidate_first_second_energy_share")).Append(',');
|
||||
builder.Append(Metric(result, "first_second_energy_excess")).Append(',');
|
||||
builder.Append(Metric(result, "candidate_tail_energy_share")).Append(',');
|
||||
builder.Append(Metric(result, "tail_energy_ratio")).Append(',');
|
||||
builder.AppendLine(Metric(result, "mode_collapse_risk"));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
|
|
@ -3628,11 +3918,11 @@ static string SongEvaluatorReport(SongChallenge challenge, IReadOnlyList<IpaTria
|
|||
builder.AppendLine();
|
||||
builder.AppendLine(SongChallengeReport(challenge));
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("| candidate | verdict | logMelCosine | score | instrument | chipRisk | envelopeDistance | rmsRatio | centroidRatio |");
|
||||
builder.AppendLine("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
||||
builder.AppendLine("| candidate | verdict | logMelCosine | score | instrument | chipRisk | motion | firstSecond | collapse | rmsRatio | centroidRatio |");
|
||||
builder.AppendLine("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
||||
foreach (var result in results.OrderByDescending(result => MetricValue(result, "log_mel_cosine")))
|
||||
{
|
||||
builder.AppendLine(CultureInfo.InvariantCulture, $"| {result.CandidateId} | {result.Verdict} | {MetricValue(result, "log_mel_cosine"):0.######} | {MetricValue(result, "audio_score"):0.######} | {MetricValue(result, "musical_instrument_score"):0.######} | {MetricValue(result, "chip_distress_risk"):0.######} | {MetricValue(result, "envelope_distance"):0.######} | {MetricValue(result, "rms_ratio"):0.######} | {MetricValue(result, "centroid_ratio"):0.######} |");
|
||||
builder.AppendLine(CultureInfo.InvariantCulture, $"| {result.CandidateId} | {result.Verdict} | {MetricValue(result, "log_mel_cosine"):0.######} | {MetricValue(result, "audio_score"):0.######} | {MetricValue(result, "musical_instrument_score"):0.######} | {MetricValue(result, "chip_distress_risk"):0.######} | {MetricValue(result, "candidate_motion_coverage"):0.######} | {MetricValue(result, "candidate_first_second_energy_share"):0.######} | {MetricValue(result, "mode_collapse_risk"):0.######} | {MetricValue(result, "rms_ratio"):0.######} | {MetricValue(result, "centroid_ratio"):0.######} |");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
|
|
@ -3828,6 +4118,20 @@ sealed record SongInstrumentProfile(
|
|||
float ChipDistressRisk,
|
||||
string Summary);
|
||||
|
||||
sealed record SongContinuityProfile(
|
||||
float TargetActiveCoverage,
|
||||
float CandidateActiveCoverage,
|
||||
float ActiveCoverageRatio,
|
||||
float TargetMotionCoverage,
|
||||
float CandidateMotionCoverage,
|
||||
float MotionCoverageRatio,
|
||||
float TargetFirstSecondEnergyShare,
|
||||
float CandidateFirstSecondEnergyShare,
|
||||
float FirstSecondEnergyExcess,
|
||||
float CandidateTailEnergyShare,
|
||||
float TailEnergyRatio,
|
||||
float ModeCollapseRisk);
|
||||
|
||||
sealed record SongSummaryRow(
|
||||
string TrialId,
|
||||
string CandidateId,
|
||||
|
|
@ -3846,6 +4150,15 @@ sealed record SongSummaryRow(
|
|||
float InstrumentVoiceSyrinx,
|
||||
float InstrumentDrumSubtractive,
|
||||
float InstrumentPadAdditive,
|
||||
float CandidateActiveCoverage,
|
||||
float ActiveCoverageRatio,
|
||||
float CandidateMotionCoverage,
|
||||
float MotionCoverageRatio,
|
||||
float CandidateFirstSecondEnergyShare,
|
||||
float FirstSecondEnergyExcess,
|
||||
float CandidateTailEnergyShare,
|
||||
float TailEnergyRatio,
|
||||
float ModeCollapseRisk,
|
||||
string SummaryPath);
|
||||
|
||||
sealed record MusicKnowledgeHit(
|
||||
|
|
|
|||
|
|
@ -172,6 +172,9 @@ function Write-PassPlaylist {
|
|||
$candidateId = $row.candidate_id
|
||||
$candidateWav = Join-Path $batchDirectory (Join-Path "song-candidates" (Join-Path $candidateId "audio/candidate.wav"))
|
||||
$referenceWav = Join-Path (Split-Path -Parent $batchDirectory) "reference.wav"
|
||||
$motionCoverage = if ([string]::IsNullOrWhiteSpace($row.candidate_motion_coverage)) { 0 } else { [double]$row.candidate_motion_coverage }
|
||||
$firstSecondEnergyShare = if ([string]::IsNullOrWhiteSpace($row.candidate_first_second_energy_share)) { 1 } else { [double]$row.candidate_first_second_energy_share }
|
||||
$modeCollapseRisk = if ([string]::IsNullOrWhiteSpace($row.mode_collapse_risk)) { 1 } else { [double]$row.mode_collapse_risk }
|
||||
$rows += [pscustomobject]@{
|
||||
CandidateId = $candidateId
|
||||
TrialId = $row.trial_id
|
||||
|
|
@ -181,6 +184,9 @@ function Write-PassPlaylist {
|
|||
AudioScore = [double]$row.audio_score
|
||||
RmsRatio = [double]$row.rms_ratio
|
||||
CentroidRatio = [double]$row.centroid_ratio
|
||||
MotionCoverage = $motionCoverage
|
||||
FirstSecondEnergyShare = $firstSecondEnergyShare
|
||||
ModeCollapseRisk = $modeCollapseRisk
|
||||
CandidateWav = $candidateWav
|
||||
ReferenceWav = $referenceWav
|
||||
SummaryPath = $summaryFile.FullName
|
||||
|
|
@ -189,7 +195,7 @@ function Write-PassPlaylist {
|
|||
}
|
||||
|
||||
$playable = @($rows | Where-Object { Test-Path -LiteralPath $_.CandidateWav })
|
||||
$ranked = @($playable | Sort-Object -Property @{ Expression = "LogMelCosine"; Descending = $true }, @{ Expression = "AudioScore"; Descending = $true } | Select-Object -First $TopCount)
|
||||
$ranked = @($playable | Sort-Object -Property @{ Expression = "ModeCollapseRisk"; Descending = $false }, @{ Expression = "MotionCoverage"; Descending = $true }, @{ Expression = "LogMelCosine"; Descending = $true }, @{ Expression = "AudioScore"; Descending = $true } | Select-Object -First $TopCount)
|
||||
$skipped = @($rows | Where-Object { -not (Test-Path -LiteralPath $_.CandidateWav) })
|
||||
$playlistPath = Join-Path $PassDirectory "top-scoring-candidates.m3u"
|
||||
$reportPath = Join-Path $PassDirectory "top-scoring-candidates.md"
|
||||
|
|
@ -203,11 +209,11 @@ function Write-PassPlaylist {
|
|||
|
||||
$rank = 1
|
||||
foreach ($row in $ranked) {
|
||||
$title = "#{0} {1} cosine={2:0.######} score={3:0.######} verdict={4}" -f $rank, $row.CandidateId, $row.LogMelCosine, $row.AudioScore, $row.Verdict
|
||||
$title = "#{0} {1} collapse={2:0.######} motion={3:0.######} cosine={4:0.######} score={5:0.######} verdict={6}" -f $rank, $row.CandidateId, $row.ModeCollapseRisk, $row.MotionCoverage, $row.LogMelCosine, $row.AudioScore, $row.Verdict
|
||||
$playlist.Add("#EXTINF:-1,$title")
|
||||
$playlist.Add($row.CandidateWav)
|
||||
|
||||
$report.Add(('- {0}. `{1}` / verdict `{2}` / cosine `{3:0.######}` / score `{4:0.######}` / rms `{5:0.######}` / centroid `{6:0.######}`' -f $rank, $row.CandidateId, $row.Verdict, $row.LogMelCosine, $row.AudioScore, $row.RmsRatio, $row.CentroidRatio))
|
||||
$report.Add(('- {0}. `{1}` / verdict `{2}` / collapse `{3:0.######}` / motion `{4:0.######}` / first-second `{5:0.######}` / cosine `{6:0.######}` / score `{7:0.######}` / rms `{8:0.######}` / centroid `{9:0.######}`' -f $rank, $row.CandidateId, $row.Verdict, $row.ModeCollapseRisk, $row.MotionCoverage, $row.FirstSecondEnergyShare, $row.LogMelCosine, $row.AudioScore, $row.RmsRatio, $row.CentroidRatio))
|
||||
$report.Add((' - candidate: `{0}`' -f $row.CandidateWav))
|
||||
$report.Add((' - reference: `{0}`' -f $row.ReferenceWav))
|
||||
$report.Add((' - summary: `{0}`' -f $row.SummaryPath))
|
||||
|
|
@ -217,8 +223,8 @@ function Write-PassPlaylist {
|
|||
if ($skipped.Count -gt 0) {
|
||||
$report.Add("")
|
||||
$report.Add("## Skipped Non-Playable Candidates")
|
||||
foreach ($row in $skipped | Sort-Object -Property @{ Expression = "LogMelCosine"; Descending = $true }, @{ Expression = "AudioScore"; Descending = $true }) {
|
||||
$report.Add(('- `{0}` / verdict `{1}` / cosine `{2:0.######}` / score `{3:0.######}` / expected candidate: `{4}`' -f $row.CandidateId, $row.Verdict, $row.LogMelCosine, $row.AudioScore, $row.CandidateWav))
|
||||
foreach ($row in $skipped | Sort-Object -Property @{ Expression = "ModeCollapseRisk"; Descending = $false }, @{ Expression = "MotionCoverage"; Descending = $true }, @{ Expression = "LogMelCosine"; Descending = $true }, @{ Expression = "AudioScore"; Descending = $true }) {
|
||||
$report.Add(('- `{0}` / verdict `{1}` / collapse `{2:0.######}` / motion `{3:0.######}` / cosine `{4:0.######}` / score `{5:0.######}` / expected candidate: `{6}`' -f $row.CandidateId, $row.Verdict, $row.ModeCollapseRisk, $row.MotionCoverage, $row.LogMelCosine, $row.AudioScore, $row.CandidateWav))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -421,8 +427,10 @@ Self-iteration loop:
|
|||
- Each attempt must write a candidate under `$iterationRoot/attempt-NN/<target-id>/patch`, run the scoring worker against that one target, inspect the rendered candidate evidence, and revise the next attempt.
|
||||
- Scoring command pattern:
|
||||
`dotnet run --project "$workerProject" -- song-score --patch-root "<attempt-patch-dir>" --challenge "<challenge-json>" --artifact-root "<attempt-dir>" --batch-id "<agent-id>-<target-id>-attempt-NN" --store "<attempt-dir>/iteration-results.cc" --hypothesizer "$agentId-iteration"`
|
||||
- After each score, read the attempt's `evaluator-report.md`, `summary.csv`, `audio/comparison.txt`, `audio/candidate-analysis.md`, `audio/candidate-logmel-band-stats.csv`, `audio/candidate-rms-envelope-autocorr.csv`, and `audio/candidate-whitened-spectral-autocorr.csv`.
|
||||
- After each score, read the attempt's `evaluator-report.md` and `summary.csv`. If the attempt rendered, also read `audio/comparison.txt`, `audio/candidate-analysis.md`, `audio/candidate-logmel-band-stats.csv`, `audio/candidate-rms-envelope-autocorr.csv`, and `audio/candidate-whitened-spectral-autocorr.csv`. If the attempt is `render-failed`, do not try to read missing candidate audio analysis files; treat the parse/lowering/render failure as negative syntax evidence and fix that before changing the composition.
|
||||
- Use those candidate-side facts the same way you use target facts: compare spectral bands, envelope autocorrelation, whitened spectral autocorrelation, centroid/RMS, instrument-role metrics, and chip-distress risk. Move timings, filters, gains, sources, roles, and patterns between attempts.
|
||||
- The evaluator records song-continuity metrics: `candidate_motion_coverage`, `motion_coverage_ratio`, `candidate_first_second_energy_share`, `first_second_energy_excess`, `candidate_tail_energy_share`, and `mode_collapse_risk`. A patch that plays a short phrase in the first second and then coasts on low-motion texture/noise is failed song evidence even if its RMS or log-mel score looks tolerable.
|
||||
- If an attempt has `mode_collapse_risk >= .45`, the next attempt must add later-section motifs or eventful motion after seconds 2, 4, 6, and 8. Do not merely raise the noise bed or pad gain; that is hiding, not composing.
|
||||
- Keep intermediate candidates inside `$iterationRoot`; publish only one final `.aqua` per target to the official target patch output directory.
|
||||
|
||||
Reusable abstraction mining:
|
||||
|
|
@ -489,6 +497,7 @@ Evidence contract:
|
|||
- 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 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.
|
||||
- Background/recording-color layers must use `texture` or an equivalently gated and band-shaped explicit voice; a static full-duration white-noise bed is failed evidence.
|
||||
- Make each patch duration/gates cover its matching target duration from the challenge report.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue