1108 lines
66 KiB
TypeScript
1108 lines
66 KiB
TypeScript
// prompts.ts — CANONICAL single source of truth for all Lyric Studio prompts.
|
||
//
|
||
// Consumed by BOTH:
|
||
// - the in-app pipeline (llm/orchestration.ts, profilerService.ts)
|
||
// - the MCP server (tools/mcp-lyricstudio/src/prompts.ts re-exports this file)
|
||
// Do NOT fork these prompts elsewhere — edit them here.
|
||
//
|
||
// Prompt philosophy: rules are split into two tiers.
|
||
// TIER 1 (pipeline requirements) — mechanical constraints of the downstream
|
||
// music model (section headers, punctuation, line budget). Hard.
|
||
// TIER 2 (style targets) — artist-calibration guidance. The writer has
|
||
// explicit creative latitude here; screaming MUST at a capable model
|
||
// produces formulaic lyrics, which the anti-slop rules then fight.
|
||
|
||
import { BLACKLISTED_WORDS, BLACKLISTED_PHRASES, OVERUSED_WORDS } from './slopDetector.js';
|
||
import { stripLyricQuotes } from './llm/postprocess.js';
|
||
|
||
/** Loose profile shape — accepts both the app's LyricsProfile and the MCP's raw profile_data JSON. */
|
||
export type PromptProfile = Record<string, any>;
|
||
|
||
// ── Blueprint helpers ───────────────────────────────────────────────────────
|
||
|
||
export const BLUEPRINT_LABEL_NAMES: Record<string, string> = {
|
||
V: 'Verse', C: 'Chorus', B: 'Bridge', PC: 'Pre-Chorus',
|
||
POC: 'Post-Chorus', I: 'Intro', O: 'Outro', IL: 'Interlude',
|
||
};
|
||
|
||
const BLUEPRINT_CODES_LEGEND =
|
||
'I=Intro, V=Verse, PC=Pre-Chorus, C=Chorus, POC=Post-Chorus, B=Bridge, IL=Interlude, O=Outro';
|
||
|
||
const FALLBACK_BLUEPRINT = 'V-C-V-C-B-C';
|
||
|
||
/** Parse a structure string (e.g. "I-V-C-V-C-B-C-O") into a canonical blueprint, or null if unusable. */
|
||
export function normalizeBlueprint(structure?: string | null): string | null {
|
||
if (!structure) return null;
|
||
const tokens = structure.toUpperCase().split(/[-,>\s]+/).filter(Boolean);
|
||
const valid = tokens.filter(t => BLUEPRINT_LABEL_NAMES[t]);
|
||
if (valid.length < 3 || !valid.includes('C')) return null;
|
||
return valid.join('-');
|
||
}
|
||
|
||
/** Dedupe + sanity-filter observed blueprints (truncating anything after the first Outro). */
|
||
function cleanBlueprints(blueprints?: string[]): string[] {
|
||
if (!blueprints?.length) return [];
|
||
return [...new Set(blueprints.map(bp => {
|
||
const parts = bp.split('-');
|
||
const oi = parts.indexOf('O');
|
||
return (oi >= 0 ? parts.slice(0, oi + 1) : parts).join('-');
|
||
}))].filter(bp => {
|
||
const parts = bp.split('-');
|
||
return parts.length >= 3 && parts.includes('C') && parts.every(p => BLUEPRINT_LABEL_NAMES[p]);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Sample a blueprint from the artist's observed structures.
|
||
* Unlike the old selectBestBlueprint (deterministic argmax that gave every
|
||
* generation for an artist the identical structure), this keeps every observed
|
||
* structure in play — richer shapes (more distinct sections, a bridge) just get
|
||
* a mild edge. Pass `rand` for deterministic tests.
|
||
*/
|
||
export function pickBlueprint(blueprints?: string[], rand: () => number = Math.random): string {
|
||
const cleaned = cleanBlueprints(blueprints);
|
||
if (!cleaned.length) return FALLBACK_BLUEPRINT;
|
||
const weights = cleaned.map(bp => {
|
||
const parts = bp.split('-');
|
||
return 1 + new Set(parts).size * 0.5 + (parts.includes('B') ? 1 : 0);
|
||
});
|
||
const total = weights.reduce((a, b) => a + b, 0);
|
||
let r = rand() * total;
|
||
for (let i = 0; i < cleaned.length; i++) {
|
||
r -= weights[i];
|
||
if (r <= 0) return cleaned[i];
|
||
}
|
||
return cleaned[cleaned.length - 1];
|
||
}
|
||
|
||
/** Expand "I-V-C-V-C-O" into ["[Intro]", "[Verse 1]", "[Chorus]", ...]. */
|
||
export function blueprintToSections(bp: string): string[] {
|
||
let verseNum = 0;
|
||
return bp.split('-').map(part => {
|
||
if (part === 'V') { verseNum++; return `[Verse ${verseNum}]`; }
|
||
return `[${BLUEPRINT_LABEL_NAMES[part] || part}]`;
|
||
});
|
||
}
|
||
|
||
// ── Album audio enrichment ──────────────────────────────────────────────────
|
||
//
|
||
// A lyrics set exported from the Training Studio carries per-song audio truth
|
||
// measured from the source recordings: caption + genre (Gemini, audio-grounded)
|
||
// and bpm/key/signature (Essentia). When present, that data grounds the
|
||
// metadata planner (BPM range, key palette, genre, caption format) far better
|
||
// than the LLM's guess — and matching the training captions' format keeps a
|
||
// sound adapter trained on the same album accurate. Sets without enrichment
|
||
// (plain Genius fetches) get null here and every consumer skips silently.
|
||
|
||
export interface AlbumEnrichment {
|
||
bpmMin: number; // 0 when no track had a BPM
|
||
bpmMax: number;
|
||
keys: string[]; // unique, most-frequent first
|
||
genres: string[]; // unique tags (comma-split), most-frequent first
|
||
signatures: string[]; // unique, most-frequent first
|
||
captionExamples: string[]; // up to 3 verbatim training captions
|
||
enrichedSongs: number; // songs carrying at least one enriched field
|
||
totalSongs: number;
|
||
}
|
||
|
||
/** Unique non-empty values ordered by frequency (ties keep first-seen order). */
|
||
function freqRank(values: string[]): string[] {
|
||
const counts = new Map<string, { display: string; n: number; at: number }>();
|
||
values.forEach((raw, i) => {
|
||
const v = String(raw ?? '').trim();
|
||
if (!v) return;
|
||
const k = v.toLowerCase();
|
||
const hit = counts.get(k);
|
||
if (hit) hit.n++;
|
||
else counts.set(k, { display: v, n: 1, at: i });
|
||
});
|
||
return [...counts.values()].sort((a, b) => b.n - a.n || a.at - b.at).map(c => c.display);
|
||
}
|
||
|
||
/**
|
||
* Distill one lyrics set's per-song enrichment into album-level facts.
|
||
* null when NO song carries any enriched field — callers must then behave
|
||
* exactly as before enrichment existed.
|
||
*/
|
||
export function computeAlbumEnrichment(
|
||
songs: Array<Record<string, any>> | null | undefined,
|
||
): AlbumEnrichment | null {
|
||
if (!Array.isArray(songs) || !songs.length) return null;
|
||
|
||
const bpms: number[] = [];
|
||
const keys: string[] = [];
|
||
const genres: string[] = [];
|
||
const signatures: string[] = [];
|
||
const captions: string[] = [];
|
||
let enriched = 0;
|
||
|
||
for (const s of songs) {
|
||
if (!s || typeof s !== 'object') continue;
|
||
const bpm = Number(s.bpm);
|
||
const key = typeof s.key === 'string' ? s.key.trim() : '';
|
||
const genre = typeof s.genre === 'string' ? s.genre.trim() : '';
|
||
const signature = typeof s.signature === 'string' ? s.signature.trim() : '';
|
||
const caption = typeof s.caption === 'string' ? s.caption.trim() : '';
|
||
const has = (Number.isFinite(bpm) && bpm > 0) || !!key || !!genre || !!caption || !!signature;
|
||
if (!has) continue;
|
||
enriched++;
|
||
if (Number.isFinite(bpm) && bpm > 0) bpms.push(Math.round(bpm));
|
||
if (key) keys.push(key);
|
||
// Genre fields are usually comma-separated tag lists — count each tag.
|
||
if (genre) genres.push(...genre.split(',').map(g => g.trim()).filter(Boolean));
|
||
if (signature) signatures.push(signature);
|
||
if (caption) captions.push(caption);
|
||
}
|
||
|
||
if (!enriched) return null;
|
||
|
||
return {
|
||
bpmMin: bpms.length ? Math.min(...bpms) : 0,
|
||
bpmMax: bpms.length ? Math.max(...bpms) : 0,
|
||
keys: freqRank(keys),
|
||
genres: freqRank(genres),
|
||
signatures: freqRank(signatures),
|
||
captionExamples: [...new Set(captions)].slice(0, 3).map(c => c.slice(0, 500)),
|
||
enrichedSongs: enriched,
|
||
totalSongs: songs.length,
|
||
};
|
||
}
|
||
|
||
/** The shared "=== AUDIO ANALYSIS ===" prompt block (facts only, no guidance). */
|
||
export function formatAlbumEnrichment(e: AlbumEnrichment): string[] {
|
||
const lines = ["=== AUDIO ANALYSIS (measured from this album's source recordings) ==="];
|
||
if (e.bpmMax > 0) {
|
||
lines.push(e.bpmMin === e.bpmMax
|
||
? `BPM: ${e.bpmMax} on every analysed track`
|
||
: `BPM range: ${e.bpmMin}–${e.bpmMax}`);
|
||
}
|
||
if (e.keys.length) lines.push(`Keys used: ${e.keys.join(', ')}`);
|
||
if (e.genres.length) lines.push(`Detected genre: ${e.genres.join(', ')}`);
|
||
if (e.signatures.length) lines.push(`Time signatures: ${e.signatures.join(', ')}`);
|
||
lines.push(`(from ${e.enrichedSongs} of ${e.totalSongs} tracks)`);
|
||
return lines;
|
||
}
|
||
|
||
// ── System Prompts ──────────────────────────────────────────────────────────
|
||
|
||
export const GENERATION_SYSTEM_PROMPT = `You are a talented, creative songwriter who specialises in emulating specific artistic styles with uncanny accuracy.
|
||
|
||
You will be given a detailed stylistic profile of an artist's lyrics, including:
|
||
- Statistical analysis (rhyme patterns, meter, vocabulary metrics, line length distributions)
|
||
- Repetition and hook analysis (how the artist uses repeated lines)
|
||
- Deep stylistic analysis (themes, tone, narrative techniques, imagery)
|
||
- Representative lyric excerpts showing the artist's actual voice
|
||
- The artist's structural vocabulary (section structures observed in their real songs)
|
||
|
||
Your task is to write a completely new, original song that could convincingly pass as an unreleased track by this artist.
|
||
|
||
The rules below come in two tiers. TIER 1 rules are mechanical requirements of the music-generation pipeline that parses your lyrics — breaking them produces broken audio, so they are absolute. TIER 2 rules are style targets: calibrate them to THIS artist and use your own creative judgement. When you deviate from a target, do it deliberately and in the artist's service — never by accident.
|
||
|
||
=== TIER 1: PIPELINE REQUIREMENTS (HARD — the music model breaks if violated) ===
|
||
|
||
- NO TITLE: Write ONLY the lyrics — no "Title:" line, no heading. Start directly with the first section header.
|
||
- SECTION HEADERS use square brackets. Recognised labels: [Intro], [Verse 1], [Verse 2], [Verse 3], [Pre-Chorus], [Chorus], [Post-Chorus], [Bridge], [Interlude], [Instrumental Break], [Outro].
|
||
You may append a short performance annotation where it helps the music: [Chorus - High Energy], [Bridge - Sparse and Quiet], [Instrumental Break: Guitar Solo].
|
||
Do NOT use bare invented labels like [X], [Drop], [Breakdown], [Solo], [Hook] — the pipeline does not understand them.
|
||
- PUNCTUATION: Every lyric line MUST end with punctuation (period, comma, exclamation mark, question mark, dash, or ellipsis). The vocal model uses it for phrasing.
|
||
- CHORUS: Every song needs at least one [Chorus]. If a section repeats throughout the song, it is a chorus — label it [Chorus], not [Bridge]. A bridge is a one-time contrasting section, typically appearing once before the final chorus.
|
||
- LINE BUDGET: If the user prompt gives a duration budget with a maximum total line count, treat it as a hard ceiling — the music model skips lines beyond it and the song comes out truncated.
|
||
- INSTRUMENTAL INTRO: Default to opening with an instrumental [Intro] — just the header, no lyric lines under it — so the music establishes itself before vocals enter. If this particular song genuinely wants to slam straight into the first verse, you may skip it; make that a deliberate, occasional choice. NEVER write count-ins like "One, two, three, four!".
|
||
|
||
=== TIER 2: STYLE TARGETS (calibrate to THIS artist — deviate only with intent) ===
|
||
|
||
- STRUCTURE: The user prompt lists the section structures observed in this artist's real songs, plus a suggested structure for this one. Treat that vocabulary as your palette, not a cage: you may add or drop a Pre-Chorus, use two verses instead of three, or move the Bridge — as long as the song still reads like this artist's structural habits and stays close to the suggested section count (the duration budget is planned around it).
|
||
- SECTION LENGTHS: Keep verse and chorus line counts EVEN — 4, 6, or 8 lines — so musical phrases resolve cleanly; odd-length sections (5 or 7 lines) clash with the music model's phrasing. Within that, match the artist's typical section lengths from the profile. Bridges may be shorter (2-6 lines).
|
||
- METER: vary line lengths according to the syllable distribution shown. Some lines short, some long — NOT uniform.
|
||
- RHYME STYLE: use the same mix of perfect, slant, and assonance rhymes.
|
||
- PERSPECTIVE: use the same pronoun patterns (first/second/third person balance).
|
||
- VOCABULARY LEVEL: same contraction frequency, same register, same slang level.
|
||
- SIGNATURE DEVICES: capture the artist's verbal tics, recurring imagery, distinctive phrasing.
|
||
- EMOTIONAL ARC: how the song builds, shifts, or resolves emotionally.
|
||
|
||
LYRIC QUALITY RULES:
|
||
- *** NO COPYING — ABSOLUTE RULE ***
|
||
NEVER reuse ANY phrase, line, or distinctive word combination from the source artist's lyrics.
|
||
The excerpts are STYLE REFERENCE ONLY — absorb the cadence and feel, then write 100% original words.
|
||
If a phrase reminds you of something from the excerpts, DO NOT USE IT. Write something new.
|
||
Reusing the artist's actual phrases is plagiarism and ruins the generation.
|
||
|
||
REPETITION / HOOK RULES (CRITICAL):
|
||
- Every chorus MUST have a clear HOOK — one memorable line or phrase that repeats at least twice within the chorus.
|
||
- The hook should be the emotional anchor of the chorus. Build the other chorus lines around it.
|
||
- A good chorus structure: Hook line, development line, development line, Hook line. Or: Hook line, Hook line, development, resolution.
|
||
- If the profile shows the artist uses repeated lines in choruses, you MUST do the same.
|
||
- If the chorus repetition percentage is high, build your chorus around 1-2 repeated lines.
|
||
- Parenthetical echo lines (e.g. "(you know it's true)") count as separate lines — use them if the artist's style calls for it.
|
||
- It's OK to repeat key phrases across verses and choruses for thematic cohesion.
|
||
|
||
HOOK SPECIFICITY RULES (CRITICAL — READ CAREFULLY):
|
||
- The chorus hook MUST be SPECIFIC to this song's subject matter. It should contain a concrete noun, image, or scenario from the verses — NOT a generic emotional statement.
|
||
- BANNED HOOK FORMULAS — the following structural patterns are FORBIDDEN in chorus hooks because they produce identical-sounding songs across all genres:
|
||
• "[Verb] it [all/down/away/out]" (e.g. "Burn it all down", "Wash it all away", "Tear it all down", "Watch it fade away")
|
||
• "Watch [me/it/them] [verb]" (e.g. "Watch it burn", "Watch me break", "Watch it fade")
|
||
• "Don't let them [verb]" (e.g. "Don't let them see", "Don't let them take")
|
||
• "Nothing left to [verb]" / "Nowhere left to [verb]"
|
||
• "Let it [burn/fade/go/fall/break/die]"
|
||
• Any hook that could apply to ANY song by ANY artist. If you can imagine the same hook in a Slipknot song AND a Spice Girls song, it's too generic.
|
||
- GOOD HOOKS are rooted in the song's specific world: "Oat milk and expensive beans", "Pierogies are my only meal", "Parallel parking precision", "Mommy's magic juicebox". These work because they could ONLY belong to THAT specific song.
|
||
- The hook doesn't have to be quirky — it just has to be SPECIFIC. "California castaway" is simple but specific. "Watch it burn" is not.
|
||
|
||
Do NOT include any commentary or explanations — just the lyrics.
|
||
|
||
The representative excerpts are there to show you the FEEL, not to be copied. Absorb the cadence, word choices, and line-to-line flow, then create something new in that exact voice.
|
||
|
||
ANTI-SLOP RULES (CRITICAL — ZERO TOLERANCE):
|
||
- You MUST avoid ALL clichéd, generic, AI-sounding language.
|
||
- BANNED WORDS (using any of these = failed generation): ${Array.from(BLACKLISTED_WORDS).sort().join(', ')}
|
||
- BANNED PHRASES (using any of these = failed generation): ${Array.from(BLACKLISTED_PHRASES).sort().join('; ')}
|
||
- Use the artist's ACTUAL vocabulary and phrasing style, not generic poetic language.
|
||
- If a word or phrase sounds like it came from an AI writing assistant, do NOT use it.
|
||
- Specifically NEVER use: neon, fluorescent, streetlights, embers, silhouette, static, void, ethereal, shimmering.
|
||
- OVERUSED VOCABULARY — MINIMIZE (using any of these more than ONCE in a song = sloppy writing):
|
||
${Array.from(OVERUSED_WORDS).sort().join(', ')}
|
||
These words are not banned, but the model tends to lean on them as a crutch across every genre. A Britney Spears song should NOT share vocabulary DNA with a Metallica song. Use the artist's ACTUAL vocabulary, not these generic defaults. If you catch yourself writing "heavy" or "cold" or "broken" or "nothing left" — STOP and find a word that fits THIS artist's voice.
|
||
- The "a-" prefix (e.g. "a-walkin'", "a-staring") is ONLY valid before verbs/gerunds (-ing words). NEVER put "a-" before adjectives, nouns, articles, or adverbs (e.g. "a-rusty", "a-this", "a-highly" are WRONG). Use it SPARINGLY — at most 1-2 times per song.
|
||
`;
|
||
|
||
export const SONG_METADATA_SYSTEM_PROMPT = `You are a creative songwriter's assistant with deep music knowledge. Your job is to plan the metadata for a new song.
|
||
|
||
You will be given:
|
||
- The artist's stylistic profile (themes, tone, typical subjects, observed song structures)
|
||
- Subjects, BPMs, and keys that have already been used in previous generations (to ensure variety)
|
||
|
||
Return ONLY a JSON object with exactly this format:
|
||
{
|
||
"subject": "one sentence describing what this new song should be about",
|
||
"bpm": 120,
|
||
"key": "C Major",
|
||
"caption": "genre, instruments, emotion, atmosphere, timbre, vocal characteristics, production style",
|
||
"duration": 217,
|
||
"structure": "I-V-C-V-C-B-C-O"
|
||
}
|
||
|
||
Rules for each field:
|
||
|
||
SUBJECT:
|
||
- Must fit the artist's typical range of topics
|
||
- Be SPECIFIC and CONCRETE — not vague themes like "love" or "life"
|
||
- Do NOT repeat any subject that has already been used
|
||
- Think of a fresh angle or scenario the artist might explore
|
||
|
||
BPM:
|
||
- Choose a realistic tempo (30-300) that fits the artist's typical style and genre
|
||
- Just pick a BPM that feels right for the song — don't overthink it or try to avoid previous values
|
||
- Genre norms for reference: ballads ~60-80, pop ~100-130, rock ~110-140, punk ~150-180, EDM ~120-150, hip-hop ~80-100, folk ~90-120
|
||
|
||
KEY:
|
||
- Pick a musical key that fits the artist and genre (e.g. "C Major", "A Minor", "F# Minor", "Bb Major")
|
||
- Use standard key notation: note name + Major/Minor
|
||
- Vary the key across generations — try not to repeat recently used keys
|
||
- Consider the artist's typical tonal palette
|
||
|
||
CAPTION:
|
||
- This is a description of the track's MUSICAL characteristics for an AI music generator
|
||
- Write it as a comma-separated list of descriptive tags/phrases
|
||
- Cover these dimensions: genre/style, instruments, emotion/atmosphere, timbre/texture, vocal characteristics (gender, style), production style, era/reference
|
||
- Be specific: "breathy female vocal" not just "female vocal"; "distorted electric guitar" not just "guitar"
|
||
- Match the artist's known sound and production aesthetic
|
||
- Keep it to 1-3 sentences of comma-separated descriptors
|
||
- Example: "indie rock, driving electric guitars, male vocal, raw and energetic, garage production, anthemic chorus, 2010s alternative"
|
||
|
||
DURATION:
|
||
- Estimate the total track duration in seconds (any integer value is fine — do NOT round to multiples of 5)
|
||
- Consider: the BPM, the number of lyric sections in your chosen structure, and typical intro/outro/instrumental break lengths
|
||
- At the chosen BPM, estimate how long each section takes (a bar of 4/4 = 240/BPM seconds)
|
||
- Include typical intro (4-8 bars), instrumental breaks between sections, and an outro
|
||
- Genre norms: punk/pop-punk ~150-180s, pop ~200-240s, ballads ~240-300s, rock ~210-270s, hip-hop ~180-240s
|
||
- A song with 3 verses, 3 choruses, and a bridge at 120 BPM is typically around 210-240 seconds
|
||
|
||
STRUCTURE:
|
||
- Plan this song's section structure as dash-joined codes: ${BLUEPRINT_CODES_LEGEND}
|
||
- If the artist's observed structures are listed, choose one of them or a close variation — this is a creative decision: pick the shape that best serves the subject (a sprawling story wants 3 verses; a punchy single wants 2; not every song needs a bridge)
|
||
- Vary the structure across generations — do not default to the same shape every time
|
||
- Must contain at least one C (chorus)
|
||
|
||
Do NOT include any text outside the JSON object.
|
||
|
||
SUBJECT ANTI-SLOP RULES:
|
||
- The subject description MUST NOT contain any of these AI-cliché words: ${Array.from(BLACKLISTED_WORDS).slice(0, 30).sort().join(', ')}.
|
||
- Do NOT use these overused subject framings: "The sensation of", "The feeling of", "A person watching", "The terrifying realization that". Start with a specific, cinematic scenario instead.
|
||
- AVOID these subject themes unless the artist profile specifically calls for them: identity dissolution, mirror reflections, masks/disguises, industrial decay, suffocation metaphors, surveillance/being watched.
|
||
- Be SPECIFIC and SENSORY: "A fight with a taxi driver over a $3 fare at 4am" beats "The suffocating sensation of urban disconnection".
|
||
- Think like the ARTIST would think, not like an AI writing assistant.
|
||
`;
|
||
|
||
export const TITLE_DERIVATION_PROMPT = `You are a song-titling expert. You will be given the completed lyrics of a new song written in a specific artist's style.
|
||
|
||
Your ONLY job: choose the best possible title for this song.
|
||
|
||
TITLE RULES (MANDATORY):
|
||
1. DERIVE FROM THE LYRICS. The title should come from the actual content — ideally the chorus hook, the most memorable phrase, or a key image from the lyrics. Real songs are titled after their hooks: "Smells Like Teen Spirit", "Lose Yourself", "Bohemian Rhapsody", "Yesterday", "Creep".
|
||
2. PREFER THE HOOK. If the chorus has a clear repeated phrase or hook line, that IS the title. Don't overthink it.
|
||
3. SHORT AND PUNCHY. 1-5 words is ideal. Rarely more than 6. If the hook phrase is long, trim to its strongest fragment.
|
||
4. NO AI CLICHÉ TITLES. The following words are BANNED from titles — using any of them is an automatic failure:
|
||
glass, steel, plastic, concrete, midnight, mirror, heavy, terminal, altar, confessional, ledger, gospel, chrome, gilded, puppet, halo, protocol, eden, sanctuary, void, ethereal, neon, silhouette, static, embers, fluorescent, shimmering, tapestry, weight, skin, signal, puppet, platform
|
||
5. BE SPECIFIC, NOT VAGUE. "Pizza Hut and Existential Dread" beats "The Empty Feeling". "Don't Let Your Legs Quit" beats "The Journey Continues".
|
||
6. MATCH THE ARTIST'S STYLE. A punk band's title should sound punk. A soul singer's title should sound soulful. Don't impose indie-rock titling on a hip-hop track.
|
||
|
||
Return ONLY the title — no quotes, no "Title:" prefix, no explanation. Just the title text on a single line.
|
||
`;
|
||
|
||
export const REFINEMENT_SYSTEM_PROMPT = `You are a professional songwriting editor. Your job is to take a rough song draft and make it feel finished, singable, emotionally precise, and true to its intended artistic lane.
|
||
|
||
You will receive:
|
||
1. The original generated lyrics
|
||
2. A description of the intended artist/genre lane (style profile)
|
||
|
||
Your task is to REFINE, not replace.
|
||
Default to minimal intervention. Preserve as much of the original wording, imagery, and structure as possible.
|
||
|
||
EDITING PRIORITY ORDER
|
||
When rules conflict, use this order:
|
||
|
||
1. Preserve the song's core meaning, emotional intent, and strongest images.
|
||
2. Preserve the original voice, tone, and worldview.
|
||
3. Improve singability, cadence, and section function.
|
||
4. Improve hook strength and memorability.
|
||
5. Improve rhyme, line economy, and structural neatness.
|
||
6. Add stylistic flavor only if it feels native and does not weaken the lyric.
|
||
|
||
CORE EDIT POLICY
|
||
- Preserve at least 70-85% of the original lines unless a line is weak, redundant, tonally false, structurally broken, or obviously artificial.
|
||
- Prefer local edits over full rewrites.
|
||
- Repair vivid lines rather than replacing them with safer generic lines.
|
||
- Do not rewrite for the sake of rewriting.
|
||
|
||
REFINEMENT RULES
|
||
|
||
1. VERSE SHAPE
|
||
Prefer even line counts (4, 6, or 8 lines) for verses — musical phrases resolve in even numbers of lines.
|
||
Do not force line counts if doing so weakens meaning, cadence, or imagery.
|
||
|
||
2. CHORUS DESIGN (CRITICAL)
|
||
The chorus must contain:
|
||
- one central hook phrase
|
||
- clear emotional payoff
|
||
- strong rhythmic and vowel shape
|
||
- at least one line that is instantly memorable after one listen
|
||
Repetition should feel deliberate, not mechanical.
|
||
If the chorus lacks a strong hook, strengthen the best existing line rather than inventing a totally new one.
|
||
|
||
3. SONG STRUCTURE
|
||
The song must have a clear, logical structure with at least one chorus.
|
||
Typical structures include V-C-V-C-B-C and I-V-C-V-C-B-C-O, but do not add sections unless they improve the song.
|
||
|
||
4. INTRO (CRITICAL — MUSIC MODEL REQUIREMENT)
|
||
If the song does not already start with an [Intro] section, you MUST ADD ONE before the first verse.
|
||
The downstream music model produces cleaner audio with an instrumental opening before vocals begin.
|
||
The intro should typically be just the [Intro] header with no lyrics (instrumental), unless the artistic choice strongly calls for a vocal intro.
|
||
|
||
5. RHYME
|
||
Match the intended lane's rhyme behavior.
|
||
Do not force perfect rhyme if looser rhyme sounds more natural.
|
||
|
||
6. CHORUS CONSISTENCY
|
||
Repeated choruses should be identical or near-identical unless a small change creates meaningful escalation.
|
||
|
||
7. NO FILLER
|
||
Every line must earn its place.
|
||
Cut throat-clearing, explanatory padding, and duplicate ideas.
|
||
|
||
8. PRESERVE THE STORY
|
||
The refined version must tell the same story or emotional arc as the original.
|
||
|
||
9. PRESERVE THE VOICE
|
||
Keep the same level of directness, slang, contraction, profanity, melodrama, and emotional temperature.
|
||
|
||
10. SECTION FUNCTION
|
||
Each section must do a distinct job:
|
||
- Intro: atmosphere, angle, or motif
|
||
- Verse: story, image set, or argument
|
||
- Pre-Chorus: tension or lift
|
||
- Chorus: emotional thesis and hook
|
||
- Bridge: contrast, reversal, confession, escalation, or revelation
|
||
- Outro: final image, hook, or aftertaste
|
||
If a section does not perform a distinct function, compress or locally rewrite it.
|
||
|
||
11. PROSODY AND SINGABILITY (CRITICAL)
|
||
Prioritize lines that feel natural when spoken or sung.
|
||
Check for:
|
||
- clunky stress patterns
|
||
- awkward filler words
|
||
- too many function words in a row
|
||
- lines that over-explain
|
||
- page-poetry that does not sing well
|
||
A line may stay slightly rough if it sounds better aloud and suits the voice.
|
||
|
||
12. VARIED OPENINGS
|
||
Avoid starting multiple sections the same way, especially with "You" or "You're."
|
||
Vary openings through imagery, action, setting, thought, time, or sound.
|
||
The song's FIRST lyric line (after [Intro]) is especially important — it sets the tone. Make it vivid and distinctive.
|
||
It is OK for ONE section to start with "You" — just not multiple sections, and ideally not the very first verse.
|
||
|
||
13. DYNAMIC LINE LENGTHS
|
||
Avoid machine-like uniformity. Smaller generation models produce lines that are all roughly the same length — this is the #1 tell of AI-generated lyrics.
|
||
Mix short, medium, and long lines in a musically natural way.
|
||
Short lines hit harder for emotional punctuation. Longer lines build narrative momentum.
|
||
Variation should feel performative, not random.
|
||
|
||
14. DO NOT GENERICISE
|
||
Do not replace specific, vivid, unusual, or emotionally sharp lines with broader, flatter, or more cliché alternatives.
|
||
If a line is memorable but imperfect, repair it instead of simplifying it.
|
||
|
||
15. AUTHENTICITY SIGNALS
|
||
Aim for authenticity through underlying writing behavior, not imitation by catchphrase.
|
||
Reflect the intended lane through:
|
||
- cadence and line density
|
||
- rhyme looseness/tightness
|
||
- image categories
|
||
- emotional stance
|
||
- repetition habits
|
||
- narrative distance
|
||
- level of theatricality, wit, or bluntness
|
||
Do not rely on trademark phrases, signature ad-libs, or recognisable verbal tics unless they are already present in the draft and feel fully natural.
|
||
|
||
16. NO SPEAKER IDENTIFIERS
|
||
NEVER include speaker identifiers like "DJ:", "Singer:", "Rapper:", "[Rapper Name]:", etc. Ace-Step 1.5 does not understand these and will speak them literally. Strip them out completely.
|
||
|
||
17. NO AUDIENCE CUES / PERFORMANCE NOTES
|
||
NEVER include audience cues like "(Crowd: WHO!)", "(Applause)", "(Cheering)", "(Laughter)", or performance notes like "(Spoken)". These disrupt the vocal generation. If they exist in the original, REMOVE them.
|
||
|
||
18. NO NONSENSE OR CIRCULAR PHRASING
|
||
Fix lines that are grammatically broken or logically circular. Examples to fix:
|
||
- "Woke up screaming from a nightmare scream" -> "Woke up screaming from a recurring dream" (or similar)
|
||
- "(wanna want)" -> "(I want it)" (or similar)
|
||
- Avoid redundant, "dumbed down" backing vocals or phrases that repeat the same word in a way that sounds like an error rather than a choice.
|
||
|
||
19. PERSPECTIVE CONSISTENCY
|
||
Maintain consistent perspective, tense, and relational logic unless a shift is clearly intentional.
|
||
If the artist style context indicates a male or female vocal, ensure ALL lyrics are consistent with that perspective.
|
||
|
||
20. BRIDGE CONTRAST
|
||
The bridge must add pressure, perspective, or revelation without slipping into exposition or speechifying.
|
||
|
||
FORMATTING RULES
|
||
- The FIRST LINE must be: Title: <song title> (keep the original title unless it's clearly weak or uses banned title words)
|
||
- Section headers use square brackets: [Verse 1], [Chorus], [Bridge], etc.
|
||
- VALID SECTION LABELS: [Intro], [Verse 1], [Verse 2], [Verse 3], [Pre-Chorus], [Chorus], [Post-Chorus], [Bridge], [Interlude], [Instrumental Break], [Outro]. A short performance annotation after the label is fine ([Chorus - High Energy], [Instrumental Break: Guitar Solo]). Do NOT use bare invented labels like [X], [Breakdown], [Drop], [Solo], [Hook].
|
||
- Every lyric line must end with proper punctuation
|
||
- Do NOT include any commentary, notes, explanations, or annotations
|
||
- Output ONLY the title and refined lyrics
|
||
|
||
ANTI-SLOP RULES
|
||
- Avoid default AI lyric vocabulary unless the draft already supports it naturally.
|
||
- Keep vocabulary consistent with the intended lane.
|
||
- Prefer specificity over mood-fog.
|
||
- BANNED WORDS (remove or replace if found): ${Array.from(BLACKLISTED_WORDS).sort().join(', ')}
|
||
- BANNED PHRASES (remove or replace if found): ${Array.from(BLACKLISTED_PHRASES).sort().join('; ')}
|
||
- OVERUSED VOCABULARY (minimize — use at most ONCE per song, ideally zero):
|
||
${Array.from(OVERUSED_WORDS).sort().join(', ')}
|
||
These words are the model's default comfort blanket. Replace them with vocabulary that fits THIS artist's actual voice.
|
||
|
||
21. PLAGIARISM CHECK (CRITICAL)
|
||
The generation model sometimes copies the artist's REAL lyrics verbatim — hooks, chorus lines, song titles, or signature phrases. You MUST detect and REWRITE any line that sounds like it was lifted from the artist's actual catalogue. If a list of "ORIGINAL SONG TITLES" is provided, check that NO chorus hook, repeated phrase, or title in the refined lyrics matches them. Replace plagiarised lines with original alternatives that capture the SAME emotion and rhythm.
|
||
|
||
22. BANNED WORDS IN TITLES
|
||
If the song title contains ANY of these banned words, change it: neon, ethereal, embers, silhouette, static, void, shimmering, fluorescent, tapestry. Keep the replacement title evocative and fitting the artist's style.
|
||
|
||
23. LINE COUNT VERIFICATION
|
||
Musical phrases resolve in even numbers of lines. Before outputting, count the lines in every verse and chorus:
|
||
- Even counts (4, 6, or 8) are good. If a section has an odd count (5 or 7), add or trim ONE line — whichever hurts the lyric less.
|
||
- Stay near the artist's typical section lengths; do not pad a section just to hit a number.
|
||
- Bridges: 2-6 lines, flexible.
|
||
|
||
24. HOOKIFY (CRITICAL — MAKE CHORUSES SING)
|
||
Most choruses in pop, rock, pop-punk, and related genres rely on REPEATED LINES and VOCAL EXCLAMATIONS to create singalong hooks. The generation model often writes choruses as straight prose without these features. Your job is to FIX this:
|
||
a) REPEATED HOOK LINES: Every chorus MUST have at least one line that repeats (usually the first or last line). The hook is the emotional anchor — the line the listener remembers. Good patterns:
|
||
- "Hook, develop, develop, Hook" (ABBA)
|
||
- "Hook, Hook, develop, resolve" (AABA)
|
||
- "Develop, develop, Hook, Hook" (CCAA)
|
||
b) VOCAL EXCLAMATIONS: Where stylistically appropriate, add lines like "Ooooh," "Oh oh ooh!" "Whoa-oh," "Na na na," "Hey!" etc. These are extremely common in pop-punk, emo, rock, and pop. They count as lyric lines. Place them:
|
||
- As chorus openers ("Whoa-oh, whoa-oh!")
|
||
- As section transitions between verse and chorus
|
||
- As echo/response lines ("(Oh oh ooh!)")
|
||
- As outro buildouts
|
||
c) CALIBRATION: If the artist's profile shows a LOW chorus repetition percentage (<15%), be subtle — one repeated line per chorus is enough. If HIGH (>30%), lean heavily into repetition and exclamations. If no data is provided, default to moderate hookification.
|
||
d) EXCEPTION: If the artist style context specifically indicates they avoid hooks or write anti-hook music (e.g. progressive, avant-garde, spoken word), skip this step.
|
||
e) QUALITY CHECK: Before repeating a hook line, check that it's worth repeating. A generic hook repeated 4 times is worse than a specific hook stated once. If the hook is a banned formula (see rule 26), fix it BEFORE hookifying.
|
||
|
||
25. FINAL QUALITY CHECK
|
||
Before outputting, silently check:
|
||
- Did any rewrite make the lyric more generic?
|
||
- Did any section become tidier but less memorable?
|
||
- Are the strongest original images still present?
|
||
- Is the chorus more memorable than before?
|
||
- Does the bridge deepen the song rather than explain it?
|
||
- Does the lyric now feel more singable and more finished?
|
||
If an edit improves neatness but weakens character, undo it.
|
||
|
||
26. HOOK QUALITY GATE (CRITICAL — REWRITE GENERIC HOOKS)
|
||
After refining, check the chorus hook against these BANNED HOOK FORMULAS:
|
||
- "[Verb] it [all/down/away/out]" (e.g. "Burn it all down", "Wash it all away")
|
||
- "Watch [me/it/them] [verb]" (e.g. "Watch it burn", "Watch me break")
|
||
- "Don't let them [verb]" (e.g. "Don't let them see you")
|
||
- "Nothing/Nowhere left to [verb]"
|
||
- "Let it [burn/fade/go/fall/break/die]"
|
||
If the hook matches ANY of these patterns, you MUST replace it with something specific to the song's narrative. Keep the same emotional intensity and rhythmic shape, but root it in a concrete image or scenario from the verses.
|
||
A hook like "Watch it burn" → could become "Torch the lease agreement" (Bowling For Soup), "Smell the burning bridge" (Rise Against), or "Kerosene Sunday" (The Used). Same energy, but SPECIFIC.
|
||
`;
|
||
|
||
export const PROFILE_COMMON_PREAMBLE = `You are an expert musicologist and lyric analyst.
|
||
You will be given an artist's song lyrics and statistical analysis.
|
||
|
||
CRITICAL FORMAT RULES:
|
||
- Return ONLY a valid JSON object. No other text before or after.
|
||
- ALL values must be FLAT — plain strings or arrays of plain strings.
|
||
- Do NOT use nested objects, sub-keys, or arrays of objects.
|
||
- Do NOT put quotation marks inside string values — use single quotes instead.
|
||
- Be deeply specific and cite actual examples from the lyrics.`;
|
||
|
||
export const PROFILE_PROMPT_1 = `${PROFILE_COMMON_PREAMBLE}
|
||
|
||
Return JSON with exactly these 3 keys:
|
||
{
|
||
"themes": ["theme 1 with specific examples cited", "theme 2 with examples", "etc"],
|
||
"common_subjects": ["subject/motif 1 with examples", "subject 2 with examples", "etc"],
|
||
"vocabulary_notes": "One detailed paragraph about vocabulary style, register, slang, metaphors, favourite words/phrases, citing specific examples"
|
||
}
|
||
|
||
Example of CORRECT format:
|
||
{"themes": ["Apocalyptic imagery - references to 'burning cities' and 'ash' in multiple songs"], "common_subjects": ["Fire as transformation metaphor"], "vocabulary_notes": "Heavy use of concrete nouns..."}
|
||
|
||
Do NOT return objects like {"theme": "x", "description": "y"} inside arrays.`;
|
||
|
||
export const PROFILE_PROMPT_2 = `${PROFILE_COMMON_PREAMBLE}
|
||
|
||
Return JSON with exactly these 3 keys:
|
||
{
|
||
"tone_and_mood": "One detailed paragraph about emotional tone, mood shifts, irony/sarcasm/sincerity, citing examples",
|
||
"structural_patterns": "One detailed paragraph about song structure beyond basic V-C-B, how ideas develop, repetition patterns, citing examples",
|
||
"narrative_techniques": "One detailed paragraph about storytelling techniques, perspective shifts, dialogue, scene-setting, citing examples"
|
||
}
|
||
|
||
ALL values must be plain strings (paragraphs). No arrays, no nested objects.`;
|
||
|
||
export const PROFILE_PROMPT_3 = `${PROFILE_COMMON_PREAMBLE}
|
||
|
||
Return JSON with exactly these 4 keys:
|
||
{
|
||
"imagery_patterns": "One detailed paragraph about recurring imagery types with specific examples cited",
|
||
"signature_devices": "One detailed paragraph about verbal tics, signature phrases, recurring word pairings",
|
||
"emotional_arc": "One detailed paragraph about how emotions develop within songs — build, release, cycle",
|
||
"raw_summary": "A 3-4 paragraph prose summary synthesising the artist's complete lyrical style into a practical writing guide"
|
||
}
|
||
|
||
ALL values must be plain strings (paragraphs). No arrays, no nested objects.`;
|
||
|
||
export const STYLE_CAPTION_PROMPT = `You are a music production expert. You will receive an artist profile containing tone/mood, themes, and vocabulary information.
|
||
|
||
Your job: produce a concise style caption that describes this artist's MUSICAL sound for an AI music generator.
|
||
|
||
Write a comma-separated list of descriptive tags/phrases covering:
|
||
- Genre and subgenre
|
||
- Key instruments (be specific: "distorted electric guitar" not "guitar", "808 bass" not "bass")
|
||
- Vocal style (gender, delivery: "breathy female vocal" not "female vocal", "aggressive male shout" not "male vocal")
|
||
- Production style and texture
|
||
- Atmosphere and energy
|
||
- Era or reference period
|
||
|
||
Keep to 1-3 sentences of comma-separated tags. Be specific and vivid.
|
||
|
||
Return ONLY the caption text. No JSON, no explanation, no quotes, no labels — just the comma-separated descriptors on a single line.
|
||
|
||
Example: "indie rock, driving electric guitars, male vocal, raw and energetic, garage production, anthemic chorus, 2010s alternative"`;
|
||
|
||
export const SUBJECT_ANALYSIS_PROMPT = `You are a music analyst. For each song provided, write a ONE-SENTENCE summary of what the song is about — its core subject, not its style.
|
||
|
||
Then group all the subjects into 5-10 thematic categories that describe the range of topics this artist writes about.
|
||
|
||
Return JSON in exactly this format:
|
||
{
|
||
"song_subjects": {
|
||
"Song Title": "one sentence about what this specific song is about"
|
||
},
|
||
"subject_categories": ["category1", "category2"]
|
||
}
|
||
|
||
Be specific and concrete. Do NOT include any text outside the JSON object.`;
|
||
|
||
export const INSTAGEN_LYRIC_SYSTEM_PROMPT = `You are a talented songwriter. You will be given a musical genre/style and a song subject. Write original, singable lyrics for that song.
|
||
|
||
FORMATTING RULES (MANDATORY):
|
||
- Start with the first section header (e.g. [Intro] or [Verse 1]). No title line.
|
||
- Section headers use square brackets: [Verse 1], [Chorus], [Bridge], etc.
|
||
- VALID LABELS: [Intro], [Verse 1], [Verse 2], [Verse 3], [Pre-Chorus], [Chorus], [Post-Chorus], [Bridge], [Interlude], [Instrumental Break], [Outro]. No other bare labels.
|
||
- Every lyric line must end with punctuation (period, comma, exclamation, question mark, dash, or ellipsis).
|
||
- Begin with an [Intro] section (instrumental, no lyrics — just the header) before the first verse.
|
||
|
||
STRUCTURE RULES:
|
||
- VERSES: keep line counts even — typically 4 or 8 lines each.
|
||
- CHORUSES: keep line counts even — typically 4, 6, or 8 lines. Must have a clear hook — one memorable repeated line.
|
||
- Every song must have at least one [Chorus].
|
||
- Typical structure: Intro → Verse 1 → Chorus → Verse 2 → Chorus → Bridge → Chorus → Outro — adapt it to fit the genre and subject.
|
||
|
||
CONTENT RULES:
|
||
- The lyrics MUST be about the given subject. This is the #1 priority.
|
||
- Match the genre's typical vocabulary, tone, and energy level.
|
||
- Write in the specified language. If no language is specified, default to English.
|
||
- Be specific and vivid — concrete imagery beats abstract statements.
|
||
- Avoid AI clichés: neon, ethereal, embers, silhouette, static, void, shimmering, tapestry.
|
||
- Do NOT include commentary, explanations, or notes — lyrics only.
|
||
|
||
HOOK RULES:
|
||
- Every chorus MUST have a clear hook line that repeats at least once.
|
||
- The hook should be the emotional anchor. Good patterns:
|
||
- "Hook, develop, develop, Hook"
|
||
- "Hook, Hook, develop, resolve"
|
||
- For energetic genres (punk, rock, pop), add vocal exclamations where appropriate ("Oh!", "Whoa-oh!", etc.)
|
||
|
||
TITLE RULE:
|
||
- After all the lyrics, on its own line, write: Title: <song title>
|
||
- The title should be short (1-6 words), catchy, and relevant to the lyrics you wrote.
|
||
- Derive it from the hook or central theme — do not just restate the subject.
|
||
|
||
Output the lyrics first, then the title line. No other commentary.
|
||
`;
|
||
|
||
export const INSTAGEN_FULL_SYSTEM_PROMPT = `You are a talented songwriter and music producer. You will be given a musical genre/style, a song subject, and a language. Your job is to design a complete song — lyrics, rich descriptive tags, and all musical metadata — as a single JSON object.
|
||
|
||
OUTPUT FORMAT (MANDATORY):
|
||
Return ONLY a valid JSON object with exactly these keys:
|
||
{
|
||
"tags": "150-200+ word natural language description of the complete sonic portrait",
|
||
"lyrics": "[Intro]\\n\\n[Verse 1]\\n...",
|
||
"title": "Song Title",
|
||
"bpm": 120,
|
||
"key": "C minor",
|
||
"time_signature": "4/4",
|
||
"duration": 210
|
||
}
|
||
|
||
Do NOT include any text outside the JSON object. No markdown, no explanation, no commentary.
|
||
|
||
=== TAGS (the "tags" field) ===
|
||
|
||
The tags field is the most critical part. It is a natural language description of the track's COMPLETE sonic identity — not a list of genre labels, but a vivid portrait of exactly what the listener will hear. Write it as flowing prose, 150-200+ words.
|
||
|
||
Your tags MUST cover these dimensions:
|
||
1. GENRE & SONIC FOUNDATION: Specific genre/subgenre blend, era and regional influence, foundational sonic character
|
||
2. RHYTHM & PERCUSSION: Drum machine or live kit specifics, pattern details, tempo feel (driving, laid-back, swung), percussive texture
|
||
3. HARMONIC & MELODIC ESSENCE: Chord progression character (suspended, dissonant, warm jazz voicings), melodic movement qualities, scale/mode colour
|
||
4. VOCAL STYLE & DELIVERY: Register/range, delivery character (breathy, aggressive, intimate, theatrical), vocal techniques, emotional embodiment
|
||
5. PRODUCTION TECHNIQUES: Effects (granular delay, tape saturation, sidechain compression), reverb types (plate, spring, cathedral), distortion character
|
||
6. SPATIAL CHARACTERISTICS: Stereo width, depth placement (intimate/distant), movement in space, layering
|
||
7. TIMBRAL QUALITIES: Warmth vs coldness, brightness vs darkness, analog vs digital character, frequency balance
|
||
8. UNIQUE SONIC SIGNATURE: What makes THIS track unmistakable — the defining element a listener would recognise in 3 seconds
|
||
|
||
BAD tags (too generic):
|
||
"Upbeat pop song with catchy melody, energetic drums, and bright synths. Positive vibes with clean production."
|
||
|
||
GOOD tags (rich and specific):
|
||
"Thunderous 808 bass tuned precisely to root note sustains with controlled decay creating physical chest-hitting impact. Hi-hat programming alternates between machine-gun triplet rolls and crisp straight sixteenth-note patterns with velocity variations creating natural human groove. Snare hits combine layered acoustic snap with synthetic clap creating sharp transient attack. Vocal delivery features confident mid-range flow with rhythmic cadence, processed through subtle pitch correction maintaining modern polished character while preserving natural tonal variation. Ad-libs strategically panned wide across stereo field with distinct processing creating call-and-response dialogue."
|
||
|
||
CRITICAL TAG RULES:
|
||
- Tags describe the SOUND, not the structure or timeline. Never write "verse starts with..." or "chorus builds to..."
|
||
- Write in English regardless of lyric language
|
||
- Be specific: "breathy female vocal with subtle plate reverb" not just "female vocal"
|
||
- Match the genre's real-world production aesthetic
|
||
|
||
=== LYRICS (the "lyrics" field) ===
|
||
|
||
FORMATTING:
|
||
- Section headers use square brackets: [Verse 1], [Chorus], [Bridge], etc.
|
||
- VALID LABELS: [Intro], [Verse 1], [Verse 2], [Verse 3], [Pre-Chorus], [Chorus], [Post-Chorus], [Bridge], [Interlude], [Outro], [Instrumental Break]
|
||
- Section annotations with context are encouraged: [Verse 1: Female Vocal], [Chorus - High Energy with Layered Vocals], [Bridge - Atmospheric and Sparse], [Instrumental Break: Saxophone Solo]
|
||
- Every lyric line must end with punctuation
|
||
- Begin with an [Intro] section (instrumental, no lyrics — just the header) before the first verse
|
||
|
||
STRUCTURE:
|
||
- VERSES: keep line counts even — typically 4 or 8 lines each
|
||
- CHORUSES: keep line counts even — typically 4, 6, or 8 lines. Must have a clear hook — one memorable repeated line
|
||
- Every song must have at least one [Chorus]
|
||
- Typical structure: Intro → Verse 1 → Chorus → Verse 2 → Chorus → Bridge → Chorus → Outro — adapt it to fit the genre and subject
|
||
- Add instrumental breaks between major sections where appropriate for the genre
|
||
|
||
QUALITY:
|
||
- The lyrics MUST be about the given subject — this is the #1 priority
|
||
- Write like a real human artist — specific, vivid, concrete imagery, not abstract platitudes
|
||
- Match the genre's typical vocabulary, tone, and energy level
|
||
- Write in the specified language (tags stay in English)
|
||
- Avoid AI clichés: neon, ethereal, embers, silhouette, static, void, shimmering, tapestry
|
||
- Every chorus MUST have a hook line that repeats at least once
|
||
|
||
=== TITLE ===
|
||
Short (1-6 words), catchy, derived from the hook or central theme. Not just restating the subject.
|
||
|
||
=== BPM ===
|
||
Choose a realistic tempo (30-300) that fits the genre:
|
||
- Ballads: 60-80, Pop: 100-130, Rock: 110-140, Punk: 150-180
|
||
- EDM/Dance: 120-150, Hip-Hop: 80-100, R&B: 70-100, Folk: 90-120
|
||
- Drum & Bass: 160-180, Reggae: 60-90, Jazz: 80-140
|
||
|
||
=== KEY ===
|
||
Use standard notation: note name + Major/Minor (e.g. "C Major", "A Minor", "F# Minor", "Bb Major").
|
||
Match the key to the emotional intent:
|
||
- Major keys: brighter, more optimistic
|
||
- Minor keys: darker, more introspective
|
||
- Common emotional associations: C major (pure, optimistic), D major (triumphant), A minor (melancholic), E minor (romantic sadness), F# minor (passionate longing)
|
||
|
||
=== TIME SIGNATURE ===
|
||
- "4/4": Standard (vast majority of popular music)
|
||
- "3/4": Waltz/ballad feel, flowing
|
||
- "6/8": Compound meter, each beat divides into 3
|
||
- "5/4" or "7/8": Complex/progressive (use sparingly)
|
||
|
||
=== DURATION ===
|
||
Estimate total track duration in seconds. Consider the BPM, number of sections, and genre norms:
|
||
- Short/radio: 150-210s, Standard: 210-270s, Extended: 270-360s
|
||
- A bar of 4/4 at the chosen BPM = 240/BPM seconds
|
||
- Include time for intro, instrumental breaks, and outro
|
||
`;
|
||
|
||
// ── Prompt Builders ─────────────────────────────────────────────────────────
|
||
|
||
export function buildMetadataPrompt(
|
||
profile: PromptProfile,
|
||
usedSubjects: string[],
|
||
usedBpms: number[],
|
||
usedKeys: string[],
|
||
usedDurations: number[],
|
||
userSubject?: string
|
||
): string {
|
||
const lines: string[] = [`Artist: ${profile.artist}`];
|
||
if (profile.album) lines.push(`Album style: ${profile.album}`);
|
||
if (profile.themes?.length) lines.push(`Themes: ${profile.themes.join(', ')}`);
|
||
if (profile.tone_and_mood) lines.push(`Tone & mood: ${profile.tone_and_mood}`);
|
||
if (profile.additional_notes) lines.push(`Additional notes: ${profile.additional_notes}`);
|
||
if (profile.perspective) lines.push(`Perspective / voice: ${profile.perspective}`);
|
||
|
||
// Audio truth from the source recordings (Training Studio export). Guidance,
|
||
// not a cage: the planner may leave the ranges when the subject demands it.
|
||
const enrich: AlbumEnrichment | null = profile.audio_enrichment ?? null;
|
||
if (enrich) {
|
||
lines.push('', ...formatAlbumEnrichment(enrich));
|
||
if (enrich.bpmMax > 0) {
|
||
lines.push(`Choose a BPM inside the album's measured range (${enrich.bpmMin}–${enrich.bpmMax}) unless the subject truly demands otherwise.`);
|
||
}
|
||
if (enrich.keys.length) {
|
||
lines.push('Prefer a key from this album\'s list (or a closely related key) so the song sits in the same tonal world.');
|
||
}
|
||
if (enrich.genres.length) {
|
||
lines.push(`Anchor the caption's genre language to the detected genre: ${enrich.genres.slice(0, 6).join(', ')}.`);
|
||
}
|
||
if (enrich.captionExamples.length) {
|
||
lines.push('', 'Captions describing this album\'s actual recordings:');
|
||
enrich.captionExamples.forEach((c, i) => lines.push(` ${i + 1}. "${c}"`));
|
||
lines.push('Write the new song\'s "caption" in the SAME format, register and level of detail as these examples — consistent caption phrasing keeps a sound adapter trained on this album accurate.');
|
||
}
|
||
}
|
||
|
||
const observedStructures = cleanBlueprints(profile.structure_blueprints);
|
||
if (observedStructures.length) {
|
||
lines.push(`\nStructures observed in this artist's songs (codes: ${BLUEPRINT_CODES_LEGEND}):`);
|
||
lines.push(` ${observedStructures.join(' | ')}`);
|
||
lines.push('Choose this song\'s "structure" from these or a close variation — pick the shape that best fits the subject, and vary it across generations.');
|
||
}
|
||
|
||
if (profile.song_subjects && typeof profile.song_subjects === 'object') {
|
||
lines.push('\nOriginal song subjects (for reference):');
|
||
for (const [songTitle, subject] of Object.entries(profile.song_subjects)) {
|
||
lines.push(` • ${songTitle}: ${subject}`);
|
||
}
|
||
}
|
||
if (profile.subject_categories?.length) {
|
||
lines.push(`\nThematic categories: ${profile.subject_categories.join(', ')}`);
|
||
}
|
||
if (userSubject) {
|
||
lines.push(`\nThe subject for this song has been chosen by the user: "${userSubject}"`);
|
||
lines.push('Use this exact subject. Plan the BPM, key, caption, structure, and duration to complement it.');
|
||
} else {
|
||
if (usedSubjects?.length) {
|
||
lines.push('\nSubjects ALREADY USED (do NOT repeat these):');
|
||
for (const s of usedSubjects) lines.push(` ✗ ${s}`);
|
||
}
|
||
}
|
||
if (usedKeys?.length) lines.push(`\nKeys ALREADY USED (try different ones): ${usedKeys.join(', ')}`);
|
||
lines.push('\nPlan the metadata for the next song:');
|
||
return lines.join('\n');
|
||
}
|
||
|
||
export function buildGenerationPrompt(
|
||
profile: PromptProfile,
|
||
extraInstructions?: string,
|
||
targetDuration?: number,
|
||
bpm?: number,
|
||
chosenStructure?: string
|
||
): string {
|
||
const lines: string[] = [`Artist: ${profile.artist}`];
|
||
if (profile.album) lines.push(`Album style: ${profile.album}`);
|
||
|
||
lines.push('', '=== STYLISTIC PROFILE ===', '');
|
||
lines.push(`Themes: ${(profile.themes || []).join(', ')}`);
|
||
lines.push(`Common subjects / motifs: ${(profile.common_subjects || []).join(', ')}`);
|
||
lines.push(`Rhyme schemes: ${(profile.rhyme_schemes || []).join(', ')}`);
|
||
lines.push(`Average verse length: ${profile.avg_verse_lines} lines`);
|
||
lines.push(`Average chorus length: ${profile.avg_chorus_lines} lines`);
|
||
if (profile.vocabulary_notes) lines.push(`Vocabulary: ${stripLyricQuotes(profile.vocabulary_notes)}`);
|
||
if (profile.tone_and_mood) lines.push(`Tone & mood: ${stripLyricQuotes(profile.tone_and_mood)}`);
|
||
if (profile.structural_patterns) lines.push(`Structural patterns: ${stripLyricQuotes(profile.structural_patterns)}`);
|
||
{
|
||
// One context line of measured audio truth — genre + tempo shape the lyric's
|
||
// energy and pacing even though bpm/key/caption are planned elsewhere.
|
||
const enrich: AlbumEnrichment | null = profile.audio_enrichment ?? null;
|
||
if (enrich && (enrich.genres.length || enrich.bpmMax > 0)) {
|
||
const bits: string[] = [];
|
||
if (enrich.genres.length) bits.push(`genre ${enrich.genres.slice(0, 5).join(', ')}`);
|
||
if (enrich.bpmMax > 0) bits.push(enrich.bpmMin === enrich.bpmMax ? `${enrich.bpmMax} BPM` : `${enrich.bpmMin}–${enrich.bpmMax} BPM`);
|
||
lines.push(`Musical context (measured from the source recordings): ${bits.join('; ')}.`);
|
||
}
|
||
}
|
||
|
||
// Structure: planned by the metadata step if provided, otherwise sampled from
|
||
// the artist's observed blueprints. Presented as a default shape, not a mandate.
|
||
const observedStructures = cleanBlueprints(profile.structure_blueprints);
|
||
const bp = normalizeBlueprint(chosenStructure)
|
||
?? pickBlueprint(profile.structure_blueprints);
|
||
const bpParts = bp.split('-');
|
||
lines.push('', '=== SONG STRUCTURE ===');
|
||
if (observedStructures.length) {
|
||
lines.push(`Structures observed in this artist's songs (codes: ${BLUEPRINT_CODES_LEGEND}):`);
|
||
lines.push(` ${observedStructures.join(' | ')}`);
|
||
}
|
||
lines.push(`${chosenStructure ? 'Planned' : 'Suggested'} structure for this song: ${blueprintToSections(bp).join(' → ')}`);
|
||
lines.push('Treat this as the default shape, not a cage — you may adapt it (add or drop a Pre-Chorus, vary the verse count, move the Bridge) as long as the song stays within this artist\'s structural vocabulary and keeps roughly this many sections, because the duration budget below is planned around that count.');
|
||
lines.push('Every song needs at least one [Chorus].');
|
||
|
||
if (profile.perspective) lines.push(`Perspective / voice: ${profile.perspective}`);
|
||
|
||
const ms = profile.meter_stats;
|
||
if (ms) {
|
||
lines.push('', '=== LINE LENGTH & METER ===');
|
||
lines.push(`Average: ~${ms.avg_syllables_per_line ?? '?'} syllables/line, ~${ms.avg_words_per_line ?? '?'} words/line`);
|
||
lines.push(`Standard deviation: ±${ms.syllable_std_dev ?? '?'} syllables (VARY your line lengths!)`);
|
||
const llv = ms.line_length_variation;
|
||
if (llv?.histogram) {
|
||
const histStr = Object.entries(llv.histogram).map(([k, v]) => `${k} syl: ${v}%`).join(', ');
|
||
lines.push(`Syllable distribution: ${histStr}`);
|
||
lines.push('Match this distribution — NOT all lines the same length!');
|
||
}
|
||
}
|
||
|
||
const rs = profile.repetition_stats;
|
||
if (rs) {
|
||
lines.push('', '=== REPETITION & HOOKS ===');
|
||
lines.push(`Chorus repetition: ${rs.chorus_repetition_pct ?? 0}% of chorus lines are repeats`);
|
||
lines.push(`Pattern: ${rs.pattern || 'unknown'}`);
|
||
if ((rs.chorus_repetition_pct ?? 0) >= 20) lines.push('You MUST use repeated lines in your chorus to create a hook effect.');
|
||
if (rs.hook_examples?.length) lines.push(`Hook examples: ${rs.hook_examples.slice(0, 3).join('; ')}`);
|
||
}
|
||
|
||
const vs = profile.vocabulary_stats;
|
||
if (vs) {
|
||
lines.push('', '=== VOCABULARY ===');
|
||
lines.push(`Level: ${vs.contraction_pct ?? 0}% contractions, ${vs.profanity_pct ?? 0}% profanity`);
|
||
lines.push(`Type-token ratio: ${vs.type_token_ratio ?? '?'} (${vs.unique_words ?? '?'} unique / ${vs.total_words ?? '?'} total)`);
|
||
if (vs.distinctive_words?.length) lines.push(`Use words like: ${vs.distinctive_words.slice(0, 10).join(', ')}`);
|
||
}
|
||
|
||
if (profile.rhyme_quality) {
|
||
const rq = profile.rhyme_quality;
|
||
const total = Object.values(rq as Record<string, number>).reduce((a: number, b: number) => a + b, 0);
|
||
if (total > 0) {
|
||
lines.push(`Rhyme mix: ${Math.round(100 * (rq.perfect || 0) / total)}% perfect, ${Math.round(100 * (rq.slant || 0) / total)}% slant, ${Math.round(100 * (rq.assonance || 0) / total)}% assonance`);
|
||
}
|
||
}
|
||
|
||
if (profile.narrative_techniques) lines.push(`Narrative techniques: ${stripLyricQuotes(profile.narrative_techniques)}`);
|
||
if (profile.imagery_patterns) lines.push(`Imagery patterns: ${stripLyricQuotes(profile.imagery_patterns)}`);
|
||
if (profile.signature_devices) lines.push(`Signature devices: ${stripLyricQuotes(profile.signature_devices)}`);
|
||
if (profile.emotional_arc) lines.push(`Emotional arc: ${stripLyricQuotes(profile.emotional_arc)}`);
|
||
|
||
if (profile.raw_summary) lines.push('', '=== PROSE SUMMARY ===', '', stripLyricQuotes(profile.raw_summary));
|
||
if (extraInstructions) lines.push('', '=== EXTRA INSTRUCTIONS ===', '', extraInstructions);
|
||
|
||
if (profile.representative_excerpts?.length) {
|
||
lines.push('', '=== REPRESENTATIVE EXCERPTS (STYLE REFERENCE ONLY — DO NOT COPY) ===');
|
||
lines.push(...profile.representative_excerpts.slice(0, 10).flatMap((e: string) => [e, '---']));
|
||
}
|
||
|
||
if (targetDuration && targetDuration > 0 && bpm && bpm > 0) {
|
||
const barSeconds = 240.0 / bpm;
|
||
const totalBars = Math.round(targetDuration / barSeconds);
|
||
const sectionCount = bpParts.length;
|
||
const transitionBars = (sectionCount - 1) * 2;
|
||
|
||
// BPM-aware bars-per-line: fast tempos need more bars per line because
|
||
// each bar is shorter in real time and the music model needs breathing room.
|
||
// Scale from 2.5 bars/line at ≤80 BPM to 4.0 bars/line at ≥180 BPM.
|
||
const barsPerLine = Math.min(4.0, Math.max(2.5, 2.5 + 1.5 * ((bpm - 80) / 100)));
|
||
const singableBars = totalBars - transitionBars;
|
||
const maxLyricLines = Math.max(8, Math.floor(singableBars / barsPerLine));
|
||
const minutes = Math.floor(targetDuration / 60);
|
||
const seconds = Math.round(targetDuration % 60);
|
||
|
||
lines.push('', '=== DURATION BUDGET (HARD LIMIT — DO NOT EXCEED) ===');
|
||
lines.push(`Target duration: ${targetDuration} seconds (${minutes}:${String(seconds).padStart(2, '0')})`);
|
||
lines.push(`BPM: ${bpm} — one bar of 4/4 = ${barSeconds.toFixed(1)} seconds`);
|
||
lines.push(`Total bars available: ~${totalBars} bars for the entire song`);
|
||
lines.push(`At this tempo, each lyric line needs ~${barsPerLine.toFixed(1)} bars (vocal delivery + melodic phrasing).`);
|
||
lines.push(`After accounting for ~${transitionBars} bars of transitions, you have ~${singableBars} singable bars.`);
|
||
lines.push(`*** MAXIMUM LYRIC LINES: ${maxLyricLines} total lines across ALL sections. ***`);
|
||
lines.push(`This is a HARD LIMIT. The music model WILL skip lines if you write more than ${maxLyricLines}.`);
|
||
lines.push('');
|
||
lines.push('USE THIS TO DECIDE LINE COUNTS:');
|
||
if (maxLyricLines <= 16) {
|
||
lines.push('- This is a SHORT/FAST song. Use 4-line verses and 4-line choruses ONLY. Minimal sections.');
|
||
lines.push('- Consider dropping one verse or chorus from the suggested structure if needed to stay under the line limit.');
|
||
} else if (maxLyricLines <= 28) {
|
||
lines.push('- This is a STANDARD-length song. Use 4-line verses and 4-line choruses. One verse can be 6 or 8 lines if budget allows.');
|
||
} else {
|
||
lines.push('- This is a LONGER song. You can use 6-8 line verses and choruses if the structure calls for it.');
|
||
}
|
||
lines.push(`- Count your total lyric lines before finalising. If you exceed ${maxLyricLines} lines, the music model WILL skip content.`);
|
||
}
|
||
|
||
lines.push(
|
||
'', '=== FINAL REMINDERS ===',
|
||
'1. SECTION LENGTHS: Even line counts for verses and choruses (4, 6, or 8) — never 5 or 7. Match the artist\'s averages.',
|
||
'2. HOOK: Each chorus needs a hook line that repeats.',
|
||
'3. *** ZERO TOLERANCE FOR COPYING ***',
|
||
'4. NO SLOP: Do not use neon, fluorescent, embers, silhouette, static, void, ethereal, or any AI cliché.',
|
||
'5. MINIMIZE OVERUSED WORDS: heavy, broken, cold, dust, ghost, machine, nothing, nowhere, searching, watch, burn, fade, wash, sold, dead, blood, gold, same — use at most ONCE if at all.',
|
||
'6. NO TECH-SLOP: The words digital, algorithm, chrome, code, circuit, grid, data, wire are BANNED. Do not force tech/digital metaphors onto non-tech artists.',
|
||
"7. VOCABULARY DIVERSITY: A Snoop Dogg song must NOT sound like a Joy Division song. Use THIS artist's actual vocabulary.",
|
||
'8. HOOK MUST BE SPECIFIC: The chorus hook must contain a concrete image or phrase from THIS song — not a generic imperative like "Watch it burn" or "Let it fade". If the hook could fit in any song by any artist, rewrite it.',
|
||
'',
|
||
'Now write the song (lyrics only, starting with [Intro] or [Verse 1] — no title line):',
|
||
);
|
||
return lines.join('\n');
|
||
}
|
||
|
||
export function buildRefinementPrompt(
|
||
originalLyrics: string,
|
||
artistName: string,
|
||
title: string,
|
||
profile?: PromptProfile,
|
||
originalSlop?: string[]
|
||
): string {
|
||
const lines = [`Artist: ${artistName}`, `Original Title: ${title}`, ''];
|
||
if (profile) {
|
||
lines.push('=== INTENDED LANE PROFILE (match this style) ===');
|
||
lines.push(`Themes: ${(profile.themes || []).slice(0, 8).join(', ')}`);
|
||
if (profile.tone_and_mood) lines.push(`Tone & mood: ${profile.tone_and_mood}`);
|
||
if (profile.vocabulary_notes) lines.push(`Vocabulary: ${profile.vocabulary_notes}`);
|
||
if (profile.imagery_patterns) lines.push(`Imagery patterns: ${profile.imagery_patterns}`);
|
||
if (profile.signature_devices) lines.push(`Signature devices: ${profile.signature_devices}`);
|
||
if (profile.narrative_techniques) lines.push(`Narrative techniques: ${profile.narrative_techniques}`);
|
||
if (profile.emotional_arc) lines.push(`Emotional arc: ${profile.emotional_arc}`);
|
||
if (profile.structural_patterns) lines.push(`Structure: ${profile.structural_patterns}`);
|
||
if (profile.perspective) lines.push(`Perspective / voice: ${profile.perspective}`);
|
||
if (profile.rhyme_schemes?.length) lines.push(`Rhyme schemes: ${profile.rhyme_schemes.join(', ')}`);
|
||
if (profile.rhyme_quality) {
|
||
const rq = profile.rhyme_quality;
|
||
const total = Object.values(rq as Record<string, number>).reduce((a: number, b: number) => a + b, 0);
|
||
if (total > 0) lines.push(`Rhyme mix: ${Math.round(100 * (rq.perfect || 0) / total)}% perfect, ${Math.round(100 * (rq.slant || 0) / total)}% slant, ${Math.round(100 * (rq.assonance || 0) / total)}% assonance`);
|
||
}
|
||
const ms = profile.meter_stats;
|
||
if (ms) lines.push(`Line density: ~${ms.avg_syllables_per_line ?? '?'} syl/line (σ=${ms.syllable_std_dev ?? '?'}), ~${ms.avg_words_per_line ?? '?'} words/line`);
|
||
const rs = profile.repetition_stats;
|
||
if (rs) {
|
||
lines.push(`Hook behavior: ${rs.pattern || 'unknown'} (${rs.chorus_repetition_pct ?? 0}% chorus repetition)`);
|
||
if ((rs.chorus_repetition_pct ?? 0) >= 20) lines.push('Calibration: This artist uses heavy chorus repetition — ensure hook lines repeat.');
|
||
else if ((rs.chorus_repetition_pct ?? 0) < 15) lines.push('Calibration: This artist uses light repetition — be subtle with hooks.');
|
||
}
|
||
if (profile.avg_verse_lines || profile.avg_chorus_lines) lines.push(`Verse/chorus: avg ${profile.avg_verse_lines} verse lines, avg ${profile.avg_chorus_lines} chorus lines`);
|
||
if (profile.song_subjects && typeof profile.song_subjects === 'object') {
|
||
const titles = Object.keys(profile.song_subjects);
|
||
if (titles.length) {
|
||
lines.push('', '=== ORIGINAL SONG TITLES (check for plagiarism) ===');
|
||
for (const t of titles) lines.push(` • ${t}`);
|
||
}
|
||
}
|
||
lines.push('');
|
||
}
|
||
if (originalSlop?.length) {
|
||
lines.push('=== KNOWN ISSUES TO FIX ===');
|
||
lines.push('The original lyrics contain the following AI-clichés or circular phrases that MUST be replaced:');
|
||
lines.push(`Words/Phrases to Remove: ${originalSlop.join(', ')}`);
|
||
lines.push('');
|
||
}
|
||
lines.push('=== ORIGINAL LYRICS ===', '', originalLyrics, '', '=== INSTRUCTIONS ===', '');
|
||
lines.push('Refine the lyrics above according to the refinement rules.');
|
||
lines.push('Keep as much of the original as possible — only change what genuinely needs fixing.');
|
||
lines.push(`Maintain ${artistName}'s distinctive style throughout.`);
|
||
lines.push('Now output the refined version (Title line first, then lyrics with [Section] headers):');
|
||
return lines.join('\n');
|
||
}
|
||
|
||
export function buildTitlePrompt(
|
||
lyrics: string, artistName: string, album?: string, usedTitles?: string[]
|
||
): string {
|
||
const lines: string[] = [`Artist: ${artistName}`];
|
||
if (album) lines.push(`Album style: ${album}`);
|
||
if (usedTitles?.length) {
|
||
lines.push('\nTitles already used (avoid these and their key words):');
|
||
for (const t of usedTitles) lines.push(` ✗ ${t}`);
|
||
}
|
||
lines.push('\n--- LYRICS ---', lyrics, '--- END LYRICS ---');
|
||
lines.push('\nChoose the best title for this song:');
|
||
return lines.join('\n');
|
||
}
|
||
|
||
export function buildProfilePrompt(
|
||
artist: string, album: string | null, songs: Array<{ title: string; lyrics: string }>, ruleStats: any
|
||
): string {
|
||
let header = `Artist: ${artist}\n`;
|
||
if (album) header += `Album: ${album}\n`;
|
||
header += `Songs analysed: ${songs.length}\n`;
|
||
|
||
// Songs exported from the Training Studio carry measured audio facts — give
|
||
// the analyst ground truth for genre/tempo instead of a lyrics-only guess.
|
||
const enrich = computeAlbumEnrichment(songs);
|
||
if (enrich) {
|
||
header += '\n' + formatAlbumEnrichment(enrich).join('\n') + '\n';
|
||
header += 'Treat the detected genre and tempo as ground truth — fold them into tone_and_mood and additional_notes rather than inferring a genre from the lyrics alone.\n';
|
||
}
|
||
|
||
header += `\n=== RULE-BASED ANALYSIS ===\n`;
|
||
|
||
header += `Average verse length: ${ruleStats.avg_verse_lines} lines\n`;
|
||
header += `Average chorus length: ${ruleStats.avg_chorus_lines} lines\n`;
|
||
header += `Top rhyme schemes: ${ruleStats.rhyme_schemes.join(', ')}\n`;
|
||
const rq = ruleStats.rhyme_quality;
|
||
header += `Rhyme quality breakdown: ${rq.perfect} perfect, ${rq.slant} slant, ${rq.assonance} assonance\n`;
|
||
header += `Structure blueprints: ${ruleStats.structure_blueprints.join(', ')}\n`;
|
||
header += `Perspective: ${ruleStats.perspective}\n`;
|
||
|
||
const ms = ruleStats.meter_stats;
|
||
header += `Meter: avg ${ms.avg_syllables_per_line} syllables/line (σ=${ms.syllable_std_dev}), ${ms.avg_words_per_line} words/line, range ${ms.line_length_range}\n`;
|
||
|
||
const vs = ruleStats.vocabulary_stats;
|
||
header += `Vocabulary: ${vs.total_words} total words, ${vs.unique_words} unique, TTR=${vs.type_token_ratio}\n`;
|
||
header += `Contractions: ${vs.contraction_pct}% of words\nProfanity: ${vs.profanity_pct}% of words\n`;
|
||
header += `Distinctive words: ${vs.distinctive_words.join(', ')}\n`;
|
||
|
||
const llv = ms.line_length_variation || {};
|
||
if (llv.histogram) {
|
||
header += `Syllable distribution: ${Object.entries(llv.histogram).map(([k, v]) => `${k}: ${v}%`).join(', ')}\n`;
|
||
}
|
||
|
||
const rs = ruleStats.repetition_stats;
|
||
if (rs) {
|
||
header += `Chorus repetition: ${rs.chorus_repetition_pct || 0}% of chorus lines are repeats\n`;
|
||
header += `Repetition pattern: ${rs.pattern || 'unknown'}\n`;
|
||
if (rs.hook_examples?.length) header += `Hook examples: ${rs.hook_examples.slice(0, 3).join('; ')}\n`;
|
||
}
|
||
|
||
let lyricsSection = "\n=== COMPLETE LYRICS ===\n\n";
|
||
for (const s of songs) lyricsSection += `--- ${s.title} ---\n${s.lyrics}\n\n`;
|
||
|
||
return header + lyricsSection;
|
||
}
|
||
|
||
export function buildSubjectAnalysisPrompt(songs: Array<{ title: string; lyrics: string }>): string {
|
||
const songList = songs.map(s => `--- ${s.title} ---\n${s.lyrics.substring(0, 500)}`).join('\n\n');
|
||
return `Analyse the subjects of these ${songs.length} songs:\n\n${songList}`;
|
||
}
|