Spaces:
Running
Running
Add resource/tool for showing available chords
Browse files
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 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.
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 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 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 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 =
|
| 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)
|