Spaces:
Sleeping
Sleeping
File size: 8,266 Bytes
6e4f2b4 fc99099 a4f531b fc99099 a4f531b fc99099 a4f531b fc99099 a4f531b fc99099 a4f531b fc99099 a4f531b 6e4f2b4 a4f531b 6e4f2b4 a4f531b 6e4f2b4 a4f531b fc99099 a4f531b 6e4f2b4 a4f531b | 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 | import os
from mido import MidiFile, Message, MidiTrack
from music21 import note, stream, interval, meter, chord
from ortools.sat.python import cp_model
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"]
_base_sequence = None
_realizations = []
def generate_chord_sequence_from_midi_pitches(self, list_of_mp, intrvl="P5"):
return self.generate_base_sequence([note.Note(mp).nameWithOctave for mp in list_of_mp], intrvl=intrvl)
def generate_base_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)
self._base_sequence = all_chords
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)
self._realizations = res1, res2, res3
return self._realizations
def chords_to_m21(self, chords):
s = stream.Stream()
s.append(meter.TimeSignature("4/4"))
for c in chords:
ch = chord.Chord(c)
ch.duration.quarterLength = 4
s.append(ch)
return s
def chords_to_m21_voices(self, chords):
s = stream.Stream()
s.append(meter.TimeSignature("4/4"))
for i_chord, c in enumerate(chords):
for chord_note in c:
n = note.Note(chord_note.pitch)
n.duration.quarterLength = 4
s.insert(i_chord * 4, n)
return s
def save_chords_to_midi_file(self, chords, file_name):
m21 = self.chords_to_m21_voices(chords)
m21.write('midi', file_name)
return file_name
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
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_base_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.save_chords_to_midi_file(cs1, 'temp.mid')
# 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")
|