import ast import gradio as gr import numpy as np from mido import Message, MidiFile, MidiTrack import os from music21 import converter, note, stream, chord, tempo, meter import hexachords from format_conversions import Format_Converter import verovio import subprocess from legacy.essai_mido_to_xml import midi_path BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def get_verovio_resource_path(): for path in verovio.__path__: candidate = os.path.join(path, "data") if os.path.exists(candidate): return candidate return None # Initialize the Verovio toolkit with rendering options tk = verovio.toolkit() resource_path = get_verovio_resource_path() print(f"resource path: {os.listdir(resource_path)}") print("[Debug] Using resource path:", resource_path) if resource_path: try: tk.setResourcePath(resource_path) except Exception as e: print("[Error] Failed to set resourcePath:", e) tk.setOptions({ "adjustPageWidth": True, "header": 'none', # This disables the rendering of the title "scale": 70, "adjustPageHeight": True, "landscape": False, }) print(tk.getOptions()) class HexachordApp: def __init__(self): self._hexachord = hexachords.Hexachord() self.ui = None self.on_huggingface = "HUGGINGFACE_SPACE" in os.environ def is_fsynth_installed(self): try: subprocess.run(["fluidsynth", "--version"], check=True) print('fluidsynth is installed') return True except Exception: print('fluidsynth is NOT installed') return False def generate_chords(self, note_names, itvl): interval_21 = 'P5' if itvl == 'fourth': interval_21 = 'P4' return self._hexachord.generate_base_sequence(note_names, intrvl=interval_21) def generate_realizations(self): # returns triples of midipath, score image, audio player reals = self._hexachord.generate_3_chords_realizations(self._hexachord._base_sequence) all_paths = [] fm = Format_Converter() for i, real in enumerate(reals): midi_path = f"real{i}.mid" all_paths.append(self.create_midi_file(real, midi_path)) all_paths.append(fm.midi_to_svg_file(tk, midi_path, f"real{i}.svg")) all_paths.append(fm.convert_midi_to_audio(midi_path, f"real{i}")) return tuple(all_paths) def create_midi_file(self, chords, file_name, duration_in_quarter_lengths=4.0, tempo_bpm=120, time_signature="4/4"): midi_path = os.path.join(BASE_DIR, file_name) self._hexachord.save_chords_to_midi_file(chords, midi_path) return file_name def generate_svg(self, midi_file, output_file_name): return Format_Converter().midi_to_svg_file(tk, midi_file, output_file_name) def launch_score_editor(self, midi_path): try: score = converter.parse(midi_path) score.show('musicxml') return "Opened MIDI file in the default score editor!" except Exception as e: return f"Error opening score editor: {str(e)}" def build_octave_dependent_notes_from_string(self, note_string): start_octave = 3 notes = [] previous_note = None for nn in note_string.split(): n = note.Note(nn) n.octave = start_octave if previous_note is not None and n.pitch.midi < previous_note.pitch.midi: n.octave = n.octave + 1 start_octave += 1 notes.append(n) previous_note = n return notes def process_hexachord(self, hexachord_str, itvl): try: notes = self.build_octave_dependent_notes_from_string(hexachord_str) if len(notes) != 6 or len(set(notes)) != 6: return "Please enter exactly 6 unique notes." except ValueError: return "Invalid input. Enter 6 notes separated by spaces." fm = Format_Converter() chords = self.generate_chords(notes, itvl) midi_path = self.create_midi_file(chords, "base_chords.mid") score_path = fm.midi_to_svg_file(tk, midi_path, "score_base_chords.svg") audio_path = fm.convert_midi_to_audio(midi_path, "base_chords") return (midi_path, score_path, audio_path) + self.generate_realizations() def generate_movements(self): # take 2 realizations of the same root everyone = () for index_of_chord in range(6): chords = [real[index_of_chord] for real in self._hexachord._realizations] fm = Format_Converter() midi_path = self.create_midi_file(chords, f"movement{index_of_chord}.mid") score_path = fm.midi_to_svg_file(tk, midi_path, f"movement{index_of_chord}.svg") audio_path = fm.convert_midi_to_audio(midi_path, f"movement{index_of_chord}") everyone = everyone + (midi_path, score_path, audio_path) return everyone def render(self): with gr.Blocks() as ui: gr.Markdown("# Hexachord-based Chord Generator") with gr.Tabs(): with gr.TabItem("Hexachord Generator"): with gr.Row(): hexachord_selector = gr.Dropdown(label="Select Known Hexachord", choices=self.get_known_hexachords_choice(), value=None, interactive=True) hexachord_input = gr.Textbox( label="Enter 6 pitchclasses, separated by spaces", value="C D E G A B", interactive = True ) interval_switch = gr.Radio( choices=["fourth", "fifth"], label="Select Interval", value="fifth" ) generate_button = gr.Button("Generate Chords") with gr.Row(): gr.Markdown(f"#### base chords") midi_output = gr.File(label="Download MIDI File", scale=1) score_output = gr.Image(label="Score Visualization", scale=3) audio_output = gr.Audio(label="Play Generated Chords", value=None, interactive=False, scale=3) realization_outputs = [midi_output, score_output, audio_output] for i in range(3): # Three alternative realizations with gr.Row(): gr.Markdown(f"#### Realization {i + 1}") midi_output = gr.File(label="Download MIDI File", scale=1) piano_roll = gr.Image(label=f"Piano Roll {i + 1}", scale=3) audio_player = gr.Audio(label=f"Play Chords {i + 1}", interactive=False, scale=3) realization_outputs += (midi_output, piano_roll, audio_player) hexachord_selector.change( fn=self.get_selected_hexachord, inputs=[hexachord_selector], outputs=[hexachord_input] ) generate_button.click( fn=self.process_hexachord, inputs=[hexachord_input, interval_switch], # outputs=[midi_output, piano_roll_output, audio_output] outputs = realization_outputs ) # Pressing Enter in the textbox also triggers processing hexachord_input.submit( fn=self.process_hexachord, inputs=[hexachord_input, interval_switch], outputs=realization_outputs ) with gr.TabItem("Movements"): gr.Markdown("Movements") with gr.Row(): generate_mvmt_button = gr.Button("Generate Movements") realization_outputs = [] for i in range(6): # Three alternative realizations with gr.Row(): gr.Markdown(f"#### Movement {i + 1}") midi_output = gr.File(label="Download MIDI File", scale=1) piano_roll = gr.Image(label=f"Piano Roll {i + 1}", scale=3) audio_player = gr.Audio(label=f"Play Chords {i + 1}", interactive=False, scale=3) realization_outputs += (midi_output, piano_roll, audio_player) generate_mvmt_button.click( fn=self.generate_movements, inputs=[], outputs = realization_outputs ) with gr.TabItem("Settings"): gr.Markdown("### Configuration Options") setting_1 = gr.Checkbox(label="Enable Advanced Mode") setting_2 = gr.Slider(0, 100, label="Complexity Level") self.ui = ui def get_known_hexachords_choice(self): return self._hexachord.known_hexachords def get_selected_hexachord(self, x): # lambda x: {"Hexachord 1": "C3 D3 E3 G3 A3 B3", "Hexachord 2": "D3 E3 F3 A3 B3 C4", # "Hexachord 3": "E3 G3 A3 C4 D4 F4"}.get(x, "") item = x[x.index('['):x.index(']')+1] int_array = np.array(ast.literal_eval(item)) hexa_string = '' start_note = note.Note('C3') for i in int_array: add_note = start_note.transpose(int(i)) hexa_string = hexa_string + ' ' + add_note.nameWithOctave return hexa_string def launch_app(): hex = HexachordApp() hex.is_fsynth_installed() hex.render() if hex.on_huggingface: hex.ui.launch(server_name="0.0.0.0", server_port=7860, share=True) else: hex.ui.launch(server_name="0.0.0.0", server_port=7860) launch_app()