pachet commited on
Commit
45d6604
·
1 Parent(s): e869bca

Update app.py and hexachords.py

Browse files
Files changed (4) hide show
  1. app.py +33 -4
  2. convert_to_svg.cjs +17 -0
  3. convert_to_svg.js +50 -0
  4. 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 = None
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, var_name) # Allow octave shifts
 
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([note.Note(f"{n.name}{solver.Value(octave_variables[f'chord_{i}_note_{n.nameWithOctave}'])}")
139
- for n in ch.notes])
 
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"]