ss
Browse files
app.py
CHANGED
@@ -86,33 +86,6 @@ except Exception as e:
|
|
86 |
# Create music analyzer instance
|
87 |
music_analyzer = MusicAnalyzer()
|
88 |
|
89 |
-
# Define emotion and theme lexicons for content analysis
|
90 |
-
emotion_lexicons = {
|
91 |
-
"happy": ["joy", "happy", "smile", "laugh", "light", "bright", "sun", "dance", "celebrate", "glow", "warm"],
|
92 |
-
"sad": ["cry", "tear", "pain", "loss", "grief", "dark", "alone", "miss", "gone", "sorrow", "heart", "break"],
|
93 |
-
"calm": ["peace", "quiet", "still", "gentle", "soft", "slow", "breath", "serene", "tranquil", "relax", "flow"],
|
94 |
-
"energetic": ["run", "fast", "beat", "pulse", "jump", "fire", "alive", "spark", "rush", "wild", "free"],
|
95 |
-
"tense": ["fear", "worry", "wait", "edge", "grip", "tight", "storm", "break", "shadow", "threat", "doubt"],
|
96 |
-
"nostalgic": ["memory", "remember", "past", "time", "ago", "once", "childhood", "return", "old", "familiar", "home"],
|
97 |
-
"reflective": ["think", "ponder", "wonder", "question", "search", "mind", "deep", "self", "mirror", "path", "journey"],
|
98 |
-
"triumphant": ["win", "rise", "stand", "overcome", "above", "victory", "summit", "conquer", "champion", "succeed"],
|
99 |
-
"yearning": ["want", "need", "desire", "reach", "seek", "dream", "hope", "wish", "long", "hunger", "thirst"],
|
100 |
-
"peaceful": ["calm", "rest", "still", "quiet", "harmony", "balance", "ease", "gentle", "soft", "float", "drift"]
|
101 |
-
}
|
102 |
-
|
103 |
-
theme_lexicons = {
|
104 |
-
"love": ["love", "heart", "touch", "together", "hold", "kiss", "embrace", "feel", "close", "intimate", "passion"],
|
105 |
-
"loss": ["gone", "away", "empty", "missing", "leave", "without", "never", "forever", "lost", "memory", "shadow"],
|
106 |
-
"freedom": ["free", "fly", "open", "release", "escape", "chain", "break", "boundless", "space", "breathe", "wings"],
|
107 |
-
"triumph": ["victory", "overcome", "win", "rise", "mountain", "climb", "top", "struggle", "strength", "succeed"],
|
108 |
-
"reflection": ["mirror", "water", "see", "self", "face", "look", "inside", "truth", "reality", "soul", "mind"],
|
109 |
-
"journey": ["road", "path", "walk", "step", "travel", "distance", "far", "way", "wander", "search", "find"],
|
110 |
-
"time": ["clock", "moment", "second", "hour", "pass", "wait", "forever", "instant", "eternity", "memory", "future"],
|
111 |
-
"conflict": ["fight", "battle", "against", "oppose", "between", "war", "struggle", "clash", "resist", "enemy"],
|
112 |
-
"nature": ["earth", "wind", "fire", "water", "sky", "tree", "flower", "mountain", "river", "ocean", "stars"],
|
113 |
-
"change": ["transform", "become", "different", "shift", "turn", "evolve", "grow", "new", "begin", "end", "cycle"]
|
114 |
-
}
|
115 |
-
|
116 |
# Process uploaded audio file
|
117 |
def process_audio(audio_file):
|
118 |
if audio_file is None:
|
@@ -217,7 +190,7 @@ def process_audio(audio_file):
|
|
217 |
# Generate lyrics only for supported genres
|
218 |
if genre_supported:
|
219 |
lyrics = generate_lyrics(music_analysis, primary_genre, duration)
|
220 |
-
beat_match_analysis = analyze_lyrics_rhythm_match(lyrics, lyric_templates, primary_genre
|
221 |
else:
|
222 |
supported_genres_str = ", ".join([genre.capitalize() for genre in beat_analyzer.supported_genres])
|
223 |
lyrics = f"Lyrics generation is only supported for the following genres: {supported_genres_str}.\n\nDetected genre '{primary_genre}' doesn't have strong syllable-to-beat patterns required for our lyric generation algorithm."
|
@@ -254,9 +227,6 @@ def generate_lyrics(music_analysis, genre, duration):
|
|
254 |
ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
|
255 |
"""
|
256 |
else:
|
257 |
-
# Create phrase examples
|
258 |
-
num_phrases = len(lyric_templates)
|
259 |
-
|
260 |
# Calculate the typical syllable range for this genre
|
261 |
if num_phrases > 0:
|
262 |
# Get max syllables per line from templates
|
@@ -268,46 +238,82 @@ ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
|
|
268 |
max_syllables = 7
|
269 |
avg_syllables = 4
|
270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
271 |
# Create a more direct prompt with examples and specific syllable count guidance
|
272 |
-
prompt = f"""Write song lyrics for a {genre} song
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
- Each line should contribute to the overall theme
|
287 |
-
- Don't copy the example structures - be creative and unique
|
288 |
-
- Use simple, concise words that evoke strong emotions
|
289 |
-
|
290 |
-
AVOID:
|
291 |
-
- Generic phrases that could apply to any song
|
292 |
-
- Copying patterns from the examples below
|
293 |
-
- Complete, independent thoughts on each line
|
294 |
-
- Abstract concepts without concrete imagery
|
295 |
|
296 |
FORMAT:
|
297 |
-
-
|
298 |
-
-
|
|
|
|
|
299 |
|
300 |
-
|
|
|
|
|
|
|
|
|
301 |
|
302 |
-
|
303 |
-
Line 2 (extends the image)
|
304 |
-
Line 3 (completes the thought)
|
305 |
|
306 |
-
|
307 |
-
|
308 |
-
|
|
|
|
|
309 |
|
310 |
-
|
|
|
|
|
|
|
|
|
311 |
"""
|
312 |
|
313 |
# Generate lyrics using the LLM model
|
@@ -491,7 +497,37 @@ Remember: YOUR LYRICS SHOULD DEEPLY EXPRESS "{emotion}" AND EXPLORE "{theme}" -
|
|
491 |
# 9. Filter out any remaining empty lines after tag removal
|
492 |
clean_lines = [line for line in clean_lines if line.strip() and not line.isspace()]
|
493 |
|
494 |
-
# 10.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
495 |
if lyric_templates:
|
496 |
num_required = len(lyric_templates)
|
497 |
|
@@ -507,76 +543,107 @@ Remember: YOUR LYRICS SHOULD DEEPLY EXPRESS "{emotion}" AND EXPLORE "{theme}" -
|
|
507 |
template = lyric_templates[i]
|
508 |
target_syllables = min(max_syllables, (template.get('min_expected', 2) + template.get('max_expected', 7)) // 2)
|
509 |
|
510 |
-
#
|
511 |
-
|
512 |
-
|
|
|
513 |
2: [
|
514 |
-
"
|
515 |
-
"
|
516 |
-
"
|
517 |
-
"
|
518 |
-
"
|
519 |
],
|
520 |
-
# 3-4 syllables
|
521 |
3: [
|
522 |
-
"
|
523 |
-
"
|
524 |
-
"
|
525 |
-
"
|
526 |
-
"
|
527 |
],
|
528 |
-
# 4-5 syllables
|
529 |
4: [
|
530 |
-
"
|
531 |
-
"
|
532 |
-
"
|
533 |
-
"
|
534 |
-
"
|
535 |
],
|
536 |
-
# 5-6 syllables
|
537 |
5: [
|
538 |
-
"
|
539 |
-
"
|
540 |
-
"
|
541 |
-
"
|
542 |
-
"
|
543 |
],
|
544 |
-
# 6-7 syllables
|
545 |
6: [
|
546 |
-
"
|
547 |
-
"
|
548 |
-
"
|
549 |
-
"
|
550 |
-
"
|
551 |
]
|
552 |
}
|
553 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
554 |
# Get the closest matching syllable group
|
555 |
-
closest_group = min(
|
|
|
|
|
|
|
556 |
|
557 |
# Choose a placeholder that hasn't been used yet
|
558 |
-
available_placeholders = [p for p in
|
559 |
-
if p not in clean_lines]
|
560 |
|
561 |
if available_placeholders:
|
562 |
-
|
|
|
|
|
563 |
else:
|
564 |
-
# If we've used all placeholders
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
569 |
else:
|
570 |
-
|
|
|
571 |
else:
|
572 |
-
placeholder = "
|
573 |
|
574 |
clean_lines.append(placeholder)
|
575 |
|
576 |
# Assemble final lyrics
|
577 |
final_lyrics = '\n'.join(clean_lines)
|
578 |
|
579 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
580 |
if not final_lyrics or len(final_lyrics) < 10:
|
581 |
return "The model generated only thinking content but no actual lyrics. Please try again."
|
582 |
|
@@ -587,7 +654,7 @@ Remember: YOUR LYRICS SHOULD DEEPLY EXPRESS "{emotion}" AND EXPLORE "{theme}" -
|
|
587 |
print(error_msg)
|
588 |
return error_msg
|
589 |
|
590 |
-
def analyze_lyrics_rhythm_match(lyrics, lyric_templates, genre="pop"
|
591 |
"""Analyze how well the generated lyrics match the beat patterns and syllable requirements"""
|
592 |
if not lyric_templates or not lyrics:
|
593 |
return "No beat templates or lyrics available for analysis."
|
@@ -674,35 +741,12 @@ def analyze_lyrics_rhythm_match(lyrics, lyric_templates, genre="pop", emotion="r
|
|
674 |
result += f"- Average lines per thought: {sentence_flow_analysis['avg_lines_per_group']:.1f}\n"
|
675 |
result += f"- Flow quality: {sentence_flow_analysis['flow_quality']}\n"
|
676 |
|
677 |
-
# Analyze theme and emotion expression
|
678 |
-
content_analysis = analyze_theme_emotion_expression(lyrics, theme, emotion)
|
679 |
-
result += f"\n**Theme & Emotion Expression:**\n"
|
680 |
-
result += f"- Emotion ({emotion}) expression: {content_analysis['emotion_score']:.1f}% ({content_analysis['emotion_words_found']} words)\n"
|
681 |
-
result += f"- Theme ({theme}) development: {content_analysis['theme_score']:.1f}% ({content_analysis['theme_words_found']} words)\n"
|
682 |
-
result += f"- Overall expression quality: {content_analysis['expression_quality']}\n"
|
683 |
-
|
684 |
# Add guidance on ideal distribution for syllables and sentence flow
|
685 |
-
result += f"\n**
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
# Flow recommendations
|
692 |
-
if sentence_flow_analysis['connected_groups'] < len(lines) / 5:
|
693 |
-
result += f"- **Line connections:** Break complete thoughts across 2-3 lines using conjunctions and prepositions\n"
|
694 |
-
result += f"- **Flow techniques:** Start lines with connecting words like 'as', 'when', 'while', 'through'\n"
|
695 |
-
|
696 |
-
# Theme/emotion recommendations
|
697 |
-
if content_analysis['emotion_score'] < 20:
|
698 |
-
result += f"- **Emotion expression:** Use more words that evoke '{emotion}' feelings (e.g., {', '.join(emotion_lexicons.get(emotion.lower(), ['expressive words'])[:3])})\n"
|
699 |
-
|
700 |
-
if content_analysis['theme_score'] < 20:
|
701 |
-
result += f"- **Theme development:** Incorporate more '{theme}' imagery and concepts (e.g., {', '.join(theme_lexicons.get(theme.lower(), ['thematic words'])[:3])})\n"
|
702 |
-
|
703 |
-
# General recommendations
|
704 |
-
result += f"- **Originality:** Avoid generic phrases and create specific, vivid imagery\n"
|
705 |
-
result += f"- **Sensory details:** Include concrete details that can be seen, heard, or felt\n"
|
706 |
|
707 |
# Add genre-specific notes
|
708 |
result += f"\n**Genre Notes ({genre}):**\n"
|
@@ -821,66 +865,6 @@ def analyze_sentence_flow(lines):
|
|
821 |
"flow_quality": flow_quality
|
822 |
}
|
823 |
|
824 |
-
def analyze_theme_emotion_expression(lyrics, theme, emotion):
|
825 |
-
"""Analyze how well the lyrics express the target theme and emotion"""
|
826 |
-
|
827 |
-
# Normalize inputs
|
828 |
-
lyrics_text = lyrics.lower()
|
829 |
-
emotion = emotion.lower()
|
830 |
-
theme = theme.lower()
|
831 |
-
|
832 |
-
# Find closest emotion lexicon if exact match not found
|
833 |
-
if emotion not in emotion_lexicons:
|
834 |
-
closest_emotion = "reflective" # Default
|
835 |
-
for key in emotion_lexicons:
|
836 |
-
if emotion in key or key in emotion:
|
837 |
-
closest_emotion = key
|
838 |
-
break
|
839 |
-
emotion = closest_emotion
|
840 |
-
|
841 |
-
# Find closest theme lexicon if exact match not found
|
842 |
-
if theme not in theme_lexicons:
|
843 |
-
closest_theme = "journey" # Default
|
844 |
-
for key in theme_lexicons:
|
845 |
-
if theme in key or key in theme:
|
846 |
-
closest_theme = key
|
847 |
-
break
|
848 |
-
theme = closest_theme
|
849 |
-
|
850 |
-
# Count emotional and thematic words in lyrics
|
851 |
-
emotion_matches = 0
|
852 |
-
theme_matches = 0
|
853 |
-
|
854 |
-
for word in emotion_lexicons[emotion]:
|
855 |
-
if word in lyrics_text:
|
856 |
-
emotion_matches += 1
|
857 |
-
|
858 |
-
for word in theme_lexicons[theme]:
|
859 |
-
if word in lyrics_text:
|
860 |
-
theme_matches += 1
|
861 |
-
|
862 |
-
# Calculate scores as percentages of available words
|
863 |
-
emotion_score = min(100, (emotion_matches / len(emotion_lexicons[emotion])) * 100)
|
864 |
-
theme_score = min(100, (theme_matches / len(theme_lexicons[theme])) * 100)
|
865 |
-
|
866 |
-
# Qualitative assessment
|
867 |
-
if emotion_score >= 30 and theme_score >= 30:
|
868 |
-
expression_quality = "Strong"
|
869 |
-
elif emotion_score >= 20 and theme_score >= 20:
|
870 |
-
expression_quality = "Good"
|
871 |
-
elif emotion_score >= 10 and theme_score >= 10:
|
872 |
-
expression_quality = "Fair"
|
873 |
-
else:
|
874 |
-
expression_quality = "Weak"
|
875 |
-
|
876 |
-
return {
|
877 |
-
"emotion_score": emotion_score,
|
878 |
-
"theme_score": theme_score,
|
879 |
-
"expression_quality": expression_quality,
|
880 |
-
"emotion_words_found": emotion_matches,
|
881 |
-
"theme_words_found": theme_matches
|
882 |
-
}
|
883 |
-
|
884 |
# Create Gradio interface
|
885 |
def create_interface():
|
886 |
with gr.Blocks(title="Music Analysis & Lyrics Generator") as demo:
|
|
|
86 |
# Create music analyzer instance
|
87 |
music_analyzer = MusicAnalyzer()
|
88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
# Process uploaded audio file
|
90 |
def process_audio(audio_file):
|
91 |
if audio_file is None:
|
|
|
190 |
# Generate lyrics only for supported genres
|
191 |
if genre_supported:
|
192 |
lyrics = generate_lyrics(music_analysis, primary_genre, duration)
|
193 |
+
beat_match_analysis = analyze_lyrics_rhythm_match(lyrics, lyric_templates, primary_genre)
|
194 |
else:
|
195 |
supported_genres_str = ", ".join([genre.capitalize() for genre in beat_analyzer.supported_genres])
|
196 |
lyrics = f"Lyrics generation is only supported for the following genres: {supported_genres_str}.\n\nDetected genre '{primary_genre}' doesn't have strong syllable-to-beat patterns required for our lyric generation algorithm."
|
|
|
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
|
|
|
238 |
max_syllables = 7
|
239 |
avg_syllables = 4
|
240 |
|
241 |
+
# Create random examples based on the song's theme and emotion
|
242 |
+
# to avoid the LLM copying our examples directly
|
243 |
+
example_themes = [
|
244 |
+
{"emotion": "love", "fragments": ["I see your face", "across the room", "my heart beats fast", "can't look away"]},
|
245 |
+
{"emotion": "sadness", "fragments": ["tears fall like rain", "on empty streets", "memories fade", "into the dark"]},
|
246 |
+
{"emotion": "nostalgia", "fragments": ["old photographs", "dusty and worn", "remind me of when", "we were young"]},
|
247 |
+
{"emotion": "hope", "fragments": ["dawn breaks through clouds", "new day begins", "darkness recedes", "light fills my soul"]},
|
248 |
+
{"emotion": "longing", "fragments": ["miles apart now", "under same stars", "thinking of you", "across the distance"]}
|
249 |
+
]
|
250 |
+
|
251 |
+
# Select a theme that doesn't match the song's emotion to avoid copying
|
252 |
+
selected_themes = [t for t in example_themes if t["emotion"].lower() != emotion.lower()]
|
253 |
+
if not selected_themes:
|
254 |
+
selected_themes = example_themes
|
255 |
+
|
256 |
+
import random
|
257 |
+
example_theme = random.choice(selected_themes)
|
258 |
+
example_fragments = example_theme["fragments"]
|
259 |
+
random.shuffle(example_fragments) # Randomize order
|
260 |
+
|
261 |
+
# Create example 1 - grammatical connection with conjunction
|
262 |
+
ex1_line1 = example_fragments[0] if len(example_fragments) > 0 else "The morning sun"
|
263 |
+
ex1_line2 = example_fragments[1] if len(example_fragments) > 1 else "breaks through clouds"
|
264 |
+
ex1_line3 = example_fragments[2] if len(example_fragments) > 2 else "as birds begin"
|
265 |
+
ex1_line4 = example_fragments[3] if len(example_fragments) > 3 else "their dawn chorus"
|
266 |
+
|
267 |
+
# Create example 2 - prepositional connection
|
268 |
+
ex2_fragments = [
|
269 |
+
"She walks alone",
|
270 |
+
"through crowded streets",
|
271 |
+
"with memories",
|
272 |
+
"of better days"
|
273 |
+
]
|
274 |
+
random.shuffle(ex2_fragments)
|
275 |
+
|
276 |
# Create a more direct prompt with examples and specific syllable count guidance
|
277 |
+
prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM.
|
278 |
+
|
279 |
+
PRIMARY THEME: {theme}
|
280 |
+
EMOTION: {emotion}
|
281 |
+
|
282 |
+
I need EXACTLY {num_phrases} lines of lyrics with these requirements:
|
283 |
+
|
284 |
+
KEY INSTRUCTIONS:
|
285 |
+
1. CONNECTED THOUGHTS: Spread complete sentences across multiple lines (2-3 lines)
|
286 |
+
2. SHORT LINES: Keep each line between {min_syllables}-{max_syllables} syllables
|
287 |
+
3. NATURAL FLOW: Use conjunctions, prepositions and fragments that flow together
|
288 |
+
4. AVOID CLICHÉS: Don't use phrases like "moonlight shimmers" or "time slips away"
|
289 |
+
5. ORIGINALITY: Create fresh, original imagery related to {theme} and {emotion}
|
290 |
+
6. DISTINCTIVE VOICE: Use specific, concrete details rather than generic phrases
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
291 |
|
292 |
FORMAT:
|
293 |
+
- Write exactly {num_phrases} plain text lines of lyrics (just the lyrics, no annotations)
|
294 |
+
- Don't include explanations, section markers, or line numbers
|
295 |
+
|
296 |
+
===== EXAMPLES OF CONNECTED THOUGHTS =====
|
297 |
|
298 |
+
Example 1 - Connection with conjunction:
|
299 |
+
{ex1_line1}
|
300 |
+
{ex1_line2}
|
301 |
+
{ex1_line3}
|
302 |
+
{ex1_line4}
|
303 |
|
304 |
+
↑ Notice how these lines connect grammatically to form complete thoughts.
|
|
|
|
|
305 |
|
306 |
+
Example 2 - Connection with prepositions:
|
307 |
+
{ex2_fragments[0]}
|
308 |
+
{ex2_fragments[1]}
|
309 |
+
{ex2_fragments[2]}
|
310 |
+
{ex2_fragments[3]}
|
311 |
|
312 |
+
↑ These lines use prepositions to create flow across multiple lines.
|
313 |
+
|
314 |
+
IMPORTANT: DO NOT copy my examples or use their wording. Create ENTIRELY NEW, ORIGINAL lyrics about {theme} with {emotion} feeling that have your own distinctive voice and perspective.
|
315 |
+
|
316 |
+
Just write {num_phrases} lines of original lyrics, focusing on connecting your lines with thought flow.
|
317 |
"""
|
318 |
|
319 |
# Generate lyrics using the LLM model
|
|
|
497 |
# 9. Filter out any remaining empty lines after tag removal
|
498 |
clean_lines = [line for line in clean_lines if line.strip() and not line.isspace()]
|
499 |
|
500 |
+
# 10. NEW: Check for template copying or clichéd phrases
|
501 |
+
cliched_patterns = [
|
502 |
+
r'moonlight (shimmers?|falls?|dances?)',
|
503 |
+
r'shadows? (dance|play|fall|stretch)',
|
504 |
+
r'time slips? away',
|
505 |
+
r'whispers? (fade|in the)',
|
506 |
+
r'silence speaks',
|
507 |
+
r'stars? shine',
|
508 |
+
r'hearts? beat',
|
509 |
+
r'footsteps (fade|echo)',
|
510 |
+
r'gentle wind',
|
511 |
+
r'(old|empty) (roads?|chair)',
|
512 |
+
r'night (holds?|falls?)',
|
513 |
+
r'memories fade',
|
514 |
+
r'dreams (linger|drift)'
|
515 |
+
]
|
516 |
+
|
517 |
+
cliche_count = 0
|
518 |
+
for line in clean_lines:
|
519 |
+
for pattern in cliched_patterns:
|
520 |
+
if re.search(pattern, line.lower()):
|
521 |
+
cliche_count += 1
|
522 |
+
break
|
523 |
+
|
524 |
+
# Calculate percentage of clichéd lines
|
525 |
+
if clean_lines:
|
526 |
+
cliche_percentage = (cliche_count / len(clean_lines)) * 100
|
527 |
+
else:
|
528 |
+
cliche_percentage = 0
|
529 |
+
|
530 |
+
# 11. If we have lyric templates, ensure we have the correct number of lines
|
531 |
if lyric_templates:
|
532 |
num_required = len(lyric_templates)
|
533 |
|
|
|
543 |
template = lyric_templates[i]
|
544 |
target_syllables = min(max_syllables, (template.get('min_expected', 2) + template.get('max_expected', 7)) // 2)
|
545 |
|
546 |
+
# Generate more creative, contextual placeholders with specificity
|
547 |
+
# Avoid clichés like "moonlight shimmers" or "time slips away"
|
548 |
+
specific_placeholders = {
|
549 |
+
# 2-3 syllables - specific, concrete phrases
|
550 |
2: [
|
551 |
+
"Phone rings twice",
|
552 |
+
"Dogs bark loud",
|
553 |
+
"Keys dropped here",
|
554 |
+
"Train rolls by",
|
555 |
+
"Birds take flight"
|
556 |
],
|
557 |
+
# 3-4 syllables - specific contexts
|
558 |
3: [
|
559 |
+
"Coffee's getting cold",
|
560 |
+
"Fan blades spinning",
|
561 |
+
"Pages turning slow",
|
562 |
+
"Neighbors argue loud",
|
563 |
+
"Radio plays soft"
|
564 |
],
|
565 |
+
# 4-5 syllables - specific details
|
566 |
4: [
|
567 |
+
"Fingers tap the table",
|
568 |
+
"Taxi waits in rain",
|
569 |
+
"Laptop screen flickers",
|
570 |
+
"Wedding ring forgotten",
|
571 |
+
"Sunday church bells ring"
|
572 |
],
|
573 |
+
# 5-6 syllables - context rich
|
574 |
5: [
|
575 |
+
"Letters sent without stamps",
|
576 |
+
"Broken watch shows wrong time",
|
577 |
+
"Faded jeans with torn knees",
|
578 |
+
"Neighbor's dog keeps barking",
|
579 |
+
"Smoke detector beeping"
|
580 |
],
|
581 |
+
# 6-7 syllables - specific scenarios
|
582 |
6: [
|
583 |
+
"Fingerprints on dusty frames",
|
584 |
+
"Afternoon bus running late",
|
585 |
+
"Yellowed photos in a box",
|
586 |
+
"Backyard swing rocks in the wind",
|
587 |
+
"Curtains move with summer breeze"
|
588 |
]
|
589 |
}
|
590 |
|
591 |
+
# Make theme and emotion specific placeholders to add to the list
|
592 |
+
theme_specific = []
|
593 |
+
if theme.lower() in ["love", "relationship", "romance"]:
|
594 |
+
theme_specific = ["Lipstick on a glass", "Texts left on read", "Perfume on your coat"]
|
595 |
+
elif theme.lower() in ["loss", "grief", "sadness"]:
|
596 |
+
theme_specific = ["Empty chair remains", "Photos face-down now", "Clothes still in the closet"]
|
597 |
+
elif theme.lower() in ["hope", "inspiration", "triumph"]:
|
598 |
+
theme_specific = ["Seeds begin to sprout", "Finish line in sight", "Mountain peak awaits"]
|
599 |
+
|
600 |
# Get the closest matching syllable group
|
601 |
+
closest_group = min(specific_placeholders.keys(), key=lambda k: abs(k - target_syllables))
|
602 |
+
|
603 |
+
# Create pool of available placeholders from both specific and theme specific options
|
604 |
+
all_placeholders = specific_placeholders[closest_group] + theme_specific
|
605 |
|
606 |
# Choose a placeholder that hasn't been used yet
|
607 |
+
available_placeholders = [p for p in all_placeholders if p not in clean_lines]
|
|
|
608 |
|
609 |
if available_placeholders:
|
610 |
+
# Use modulo for more variation
|
611 |
+
idx = (i * 17 + len(clean_lines) * 13) % len(available_placeholders)
|
612 |
+
placeholder = available_placeholders[idx]
|
613 |
else:
|
614 |
+
# If we've used all placeholders, create something random and specific
|
615 |
+
subjects = ["Car", "Dog", "Child", "Clock", "Phone", "Tree", "Book", "Door", "Light"]
|
616 |
+
verbs = ["waits", "moves", "stops", "falls", "breaks", "turns", "opens", "runs", "sleeps"]
|
617 |
+
modifiers = ["slowly", "loudly", "brightly", "quickly", "gently", "badly", "forever", "never", "always"]
|
618 |
+
|
619 |
+
# Ensure randomness with seed that changes with each call
|
620 |
+
import random
|
621 |
+
random.seed(len(clean_lines) * 27 + i * 31)
|
622 |
+
|
623 |
+
subj = random.choice(subjects)
|
624 |
+
verb = random.choice(verbs)
|
625 |
+
|
626 |
+
if target_syllables <= 3:
|
627 |
+
placeholder = f"{subj} {verb}"
|
628 |
else:
|
629 |
+
mod = random.choice(modifiers)
|
630 |
+
placeholder = f"{subj} {verb} {mod}"
|
631 |
else:
|
632 |
+
placeholder = "Final page turns now"
|
633 |
|
634 |
clean_lines.append(placeholder)
|
635 |
|
636 |
# Assemble final lyrics
|
637 |
final_lyrics = '\n'.join(clean_lines)
|
638 |
|
639 |
+
# Add a warning if we detected too many clichés
|
640 |
+
if cliche_percentage >= 40:
|
641 |
+
final_lyrics = f"""WARNING: These lyrics contain several overused phrases and clichés.
|
642 |
+
Try regenerating for more original content.
|
643 |
+
|
644 |
+
{final_lyrics}"""
|
645 |
+
|
646 |
+
# 12. 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 |
|
|
|
654 |
print(error_msg)
|
655 |
return error_msg
|
656 |
|
657 |
+
def analyze_lyrics_rhythm_match(lyrics, lyric_templates, genre="pop"):
|
658 |
"""Analyze how well the generated lyrics match the beat patterns and syllable requirements"""
|
659 |
if not lyric_templates or not lyrics:
|
660 |
return "No beat templates or lyrics available for analysis."
|
|
|
741 |
result += f"- Average lines per thought: {sentence_flow_analysis['avg_lines_per_group']:.1f}\n"
|
742 |
result += f"- Flow quality: {sentence_flow_analysis['flow_quality']}\n"
|
743 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
744 |
# Add guidance on ideal distribution for syllables and sentence flow
|
745 |
+
result += f"\n**Syllable & Flow Guidance:**\n"
|
746 |
+
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"
|
747 |
+
result += f"- Break complete thoughts across 2-3 lines for natural flow\n"
|
748 |
+
result += f"- Connect your lyrics with sentence fragments that flow across lines\n"
|
749 |
+
result += f"- Use conjunctions, prepositions, and dependent clauses to connect lines\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
750 |
|
751 |
# Add genre-specific notes
|
752 |
result += f"\n**Genre Notes ({genre}):**\n"
|
|
|
865 |
"flow_quality": flow_quality
|
866 |
}
|
867 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
868 |
# Create Gradio interface
|
869 |
def create_interface():
|
870 |
with gr.Blocks(title="Music Analysis & Lyrics Generator") as demo:
|