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 @classmethod 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 @classmethod 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"
Error: {e}
" return output_file @classmethod 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) @classmethod def xml_to_mei_string(cls, tk, xmlstring): tk.loadData(xmlstring) return tk.getMEI() @classmethod 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"Error: {e}
" @classmethod def m21_to_xml(cls, m21): # Export to MusicXML string exporter = musicxml.m21ToXml.GeneralObjectExporter() return exporter.parse(m21).decode('utf-8') @classmethod 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"Error: {e}
" 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')