Spaces:
Sleeping
Sleeping
| import copy | |
| import os | |
| from pathlib import Path | |
| from music21 import converter, musicxml, stream, note, chord, clef | |
| import verovio | |
| import shutil | |
| import subprocess | |
| from pydub import AudioSegment | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| class Format_Converter: | |
| def midi_to_musicxml_string_two_staves(midi_path, split_pitch=60): | |
| score = converter.parse(midi_path) | |
| merged = score.flatten() | |
| treble = stream.Part() | |
| bass = stream.Part() | |
| treble.insert(0, clef.TrebleClef()) | |
| bass.insert(0, clef.BassClef()) | |
| for el in merged.recurse().notesAndRests: | |
| true_offset = el.getOffsetInHierarchy(score) | |
| if isinstance(el, note.Rest): | |
| treble.insert(true_offset, copy.deepcopy(el)) | |
| bass.insert(true_offset, copy.deepcopy(el)) | |
| elif isinstance(el, note.Note): | |
| target = treble if el.pitch.midi >= split_pitch else bass | |
| target.insert(true_offset, copy.deepcopy(el)) | |
| elif isinstance(el, chord.Chord): | |
| treble_pitches = [p for p in el.pitches if p.midi >= split_pitch] | |
| bass_pitches = [p for p in el.pitches if p.midi < split_pitch] | |
| if treble_pitches: | |
| treble_chord = chord.Chord(treble_pitches) | |
| treble_chord.quarterLength = el.quarterLength | |
| treble_chord.volume = el.volume | |
| treble.insert(true_offset, treble_chord) | |
| if bass_pitches: | |
| bass_chord = chord.Chord(bass_pitches) | |
| bass_chord.quarterLength = el.quarterLength | |
| bass_chord.volume = el.volume | |
| bass.insert(true_offset, bass_chord) | |
| final_score = stream.Score() | |
| final_score.insert(0, treble) # order: top staff first | |
| final_score.insert(0, bass) | |
| exporter = musicxml.m21ToXml.GeneralObjectExporter(final_score) | |
| musicxml_str = exporter.parse().decode('utf-8') | |
| return musicxml_str | |
| def midi_to_musicxml_string(cls, midi_path): | |
| # Parse MIDI file into a music21 stream | |
| score = converter.parse(midi_path) | |
| # Export to MusicXML string | |
| exporter = musicxml.m21ToXml.GeneralObjectExporter(score) | |
| return exporter.parse().decode('utf-8') # parse() returns bytes | |
| def xml_to_svg_file(cls, tk, musicxml, output_file): | |
| try: | |
| mei_str = cls.xml_to_mei_string(tk, musicxml) | |
| # Load MEI and render SVG | |
| tk.loadData(mei_str) | |
| svg = tk.renderToSVGFile(output_file) | |
| return output_file | |
| except Exception as e: | |
| print("error in midi_to_svg_file") | |
| # return f"<p style='color:red'>Error: {e}</p>" | |
| return output_file | |
| def midi_to_svg_file(cls, tk, midi_path, output_file): | |
| musicxml = cls.midi_to_musicxml_string_two_staves(midi_path) | |
| return cls.xml_to_svg_file(tk, musicxml, output_file) | |
| def xml_to_mei_string(cls, tk, xmlstring): | |
| tk.loadData(xmlstring) | |
| return tk.getMEI() | |
| def midi_to_svg(cls, tk, midi_path): | |
| try: | |
| # Convert MIDI to MEI using music21 | |
| musicxmlobj = cls.midi_to_musicxml_string(midi_path) | |
| mei_str = cls.xml_to_mei_string(tk, musicxmlobj) | |
| # Load MEI and render SVG | |
| tk.loadData(mei_str) | |
| svg = tk.renderToSVG(1) | |
| return svg | |
| except Exception as e: | |
| return f"<p style='color:red'>Error: {e}</p>" | |
| def m21_to_xml(cls, m21): | |
| # Export to MusicXML string | |
| exporter = musicxml.m21ToXml.GeneralObjectExporter() | |
| return exporter.parse(m21).decode('utf-8') | |
| def m21_to_svg_file(cls, m21, output_file): | |
| # Export to MusicXML string | |
| exporter = musicxml.m21ToXml.GeneralObjectExporter() | |
| musicxmlobj = exporter.parse(m21).decode('utf-8') | |
| try: | |
| tk = verovio.toolkit() | |
| mei_str = cls.xml_to_mei_string(tk, musicxmlobj) | |
| # Load MEI and render SVG | |
| tk.loadData(mei_str) | |
| svg = tk.renderToSVGFile(output_file) | |
| return output_file | |
| except Exception as e: | |
| print("error in midi_to_svg_file") | |
| # return f"<p style='color:red'>Error: {e}</p>" | |
| return output_file | |
| def convert_midi_to_audio(self, midi_path, file_name): | |
| if not shutil.which("fluidsynth"): | |
| try: | |
| subprocess.run(["apt-get", "update"], check=True) | |
| subprocess.run(["apt-get", "install", "-y", "fluidsynth"], check=True) | |
| except Exception as e: | |
| return f"Error installing Fluidsynth: {str(e)}" | |
| wav_path = os.path.join(BASE_DIR, file_name + ".wav") | |
| mp3_path = os.path.join(BASE_DIR, file_name + ".mp3") | |
| soundfont_path = os.path.join(BASE_DIR, "soundfont.sf2") | |
| if not os.path.exists(soundfont_path): | |
| return "Error: SoundFont file not found. Please provide a valid .sf2 file." | |
| try: | |
| subprocess.run(["fluidsynth", "-ni", soundfont_path, midi_path, "-F", wav_path, "-r", "44100"], check=True) | |
| AudioSegment.converter = "ffmpeg" | |
| audio = AudioSegment.from_wav(wav_path) | |
| audio.export(mp3_path, format="mp3") | |
| return mp3_path | |
| except Exception as e: | |
| return f"Error converting MIDI to audio: {str(e)}" | |
| def musescore_midi_to_svg(midi_path, svg_path, mscore_path="/Applications/MuseScore 4.app/Contents/MacOS/mscore"): | |
| subprocess.run([mscore_path, midi_path, "-o", svg_path, "-T"], check=True) | |
| def musescore_midi_to_musicxml( | |
| midi_path: str, | |
| output_path: str = None, | |
| musescore_path: str = "/Applications/MuseScore 4.app/Contents/MacOS/mscore" | |
| ): | |
| midi_file = Path(midi_path) | |
| if not midi_file.exists(): | |
| raise FileNotFoundError(f"MIDI file not found: {midi_path}") | |
| if output_path is None: | |
| output_path = midi_file.with_suffix(".musicxml") | |
| else: | |
| output_path = Path(output_path) | |
| command = [musescore_path, str(midi_file), "-o", str(output_path)] | |
| try: | |
| result = subprocess.run(command, capture_output=True, text=True, check=True) | |
| print("✅ MuseScore conversion successful.") | |
| print(f"➡️ Output: {output_path}") | |
| except subprocess.CalledProcessError as e: | |
| print("❌ MuseScore conversion failed.") | |
| print("stderr:", e.stderr) | |
| print("stdout:", e.stdout) | |
| raise | |
| if __name__ == '__main__': | |
| svg_string = Format_Converter().midi_to_svg(verovio.toolkit(), 'output.mid') | |