Design: Surprise Score¶
Problem¶
YaO generates spec-correct music that can be predictable and flat — the Prosaic Output Problem. Listeners need a balance of predictability and novelty.
Solution¶
Per-note surprise scoring via n-gram + Krumhansl tonal hierarchy. No ML dependencies.
Components¶
SurpriseScorer— bigram pitch-class transition model + tonal stabilitySurpriseAnalysis— frozen result: per-note scores, moving average, peaks, summaryNoteSurprise— per-note annotation with ngram + tonal + combined scores
Algorithm¶
- Collect all notes across sections/parts, sorted by beat
- Build bigram transition model from pitch classes
- For each note: tonal surprise = 1 - (stability / max_stability)
- For each note: ngram surprise = -log2(transition probability) normalized
- Combined = tonal_weight * tonal + (1 - tonal_weight) * ngram
Integration¶
- Surfaced in evaluation.json under
structure.surprise_distribution - Consumed by
surprise_deficitandsurprise_overloadcritique rules - Note Realizers can query surprise to inject variation
Thresholds¶
- Deficit: overall_predictability < 0.15
- Overload: overall_predictability > 0.65
- Healthy range: 0.2–0.6
Files¶
src/yao/perception/surprise.py— SurpriseScorer, SurpriseAnalysissrc/yao/verify/critique/surprise_rules.py— SurpriseDeficitDetector, SurpriseOverloadDetector