ss
Browse files- app.py +109 -66
- beat_analysis.py +12 -12
app.py
CHANGED
|
@@ -282,41 +282,42 @@ ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
|
|
| 282 |
PRIMARY THEME: {theme}
|
| 283 |
EMOTION: {emotion}
|
| 284 |
|
| 285 |
-
I need EXACTLY {num_phrases} lines of lyrics with these requirements:
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
1.
|
| 289 |
-
2.
|
| 290 |
-
3.
|
| 291 |
-
4.
|
| 292 |
-
5.
|
| 293 |
-
6.
|
|
|
|
|
|
|
| 294 |
|
| 295 |
FORMAT:
|
| 296 |
-
- Write exactly {num_phrases}
|
| 297 |
-
-
|
| 298 |
-
|
| 299 |
-
===== EXAMPLES OF CONNECTED THOUGHTS =====
|
| 300 |
|
| 301 |
-
|
| 302 |
-
{ex1_line1}
|
| 303 |
-
{ex1_line2}
|
| 304 |
-
{ex1_line3}
|
| 305 |
-
{ex1_line4}
|
| 306 |
|
| 307 |
-
|
| 308 |
|
| 309 |
-
Example
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
|
| 315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
|
| 317 |
-
|
| 318 |
|
| 319 |
-
|
| 320 |
"""
|
| 321 |
|
| 322 |
# Generate lyrics using the LLM model
|
|
@@ -500,7 +501,16 @@ Just write {num_phrases} lines of original lyrics, focusing on connecting your l
|
|
| 500 |
# 9. Filter out any remaining empty lines after tag removal
|
| 501 |
clean_lines = [line for line in clean_lines if line.strip() and not line.isspace()]
|
| 502 |
|
| 503 |
-
# 10. NEW:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
cliched_patterns = [
|
| 505 |
r'moonlight (shimmers?|falls?|dances?)',
|
| 506 |
r'shadows? (dance|play|fall|stretch)',
|
|
@@ -530,7 +540,7 @@ Just write {num_phrases} lines of original lyrics, focusing on connecting your l
|
|
| 530 |
else:
|
| 531 |
cliche_percentage = 0
|
| 532 |
|
| 533 |
-
#
|
| 534 |
if lyric_templates:
|
| 535 |
num_required = len(lyric_templates)
|
| 536 |
|
|
@@ -544,7 +554,7 @@ Just write {num_phrases} lines of original lyrics, focusing on connecting your l
|
|
| 544 |
i = len(clean_lines)
|
| 545 |
if i < len(lyric_templates):
|
| 546 |
template = lyric_templates[i]
|
| 547 |
-
target_syllables = min(
|
| 548 |
|
| 549 |
# Generate more creative, contextual placeholders with specificity
|
| 550 |
# Avoid clichés like "moonlight shimmers" or "time slips away"
|
|
@@ -559,46 +569,38 @@ Just write {num_phrases} lines of original lyrics, focusing on connecting your l
|
|
| 559 |
],
|
| 560 |
# 3-4 syllables - specific contexts
|
| 561 |
3: [
|
| 562 |
-
"Coffee
|
| 563 |
-
"Fan blades
|
| 564 |
-
"Pages
|
| 565 |
-
"Neighbors
|
| 566 |
-
"Radio
|
| 567 |
],
|
| 568 |
# 4-5 syllables - specific details
|
| 569 |
4: [
|
| 570 |
-
"Fingers tap
|
| 571 |
"Taxi waits in rain",
|
| 572 |
-
"Laptop screen
|
| 573 |
-
"
|
| 574 |
-
"
|
| 575 |
],
|
| 576 |
# 5-6 syllables - context rich
|
| 577 |
5: [
|
| 578 |
-
"Letters
|
| 579 |
-
"
|
| 580 |
-
"
|
| 581 |
-
"
|
| 582 |
-
"Smoke
|
| 583 |
-
],
|
| 584 |
-
# 6-7 syllables - specific scenarios
|
| 585 |
-
6: [
|
| 586 |
-
"Fingerprints on dusty frames",
|
| 587 |
-
"Afternoon bus running late",
|
| 588 |
-
"Yellowed photos in a box",
|
| 589 |
-
"Backyard swing rocks in the wind",
|
| 590 |
-
"Curtains move with summer breeze"
|
| 591 |
]
|
| 592 |
}
|
| 593 |
|
| 594 |
# Make theme and emotion specific placeholders to add to the list
|
| 595 |
theme_specific = []
|
| 596 |
if theme.lower() in ["love", "relationship", "romance"]:
|
| 597 |
-
theme_specific = ["Lipstick on
|
| 598 |
elif theme.lower() in ["loss", "grief", "sadness"]:
|
| 599 |
-
theme_specific = ["
|
| 600 |
elif theme.lower() in ["hope", "inspiration", "triumph"]:
|
| 601 |
-
theme_specific = ["Seeds
|
| 602 |
|
| 603 |
# Get the closest matching syllable group
|
| 604 |
closest_group = min(specific_placeholders.keys(), key=lambda k: abs(k - target_syllables))
|
|
@@ -615,9 +617,8 @@ Just write {num_phrases} lines of original lyrics, focusing on connecting your l
|
|
| 615 |
placeholder = available_placeholders[idx]
|
| 616 |
else:
|
| 617 |
# If we've used all placeholders, create something random and specific
|
| 618 |
-
subjects = ["Car", "Dog", "
|
| 619 |
-
verbs = ["waits", "moves", "stops", "falls", "breaks", "turns", "
|
| 620 |
-
modifiers = ["slowly", "loudly", "brightly", "quickly", "gently", "badly", "forever", "never", "always"]
|
| 621 |
|
| 622 |
# Ensure randomness with seed that changes with each call
|
| 623 |
import random
|
|
@@ -626,13 +627,9 @@ Just write {num_phrases} lines of original lyrics, focusing on connecting your l
|
|
| 626 |
subj = random.choice(subjects)
|
| 627 |
verb = random.choice(verbs)
|
| 628 |
|
| 629 |
-
|
| 630 |
-
placeholder = f"{subj} {verb}"
|
| 631 |
-
else:
|
| 632 |
-
mod = random.choice(modifiers)
|
| 633 |
-
placeholder = f"{subj} {verb} {mod}"
|
| 634 |
else:
|
| 635 |
-
placeholder = "
|
| 636 |
|
| 637 |
clean_lines.append(placeholder)
|
| 638 |
|
|
@@ -645,8 +642,8 @@ Just write {num_phrases} lines of original lyrics, focusing on connecting your l
|
|
| 645 |
Try regenerating for more original content.
|
| 646 |
|
| 647 |
{final_lyrics}"""
|
| 648 |
-
|
| 649 |
-
#
|
| 650 |
if not final_lyrics or len(final_lyrics) < 10:
|
| 651 |
return "The model generated only thinking content but no actual lyrics. Please try again."
|
| 652 |
|
|
@@ -868,6 +865,52 @@ def analyze_sentence_flow(lines):
|
|
| 868 |
"flow_quality": flow_quality
|
| 869 |
}
|
| 870 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
# Create Gradio interface
|
| 872 |
def create_interface():
|
| 873 |
with gr.Blocks(title="Music Analysis & Lyrics Generator") as demo:
|
|
|
|
| 282 |
PRIMARY THEME: {theme}
|
| 283 |
EMOTION: {emotion}
|
| 284 |
|
| 285 |
+
I need EXACTLY {num_phrases} lines of lyrics with these STRICT requirements:
|
| 286 |
+
|
| 287 |
+
CRITICAL INSTRUCTIONS:
|
| 288 |
+
1. EXTREMELY SHORT LINES: Each line MUST be between {min_syllables}-{max_syllables} syllables MAXIMUM
|
| 289 |
+
2. ENFORCE BREVITY: NO exceptions to the syllable limit - not a single line should exceed {max_syllables} syllables
|
| 290 |
+
3. FRAGMENT STYLE: Use sentence fragments and short phrases instead of complete sentences
|
| 291 |
+
4. CONNECTED THOUGHTS: Use prepositions and conjunctions at the start of lines to connect ideas
|
| 292 |
+
5. SIMPLE WORDS: Choose one or two-syllable words whenever possible
|
| 293 |
+
6. CONCRETE IMAGERY: Use specific, tangible details rather than abstract concepts
|
| 294 |
+
7. NO CLICHÉS: Avoid common phrases like "time slips away" or "memories fade"
|
| 295 |
+
8. ONE THOUGHT PER LINE: Express just one simple idea in each line
|
| 296 |
|
| 297 |
FORMAT:
|
| 298 |
+
- Write exactly {num_phrases} short text lines
|
| 299 |
+
- No annotations, explanations, or line numbers
|
| 300 |
+
- Do not count syllables in the output
|
|
|
|
| 301 |
|
| 302 |
+
IMPORTANT: If you can't express an idea in {max_syllables} or fewer syllables, break it across two lines or choose a simpler way to express it.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
+
===== EXAMPLES OF CORRECT LENGTH =====
|
| 305 |
|
| 306 |
+
Example 1 (short fragments connected by flow):
|
| 307 |
+
Cold tea cup (3 syllables)
|
| 308 |
+
on windowsill (3 syllables)
|
| 309 |
+
cat watches rain (3 syllables)
|
| 310 |
+
through foggy glass (3 syllables)
|
| 311 |
|
| 312 |
+
Example 2 (prepositional connections):
|
| 313 |
+
Keys dropped here (3 syllables)
|
| 314 |
+
by the front door (3 syllables)
|
| 315 |
+
where shoes pile up (3 syllables)
|
| 316 |
+
since you moved in (3 syllables)
|
| 317 |
|
| 318 |
+
DO NOT copy my examples. Create ENTIRELY NEW lyrics about {theme} with {emotion} feeling.
|
| 319 |
|
| 320 |
+
REMEMBER: NO LINE SHOULD EXCEED {max_syllables} SYLLABLES - this is the most important rule!
|
| 321 |
"""
|
| 322 |
|
| 323 |
# Generate lyrics using the LLM model
|
|
|
|
| 501 |
# 9. Filter out any remaining empty lines after tag removal
|
| 502 |
clean_lines = [line for line in clean_lines if line.strip() and not line.isspace()]
|
| 503 |
|
| 504 |
+
# 10. NEW: Apply strict syllable enforcement - split or truncate lines that are too long
|
| 505 |
+
# This is a critical step to ensure no line exceeds our max syllable count
|
| 506 |
+
if lyric_templates:
|
| 507 |
+
max_allowed_syllables = min(7, max([t.get('max_expected', 6) for t in lyric_templates]))
|
| 508 |
+
else:
|
| 509 |
+
max_allowed_syllables = 6
|
| 510 |
+
|
| 511 |
+
clean_lines = enforce_syllable_limits(clean_lines, max_allowed_syllables)
|
| 512 |
+
|
| 513 |
+
# 11. NEW: Check for template copying or clichéd phrases
|
| 514 |
cliched_patterns = [
|
| 515 |
r'moonlight (shimmers?|falls?|dances?)',
|
| 516 |
r'shadows? (dance|play|fall|stretch)',
|
|
|
|
| 540 |
else:
|
| 541 |
cliche_percentage = 0
|
| 542 |
|
| 543 |
+
# 12. If we have lyric templates, ensure we have the correct number of lines
|
| 544 |
if lyric_templates:
|
| 545 |
num_required = len(lyric_templates)
|
| 546 |
|
|
|
|
| 554 |
i = len(clean_lines)
|
| 555 |
if i < len(lyric_templates):
|
| 556 |
template = lyric_templates[i]
|
| 557 |
+
target_syllables = min(max_allowed_syllables - 1, (template.get('min_expected', 2) + template.get('max_expected', 6)) // 2)
|
| 558 |
|
| 559 |
# Generate more creative, contextual placeholders with specificity
|
| 560 |
# Avoid clichés like "moonlight shimmers" or "time slips away"
|
|
|
|
| 569 |
],
|
| 570 |
# 3-4 syllables - specific contexts
|
| 571 |
3: [
|
| 572 |
+
"Coffee gets cold",
|
| 573 |
+
"Fan blades spin",
|
| 574 |
+
"Pages turn slow",
|
| 575 |
+
"Neighbors talk",
|
| 576 |
+
"Radio hums soft"
|
| 577 |
],
|
| 578 |
# 4-5 syllables - specific details
|
| 579 |
4: [
|
| 580 |
+
"Fingers tap table",
|
| 581 |
"Taxi waits in rain",
|
| 582 |
+
"Laptop screen blinks",
|
| 583 |
+
"Ring left on sink",
|
| 584 |
+
"Church bells ring loud"
|
| 585 |
],
|
| 586 |
# 5-6 syllables - context rich
|
| 587 |
5: [
|
| 588 |
+
"Letters with no stamps",
|
| 589 |
+
"Watch shows wrong time",
|
| 590 |
+
"Jeans with torn knees",
|
| 591 |
+
"Dog barks next door",
|
| 592 |
+
"Smoke alarm beeps"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
]
|
| 594 |
}
|
| 595 |
|
| 596 |
# Make theme and emotion specific placeholders to add to the list
|
| 597 |
theme_specific = []
|
| 598 |
if theme.lower() in ["love", "relationship", "romance"]:
|
| 599 |
+
theme_specific = ["Lipstick on glass", "Text left on read", "Scent on your coat"]
|
| 600 |
elif theme.lower() in ["loss", "grief", "sadness"]:
|
| 601 |
+
theme_specific = ["Chair sits empty", "Photos face down", "Clothes in closet"]
|
| 602 |
elif theme.lower() in ["hope", "inspiration", "triumph"]:
|
| 603 |
+
theme_specific = ["Seeds start to grow", "Finish line waits", "New day breaks through"]
|
| 604 |
|
| 605 |
# Get the closest matching syllable group
|
| 606 |
closest_group = min(specific_placeholders.keys(), key=lambda k: abs(k - target_syllables))
|
|
|
|
| 617 |
placeholder = available_placeholders[idx]
|
| 618 |
else:
|
| 619 |
# If we've used all placeholders, create something random and specific
|
| 620 |
+
subjects = ["Car", "Dog", "Kid", "Clock", "Phone", "Tree", "Book", "Door", "Light"]
|
| 621 |
+
verbs = ["waits", "moves", "stops", "falls", "breaks", "turns", "sleeps"]
|
|
|
|
| 622 |
|
| 623 |
# Ensure randomness with seed that changes with each call
|
| 624 |
import random
|
|
|
|
| 627 |
subj = random.choice(subjects)
|
| 628 |
verb = random.choice(verbs)
|
| 629 |
|
| 630 |
+
placeholder = f"{subj} {verb}"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 631 |
else:
|
| 632 |
+
placeholder = "Page turns slow"
|
| 633 |
|
| 634 |
clean_lines.append(placeholder)
|
| 635 |
|
|
|
|
| 642 |
Try regenerating for more original content.
|
| 643 |
|
| 644 |
{final_lyrics}"""
|
| 645 |
+
|
| 646 |
+
# 13. Final sanity check - if we have nothing or garbage, return an error
|
| 647 |
if not final_lyrics or len(final_lyrics) < 10:
|
| 648 |
return "The model generated only thinking content but no actual lyrics. Please try again."
|
| 649 |
|
|
|
|
| 865 |
"flow_quality": flow_quality
|
| 866 |
}
|
| 867 |
|
| 868 |
+
def enforce_syllable_limits(lines, max_syllables=6):
|
| 869 |
+
"""
|
| 870 |
+
Enforce syllable limits by splitting or truncating lines that are too long.
|
| 871 |
+
Returns a modified list of lines where no line exceeds max_syllables.
|
| 872 |
+
"""
|
| 873 |
+
if not lines:
|
| 874 |
+
return []
|
| 875 |
+
|
| 876 |
+
result_lines = []
|
| 877 |
+
|
| 878 |
+
for line in lines:
|
| 879 |
+
words = line.split()
|
| 880 |
+
if not words:
|
| 881 |
+
continue
|
| 882 |
+
|
| 883 |
+
# Count syllables in the line
|
| 884 |
+
syllable_count = sum(count_syllables_for_word(word) for word in words)
|
| 885 |
+
|
| 886 |
+
# If within limits, keep the line as is
|
| 887 |
+
if syllable_count <= max_syllables:
|
| 888 |
+
result_lines.append(line)
|
| 889 |
+
continue
|
| 890 |
+
|
| 891 |
+
# Line is too long - we need to split or truncate it
|
| 892 |
+
current_line = []
|
| 893 |
+
current_syllables = 0
|
| 894 |
+
|
| 895 |
+
for word in words:
|
| 896 |
+
word_syllables = count_syllables_for_word(word)
|
| 897 |
+
|
| 898 |
+
# If adding this word would exceed the limit, start a new line
|
| 899 |
+
if current_syllables + word_syllables > max_syllables and current_line:
|
| 900 |
+
result_lines.append(" ".join(current_line))
|
| 901 |
+
current_line = [word]
|
| 902 |
+
current_syllables = word_syllables
|
| 903 |
+
else:
|
| 904 |
+
# Add the word to the current line
|
| 905 |
+
current_line.append(word)
|
| 906 |
+
current_syllables += word_syllables
|
| 907 |
+
|
| 908 |
+
# Don't forget the last line if there are words left
|
| 909 |
+
if current_line:
|
| 910 |
+
result_lines.append(" ".join(current_line))
|
| 911 |
+
|
| 912 |
+
return result_lines
|
| 913 |
+
|
| 914 |
# Create Gradio interface
|
| 915 |
def create_interface():
|
| 916 |
with gr.Blocks(title="Music Analysis & Lyrics Generator") as demo:
|
beat_analysis.py
CHANGED
|
@@ -276,16 +276,16 @@ class BeatAnalyzer:
|
|
| 276 |
visual_pattern += "weak "
|
| 277 |
|
| 278 |
# Estimate number of words based on beats (very rough estimate)
|
| 279 |
-
est_words = max(1, int(num_beats * 0.
|
| 280 |
|
| 281 |
-
# Estimate syllables - use
|
| 282 |
-
# For 4/4 time signature, we want to
|
| 283 |
if stress_pattern == "SWMW": # 4/4 time
|
| 284 |
-
min_syllables = max(1, int(num_beats * 0.
|
| 285 |
-
max_syllables = min(
|
| 286 |
else:
|
| 287 |
-
min_syllables = max(1, int(num_beats * 0.
|
| 288 |
-
max_syllables = min(
|
| 289 |
|
| 290 |
# Store these in the template for future reference
|
| 291 |
template['min_expected'] = min_syllables
|
|
@@ -294,7 +294,7 @@ class BeatAnalyzer:
|
|
| 294 |
guide = f"~{est_words} words, ~{min_syllables}-{max_syllables} syllables | Pattern: {visual_pattern}"
|
| 295 |
|
| 296 |
# Add additional guidance to the template for natural phrasing
|
| 297 |
-
template['phrasing_guide'] = "
|
| 298 |
|
| 299 |
return guide
|
| 300 |
|
|
@@ -315,13 +315,13 @@ class BeatAnalyzer:
|
|
| 315 |
min_ratio, typical_ratio, max_ratio = self.genre_syllable_ratios['default']
|
| 316 |
|
| 317 |
# Calculate flexible min and max syllable expectations based on genre
|
| 318 |
-
# Use
|
| 319 |
min_expected = max(1, int(expected_count * min_ratio))
|
| 320 |
-
max_expected = min(
|
| 321 |
|
| 322 |
-
# For 4/4 time signature, cap the max syllables per line
|
| 323 |
if template['stress_pattern'] == "SWMW": # 4/4 time
|
| 324 |
-
max_expected = min(max_expected,
|
| 325 |
|
| 326 |
# Record min and max expected in the template for future reference
|
| 327 |
template['min_expected'] = min_expected
|
|
|
|
| 276 |
visual_pattern += "weak "
|
| 277 |
|
| 278 |
# Estimate number of words based on beats (very rough estimate)
|
| 279 |
+
est_words = max(1, int(num_beats * 0.3)) # Reduced further to encourage extreme brevity
|
| 280 |
|
| 281 |
+
# Estimate syllables - use ultra conservative ranges
|
| 282 |
+
# For 4/4 time signature, we want to enforce extremely short phrases
|
| 283 |
if stress_pattern == "SWMW": # 4/4 time
|
| 284 |
+
min_syllables = max(1, int(num_beats * 0.4)) # Reduced from 0.5
|
| 285 |
+
max_syllables = min(6, int(num_beats * 1.2)) # Reduced from 1.3 to max 6
|
| 286 |
else:
|
| 287 |
+
min_syllables = max(1, int(num_beats * 0.4)) # Reduced from 0.5
|
| 288 |
+
max_syllables = min(6, int(num_beats * 1.1)) # Reduced from 1.2 to max 6
|
| 289 |
|
| 290 |
# Store these in the template for future reference
|
| 291 |
template['min_expected'] = min_syllables
|
|
|
|
| 294 |
guide = f"~{est_words} words, ~{min_syllables}-{max_syllables} syllables | Pattern: {visual_pattern}"
|
| 295 |
|
| 296 |
# Add additional guidance to the template for natural phrasing
|
| 297 |
+
template['phrasing_guide'] = "ULTRA SHORT LINES. One thought per line. Use FRAGMENTS not sentences."
|
| 298 |
|
| 299 |
return guide
|
| 300 |
|
|
|
|
| 315 |
min_ratio, typical_ratio, max_ratio = self.genre_syllable_ratios['default']
|
| 316 |
|
| 317 |
# Calculate flexible min and max syllable expectations based on genre
|
| 318 |
+
# Use extremely conservative ranges to enforce ultra-short lines
|
| 319 |
min_expected = max(1, int(expected_count * min_ratio))
|
| 320 |
+
max_expected = min(6, int(expected_count * max_ratio)) # Hard cap at 6 syllables
|
| 321 |
|
| 322 |
+
# For 4/4 time signature, cap the max syllables per line even lower
|
| 323 |
if template['stress_pattern'] == "SWMW": # 4/4 time
|
| 324 |
+
max_expected = min(max_expected, 6) # Cap at 6 syllables max for 4/4
|
| 325 |
|
| 326 |
# Record min and max expected in the template for future reference
|
| 327 |
template['min_expected'] = min_expected
|