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):
|