pachet's picture
Update app.py, format_conversions.py, and hexachords.py
6e4f2b4
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()