skytnt commited on
Commit
6481a43
1 Parent(s): 976ec92

use javascript

Browse files
Files changed (3) hide show
  1. app.py +58 -55
  2. javascript/app.js +252 -0
  3. midi_tokenizer.py +13 -0
app.py CHANGED
@@ -2,12 +2,11 @@ import argparse
2
  import glob
3
  import os.path
4
 
5
- import PIL
6
- import PIL.ImageColor
7
  import gradio as gr
8
  import numpy as np
9
  import onnxruntime as rt
10
  import tqdm
 
11
  from huggingface_hub import hf_hub_download
12
 
13
  import MIDI
@@ -107,44 +106,14 @@ def generate(model, prompt=None, max_len=512, temp=1.0, top_p=0.98, top_k=20,
107
  break
108
 
109
 
110
- def run(model_name, tab, instruments, drum_kit, mid, midi_events, gen_events, temp, top_p, top_k, allow_cc):
111
- mid_seq = []
112
- max_len = int(gen_events)
113
- img_len = 1024
114
- img = np.full((128 * 2, img_len, 3), 255, dtype=np.uint8)
115
- state = {"t1": 0, "t": 0, "cur_pos": 0}
116
- colors = ['navy', 'blue', 'deepskyblue', 'teal', 'green', 'lightgreen', 'lime', 'orange',
117
- 'brown', 'grey', 'red', 'pink', 'aqua', 'orchid', 'bisque', 'coral']
118
- colors = [PIL.ImageColor.getrgb(color) for color in colors]
119
 
120
- def draw_event(tokens):
121
- if tokens[0] in tokenizer.id_events:
122
- name = tokenizer.id_events[tokens[0]]
123
- if len(tokens) <= len(tokenizer.events[name]):
124
- return
125
- params = tokens[1:]
126
- params = [params[i] - tokenizer.parameter_ids[p][0] for i, p in enumerate(tokenizer.events[name])]
127
- if not all([0 <= params[i] < tokenizer.event_parameters[p] for i, p in enumerate(tokenizer.events[name])]):
128
- return
129
- event = [name] + params
130
- state["t1"] += event[1]
131
- t = state["t1"] * 16 + event[2]
132
- state["t"] = t
133
- if name == "note":
134
- tr, d, c, p = event[3:7]
135
- shift = t + d - (state["cur_pos"] + img_len)
136
- if shift > 0:
137
- img[:, :-shift] = img[:, shift:]
138
- img[:, -shift:] = 255
139
- state["cur_pos"] += shift
140
- t = t - state["cur_pos"]
141
- img[p * 2:(p + 1) * 2, t: t + d] = colors[c]
142
 
143
- def get_img():
144
- t = state["t"] - state["cur_pos"]
145
- img_new = img.copy()
146
- img_new[:, t: t + 2] = 0
147
- return PIL.Image.fromarray(np.flip(img_new, 0))
148
 
149
  disable_patch_change = False
150
  disable_channels = None
@@ -170,25 +139,25 @@ def run(model_name, tab, instruments, drum_kit, mid, midi_events, gen_events, te
170
  mid = mid[:int(midi_events)]
171
  max_len += len(mid)
172
  for token_seq in mid:
173
- mid_seq.append(token_seq)
174
- draw_event(token_seq)
 
 
 
175
  model = models[model_name]
176
  generator = generate(model, mid, max_len=max_len, temp=temp, top_p=top_p, top_k=top_k,
177
  disable_patch_change=disable_patch_change, disable_control_change=not allow_cc,
178
  disable_channels=disable_channels)
179
- try:
180
- for token_seq in generator:
181
- mid_seq.append(token_seq)
182
- draw_event(token_seq)
183
- yield mid_seq, get_img(), None, None
184
- except Exception as e:
185
- print(e)
186
-
187
  mid = tokenizer.detokenize(mid_seq)
188
  with open(f"output.mid", 'wb') as f:
189
  f.write(MIDI.score2midi(mid))
190
- audio = synthesis(MIDI.score2opus(mid), soundfont_path)
191
- yield mid_seq, get_img(), "output.mid", (44100, audio)
192
 
193
 
194
  def cancel_run(mid_seq):
@@ -198,7 +167,39 @@ def cancel_run(mid_seq):
198
  with open(f"output.mid", 'wb') as f:
199
  f.write(MIDI.score2midi(mid))
200
  audio = synthesis(MIDI.score2opus(mid), soundfont_path)
201
- return "output.mid", (44100, audio)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
 
204
  number2drum_kits = {-1: "None", 0: "Standard", 8: "Room", 16: "Power", 24: "Electric", 25: "TR-808", 32: "Jazz",
@@ -226,6 +227,7 @@ if __name__ == "__main__":
226
  model_token = rt.InferenceSession(model_token_path, providers=providers)
227
  models[name] = [model_base, model_token]
228
 
 
229
  app = gr.Blocks()
230
  with app:
231
  gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Midi Composer</h1>")
@@ -236,6 +238,7 @@ if __name__ == "__main__":
236
  "(https://colab.research.google.com/github/SkyTNT/midi-model/blob/main/demo.ipynb)"
237
  " for faster running and longer generation"
238
  )
 
239
  input_model = gr.Dropdown(label="select model", choices=list(models.keys()),
240
  type="value", value=list(models.keys())[0])
241
  tab_select = gr.Variable(value=0)
@@ -277,12 +280,12 @@ if __name__ == "__main__":
277
  run_btn = gr.Button("generate", variant="primary")
278
  stop_btn = gr.Button("stop and output")
279
  output_midi_seq = gr.Variable()
280
- output_midi_img = gr.Image(label="output image")
281
- output_midi = gr.File(label="output midi", file_types=[".mid"])
282
  output_audio = gr.Audio(label="output audio", format="mp3")
 
283
  run_event = run_btn.click(run, [input_model, tab_select, input_instruments, input_drum_kit, input_midi,
284
  input_midi_events, input_gen_events, input_temp, input_top_p, input_top_k,
285
  input_allow_cc],
286
- [output_midi_seq, output_midi_img, output_midi, output_audio])
287
- stop_btn.click(cancel_run, output_midi_seq, [output_midi, output_audio], cancels=run_event, queue=False)
288
  app.queue(2).launch(server_port=opt.port, share=opt.share, inbrowser=True)
 
2
  import glob
3
  import os.path
4
 
 
 
5
  import gradio as gr
6
  import numpy as np
7
  import onnxruntime as rt
8
  import tqdm
9
+ import json
10
  from huggingface_hub import hf_hub_download
11
 
12
  import MIDI
 
106
  break
107
 
108
 
109
+ def create_msg(name, data):
110
+ return {"name": name, "data": data}
 
 
 
 
 
 
 
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ def run(model_name, tab, instruments, drum_kit, mid, midi_events, gen_events, temp, top_p, top_k, allow_cc):
114
+ mid_seq = []
115
+ gen_events = int(gen_events)
116
+ max_len = gen_events
 
117
 
118
  disable_patch_change = False
119
  disable_channels = None
 
139
  mid = mid[:int(midi_events)]
140
  max_len += len(mid)
141
  for token_seq in mid:
142
+ mid_seq.append(token_seq.tolist())
143
+ init_msgs = [create_msg("visualizer_clear", None)]
144
+ for tokens in mid_seq:
145
+ init_msgs.append(create_msg("visualizer_append", tokenizer.tokens2event(tokens)))
146
+ yield mid_seq, None, None, init_msgs
147
  model = models[model_name]
148
  generator = generate(model, mid, max_len=max_len, temp=temp, top_p=top_p, top_k=top_k,
149
  disable_patch_change=disable_patch_change, disable_control_change=not allow_cc,
150
  disable_channels=disable_channels)
151
+ for i, token_seq in enumerate(generator):
152
+ token_seq = token_seq.tolist()
153
+ mid_seq.append(token_seq)
154
+ event = tokenizer.tokens2event(token_seq)
155
+ yield mid_seq, None, None, [create_msg("visualizer_append", event), create_msg("progress", [i + 1, gen_events])]
 
 
 
156
  mid = tokenizer.detokenize(mid_seq)
157
  with open(f"output.mid", 'wb') as f:
158
  f.write(MIDI.score2midi(mid))
159
+ audio = synthesis(MIDI.score2opus(mid), opt.soundfont_path)
160
+ yield mid_seq, "output.mid", (44100, audio), [create_msg("visualizer_end", None)]
161
 
162
 
163
  def cancel_run(mid_seq):
 
167
  with open(f"output.mid", 'wb') as f:
168
  f.write(MIDI.score2midi(mid))
169
  audio = synthesis(MIDI.score2opus(mid), soundfont_path)
170
+ return "output.mid", (44100, audio), [create_msg("visualizer_end", None)]
171
+
172
+
173
+ def load_javascript(dir="javascript"):
174
+ scripts_list = glob.glob(f"{dir}/*.js")
175
+ javascript = ""
176
+ for path in scripts_list:
177
+ with open(path, "r", encoding="utf8") as jsfile:
178
+ javascript += f"\n<!-- {path} --><script>{jsfile.read()}</script>"
179
+ template_response_ori = gr.routes.templates.TemplateResponse
180
+
181
+ def template_response(*args, **kwargs):
182
+ res = template_response_ori(*args, **kwargs)
183
+ res.body = res.body.replace(
184
+ b'</head>', f'{javascript}</head>'.encode("utf8"))
185
+ res.init_headers()
186
+ return res
187
+
188
+ gr.routes.templates.TemplateResponse = template_response
189
+
190
+
191
+ class JSMsgReceiver(gr.HTML):
192
+
193
+ def __init__(self, **kwargs):
194
+ super().__init__(elem_id="msg_receiver", visible=False, **kwargs)
195
+
196
+ def postprocess(self, y):
197
+ if y:
198
+ y = f"<p>{json.dumps(y)}</p>"
199
+ return super().postprocess(y)
200
+
201
+ def get_block_name(self) -> str:
202
+ return "html"
203
 
204
 
205
  number2drum_kits = {-1: "None", 0: "Standard", 8: "Room", 16: "Power", 24: "Electric", 25: "TR-808", 32: "Jazz",
 
227
  model_token = rt.InferenceSession(model_token_path, providers=providers)
228
  models[name] = [model_base, model_token]
229
 
230
+ load_javascript()
231
  app = gr.Blocks()
232
  with app:
233
  gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Midi Composer</h1>")
 
238
  "(https://colab.research.google.com/github/SkyTNT/midi-model/blob/main/demo.ipynb)"
239
  " for faster running and longer generation"
240
  )
241
+ js_msg = JSMsgReceiver()
242
  input_model = gr.Dropdown(label="select model", choices=list(models.keys()),
243
  type="value", value=list(models.keys())[0])
244
  tab_select = gr.Variable(value=0)
 
280
  run_btn = gr.Button("generate", variant="primary")
281
  stop_btn = gr.Button("stop and output")
282
  output_midi_seq = gr.Variable()
283
+ output_midi_visualizer = gr.HTML(elem_id="midi_visualizer_container")
 
284
  output_audio = gr.Audio(label="output audio", format="mp3")
285
+ output_midi = gr.File(label="output midi", file_types=[".mid"])
286
  run_event = run_btn.click(run, [input_model, tab_select, input_instruments, input_drum_kit, input_midi,
287
  input_midi_events, input_gen_events, input_temp, input_top_p, input_top_k,
288
  input_allow_cc],
289
+ [output_midi_seq, output_midi, output_audio, js_msg])
290
+ stop_btn.click(cancel_run, output_midi_seq, [output_midi, output_audio, js_msg], cancels=run_event, queue=False)
291
  app.queue(2).launch(server_port=opt.port, share=opt.share, inbrowser=True)
javascript/app.js ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function gradioApp() {
2
+ const elems = document.getElementsByTagName('gradio-app')
3
+ const gradioShadowRoot = elems.length == 0 ? null : elems[0].shadowRoot
4
+ return !!gradioShadowRoot ? gradioShadowRoot : document;
5
+ }
6
+
7
+ uiUpdateCallbacks = []
8
+ msgReceiveCallbacks = []
9
+
10
+ function onUiUpdate(callback){
11
+ uiUpdateCallbacks.push(callback)
12
+ }
13
+
14
+ function onMsgReceive(callback){
15
+ msgReceiveCallbacks.push(callback)
16
+ }
17
+
18
+ function runCallback(x, m){
19
+ try {
20
+ x(m)
21
+ } catch (e) {
22
+ (console.error || console.log).call(console, e.message, e);
23
+ }
24
+ }
25
+ function executeCallbacks(queue, m) {
26
+ queue.forEach(function(x){runCallback(x, m)})
27
+ }
28
+
29
+ document.addEventListener("DOMContentLoaded", function() {
30
+ var mutationObserver = new MutationObserver(function(m){
31
+ executeCallbacks(uiUpdateCallbacks, m);
32
+ });
33
+ mutationObserver.observe( gradioApp(), { childList:true, subtree:true })
34
+ });
35
+
36
+ (()=>{
37
+ let mse_receiver_inited = null
38
+ onUiUpdate(()=>{
39
+ let app = gradioApp()
40
+ let msg_receiver = app.querySelector("#msg_receiver");
41
+ if(!!msg_receiver && mse_receiver_inited !== msg_receiver){
42
+ let mutationObserver = new MutationObserver(function(ms){
43
+ ms.forEach((m)=>{
44
+ m.addedNodes.forEach((node)=>{
45
+ if(node.nodeName === "P"){
46
+ let obj = JSON.parse(node.innerText);
47
+ if(obj instanceof Array){
48
+ obj.forEach((o)=>{executeCallbacks(msgReceiveCallbacks, o);});
49
+ }else{
50
+ executeCallbacks(msgReceiveCallbacks, obj);
51
+ }
52
+ }
53
+ })
54
+ })
55
+ });
56
+ mutationObserver.observe( msg_receiver, {childList:true, subtree:true, characterData:true})
57
+ console.log("receiver init");
58
+ mse_receiver_inited = msg_receiver;
59
+ }
60
+ })
61
+ })();
62
+
63
+ class MidiVisualizer extends HTMLElement{
64
+ constructor() {
65
+ super();
66
+ this.midiEvents = [];
67
+ this.wrapper = null;
68
+ this.svg = null;
69
+ this.timeLine = null;
70
+ this.config = {
71
+ noteHeight : 4,
72
+ beatWidth: 32
73
+ }
74
+ this.svgWidth = 0;
75
+ this.t1 = 0;
76
+ this.playTime = 0
77
+ this.colorMap = new Map();
78
+ this.init();
79
+ }
80
+
81
+ init(){
82
+ this.innerHTML=''
83
+ const shadow = this.attachShadow({mode: 'open'});
84
+ const style = document.createElement("style");
85
+ const wrapper = document.createElement('div');
86
+ style.textContent = ".note.active {stroke: black;stroke-width: 0.75;stroke-opacity: 0.75;}";
87
+ wrapper.style.overflowX= "scroll"
88
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
89
+ svg.style.height = `${this.config.noteHeight*128}px`;
90
+ svg.style.width = `${this.svgWidth}px`;
91
+ const timeLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
92
+ timeLine.style.stroke = "green"
93
+ timeLine.style.strokeWidth = 2;
94
+ shadow.appendChild(style)
95
+ shadow.appendChild(wrapper);
96
+ wrapper.appendChild(svg);
97
+ svg.appendChild(timeLine)
98
+ this.wrapper = wrapper;
99
+ this.svg = svg;
100
+ this.timeLine= timeLine;
101
+ this.setPlayTime(0);
102
+ }
103
+
104
+ setPlayTime(t){
105
+ this.playTime = t
106
+ let x = Math.round((t/16)*this.config.beatWidth)
107
+ this.timeLine.setAttribute('x1', `${x}`);
108
+ this.timeLine.setAttribute('y1', '0');
109
+ this.timeLine.setAttribute('x2', `${x}`);
110
+ this.timeLine.setAttribute('y2', `${this.config.noteHeight*128}`);
111
+ }
112
+
113
+ clearMidiEvents(){
114
+ this.midiEvents = [];
115
+ this.svgWidth = 0
116
+ this.svg.innerHTML = ''
117
+ this.t1 = 0
118
+ this.colorMap.clear()
119
+ this.svg.style.width = `${this.svgWidth}px`;
120
+ this.setPlayTime(0);
121
+ this.svg.appendChild(this.timeLine)
122
+ }
123
+
124
+ appendMidiEvent(midiEvent){
125
+ if(midiEvent instanceof Array && midiEvent.length > 0){
126
+ this.midiEvents.push(midiEvent);
127
+ this.t1 += midiEvent[1]
128
+ let t = this.t1*16 + midiEvent[2]
129
+ if(midiEvent[0] === "note"){
130
+ let track = midiEvent[3]
131
+ let duration = midiEvent[4]
132
+ let channel = midiEvent[5]
133
+ let pitch = midiEvent[6]
134
+ let velocity = midiEvent[7]
135
+ let x = (t/16)*this.config.beatWidth
136
+ let y = (127 - pitch)*this.config.noteHeight
137
+ let w = (duration/16)*this.config.beatWidth
138
+ let h = this.config.noteHeight
139
+ this.svgWidth = Math.ceil(Math.max(x + w, this.svgWidth))
140
+ let color = this.getColor(track, channel)
141
+ let opacity = Math.min(1, velocity/127 + 0.1).toFixed(2)
142
+ this.drawNote(x,y,w,h, `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${opacity})`)
143
+ this.setPlayTime(t);
144
+ this.wrapper.scrollTo(this.svgWidth - this.wrapper.offsetWidth, 0)
145
+ }
146
+ this.svg.style.width = `${this.svgWidth}px`;
147
+ }
148
+
149
+ }
150
+
151
+ getColor(track, channel){
152
+ let key = `${track},${channel}`;
153
+ let color = this.colorMap.get(key);
154
+ if(!!color){
155
+ return color;
156
+ }
157
+ color = [Math.round(Math.random()*240) + 10, Math.round(Math.random()*240)+ 10, Math.round(Math.random()*240)+ 10];
158
+ this.colorMap.set(key, color);
159
+ return color;
160
+ }
161
+
162
+ drawNote(x, y, w, h, fill) {
163
+ if (!this.svg) {
164
+ return null;
165
+ }
166
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
167
+ rect.classList.add('note');
168
+ rect.setAttribute('fill', fill);
169
+ // Round values to the nearest integer to avoid partially filled pixels.
170
+ rect.setAttribute('x', `${Math.round(x)}`);
171
+ rect.setAttribute('y', `${Math.round(y)}`);
172
+ rect.setAttribute('width', `${Math.round(w)}`);
173
+ rect.setAttribute('height', `${Math.round(h)}`);
174
+ this.svg.appendChild(rect);
175
+ return rect
176
+ }
177
+ }
178
+
179
+ customElements.define('midi-visualizer', MidiVisualizer);
180
+
181
+ (()=>{
182
+ let midi_visualizer_container_inited = null
183
+ let midi_visualizer = document.createElement('midi-visualizer')
184
+ onUiUpdate((m)=>{
185
+ let app = gradioApp()
186
+ let midi_visualizer_container = app.querySelector("#midi_visualizer_container");
187
+ if(!!midi_visualizer_container && midi_visualizer_container_inited!== midi_visualizer_container){
188
+ midi_visualizer_container.appendChild(midi_visualizer)
189
+ midi_visualizer_container_inited = midi_visualizer_container;
190
+ }
191
+ })
192
+
193
+ function createProgressBar(progressbarContainer){
194
+ let parentProgressbar = progressbarContainer.parentNode;
195
+ let divProgress = document.createElement('div');
196
+ divProgress.className='progressDiv';
197
+ let rect = progressbarContainer.getBoundingClientRect();
198
+ divProgress.style.width = rect.width + "px";
199
+ divProgress.style.background = "#b4c0cc";
200
+ divProgress.style.borderRadius = "8px";
201
+ let divInner = document.createElement('div');
202
+ divInner.className='progress';
203
+ divInner.style.color = "white";
204
+ divInner.style.background = "#0060df";
205
+ divInner.style.textAlign = "right";
206
+ divInner.style.fontWeight = "bold";
207
+ divInner.style.borderRadius = "8px";
208
+ divInner.style.height = "20px";
209
+ divInner.style.lineHeight = "20px";
210
+ divInner.style.paddingRight = "8px"
211
+ divProgress.appendChild(divInner);
212
+ parentProgressbar.insertBefore(divProgress, progressbarContainer);
213
+ }
214
+
215
+ function removeProgressBar(progressbarContainer){
216
+ let parentProgressbar = progressbarContainer.parentNode;
217
+ let divProgress = parentProgressbar.querySelector(".progressDiv");
218
+ parentProgressbar.removeChild(divProgress);
219
+ }
220
+
221
+ function setProgressBar(progressbarContainer, progress, total){
222
+ let parentProgressbar = progressbarContainer.parentNode;
223
+ let divProgress = parentProgressbar.querySelector(".progressDiv");
224
+ let divInner = parentProgressbar.querySelector(".progress");
225
+ if(total===0)
226
+ total = 1;
227
+ divInner.style.width = `${(progress/total)*100}%`;
228
+ divInner.textContent = `${progress}/${total}`;
229
+ }
230
+
231
+ onMsgReceive((msg)=>{
232
+ switch (msg.name) {
233
+ case "visualizer_clear":
234
+ midi_visualizer.clearMidiEvents();
235
+ createProgressBar(midi_visualizer_container_inited)
236
+ break;
237
+ case "visualizer_append":
238
+ midi_visualizer.appendMidiEvent(msg.data);
239
+ break;
240
+ case "progress":
241
+ let progress = msg.data[0]
242
+ let total = msg.data[1]
243
+ setProgressBar(midi_visualizer_container_inited, progress, total)
244
+ break;
245
+ case "visualizer_end":
246
+ midi_visualizer.setPlayTime(0)
247
+ removeProgressBar(midi_visualizer_container_inited)
248
+ break;
249
+ default:
250
+ }
251
+ })
252
+ })();
midi_tokenizer.py CHANGED
@@ -101,6 +101,19 @@ class MIDITokenizer:
101
  tokens += [self.pad_id] * (self.max_token_seq - len(tokens))
102
  return tokens
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  def detokenize(self, midi_seq):
105
  ticks_per_beat = 480
106
  tracks_dict = {}
 
101
  tokens += [self.pad_id] * (self.max_token_seq - len(tokens))
102
  return tokens
103
 
104
+ def tokens2event(self, tokens):
105
+ if tokens[0] in self.id_events:
106
+ name = self.id_events[tokens[0]]
107
+ if len(tokens) <= len(self.events[name]):
108
+ return []
109
+ params = tokens[1:]
110
+ params = [params[i] - self.parameter_ids[p][0] for i, p in enumerate(self.events[name])]
111
+ if not all([0 <= params[i] < self.event_parameters[p] for i, p in enumerate(self.events[name])]):
112
+ return []
113
+ event = [name] + params
114
+ return event
115
+ return []
116
+
117
  def detokenize(self, midi_seq):
118
  ticks_per_beat = 480
119
  tracks_dict = {}