Spaces:
Sleeping
Sleeping
| 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() | |