Spaces:
Running
Running
File size: 8,498 Bytes
d4bbd3b 47de94c f132626 ad5f41f b50a375 d4bbd3b c5184df 47de94c c5184df 87093fc c5184df 12a0c2c 0124194 87093fc 0124194 f132626 b50a375 f132626 12a0c2c b50a375 12a0c2c b50a375 12a0c2c f132626 b50a375 f132626 12a0c2c b50a375 12a0c2c b50a375 d4bbd3b 0124194 47de94c f132626 d4bbd3b 1581f07 d4bbd3b a90133c f132626 1581f07 d4bbd3b 47de94c d4bbd3b f132626 d4bbd3b 87093fc d4bbd3b c5184df 47de94c c5184df 87093fc d4bbd3b 87093fc d4bbd3b 1581f07 d4bbd3b b50a375 d4bbd3b b50a375 d4bbd3b f132626 d4bbd3b c5184df 87093fc c5184df d4bbd3b 12a0c2c f132626 12a0c2c d4bbd3b 12a0c2c f132626 12a0c2c d4bbd3b 12a0c2c f132626 12a0c2c d4bbd3b 12a0c2c f132626 12a0c2c 0124194 d4bbd3b d907a5f 87093fc d4bbd3b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | import gradio as gr
import cfg
from setup import analyze_chord_sequence_text, analyze_music_file
# Gradio 5.49 bug: monitoring/summary serialises function stats with integer keys,
# which orjson rejects. Patch _render to allow non-string keys.
import orjson
import gradio.routes as _gr_routes
@staticmethod
def _patched_render(content):
return orjson.dumps(content, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY)
_gr_routes.ORJSONResponse._render = _patched_render
# Load how it works content
with open(cfg.HOW_IT_WORKS_MD_LOCATION, 'r') as f:
how_it_works_content = f.read()
with open(cfg.HOW_IT_WORKS_SVG_LOCATION, 'r') as f:
how_it_works_svg = f.read()
def _get_supported_chord_formats() -> str:
return (
"Roots: A, Ab, B, Bb, C, C#, D, Eb, E, F, F#, G \n"
"Modifiers: m, m7, maj7, m7b5, aug, dim, 6, 7, and slash chords (e.g. C/Bb, C/G) \n"
"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"
)
@gr.mcp.resource("chords://supported-formats")
def supported_chord_formats() -> str:
"""Supported chord roots and modifiers for use with the analyze_chord_sequence_text tool."""
return _get_supported_chord_formats()
def get_supported_chord_formats() -> str:
"""Returns supported chord roots and modifiers for use with the analyze_chord_sequence_text tool."""
return _get_supported_chord_formats()
def _format_chord_analysis_for_ui(chord_text, limit):
yield "⏳ Analysing...", ""
score, neighbours = analyze_chord_sequence_text(chord_text, limit=int(limit))
if score is None:
yield "Please enter some chords!", ""
return
scores_text = f"**Originality Score:** {score:.4f}"
neighbours_text = "**Similar Songs:**\n"
if neighbours:
for i, neighbor in enumerate(neighbours, 1):
title = neighbor['title']
artist = neighbor['artist']
similarity = neighbor['similarity']
neighbours_text += f"{i}. {title} by {artist} (similarity: {similarity:.3f})\n"
else:
neighbours_text += "No similar songs found."
yield scores_text, neighbours_text
def _format_music_analysis_for_ui(audio_file, limit):
yield "", "⏳ Analysing...", ""
file_info, score, neighbours = analyze_music_file(audio_file, limit=int(limit))
if score is None:
yield "Please upload a music file!", "", ""
return
scores_text = f"**Originality Score:** {score:.4f}"
neighbours_text = "**Similar Songs:**\n"
if neighbours:
for i, neighbor in enumerate(neighbours, 1):
title = neighbor['title']
artist = neighbor['artist']
similarity = neighbor['similarity']
neighbours_text += f"{i}. {title} by {artist} (similarity: {similarity:.3f})\n"
else:
neighbours_text += "No similar songs found."
file_info_text = f"**File analyzed:** {file_info}" if file_info else ""
yield file_info_text, scores_text, neighbours_text
_preamble = (
"Enter chords separated by commas or spaces. "
"Chords are expressed with a root note followed by a modifier without a space.\n\n"
+ _get_supported_chord_formats()
)
def _similar_songs_limit_input():
return gr.Number(label="Max similar songs", value=5, minimum=1, maximum=cfg.MAX_SIMILAR_SONGS, step=1, scale=0)
# Create Gradio interface
with gr.Blocks(title="Harmonic Analysis Tool", theme=gr.themes.Soft(),
css=".gradio-container { max-width: none !important; padding-left: 2rem !important; padding-right: 2rem !important; }") as app:
gr.Markdown("# 🎵 Harmonic Analysis Tool")
gr.Markdown("Inspect chord progressions for originality and find similar songs in a dataset (or songs containing similar segments). Input chords as text or upload a music file.")
gr.Markdown("Originality score ranges from 0 (many similar songs harmonically) to 1 (no similar songs harmonically). "
"Conversely, for each similar song, a similarity score is given where 1 means most similar. "
"Score and similar songs are formed using data from the [lmd_chord dataset](https://huggingface.co/datasets/ohollo/lmd_chords).")
with gr.Tabs():
# Tab 1: Text Input
with gr.TabItem("Text Input"):
gr.Markdown("### Enter Chord Sequence")
gr.Markdown(_preamble)
with gr.Row():
with gr.Column(scale=2):
chord_input = gr.Textbox(
label="Chord Sequence",
placeholder="C, Am, F, G",
lines=2
)
similar_songs_limit = _similar_songs_limit_input()
analyze_btn = gr.Button("Analyze Chords", variant="primary")
with gr.Column(scale=3):
scores_output = gr.Markdown(label="Analysis Results")
neighbours_output = gr.Markdown(label="Similar Songs")
# Example chord progressions
gr.Markdown("### Example Chord Progressions")
gr.Examples(
examples=[
["C, Am, F, G"],
["D, A, Bm, G"],
["F, C, Dm, Bb"],
["C, C/Bb, C/D, C/E, C/G, C6, C7, C7/E, Caug, Cdim, Cmaj7, Cm, Cm6, Cm7, Cm7b5, Cm7b5/Bb"],
["A7, D7, A7, E7, D7, A7"],
["Am, F, C, G, Am, F, C, G, C, G, Am, F, C, G, Am, F, "
"Am, F, C, G, Am, F, C, G, C, G, Am, F, C, G, Am, F"],
],
inputs=[chord_input],
)
# Tab 2: File Upload
with gr.TabItem("File Upload"):
gr.Markdown("### Upload Music File")
gr.Markdown("Upload an audio file to extract and inspect its chord progression.")
gr.Markdown("This feature is very experimental and works best with MIDI files but other audio formats including .mp3, .wav and .ogg are also accepted.")
with gr.Row():
with gr.Column(scale=1):
audio_input = gr.File(
label="Upload Audio File",
file_types=["audio", ".mid", ".midi"],
)
audio_similar_songs_limit = _similar_songs_limit_input()
upload_btn = gr.Button("Analyze Audio", variant="primary")
with gr.Column(scale=2):
file_display = gr.Markdown(label="File Info")
audio_scores_output = gr.Markdown(label="Analysis Results")
audio_neighbours_output = gr.Markdown(label="Similar Songs")
# Tab 3: How It Works
with gr.TabItem("How It Works"):
gr.HTML(f'<div style="text-align:center;margin:2em 0">{how_it_works_svg}</div>')
gr.Markdown(how_it_works_content)
# Event handlers
analyze_btn.click(
fn=_format_chord_analysis_for_ui,
inputs=[chord_input, similar_songs_limit],
outputs=[scores_output, neighbours_output],
api_name=False
)
upload_btn.click(
fn=_format_music_analysis_for_ui,
inputs=[audio_input, audio_similar_songs_limit],
outputs=[file_display, audio_scores_output, audio_neighbours_output],
api_name=False
)
# Register API-only endpoints using dummy hidden components
gr.Textbox(visible=False).change(
fn=analyze_chord_sequence_text,
inputs=[gr.Textbox(visible=False), gr.Number(visible=False, value=10)],
outputs=[gr.Number(visible=False), gr.JSON(visible=False)],
api_name="analyze_chord_sequence_text"
)
gr.Audio(visible=False, type="filepath").change(
fn=analyze_music_file,
inputs=[gr.Audio(visible=False, type="filepath"), gr.Number(visible=False, value=10)],
outputs=[gr.Textbox(visible=False), gr.Number(visible=False), gr.JSON(visible=False)],
api_name="analyze_music_file"
)
gr.Textbox(visible=False).change(
fn=get_supported_chord_formats,
inputs=[],
outputs=[gr.Textbox(visible=False)],
api_name="get_supported_chord_formats"
)
if __name__ == "__main__":
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
mcp_server=True,
) |