ohollo commited on
Commit
0124194
·
1 Parent(s): 8bc57ac

Add resource/tool for showing available chords

Browse files
Files changed (2) hide show
  1. app.py +43 -20
  2. cfg.py +1 -1
app.py CHANGED
@@ -1,4 +1,6 @@
1
  import logging
 
 
2
  import gradio as gr
3
  import faiss
4
  import joblib
@@ -8,6 +10,8 @@ from datasets import load_dataset
8
  import json
9
  import os
10
 
 
 
11
  from src.convert import get_embeddings_from_chord_sequences, get_embedding_from_filepaths
12
  from src.analysis import EmbeddingsAnalysis
13
  import cfg
@@ -41,6 +45,8 @@ def _parse_chord_input(chord_text):
41
  else:
42
  chords = chord_text.split()
43
 
 
 
44
  return chords
45
 
46
 
@@ -83,16 +89,36 @@ def _perform_analysis(embeddings, sequence_lengths, neighbour_embeddings=None):
83
  return score, neighbours_dict
84
 
85
 
86
- def analyze_chord_sequence_text(chord_text: str) -> tuple[float, list[dict]]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  """
88
  Analyze a chord sequence from text input. Analysis is in the form of
89
  an originality score and a list of similar songs from a non-exhaustive
90
  sample set of songs in the system data store.
91
 
92
  Args:
93
- chord_text: Chord sequence input as text (comma-separated or space-separated). IMPORTANT: Unless length is explicitly specified, for accurate results, provide the complete sequence of chords that would feature in a typical song, e.g. "C, Am, F, G, C, Am, F, G, C, Am, F, G, C, Am, F, G, ..." NOT just "C, Am, F, G". This could be 20-30 chords for a three minute song.
94
  Returns:
95
- tuple[float, list[dict]]: Originality score and list of dictionaries, each representing a similar song. You may infer that some items in the list are essentially the same song - if so don't repeat them to the user. Also some songs are more famous than others in the results - if you come across a famous one, then highlight it.
96
  """
97
  logging.info(f"Analyzing chord sequence: {chord_text}")
98
  try:
@@ -106,7 +132,7 @@ def analyze_chord_sequence_text(chord_text: str) -> tuple[float, list[dict]]:
106
  neighbour_embeddings = get_embeddings_from_chord_sequences([padded_chords])
107
  score, neighbours = _perform_analysis(embeddings, [len(chords)], neighbour_embeddings)
108
  return score, neighbours
109
- except Exception as e:
110
  logger.error(f"Error analyzing chord sequence: {e}")
111
  return None, None
112
 
@@ -157,15 +183,11 @@ def _format_music_analysis_for_ui(audio_file):
157
  file_info_text = f"**File analyzed:** {file_info}" if file_info else ""
158
  return file_info_text, scores_text, neighbours_text
159
 
160
- _preamble = """
161
- Enter chords separated by commas or spaces. Chords are expressed with a root note followed by a modifier without a space.
162
-
163
- Available roots: A, Ab, B, Bb, C, C#, D, Eb, E, F, F#, G\n
164
- Available modifiers: m, m7, maj7, m7b5, aug, dim, 6, 7, certain slash chords.
165
-
166
- For example, the following are all recognised chords for C:\n
167
- C, C/Bb, C/D, C/E, C/G, C6, C7, C7/E, Caug, Cdim, Cmaj7, Cm, Cm6, Cm7, Cm7b5, Cm7b5/Bb
168
- """
169
 
170
  # Create Gradio interface
171
  with gr.Blocks(title="Harmonic Analysis Tool", theme=gr.themes.Soft()) as app:
@@ -257,13 +279,14 @@ with gr.Blocks(title="Harmonic Analysis Tool", theme=gr.themes.Soft()) as app:
257
  outputs=[gr.Textbox(visible=False), gr.Number(visible=False), gr.JSON(visible=False)],
258
  api_name="analyze_music_file"
259
  )
260
-
261
- # Allow Enter key to trigger analysis
262
- # chord_input.submit(
263
- # fn=analyze_chord_sequence,
264
- # inputs=[chord_input],
265
- # outputs=[scores_output, neighbours_output]
266
- # )
 
267
 
268
  if __name__ == "__main__":
269
  app.launch(
 
1
  import logging
2
+ from typing import Optional
3
+
4
  import gradio as gr
5
  import faiss
6
  import joblib
 
10
  import json
11
  import os
12
 
13
+ from gradio_client.exceptions import AppError
14
+
15
  from src.convert import get_embeddings_from_chord_sequences, get_embedding_from_filepaths
16
  from src.analysis import EmbeddingsAnalysis
17
  import cfg
 
45
  else:
46
  chords = chord_text.split()
47
 
48
+ # Remove consecutive duplicates
49
+ chords = [c for i, c in enumerate(chords) if i == 0 or c != chords[i - 1]]
50
  return chords
51
 
52
 
 
89
  return score, neighbours_dict
90
 
91
 
92
+ def _get_supported_chord_formats() -> str:
93
+ return (
94
+ "Roots: A, Ab, B, Bb, C, C#, D, Eb, E, F, F#, G\n"
95
+ "Modifiers: m, m7, maj7, m7b5, aug, dim, 6, 7, and slash chords (e.g. C/Bb, C/G)\n"
96
+ "Example chords for C: C, C/Bb, C/D, C/E, C/G, C6, C7, C7/E, Caug, Cdim, Cmaj7, Cm, Cm6, Cm7, Cm7b5, Cm7b5/Bb"
97
+ )
98
+
99
+
100
+ @gr.mcp.resource("chords://supported-formats")
101
+ def supported_chord_formats() -> str:
102
+ """Supported chord roots and modifiers for use with the analyze_chord_sequence_text tool."""
103
+ return _get_supported_chord_formats()
104
+
105
+
106
+
107
+ def get_supported_chord_formats() -> str:
108
+ """Returns supported chord roots and modifiers for use with the analyze_chord_sequence_text tool."""
109
+ return _get_supported_chord_formats()
110
+
111
+
112
+ def analyze_chord_sequence_text(chord_text: str) -> tuple[Optional[float], Optional[list[dict]]]:
113
  """
114
  Analyze a chord sequence from text input. Analysis is in the form of
115
  an originality score and a list of similar songs from a non-exhaustive
116
  sample set of songs in the system data store.
117
 
118
  Args:
119
+ chord_text: Chord sequence as text (comma or space separated). Consult the chords://supported-formats resource (or the get_supported_chord_formats tool if resource access is unavailable) for supported chord names. IMPORTANT: Unless length is explicitly specified, for accurate results, provide the complete sequence of chords that would feature in a typical song, e.g. "C, Am, F, G, C, Am, F, G, ..." NOT just "C, Am, F, G". This could be 20-30 chords for a three minute song.
120
  Returns:
121
+ tuple[float, list[dict]]: Originality score and list of dictionaries, each representing a similar song. You may infer that some items in the list are essentially the same song - if so don't repeat them to the user. Also some songs are more famous than others in the results - if you come across a famous one, then highlight it. Returns None results if there was error, likely due to wrong chord format.
122
  """
123
  logging.info(f"Analyzing chord sequence: {chord_text}")
124
  try:
 
132
  neighbour_embeddings = get_embeddings_from_chord_sequences([padded_chords])
133
  score, neighbours = _perform_analysis(embeddings, [len(chords)], neighbour_embeddings)
134
  return score, neighbours
135
+ except AppError as e:
136
  logger.error(f"Error analyzing chord sequence: {e}")
137
  return None, None
138
 
 
183
  file_info_text = f"**File analyzed:** {file_info}" if file_info else ""
184
  return file_info_text, scores_text, neighbours_text
185
 
186
+ _preamble = (
187
+ "Enter chords separated by commas or spaces. "
188
+ "Chords are expressed with a root note followed by a modifier without a space.\n\n"
189
+ + _get_supported_chord_formats()
190
+ )
 
 
 
 
191
 
192
  # Create Gradio interface
193
  with gr.Blocks(title="Harmonic Analysis Tool", theme=gr.themes.Soft()) as app:
 
279
  outputs=[gr.Textbox(visible=False), gr.Number(visible=False), gr.JSON(visible=False)],
280
  api_name="analyze_music_file"
281
  )
282
+
283
+ gr.Textbox(visible=False).change(
284
+ fn=get_supported_chord_formats,
285
+ inputs=[],
286
+ outputs=[gr.Textbox(visible=False)],
287
+ api_name="get_supported_chord_formats"
288
+ )
289
+
290
 
291
  if __name__ == "__main__":
292
  app.launch(
cfg.py CHANGED
@@ -3,7 +3,7 @@ LABELS_LOCATION = './assets/all_labels.csv'
3
  LOOKUP_DS_NAME = 'ohollo/lmd_chords'
4
  CLOSE_THRESHOLD = 0.9
5
  SCALER_DICT_LOCATION = './assets/quantile_transformers.joblib'
6
- MIN_SEQUENCE_LENGTH_FOR_NEIGHBOURS = 24
7
  HOW_IT_WORKS_MD_LOCATION = './how_it_works.md'
8
  RADII = (0.8, 0.85, 0.9, 0.925, 0.95)
9
  # RADII = (0.9, 0.925, 0.95, 0.975, 0.99)
 
3
  LOOKUP_DS_NAME = 'ohollo/lmd_chords'
4
  CLOSE_THRESHOLD = 0.9
5
  SCALER_DICT_LOCATION = './assets/quantile_transformers.joblib'
6
+ MIN_SEQUENCE_LENGTH_FOR_NEIGHBOURS = 18
7
  HOW_IT_WORKS_MD_LOCATION = './how_it_works.md'
8
  RADII = (0.8, 0.85, 0.9, 0.925, 0.95)
9
  # RADII = (0.9, 0.925, 0.95, 0.975, 0.99)