root commited on
Commit
bea11aa
·
1 Parent(s): a1321b3
Files changed (2) hide show
  1. app.py +76 -26
  2. emotionanalysis.py +35 -24
app.py CHANGED
@@ -89,7 +89,7 @@ music_analyzer = MusicAnalyzer()
89
  # Process uploaded audio file
90
  def process_audio(audio_file):
91
  if audio_file is None:
92
- return "No audio file provided", None, None, None, None, None, None, None
93
 
94
  try:
95
  # Load and analyze audio
@@ -107,8 +107,18 @@ def process_audio(audio_file):
107
 
108
  # Extract key information
109
  tempo = music_analysis["rhythm_analysis"]["tempo"]
110
- emotion = music_analysis["emotion_analysis"]["primary_emotion"]
111
- theme = music_analysis["theme_analysis"]["primary_theme"]
 
 
 
 
 
 
 
 
 
 
112
 
113
  # Use genre classification directly instead of pipeline
114
  if genre_model is not None and genre_feature_extractor is not None:
@@ -159,8 +169,15 @@ def process_audio(audio_file):
159
  **Tempo:** {tempo:.1f} BPM
160
  **Time Signature:** {time_signature} (Confidence: {time_sig_result["confidence"]:.1%})
161
  **Key:** {music_analysis["tonal_analysis"]["key"]} {music_analysis["tonal_analysis"]["mode"]}
162
- **Primary Emotion:** {emotion}
163
- **Primary Theme:** {theme}
 
 
 
 
 
 
 
164
  **Top Genre:** {primary_genre}
165
 
166
  {genre_results_text}
@@ -179,7 +196,6 @@ def process_audio(audio_file):
179
  """
180
 
181
  # Check if genre is supported for lyrics generation
182
- # Use the supported_genres list from BeatAnalyzer
183
  genre_supported = any(genre.lower() in primary_genre.lower() for genre in beat_analyzer.supported_genres)
184
 
185
  # Generate lyrics only for supported genres
@@ -191,12 +207,12 @@ def process_audio(audio_file):
191
  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."
192
  beat_match_analysis = "Lyrics generation not available for this genre."
193
 
194
- return analysis_summary, lyrics, tempo, time_signature, emotion, theme, primary_genre, beat_match_analysis
195
 
196
  except Exception as e:
197
  error_msg = f"Error processing audio: {str(e)}"
198
  print(error_msg)
199
- return error_msg, None, None, None, None, None, None, None
200
 
201
  def generate_lyrics(music_analysis, genre, duration):
202
  try:
@@ -204,8 +220,17 @@ def generate_lyrics(music_analysis, genre, duration):
204
  tempo = music_analysis["rhythm_analysis"]["tempo"]
205
  key = music_analysis["tonal_analysis"]["key"]
206
  mode = music_analysis["tonal_analysis"]["mode"]
207
- emotion = music_analysis["emotion_analysis"]["primary_emotion"]
208
- theme = music_analysis["theme_analysis"]["primary_theme"]
 
 
 
 
 
 
 
 
 
209
 
210
  # Get beat analysis and templates
211
  lyric_templates = music_analysis.get("lyric_templates", [])
@@ -219,8 +244,16 @@ def generate_lyrics(music_analysis, genre, duration):
219
 
220
  # If no templates, fall back to original method
221
  if not lyric_templates:
222
- # Simplified prompt
223
- prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM. The emotion is {emotion} and theme is {theme}.
 
 
 
 
 
 
 
 
224
 
225
  ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
226
  """
@@ -236,7 +269,7 @@ ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
236
  max_syllables = 7
237
  avg_syllables = 4
238
 
239
- # Create random examples based on the song's theme and emotion
240
  # to avoid the LLM copying our examples directly
241
  example_themes = [
242
  {"emotion": "love", "fragments": ["I see your face", "across the room", "my heart beats fast", "can't look away"]},
@@ -246,8 +279,8 @@ ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
246
  {"emotion": "longing", "fragments": ["miles apart now", "under same stars", "thinking of you", "across the distance"]}
247
  ]
248
 
249
- # Select a theme that doesn't match the song's emotion to avoid copying
250
- selected_themes = [t for t in example_themes if t["emotion"].lower() != emotion.lower()]
251
  if not selected_themes:
252
  selected_themes = example_themes
253
 
@@ -274,8 +307,13 @@ ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
274
  # Create a more direct prompt with examples and specific syllable count guidance
275
  prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM.
276
 
277
- PRIMARY THEME: {theme}
278
- EMOTION: {emotion}
 
 
 
 
 
279
 
280
  I need EXACTLY {num_phrases} lines of lyrics with these STRICT requirements:
281
 
@@ -288,6 +326,8 @@ CRITICAL INSTRUCTIONS:
288
  6. CONCRETE IMAGERY: Use specific, tangible details rather than abstract concepts
289
  7. NO CLICHÉS: Avoid common phrases like "time slips away" or "memories fade"
290
  8. ONE THOUGHT PER LINE: Express just one simple idea in each line
 
 
291
 
292
  FORMAT:
293
  - Write exactly {num_phrases} short text lines
@@ -310,7 +350,7 @@ by the front door (3 syllables)
310
  where shoes pile up (3 syllables)
311
  since you moved in (3 syllables)
312
 
313
- DO NOT copy my examples. Create ENTIRELY NEW lyrics about {theme} with {emotion} feeling.
314
 
315
  REMEMBER: NO LINE SHOULD EXCEED {max_syllables} SYLLABLES - this is the most important rule!
316
  """
@@ -590,11 +630,11 @@ REMEMBER: NO LINE SHOULD EXCEED {max_syllables} SYLLABLES - this is the most imp
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 glass", "Text left on read", "Scent on your coat"]
595
- elif theme.lower() in ["loss", "grief", "sadness"]:
596
  theme_specific = ["Chair sits empty", "Photos face down", "Clothes in closet"]
597
- elif theme.lower() in ["hope", "inspiration", "triumph"]:
598
  theme_specific = ["Seeds start to grow", "Finish line waits", "New day breaks through"]
599
 
600
  # Get the closest matching syllable group
@@ -928,8 +968,14 @@ def create_interface():
928
  with gr.Row():
929
  tempo_output = gr.Number(label="Tempo (BPM)")
930
  time_sig_output = gr.Textbox(label="Time Signature")
931
- emotion_output = gr.Textbox(label="Primary Emotion")
932
- theme_output = gr.Textbox(label="Primary Theme")
 
 
 
 
 
 
933
  genre_output = gr.Textbox(label="Primary Genre")
934
 
935
  with gr.Tab("Generated Lyrics"):
@@ -942,8 +988,12 @@ def create_interface():
942
  analyze_btn.click(
943
  fn=process_audio,
944
  inputs=[audio_input],
945
- outputs=[analysis_output, lyrics_output, tempo_output, time_sig_output,
946
- emotion_output, theme_output, genre_output, beat_match_output]
 
 
 
 
947
  )
948
 
949
  # Format supported genres for display
@@ -953,7 +1003,7 @@ def create_interface():
953
  ## How it works
954
  1. Upload or record a music file
955
  2. The system analyzes tempo, beats, time signature and other musical features
956
- 3. It detects emotion, theme, and music genre
957
  4. Using beat patterns and syllable stress analysis, it generates perfectly aligned lyrics
958
  5. Each line of the lyrics is matched to the beat pattern of the corresponding musical phrase
959
 
 
89
  # Process uploaded audio file
90
  def process_audio(audio_file):
91
  if audio_file is None:
92
+ return "No audio file provided", None, None, None, None, None, None, None, None, None
93
 
94
  try:
95
  # Load and analyze audio
 
107
 
108
  # Extract key information
109
  tempo = music_analysis["rhythm_analysis"]["tempo"]
110
+
111
+ # Get top two emotions
112
+ emotion_scores = music_analysis["emotion_analysis"]["emotion_scores"]
113
+ sorted_emotions = sorted(emotion_scores.items(), key=lambda x: x[1], reverse=True)
114
+ primary_emotion = sorted_emotions[0][0]
115
+ secondary_emotion = sorted_emotions[1][0] if len(sorted_emotions) > 1 else None
116
+
117
+ # Get top two themes
118
+ theme_scores = music_analysis["theme_analysis"]["theme_scores"]
119
+ sorted_themes = sorted(theme_scores.items(), key=lambda x: x[1], reverse=True)
120
+ primary_theme = sorted_themes[0][0]
121
+ secondary_theme = sorted_themes[1][0] if len(sorted_themes) > 1 else None
122
 
123
  # Use genre classification directly instead of pipeline
124
  if genre_model is not None and genre_feature_extractor is not None:
 
169
  **Tempo:** {tempo:.1f} BPM
170
  **Time Signature:** {time_signature} (Confidence: {time_sig_result["confidence"]:.1%})
171
  **Key:** {music_analysis["tonal_analysis"]["key"]} {music_analysis["tonal_analysis"]["mode"]}
172
+
173
+ **Emotions:**
174
+ - Primary: {primary_emotion} (Confidence: {emotion_scores[primary_emotion]:.1%})
175
+ - Secondary: {secondary_emotion} (Confidence: {emotion_scores[secondary_emotion]:.1%})
176
+
177
+ **Themes:**
178
+ - Primary: {primary_theme} (Confidence: {theme_scores[primary_theme]:.1%})
179
+ - Secondary: {secondary_theme} (Confidence: {theme_scores[secondary_theme]:.1%})
180
+
181
  **Top Genre:** {primary_genre}
182
 
183
  {genre_results_text}
 
196
  """
197
 
198
  # Check if genre is supported for lyrics generation
 
199
  genre_supported = any(genre.lower() in primary_genre.lower() for genre in beat_analyzer.supported_genres)
200
 
201
  # Generate lyrics only for supported genres
 
207
  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."
208
  beat_match_analysis = "Lyrics generation not available for this genre."
209
 
210
+ return analysis_summary, lyrics, tempo, time_signature, primary_emotion, secondary_emotion, primary_theme, secondary_theme, primary_genre, beat_match_analysis
211
 
212
  except Exception as e:
213
  error_msg = f"Error processing audio: {str(e)}"
214
  print(error_msg)
215
+ return error_msg, None, None, None, None, None, None, None, None, None
216
 
217
  def generate_lyrics(music_analysis, genre, duration):
218
  try:
 
220
  tempo = music_analysis["rhythm_analysis"]["tempo"]
221
  key = music_analysis["tonal_analysis"]["key"]
222
  mode = music_analysis["tonal_analysis"]["mode"]
223
+
224
+ # Get both primary and secondary emotions and themes
225
+ emotion_scores = music_analysis["emotion_analysis"]["emotion_scores"]
226
+ sorted_emotions = sorted(emotion_scores.items(), key=lambda x: x[1], reverse=True)
227
+ primary_emotion = sorted_emotions[0][0]
228
+ secondary_emotion = sorted_emotions[1][0] if len(sorted_emotions) > 1 else None
229
+
230
+ theme_scores = music_analysis["theme_analysis"]["theme_scores"]
231
+ sorted_themes = sorted(theme_scores.items(), key=lambda x: x[1], reverse=True)
232
+ primary_theme = sorted_themes[0][0]
233
+ secondary_theme = sorted_themes[1][0] if len(sorted_themes) > 1 else None
234
 
235
  # Get beat analysis and templates
236
  lyric_templates = music_analysis.get("lyric_templates", [])
 
244
 
245
  # If no templates, fall back to original method
246
  if not lyric_templates:
247
+ # Enhanced prompt with both emotions and themes
248
+ prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM.
249
+
250
+ EMOTIONS:
251
+ - Primary: {primary_emotion}
252
+ - Secondary: {secondary_emotion}
253
+
254
+ THEMES:
255
+ - Primary: {primary_theme}
256
+ - Secondary: {secondary_theme}
257
 
258
  ONLY WRITE THE ACTUAL LYRICS. NO EXPLANATIONS OR META-TEXT.
259
  """
 
269
  max_syllables = 7
270
  avg_syllables = 4
271
 
272
+ # Create random examples based on the song's themes and emotions
273
  # to avoid the LLM copying our examples directly
274
  example_themes = [
275
  {"emotion": "love", "fragments": ["I see your face", "across the room", "my heart beats fast", "can't look away"]},
 
279
  {"emotion": "longing", "fragments": ["miles apart now", "under same stars", "thinking of you", "across the distance"]}
280
  ]
281
 
282
+ # Select a theme that doesn't match the song's emotions to avoid copying
283
+ selected_themes = [t for t in example_themes if t["emotion"].lower() not in [primary_emotion.lower(), secondary_emotion.lower()]]
284
  if not selected_themes:
285
  selected_themes = example_themes
286
 
 
307
  # Create a more direct prompt with examples and specific syllable count guidance
308
  prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM.
309
 
310
+ EMOTIONS:
311
+ - Primary: {primary_emotion}
312
+ - Secondary: {secondary_emotion}
313
+
314
+ THEMES:
315
+ - Primary: {primary_theme}
316
+ - Secondary: {secondary_theme}
317
 
318
  I need EXACTLY {num_phrases} lines of lyrics with these STRICT requirements:
319
 
 
326
  6. CONCRETE IMAGERY: Use specific, tangible details rather than abstract concepts
327
  7. NO CLICHÉS: Avoid common phrases like "time slips away" or "memories fade"
328
  8. ONE THOUGHT PER LINE: Express just one simple idea in each line
329
+ 9. EMOTION BLEND: Blend both {primary_emotion} and {secondary_emotion} emotions naturally
330
+ 10. THEME WEAVING: Weave both {primary_theme} and {secondary_theme} themes together
331
 
332
  FORMAT:
333
  - Write exactly {num_phrases} short text lines
 
350
  where shoes pile up (3 syllables)
351
  since you moved in (3 syllables)
352
 
353
+ DO NOT copy my examples. Create ENTIRELY NEW lyrics that blend {primary_emotion} and {secondary_emotion} emotions while exploring {primary_theme} and {secondary_theme} themes.
354
 
355
  REMEMBER: NO LINE SHOULD EXCEED {max_syllables} SYLLABLES - this is the most important rule!
356
  """
 
630
 
631
  # Make theme and emotion specific placeholders to add to the list
632
  theme_specific = []
633
+ if primary_theme.lower() in ["love", "relationship", "romance"]:
634
  theme_specific = ["Lipstick on glass", "Text left on read", "Scent on your coat"]
635
+ elif primary_theme.lower() in ["loss", "grief", "sadness"]:
636
  theme_specific = ["Chair sits empty", "Photos face down", "Clothes in closet"]
637
+ elif primary_theme.lower() in ["hope", "inspiration", "triumph"]:
638
  theme_specific = ["Seeds start to grow", "Finish line waits", "New day breaks through"]
639
 
640
  # Get the closest matching syllable group
 
968
  with gr.Row():
969
  tempo_output = gr.Number(label="Tempo (BPM)")
970
  time_sig_output = gr.Textbox(label="Time Signature")
971
+
972
+ with gr.Row():
973
+ primary_emotion_output = gr.Textbox(label="Primary Emotion")
974
+ secondary_emotion_output = gr.Textbox(label="Secondary Emotion")
975
+
976
+ with gr.Row():
977
+ primary_theme_output = gr.Textbox(label="Primary Theme")
978
+ secondary_theme_output = gr.Textbox(label="Secondary Theme")
979
  genre_output = gr.Textbox(label="Primary Genre")
980
 
981
  with gr.Tab("Generated Lyrics"):
 
988
  analyze_btn.click(
989
  fn=process_audio,
990
  inputs=[audio_input],
991
+ outputs=[
992
+ analysis_output, lyrics_output, tempo_output, time_sig_output,
993
+ primary_emotion_output, secondary_emotion_output,
994
+ primary_theme_output, secondary_theme_output,
995
+ genre_output, beat_match_output
996
+ ]
997
  )
998
 
999
  # Format supported genres for display
 
1003
  ## How it works
1004
  1. Upload or record a music file
1005
  2. The system analyzes tempo, beats, time signature and other musical features
1006
+ 3. It detects emotions, themes, and music genre
1007
  4. Using beat patterns and syllable stress analysis, it generates perfectly aligned lyrics
1008
  5. Each line of the lyrics is matched to the beat pattern of the corresponding musical phrase
1009
 
emotionanalysis.py CHANGED
@@ -11,33 +11,33 @@ except ImportError:
11
 
12
  class MusicAnalyzer:
13
  def __init__(self):
14
- # Scientifically grounded emotion classes (valence, arousal space)
15
- # See: Eerola & Vuoskoski, 2011; Russell, 1980
16
  self.emotion_classes = {
17
- 'happy': {'valence': 0.9, 'arousal': 0.7},
18
- 'excited': {'valence': 0.8, 'arousal': 0.95},
19
- 'tender': {'valence': 0.7, 'arousal': 0.3},
20
- 'calm': {'valence': 0.65, 'arousal': 0.15},
21
- 'sad': {'valence': 0.2, 'arousal': 0.25},
22
- 'depressed': {'valence': 0.05, 'arousal': 0.05},
23
- 'angry': {'valence': 0.1, 'arousal': 0.8},
24
- 'fearful': {'valence': 0.05, 'arousal': 0.95}
25
  }
26
- # Theme classes based on emotion clusters (from Allan, 2014, with mapping)
27
  self.theme_classes = {
28
- 'love': ['tender', 'calm', 'happy'],
29
  'triumph': ['excited', 'happy', 'angry'],
30
  'loss': ['sad', 'depressed'],
31
  'adventure': ['excited', 'fearful'],
32
- 'reflection': ['calm', 'sad'],
33
  'conflict': ['angry', 'fearful']
34
  }
 
35
  self.feature_weights = {
36
- 'mode': 0.25,
37
- 'tempo': 0.2,
38
- 'energy': 0.2,
39
- 'brightness': 0.2,
40
- 'rhythm_complexity': 0.15
41
  }
42
  self.key_names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
43
 
@@ -133,11 +133,13 @@ class MusicAnalyzer:
133
  }
134
 
135
  def feature_to_valence_arousal(self, features):
136
- # Normalize features to [0, 1]
137
- tempo_norm = np.clip((features['tempo'] - 40) / (200 - 40), 0, 1)
138
- energy_norm = np.clip(features['energy'] / 1.0, 0, 1)
139
- brightness_norm = np.clip(features['brightness'] / 1.0, 0, 1)
140
- rhythm_complexity_norm = np.clip(features['rhythm_complexity'] / 2.0, 0, 1)
 
 
141
  valence = (
142
  self.feature_weights['mode'] * (1.0 if features['is_major'] else 0.0) +
143
  self.feature_weights['tempo'] * tempo_norm +
@@ -150,6 +152,12 @@ class MusicAnalyzer:
150
  self.feature_weights['brightness'] * brightness_norm +
151
  self.feature_weights['rhythm_complexity'] * rhythm_complexity_norm
152
  )
 
 
 
 
 
 
153
  return float(np.clip(valence, 0, 1)), float(np.clip(arousal, 0, 1))
154
 
155
  def analyze_emotion(self, rhythm_data, tonal_data, energy_data):
@@ -263,4 +271,7 @@ if __name__ == "__main__":
263
  # Show detailed results (optional)
264
  import json
265
  print("\n=== DETAILED ANALYSIS ===")
266
- print(json.dumps(results, indent=2))
 
 
 
 
11
 
12
  class MusicAnalyzer:
13
  def __init__(self):
14
+ # Emotion coordinates (pop-optimized, more separation)
 
15
  self.emotion_classes = {
16
+ 'happy': {'valence': 0.96, 'arousal': 0.72},
17
+ 'excited': {'valence': 0.88, 'arousal': 0.96},
18
+ 'tender': {'valence': 0.70, 'arousal': 0.39},
19
+ 'calm': {'valence': 0.58, 'arousal': 0.18},
20
+ 'sad': {'valence': 0.18, 'arousal': 0.19},
21
+ 'depressed': {'valence': 0.09, 'arousal': 0.06},
22
+ 'angry': {'valence': 0.11, 'arousal': 0.80},
23
+ 'fearful': {'valence': 0.13, 'arousal': 0.99}
24
  }
25
+ # More realistic pop theme mapping
26
  self.theme_classes = {
27
+ 'love': ['happy', 'excited', 'tender'],
28
  'triumph': ['excited', 'happy', 'angry'],
29
  'loss': ['sad', 'depressed'],
30
  'adventure': ['excited', 'fearful'],
31
+ 'reflection': ['calm', 'tender', 'sad'],
32
  'conflict': ['angry', 'fearful']
33
  }
34
+ # Pop-tuned feature weights
35
  self.feature_weights = {
36
+ 'mode': 0.34,
37
+ 'tempo': 0.32,
38
+ 'energy': 0.16,
39
+ 'brightness': 0.14,
40
+ 'rhythm_complexity': 0.04
41
  }
42
  self.key_names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
43
 
 
133
  }
134
 
135
  def feature_to_valence_arousal(self, features):
136
+ # Normalization for typical pop values
137
+ # tempo: 40-180 BPM, energy: 0.08-0.5 (librosa RMS), brightness: 0.25-0.7
138
+ tempo_norm = np.clip((features['tempo'] - 70) / (170 - 70), 0, 1)
139
+ energy_norm = np.clip((features['energy'] - 0.08) / (0.5 - 0.08), 0, 1)
140
+ brightness_norm = np.clip((features['brightness'] - 0.25) / (0.7 - 0.25), 0, 1)
141
+ rhythm_complexity_norm = np.clip((features['rhythm_complexity'] - 0.1) / (0.8 - 0.1), 0, 1)
142
+
143
  valence = (
144
  self.feature_weights['mode'] * (1.0 if features['is_major'] else 0.0) +
145
  self.feature_weights['tempo'] * tempo_norm +
 
152
  self.feature_weights['brightness'] * brightness_norm +
153
  self.feature_weights['rhythm_complexity'] * rhythm_complexity_norm
154
  )
155
+
156
+ # Explicit bias: if major mode + tempo > 100 + brightness > 0.5, boost valence/arousal toward happy/excited
157
+ if features['is_major'] and features['tempo'] > 100 and features['brightness'] > 0.5:
158
+ valence = max(valence, 0.85)
159
+ arousal = max(arousal, 0.7)
160
+
161
  return float(np.clip(valence, 0, 1)), float(np.clip(arousal, 0, 1))
162
 
163
  def analyze_emotion(self, rhythm_data, tonal_data, energy_data):
 
271
  # Show detailed results (optional)
272
  import json
273
  print("\n=== DETAILED ANALYSIS ===")
274
+ print(json.dumps(results, indent=2))
275
+
276
+ # Visualize the analysis
277
+ # analyzer.visualize_analysis(demo_file)