Spaces:
Sleeping
Sleeping
Updated app.py and format_conversions.py
Browse files- app.py +2 -0
- format_conversions.py +100 -2
app.py
CHANGED
|
@@ -89,11 +89,13 @@ class HexachordApp:
|
|
| 89 |
for i, note in enumerate(chord):
|
| 90 |
if i == 0 and i_chord != 0:
|
| 91 |
track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=1))
|
|
|
|
| 92 |
else:
|
| 93 |
track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=0))
|
| 94 |
for i, note in enumerate(chord):
|
| 95 |
if i==0:
|
| 96 |
track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=delta_time - 1))
|
|
|
|
| 97 |
else:
|
| 98 |
track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=0))
|
| 99 |
midi_path = os.path.join(BASE_DIR, file_name)
|
|
|
|
| 89 |
for i, note in enumerate(chord):
|
| 90 |
if i == 0 and i_chord != 0:
|
| 91 |
track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=1))
|
| 92 |
+
# track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=0))
|
| 93 |
else:
|
| 94 |
track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=0))
|
| 95 |
for i, note in enumerate(chord):
|
| 96 |
if i==0:
|
| 97 |
track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=delta_time - 1))
|
| 98 |
+
# track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=delta_time))
|
| 99 |
else:
|
| 100 |
track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=0))
|
| 101 |
midi_path = os.path.join(BASE_DIR, file_name)
|
format_conversions.py
CHANGED
|
@@ -1,8 +1,105 @@
|
|
| 1 |
-
|
|
|
|
| 2 |
import verovio
|
| 3 |
|
|
|
|
| 4 |
class Format_Converter:
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
@classmethod
|
| 7 |
def midi_to_musicxml_string(cls, midi_path):
|
| 8 |
# Parse MIDI file into a music21 stream
|
|
@@ -30,11 +127,12 @@ class Format_Converter:
|
|
| 30 |
except Exception as e:
|
| 31 |
return f"<p style='color:red'>Error: {e}</p>"
|
| 32 |
|
|
|
|
| 33 |
@classmethod
|
| 34 |
def midi_to_svg_file(cls, tk, midi_path, output_file):
|
| 35 |
try:
|
| 36 |
# Convert MIDI to MEI using music21
|
| 37 |
-
musicxml = cls.
|
| 38 |
mei_str = cls.xml_to_mei_string(tk, musicxml)
|
| 39 |
# Load MEI and render SVG
|
| 40 |
tk.loadData(mei_str)
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
from music21 import converter, musicxml, stream, note, chord, clef
|
| 3 |
import verovio
|
| 4 |
|
| 5 |
+
|
| 6 |
class Format_Converter:
|
| 7 |
|
| 8 |
+
def midi_to_musicxml_string_two_staves(midi_path, split_pitch=60):
|
| 9 |
+
score = converter.parse(midi_path)
|
| 10 |
+
merged = score.flatten()
|
| 11 |
+
treble = stream.Part()
|
| 12 |
+
bass = stream.Part()
|
| 13 |
+
treble.insert(0, clef.TrebleClef())
|
| 14 |
+
bass.insert(0, clef.BassClef())
|
| 15 |
+
|
| 16 |
+
for el in merged.recurse().notesAndRests:
|
| 17 |
+
true_offset = el.getOffsetInHierarchy(score)
|
| 18 |
+
|
| 19 |
+
if isinstance(el, note.Rest):
|
| 20 |
+
treble.insert(true_offset, copy.deepcopy(el))
|
| 21 |
+
bass.insert(true_offset, copy.deepcopy(el))
|
| 22 |
+
|
| 23 |
+
elif isinstance(el, note.Note):
|
| 24 |
+
target = treble if el.pitch.midi >= split_pitch else bass
|
| 25 |
+
target.insert(true_offset, copy.deepcopy(el))
|
| 26 |
+
|
| 27 |
+
elif isinstance(el, chord.Chord):
|
| 28 |
+
treble_pitches = [p for p in el.pitches if p.midi >= split_pitch]
|
| 29 |
+
bass_pitches = [p for p in el.pitches if p.midi < split_pitch]
|
| 30 |
+
|
| 31 |
+
if treble_pitches:
|
| 32 |
+
treble_chord = chord.Chord(treble_pitches)
|
| 33 |
+
treble_chord.quarterLength = el.quarterLength
|
| 34 |
+
treble_chord.volume = el.volume
|
| 35 |
+
treble.insert(true_offset, treble_chord)
|
| 36 |
+
|
| 37 |
+
if bass_pitches:
|
| 38 |
+
bass_chord = chord.Chord(bass_pitches)
|
| 39 |
+
bass_chord.quarterLength = el.quarterLength
|
| 40 |
+
bass_chord.volume = el.volume
|
| 41 |
+
bass.insert(true_offset, bass_chord)
|
| 42 |
+
|
| 43 |
+
final_score = stream.Score()
|
| 44 |
+
final_score.insert(0, treble) # order: top staff first
|
| 45 |
+
final_score.insert(0, bass)
|
| 46 |
+
|
| 47 |
+
exporter = musicxml.m21ToXml.GeneralObjectExporter(final_score)
|
| 48 |
+
musicxml_str = exporter.parse().decode('utf-8')
|
| 49 |
+
return musicxml_str
|
| 50 |
+
|
| 51 |
+
@classmethod
|
| 52 |
+
def midi_to_musicxml_string_two_staves_old(cls, midi_path):
|
| 53 |
+
# Parse MIDI file into a music21 stream
|
| 54 |
+
score = converter.parse(midi_path)
|
| 55 |
+
score = score.flattenParts()
|
| 56 |
+
# Create a new Score with two staves: Treble and Bass
|
| 57 |
+
treble = stream.Part()
|
| 58 |
+
bass = stream.Part()
|
| 59 |
+
treble.id = 'Treble'
|
| 60 |
+
bass.id = 'Bass'
|
| 61 |
+
# Assign clefs (optional but recommended)
|
| 62 |
+
from music21 import clef
|
| 63 |
+
treble.insert(0, clef.TrebleClef())
|
| 64 |
+
bass.insert(0, clef.BassClef())
|
| 65 |
+
split_pitch = 60
|
| 66 |
+
# Split notes by pitch (simple heuristic)
|
| 67 |
+
treble_voice = stream.Voice()
|
| 68 |
+
bass_voice = stream.Voice()
|
| 69 |
+
for el in score.recurse().notesAndRests:
|
| 70 |
+
true_offset = el.getOffsetInHierarchy(score)
|
| 71 |
+
if isinstance(el, note.Rest):
|
| 72 |
+
# Send same rest to both staves (or adjust as needed)
|
| 73 |
+
treble.insert(true_offset, el)
|
| 74 |
+
bass.insert(true_offset, el)
|
| 75 |
+
|
| 76 |
+
elif isinstance(el, note.Note):
|
| 77 |
+
target = treble if el.pitch.midi >= split_pitch else bass
|
| 78 |
+
target.insert(true_offset, el)
|
| 79 |
+
|
| 80 |
+
elif isinstance(el, chord.Chord):
|
| 81 |
+
# Split the chord into individual notes
|
| 82 |
+
for p in el.pitches:
|
| 83 |
+
n = note.Note(p)
|
| 84 |
+
n.quarterLength = el.quarterLength
|
| 85 |
+
n.volume = el.volume
|
| 86 |
+
if p.midi >= split_pitch:
|
| 87 |
+
treble_voice.insert(true_offset, n)
|
| 88 |
+
else:
|
| 89 |
+
bass_voice.insert(true_offset, n)
|
| 90 |
+
# Combine the two parts into a score
|
| 91 |
+
treble.append(treble_voice)
|
| 92 |
+
bass.append(bass_voice)
|
| 93 |
+
new_score = stream.Score()
|
| 94 |
+
new_score.insert(0, treble)
|
| 95 |
+
new_score.insert(0, bass)
|
| 96 |
+
# staff_group = StaffGroup([bass, treble], symbol='brace', barTogether=True)
|
| 97 |
+
# new_score.insert(0, staff_group)
|
| 98 |
+
# Export to MusicXML string
|
| 99 |
+
exporter = musicxml.m21ToXml.GeneralObjectExporter(new_score)
|
| 100 |
+
musicxml_str = exporter.parse().decode('utf-8') # parse() returns bytes
|
| 101 |
+
return musicxml_str
|
| 102 |
+
|
| 103 |
@classmethod
|
| 104 |
def midi_to_musicxml_string(cls, midi_path):
|
| 105 |
# Parse MIDI file into a music21 stream
|
|
|
|
| 127 |
except Exception as e:
|
| 128 |
return f"<p style='color:red'>Error: {e}</p>"
|
| 129 |
|
| 130 |
+
|
| 131 |
@classmethod
|
| 132 |
def midi_to_svg_file(cls, tk, midi_path, output_file):
|
| 133 |
try:
|
| 134 |
# Convert MIDI to MEI using music21
|
| 135 |
+
musicxml = cls.midi_to_musicxml_string_two_staves(midi_path)
|
| 136 |
mei_str = cls.xml_to_mei_string(tk, musicxml)
|
| 137 |
# Load MEI and render SVG
|
| 138 |
tk.loadData(mei_str)
|