root
commited on
Commit
·
bea11aa
1
Parent(s):
a1321b3
ss
Browse files- app.py +76 -26
- 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 |
-
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 163 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 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 |
-
|
| 208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 223 |
-
prompt = f"""Write song lyrics for a {genre} song in {key} {mode} with tempo {tempo} BPM.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 250 |
-
selected_themes = [t for t in example_themes if t["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 |
-
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 594 |
theme_specific = ["Lipstick on glass", "Text left on read", "Scent on your coat"]
|
| 595 |
-
elif
|
| 596 |
theme_specific = ["Chair sits empty", "Photos face down", "Clothes in closet"]
|
| 597 |
-
elif
|
| 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 |
-
|
| 932 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=[
|
| 946 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
#
|
| 15 |
-
# See: Eerola & Vuoskoski, 2011; Russell, 1980
|
| 16 |
self.emotion_classes = {
|
| 17 |
-
'happy': {'valence': 0.
|
| 18 |
-
'excited': {'valence': 0.
|
| 19 |
-
'tender': {'valence': 0.
|
| 20 |
-
'calm': {'valence': 0.
|
| 21 |
-
'sad': {'valence': 0.
|
| 22 |
-
'depressed': {'valence': 0.
|
| 23 |
-
'angry': {'valence': 0.
|
| 24 |
-
'fearful': {'valence': 0.
|
| 25 |
}
|
| 26 |
-
#
|
| 27 |
self.theme_classes = {
|
| 28 |
-
'love': ['
|
| 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.
|
| 37 |
-
'tempo': 0.
|
| 38 |
-
'energy': 0.
|
| 39 |
-
'brightness': 0.
|
| 40 |
-
'rhythm_complexity': 0.
|
| 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 |
-
#
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
| 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)
|