Spaces:
Running
Running
| 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 | |
| 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" | |
| ) | |
| 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, | |
| ) |