Spaces:
Sleeping
Sleeping
File size: 6,804 Bytes
d877082 fc99099 c2f178d d877082 a3cb78c fc99099 a3cb78c d877082 a3cb78c 6e4f2b4 a3cb78c c2f178d a3cb78c 6e4f2b4 a3cb78c c2f178d 52d7d5e 6e4f2b4 fc99099 c2f178d fc99099 a3cb78c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | 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"<p style='color:red'>Error: {e}</p>"
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"<p style='color:red'>Error: {e}</p>"
@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"<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')
|