skytnt commited on
Commit
d25994f
1 Parent(s): 256bea9

new MidiVisualizer

Browse files
Files changed (3) hide show
  1. app.py +3 -2
  2. javascript/app.js +181 -35
  3. midi_synthesizer.py +1 -1
app.py CHANGED
@@ -424,9 +424,10 @@ if __name__ == "__main__":
424
  with gr.Accordion("options", open=False):
425
  input_temp = gr.Slider(label="temperature", minimum=0.1, maximum=1.2, step=0.01, value=1)
426
  input_top_p = gr.Slider(label="top p", minimum=0.1, maximum=1, step=0.01, value=0.98)
427
- input_top_k = gr.Slider(label="top k", minimum=1, maximum=128, step=1, value=10)
428
  input_allow_cc = gr.Checkbox(label="allow midi cc event", value=True)
429
- example3 = gr.Examples([[1, 0.98, 20], [1, 0.98, 12]], [input_temp, input_top_p, input_top_k])
 
430
  run_btn = gr.Button("generate", variant="primary")
431
  stop_btn = gr.Button("stop and output")
432
  output_midi_seq = gr.State()
 
424
  with gr.Accordion("options", open=False):
425
  input_temp = gr.Slider(label="temperature", minimum=0.1, maximum=1.2, step=0.01, value=1)
426
  input_top_p = gr.Slider(label="top p", minimum=0.1, maximum=1, step=0.01, value=0.98)
427
+ input_top_k = gr.Slider(label="top k", minimum=1, maximum=128, step=1, value=12)
428
  input_allow_cc = gr.Checkbox(label="allow midi cc event", value=True)
429
+ example3 = gr.Examples([[1, 0.95, 128], [1, 0.98, 20], [1, 0.98, 12]],
430
+ [input_temp, input_top_p, input_top_k])
431
  run_btn = gr.Button("generate", variant="primary")
432
  stop_btn = gr.Button("stop and output")
433
  output_midi_seq = gr.State()
javascript/app.js CHANGED
@@ -98,14 +98,22 @@ function HSVtoRGB(h, s, v) {
98
  };
99
  }
100
 
 
 
 
101
  class MidiVisualizer extends HTMLElement{
102
  constructor() {
103
  super();
104
  this.midiEvents = [];
105
  this.activeNotes = [];
106
  this.midiTimes = [];
107
- this.patches = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
108
- this.wrapper = null;
 
 
 
 
 
109
  this.svg = null;
110
  this.timeLine = null;
111
  this.config = {
@@ -130,40 +138,167 @@ class MidiVisualizer extends HTMLElement{
130
  this.innerHTML=''
131
  const shadow = this.attachShadow({mode: 'open'});
132
  const style = document.createElement("style");
133
- const wrapper = document.createElement('div');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  style.textContent = ".note.active {stroke: black;stroke-width: 0.75;stroke-opacity: 0.75;}";
135
- wrapper.style.overflowX= "scroll"
 
136
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
137
  svg.style.height = `${this.config.noteHeight*128}px`;
138
  svg.style.width = `${this.svgWidth}px`;
139
  const timeLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
140
  timeLine.style.stroke = "green"
141
  timeLine.style.strokeWidth = 2;
 
142
  shadow.appendChild(style)
143
- shadow.appendChild(wrapper);
144
- wrapper.appendChild(svg);
 
 
 
 
 
 
145
  svg.appendChild(timeLine)
146
- this.wrapper = wrapper;
 
147
  this.svg = svg;
148
  this.timeLine= timeLine;
149
- for(let i = 0; i <= 128 ; i++){
150
- this.colorMap.set(i, HSVtoRGB(i / 129, 1, 1))
151
  }
152
- console.log(this.colorMap)
153
  this.setPlayTime(0);
154
  }
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  clearMidiEvents(){
157
  this.pause()
158
  this.midiEvents = [];
159
  this.activeNotes = [];
160
  this.midiTimes = [];
161
- this.patches = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
 
 
 
 
162
  this.t1 = 0
163
  this.setPlayTime(0);
164
  this.totalTimeMs = 0;
165
  this.playTimeMs = 0
166
  this.lastUpdateTime = 0
 
167
  this.svgWidth = 0
168
  this.svg.innerHTML = ''
169
  this.svg.style.width = `${this.svgWidth}px`;
@@ -193,21 +328,24 @@ class MidiVisualizer extends HTMLElement{
193
  velocity = midiEvent[5]
194
  duration = midiEvent[6]
195
  }
 
196
 
197
  let x = (t/this.timePreBeat)*this.config.beatWidth
198
  let y = (127 - pitch)*this.config.noteHeight
199
  let w = (duration/this.timePreBeat)*this.config.beatWidth
200
  let h = this.config.noteHeight
201
  this.svgWidth = Math.ceil(Math.max(x + w, this.svgWidth))
202
- let color = this.getColor(track, channel)
203
  let opacity = Math.min(1, velocity/127 + 0.1).toFixed(2)
204
- let rect = this.drawNote(x,y,w,h, `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`)
 
205
  midiEvent.push(rect)
206
  this.setPlayTime(t);
207
- this.wrapper.scrollTo(this.svgWidth - this.wrapper.offsetWidth, 0)
208
  }else if(midiEvent[0] === "patch_change"){
209
  let channel = midiEvent[3]
210
- this.patches[channel] = midiEvent[4]
 
211
  }
212
  this.midiEvents.push(midiEvent);
213
  this.svg.style.width = `${this.svgWidth}px`;
@@ -215,23 +353,8 @@ class MidiVisualizer extends HTMLElement{
215
 
216
  }
217
 
218
- getColor(track, channel){
219
- let key = this.patches[channel];
220
- if( channel === 9){
221
- // drum
222
- key = 128;
223
- }
224
- let color = this.colorMap.get(key);
225
- if(!!color){
226
- return color;
227
- }
228
- color = HSVtoRGB(Math.random(),Math.random()*0.5 + 0.5,1);
229
- this.colorMap.set(key, color);
230
- return color;
231
- }
232
-
233
- drawNote(x, y, w, h, fill) {
234
- if (!this.svg) {
235
  return null;
236
  }
237
  const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
@@ -242,7 +365,7 @@ class MidiVisualizer extends HTMLElement{
242
  rect.setAttribute('y', `${Math.round(y)}`);
243
  rect.setAttribute('width', `${Math.round(w)}`);
244
  rect.setAttribute('height', `${Math.round(h)}`);
245
- this.svg.appendChild(rect);
246
  return rect
247
  }
248
 
@@ -277,7 +400,30 @@ class MidiVisualizer extends HTMLElement{
277
  this.timeLine.setAttribute('x2', `${x}`);
278
  this.timeLine.setAttribute('y2', `${this.config.noteHeight*128}`);
279
 
280
- this.wrapper.scrollTo(Math.max(0, x - this.wrapper.offsetWidth/2), 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  let dt = Date.now() - this.lastUpdateTime; // limit the update rate of ActiveNotes
282
  if(this.playing && dt > 50){
283
  let activeNotes = []
@@ -291,7 +437,7 @@ class MidiVisualizer extends HTMLElement{
291
  activeNotes.push(note)
292
  }
293
  }
294
- })
295
  this.addActiveNotes(activeNotes)
296
  this.lastUpdateTime = Date.now();
297
  }
 
98
  };
99
  }
100
 
101
+ const number2patch = ['Acoustic Grand', 'Bright Acoustic', 'Electric Grand', 'Honky-Tonk', 'Electric Piano 1', 'Electric Piano 2', 'Harpsichord', 'Clav', 'Celesta', 'Glockenspiel', 'Music Box', 'Vibraphone', 'Marimba', 'Xylophone', 'Tubular Bells', 'Dulcimer', 'Drawbar Organ', 'Percussive Organ', 'Rock Organ', 'Church Organ', 'Reed Organ', 'Accordion', 'Harmonica', 'Tango Accordion', 'Acoustic Guitar(nylon)', 'Acoustic Guitar(steel)', 'Electric Guitar(jazz)', 'Electric Guitar(clean)', 'Electric Guitar(muted)', 'Overdriven Guitar', 'Distortion Guitar', 'Guitar Harmonics', 'Acoustic Bass', 'Electric Bass(finger)', 'Electric Bass(pick)', 'Fretless Bass', 'Slap Bass 1', 'Slap Bass 2', 'Synth Bass 1', 'Synth Bass 2', 'Violin', 'Viola', 'Cello', 'Contrabass', 'Tremolo Strings', 'Pizzicato Strings', 'Orchestral Harp', 'Timpani', 'String Ensemble 1', 'String Ensemble 2', 'SynthStrings 1', 'SynthStrings 2', 'Choir Aahs', 'Voice Oohs', 'Synth Voice', 'Orchestra Hit', 'Trumpet', 'Trombone', 'Tuba', 'Muted Trumpet', 'French Horn', 'Brass Section', 'SynthBrass 1', 'SynthBrass 2', 'Soprano Sax', 'Alto Sax', 'Tenor Sax', 'Baritone Sax', 'Oboe', 'English Horn', 'Bassoon', 'Clarinet', 'Piccolo', 'Flute', 'Recorder', 'Pan Flute', 'Blown Bottle', 'Skakuhachi', 'Whistle', 'Ocarina', 'Lead 1 (square)', 'Lead 2 (sawtooth)', 'Lead 3 (calliope)', 'Lead 4 (chiff)', 'Lead 5 (charang)', 'Lead 6 (voice)', 'Lead 7 (fifths)', 'Lead 8 (bass+lead)', 'Pad 1 (new age)', 'Pad 2 (warm)', 'Pad 3 (polysynth)', 'Pad 4 (choir)', 'Pad 5 (bowed)', 'Pad 6 (metallic)', 'Pad 7 (halo)', 'Pad 8 (sweep)', 'FX 1 (rain)', 'FX 2 (soundtrack)', 'FX 3 (crystal)', 'FX 4 (atmosphere)', 'FX 5 (brightness)', 'FX 6 (goblins)', 'FX 7 (echoes)', 'FX 8 (sci-fi)', 'Sitar', 'Banjo', 'Shamisen', 'Koto', 'Kalimba', 'Bagpipe', 'Fiddle', 'Shanai', 'Tinkle Bell', 'Agogo', 'Steel Drums', 'Woodblock', 'Taiko Drum', 'Melodic Tom', 'Synth Drum', 'Reverse Cymbal', 'Guitar Fret Noise', 'Breath Noise', 'Seashore', 'Bird Tweet', 'Telephone Ring', 'Helicopter', 'Applause', 'Gunshot']
102
+ const number2drum_kits = {0: "Standard", 8: "Room", 16: "Power", 24: "Electric", 25: "TR-808", 32: "Jazz", 40: "Blush", 48: "Orchestra"}
103
+
104
  class MidiVisualizer extends HTMLElement{
105
  constructor() {
106
  super();
107
  this.midiEvents = [];
108
  this.activeNotes = [];
109
  this.midiTimes = [];
110
+ this.trackMap = new Map()
111
+ this.patches = [];
112
+ for (let i=0;i<16;i++){
113
+ this.patches.push([[0,0]])
114
+ }
115
+ this.trackList = null
116
+ this.pianoRoll = null;
117
  this.svg = null;
118
  this.timeLine = null;
119
  this.config = {
 
138
  this.innerHTML=''
139
  const shadow = this.attachShadow({mode: 'open'});
140
  const style = document.createElement("style");
141
+ const container = document.createElement('div');
142
+ container.style.display="flex";
143
+ container.style.height=`${this.config.noteHeight*128}px`;
144
+ const trackListContainer = document.createElement('div');
145
+ trackListContainer.style.width = "260px";
146
+ trackListContainer.style.minWidth = "260px";
147
+ trackListContainer.style.height = "100%";
148
+ trackListContainer.style.display="flex";
149
+ trackListContainer.style.flexDirection="column";
150
+ const trackList = document.createElement('div');
151
+ trackList.style.width = "100%";
152
+ trackList.style.height = "100%";
153
+ trackList.style.overflowY= "scroll";
154
+ trackList.style.display="flex";
155
+ trackList.style.flexDirection="column";
156
+ trackList.style.flexGrow="1";
157
+ const trackControls = document.createElement('div');
158
+ trackControls.style.display="flex";
159
+ trackControls.style.flexDirection="row";
160
+ trackControls.style.width = "100%";
161
+ trackControls.style.height = "50px";
162
+ const allTrackBtn = document.createElement('button');
163
+ allTrackBtn.textContent = "All";
164
+ allTrackBtn.style.width = "50%";
165
+ allTrackBtn.style.height = "100%";
166
+ allTrackBtn.style.backgroundColor = "transparent";
167
+ allTrackBtn.style.border = "none";
168
+ allTrackBtn.style.cursor = 'pointer';
169
+ let self = this;
170
+ allTrackBtn.onclick = function (){
171
+ self.trackMap.forEach((track, id) => {
172
+ track.setChecked(true);
173
+ })
174
+ };
175
+ const noneTrackBtn = document.createElement('button');
176
+ noneTrackBtn.textContent = "None";
177
+ noneTrackBtn.style.width = "50%";
178
+ noneTrackBtn.style.height = "100%";
179
+ noneTrackBtn.style.backgroundColor = "transparent";
180
+ noneTrackBtn.style.border = "none";
181
+ noneTrackBtn.style.cursor = 'pointer';
182
+ noneTrackBtn.onclick = function (){
183
+ self.trackMap.forEach((track, id) => {
184
+ track.setChecked(false);
185
+ });
186
+ };
187
+ const pianoRoll = document.createElement('div');
188
  style.textContent = ".note.active {stroke: black;stroke-width: 0.75;stroke-opacity: 0.75;}";
189
+ pianoRoll.style.overflowX= "scroll";
190
+ pianoRoll.style.flexGrow="1";
191
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
192
  svg.style.height = `${this.config.noteHeight*128}px`;
193
  svg.style.width = `${this.svgWidth}px`;
194
  const timeLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
195
  timeLine.style.stroke = "green"
196
  timeLine.style.strokeWidth = 2;
197
+
198
  shadow.appendChild(style)
199
+ shadow.appendChild(container);
200
+ container.appendChild(trackListContainer);
201
+ trackListContainer.appendChild(trackList);
202
+ trackListContainer.appendChild(trackControls);
203
+ trackControls.appendChild(allTrackBtn);
204
+ trackControls.appendChild(noneTrackBtn);
205
+ container.appendChild(pianoRoll);
206
+ pianoRoll.appendChild(svg);
207
  svg.appendChild(timeLine)
208
+ this.trackList = trackList;
209
+ this.pianoRoll = pianoRoll;
210
  this.svg = svg;
211
  this.timeLine= timeLine;
212
+ for(let i = 0; i < 128 ; i++){
213
+ this.colorMap.set(i, HSVtoRGB(i / 128, 1, 1))
214
  }
 
215
  this.setPlayTime(0);
216
  }
217
 
218
+ addTrack(id, tr, cl, name, color){
219
+ const track = {id, tr, cl, name, color,
220
+ instrument: cl===9?"Standard Drum":"Acoustic Grand",
221
+ svg: document.createElementNS('http://www.w3.org/2000/svg', 'g')}
222
+ this.svg.appendChild(track.svg)
223
+ const trackItem = this.createTrackItem(track);
224
+ this.trackList.appendChild(trackItem);
225
+ this.trackMap.set(id, track);
226
+ return track;
227
+ }
228
+
229
+ getTrack(tr, cl){
230
+ const id = tr * 16 + cl
231
+ let track = this.trackMap.get(id)
232
+ if (!!track){
233
+ return track
234
+ }
235
+ let color = this.colorMap.get((this.trackMap.size*17)%128)
236
+ return this.addTrack(id, tr, cl, `Track ${tr}, Channel ${cl}`, color)
237
+ }
238
+
239
+ createTrackItem(track) {
240
+ const trackItem = document.createElement('div');
241
+ trackItem.style.display = 'flex';
242
+ trackItem.style.alignItems = 'center';
243
+ trackItem.style.width = '100%';
244
+ trackItem.style.position = 'relative';
245
+
246
+ const colorBar = document.createElement('div');
247
+ colorBar.style.width = '5%';
248
+ colorBar.style.height = '100%';
249
+ colorBar.style.position = 'absolute';
250
+ colorBar.style.left = '0';
251
+ colorBar.style.top = '0';
252
+ let color = track.color;
253
+ colorBar.style.backgroundColor = `rgb(${color.r}, ${color.g}, ${color.b})`;
254
+ trackItem.appendChild(colorBar);
255
+
256
+ const content = document.createElement('div');
257
+ content.style.paddingLeft = '30px';
258
+ content.style.flexGrow = '1';
259
+ content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
260
+ trackItem.appendChild(content);
261
+ track.updateInstrument = function (instrument){
262
+ track.instrument = instrument;
263
+ content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
264
+ }
265
+
266
+ const toggleSwitch = document.createElement('input');
267
+ toggleSwitch.type = 'checkbox';
268
+ toggleSwitch.checked = true;
269
+ toggleSwitch.style.marginLeft = 'auto';
270
+ toggleSwitch.style.marginRight = '10px';
271
+ toggleSwitch.style.width = '20px';
272
+ toggleSwitch.style.height = '20px';
273
+ toggleSwitch.style.cursor = 'pointer';
274
+
275
+ toggleSwitch.onchange = function () {
276
+ track.svg.setAttribute('visibility',toggleSwitch.checked? "visible" : "hidden")
277
+ };
278
+ track.setChecked = function (checked){
279
+ toggleSwitch.checked = checked;
280
+ track.svg.setAttribute('visibility',toggleSwitch.checked? "visible" : "hidden")
281
+ }
282
+ trackItem.appendChild(toggleSwitch);
283
+ return trackItem;
284
+ }
285
+
286
  clearMidiEvents(){
287
  this.pause()
288
  this.midiEvents = [];
289
  this.activeNotes = [];
290
  this.midiTimes = [];
291
+ this.trackMap = new Map()
292
+ this.patches = [];
293
+ for (let i=0;i<16;i++){
294
+ this.patches.push([[0,0]])
295
+ }
296
  this.t1 = 0
297
  this.setPlayTime(0);
298
  this.totalTimeMs = 0;
299
  this.playTimeMs = 0
300
  this.lastUpdateTime = 0
301
+ this.trackList.innerHTML = ''
302
  this.svgWidth = 0
303
  this.svg.innerHTML = ''
304
  this.svg.style.width = `${this.svgWidth}px`;
 
328
  velocity = midiEvent[5]
329
  duration = midiEvent[6]
330
  }
331
+ let vis_track = this.getTrack(track, channel);
332
 
333
  let x = (t/this.timePreBeat)*this.config.beatWidth
334
  let y = (127 - pitch)*this.config.noteHeight
335
  let w = (duration/this.timePreBeat)*this.config.beatWidth
336
  let h = this.config.noteHeight
337
  this.svgWidth = Math.ceil(Math.max(x + w, this.svgWidth))
338
+ let color = vis_track.color
339
  let opacity = Math.min(1, velocity/127 + 0.1).toFixed(2)
340
+ let rect = this.drawNote(vis_track.svg, x,y,w,h,
341
+ `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`)
342
  midiEvent.push(rect)
343
  this.setPlayTime(t);
344
+ this.pianoRoll.scrollTo(this.svgWidth - this.pianoRoll.offsetWidth, this.pianoRoll.scrollTop)
345
  }else if(midiEvent[0] === "patch_change"){
346
  let channel = midiEvent[3]
347
+ this.patches[channel].push([t, midiEvent[4]])
348
+ this.patches[channel].sort((a, b) => a[0] - b[0])
349
  }
350
  this.midiEvents.push(midiEvent);
351
  this.svg.style.width = `${this.svgWidth}px`;
 
353
 
354
  }
355
 
356
+ drawNote(svg, x, y, w, h, fill) {
357
+ if (!svg) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  return null;
359
  }
360
  const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
 
365
  rect.setAttribute('y', `${Math.round(y)}`);
366
  rect.setAttribute('width', `${Math.round(w)}`);
367
  rect.setAttribute('height', `${Math.round(h)}`);
368
+ svg.appendChild(rect);
369
  return rect
370
  }
371
 
 
400
  this.timeLine.setAttribute('x2', `${x}`);
401
  this.timeLine.setAttribute('y2', `${this.config.noteHeight*128}`);
402
 
403
+ this.pianoRoll.scrollTo(Math.max(0, x - this.pianoRoll.offsetWidth/2), this.pianoRoll.scrollTop)
404
+
405
+ this.trackMap.forEach((track, id)=>{
406
+ let instrument = track.instrument
407
+ let cl = track.cl;
408
+ let patches = this.patches[cl]
409
+ let p = 0
410
+ for (let i = 0; i < patches.length ; i++){
411
+ let tp = patches[i]
412
+ if (t < tp[0])
413
+ break
414
+ p = tp[1]
415
+ }
416
+ if (cl === 9){
417
+ let drumKit = number2drum_kits[`${p}`];
418
+ if (!!drumKit)
419
+ instrument = drumKit + " Drum";
420
+ }else{
421
+ instrument = number2patch[p]
422
+ }
423
+ if (instrument !== track.instrument)
424
+ track.updateInstrument(instrument)
425
+ });
426
+
427
  let dt = Date.now() - this.lastUpdateTime; // limit the update rate of ActiveNotes
428
  if(this.playing && dt > 50){
429
  let activeNotes = []
 
437
  activeNotes.push(note)
438
  }
439
  }
440
+ });
441
  this.addActiveNotes(activeNotes)
442
  this.lastUpdateTime = Date.now();
443
  }
midi_synthesizer.py CHANGED
@@ -22,8 +22,8 @@ class MidiSynthesizer:
22
  return device
23
 
24
  def release_fluidsynth(self, device):
25
- device[0].get_samples(self.sample_rate*5) # wait for silence
26
  device[0].system_reset()
 
27
  device[2] = False
28
 
29
  def synthesis(self, midi_opus):
 
22
  return device
23
 
24
  def release_fluidsynth(self, device):
 
25
  device[0].system_reset()
26
+ device[0].get_samples(self.sample_rate*5) # wait for silence
27
  device[2] = False
28
 
29
  def synthesis(self, midi_opus):