rushi69kk commited on
Commit
5b1971b
Β·
verified Β·
1 Parent(s): f0fe3ce

create app.py

Browse files
Files changed (1) hide show
  1. app.py +786 -0
app.py ADDED
@@ -0,0 +1,786 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import pandas as pd
4
+ import tensorflow as tf
5
+ from sklearn.preprocessing import StandardScaler
6
+ import joblib
7
+ from sklearn.metrics.pairwise import cosine_similarity
8
+ import ast
9
+ import os
10
+ from pathlib import Path
11
+ import matplotlib.pyplot as plt
12
+ import seaborn as sns
13
+ import time
14
+
15
+ # Set page config and theme
16
+ st.set_page_config(
17
+ page_title="CHORD based Music Recommendation System",
18
+ page_icon=None,
19
+ layout="wide",
20
+ initial_sidebar_state="expanded"
21
+ )
22
+
23
+ # Professional Dark Theme CSS
24
+ st.markdown("""
25
+ <style>
26
+ .main {
27
+ background-color: #1E1E1E;
28
+ color: #E0E0E0;
29
+ }
30
+
31
+ h1, h2, h3, h4 {
32
+ color: #FFFFFF;
33
+ font-family: 'Helvetica Neue', sans-serif;
34
+ font-weight: 500;
35
+ margin-bottom: 1.5rem;
36
+ }
37
+
38
+ .stButton>button {
39
+ background: linear-gradient(90deg, #2C5364, #203A43);
40
+ color: white;
41
+ border-radius: 4px;
42
+ padding: 0.5rem 1rem;
43
+ border: none;
44
+ font-weight: 500;
45
+ font-size: 0.9rem;
46
+ transition: all 0.3s ease;
47
+ }
48
+
49
+ .stButton>button:hover {
50
+ transform: translateY(-2px);
51
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
52
+ }
53
+
54
+ .card {
55
+ background: #2D2D2D;
56
+ padding: 1.5rem;
57
+ border-radius: 8px;
58
+ margin: 1rem 0;
59
+ border: 1px solid #3D3D3D;
60
+ transition: all 0.3s ease;
61
+ }
62
+
63
+ .card:hover {
64
+ transform: translateY(-2px);
65
+ box-shadow: 0 8px 16px rgba(0,0,0,0.2);
66
+ }
67
+
68
+ .highlight {
69
+ color: #4A90E2;
70
+ font-weight: 500;
71
+ }
72
+
73
+ .pattern-match {
74
+ color: #4A90E2;
75
+ font-weight: 500;
76
+ }
77
+
78
+ .metric-card {
79
+ background: #2D2D2D;
80
+ padding: 1rem;
81
+ border-radius: 8px;
82
+ text-align: center;
83
+ border: 1px solid #3D3D3D;
84
+ }
85
+
86
+ .metric-value {
87
+ font-size: 1.5rem;
88
+ font-weight: 500;
89
+ color: #4A90E2;
90
+ }
91
+
92
+ .metric-label {
93
+ font-size: 0.9rem;
94
+ color: #888888;
95
+ }
96
+
97
+ .stTabs [data-baseweb="tab-list"] {
98
+ gap: 1rem;
99
+ background-color: #2D2D2D;
100
+ padding: 0.5rem;
101
+ border-radius: 8px;
102
+ }
103
+
104
+ .stTabs [data-baseweb="tab"] {
105
+ padding: 0.5rem 1rem;
106
+ font-weight: 500;
107
+ }
108
+
109
+ .stTabs [aria-selected="true"] {
110
+ background: linear-gradient(90deg, #2C5364, #203A43);
111
+ color: white;
112
+ border-radius: 4px;
113
+ }
114
+
115
+ audio {
116
+ width: 100%;
117
+ height: 40px;
118
+ border-radius: 4px;
119
+ margin: 0.5rem 0;
120
+ }
121
+ </style>
122
+ """, unsafe_allow_html=True)
123
+
124
+ # Sidebar
125
+ def show_sidebar():
126
+ with st.sidebar:
127
+ st.markdown("""
128
+ <div style='text-align: center;'>
129
+ <h1>🎡 Music Recommender</h1>
130
+ <p>Discover similar songs based on audio features</p>
131
+ </div>
132
+ """, unsafe_allow_html=True)
133
+
134
+ st.markdown("---")
135
+ st.markdown("### About")
136
+ st.markdown("""
137
+ This system uses:
138
+ - Deep Learning (CNN)
139
+ - Audio Feature Analysis
140
+ - Cosine Similarity
141
+ """)
142
+
143
+ st.markdown("---")
144
+ st.markdown("### How to Use")
145
+ st.markdown("""
146
+ 1. Search for a song
147
+ 2. Select from results
148
+ 3. Get recommendations
149
+ 4. Analyze similarities
150
+ """)
151
+
152
+ def display_loading():
153
+ st.markdown("""
154
+ <div class="loading"></div>
155
+ """, unsafe_allow_html=True)
156
+
157
+ def display_song_info(song_name, df):
158
+ song_data = df[df["Song"] == song_name].iloc[0]
159
+
160
+ # Handle chords display
161
+ chords_display = "No chord data available"
162
+ if 'Chords' in song_data:
163
+ try:
164
+ if isinstance(song_data['Chords'], str):
165
+ # If chords are stored as string, try to evaluate it
166
+ chords = ast.literal_eval(song_data['Chords'])
167
+ else:
168
+ chords = song_data['Chords']
169
+
170
+ if isinstance(chords, (list, tuple, set)):
171
+ chords_display = ', '.join(str(chord) for chord in chords)
172
+ else:
173
+ chords_display = str(chords)
174
+ except:
175
+ chords_display = "Error displaying chords"
176
+
177
+ st.markdown(f"""
178
+ <div class="card">
179
+ <h3>{song_name}</h3>
180
+ <p><strong>Features:</strong></p>
181
+ <ul>
182
+ <li>Tempo: {len(song_data['Tempo'])} features</li>
183
+ <li>Chroma: {len(song_data['Chroma'])} features</li>
184
+ <li>MFCC: {len(song_data['MFCC'])} features</li>
185
+ <li>Chords: {chords_display}</li>
186
+ </ul>
187
+ </div>
188
+ """, unsafe_allow_html=True)
189
+
190
+ def load_model_and_data():
191
+ model = tf.keras.models.load_model('music_recommender_model.h5')
192
+ scaler = joblib.load('feature_scaler.joblib')
193
+ df = pd.read_csv("music_features.csv")
194
+ for col in ["Tempo", "Chroma", "MFCC", "Chords"]:
195
+ df[col] = df[col].apply(ast.literal_eval)
196
+ return model, scaler, df
197
+
198
+ def preprocess_features(df, scaler):
199
+ # Combine features
200
+ df["Features"] = df.apply(lambda row: row["Tempo"] + row["Chroma"] + row["MFCC"], axis=1)
201
+ X = np.stack(df["Features"].values)
202
+
203
+ # Normalize
204
+ X_scaled = scaler.transform(X)
205
+
206
+ # Pad to 36 and reshape to 6x6x1 for CNN
207
+ X_padded = np.zeros((X_scaled.shape[0], 36))
208
+ X_padded[:, :X_scaled.shape[1]] = X_scaled
209
+ X_cnn = X_padded.reshape(-1, 6, 6, 1)
210
+
211
+ # Also return flattened features for similarity calculation
212
+ X_flat = X_padded.reshape(X_padded.shape[0], -1)
213
+
214
+ return X_cnn, X_flat
215
+
216
+ def get_recommendations(model, X_cnn, X_flat, df, song_index, num_recommendations=5):
217
+ # Get predictions
218
+ predicted_chords = model.predict(X_cnn)
219
+
220
+ # Calculate similarities using flattened features
221
+ similarities = cosine_similarity([X_flat[song_index]], X_flat)[0]
222
+ top_indices = similarities.argsort()[-(num_recommendations+1):][::-1]
223
+
224
+ # Get recommendations
225
+ input_song = df.iloc[song_index]["Song"]
226
+ recommendations = []
227
+ for idx in top_indices[1:]: # Skip self
228
+ recommendations.append(df.iloc[idx]["Song"])
229
+
230
+ return input_song, recommendations
231
+
232
+ def get_audio_path(song_name):
233
+ """Get the exact audio file path from audio_clips folder"""
234
+ return str(Path('audio_clips') / f"{song_name}.wav")
235
+
236
+ def display_audio_player(song_name, title_color="#FF7676"):
237
+ """Display an audio player with song title"""
238
+ try:
239
+ audio_dir = Path('audio_clips')
240
+ if not audio_dir.exists():
241
+ st.error("Audio clips directory not found!")
242
+ return
243
+
244
+ audio_path = get_audio_path(song_name)
245
+ if os.path.exists(audio_path):
246
+ st.markdown(f"""
247
+ <div class="audio-card">
248
+ <h4 style='color: {title_color}; margin-bottom: 10px;'>{song_name}</h4>
249
+ </div>
250
+ """, unsafe_allow_html=True)
251
+ st.audio(audio_path)
252
+ else:
253
+ st.markdown(f"""
254
+ <div class="audio-card">
255
+ <h4 style='color: {title_color}; margin-bottom: 10px;'>{song_name}</h4>
256
+ <p style='color: #cccccc;'>Audio file not found</p>
257
+ </div>
258
+ """, unsafe_allow_html=True)
259
+
260
+ except Exception as e:
261
+ st.error(f"Error accessing audio file: {str(e)}")
262
+
263
+ def plot_similarity_scores(similarities, songs):
264
+ """Plot similarity scores with enhanced styling"""
265
+ fig, ax = plt.subplots(figsize=(10, 6))
266
+ y_pos = np.arange(len(songs))
267
+
268
+ # Create gradient bars
269
+ colors = plt.cm.viridis(np.linspace(0.2, 1, len(songs)))
270
+ bars = ax.barh(y_pos, similarities, align='center', color=colors)
271
+
272
+ # Add value labels
273
+ for i, v in enumerate(similarities):
274
+ ax.text(v, i, f' {v:.3f}', color='white', va='center')
275
+
276
+ ax.set_yticks(y_pos)
277
+ ax.set_yticklabels(songs)
278
+ ax.invert_yaxis()
279
+ ax.set_xlabel('Similarity Score', color='white')
280
+ ax.set_title('Most Similar Songs', color='white', pad=20)
281
+
282
+ # Set dark theme
283
+ fig.patch.set_facecolor('#1a1a1a')
284
+ ax.set_facecolor('#2d2d2d')
285
+ ax.spines['bottom'].set_color('white')
286
+ ax.spines['top'].set_color('white')
287
+ ax.spines['right'].set_color('white')
288
+ ax.spines['left'].set_color('white')
289
+ ax.tick_params(colors='white')
290
+
291
+ plt.tight_layout()
292
+ return fig
293
+
294
+ def get_unique_chords(df):
295
+ """Get unique chords from the dataset"""
296
+ all_chords = set()
297
+ for chords in df['Chords']:
298
+ try:
299
+ if isinstance(chords, str):
300
+ # If chords are stored as string, try to evaluate it
301
+ chord_list = ast.literal_eval(chords)
302
+ else:
303
+ chord_list = chords
304
+
305
+ if isinstance(chord_list, (list, tuple, set)):
306
+ all_chords.update(str(chord) for chord in chord_list)
307
+ else:
308
+ all_chords.add(str(chords))
309
+ except:
310
+ continue
311
+ return sorted(list(all_chords))
312
+
313
+ def get_chord_name(chord_number):
314
+ """Convert chord number to musical note name"""
315
+ chord_names = {
316
+ '0': 'C',
317
+ '1': 'C#/Db',
318
+ '2': 'D',
319
+ '3': 'D#/Eb',
320
+ '4': 'E',
321
+ '5': 'F',
322
+ '6': 'F#/Gb',
323
+ '7': 'G',
324
+ '8': 'G#/Ab',
325
+ '9': 'A',
326
+ '10': 'A#/Bb',
327
+ '11': 'B'
328
+ }
329
+ return f"{chord_number} ({chord_names[str(chord_number)]})"
330
+
331
+ def get_chord_progression(chords):
332
+ """Convert chord list to progression string with note names"""
333
+ return ' β†’ '.join(get_chord_name(chord) for chord in chords)
334
+
335
+ def find_chord_pattern(song_chords, pattern):
336
+ """Find if a pattern of chords appears anywhere in the song's progression"""
337
+ if not pattern or not song_chords:
338
+ return 0, []
339
+
340
+ # Convert everything to strings for comparison
341
+ song_chords = [str(c) for c in song_chords]
342
+ pattern = [str(c) for c in pattern]
343
+ pattern_len = len(pattern)
344
+
345
+ # Find all occurrences of the pattern
346
+ occurrences = []
347
+
348
+ # Look for the pattern anywhere in the progression
349
+ for i in range(len(song_chords) - pattern_len + 1):
350
+ # Check if pattern starts at position i
351
+ matches = True
352
+ for j in range(pattern_len):
353
+ if song_chords[i + j] != pattern[j]:
354
+ matches = False
355
+ break
356
+ if matches:
357
+ occurrences.extend(range(i, i + pattern_len))
358
+
359
+ # Count unique occurrences (some positions might overlap)
360
+ occurrences = list(set(occurrences))
361
+ return len(occurrences) // pattern_len, occurrences
362
+
363
+ def get_tempo_range(tempo, tolerance=0.2):
364
+ """Get tempo range with tolerance"""
365
+ lower = tempo * (1 - tolerance)
366
+ upper = tempo * (1 + tolerance)
367
+ return lower, upper
368
+
369
+ def get_average_tempo(tempo_features):
370
+ """Calculate average tempo from tempo features"""
371
+ try:
372
+ if isinstance(tempo_features, str):
373
+ tempo_features = ast.literal_eval(tempo_features)
374
+ return sum(tempo_features) / len(tempo_features)
375
+ except:
376
+ return 0
377
+
378
+ def get_songs_by_chord_sequence(df, selected_chords, similarity_threshold=0.2, tempo_filter=None):
379
+ """Get songs that contain the selected chord sequence anywhere in their progression"""
380
+ matching_songs = []
381
+
382
+ for _, row in df.iterrows():
383
+ try:
384
+ # Get song chords
385
+ if isinstance(row['Chords'], str):
386
+ song_chords = ast.literal_eval(row['Chords'])
387
+ else:
388
+ song_chords = row['Chords']
389
+
390
+ # Convert to list if not already
391
+ if not isinstance(song_chords, (list, tuple)):
392
+ song_chords = list(song_chords)
393
+
394
+ # Find pattern occurrences
395
+ repeats, positions = find_chord_pattern(song_chords, selected_chords)
396
+
397
+ # Calculate similarity based on pattern presence
398
+ if positions: # If pattern is found anywhere
399
+ # Calculate how much of the song contains the pattern
400
+ pattern_coverage = len(positions) / len(song_chords)
401
+ # Base similarity on coverage and number of occurrences
402
+ similarity = min(1.0, pattern_coverage + (repeats * 0.1))
403
+
404
+ # Check tempo if filter is active
405
+ if tempo_filter:
406
+ avg_tempo = get_average_tempo(row['Tempo'])
407
+ tempo_lower, tempo_upper = tempo_filter
408
+ if not (tempo_lower <= avg_tempo <= tempo_upper):
409
+ continue
410
+
411
+ if similarity >= similarity_threshold:
412
+ matching_songs.append({
413
+ 'song': row['Song'],
414
+ 'similarity': similarity,
415
+ 'progression': song_chords,
416
+ 'pattern_positions': positions,
417
+ 'repeats': repeats,
418
+ 'tempo': get_average_tempo(row['Tempo'])
419
+ })
420
+ except:
421
+ continue
422
+
423
+ # Sort by number of repeats first, then similarity
424
+ matching_songs.sort(key=lambda x: (x['repeats'], x['similarity']), reverse=True)
425
+ return matching_songs
426
+
427
+ def display_chord_progression(song_info):
428
+ """Display chord progression with pattern highlighting"""
429
+ progression = song_info['progression']
430
+ pattern_positions = set(song_info['pattern_positions'])
431
+
432
+ # Create HTML for the progression
433
+ chord_elements = []
434
+ for i, chord in enumerate(progression):
435
+ chord_name = get_chord_name(chord)
436
+ if i in pattern_positions:
437
+ # Highlight matching pattern
438
+ chord_elements.append(f'<span style="color: #FF4B91; font-weight: bold;">{chord_name}</span>')
439
+ else:
440
+ chord_elements.append(chord_name)
441
+
442
+ progression_html = ' β†’ '.join(chord_elements)
443
+
444
+ st.markdown(f"""
445
+ <div class="card">
446
+ <h4>{song_info['song']}</h4>
447
+ <p><strong>Pattern Found:</strong> {song_info['repeats']} time(s)</p>
448
+ <p><strong>Match Score:</strong> {song_info['similarity']*100:.1f}%</p>
449
+ <p><strong>Tempo:</strong> {song_info['tempo']:.1f} BPM</p>
450
+ <p><strong>Full Progression:</strong></p>
451
+ <p style="font-size: 1.1em; margin-top: 5px;">{progression_html}</p>
452
+ <p><em>Pink highlights show your chord sequence in the progression</em></p>
453
+ </div>
454
+ """, unsafe_allow_html=True)
455
+
456
+ def display_chord_selector(df):
457
+ """Display chord selection interface with ordered selection and tempo filtering"""
458
+ st.markdown("### 🎼 Select Chords")
459
+ st.markdown("Choose the chords you want to find similar songs with. The order of selection matters!")
460
+
461
+ # Get unique chords
462
+ unique_chords = get_unique_chords(df)
463
+
464
+ # Use session state to track chord selection order
465
+ if 'selected_chords_order' not in st.session_state:
466
+ st.session_state.selected_chords_order = []
467
+
468
+ # Create columns for chord selection
469
+ cols = st.columns(4)
470
+
471
+ # Track changes in checkboxes
472
+ for i, chord in enumerate(unique_chords):
473
+ with cols[i % 4]:
474
+ was_selected = chord in st.session_state.selected_chords_order
475
+ is_selected = st.checkbox(get_chord_name(chord), key=f"chord_{i}", value=was_selected)
476
+
477
+ if is_selected and chord not in st.session_state.selected_chords_order:
478
+ st.session_state.selected_chords_order.append(chord)
479
+ elif not is_selected and chord in st.session_state.selected_chords_order:
480
+ st.session_state.selected_chords_order.remove(chord)
481
+
482
+ # Add clear selection button
483
+ if st.button("Clear Selection"):
484
+ st.session_state.selected_chords_order = []
485
+ st.rerun()
486
+
487
+ # Add tempo filtering
488
+ st.markdown("### 🎡 Tempo Filter")
489
+ use_tempo = st.checkbox("Filter by Tempo", value=False)
490
+ tempo_filter = None
491
+
492
+ if use_tempo:
493
+ col1, col2 = st.columns(2)
494
+ with col1:
495
+ target_tempo = st.number_input("Target Tempo (BPM)", min_value=1, max_value=300, value=120)
496
+ with col2:
497
+ tempo_tolerance = st.slider("Tempo Tolerance", min_value=0.1, max_value=0.5, value=0.2,
498
+ format="Β±%.0f%%", help="How much the tempo can vary from target")
499
+ tempo_filter = get_tempo_range(target_tempo, tempo_tolerance)
500
+
501
+ st.markdown(f"""
502
+ Looking for songs with tempo between
503
+ **{tempo_filter[0]:.1f}** and **{tempo_filter[1]:.1f}** BPM
504
+ """)
505
+
506
+ # Add similarity threshold slider
507
+ if st.session_state.selected_chords_order:
508
+ st.markdown("### 🎯 Similarity Threshold")
509
+ similarity_threshold = st.slider(
510
+ "Minimum chord similarity percentage",
511
+ min_value=0.2,
512
+ max_value=1.0,
513
+ value=0.5,
514
+ step=0.1,
515
+ format="%.0f%%"
516
+ )
517
+ return st.session_state.selected_chords_order, similarity_threshold, tempo_filter
518
+
519
+ return [], 0.5, None
520
+
521
+ def plot_chord_distribution(df):
522
+ """Plot distribution of chord usage across the dataset"""
523
+ all_chords = []
524
+ for chords in df['Chords']:
525
+ if isinstance(chords, str):
526
+ chords = ast.literal_eval(chords)
527
+ all_chords.extend(chords)
528
+
529
+ chord_counts = pd.Series(all_chords).value_counts()
530
+
531
+ fig, ax = plt.subplots(figsize=(12, 6))
532
+ sns.barplot(x=chord_counts.index, y=chord_counts.values, ax=ax, palette='viridis')
533
+
534
+ plt.title('Chord Distribution in Dataset', pad=20)
535
+ plt.xlabel('Chord')
536
+ plt.ylabel('Frequency')
537
+ plt.xticks(rotation=45)
538
+
539
+ # Style the plot
540
+ ax.set_facecolor('#2D2D2D')
541
+ fig.patch.set_facecolor('#1E1E1E')
542
+ ax.spines['bottom'].set_color('#888888')
543
+ ax.spines['top'].set_color('#888888')
544
+ ax.spines['right'].set_color('#888888')
545
+ ax.spines['left'].set_color('#888888')
546
+ ax.tick_params(colors='#E0E0E0')
547
+ ax.xaxis.label.set_color('#E0E0E0')
548
+ ax.yaxis.label.set_color('#E0E0E0')
549
+ ax.title.set_color('#E0E0E0')
550
+
551
+ plt.tight_layout()
552
+ return fig
553
+
554
+ def plot_tempo_distribution(df):
555
+ """Plot distribution of tempos across the dataset"""
556
+ tempos = [get_average_tempo(tempo) for tempo in df['Tempo']]
557
+
558
+ fig, ax = plt.subplots(figsize=(12, 6))
559
+ sns.histplot(tempos, bins=30, ax=ax, color='#4A90E2')
560
+
561
+ plt.title('Tempo Distribution in Dataset', pad=20)
562
+ plt.xlabel('Tempo (BPM)')
563
+ plt.ylabel('Count')
564
+
565
+ # Style the plot
566
+ ax.set_facecolor('#2D2D2D')
567
+ fig.patch.set_facecolor('#1E1E1E')
568
+ ax.spines['bottom'].set_color('#888888')
569
+ ax.spines['top'].set_color('#888888')
570
+ ax.spines['right'].set_color('#888888')
571
+ ax.spines['left'].set_color('#888888')
572
+ ax.tick_params(colors='#E0E0E0')
573
+ ax.xaxis.label.set_color('#E0E0E0')
574
+ ax.yaxis.label.set_color('#E0E0E0')
575
+ ax.title.set_color('#E0E0E0')
576
+
577
+ plt.tight_layout()
578
+ return fig
579
+
580
+ def analyze_song_features(song_data):
581
+ """Analyze musical features of a song"""
582
+ tempo = get_average_tempo(song_data['Tempo'])
583
+ chord_progression = song_data['Chords']
584
+ if isinstance(chord_progression, str):
585
+ chord_progression = ast.literal_eval(chord_progression)
586
+
587
+ unique_chords = len(set(chord_progression))
588
+ progression_length = len(chord_progression)
589
+
590
+ return {
591
+ 'tempo': tempo,
592
+ 'unique_chords': unique_chords,
593
+ 'progression_length': progression_length,
594
+ 'chord_progression': chord_progression
595
+ }
596
+
597
+ def display_song_analysis(song_name, df):
598
+ """Display detailed analysis of a song"""
599
+ song_data = df[df['Song'] == song_name].iloc[0]
600
+ analysis = analyze_song_features(song_data)
601
+
602
+ col1, col2, col3 = st.columns(3)
603
+
604
+ with col1:
605
+ st.markdown("""
606
+ <div class="metric-card">
607
+ <div class="metric-value">{:.1f}</div>
608
+ <div class="metric-label">Tempo (BPM)</div>
609
+ </div>
610
+ """.format(analysis['tempo']), unsafe_allow_html=True)
611
+
612
+ with col2:
613
+ st.markdown("""
614
+ <div class="metric-card">
615
+ <div class="metric-value">{}</div>
616
+ <div class="metric-label">Unique Chords</div>
617
+ </div>
618
+ """.format(analysis['unique_chords']), unsafe_allow_html=True)
619
+
620
+ with col3:
621
+ st.markdown("""
622
+ <div class="metric-card">
623
+ <div class="metric-value">{}</div>
624
+ <div class="metric-label">Progression Length</div>
625
+ </div>
626
+ """.format(analysis['progression_length']), unsafe_allow_html=True)
627
+
628
+ st.markdown("### Chord Progression")
629
+ progression = ' β†’ '.join(get_chord_name(chord) for chord in analysis['chord_progression'])
630
+ st.markdown(f"""
631
+ <div class="card">
632
+ <p style="font-family: monospace; font-size: 1.1em;">{progression}</p>
633
+ </div>
634
+ """, unsafe_allow_html=True)
635
+
636
+ return analysis
637
+
638
+ def main():
639
+ show_sidebar()
640
+
641
+ # Main content
642
+ st.title("🎡 CHORD based Music Recommendation System")
643
+
644
+ # Create tabs
645
+ tabs = st.tabs(["🎧 Song Selection", "🎼 Chord Search", "πŸ” Recommendations", "πŸ“Š Analysis"])
646
+
647
+ # Load data and model with loading state
648
+ with st.spinner("Loading model and data..."):
649
+ model, scaler, df = load_model_and_data()
650
+ X_cnn, X_flat = preprocess_features(df, scaler)
651
+
652
+ # Song Selection Tab
653
+ with tabs[0]:
654
+ st.markdown("### πŸ” Search and Select a Song")
655
+
656
+ search_query = st.text_input("Search for a song", key="search")
657
+ if search_query:
658
+ filtered_songs = [song for song in df["Song"].tolist()
659
+ if search_query.lower() in song.lower()]
660
+ if not filtered_songs:
661
+ st.info("No songs found matching your search.")
662
+ else:
663
+ filtered_songs = df["Song"].tolist()
664
+
665
+ selected_song = st.selectbox("Select a song:", filtered_songs)
666
+
667
+ if selected_song:
668
+ st.markdown("### 🎡 Now Playing")
669
+ song_data = df[df["Song"] == selected_song].iloc[0]
670
+ tempo = get_average_tempo(song_data['Tempo'])
671
+ st.markdown(f"**Tempo:** {tempo:.1f} BPM")
672
+ display_song_info(selected_song, df)
673
+ display_audio_player(selected_song)
674
+
675
+ # Chord Search Tab
676
+ with tabs[1]:
677
+ st.markdown("### 🎼 Find Songs by Chord Pattern")
678
+ st.markdown("""
679
+ Select chords to find songs containing your pattern:
680
+ - Your chord sequence can appear anywhere in the song
681
+ - Pink highlights show where your sequence appears
682
+ - Songs are ranked by how many times your pattern appears
683
+ - Lower similarity threshold to find more matches
684
+ """)
685
+
686
+ # Lower default similarity threshold
687
+ selected_chords, similarity_threshold, tempo_filter = display_chord_selector(df)
688
+
689
+ if selected_chords:
690
+ st.markdown("### 🎸 Selected Pattern")
691
+ st.markdown(f"**Progression:** {get_chord_progression(selected_chords)}")
692
+
693
+ matching_songs = get_songs_by_chord_sequence(df, selected_chords, similarity_threshold, tempo_filter)
694
+
695
+ if matching_songs:
696
+ st.markdown(f"### 🎡 Found {len(matching_songs)} Songs Containing Your Pattern")
697
+ for song_info in matching_songs:
698
+ with st.container():
699
+ display_chord_progression(song_info)
700
+ display_audio_player(song_info['song'])
701
+ else:
702
+ st.info("No songs found with this chord pattern. Try lowering the similarity threshold (current: {:.0f}%) or selecting different chords.".format(similarity_threshold * 100))
703
+
704
+ # Recommendations Tab
705
+ with tabs[2]:
706
+ if selected_song:
707
+ st.markdown("### 🎧 Recommended Songs")
708
+ with st.spinner("Finding similar songs..."):
709
+ song_index = df[df["Song"] == selected_song].index[0]
710
+ input_song, recommendations = get_recommendations(model, X_cnn, X_flat, df, song_index)
711
+
712
+ for i, rec_song in enumerate(recommendations, 1):
713
+ with st.container():
714
+ st.markdown(f"#### Recommendation #{i}")
715
+ display_song_info(rec_song, df)
716
+ display_audio_player(rec_song)
717
+
718
+ # Analysis Tab
719
+ with tabs[3]:
720
+ st.header("Musical Analysis")
721
+
722
+ # Dataset Overview
723
+ st.subheader("Dataset Statistics")
724
+ col1, col2, col3 = st.columns(3)
725
+
726
+ with col1:
727
+ st.markdown("""
728
+ <div class="metric-card">
729
+ <div class="metric-value">{}</div>
730
+ <div class="metric-label">Total Songs</div>
731
+ </div>
732
+ """.format(len(df)), unsafe_allow_html=True)
733
+
734
+ with col2:
735
+ unique_chords = len(get_unique_chords(df))
736
+ st.markdown("""
737
+ <div class="metric-card">
738
+ <div class="metric-value">{}</div>
739
+ <div class="metric-label">Unique Chords</div>
740
+ </div>
741
+ """.format(unique_chords), unsafe_allow_html=True)
742
+
743
+ with col3:
744
+ avg_tempo = np.mean([get_average_tempo(tempo) for tempo in df['Tempo']])
745
+ st.markdown("""
746
+ <div class="metric-card">
747
+ <div class="metric-value">{:.1f}</div>
748
+ <div class="metric-label">Average Tempo (BPM)</div>
749
+ </div>
750
+ """.format(avg_tempo), unsafe_allow_html=True)
751
+
752
+ # Chord Distribution
753
+ st.subheader("Chord Distribution")
754
+ chord_dist_fig = plot_chord_distribution(df)
755
+ st.pyplot(chord_dist_fig)
756
+
757
+ # Tempo Distribution
758
+ st.subheader("Tempo Distribution")
759
+ tempo_dist_fig = plot_tempo_distribution(df)
760
+ st.pyplot(tempo_dist_fig)
761
+
762
+ # Individual Song Analysis
763
+ st.subheader("Song Analysis")
764
+ selected_song = st.selectbox("Select a song to analyze:", df["Song"].tolist())
765
+
766
+ if selected_song:
767
+ analysis = display_song_analysis(selected_song, df)
768
+
769
+ # Find similar songs based on features
770
+ song_index = df[df["Song"] == selected_song].index[0]
771
+ similarities = cosine_similarity([X_flat[song_index]], X_flat)[0]
772
+ top_indices = similarities.argsort()[-6:][::-1][1:]
773
+
774
+ st.subheader("Similar Songs")
775
+ for idx in top_indices:
776
+ similarity = similarities[idx]
777
+ similar_song = df.iloc[idx]["Song"]
778
+ st.markdown(f"""
779
+ <div class="card">
780
+ <h4>{similar_song}</h4>
781
+ <p>Similarity Score: {similarity:.2f}</p>
782
+ </div>
783
+ """, unsafe_allow_html=True)
784
+
785
+ if __name__ == "__main__":
786
+ main()