Spaces:
Sleeping
Sleeping
Update app.py and hexachords.py
Browse files- app.py +33 -4
- convert_to_svg.cjs +17 -0
- convert_to_svg.js +50 -0
- hexachords.py +34 -13
app.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
|
|
| 1 |
import shutil
|
| 2 |
import gradio as gr
|
|
|
|
| 3 |
from matplotlib import pyplot as plt
|
| 4 |
from mido import Message, MidiFile, MidiTrack
|
| 5 |
import os
|
| 6 |
import subprocess
|
| 7 |
-
from music21 import converter, note
|
| 8 |
from pydub import AudioSegment
|
| 9 |
import hexachords
|
| 10 |
|
|
@@ -13,7 +15,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
| 13 |
class HexachordApp:
|
| 14 |
|
| 15 |
def __init__(self):
|
| 16 |
-
self._hexachord =
|
| 17 |
self._hexachord_base_sequence = None
|
| 18 |
self.ui = None
|
| 19 |
self.on_huggingface = "HUGGINGFACE_SPACE" in os.environ
|
|
@@ -31,7 +33,6 @@ class HexachordApp:
|
|
| 31 |
def generate_chords(self, note_names, itvl):
|
| 32 |
# Placeholder for your actual chord generation function
|
| 33 |
# Assuming hexachord is a list of MIDI note numbers
|
| 34 |
-
self._hexachord = hexachords.Hexachord()
|
| 35 |
interval_21 = 'P5'
|
| 36 |
if itvl == 'fourth':
|
| 37 |
interval_21 = 'P4'
|
|
@@ -137,7 +138,6 @@ class HexachordApp:
|
|
| 137 |
return "Please enter exactly 6 unique MIDI note numbers."
|
| 138 |
except ValueError:
|
| 139 |
return "Invalid input. Enter 6 MIDI note numbers separated by spaces."
|
| 140 |
-
self._hexachord = notes
|
| 141 |
chords = self.generate_chords(notes, itvl)
|
| 142 |
midi_path = self.create_midi(chords, "base_chords.mid")
|
| 143 |
piano_roll_path = self.generate_piano_roll(chords, "base_chords.png")
|
|
@@ -153,6 +153,8 @@ class HexachordApp:
|
|
| 153 |
with gr.Tabs():
|
| 154 |
with gr.TabItem("Hexachord Generator"):
|
| 155 |
with gr.Row():
|
|
|
|
|
|
|
| 156 |
hexachord_input = gr.Textbox(
|
| 157 |
label="Enter 6 notes (pitchclass plus octave, separated by spaces)",
|
| 158 |
value="C3 D3 E3 G3 A3 B3",
|
|
@@ -169,6 +171,17 @@ class HexachordApp:
|
|
| 169 |
score_output = gr.Image(label="Score Visualization")
|
| 170 |
audio_output = gr.Audio(label="Play Generated Chords", value=None, interactive=False)
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
generate_button.click(
|
| 173 |
fn=self.process_hexachord,
|
| 174 |
inputs=[hexachord_input, interval_switch],
|
|
@@ -209,6 +222,21 @@ class HexachordApp:
|
|
| 209 |
setting_2 = gr.Slider(0, 100, label="Complexity Level")
|
| 210 |
self.ui = ui
|
| 211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
| 213 |
def launch_app():
|
| 214 |
hex = HexachordApp()
|
|
@@ -221,3 +249,4 @@ def launch_app():
|
|
| 221 |
|
| 222 |
|
| 223 |
launch_app()
|
|
|
|
|
|
| 1 |
+
import ast
|
| 2 |
import shutil
|
| 3 |
import gradio as gr
|
| 4 |
+
import numpy as np
|
| 5 |
from matplotlib import pyplot as plt
|
| 6 |
from mido import Message, MidiFile, MidiTrack
|
| 7 |
import os
|
| 8 |
import subprocess
|
| 9 |
+
from music21 import converter, note, interval
|
| 10 |
from pydub import AudioSegment
|
| 11 |
import hexachords
|
| 12 |
|
|
|
|
| 15 |
class HexachordApp:
|
| 16 |
|
| 17 |
def __init__(self):
|
| 18 |
+
self._hexachord = hexachords.Hexachord()
|
| 19 |
self._hexachord_base_sequence = None
|
| 20 |
self.ui = None
|
| 21 |
self.on_huggingface = "HUGGINGFACE_SPACE" in os.environ
|
|
|
|
| 33 |
def generate_chords(self, note_names, itvl):
|
| 34 |
# Placeholder for your actual chord generation function
|
| 35 |
# Assuming hexachord is a list of MIDI note numbers
|
|
|
|
| 36 |
interval_21 = 'P5'
|
| 37 |
if itvl == 'fourth':
|
| 38 |
interval_21 = 'P4'
|
|
|
|
| 138 |
return "Please enter exactly 6 unique MIDI note numbers."
|
| 139 |
except ValueError:
|
| 140 |
return "Invalid input. Enter 6 MIDI note numbers separated by spaces."
|
|
|
|
| 141 |
chords = self.generate_chords(notes, itvl)
|
| 142 |
midi_path = self.create_midi(chords, "base_chords.mid")
|
| 143 |
piano_roll_path = self.generate_piano_roll(chords, "base_chords.png")
|
|
|
|
| 153 |
with gr.Tabs():
|
| 154 |
with gr.TabItem("Hexachord Generator"):
|
| 155 |
with gr.Row():
|
| 156 |
+
hexachord_selector = gr.Dropdown(label="Select Known Hexachord",
|
| 157 |
+
choices=self.get_known_hexachords_choice(), value=None, interactive=True)
|
| 158 |
hexachord_input = gr.Textbox(
|
| 159 |
label="Enter 6 notes (pitchclass plus octave, separated by spaces)",
|
| 160 |
value="C3 D3 E3 G3 A3 B3",
|
|
|
|
| 171 |
score_output = gr.Image(label="Score Visualization")
|
| 172 |
audio_output = gr.Audio(label="Play Generated Chords", value=None, interactive=False)
|
| 173 |
|
| 174 |
+
hexachord_selector.change(
|
| 175 |
+
fn=self.get_selected_hexachord,
|
| 176 |
+
inputs=[hexachord_selector],
|
| 177 |
+
outputs=[hexachord_input]
|
| 178 |
+
)
|
| 179 |
+
# generate_button.click(
|
| 180 |
+
# fn=self.process_hexachord,
|
| 181 |
+
# inputs=[hexachord_selector, interval_switch],
|
| 182 |
+
# outputs=[midi_output, score_output, audio_output]
|
| 183 |
+
# )
|
| 184 |
+
|
| 185 |
generate_button.click(
|
| 186 |
fn=self.process_hexachord,
|
| 187 |
inputs=[hexachord_input, interval_switch],
|
|
|
|
| 222 |
setting_2 = gr.Slider(0, 100, label="Complexity Level")
|
| 223 |
self.ui = ui
|
| 224 |
|
| 225 |
+
def get_known_hexachords_choice(self):
|
| 226 |
+
return self._hexachord.known_hexachords
|
| 227 |
+
|
| 228 |
+
def get_selected_hexachord(self, x):
|
| 229 |
+
# lambda x: {"Hexachord 1": "C3 D3 E3 G3 A3 B3", "Hexachord 2": "D3 E3 F3 A3 B3 C4",
|
| 230 |
+
# "Hexachord 3": "E3 G3 A3 C4 D4 F4"}.get(x, "")
|
| 231 |
+
item = x[x.index('['):x.index(']')+1]
|
| 232 |
+
int_array = np.array(ast.literal_eval(item))
|
| 233 |
+
hexa_string = ''
|
| 234 |
+
start_note = note.Note('C3')
|
| 235 |
+
for i in int_array:
|
| 236 |
+
start_note = start_note.transpose(int(i))
|
| 237 |
+
hexa_string = hexa_string + ' ' + start_note.nameWithOctave
|
| 238 |
+
return hexa_string
|
| 239 |
+
|
| 240 |
|
| 241 |
def launch_app():
|
| 242 |
hex = HexachordApp()
|
|
|
|
| 249 |
|
| 250 |
|
| 251 |
launch_app()
|
| 252 |
+
|
convert_to_svg.cjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const verovio = require('verovio');
|
| 2 |
+
const fs = require('fs');
|
| 3 |
+
|
| 4 |
+
/* Wait for verovio to load */
|
| 5 |
+
verovio.module.onRuntimeInitialized = function ()
|
| 6 |
+
{
|
| 7 |
+
// create the toolkit instance
|
| 8 |
+
const vrvToolkit = new verovio.toolkit();
|
| 9 |
+
// read the MEI file
|
| 10 |
+
mei = fs.readFileSync('hello.mei');
|
| 11 |
+
// load the MEI data as string into the toolkit
|
| 12 |
+
vrvToolkit.loadData(mei.toString());
|
| 13 |
+
// render the fist page as SVG
|
| 14 |
+
svg = vrvToolkit.renderToSVG(1, {});
|
| 15 |
+
// save the SVG into a file
|
| 16 |
+
fs.writeFileSync('hello.svg', svg);
|
| 17 |
+
}
|
convert_to_svg.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const verovio = require('verovio');
|
| 2 |
+
const fs = require('fs');
|
| 3 |
+
const midi2musicxml = require('midi-to-musicxml'); // You can use a library like 'midi-to-musicxml' to convert MIDI to MusicXML
|
| 4 |
+
|
| 5 |
+
// Convert MIDI or MEI to SVG
|
| 6 |
+
function convertMidiToSvg(inputFile, outputFile) {
|
| 7 |
+
// Check if input is a MIDI or MEI file
|
| 8 |
+
const ext = inputFile.split('.').pop().toLowerCase();
|
| 9 |
+
|
| 10 |
+
if (ext === 'midi') {
|
| 11 |
+
// If it's a MIDI file, first convert to MusicXML
|
| 12 |
+
midi2musicxml(inputFile, function(err, musicXML) {
|
| 13 |
+
if (err) {
|
| 14 |
+
console.error('Error converting MIDI to MusicXML:', err);
|
| 15 |
+
return;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
// Now call Verovio to convert the MusicXML to SVG
|
| 19 |
+
generateSvgFromMusicXML(musicXML, outputFile);
|
| 20 |
+
});
|
| 21 |
+
} else if (ext === 'mei' || ext === 'musicxml') {
|
| 22 |
+
// If it's already MusicXML or MEI, directly generate SVG
|
| 23 |
+
const musicXML = fs.readFileSync(inputFile, 'utf-8');
|
| 24 |
+
generateSvgFromMusicXML(musicXML, outputFile);
|
| 25 |
+
} else {
|
| 26 |
+
console.error('Unsupported file type. Please provide a MIDI or MEI file.');
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// Function to generate SVG from MusicXML using Verovio
|
| 31 |
+
function generateSvgFromMusicXML(musicXML, outputFile) {
|
| 32 |
+
// Wait for Verovio to load
|
| 33 |
+
verovio.module.onRuntimeInitialized = function () {
|
| 34 |
+
// Create the Verovio toolkit instance
|
| 35 |
+
const vrvToolkit = new verovio.toolkit();
|
| 36 |
+
|
| 37 |
+
// Load the MusicXML data as string into the toolkit
|
| 38 |
+
vrvToolkit.loadData(musicXML);
|
| 39 |
+
|
| 40 |
+
// Render the first page as SVG
|
| 41 |
+
const svg = vrvToolkit.renderToSVG(1);
|
| 42 |
+
|
| 43 |
+
// Save the SVG into the output file
|
| 44 |
+
fs.writeFileSync(outputFile, svg);
|
| 45 |
+
console.log(`✅ SVG file saved to: ${outputFile}`);
|
| 46 |
+
};
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// Example usage:
|
| 50 |
+
convertMidiToSvg('path/to/your/input.midi', 'output.svg');
|
hexachords.py
CHANGED
|
@@ -6,6 +6,27 @@ from verovio import verovio
|
|
| 6 |
|
| 7 |
|
| 8 |
class Hexachord:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def generate_chord_sequence_from_midi_pitches(self, list_of_mp, intrvl="P5"):
|
| 11 |
return self.generate_chord_sequence([note.Note(mp).nameWithOctave for mp in list_of_mp], intrvl=intrvl)
|
|
@@ -35,7 +56,7 @@ class Hexachord:
|
|
| 35 |
for ch in chord_seq:
|
| 36 |
new_ch = chord.Chord()
|
| 37 |
for i, n in enumerate(ch.notes):
|
| 38 |
-
if i == 4 or i== 5:
|
| 39 |
new_ch.add(n.transpose(-24))
|
| 40 |
else:
|
| 41 |
new_ch.add(n)
|
|
@@ -43,7 +64,7 @@ class Hexachord:
|
|
| 43 |
for ch in chord_seq:
|
| 44 |
new_ch = chord.Chord()
|
| 45 |
for i, n in enumerate(ch.notes):
|
| 46 |
-
if i == 4 or i== 5:
|
| 47 |
new_ch.add(n.transpose(-24))
|
| 48 |
elif i == 3:
|
| 49 |
new_ch.add(n.transpose(-12))
|
|
@@ -53,7 +74,7 @@ class Hexachord:
|
|
| 53 |
for ch in chord_seq:
|
| 54 |
new_ch = chord.Chord()
|
| 55 |
for i, n in enumerate(ch.notes):
|
| 56 |
-
if i == 4 or i== 5:
|
| 57 |
new_ch.add(n.transpose(-24))
|
| 58 |
elif i == 3 or i == 2:
|
| 59 |
new_ch.add(n.transpose(-12))
|
|
@@ -62,7 +83,6 @@ class Hexachord:
|
|
| 62 |
res3.append(new_ch)
|
| 63 |
return res1, res2, res3
|
| 64 |
|
| 65 |
-
|
| 66 |
def chords_to_stream(self, chords, file_name):
|
| 67 |
s = stream.Stream()
|
| 68 |
s.append(meter.TimeSignature("4/4"))
|
|
@@ -71,10 +91,9 @@ class Hexachord:
|
|
| 71 |
ch.duration.quarterLength = 4
|
| 72 |
s.append(ch)
|
| 73 |
# s.show('midi')
|
| 74 |
-
s.write('midi',file_name)
|
| 75 |
return s
|
| 76 |
|
| 77 |
-
|
| 78 |
def alternate_chords(self, s1, s2):
|
| 79 |
"""Create a new stream alternating between chords from s1 and s2"""
|
| 80 |
new_stream = stream.Stream()
|
|
@@ -89,7 +108,6 @@ class Hexachord:
|
|
| 89 |
new_stream.append(c2)
|
| 90 |
return new_stream
|
| 91 |
|
| 92 |
-
|
| 93 |
def optimize_voice_leading(self, chord_sequence):
|
| 94 |
model = cp_model.CpModel()
|
| 95 |
octave_variables = {}
|
|
@@ -99,12 +117,13 @@ class Hexachord:
|
|
| 99 |
for i, ch in enumerate(chord_sequence):
|
| 100 |
for n in ch.notes:
|
| 101 |
var_name = f"chord_{i}_note_{n.nameWithOctave}"
|
| 102 |
-
octave_variables[var_name] = model.NewIntVar(n.octave - 1, n.octave + 1,
|
|
|
|
| 103 |
spread_vars = []
|
| 104 |
# Add constraints to minimize movement between chords
|
| 105 |
for i in range(len(chord_sequence) - 1):
|
| 106 |
-
max_octave = model.NewIntVar(0, 10, "max_pitch"+str(i))
|
| 107 |
-
min_octave = model.NewIntVar(0, 10, "min_pitch"+str(i))
|
| 108 |
for n in chord_sequence[i]:
|
| 109 |
v = octave_variables[f"chord_{i}_note_{n.nameWithOctave}"]
|
| 110 |
# model.Add(max_pitch >= v) # max_pitch must be at least as high as any note
|
|
@@ -114,7 +133,7 @@ class Hexachord:
|
|
| 114 |
for i in range(len(chord_sequence) - 1):
|
| 115 |
for n1, n2 in zip(chord_sequence[i].notes, chord_sequence[i + 1].notes):
|
| 116 |
var1 = octave_variables[f"chord_{i}_note_{n1.nameWithOctave}"]
|
| 117 |
-
var2 = octave_variables[f"chord_{i+1}_note_{n2.nameWithOctave}"]
|
| 118 |
# Define movement variable
|
| 119 |
movement_var = model.NewIntVar(0, 36, f"movement_{i}_{n1.name}")
|
| 120 |
model.AddAbsEquality(movement_var, var2 - var1)
|
|
@@ -135,8 +154,9 @@ class Hexachord:
|
|
| 135 |
# Apply changes to music21 chord sequence
|
| 136 |
optimized_chords = []
|
| 137 |
for i, ch in enumerate(chord_sequence):
|
| 138 |
-
new_chord = chord.Chord(
|
| 139 |
-
|
|
|
|
| 140 |
optimized_chords.append(new_chord)
|
| 141 |
|
| 142 |
return optimized_chords
|
|
@@ -169,6 +189,7 @@ class Hexachord:
|
|
| 169 |
return f"Error running Verovio: {result.stderr}"
|
| 170 |
return svg_output
|
| 171 |
|
|
|
|
| 172 |
if __name__ == '__main__':
|
| 173 |
hexa = Hexachord()
|
| 174 |
note_names = ["C3", "Eb3", "E3", "F#3", "G3", "Bb3"]
|
|
|
|
| 6 |
|
| 7 |
|
| 8 |
class Hexachord:
|
| 9 |
+
known_hexachords = [
|
| 10 |
+
"6-1 [0,1,2,3,4,5] Chromatic hexachord",
|
| 11 |
+
"6-7 [0,1,2,6,7,8] Two-semitone tritone scale",
|
| 12 |
+
"6-Z17A [0,1,2,4,7,8] All-trichord hexachord",
|
| 13 |
+
"6-20 [0,1,4,5,8,9] Augmented scale, Ode-to-Napoleon hexachord",
|
| 14 |
+
"6-Z24A [0,1,3,4,6,8] Minor major eleventh chord",
|
| 15 |
+
"6-Z24B [0,2,4,5,7,8] Half-diminished eleventh chord",
|
| 16 |
+
"6-Z25A [0,1,3,5,6,8] Major eleventh chord",
|
| 17 |
+
"6-Z26 [0,1,3,5,7,8] Major ninth sharp eleventh chord",
|
| 18 |
+
"6-27B [0,2,3,5,6,9] Diminished eleventh chord",
|
| 19 |
+
"6-Z28 [0,1,3,5,6,9] Augmented major eleventh chord",
|
| 20 |
+
"6-Z29 [0,2,3,6,7,9] Bridge chord",
|
| 21 |
+
"6-30B [0,2,3,6,8,9] Petrushka chord, tritone scale",
|
| 22 |
+
"6-32 [0,2,4,5,7,9] Diatonic hexachord, minor eleventh chord",
|
| 23 |
+
"6-33B [0,2,4,6,7,9] Dominant eleventh chord",
|
| 24 |
+
"6-34A [0,1,3,5,7,9] Mystic chord",
|
| 25 |
+
"6-34B [0,2,4,6,8,9] Augmented eleventh chord, dominant sharp eleventh chord, Prélude chord",
|
| 26 |
+
"6-35 [0,2,4,6,8,T] Whole tone scale",
|
| 27 |
+
"6-Z44A [0,1,2,5,6,9] Schoenberg hexachord",
|
| 28 |
+
"6-Z46A [0,1,2,4,6,9] Scale of harmonics",
|
| 29 |
+
"6-Z47B [0,2,3,4,7,9] Blues scale"]
|
| 30 |
|
| 31 |
def generate_chord_sequence_from_midi_pitches(self, list_of_mp, intrvl="P5"):
|
| 32 |
return self.generate_chord_sequence([note.Note(mp).nameWithOctave for mp in list_of_mp], intrvl=intrvl)
|
|
|
|
| 56 |
for ch in chord_seq:
|
| 57 |
new_ch = chord.Chord()
|
| 58 |
for i, n in enumerate(ch.notes):
|
| 59 |
+
if i == 4 or i == 5:
|
| 60 |
new_ch.add(n.transpose(-24))
|
| 61 |
else:
|
| 62 |
new_ch.add(n)
|
|
|
|
| 64 |
for ch in chord_seq:
|
| 65 |
new_ch = chord.Chord()
|
| 66 |
for i, n in enumerate(ch.notes):
|
| 67 |
+
if i == 4 or i == 5:
|
| 68 |
new_ch.add(n.transpose(-24))
|
| 69 |
elif i == 3:
|
| 70 |
new_ch.add(n.transpose(-12))
|
|
|
|
| 74 |
for ch in chord_seq:
|
| 75 |
new_ch = chord.Chord()
|
| 76 |
for i, n in enumerate(ch.notes):
|
| 77 |
+
if i == 4 or i == 5:
|
| 78 |
new_ch.add(n.transpose(-24))
|
| 79 |
elif i == 3 or i == 2:
|
| 80 |
new_ch.add(n.transpose(-12))
|
|
|
|
| 83 |
res3.append(new_ch)
|
| 84 |
return res1, res2, res3
|
| 85 |
|
|
|
|
| 86 |
def chords_to_stream(self, chords, file_name):
|
| 87 |
s = stream.Stream()
|
| 88 |
s.append(meter.TimeSignature("4/4"))
|
|
|
|
| 91 |
ch.duration.quarterLength = 4
|
| 92 |
s.append(ch)
|
| 93 |
# s.show('midi')
|
| 94 |
+
s.write('midi', file_name)
|
| 95 |
return s
|
| 96 |
|
|
|
|
| 97 |
def alternate_chords(self, s1, s2):
|
| 98 |
"""Create a new stream alternating between chords from s1 and s2"""
|
| 99 |
new_stream = stream.Stream()
|
|
|
|
| 108 |
new_stream.append(c2)
|
| 109 |
return new_stream
|
| 110 |
|
|
|
|
| 111 |
def optimize_voice_leading(self, chord_sequence):
|
| 112 |
model = cp_model.CpModel()
|
| 113 |
octave_variables = {}
|
|
|
|
| 117 |
for i, ch in enumerate(chord_sequence):
|
| 118 |
for n in ch.notes:
|
| 119 |
var_name = f"chord_{i}_note_{n.nameWithOctave}"
|
| 120 |
+
octave_variables[var_name] = model.NewIntVar(n.octave - 1, n.octave + 1,
|
| 121 |
+
var_name) # Allow octave shifts
|
| 122 |
spread_vars = []
|
| 123 |
# Add constraints to minimize movement between chords
|
| 124 |
for i in range(len(chord_sequence) - 1):
|
| 125 |
+
max_octave = model.NewIntVar(0, 10, "max_pitch" + str(i))
|
| 126 |
+
min_octave = model.NewIntVar(0, 10, "min_pitch" + str(i))
|
| 127 |
for n in chord_sequence[i]:
|
| 128 |
v = octave_variables[f"chord_{i}_note_{n.nameWithOctave}"]
|
| 129 |
# model.Add(max_pitch >= v) # max_pitch must be at least as high as any note
|
|
|
|
| 133 |
for i in range(len(chord_sequence) - 1):
|
| 134 |
for n1, n2 in zip(chord_sequence[i].notes, chord_sequence[i + 1].notes):
|
| 135 |
var1 = octave_variables[f"chord_{i}_note_{n1.nameWithOctave}"]
|
| 136 |
+
var2 = octave_variables[f"chord_{i + 1}_note_{n2.nameWithOctave}"]
|
| 137 |
# Define movement variable
|
| 138 |
movement_var = model.NewIntVar(0, 36, f"movement_{i}_{n1.name}")
|
| 139 |
model.AddAbsEquality(movement_var, var2 - var1)
|
|
|
|
| 154 |
# Apply changes to music21 chord sequence
|
| 155 |
optimized_chords = []
|
| 156 |
for i, ch in enumerate(chord_sequence):
|
| 157 |
+
new_chord = chord.Chord(
|
| 158 |
+
[note.Note(f"{n.name}{solver.Value(octave_variables[f'chord_{i}_note_{n.nameWithOctave}'])}")
|
| 159 |
+
for n in ch.notes])
|
| 160 |
optimized_chords.append(new_chord)
|
| 161 |
|
| 162 |
return optimized_chords
|
|
|
|
| 189 |
return f"Error running Verovio: {result.stderr}"
|
| 190 |
return svg_output
|
| 191 |
|
| 192 |
+
|
| 193 |
if __name__ == '__main__':
|
| 194 |
hexa = Hexachord()
|
| 195 |
note_names = ["C3", "Eb3", "E3", "F#3", "G3", "Bb3"]
|