pachet commited on
Commit
50062d3
·
1 Parent(s): d877082

restructured the project a bit, added legacy folder

Browse files
.gitignore CHANGED
@@ -19,7 +19,7 @@ __pycache__/
19
  *.mp3
20
  *.wav
21
  *.ly
22
- output.*
23
  temp.*
24
 
25
  # Project specific
 
19
  *.mp3
20
  *.wav
21
  *.ly
22
+ legacy/output.mei
23
  temp.*
24
 
25
  # Project specific
README.md CHANGED
@@ -51,7 +51,7 @@ To run this project locally:
51
 
52
  1. Local Development:
53
  ```bash
54
- python app.py
55
  ```
56
 
57
  2. Web Interface:
 
51
 
52
  1. Local Development:
53
  ```bash
54
+ python working_app_one_score.py
55
  ```
56
 
57
  2. Web Interface:
format_conversions.py CHANGED
@@ -2,7 +2,6 @@ import copy
2
  from music21 import converter, musicxml, stream, note, chord, clef
3
  import verovio
4
 
5
-
6
  class Format_Converter:
7
 
8
  def midi_to_musicxml_string_two_staves(midi_path, split_pitch=60):
 
2
  from music21 import converter, musicxml, stream, note, chord, clef
3
  import verovio
4
 
 
5
  class Format_Converter:
6
 
7
  def midi_to_musicxml_string_two_staves(midi_path, split_pitch=60):
legacy/__init__.py ADDED
File without changes
app_in_progress.py → legacy/app_in_progress.py RENAMED
@@ -139,7 +139,7 @@ class HexachordApp:
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")
 
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")
essai.py → legacy/essai.py RENAMED
File without changes
example.mei → legacy/example.mei RENAMED
File without changes
old_app.py → legacy/old_app.py RENAMED
File without changes
legacy/working_app_one_score.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
10
+ from pydub import AudioSegment
11
+ import hexachords
12
+ from format_conversions import Format_Converter
13
+ import verovio
14
+
15
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
16
+
17
+ def get_verovio_resource_path():
18
+ for path in verovio.__path__:
19
+ candidate = os.path.join(path, "data")
20
+ if os.path.exists(candidate):
21
+ return candidate
22
+ return None
23
+
24
+ # Initialize the Verovio toolkit with rendering options
25
+ tk = verovio.toolkit()
26
+ resource_path = get_verovio_resource_path()
27
+ print(f"resource path: {os.listdir(resource_path)}")
28
+ print("[Debug] Using resource path:", resource_path)
29
+ if resource_path:
30
+ try:
31
+ tk.setResourcePath(resource_path)
32
+ except Exception as e:
33
+ print("[Error] Failed to set resourcePath:", e)
34
+ tk.setOptions({
35
+ "adjustPageWidth": True,
36
+ "header": 'none', # This disables the rendering of the title
37
+ "scale": 70,
38
+ "adjustPageHeight": True,
39
+ "landscape": False,
40
+ })
41
+ print(tk.getOptions())
42
+
43
+ class HexachordApp:
44
+
45
+ def __init__(self):
46
+ self._hexachord = hexachords.Hexachord()
47
+ self._hexachord_base_sequence = None
48
+ self.ui = None
49
+ self.on_huggingface = "HUGGINGFACE_SPACE" in os.environ
50
+
51
+ def is_fsynth_installed(self):
52
+ try:
53
+ subprocess.run(["fluidsynth", "--version"], check=True)
54
+ print('fluidsynth is installed')
55
+ return True
56
+ except Exception:
57
+ print('fluidsynth is NOT installed')
58
+ return False
59
+
60
+ def generate_chords(self, note_names, itvl):
61
+ # Placeholder for your actual chord generation function
62
+ # Assuming hexachord is a list of MIDI note numbers
63
+ interval_21 = 'P5'
64
+ if itvl == 'fourth':
65
+ interval_21 = 'P4'
66
+ self._hexachord_base_sequence = self._hexachord.generate_chord_sequence(note_names, intrvl=interval_21)
67
+ return self._hexachord_base_sequence
68
+
69
+ def generate_realizations(self):
70
+ # returns 3 triples of midipath, piano roll, audio player
71
+ reals = self._hexachord.generate_3_chords_realizations(self._hexachord_base_sequence)
72
+ midi_path1 = self.create_midi(reals[0], "real1.mid")
73
+ piano_roll_path1 = self.generate_piano_roll(reals[0], "real1.png")
74
+ audio_path1 = self.convert_midi_to_audio(midi_path1, "real1")
75
+ midi_path2 = self.create_midi(reals[1], "real2.mid")
76
+ piano_roll_path2 = self.generate_piano_roll(reals[1], "real2.png")
77
+ audio_path2 = self.convert_midi_to_audio(midi_path2, "real2")
78
+ midi_path3 = self.create_midi(reals[2], "real3.mid")
79
+ piano_roll_path3 = self.generate_piano_roll(reals[2], "real3.png")
80
+ audio_path3 = self.convert_midi_to_audio(midi_path3, "real3")
81
+ return midi_path1, piano_roll_path1, audio_path1, midi_path2, piano_roll_path2, audio_path2, midi_path3, piano_roll_path3, audio_path3
82
+
83
+ def create_midi(self, chords, file_name):
84
+ mid = MidiFile()
85
+ track = MidiTrack()
86
+ mid.tracks.append(track)
87
+ delta_time = 480 * 4
88
+ for i_chord, chord in enumerate(chords):
89
+ for i, note in enumerate(chord):
90
+ if i == 0 and i_chord != 0:
91
+ track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=1))
92
+ # track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=0))
93
+ else:
94
+ track.append(Message('note_on', note=note.pitch.midi, velocity=64, time=0))
95
+ for i, note in enumerate(chord):
96
+ if i==0:
97
+ track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=delta_time - 1))
98
+ # track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=delta_time))
99
+ else:
100
+ track.append(Message('note_off', note=note.pitch.midi, velocity=0, time=0))
101
+ midi_path = os.path.join(BASE_DIR, file_name)
102
+ mid.save(midi_path)
103
+ return midi_path
104
+
105
+
106
+ def convert_midi_to_audio(self, midi_path, file_name):
107
+ if not shutil.which("fluidsynth"):
108
+ try:
109
+ subprocess.run(["apt-get", "update"], check=True)
110
+ subprocess.run(["apt-get", "install", "-y", "fluidsynth"], check=True)
111
+ except Exception as e:
112
+ return f"Error installing Fluidsynth: {str(e)}"
113
+ wav_path = os.path.join(BASE_DIR, file_name + ".wav")
114
+ mp3_path = os.path.join(BASE_DIR, file_name + ".mp3")
115
+ soundfont_path = os.path.join(BASE_DIR, "soundfont.sf2")
116
+ if not os.path.exists(soundfont_path):
117
+ return "Error: SoundFont file not found. Please provide a valid .sf2 file."
118
+ try:
119
+ subprocess.run(["fluidsynth", "-ni", soundfont_path, midi_path, "-F", wav_path, "-r", "44100"], check=True)
120
+ AudioSegment.converter = "ffmpeg"
121
+ audio = AudioSegment.from_wav(wav_path)
122
+ audio.export(mp3_path, format="mp3")
123
+ return mp3_path
124
+ except Exception as e:
125
+ return f"Error converting MIDI to audio: {str(e)}"
126
+
127
+ def generate_svg(self, midi_file, output_file_name):
128
+ return Format_Converter().midi_to_svg_file(tk, midi_file, output_file_name)
129
+
130
+ def generate_piano_roll(self, chords, label):
131
+ fig, ax = plt.subplots(figsize=(8, 4))
132
+
133
+ for i, chord in enumerate(chords):
134
+ for note in chord:
135
+ ax.broken_barh([(i * 1, 0.8)], (note.pitch.midi - 0.4, 0.8), facecolors='blue')
136
+
137
+ ax.set_xlabel("Chord Progression")
138
+ ax.set_ylabel("MIDI Note Number")
139
+ min_min_chords = 128
140
+ max_max_chords = 0
141
+ for ch in chords:
142
+ for note in ch:
143
+ if note.pitch.midi < min_min_chords:
144
+ min_min_chords = note.pitch.midi
145
+ if note.pitch.midi > max_max_chords:
146
+ max_max_chords = note.pitch.midi
147
+ ax.set_yticks(range(min_min_chords, max_max_chords + 1, 2))
148
+ ax.set_xticks(range(len(chords)))
149
+ ax.set_xticklabels([f"Chord {i + 1}" for i in range(len(chords))])
150
+
151
+ plt.grid(True, linestyle='--', alpha=0.5)
152
+ plt.savefig(label)
153
+ return label
154
+
155
+ def launch_score_editor(self, midi_path):
156
+ try:
157
+ score = converter.parse(midi_path)
158
+ score.show('musicxml')
159
+ return "Opened MIDI file in the default score editor!"
160
+ except Exception as e:
161
+ return f"Error opening score editor: {str(e)}"
162
+
163
+ def build_octave_dependent_notes_from_string(self, note_string):
164
+ start_octave = 3
165
+ notes = []
166
+ previous_note = None
167
+ for nn in note_string.split():
168
+ n = note.Note(nn)
169
+ n.octave = start_octave
170
+ if previous_note is not None and n.pitch.midi < previous_note.pitch.midi:
171
+ n.octave = n.octave + 1
172
+ start_octave += 1
173
+ notes.append(n)
174
+ previous_note = n
175
+ return notes
176
+
177
+ def process_hexachord(self, hexachord_str, itvl):
178
+ try:
179
+ # notes = [note.Note(n) for n in hexachord_str.split()]
180
+ notes = self.build_octave_dependent_notes_from_string(hexachord_str)
181
+ if len(notes) != 6 or len(set(notes)) != 6:
182
+ return "Please enter exactly 6 unique MIDI note numbers."
183
+ except ValueError:
184
+ return "Invalid input. Enter 6 MIDI note numbers separated by spaces."
185
+ chords = self.generate_chords(notes, itvl)
186
+ midi_path = self.create_midi(chords, "base_chords.mid")
187
+ # piano_roll_path = self.generate_piano_roll(chords, "base_chords.png")
188
+ score_path = self.generate_svg (midi_path, "score_base_chords.svg")
189
+ audio_path = self.convert_midi_to_audio(midi_path, "base_chords")
190
+
191
+ # return midi_path, piano_roll_path, audio_path
192
+ return midi_path, score_path, audio_path
193
+
194
+ def render(self):
195
+ with gr.Blocks() as ui:
196
+ gr.Markdown("# Hexachord-based Chord Generator")
197
+ with gr.Tabs():
198
+ with gr.TabItem("Hexachord Generator"):
199
+ with gr.Row():
200
+ hexachord_selector = gr.Dropdown(label="Select Known Hexachord",
201
+ choices=self.get_known_hexachords_choice(), value=None, interactive=True)
202
+ hexachord_input = gr.Textbox(
203
+ label="Enter 6 notes (pitchclass plus octave, separated by spaces)",
204
+ value="C D E G A B",
205
+ interactive = True
206
+ )
207
+ interval_switch = gr.Radio(
208
+ choices=["fourth", "fifth"],
209
+ label="Select Interval",
210
+ value="fifth"
211
+ )
212
+ generate_button = gr.Button("Generate Chords")
213
+ with gr.Row():
214
+ midi_output = gr.File(label="Download MIDI File", scale=1)
215
+ # piano_roll_output = gr.Image(label="Piano Roll Visualization")
216
+ score_output = gr.Image(label="Score Visualization", scale=3)
217
+ audio_output = gr.Audio(label="Play Generated Chords", value=None, interactive=False, scale=3)
218
+ hexachord_selector.change(
219
+ fn=self.get_selected_hexachord,
220
+ inputs=[hexachord_selector],
221
+ outputs=[hexachord_input]
222
+ )
223
+ generate_button.click(
224
+ fn=self.process_hexachord,
225
+ inputs=[hexachord_input, interval_switch],
226
+ # outputs=[midi_output, piano_roll_output, audio_output]
227
+ outputs = [midi_output, score_output, audio_output]
228
+ )
229
+ # Pressing Enter in the textbox also triggers processing
230
+ hexachord_input.submit(
231
+ fn=self.process_hexachord,
232
+ inputs=[hexachord_input, interval_switch],
233
+ outputs=[midi_output, score_output, audio_output]
234
+ )
235
+
236
+ with gr.TabItem("Alternative Chord Realizations"):
237
+ gr.Markdown("Alternative Chord Realizations")
238
+
239
+ realization_button = gr.Button("Generate Alternative Realizations")
240
+ realization_outputs = []
241
+
242
+ for i in range(3): # Three alternative realizations
243
+ with gr.Group():
244
+ gr.Markdown(f"#### Realization {i + 1}")
245
+ midi_output = gr.File(label="Download MIDI File")
246
+ piano_roll = gr.Image(label=f"Piano Roll {i + 1}")
247
+ audio_player = gr.Audio(label=f"Play Chords {i + 1}", interactive=False)
248
+ realization_outputs.append((midi_output, piano_roll, audio_player))
249
+
250
+ # Clicking the button triggers realization generation
251
+ realization_button.click(
252
+ fn=self.generate_realizations,
253
+ inputs=[],
254
+ outputs=[output for trip in realization_outputs for output in trip]
255
+ )
256
+
257
+ with gr.TabItem("Settings"):
258
+ gr.Markdown("### Configuration Options")
259
+ setting_1 = gr.Checkbox(label="Enable Advanced Mode")
260
+ setting_2 = gr.Slider(0, 100, label="Complexity Level")
261
+ self.ui = ui
262
+
263
+ def get_known_hexachords_choice(self):
264
+ return self._hexachord.known_hexachords
265
+
266
+ def get_selected_hexachord(self, x):
267
+ # lambda x: {"Hexachord 1": "C3 D3 E3 G3 A3 B3", "Hexachord 2": "D3 E3 F3 A3 B3 C4",
268
+ # "Hexachord 3": "E3 G3 A3 C4 D4 F4"}.get(x, "")
269
+ item = x[x.index('['):x.index(']')+1]
270
+ int_array = np.array(ast.literal_eval(item))
271
+ hexa_string = ''
272
+ start_note = note.Note('C3')
273
+ for i in int_array:
274
+ add_note = start_note.transpose(int(i))
275
+ hexa_string = hexa_string + ' ' + add_note.nameWithOctave
276
+ return hexa_string
277
+
278
+
279
+ def launch_app():
280
+ hex = HexachordApp()
281
+ hex.is_fsynth_installed()
282
+ hex.render()
283
+ if hex.on_huggingface:
284
+ hex.ui.launch(server_name="0.0.0.0", server_port=7860, share=True)
285
+ else:
286
+ hex.ui.launch(server_name="0.0.0.0", server_port=7860)
287
+
288
+
289
+ launch_app()
290
+
output.mei DELETED
@@ -1,126 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.1">
3
- <meiHead xml:id="maqjp63">
4
- <fileDesc xml:id="f7jp7g9">
5
- <titleStmt xml:id="t8iiqsi">
6
- <title>Music21 Fragment</title>
7
- <respStmt>
8
- <persName role="composer">Music21</persName>
9
- </respStmt>
10
- </titleStmt>
11
- <pubStmt xml:id="phleh0y">
12
- <date isodate="2025-03-19" type="encoding-date">2025-03-19</date>
13
- </pubStmt>
14
- </fileDesc>
15
- <encodingDesc xml:id="eo6csxx">
16
- <appInfo xml:id="a1c3w7ux">
17
- <application xml:id="a164khur" isodate="2025-03-19T22:43:27" version="5.1.0-8c3069d">
18
- <name xml:id="n1s9x4c2">Verovio</name>
19
- <p xml:id="pyiw3j3">Transcoded from MusicXML</p>
20
- </application>
21
- </appInfo>
22
- </encodingDesc>
23
- </meiHead>
24
- <music>
25
- <body>
26
- <mdiv xml:id="mbezojg">
27
- <score xml:id="s132ublt">
28
- <scoreDef xml:id="s18pzn4w">
29
- <staffGrp xml:id="sx01ah5">
30
- <staffDef xml:id="P54fd32f1e7813de141d5e94b173800dd" n="1" lines="5" ppq="10080">
31
- <clef xml:id="cuxwkmr" shape="G" line="2" />
32
- <meterSig xml:id="m3eod4t" count="4" unit="4" />
33
- </staffDef>
34
- </staffGrp>
35
- </scoreDef>
36
- <section xml:id="s14syw2w">
37
- <measure xml:id="m1lvm0yy" n="1">
38
- <staff xml:id="s1b6zc5j" n="1">
39
- <layer xml:id="l1csnxpz" n="1">
40
- <chord xml:id="ck94yh6" dur.ppq="40320" dur="1">
41
- <note xml:id="ngp84u5" oct="3" pname="c" />
42
- <note xml:id="nde028f" oct="3" pname="g" />
43
- <note xml:id="n1yg2sol" oct="4" pname="d" />
44
- <note xml:id="nonpwmg" oct="4" pname="a" />
45
- <note xml:id="n1n8asow" oct="5" pname="e" />
46
- <note xml:id="n1ldxmuk" oct="5" pname="b" />
47
- </chord>
48
- </layer>
49
- </staff>
50
- </measure>
51
- <measure xml:id="m14inp6s" n="2">
52
- <staff xml:id="s1lhc1b1" n="1">
53
- <layer xml:id="lsqjdvd" n="1">
54
- <chord xml:id="ciuhgaf" dur.ppq="40320" dur="1">
55
- <note xml:id="ne6ljq2" oct="3" pname="d" />
56
- <note xml:id="n1kvdtq9" oct="3" pname="a" />
57
- <note xml:id="n1xr0clo" oct="4" pname="e" />
58
- <note xml:id="nrbjlqa" oct="4" pname="b" />
59
- <note xml:id="n28mjc0" oct="6" pname="c" />
60
- <note xml:id="nlw8k83" oct="6" pname="g" />
61
- </chord>
62
- </layer>
63
- </staff>
64
- </measure>
65
- <measure xml:id="m1trdq8d" n="3">
66
- <staff xml:id="sxxezgl" n="1">
67
- <layer xml:id="l1hinj87" n="1">
68
- <chord xml:id="c2m0w8t" dur.ppq="40320" dur="1">
69
- <note xml:id="nsgm2a0" oct="3" pname="e" />
70
- <note xml:id="n1wvcozg" oct="3" pname="b" />
71
- <note xml:id="n1bc7t89" oct="5" pname="c" />
72
- <note xml:id="n1gsv9bl" oct="5" pname="g" />
73
- <note xml:id="n1slsoot" oct="6" pname="d" />
74
- <note xml:id="np4pub2" oct="6" pname="a" />
75
- </chord>
76
- </layer>
77
- </staff>
78
- </measure>
79
- <measure xml:id="mb4850f" n="4">
80
- <staff xml:id="s1didadp" n="1">
81
- <layer xml:id="l1vc3d6s" n="1">
82
- <chord xml:id="c1271to" dur.ppq="40320" dur="1">
83
- <note xml:id="nfhf7lb" oct="3" pname="g" />
84
- <note xml:id="n1hsnwjo" oct="4" pname="d" />
85
- <note xml:id="n1ea78x5" oct="4" pname="a" />
86
- <note xml:id="nonhxoz" oct="5" pname="e" />
87
- <note xml:id="n2tp0ge" oct="5" pname="b" />
88
- <note xml:id="n1keg9ge" oct="7" pname="c" />
89
- </chord>
90
- </layer>
91
- </staff>
92
- </measure>
93
- <measure xml:id="m1cfva6" n="5">
94
- <staff xml:id="sbphnat" n="1">
95
- <layer xml:id="lulnle2" n="1">
96
- <chord xml:id="c1hbxjwb" dur.ppq="40320" dur="1">
97
- <note xml:id="nwzeofb" oct="3" pname="a" />
98
- <note xml:id="n56onxs" oct="4" pname="e" />
99
- <note xml:id="n77tugx" oct="4" pname="b" />
100
- <note xml:id="n15a2loc" oct="6" pname="c" />
101
- <note xml:id="n1xwblav" oct="6" pname="g" />
102
- <note xml:id="n1y7kzmd" oct="7" pname="d" />
103
- </chord>
104
- </layer>
105
- </staff>
106
- </measure>
107
- <measure xml:id="m959oif" right="end" n="6">
108
- <staff xml:id="sxe147o" n="1">
109
- <layer xml:id="l1n1reed" n="1">
110
- <chord xml:id="civ4vs9" dur.ppq="40320" dur="1">
111
- <note xml:id="n1q55ord" oct="3" pname="b" />
112
- <note xml:id="n6a3qk" oct="5" pname="c" />
113
- <note xml:id="n1fetjh5" oct="5" pname="g" />
114
- <note xml:id="nfc1bgg" oct="6" pname="d" />
115
- <note xml:id="nk15vap" oct="6" pname="a" />
116
- <note xml:id="n1939asj" oct="7" pname="e" />
117
- </chord>
118
- </layer>
119
- </staff>
120
- </measure>
121
- </section>
122
- </score>
123
- </mdiv>
124
- </body>
125
- </music>
126
- </mei>