Spaces:
Sleeping
Sleeping
File size: 9,134 Bytes
d020355 0a882c0 99a4b1a 6fcf15f 99a4b1a 45d6604 99a4b1a ead7d25 99a4b1a f9418ac 99a4b1a f9418ac 99a4b1a f9418ac 99a4b1a ff7a47f 99a4b1a 0cc4ffc f9418ac 0cc4ffc f9418ac 45d6604 f9418ac 0cc4ffc 45d6604 0cc4ffc 45d6604 0cc4ffc f9418ac 99a4b1a 45d6604 99a4b1a 45d6604 99a4b1a 45d6604 99a4b1a 45d6604 99a4b1a 45d6604 99a4b1a 6fcf15f 0a882c0 d020355 0a882c0 d020355 0a882c0 99a4b1a 45d6604 99a4b1a ff7a47f 99a4b1a | 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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | import os
import subprocess
from music21 import note, stream, interval, meter, chord, converter, metadata
from ortools.sat.python import cp_model
from verovio import verovio
class Hexachord:
known_hexachords = [
"6-1 [0,1,2,3,4,5] Chromatic hexachord",
"6-7 [0,1,2,6,7,8] Two-semitone tritone scale",
"6-Z17A [0,1,2,4,7,8] All-trichord hexachord",
"6-20 [0,1,4,5,8,9] Augmented scale, Ode-to-Napoleon hexachord",
"6-Z24A [0,1,3,4,6,8] Minor major eleventh chord",
"6-Z24B [0,2,4,5,7,8] Half-diminished eleventh chord",
"6-Z25A [0,1,3,5,6,8] Major eleventh chord",
"6-Z26 [0,1,3,5,7,8] Major ninth sharp eleventh chord",
"6-27B [0,2,3,5,6,9] Diminished eleventh chord",
"6-Z28 [0,1,3,5,6,9] Augmented major eleventh chord",
"6-Z29 [0,2,3,6,7,9] Bridge chord",
"6-30B [0,2,3,6,8,9] Petrushka chord, tritone scale",
"6-32 [0,2,4,5,7,9] Diatonic hexachord, minor eleventh chord",
"6-33B [0,2,4,6,7,9] Dominant eleventh chord",
"6-34A [0,1,3,5,7,9] Mystic chord",
"6-34B [0,2,4,6,8,9] Augmented eleventh chord, dominant sharp eleventh chord, Prélude chord",
"6-35 [0,2,4,6,8,T] Whole tone scale",
"6-Z44A [0,1,2,5,6,9] Schoenberg hexachord",
"6-Z46A [0,1,2,4,6,9] Scale of harmonics",
"6-Z47B [0,2,3,4,7,9] Blues scale"]
def generate_chord_sequence_from_midi_pitches(self, list_of_mp, intrvl="P5"):
return self.generate_chord_sequence([note.Note(mp).nameWithOctave for mp in list_of_mp], intrvl=intrvl)
def generate_chord_sequence(self, six_notes, intrvl="P5"):
fifth = interval.Interval(intrvl) # Perfect fifth
all_pc = [n.pitch.pitchClass for n in six_notes]
all_chords = []
for n in six_notes:
ch = chord.Chord([n])
current_note = n
while len(ch) < 6:
current_note = fifth.transposeNote(current_note)
if current_note.pitch.pitchClass in all_pc and current_note not in ch:
while interval.Interval(noteStart=ch[-1], noteEnd=current_note).semitones > (12 + 7):
current_note = current_note.transpose(-12)
ch.add(current_note)
all_chords.append(ch)
return all_chords
def generate_3_chords_realizations(self, chord_seq):
# lower each of the top to notes by 2 ocatves
res1 = []
res2 = []
res3 = []
for ch in chord_seq:
new_ch = chord.Chord()
for i, n in enumerate(ch.notes):
if i == 4 or i == 5:
new_ch.add(n.transpose(-24))
else:
new_ch.add(n)
res1.append(new_ch)
for ch in chord_seq:
new_ch = chord.Chord()
for i, n in enumerate(ch.notes):
if i == 4 or i == 5:
new_ch.add(n.transpose(-24))
elif i == 3:
new_ch.add(n.transpose(-12))
else:
new_ch.add(n)
res2.append(new_ch)
for ch in chord_seq:
new_ch = chord.Chord()
for i, n in enumerate(ch.notes):
if i == 4 or i == 5:
new_ch.add(n.transpose(-24))
elif i == 3 or i == 2:
new_ch.add(n.transpose(-12))
else:
new_ch.add(n)
res3.append(new_ch)
return res1, res2, res3
def chords_to_stream(self, chords, file_name):
s = stream.Stream()
s.append(meter.TimeSignature("4/4"))
for c in chords:
ch = chord.Chord(c)
ch.duration.quarterLength = 4
s.append(ch)
# s.show('midi')
s.write('midi', file_name)
return s
def alternate_chords(self, s1, s2):
"""Create a new stream alternating between chords from s1 and s2"""
new_stream = stream.Stream()
# Get chords from both streams
chords1 = list(s1.getElementsByClass(chord.Chord))
chords2 = list(s2.getElementsByClass(chord.Chord))
# Interleave chords from s1 and s2
for c1, c2 in zip(chords1, chords2):
new_stream.append(c1)
new_stream.append(c2)
return new_stream
def optimize_voice_leading(self, chord_sequence):
model = cp_model.CpModel()
octave_variables = {}
movement_vars = []
# Define variables and domains (allowing octave shifts)
for i, ch in enumerate(chord_sequence):
for n in ch.notes:
var_name = f"chord_{i}_note_{n.nameWithOctave}"
octave_variables[var_name] = model.NewIntVar(n.octave - 1, n.octave + 1,
var_name) # Allow octave shifts
spread_vars = []
# Add constraints to minimize movement between chords
for i in range(len(chord_sequence) - 1):
max_octave = model.NewIntVar(0, 10, "max_pitch" + str(i))
min_octave = model.NewIntVar(0, 10, "min_pitch" + str(i))
for n in chord_sequence[i]:
v = octave_variables[f"chord_{i}_note_{n.nameWithOctave}"]
# model.Add(max_pitch >= v) # max_pitch must be at least as high as any note
# model.Add(min_pitch <= v) # min_pitch must
spread_var = max_octave - min_octave
spread_vars.append(spread_var)
for i in range(len(chord_sequence) - 1):
for n1, n2 in zip(chord_sequence[i].notes, chord_sequence[i + 1].notes):
var1 = octave_variables[f"chord_{i}_note_{n1.nameWithOctave}"]
var2 = octave_variables[f"chord_{i + 1}_note_{n2.nameWithOctave}"]
# Define movement variable
movement_var = model.NewIntVar(0, 36, f"movement_{i}_{n1.name}")
model.AddAbsEquality(movement_var, var2 - var1)
# Track movement variable in objective function
movement_vars.append(movement_var)
# Define objective: minimize sum of all movement values
model.Minimize(sum(spread_vars))
# obj_var = sum(movement_vars)
# model.Minimize(obj_var)
# Solve
solver = cp_model.CpSolver()
solver.Solve(model)
# print(solver.Value(obj_var))
# for v in variables:
# print(v)
# print(variables[v].Proto().domain)
# print(solver.Value(variables[v]))
# Apply changes to music21 chord sequence
optimized_chords = []
for i, ch in enumerate(chord_sequence):
new_chord = chord.Chord(
[note.Note(f"{n.name}{solver.Value(octave_variables[f'chord_{i}_note_{n.nameWithOctave}'])}")
for n in ch.notes])
optimized_chords.append(new_chord)
return optimized_chords
def midi_to_svg_file(self, midi_file, output_file):
score = converter.parse(midi_file)
musicxml_data = score.write('musicxml') # Get MusicXML as a string
# Step 2: Load MusicXML into Verovio
tk = verovio.toolkit()
tk.loadData(musicxml_data.encode()) # Convert to bytes and load into Verovio
tk.renderToSVGFile(output_file, 1)
def midi_to_svg(self, midi_file, svg_output):
"""Convert MIDI to SVG using Verovio's command-line tool."""
score = converter.parse(midi_file)
score.metadata = metadata.Metadata()
score.metadata.title = ''
musicxml_path = "temp.musicxml"
score.write('musicxml', fp=musicxml_path)
# Run Verovio via command line (since Python API fails)
verovio_executable = "verovio/build/verovio" # Ensure correct path
if not os.path.exists(verovio_executable):
return "Error: Verovio binary not found!"
# Run Verovio with the full path
command = f"{verovio_executable} {musicxml_path} -o {svg_output} --smufl-text-font embedded --scale 50 --page-width 1000 --page-height 500 --footer none"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("Verovio Error:", result.stderr)
return f"Error running Verovio: {result.stderr}"
return svg_output
if __name__ == '__main__':
hexa = Hexachord()
note_names = ["C3", "Eb3", "E3", "F#3", "G3", "Bb3"]
notes = [note.Note(n) for n in note_names]
cs1 = hexa.generate_chord_sequence(notes, intrvl="P4")
# cs1 = generate_chord_sequence(["E3", "G3", "Ab3", "B3", "C4", "Eb4"])
# cs2 = generate_chord_sequence(["C3", "F3", "F#3", "A3", "B4", "E4"])
# alternation = alternate_chords(cs1, cs2)
# alternation.write('midi',"alternation.mid")
hexa.chords_to_stream(cs1, 'temp.mid').show('text')
# optimized = optimize_voice_leading([c1, c2, c3])
optimized = hexa.optimize_voice_leading(cs1)
stream1 = stream.Stream(optimized)
stream1.show('text')
# stream1.write('midi', "optimized.mid")
|