ss
Browse files- __pycache__/beat_analysis.cpython-310.pyc +0 -0
- app.py +120 -59
- beat_analysis.py +22 -17
__pycache__/beat_analysis.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/beat_analysis.cpython-310.pyc and b/__pycache__/beat_analysis.cpython-310.pyc differ
|
|
|
app.py
CHANGED
|
@@ -227,19 +227,16 @@ def generate_lyrics(music_analysis, genre, duration):
|
|
| 227 |
ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
|
| 228 |
"""
|
| 229 |
else:
|
| 230 |
-
# Create phrase examples
|
| 231 |
-
num_phrases = len(lyric_templates)
|
| 232 |
-
|
| 233 |
# Calculate the typical syllable range for this genre
|
| 234 |
if num_phrases > 0:
|
| 235 |
# Get max syllables per line from templates
|
| 236 |
-
max_syllables = max([t.get('max_expected',
|
| 237 |
-
min_syllables = min([t.get('min_expected',
|
| 238 |
avg_syllables = (min_syllables + max_syllables) // 2
|
| 239 |
else:
|
| 240 |
-
min_syllables =
|
| 241 |
-
max_syllables =
|
| 242 |
-
avg_syllables =
|
| 243 |
|
| 244 |
# Create a more direct prompt with examples and specific syllable count guidance
|
| 245 |
prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM. The emotion is {emotion} and theme is {theme}.
|
|
@@ -247,27 +244,29 @@ ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
|
|
| 247 |
I need EXACTLY {num_phrases} lines of lyrics - one line for each musical phrase. Not one more, not one less.
|
| 248 |
|
| 249 |
CRITICAL INSTRUCTIONS:
|
| 250 |
-
- Each line MUST
|
| 251 |
-
-
|
| 252 |
-
-
|
| 253 |
-
-
|
| 254 |
-
-
|
| 255 |
-
-
|
|
|
|
|
|
|
| 256 |
|
| 257 |
FORMAT:
|
| 258 |
- Just write {num_phrases} plain text lines
|
| 259 |
-
- Each line should be simple song lyrics (no annotations
|
| 260 |
-
- Don't include any explanations
|
| 261 |
-
- Don't use any
|
| 262 |
-
- Don't include [Verse]
|
| 263 |
-
- Don't include line numbers
|
| 264 |
|
| 265 |
-
EXAMPLE OF WHAT I WANT
|
| 266 |
-
|
| 267 |
-
Waiting
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
| 271 |
|
| 272 |
JUST THE PLAIN LYRICS, EXACTLY {num_phrases} LINES, KEEPING EACH LINE TO {min_syllables}-{max_syllables} SYLLABLES.
|
| 273 |
"""
|
|
@@ -467,21 +466,71 @@ JUST THE PLAIN LYRICS, EXACTLY {num_phrases} LINES, KEEPING EACH LINE TO {min_sy
|
|
| 467 |
i = len(clean_lines)
|
| 468 |
if i < len(lyric_templates):
|
| 469 |
template = lyric_templates[i]
|
| 470 |
-
target_syllables = min(max_syllables, (template.get('min_expected',
|
| 471 |
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
else:
|
| 482 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
else:
|
| 484 |
-
placeholder = "
|
| 485 |
|
| 486 |
clean_lines.append(placeholder)
|
| 487 |
|
|
@@ -531,22 +580,26 @@ def analyze_lyrics_rhythm_match(lyrics, lyric_templates, genre="pop"):
|
|
| 531 |
check_result = beat_analyzer.check_syllable_stress_match(line, template, genre)
|
| 532 |
|
| 533 |
# Get match symbols
|
| 534 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
stress_match = "✓" if check_result["stress_matches"] else f"{int(check_result['stress_match_percentage']*100)}%"
|
| 536 |
|
| 537 |
# Update stats
|
| 538 |
-
if check_result["
|
| 539 |
total_matches += 1
|
| 540 |
-
|
|
|
|
| 541 |
total_range_matches += 1
|
|
|
|
| 542 |
if check_result["stress_matches"]:
|
| 543 |
total_stress_matches += 1
|
| 544 |
total_stress_percentage += check_result["stress_match_percentage"]
|
| 545 |
|
| 546 |
-
# Track how close we are to ideal count for this genre
|
| 547 |
-
if abs(check_result["syllable_count"] - check_result["ideal_syllable_count"]) <= 1:
|
| 548 |
-
total_ideal_matches += 1
|
| 549 |
-
|
| 550 |
# Create visual representation of the stress pattern
|
| 551 |
stress_visual = ""
|
| 552 |
for char in template['stress_pattern']:
|
|
@@ -563,38 +616,46 @@ def analyze_lyrics_rhythm_match(lyrics, lyric_templates, genre="pop"):
|
|
| 563 |
# Add summary statistics
|
| 564 |
if line_count > 0:
|
| 565 |
exact_match_rate = (total_matches / line_count) * 100
|
| 566 |
-
range_match_rate = (total_range_matches / line_count) * 100
|
| 567 |
ideal_match_rate = (total_ideal_matches / line_count) * 100
|
| 568 |
stress_match_rate = (total_stress_matches / line_count) * 100
|
| 569 |
avg_stress_percentage = (total_stress_percentage / line_count) * 100
|
| 570 |
|
| 571 |
result += f"\n**Summary:**\n"
|
| 572 |
-
result += f"-
|
| 573 |
result += f"- Genre-appropriate syllable range match rate: {range_match_rate:.1f}%\n"
|
| 574 |
-
result += f"- Ideal genre syllable count match rate: {ideal_match_rate:.1f}%\n"
|
| 575 |
result += f"- Perfect stress pattern match rate: {stress_match_rate:.1f}%\n"
|
| 576 |
result += f"- Average stress pattern accuracy: {avg_stress_percentage:.1f}%\n"
|
| 577 |
result += f"- Overall rhythmic accuracy: {((range_match_rate + avg_stress_percentage) / 2):.1f}%\n"
|
| 578 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
# Add genre-specific notes
|
| 580 |
result += f"\n**Genre Notes ({genre}):**\n"
|
| 581 |
|
| 582 |
# Add appropriate genre notes based on genre
|
| 583 |
if genre.lower() == "pop":
|
| 584 |
-
result += "- Pop
|
| 585 |
-
result += "- Strong
|
| 586 |
elif genre.lower() == "rock":
|
| 587 |
-
result += "- Rock
|
| 588 |
-
result += "- Emphasis on strong beats for
|
| 589 |
-
elif genre.lower()
|
| 590 |
-
result += "-
|
| 591 |
-
result += "-
|
| 592 |
-
elif genre.lower()
|
| 593 |
-
result += "-
|
| 594 |
-
result += "-
|
|
|
|
|
|
|
|
|
|
| 595 |
else:
|
| 596 |
-
result += "- This genre typically
|
| 597 |
-
result += "-
|
| 598 |
|
| 599 |
return result
|
| 600 |
|
|
|
|
| 227 |
ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
|
| 228 |
"""
|
| 229 |
else:
|
|
|
|
|
|
|
|
|
|
| 230 |
# Calculate the typical syllable range for this genre
|
| 231 |
if num_phrases > 0:
|
| 232 |
# Get max syllables per line from templates
|
| 233 |
+
max_syllables = max([t.get('max_expected', 7) for t in lyric_templates]) if lyric_templates[0].get('max_expected') else 7
|
| 234 |
+
min_syllables = min([t.get('min_expected', 2) for t in lyric_templates]) if lyric_templates[0].get('min_expected') else 2
|
| 235 |
avg_syllables = (min_syllables + max_syllables) // 2
|
| 236 |
else:
|
| 237 |
+
min_syllables = 2
|
| 238 |
+
max_syllables = 7
|
| 239 |
+
avg_syllables = 4
|
| 240 |
|
| 241 |
# Create a more direct prompt with examples and specific syllable count guidance
|
| 242 |
prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM. The emotion is {emotion} and theme is {theme}.
|
|
|
|
| 244 |
I need EXACTLY {num_phrases} lines of lyrics - one line for each musical phrase. Not one more, not one less.
|
| 245 |
|
| 246 |
CRITICAL INSTRUCTIONS:
|
| 247 |
+
- Each line MUST be VERY SHORT with only {min_syllables}-{max_syllables} syllables (aim for {avg_syllables} or fewer)
|
| 248 |
+
- PRIORITIZE BREVITY - use fewer syllables rather than more
|
| 249 |
+
- Keep each line SIMPLE and DIRECT - avoid complex phrases
|
| 250 |
+
- Break complete thoughts across MULTIPLE LINES rather than fitting them into one line
|
| 251 |
+
- Think of each line as part of a flowing conversation, not a complete sentence
|
| 252 |
+
- Each phrase should fit into one measure of music
|
| 253 |
+
- Use simple, short words whenever possible
|
| 254 |
+
- End each line at a natural speaking pause point
|
| 255 |
|
| 256 |
FORMAT:
|
| 257 |
- Just write {num_phrases} plain text lines
|
| 258 |
+
- Each line should be simple song lyrics (no annotations)
|
| 259 |
+
- Don't include any explanations or commentary
|
| 260 |
+
- Don't use any tags or markers
|
| 261 |
+
- Don't include section labels like [Verse] or [Chorus]
|
|
|
|
| 262 |
|
| 263 |
+
EXAMPLE OF WHAT I WANT:
|
| 264 |
+
Empty chair ({min_syllables} syllables)
|
| 265 |
+
Waiting by the door ({avg_syllables} syllables)
|
| 266 |
+
Memories fade ({min_syllables+1} syllables)
|
| 267 |
+
Into silence ({avg_syllables-1} syllables)
|
| 268 |
+
Your ghost remains ({avg_syllables} syllables)
|
| 269 |
+
(... and so on)
|
| 270 |
|
| 271 |
JUST THE PLAIN LYRICS, EXACTLY {num_phrases} LINES, KEEPING EACH LINE TO {min_syllables}-{max_syllables} SYLLABLES.
|
| 272 |
"""
|
|
|
|
| 466 |
i = len(clean_lines)
|
| 467 |
if i < len(lyric_templates):
|
| 468 |
template = lyric_templates[i]
|
| 469 |
+
target_syllables = min(max_syllables, (template.get('min_expected', 2) + template.get('max_expected', 7)) // 2)
|
| 470 |
|
| 471 |
+
# Create a diverse set of placeholders that match the theme/emotion
|
| 472 |
+
placeholders = {
|
| 473 |
+
# 2-3 syllables
|
| 474 |
+
2: [
|
| 475 |
+
"Night falls",
|
| 476 |
+
"Time stops",
|
| 477 |
+
"Hearts beat",
|
| 478 |
+
"Rain falls",
|
| 479 |
+
"Stars shine"
|
| 480 |
+
],
|
| 481 |
+
# 3-4 syllables
|
| 482 |
+
3: [
|
| 483 |
+
"Empty chair",
|
| 484 |
+
"Shadows dance",
|
| 485 |
+
"Whispers fade",
|
| 486 |
+
"Memories",
|
| 487 |
+
"Silent room"
|
| 488 |
+
],
|
| 489 |
+
# 4-5 syllables
|
| 490 |
+
4: [
|
| 491 |
+
"Moonlight shimmers",
|
| 492 |
+
"Echoes of time",
|
| 493 |
+
"Footsteps fading",
|
| 494 |
+
"Memories drift",
|
| 495 |
+
"Silence speaks loud"
|
| 496 |
+
],
|
| 497 |
+
# 5-6 syllables
|
| 498 |
+
5: [
|
| 499 |
+
"Walking in the rain",
|
| 500 |
+
"Whispers in the dark",
|
| 501 |
+
"Echoes of your voice",
|
| 502 |
+
"Traces left behind",
|
| 503 |
+
"Time moves ever on"
|
| 504 |
+
],
|
| 505 |
+
# 6-7 syllables
|
| 506 |
+
6: [
|
| 507 |
+
"Dancing in the moonlight",
|
| 508 |
+
"Shadows play on the wall",
|
| 509 |
+
"Memories fade to silence",
|
| 510 |
+
"Moments lost in the wind",
|
| 511 |
+
"Whispers of a better time"
|
| 512 |
+
]
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
# Get the closest matching syllable group
|
| 516 |
+
closest_group = min(placeholders.keys(), key=lambda k: abs(k - target_syllables))
|
| 517 |
+
|
| 518 |
+
# Choose a placeholder that hasn't been used yet
|
| 519 |
+
available_placeholders = [p for p in placeholders[closest_group]
|
| 520 |
+
if p not in clean_lines]
|
| 521 |
+
|
| 522 |
+
if available_placeholders:
|
| 523 |
+
placeholder = available_placeholders[i % len(available_placeholders)]
|
| 524 |
else:
|
| 525 |
+
# If we've used all placeholders in this group, create a custom one
|
| 526 |
+
if emotion.lower() in ["sad", "nostalgic", "calm"]:
|
| 527 |
+
placeholder = f"Memories of {emotion}"
|
| 528 |
+
elif emotion.lower() in ["happy", "energetic"]:
|
| 529 |
+
placeholder = f"Dancing through {emotion}"
|
| 530 |
+
else:
|
| 531 |
+
placeholder = f"Feeling {emotion} now"
|
| 532 |
else:
|
| 533 |
+
placeholder = "Silence speaks volumes"
|
| 534 |
|
| 535 |
clean_lines.append(placeholder)
|
| 536 |
|
|
|
|
| 580 |
check_result = beat_analyzer.check_syllable_stress_match(line, template, genre)
|
| 581 |
|
| 582 |
# Get match symbols
|
| 583 |
+
if check_result["close_to_ideal"]:
|
| 584 |
+
syllable_match = "✓" # Ideal or very close
|
| 585 |
+
elif check_result["within_range"]:
|
| 586 |
+
syllable_match = "✓*" # Within range but not ideal
|
| 587 |
+
else:
|
| 588 |
+
syllable_match = "✗" # Outside range
|
| 589 |
+
|
| 590 |
stress_match = "✓" if check_result["stress_matches"] else f"{int(check_result['stress_match_percentage']*100)}%"
|
| 591 |
|
| 592 |
# Update stats
|
| 593 |
+
if check_result["close_to_ideal"]:
|
| 594 |
total_matches += 1
|
| 595 |
+
total_ideal_matches += 1
|
| 596 |
+
elif check_result["within_range"]:
|
| 597 |
total_range_matches += 1
|
| 598 |
+
|
| 599 |
if check_result["stress_matches"]:
|
| 600 |
total_stress_matches += 1
|
| 601 |
total_stress_percentage += check_result["stress_match_percentage"]
|
| 602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
# Create visual representation of the stress pattern
|
| 604 |
stress_visual = ""
|
| 605 |
for char in template['stress_pattern']:
|
|
|
|
| 616 |
# Add summary statistics
|
| 617 |
if line_count > 0:
|
| 618 |
exact_match_rate = (total_matches / line_count) * 100
|
| 619 |
+
range_match_rate = ((total_matches + total_range_matches) / line_count) * 100
|
| 620 |
ideal_match_rate = (total_ideal_matches / line_count) * 100
|
| 621 |
stress_match_rate = (total_stress_matches / line_count) * 100
|
| 622 |
avg_stress_percentage = (total_stress_percentage / line_count) * 100
|
| 623 |
|
| 624 |
result += f"\n**Summary:**\n"
|
| 625 |
+
result += f"- Ideal or near-ideal syllable match rate: {exact_match_rate:.1f}%\n"
|
| 626 |
result += f"- Genre-appropriate syllable range match rate: {range_match_rate:.1f}%\n"
|
|
|
|
| 627 |
result += f"- Perfect stress pattern match rate: {stress_match_rate:.1f}%\n"
|
| 628 |
result += f"- Average stress pattern accuracy: {avg_stress_percentage:.1f}%\n"
|
| 629 |
result += f"- Overall rhythmic accuracy: {((range_match_rate + avg_stress_percentage) / 2):.1f}%\n"
|
| 630 |
|
| 631 |
+
# Add guidance on ideal distribution for syllables
|
| 632 |
+
result += f"\n**Syllable Distribution Guidance:**\n"
|
| 633 |
+
result += f"- Aim for {min([t.get('min_expected', 3) for t in lyric_templates])}-{max([t.get('max_expected', 7) for t in lyric_templates])} syllables per line\n"
|
| 634 |
+
result += f"- Break complete thoughts across multiple lines for a more natural flow\n"
|
| 635 |
+
result += f"- Allow sentences to span 2-3 measures for better musical phrasing\n"
|
| 636 |
+
|
| 637 |
# Add genre-specific notes
|
| 638 |
result += f"\n**Genre Notes ({genre}):**\n"
|
| 639 |
|
| 640 |
# Add appropriate genre notes based on genre
|
| 641 |
if genre.lower() == "pop":
|
| 642 |
+
result += "- Pop lyrics are typically concise with 3-7 syllables per musical phrase\n"
|
| 643 |
+
result += "- Strong beats often align with stressed syllables in important words\n"
|
| 644 |
elif genre.lower() == "rock":
|
| 645 |
+
result += "- Rock lyrics favor brevity with 3-6 syllables per musical phrase\n"
|
| 646 |
+
result += "- Emphasis on strong beats for rhythmic impact\n"
|
| 647 |
+
elif genre.lower() == "country":
|
| 648 |
+
result += "- Country lyrics tend toward clear storytelling with 3-6 syllables per phrase\n"
|
| 649 |
+
result += "- Natural speech rhythms are important for authentic delivery\n"
|
| 650 |
+
elif genre.lower() == "disco":
|
| 651 |
+
result += "- Disco lyrics work well with 4-7 syllables per musical phrase\n"
|
| 652 |
+
result += "- Rhythmic patterns often emphasize dance-friendly phrasing\n"
|
| 653 |
+
elif genre.lower() == "metal":
|
| 654 |
+
result += "- Metal lyrics balance intensity with 3-7 syllables per musical phrase\n"
|
| 655 |
+
result += "- Strong syllables on strong beats create powerful impact\n"
|
| 656 |
else:
|
| 657 |
+
result += "- This genre typically works well with concise, focused phrasing\n"
|
| 658 |
+
result += "- Consider breaking complete thoughts across multiple lines\n"
|
| 659 |
|
| 660 |
return result
|
| 661 |
|
beat_analysis.py
CHANGED
|
@@ -32,11 +32,11 @@ class BeatAnalyzer:
|
|
| 32 |
# Genre-specific syllable-to-beat ratio guidelines
|
| 33 |
self.genre_syllable_ratios = {
|
| 34 |
# Supported genres with strong syllable-to-beat patterns
|
| 35 |
-
'pop': (0.
|
| 36 |
-
'rock': (0.
|
| 37 |
-
'country': (0.
|
| 38 |
-
'disco': (0.
|
| 39 |
-
'metal': (0.
|
| 40 |
|
| 41 |
# Other genres (analysis only, no lyrics generation)
|
| 42 |
'hiphop': (1.8, 2.5, 3.5), # Hip hop often has many syllables per beat
|
|
@@ -49,7 +49,7 @@ class BeatAnalyzer:
|
|
| 49 |
'electronic': (0.7, 1.0, 1.5), # Electronic music varies widely
|
| 50 |
'classical': (0.7, 1.0, 1.4), # Classical can vary by subgenre
|
| 51 |
'blues': (0.6, 0.8, 1.2), # Blues often extends syllables
|
| 52 |
-
'default': (0.
|
| 53 |
}
|
| 54 |
|
| 55 |
# List of genres supported for lyrics generation
|
|
@@ -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 *
|
| 280 |
|
| 281 |
-
# Estimate syllables - use more conservative ranges
|
| 282 |
# For 4/4 time signature, we want to encourage shorter phrases
|
| 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 = int(num_beats * 1.5
|
| 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'] = "Keep lines
|
| 298 |
|
| 299 |
return guide
|
| 300 |
|
|
@@ -317,11 +317,11 @@ class BeatAnalyzer:
|
|
| 317 |
# Calculate flexible min and max syllable expectations based on genre
|
| 318 |
# Use more conservative ranges to avoid too many syllables
|
| 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
|
|
@@ -335,6 +335,10 @@ class BeatAnalyzer:
|
|
| 335 |
# Ensure ideal count is also within our constrained range
|
| 336 |
ideal_count = max(min_expected, min(max_expected, ideal_count))
|
| 337 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
closeness_to_ideal = 1.0 - min(abs(syllable_count - ideal_count) / (max_expected - min_expected + 1), 1.0)
|
| 339 |
|
| 340 |
# Get detailed syllable breakdown for stress analysis
|
|
@@ -354,7 +358,7 @@ class BeatAnalyzer:
|
|
| 354 |
stress_match_percentage = self._calculate_stress_match(words, word_syllables, syllable_to_beat_mapping, stress_pattern)
|
| 355 |
|
| 356 |
# Consider a stress match if the percentage is high enough
|
| 357 |
-
stress_matches = stress_match_percentage >= 0.7
|
| 358 |
|
| 359 |
return {
|
| 360 |
'syllable_count': syllable_count,
|
|
@@ -368,7 +372,8 @@ class BeatAnalyzer:
|
|
| 368 |
'stress_match_percentage': stress_match_percentage,
|
| 369 |
'closeness_to_ideal': closeness_to_ideal,
|
| 370 |
'word_syllables': word_syllables,
|
| 371 |
-
'ideal_syllable_count': ideal_count
|
|
|
|
| 372 |
}
|
| 373 |
|
| 374 |
def _map_syllables_to_beats(self, word_syllables, stress_pattern):
|
|
|
|
| 32 |
# Genre-specific syllable-to-beat ratio guidelines
|
| 33 |
self.genre_syllable_ratios = {
|
| 34 |
# Supported genres with strong syllable-to-beat patterns
|
| 35 |
+
'pop': (0.5, 1.0, 1.5), # Pop - significantly reduced range
|
| 36 |
+
'rock': (0.5, 0.9, 1.3), # Rock - reduced for brevity
|
| 37 |
+
'country': (0.6, 0.9, 1.2), # Country - simpler syllable patterns
|
| 38 |
+
'disco': (0.7, 1.0, 1.3), # Disco - tightened range
|
| 39 |
+
'metal': (0.6, 1.0, 1.3), # Metal - reduced upper limit
|
| 40 |
|
| 41 |
# Other genres (analysis only, no lyrics generation)
|
| 42 |
'hiphop': (1.8, 2.5, 3.5), # Hip hop often has many syllables per beat
|
|
|
|
| 49 |
'electronic': (0.7, 1.0, 1.5), # Electronic music varies widely
|
| 50 |
'classical': (0.7, 1.0, 1.4), # Classical can vary by subgenre
|
| 51 |
'blues': (0.6, 0.8, 1.2), # Blues often extends syllables
|
| 52 |
+
'default': (0.6, 1.0, 1.3) # Default for unknown genres - more conservative
|
| 53 |
}
|
| 54 |
|
| 55 |
# List of genres supported for lyrics generation
|
|
|
|
| 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.4)) # Reduced from 0.5 to encourage fewer words
|
| 280 |
|
| 281 |
+
# Estimate syllables - use even more conservative ranges
|
| 282 |
# For 4/4 time signature, we want to encourage shorter phrases
|
| 283 |
if stress_pattern == "SWMW": # 4/4 time
|
| 284 |
+
min_syllables = max(1, int(num_beats * 0.5)) # Reduced from 0.7
|
| 285 |
+
max_syllables = min(7, int(num_beats * 1.3)) # Reduced from 1.6 to max 7
|
| 286 |
else:
|
| 287 |
+
min_syllables = max(1, int(num_beats * 0.5)) # Reduced from 0.7
|
| 288 |
+
max_syllables = min(7, int(num_beats * 1.2)) # Reduced from 1.5 to max 7
|
| 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'] = "Keep lines SHORT. Break complete thoughts across MULTIPLE LINES."
|
| 298 |
|
| 299 |
return guide
|
| 300 |
|
|
|
|
| 317 |
# Calculate flexible min and max syllable expectations based on genre
|
| 318 |
# Use more conservative ranges to avoid too many syllables
|
| 319 |
min_expected = max(1, int(expected_count * min_ratio))
|
| 320 |
+
max_expected = min(7, int(expected_count * max_ratio))
|
| 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, 7) # Cap at 7 syllables max for 4/4
|
| 325 |
|
| 326 |
# Record min and max expected in the template for future reference
|
| 327 |
template['min_expected'] = min_expected
|
|
|
|
| 335 |
# Ensure ideal count is also within our constrained range
|
| 336 |
ideal_count = max(min_expected, min(max_expected, ideal_count))
|
| 337 |
|
| 338 |
+
# More lenient approach to determining "ideal"
|
| 339 |
+
# Count as ideal if within 1 syllable of the target instead of exact match
|
| 340 |
+
close_to_ideal = abs(syllable_count - ideal_count) <= 1
|
| 341 |
+
|
| 342 |
closeness_to_ideal = 1.0 - min(abs(syllable_count - ideal_count) / (max_expected - min_expected + 1), 1.0)
|
| 343 |
|
| 344 |
# Get detailed syllable breakdown for stress analysis
|
|
|
|
| 358 |
stress_match_percentage = self._calculate_stress_match(words, word_syllables, syllable_to_beat_mapping, stress_pattern)
|
| 359 |
|
| 360 |
# Consider a stress match if the percentage is high enough
|
| 361 |
+
stress_matches = stress_match_percentage >= 0.6 # Reduced from 0.7 to be more lenient
|
| 362 |
|
| 363 |
return {
|
| 364 |
'syllable_count': syllable_count,
|
|
|
|
| 372 |
'stress_match_percentage': stress_match_percentage,
|
| 373 |
'closeness_to_ideal': closeness_to_ideal,
|
| 374 |
'word_syllables': word_syllables,
|
| 375 |
+
'ideal_syllable_count': ideal_count,
|
| 376 |
+
'close_to_ideal': close_to_ideal # New field
|
| 377 |
}
|
| 378 |
|
| 379 |
def _map_syllables_to_beats(self, word_syllables, stress_pattern):
|