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