awacke1 commited on
Commit
e8f8f3c
·
verified ·
1 Parent(s): 200c076

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -123
app.py CHANGED
@@ -1,17 +1,20 @@
1
  import random
 
 
 
 
2
  import gradio as gr
3
  import numpy as np
4
- import rtmidi
5
  import MIDI
6
  import base64
7
  import io
8
- import os
9
  from huggingface_hub import hf_hub_download
10
  from midi_synthesizer import MidiSynthesizer
11
 
12
  MAX_SEED = np.iinfo(np.int32).max
 
13
 
14
- # Example song data (simplified from original)
15
  SONG_DATA = {
16
  "title": "Do You Believe in Love",
17
  "progression": ["G", "D", "Em", "C"],
@@ -22,42 +25,95 @@ class MIDIDeviceManager:
22
  def __init__(self):
23
  self.midiout = rtmidi.MidiOut()
24
  self.midiin = rtmidi.MidiIn()
25
-
26
- def get_available_devices(self):
27
- return self.midiout.get_ports() or ["No MIDI devices"]
 
 
 
28
 
29
  def get_device_info(self):
30
- devices = self.get_available_devices()
31
- return "\n".join([f"Port {i}: {name}" for i, name in enumerate(devices)]) if devices else "No MIDI devices detected"
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  class MIDIManager:
34
  def __init__(self):
35
  self.soundfont_path = hf_hub_download(repo_id="skytnt/midi-model", filename="soundfont.sf2")
36
  self.synthesizer = MidiSynthesizer(self.soundfont_path)
37
  self.loaded_midi = {} # midi_id: (file_path, midi_obj)
38
- self.modified_files = []
39
  self.is_playing = False
 
 
 
40
  self.example_files = self.load_example_midis()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  def load_example_midis(self):
43
  examples = {}
44
- example_dir = "examples"
45
- if os.path.exists(example_dir):
46
- for file in os.listdir(example_dir):
47
- if file.endswith(".mid") or file.endswith(".midi"):
48
- midi_id = f"example_{len(examples)}"
49
- file_path = os.path.join(example_dir, file)
50
- examples[midi_id] = (file_path, MIDI.load(file_path))
51
- if not examples:
52
- midi = MIDI.MIDIFile(1)
53
- midi.addTrack()
54
- midi.addNote(0, 0, 60, 0, 100, 100) # Default C4
55
- examples["example_0"] = ("Simple C4.mid", midi)
 
 
 
 
 
56
  return examples
57
 
58
  def load_midi(self, file_path):
59
  midi = MIDI.load(file_path)
60
- midi_id = f"midi_{len(self.loaded_midi) - len(self.example_files)}"
61
  self.loaded_midi[midi_id] = (file_path, midi)
62
  return midi_id
63
 
@@ -74,7 +130,7 @@ class MIDIManager:
74
 
75
  def generate_variation(self, midi_id, length_factor=2, variation=0.3):
76
  if midi_id not in self.loaded_midi:
77
- return None
78
  _, midi = self.loaded_midi[midi_id]
79
  notes, instruments = self.extract_notes_and_instruments(midi)
80
  new_notes = []
@@ -94,11 +150,22 @@ class MIDIManager:
94
  for note, vel, time in new_notes:
95
  new_midi.addNote(i, 0, note, time, 100, vel)
96
 
97
- output = io.BytesIO()
98
- new_midi.writeFile(output)
99
- midi_data = base64.b64encode(output.getvalue()).decode('utf-8')
100
- self.modified_files.append(midi_data)
101
- return midi_data
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  def apply_synth_effect(self, midi_data, effect, intensity):
104
  midi = MIDI.load(io.BytesIO(base64.b64decode(midi_data)))
@@ -107,11 +174,22 @@ class MIDIManager:
107
  for track in midi.tracks:
108
  for event in track.events:
109
  event.time = int(event.time * factor)
110
- output = io.BytesIO()
111
- midi.writeFile(output)
112
- midi_data = base64.b64encode(output.getvalue()).decode('utf-8')
113
- self.modified_files.append(midi_data)
114
- return midi_data
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  def play_with_loop(self, midi_data):
117
  self.is_playing = True
@@ -124,101 +202,131 @@ class MIDIManager:
124
  self.is_playing = False
125
  return "Stopping..."
126
 
127
- midi_manager = MIDIDeviceManager()
128
- midi_processor = MIDIManager()
129
-
130
  def create_download_list():
131
  html = "<h3>Downloads</h3><ul>"
132
- for i, data in enumerate(midi_processor.modified_files):
133
- html += f'<li><a href="data:audio/midi;base64,{data}" download="midi_{i}.mid">MIDI {i}</a></li>'
 
 
 
134
  html += "</ul>"
135
  return html
136
 
137
  def get_midi_choices():
138
  return [(os.path.basename(path), midi_id) for midi_id, (path, _) in midi_processor.loaded_midi.items()]
139
 
140
- with gr.Blocks(theme=gr.themes.Soft()) as app:
141
- gr.Markdown("<h1>🎵 MIDI Composer 🎵</h1>")
142
-
143
- with gr.Tabs():
144
- # Tab 1: Upload MIDI
145
- with gr.Tab("Upload MIDI"):
146
- midi_files = gr.File(label="Upload MIDI Files", file_count="multiple")
147
- loaded_display = gr.HTML(value="No files loaded")
148
- output = gr.Audio(label="Generated Preview", type="bytes", autoplay=True)
149
-
150
- def load_and_generate(files):
151
- html = "<h3>Loaded Files</h3>"
152
- midi_data = None
153
- for file in files or []:
154
- midi_id = midi_processor.load_midi(file.name)
155
- html += f"<div>{file.name} <button onclick=\"remove_midi('{midi_id}')\">X</button></div>"
156
- midi_data = midi_processor.generate_variation(midi_id) # Auto-generate
157
- return html, (io.BytesIO(base64.b64decode(midi_data)) if midi_data else None), get_midi_choices()
158
-
159
- midi_files.change(load_and_generate, inputs=[midi_files],
160
- outputs=[loaded_display, output, gr.State(get_midi_choices())])
161
-
162
- # Tab 2: Generate & Perform
163
- with gr.Tab("Generate & Perform"):
164
- midi_select = gr.Dropdown(label="Select MIDI", choices=get_midi_choices(), value=None)
165
- length_factor = gr.Slider(1, 5, value=2, step=1, label="Length Factor")
166
- variation = gr.Slider(0, 1, value=0.3, label="Variation")
167
- generate_btn = gr.Button("Generate")
168
- effect = gr.Radio(["tempo"], label="Synth Effect", value="tempo")
169
- intensity = gr.Slider(0, 1, value=0.5, label="Effect Intensity")
170
- apply_btn = gr.Button("Apply Effect")
171
- stop_btn = gr.Button("Stop Playback")
172
- output = gr.Audio(label="Preview", type="bytes", autoplay=True)
173
- status = gr.Textbox(label="Status", value="Ready")
174
- midi_device = gr.Dropdown(label="MIDI Output Device", choices=midi_manager.get_available_devices(), type="index")
175
- tempo = gr.Slider(label="Tempo (BPM)", minimum=40, maximum=200, value=120, step=1)
176
-
177
- def update_dropdown(choices):
178
- return gr.update(choices=choices)
179
-
180
- gr.State(get_midi_choices()).change(update_dropdown, inputs=[gr.State()], outputs=[midi_select])
181
-
182
- def generate(midi_id, length, var):
183
- if not midi_id:
184
- return None, "Select a MIDI file"
185
- midi_data = midi_processor.generate_variation(midi_id, length, var)
186
- midi_processor.play_with_loop(midi_data)
187
- return io.BytesIO(base64.b64decode(midi_data)), "Playing"
188
-
189
- def apply_effect(midi_data, fx, inten):
190
- if not midi_data:
191
- return None, "Generate a MIDI first"
192
- new_data = midi_processor.apply_synth_effect(midi_data.decode('utf-8'), fx, inten)
193
- midi_processor.play_with_loop(new_data)
194
- return io.BytesIO(base64.b64decode(new_data)), "Playing"
195
-
196
- generate_btn.click(generate, inputs=[midi_select, length_factor, variation],
197
- outputs=[output, status])
198
- apply_btn.click(apply_effect, inputs=[output, effect, intensity],
199
- outputs=[output, status])
200
- stop_btn.click(midi_processor.stop_playback, inputs=None, outputs=[status])
201
-
202
- # Tab 3: Downloads
203
- with gr.Tab("Downloads"):
204
- downloads = gr.HTML(value="No files yet")
205
- def update_downloads(*args):
206
- return create_download_list()
207
- gr.on(triggers=[midi_files.change, generate_btn.click, apply_btn.click],
208
- fn=update_downloads, inputs=None, outputs=[downloads])
209
-
210
- gr.Markdown("""
211
- <div style='text-align: center; margin-top: 20px;'>
212
- <img src='https://huggingface.co/front/assets/huggingface_logo-noborder.svg' alt='Hugging Face Logo' style='width: 50px;'><br>
213
- <strong>Hugging Face</strong><br>
214
- <a href='https://huggingface.co/models'>Models</a> |
215
- <a href='https://huggingface.co/datasets'>Datasets</a> |
216
- <a href='https://huggingface.co/spaces'>Spaces</a> |
217
- <a href='https://huggingface.co/posts'>Posts</a> |
218
- <a href='https://huggingface.co/docs'>Docs</a> |
219
- <a href='https://huggingface.co/enterprise'>Enterprise</a> |
220
- <a href='https://huggingface.co/pricing'>Pricing</a>
221
- </div>
222
- """)
223
-
224
- app.queue().launch(inbrowser=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import random
2
+ import argparse
3
+ import os
4
+ import glob
5
+ import rtmidi
6
  import gradio as gr
7
  import numpy as np
 
8
  import MIDI
9
  import base64
10
  import io
11
+ import soundfile as sf # Placeholder for audio rendering
12
  from huggingface_hub import hf_hub_download
13
  from midi_synthesizer import MidiSynthesizer
14
 
15
  MAX_SEED = np.iinfo(np.int32).max
16
+ in_space = os.getenv("SYSTEM") == "spaces"
17
 
 
18
  SONG_DATA = {
19
  "title": "Do You Believe in Love",
20
  "progression": ["G", "D", "Em", "C"],
 
25
  def __init__(self):
26
  self.midiout = rtmidi.MidiOut()
27
  self.midiin = rtmidi.MidiIn()
28
+
29
+ def get_output_devices(self):
30
+ return self.midiout.get_ports() or ["No MIDI output devices"]
31
+
32
+ def get_input_devices(self):
33
+ return self.midiin.get_ports() or ["No MIDI input devices"]
34
 
35
  def get_device_info(self):
36
+ out_devices = self.get_output_devices()
37
+ in_devices = self.get_input_devices()
38
+ out_info = "\n".join([f"Out Port {i}: {name}" for i, name in enumerate(out_devices)]) if out_devices else "No MIDI output devices detected"
39
+ in_info = "\n".join([f"In Port {i}: {name}" for i, name in enumerate(in_devices)]) if in_devices else "No MIDI input devices detected"
40
+ return f"Output Devices:\n{out_info}\n\nInput Devices:\n{in_info}"
41
+
42
+ def close(self):
43
+ if self.midiout.is_port_open():
44
+ self.midiout.close_port()
45
+ if self.midiin.is_port_open():
46
+ self.midiin.close_port()
47
+ del self.midiout
48
+ del self.midiin
49
 
50
  class MIDIManager:
51
  def __init__(self):
52
  self.soundfont_path = hf_hub_download(repo_id="skytnt/midi-model", filename="soundfont.sf2")
53
  self.synthesizer = MidiSynthesizer(self.soundfont_path)
54
  self.loaded_midi = {} # midi_id: (file_path, midi_obj)
55
+ self.modified_files = [] # Stores (midi_base64, audio_base64) tuples
56
  self.is_playing = False
57
+ self.instruments = self.random_instrument_set()
58
+ self.drum_beat = self.create_drum_beat()
59
+ self.starter_midi = self.create_starter_midi()
60
  self.example_files = self.load_example_midis()
61
+ self.loaded_midi["starter"] = ("Starter MIDI", self.starter_midi)
62
+ self.preload_default_midi()
63
+
64
+ def random_instrument_set(self):
65
+ instrument_pool = [0, 24, 32, 48] # Piano, Guitar, Bass, Strings
66
+ return random.sample(instrument_pool, 4)
67
+
68
+ def create_drum_beat(self):
69
+ return [(36, 100, 0), (42, 80, 50), (38, 90, 100), (42, 80, 150)] # Kick, hi-hat, snare, hi-hat
70
+
71
+ def create_starter_midi(self):
72
+ midi = MIDI.MIDIFile(5) # 4 instruments + 1 drum track
73
+ for i, inst in enumerate(self.instruments):
74
+ midi.addTrack()
75
+ midi.addProgramChange(i, 0, 0, inst)
76
+ for t in range(0, 400, 100):
77
+ note = random.randint(60, 84) # C4 to C6
78
+ midi.addNote(i, 0, note, t, 100, 100)
79
+ midi.addTrack()
80
+ for note, vel, time in self.drum_beat:
81
+ midi.addNote(4, 9, note, time, 100, vel)
82
+ return midi
83
+
84
+ def preload_default_midi(self):
85
+ default_path = "default.mid"
86
+ if os.path.exists(default_path):
87
+ midi_id = "default"
88
+ midi = MIDI.load(default_path)
89
+ self.loaded_midi[midi_id] = (default_path, midi)
90
+ midi_data, audio_data = self.generate_variation(midi_id)
91
+ self.play_with_loop(midi_data)
92
 
93
  def load_example_midis(self):
94
  examples = {}
95
+ for file_path in glob.glob("*.mid") + glob.glob("*.midi"):
96
+ if file_path == "default.mid":
97
+ continue
98
+ midi_id = f"example_{len(examples)}"
99
+ midi = MIDI.load(file_path)
100
+ new_midi = MIDI.MIDIFile(5)
101
+ notes, _ = self.extract_notes_and_instruments(midi)
102
+ for i, inst in enumerate(self.instruments):
103
+ new_midi.addTrack()
104
+ new_midi.addProgramChange(i, 0, 0, inst)
105
+ for note, vel, time in notes:
106
+ new_midi.addNote(i, 0, note, time, 100, vel)
107
+ new_midi.addTrack()
108
+ for note, vel, time in self.drum_beat:
109
+ new_midi.addNote(4, 9, note, time, 100, vel)
110
+ examples[midi_id] = (file_path, new_midi)
111
+ self.loaded_midi.update(examples)
112
  return examples
113
 
114
  def load_midi(self, file_path):
115
  midi = MIDI.load(file_path)
116
+ midi_id = f"midi_{len(self.loaded_midi) - len(self.example_files) - 1}"
117
  self.loaded_midi[midi_id] = (file_path, midi)
118
  return midi_id
119
 
 
130
 
131
  def generate_variation(self, midi_id, length_factor=2, variation=0.3):
132
  if midi_id not in self.loaded_midi:
133
+ return None, None
134
  _, midi = self.loaded_midi[midi_id]
135
  notes, instruments = self.extract_notes_and_instruments(midi)
136
  new_notes = []
 
150
  for note, vel, time in new_notes:
151
  new_midi.addNote(i, 0, note, time, 100, vel)
152
 
153
+ midi_output = io.BytesIO()
154
+ new_midi.writeFile(midi_output)
155
+ midi_data = base64.b64encode(midi_output.getvalue()).decode('utf-8')
156
+
157
+ temp_midi = 'temp.mid'
158
+ with open(temp_midi, 'wb') as f:
159
+ f.write(midi_output.getvalue())
160
+ audio_output = io.BytesIO()
161
+ # Placeholder for audio rendering; needs fluidsynth or similar
162
+ self.synthesizer.play_midi(new_midi)
163
+ audio_data = None # See Notes below
164
+ if os.path.exists(temp_midi):
165
+ os.remove(temp_midi)
166
+
167
+ self.modified_files.append((midi_data, audio_data))
168
+ return midi_data, audio_data
169
 
170
  def apply_synth_effect(self, midi_data, effect, intensity):
171
  midi = MIDI.load(io.BytesIO(base64.b64decode(midi_data)))
 
174
  for track in midi.tracks:
175
  for event in track.events:
176
  event.time = int(event.time * factor)
177
+
178
+ midi_output = io.BytesIO()
179
+ midi.writeFile(midi_output)
180
+ midi_data = base64.b64encode(midi_output.getvalue()).decode('utf-8')
181
+
182
+ temp_midi = 'temp.mid'
183
+ with open(temp_midi, 'wb') as f:
184
+ f.write(midi_output.getvalue())
185
+ audio_output = io.BytesIO()
186
+ self.synthesizer.play_midi(midi)
187
+ audio_data = None # Placeholder
188
+ if os.path.exists(temp_midi):
189
+ os.remove(temp_midi)
190
+
191
+ self.modified_files.append((midi_data, audio_data))
192
+ return midi_data, audio_data
193
 
194
  def play_with_loop(self, midi_data):
195
  self.is_playing = True
 
202
  self.is_playing = False
203
  return "Stopping..."
204
 
 
 
 
205
  def create_download_list():
206
  html = "<h3>Downloads</h3><ul>"
207
+ for i, (midi_data, audio_data) in enumerate(midi_processor.modified_files):
208
+ html += f'<li><a href="data:audio/midi;base64,{midi_data}" download="midi_{i}.mid">MIDI {i}</a>'
209
+ if audio_data:
210
+ html += f' | <a href="data:audio/wav;base64,{audio_data}" download="audio_{i}.wav">Audio {i}</a>'
211
+ html += '</li>'
212
  html += "</ul>"
213
  return html
214
 
215
  def get_midi_choices():
216
  return [(os.path.basename(path), midi_id) for midi_id, (path, _) in midi_processor.loaded_midi.items()]
217
 
218
+ if __name__ == "__main__":
219
+ parser = argparse.ArgumentParser()
220
+ parser.add_argument("--port", type=int, default=7860)
221
+ parser.add_argument("--share", action="store_true")
222
+ parser.add_argument("--batch", type=int, default=1)
223
+ opt = parser.parse_args()
224
+
225
+ midi_manager = MIDIDeviceManager()
226
+ midi_processor = MIDIManager()
227
+
228
+ with gr.Blocks(theme=gr.themes.Soft()) as app:
229
+ gr.Markdown("<h1>🎵 MIDI Composer 🎵</h1>")
230
+
231
+ with gr.Tabs():
232
+ # Tab 1: MIDI Prompt (Main Tab)
233
+ with gr.Tab("MIDI Prompt"):
234
+ midi_files = gr.File(label="Upload MIDI Files", file_count="multiple")
235
+ loaded_display = gr.HTML(value="No files loaded")
236
+ output = gr.Audio(label="Generated Preview", type="bytes", autoplay=True)
237
+
238
+ def load_and_generate(files):
239
+ html = "<h3>Loaded Files</h3>"
240
+ midi_data = None
241
+ for file in files or []:
242
+ midi_id = midi_processor.load_midi(file.name)
243
+ html += f"<div>{file.name} <button onclick=\"remove_midi('{midi_id}')\">X</button></div>"
244
+ midi_data, _ = midi_processor.generate_variation(midi_id)
245
+ return html, (io.BytesIO(base64.b64decode(midi_data)) if midi_data else None), get_midi_choices(), create_download_list()
246
+
247
+ midi_files.change(load_and_generate, inputs=[midi_files],
248
+ outputs=[loaded_display, output, gr.State(get_midi_choices()), "downloads"])
249
+
250
+ # Tab 2: Examples
251
+ with gr.Tab("Examples"):
252
+ example_select = gr.Dropdown(label="Select Example", choices=get_midi_choices(), value=None)
253
+ example_output = gr.Audio(label="Example Preview", type="bytes", autoplay=True)
254
+
255
+ def load_example(midi_id):
256
+ if not midi_id:
257
+ return None
258
+ midi_data, audio_data = midi_processor.generate_variation(midi_id)
259
+ midi_processor.play_with_loop(midi_data)
260
+ return io.BytesIO(base64.b64decode(midi_data)), create_download_list()
261
+
262
+ example_select.change(load_example, inputs=[example_select],
263
+ outputs=[example_output, "downloads"])
264
+ gr.State(get_midi_choices()).change(lambda choices: gr.update(choices=choices),
265
+ inputs=[gr.State()], outputs=[example_select])
266
+
267
+ # Tab 3: Generate & Perform
268
+ with gr.Tab("Generate & Perform"):
269
+ midi_select = gr.Dropdown(label="Select MIDI", choices=get_midi_choices(), value="starter")
270
+ length_factor = gr.Slider(1, 5, value=2, step=1, label="Length Factor")
271
+ variation = gr.Slider(0, 1, value=0.3, label="Variation")
272
+ generate_btn = gr.Button("Generate")
273
+ effect = gr.Radio(["tempo"], label="Synth Effect", value="tempo")
274
+ intensity = gr.Slider(0, 1, value=0.5, label="Effect Intensity")
275
+ apply_btn = gr.Button("Apply Effect")
276
+ stop_btn = gr.Button("Stop Playback")
277
+ output = gr.Audio(label="Preview", type="bytes", autoplay=True)
278
+ status = gr.Textbox(label="Status", value="Ready")
279
+ midi_device = gr.Dropdown(label="MIDI Output Device", choices=midi_manager.get_output_devices(), type="index")
280
+ tempo = gr.Slider(label="Tempo (BPM)", minimum=40, maximum=200, value=120, step=1)
281
+ device_info = gr.Textbox(label="Connected MIDI Devices", value=midi_manager.get_device_info(), readonly=True)
282
+ refresh_btn = gr.Button("🔄 Refresh MIDI Devices")
283
+
284
+ def update_dropdown(choices):
285
+ return gr.update(choices=choices)
286
+
287
+ gr.State(get_midi_choices()).change(update_dropdown, inputs=[gr.State()], outputs=[midi_select])
288
+
289
+ def generate(midi_id, length, var):
290
+ if not midi_id:
291
+ return None, "Select a MIDI file", create_download_list()
292
+ midi_data, audio_data = midi_processor.generate_variation(midi_id, length, var)
293
+ midi_processor.play_with_loop(midi_data)
294
+ return io.BytesIO(base64.b64decode(midi_data)), "Playing", create_download_list()
295
+
296
+ def apply_effect(midi_data, fx, inten):
297
+ if not midi_data:
298
+ return None, "Generate a MIDI first", create_download_list()
299
+ new_midi_data, audio_data = midi_processor.apply_synth_effect(midi_data.decode('utf-8'), fx, inten)
300
+ midi_processor.play_with_loop(new_midi_data)
301
+ return io.BytesIO(base64.b64decode(new_midi_data)), "Playing", create_download_list()
302
+
303
+ def refresh_devices():
304
+ return midi_manager.get_output_devices(), midi_manager.get_device_info()
305
+
306
+ generate_btn.click(generate, inputs=[midi_select, length_factor, variation],
307
+ outputs=[output, status, "downloads"])
308
+ apply_btn.click(apply_effect, inputs=[output, effect, intensity],
309
+ outputs=[output, status, "downloads"])
310
+ stop_btn.click(midi_processor.stop_playback, inputs=None, outputs=[status])
311
+ refresh_btn.click(refresh_devices, inputs=None, outputs=[midi_device, device_info])
312
+
313
+ # Tab 4: Downloads
314
+ with gr.Tab("Downloads", elem_id="downloads"):
315
+ downloads = gr.HTML(value=create_download_list())
316
+
317
+ gr.Markdown("""
318
+ <div style='text-align: center; margin-top: 20px;'>
319
+ <img src='https://huggingface.co/front/assets/huggingface_logo-noborder.svg' alt='Hugging Face Logo' style='width: 50px;'><br>
320
+ <strong>Hugging Face</strong><br>
321
+ <a href='https://huggingface.co/models'>Models</a> |
322
+ <a href='https://huggingface.co/datasets'>Datasets</a> |
323
+ <a href='https://huggingface.co/spaces'>Spaces</a> |
324
+ <a href='https://huggingface.co/posts'>Posts</a> |
325
+ <a href='https://huggingface.co/docs'>Docs</a> |
326
+ <a href='https://huggingface.co/enterprise'>Enterprise</a> |
327
+ <a href='https://huggingface.co/pricing'>Pricing</a>
328
+ </div>
329
+ """)
330
+
331
+ app.queue().launch(server_port=opt.port, share=opt.share, inbrowser=True)
332
+ midi_manager.close()