pachet commited on
Commit
9fa389d
·
1 Parent(s): 479cb41

created working versions folder with a working version with piano rolls.

Browse files
convert_to_svg.cjs CHANGED
@@ -7,7 +7,7 @@ verovio.module.onRuntimeInitialized = function ()
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
 
7
  // create the toolkit instance
8
  const vrvToolkit = new verovio.toolkit();
9
  // read the MEI file
10
+ mei = fs.readFileSync('output.mei');
11
  // load the MEI data as string into the toolkit
12
  vrvToolkit.loadData(mei.toString());
13
  // render the fist page as SVG
new_app.py CHANGED
@@ -188,6 +188,16 @@ class HexachordApp:
188
 
189
  return midi_path, score_path, audio_path
190
 
 
 
 
 
 
 
 
 
 
 
191
  def render(self):
192
  with gr.Blocks() as ui:
193
  gr.Markdown("# Hexachord-based Chord Generator")
 
188
 
189
  return midi_path, score_path, audio_path
190
 
191
+ def midi_to_musicxml_string(self, midi_file):
192
+ # midi_file_path = "example.mid" # Replace with your MIDI file
193
+ # musicxml_string = midi_to_musicxml_string(midi_file_path)
194
+ # print(musicxml_string)
195
+ """Convert MIDI to MusicXML string using music21."""
196
+ score = converter.parse(midi_file)
197
+ return score.write('musicxml')
198
+ # Example usage
199
+
200
+
201
  def render(self):
202
  with gr.Blocks() as ui:
203
  gr.Markdown("# Hexachord-based Chord Generator")
working_version.py CHANGED
@@ -1,223 +1,46 @@
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
-
11
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
12
-
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
20
-
21
- def is_fsynth_installed(self):
22
- """ Check to make sure fluidsynth exists in the PATH """
23
- for path in os.environ['PATH'].split(os.pathsep):
24
- f = os.path.join(path, 'fluidsynth')
25
- if os.path.exists(f) and os.access(f, os.X_OK):
26
- print('fluidsynth is installed')
27
- return True
28
- print('fluidsynth is NOT installed')
29
- return False
30
-
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'
38
- self._hexachord_base_sequence = self._hexachord.generate_chord_sequence(note_names, intrvl=interval_21)
39
- return self._hexachord_base_sequence
40
-
41
- def generate_realizations(self):
42
- # returns 3 triples of midipath, piano roll, audio player
43
- reals = self._hexachord.generate_3_chords_realizations(self._hexachord_base_sequence)
44
- midi_path1 = self.create_midi(reals[0], "real1.mid")
45
- piano_roll_path1 = self.generate_piano_roll(reals[0], "real1.png")
46
- audio_path1 = self.convert_midi_to_audio(midi_path1, "real1")
47
- midi_path2 = self.create_midi(reals[1], "real2.mid")
48
- piano_roll_path2 = self.generate_piano_roll(reals[1], "real2.png")
49
- audio_path2 = self.convert_midi_to_audio(midi_path2, "real2")
50
- midi_path3 = self.create_midi(reals[2], "real3.mid")
51
- piano_roll_path3 = self.generate_piano_roll(reals[2], "real3.png")
52
- audio_path3 = self.convert_midi_to_audio(midi_path3, "real3")
53
- return midi_path1, piano_roll_path1, audio_path1, midi_path2, piano_roll_path2, audio_path2, midi_path3, piano_roll_path3, audio_path3
54
-
55
- def create_midi(self, chords, file_name):
56
- mid = MidiFile()
57
- track = MidiTrack()
58
- mid.tracks.append(track)
59
- delta_time = 480 * 4
60
- for i_chord, chord in enumerate(chords):
61
- for i, note in enumerate(chord):
62
- if i == 0 and i_chord != 0:
63
- track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=1))
64
- else:
65
- track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=0))
66
- for i, note in enumerate(chord):
67
- if i==0:
68
- track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=delta_time - 1))
69
- else:
70
- track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=0))
71
- midi_path = os.path.join(BASE_DIR, file_name)
72
- mid.save(midi_path)
73
- return midi_path
74
-
75
-
76
- def convert_midi_to_audio(self, midi_path, file_name):
77
- if not shutil.which("fluidsynth"):
78
- try:
79
- subprocess.run(["apt-get", "update"], check=True)
80
- subprocess.run(["apt-get", "install", "-y", "fluidsynth"], check=True)
81
- except Exception as e:
82
- return f"Error installing Fluidsynth: {str(e)}"
83
- wav_path = os.path.join(BASE_DIR, file_name + ".wav")
84
- mp3_path = os.path.join(BASE_DIR, file_name + ".mp3")
85
- soundfont_path = os.path.join(BASE_DIR, "soundfont.sf2")
86
- if not os.path.exists(soundfont_path):
87
- return "Error: SoundFont file not found. Please provide a valid .sf2 file."
88
- try:
89
- subprocess.run(["fluidsynth", "-ni", soundfont_path, midi_path, "-F", wav_path, "-r", "44100"], check=True)
90
- AudioSegment.converter = "ffmpeg"
91
- audio = AudioSegment.from_wav(wav_path)
92
- audio.export(mp3_path, format="mp3")
93
- return mp3_path
94
- except Exception as e:
95
- return f"Error converting MIDI to audio: {str(e)}"
96
-
97
- def generate_png(self, midi_file, output_file_name):
98
- self._hexachord.midi_to_png(midi_file, output_file_name)
99
-
100
- def generate_piano_roll(self, chords, label):
101
- fig, ax = plt.subplots(figsize=(8, 4))
102
-
103
- for i, chord in enumerate(chords):
104
- for note in chord:
105
- ax.broken_barh([(i * 1, 0.8)], (note.pitch.midi - 0.4, 0.8), facecolors='blue')
106
-
107
- ax.set_xlabel("Chord Progression")
108
- ax.set_ylabel("MIDI Note Number")
109
- min_min_chords = 128
110
- max_max_chords = 0
111
- for ch in chords:
112
- for note in ch:
113
- if note.pitch.midi < min_min_chords:
114
- min_min_chords = note.pitch.midi
115
- if note.pitch.midi > max_max_chords:
116
- max_max_chords = note.pitch.midi
117
- ax.set_yticks(range(min_min_chords, max_max_chords + 1, 2))
118
- ax.set_xticks(range(len(chords)))
119
- ax.set_xticklabels([f"Chord {i + 1}" for i in range(len(chords))])
120
-
121
- plt.grid(True, linestyle='--', alpha=0.5)
122
- plt.savefig(label)
123
- return label
124
-
125
- def launch_score_editor(self, midi_path):
126
- try:
127
- score = converter.parse(midi_path)
128
- score.show('musicxml')
129
- return "Opened MIDI file in the default score editor!"
130
- except Exception as e:
131
- return f"Error opening score editor: {str(e)}"
132
-
133
- def process_hexachord(self, hexachord_str, itvl):
134
- try:
135
- notes = [note.Note(n) for n in hexachord_str.split()]
136
- if len(notes) != 6 or len(set(notes)) != 6:
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")
144
- score_path = self.generate_png (midi_path, "score_base_chords.png")
145
- audio_path = self.convert_midi_to_audio(midi_path, "base_chords")
146
-
147
- return midi_path, score_path, audio_path
148
-
149
- def render(self):
150
- with gr.Blocks() as ui:
151
- gr.Markdown("# Hexachord-based Chord Generator")
152
-
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",
159
- interactive = True
160
- )
161
- interval_switch = gr.Radio(
162
- choices=["fourth", "fifth"],
163
- label="Select Interval",
164
- value="fifth"
165
- )
166
- generate_button = gr.Button("Generate Chords")
167
- midi_output = gr.File(label="Download MIDI File")
168
- # piano_roll_output = gr.Image(label="Piano Roll Visualization")
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],
175
- # outputs=[midi_output, piano_roll_output, audio_output]
176
- outputs = [midi_output, score_output, audio_output]
177
- )
178
- # Pressing Enter in the textbox also triggers processing
179
- hexachord_input.submit(
180
- fn=self.process_hexachord,
181
- inputs=[hexachord_input, interval_switch],
182
- outputs=[midi_output, score_output, audio_output]
183
- )
184
-
185
- with gr.TabItem("Alternative Chord Realizations"):
186
- gr.Markdown("Alternative Chord Realizations")
187
-
188
- realization_button = gr.Button("Generate Alternative Realizations")
189
- realization_outputs = []
190
-
191
- for i in range(3): # Three alternative realizations
192
- with gr.Group():
193
- gr.Markdown(f"#### Realization {i + 1}")
194
- midi_output = gr.File(label="Download MIDI File")
195
- piano_roll = gr.Image(label=f"Piano Roll {i + 1}")
196
- audio_player = gr.Audio(label=f"Play Chords {i + 1}", interactive=False)
197
- realization_outputs.append((midi_output, piano_roll, audio_player))
198
 
199
- # Clicking the button triggers realization generation
200
- realization_button.click(
201
- fn=self.generate_realizations,
202
- inputs=[],
203
- outputs=[output for trip in realization_outputs for output in trip]
204
- )
205
 
206
- with gr.TabItem("Settings"):
207
- gr.Markdown("### Configuration Options")
208
- setting_1 = gr.Checkbox(label="Enable Advanced Mode")
209
- setting_2 = gr.Slider(0, 100, label="Complexity Level")
210
- self.ui = ui
211
 
 
 
 
 
 
 
 
 
 
212
 
213
- def launch_app():
214
- hex = HexachordApp()
215
- hex.is_fsynth_installed()
216
- hex.render()
217
- if hex.on_huggingface:
218
- hex.ui.launch(server_name="0.0.0.0", server_port=7860)
219
- else:
220
- hex.ui.launch()
221
 
 
 
 
222
 
223
- launch_app()
 
1
+ import requests
2
+ from music21 import converter
3
+
4
+
5
+ def midi_to_musicxml(midi_file):
6
+ """Convert MIDI to MusicXML using music21."""
7
+ score = converter.parse(midi_file)
8
+ musicxml_path = "temp.musicxml"
9
+ score.write('musicxml', fp=musicxml_path)
10
+ return musicxml_path
11
+
12
+
13
+ def convert_musicxml_to_svg_online(musicxml_path):
14
+ """Send MusicXML file to Verovio API and get SVG output."""
15
+ url = "https://verovio.humdrum.org/cgi-bin/verovio.cgi"
16
+ with open(musicxml_path, "rb") as file:
17
+ files = {
18
+ "file": ("score.musicxml", file, "application/xml"),
19
+ "format": (None, "svg"), # Request SVG format
20
+ }
21
+ response = requests.post(url, files=files)
22
+ if response.status_code == 200:
23
+ return response.text # SVG content
24
+ else:
25
+ return f"Error: {response.status_code}, {response.text}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
 
 
 
 
 
 
27
 
28
+ def midi_to_svg(midi_file):
29
+ """Convert MIDI to SVG using Verovio Web API."""
 
 
 
30
 
31
+ # Step 1: Convert MIDI to MusicXML
32
+ musicxml_path = midi_to_musicxml(midi_file)
33
+ # Step 2: Convert MusicXML to SVG via Verovio API
34
+ svg_content = convert_musicxml_to_svg_online(musicxml_path)
35
+ # Save SVG
36
+ output_svg = "output.svg"
37
+ with open(output_svg, "w") as f:
38
+ f.write(svg_content)
39
+ return output_svg
40
 
 
 
 
 
 
 
 
 
41
 
42
+ # Example Usage
43
+ midi_file_path = "base_chords.mid" # Replace with actual MIDI file
44
+ svg_output = midi_to_svg(midi_file_path)
45
 
46
+ print(f"SVG file generated: {svg_output}")
working_versions/__init__.py ADDED
File without changes
working_versions/app.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
13
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
14
+
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
22
+
23
+ def is_fsynth_installed(self):
24
+ """ Check to make sure fluidsynth exists in the PATH """
25
+ for path in os.environ['PATH'].split(os.pathsep):
26
+ f = os.path.join(path, 'fluidsynth')
27
+ if os.path.exists(f) and os.access(f, os.X_OK):
28
+ print('fluidsynth is installed')
29
+ return True
30
+ print('fluidsynth is NOT installed')
31
+ return False
32
+
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'
39
+ self._hexachord_base_sequence = self._hexachord.generate_chord_sequence(note_names, intrvl=interval_21)
40
+ return self._hexachord_base_sequence
41
+
42
+ def generate_realizations(self):
43
+ # returns 3 triples of midipath, piano roll, audio player
44
+ reals = self._hexachord.generate_3_chords_realizations(self._hexachord_base_sequence)
45
+ midi_path1 = self.create_midi(reals[0], "real1.mid")
46
+ piano_roll_path1 = self.generate_piano_roll(reals[0], "real1.png")
47
+ audio_path1 = self.convert_midi_to_audio(midi_path1, "real1")
48
+ midi_path2 = self.create_midi(reals[1], "real2.mid")
49
+ piano_roll_path2 = self.generate_piano_roll(reals[1], "real2.png")
50
+ audio_path2 = self.convert_midi_to_audio(midi_path2, "real2")
51
+ midi_path3 = self.create_midi(reals[2], "real3.mid")
52
+ piano_roll_path3 = self.generate_piano_roll(reals[2], "real3.png")
53
+ audio_path3 = self.convert_midi_to_audio(midi_path3, "real3")
54
+ return midi_path1, piano_roll_path1, audio_path1, midi_path2, piano_roll_path2, audio_path2, midi_path3, piano_roll_path3, audio_path3
55
+
56
+ def create_midi(self, chords, file_name):
57
+ mid = MidiFile()
58
+ track = MidiTrack()
59
+ mid.tracks.append(track)
60
+ delta_time = 480 * 4
61
+ for i_chord, chord in enumerate(chords):
62
+ for i, note in enumerate(chord):
63
+ if i == 0 and i_chord != 0:
64
+ track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=1))
65
+ else:
66
+ track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=0))
67
+ for i, note in enumerate(chord):
68
+ if i==0:
69
+ track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=delta_time - 1))
70
+ else:
71
+ track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=0))
72
+ midi_path = os.path.join(BASE_DIR, file_name)
73
+ mid.save(midi_path)
74
+ return midi_path
75
+
76
+
77
+ def convert_midi_to_audio(self, midi_path, file_name):
78
+ if not shutil.which("fluidsynth"):
79
+ try:
80
+ subprocess.run(["apt-get", "update"], check=True)
81
+ subprocess.run(["apt-get", "install", "-y", "fluidsynth"], check=True)
82
+ except Exception as e:
83
+ return f"Error installing Fluidsynth: {str(e)}"
84
+ wav_path = os.path.join(BASE_DIR, file_name + ".wav")
85
+ mp3_path = os.path.join(BASE_DIR, file_name + ".mp3")
86
+ soundfont_path = os.path.join(BASE_DIR, "soundfont.sf2")
87
+ if not os.path.exists(soundfont_path):
88
+ return "Error: SoundFont file not found. Please provide a valid .sf2 file."
89
+ try:
90
+ subprocess.run(["fluidsynth", "-ni", soundfont_path, midi_path, "-F", wav_path, "-r", "44100"], check=True)
91
+ AudioSegment.converter = "ffmpeg"
92
+ audio = AudioSegment.from_wav(wav_path)
93
+ audio.export(mp3_path, format="mp3")
94
+ return mp3_path
95
+ except Exception as e:
96
+ return f"Error converting MIDI to audio: {str(e)}"
97
+
98
+ def generate_png(self, midi_file, output_file_name):
99
+ self._hexachord.midi_to_png(midi_file, output_file_name)
100
+
101
+ def generate_piano_roll(self, chords, label):
102
+ fig, ax = plt.subplots(figsize=(8, 4))
103
+
104
+ for i, chord in enumerate(chords):
105
+ for note in chord:
106
+ ax.broken_barh([(i * 1, 0.8)], (note.pitch.midi - 0.4, 0.8), facecolors='blue')
107
+
108
+ ax.set_xlabel("Chord Progression")
109
+ ax.set_ylabel("MIDI Note Number")
110
+ min_min_chords = 128
111
+ max_max_chords = 0
112
+ for ch in chords:
113
+ for note in ch:
114
+ if note.pitch.midi < min_min_chords:
115
+ min_min_chords = note.pitch.midi
116
+ if note.pitch.midi > max_max_chords:
117
+ max_max_chords = note.pitch.midi
118
+ ax.set_yticks(range(min_min_chords, max_max_chords + 1, 2))
119
+ ax.set_xticks(range(len(chords)))
120
+ ax.set_xticklabels([f"Chord {i + 1}" for i in range(len(chords))])
121
+
122
+ plt.grid(True, linestyle='--', alpha=0.5)
123
+ plt.savefig(label)
124
+ return label
125
+
126
+ def launch_score_editor(self, midi_path):
127
+ try:
128
+ score = converter.parse(midi_path)
129
+ score.show('musicxml')
130
+ return "Opened MIDI file in the default score editor!"
131
+ except Exception as e:
132
+ return f"Error opening score editor: {str(e)}"
133
+
134
+ def process_hexachord(self, hexachord_str, itvl):
135
+ try:
136
+ notes = [note.Note(n) for n in hexachord_str.split()]
137
+ if len(notes) != 6 or len(set(notes)) != 6:
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")
144
+ # score_path = self.generate_png (midi_path, "score_base_chords.png")
145
+ audio_path = self.convert_midi_to_audio(midi_path, "base_chords")
146
+
147
+ return midi_path, piano_roll_path, audio_path
148
+
149
+ def render(self):
150
+ with gr.Blocks() as ui:
151
+ gr.Markdown("# Hexachord-based Chord Generator")
152
+
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",
161
+ interactive = True
162
+ )
163
+ interval_switch = gr.Radio(
164
+ choices=["fourth", "fifth"],
165
+ label="Select Interval",
166
+ value="fifth"
167
+ )
168
+ generate_button = gr.Button("Generate Chords")
169
+ midi_output = gr.File(label="Download MIDI File")
170
+ # piano_roll_output = gr.Image(label="Piano Roll Visualization")
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],
188
+ # outputs=[midi_output, piano_roll_output, audio_output]
189
+ outputs = [midi_output, score_output, audio_output]
190
+ )
191
+ # Pressing Enter in the textbox also triggers processing
192
+ hexachord_input.submit(
193
+ fn=self.process_hexachord,
194
+ inputs=[hexachord_input, interval_switch],
195
+ outputs=[midi_output, score_output, audio_output]
196
+ )
197
+
198
+ with gr.TabItem("Alternative Chord Realizations"):
199
+ gr.Markdown("Alternative Chord Realizations")
200
+
201
+ realization_button = gr.Button("Generate Alternative Realizations")
202
+ realization_outputs = []
203
+
204
+ for i in range(3): # Three alternative realizations
205
+ with gr.Group():
206
+ gr.Markdown(f"#### Realization {i + 1}")
207
+ midi_output = gr.File(label="Download MIDI File")
208
+ piano_roll = gr.Image(label=f"Piano Roll {i + 1}")
209
+ audio_player = gr.Audio(label=f"Play Chords {i + 1}", interactive=False)
210
+ realization_outputs.append((midi_output, piano_roll, audio_player))
211
+
212
+ # Clicking the button triggers realization generation
213
+ realization_button.click(
214
+ fn=self.generate_realizations,
215
+ inputs=[],
216
+ outputs=[output for trip in realization_outputs for output in trip]
217
+ )
218
+
219
+ with gr.TabItem("Settings"):
220
+ gr.Markdown("### Configuration Options")
221
+ setting_1 = gr.Checkbox(label="Enable Advanced Mode")
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
+ add_note = start_note.transpose(int(i))
237
+ hexa_string = hexa_string + ' ' + add_note.nameWithOctave
238
+ return hexa_string
239
+
240
+
241
+ def launch_app():
242
+ hex = HexachordApp()
243
+ hex.is_fsynth_installed()
244
+ hex.render()
245
+ if hex.on_huggingface:
246
+ hex.ui.launch(server_name="0.0.0.0", server_port=7860)
247
+ else:
248
+ hex.ui.launch()
249
+
250
+
251
+ launch_app()
252
+
working_versions/hexachords.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ from music21 import note, stream, interval, meter, chord, converter, metadata
4
+ from ortools.sat.python import cp_model
5
+ from verovio import verovio
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)
33
+
34
+ def generate_chord_sequence(self, six_notes, intrvl="P5"):
35
+
36
+ fifth = interval.Interval(intrvl) # Perfect fifth
37
+ all_pc = [n.pitch.pitchClass for n in six_notes]
38
+ all_chords = []
39
+ for n in six_notes:
40
+ ch = chord.Chord([n])
41
+ current_note = n
42
+ while len(ch) < 6:
43
+ current_note = fifth.transposeNote(current_note)
44
+ if current_note.pitch.pitchClass in all_pc and current_note not in ch:
45
+ while interval.Interval(noteStart=ch[-1], noteEnd=current_note).semitones > (12 + 7):
46
+ current_note = current_note.transpose(-12)
47
+ ch.add(current_note)
48
+ all_chords.append(ch)
49
+ return all_chords
50
+
51
+ def generate_3_chords_realizations(self, chord_seq):
52
+ # lower each of the top to notes by 2 ocatves
53
+ res1 = []
54
+ res2 = []
55
+ res3 = []
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)
63
+ res1.append(new_ch)
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))
71
+ else:
72
+ new_ch.add(n)
73
+ res2.append(new_ch)
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))
81
+ else:
82
+ new_ch.add(n)
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"))
89
+ for c in chords:
90
+ ch = chord.Chord(c)
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()
100
+
101
+ # Get chords from both streams
102
+ chords1 = list(s1.getElementsByClass(chord.Chord))
103
+ chords2 = list(s2.getElementsByClass(chord.Chord))
104
+
105
+ # Interleave chords from s1 and s2
106
+ for c1, c2 in zip(chords1, chords2):
107
+ new_stream.append(c1)
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 = {}
114
+ movement_vars = []
115
+
116
+ # Define variables and domains (allowing octave shifts)
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
130
+ # model.Add(min_pitch <= v) # min_pitch must
131
+ spread_var = max_octave - min_octave
132
+ spread_vars.append(spread_var)
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)
140
+ # Track movement variable in objective function
141
+ movement_vars.append(movement_var)
142
+ # Define objective: minimize sum of all movement values
143
+ model.Minimize(sum(spread_vars))
144
+ # obj_var = sum(movement_vars)
145
+ # model.Minimize(obj_var)
146
+ # Solve
147
+ solver = cp_model.CpSolver()
148
+ solver.Solve(model)
149
+ # print(solver.Value(obj_var))
150
+ # for v in variables:
151
+ # print(v)
152
+ # print(variables[v].Proto().domain)
153
+ # print(solver.Value(variables[v]))
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
163
+
164
+ def midi_to_svg_file(self, midi_file, output_file):
165
+ score = converter.parse(midi_file)
166
+ musicxml_data = score.write('musicxml') # Get MusicXML as a string
167
+ # Step 2: Load MusicXML into Verovio
168
+ tk = verovio.toolkit()
169
+ tk.loadData(musicxml_data.encode()) # Convert to bytes and load into Verovio
170
+ tk.renderToSVGFile(output_file, 1)
171
+
172
+ def midi_to_svg(self, midi_file, svg_output):
173
+ """Convert MIDI to SVG using Verovio's command-line tool."""
174
+ score = converter.parse(midi_file)
175
+ score.metadata = metadata.Metadata()
176
+ score.metadata.title = ''
177
+ musicxml_path = "temp.musicxml"
178
+ score.write('musicxml', fp=musicxml_path)
179
+ # Run Verovio via command line (since Python API fails)
180
+ verovio_executable = "verovio/build/verovio" # Ensure correct path
181
+ if not os.path.exists(verovio_executable):
182
+ return "Error: Verovio binary not found!"
183
+ # Run Verovio with the full path
184
+ command = f"{verovio_executable} {musicxml_path} -o {svg_output} --smufl-text-font embedded --scale 50 --page-width 1000 --page-height 500 --footer none"
185
+ result = subprocess.run(command, shell=True, capture_output=True, text=True)
186
+
187
+ if result.returncode != 0:
188
+ print("Verovio Error:", result.stderr)
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"]
196
+ notes = [note.Note(n) for n in note_names]
197
+ cs1 = hexa.generate_chord_sequence(notes, intrvl="P4")
198
+ # cs1 = generate_chord_sequence(["E3", "G3", "Ab3", "B3", "C4", "Eb4"])
199
+ # cs2 = generate_chord_sequence(["C3", "F3", "F#3", "A3", "B4", "E4"])
200
+ # alternation = alternate_chords(cs1, cs2)
201
+ # alternation.write('midi',"alternation.mid")
202
+
203
+ hexa.chords_to_stream(cs1, 'temp.mid').show('text')
204
+ # optimized = optimize_voice_leading([c1, c2, c3])
205
+ optimized = hexa.optimize_voice_leading(cs1)
206
+ stream1 = stream.Stream(optimized)
207
+ stream1.show('text')
208
+ # stream1.write('midi', "optimized.mid")