jacob-c commited on
Commit
4adac6c
·
1 Parent(s): c61f3e3
Files changed (2) hide show
  1. app.py +109 -66
  2. 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
- KEY INSTRUCTIONS:
288
- 1. CONNECTED THOUGHTS: Spread complete sentences across multiple lines (2-3 lines)
289
- 2. SHORT LINES: Keep each line between {min_syllables}-{max_syllables} syllables
290
- 3. NATURAL FLOW: Use conjunctions, prepositions and fragments that flow together
291
- 4. AVOID CLICHÉS: Don't use phrases like "moonlight shimmers" or "time slips away"
292
- 5. ORIGINALITY: Create fresh, original imagery related to {theme} and {emotion}
293
- 6. DISTINCTIVE VOICE: Use specific, concrete details rather than generic phrases
 
 
294
 
295
  FORMAT:
296
- - Write exactly {num_phrases} plain text lines of lyrics (just the lyrics, no annotations)
297
- - Don't include explanations, section markers, or line numbers
298
-
299
- ===== EXAMPLES OF CONNECTED THOUGHTS =====
300
 
301
- Example 1 - Connection with conjunction:
302
- {ex1_line1}
303
- {ex1_line2}
304
- {ex1_line3}
305
- {ex1_line4}
306
 
307
- Notice how these lines connect grammatically to form complete thoughts.
308
 
309
- Example 2 - Connection with prepositions:
310
- {ex2_fragments[0]}
311
- {ex2_fragments[1]}
312
- {ex2_fragments[2]}
313
- {ex2_fragments[3]}
314
 
315
- These lines use prepositions to create flow across multiple lines.
 
 
 
 
316
 
317
- 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.
318
 
319
- Just write {num_phrases} lines of original lyrics, focusing on connecting your lines with thought flow.
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: Check for template copying or clichéd phrases
 
 
 
 
 
 
 
 
 
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
- # 11. If we have lyric templates, ensure we have the correct number of lines
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(max_syllables, (template.get('min_expected', 2) + template.get('max_expected', 7)) // 2)
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's getting cold",
563
- "Fan blades spinning",
564
- "Pages turning slow",
565
- "Neighbors argue loud",
566
- "Radio plays soft"
567
  ],
568
  # 4-5 syllables - specific details
569
  4: [
570
- "Fingers tap the table",
571
  "Taxi waits in rain",
572
- "Laptop screen flickers",
573
- "Wedding ring forgotten",
574
- "Sunday church bells ring"
575
  ],
576
  # 5-6 syllables - context rich
577
  5: [
578
- "Letters sent without stamps",
579
- "Broken watch shows wrong time",
580
- "Faded jeans with torn knees",
581
- "Neighbor's dog keeps barking",
582
- "Smoke detector beeping"
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 a glass", "Texts left on read", "Perfume on your coat"]
598
  elif theme.lower() in ["loss", "grief", "sadness"]:
599
- theme_specific = ["Empty chair remains", "Photos face-down now", "Clothes still in the closet"]
600
  elif theme.lower() in ["hope", "inspiration", "triumph"]:
601
- theme_specific = ["Seeds begin to sprout", "Finish line in sight", "Mountain peak awaits"]
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", "Child", "Clock", "Phone", "Tree", "Book", "Door", "Light"]
619
- verbs = ["waits", "moves", "stops", "falls", "breaks", "turns", "opens", "runs", "sleeps"]
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
- if target_syllables <= 3:
630
- placeholder = f"{subj} {verb}"
631
- else:
632
- mod = random.choice(modifiers)
633
- placeholder = f"{subj} {verb} {mod}"
634
  else:
635
- placeholder = "Final page turns now"
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
- # 12. Final sanity check - if we have nothing or garbage, return an error
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.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,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 SHORT. Break complete thoughts across MULTIPLE LINES."
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 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
 
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