SteveZerb commited on
Commit
2ecbccf
·
verified ·
1 Parent(s): 671fe47

Upload app.js

Browse files
Files changed (1) hide show
  1. javascript/app.js +732 -0
javascript/app.js ADDED
@@ -0,0 +1,732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const MIDI_OUTPUT_BATCH_SIZE=4;
2
+ //Do not change MIDI_OUTPUT_BATCH_SIZE. It will be automatically replaced.
3
+
4
+ /**
5
+ * 自动绕过 shadowRoot 的 querySelector
6
+ * @param {string} selector - 要查询的 CSS 选择器
7
+ * @returns {Element|null} - 匹配的元素或 null 如果未找到
8
+ */
9
+ function deepQuerySelector(selector) {
10
+ /**
11
+ * 在指定的根元素或文档对象下深度查询元素
12
+ * @param {Element|Document} root - 要开始搜索的根元素或文档对象
13
+ * @param {string} selector - 要查询的 CSS 选择器
14
+ * @returns {Element|null} - 匹配的元素或 null 如果未找到
15
+ */
16
+ function deepSearch(root, selector) {
17
+ // 在当前根元素下查找
18
+ let element = root.querySelector(selector);
19
+ if (element) {
20
+ return element;
21
+ }
22
+
23
+ // 如果未找到,递归检查 shadow DOM
24
+ const shadowHosts = root.querySelectorAll('*');
25
+
26
+ for (let i = 0; i < shadowHosts.length; i++) {
27
+ const host = shadowHosts[i];
28
+
29
+ // 检查当前元素是否有 shadowRoot
30
+ if (host.shadowRoot) {
31
+ element = deepSearch(host.shadowRoot, selector);
32
+ if (element) {
33
+ return element;
34
+ }
35
+ }
36
+ }
37
+ // 未找到元素
38
+ return null;
39
+ }
40
+
41
+ return deepSearch(this, selector);
42
+ }
43
+
44
+ Element.prototype.deepQuerySelector = deepQuerySelector;
45
+ Document.prototype.deepQuerySelector = deepQuerySelector;
46
+
47
+ function gradioApp() {
48
+ const elems = document.getElementsByTagName('gradio-app')
49
+ const gradioShadowRoot = elems.length == 0 ? null : elems[0].shadowRoot
50
+ return !!gradioShadowRoot ? gradioShadowRoot : document;
51
+ }
52
+
53
+ uiUpdateCallbacks = []
54
+ msgReceiveCallbacks = []
55
+
56
+ function onUiUpdate(callback){
57
+ uiUpdateCallbacks.push(callback)
58
+ }
59
+
60
+ function onMsgReceive(callback){
61
+ msgReceiveCallbacks.push(callback)
62
+ }
63
+
64
+ function runCallback(x, m){
65
+ try {
66
+ x(m)
67
+ } catch (e) {
68
+ (console.error || console.log).call(console, e.message, e);
69
+ }
70
+ }
71
+ function executeCallbacks(queue, m) {
72
+ queue.forEach(function(x){runCallback(x, m)})
73
+ }
74
+
75
+ document.addEventListener("DOMContentLoaded", function() {
76
+ var mutationObserver = new MutationObserver(function(m){
77
+ executeCallbacks(uiUpdateCallbacks, m);
78
+ });
79
+ mutationObserver.observe( gradioApp(), { childList:true, subtree:true })
80
+ });
81
+
82
+ function HSVtoRGB(h, s, v) {
83
+ let r, g, b, i, f, p, q, t;
84
+ i = Math.floor(h * 6);
85
+ f = h * 6 - i;
86
+ p = v * (1 - s);
87
+ q = v * (1 - f * s);
88
+ t = v * (1 - (1 - f) * s);
89
+ switch (i % 6) {
90
+ case 0: r = v; g = t; b = p; break;
91
+ case 1: r = q; g = v; b = p; break;
92
+ case 2: r = p; g = v; b = t; break;
93
+ case 3: r = p; g = q; b = v; break;
94
+ case 4: r = t; g = p; b = v; break;
95
+ case 5: r = v; g = p; b = q; break;
96
+ }
97
+ return {
98
+ r: Math.round(r * 255),
99
+ g: Math.round(g * 255),
100
+ b: Math.round(b * 255)
101
+ };
102
+ }
103
+
104
+ function isMobile(){
105
+ return /(iPhone|iPad|iPod|iOS|Android|Windows Phone)/i.test(navigator.userAgent);
106
+ }
107
+
108
+ 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']
109
+ const number2drum_kits = {0: "Standard", 8: "Room", 16: "Power", 24: "Electric", 25: "TR-808", 32: "Jazz", 40: "Blush", 48: "Orchestra"}
110
+
111
+ class MidiVisualizer extends HTMLElement{
112
+ constructor() {
113
+ super();
114
+ this.midiEvents = [];
115
+ this.activeNotes = [];
116
+ this.midiTimes = [];
117
+ this.trackMap = new Map()
118
+ this.patches = [];
119
+ for (let i=0;i<16;i++){
120
+ this.patches.push([[0,0]])
121
+ }
122
+ this.container = null;
123
+ this.trackList = null
124
+ this.pianoRoll = null;
125
+ this.svg = null;
126
+ this.timeLine = null;
127
+ this.config = {
128
+ noteHeight : 4,
129
+ beatWidth: 32
130
+ }
131
+ if (isMobile()){
132
+ this.config.noteHeight = 1;
133
+ this.config.beatWidth = 16;
134
+ }
135
+ this.timePreBeat = 16
136
+ this.svgWidth = 0;
137
+ this.t1 = 0;
138
+ this.totalTimeMs = 0
139
+ this.playTime = 0
140
+ this.playTimeMs = 0
141
+ this.lastUpdateTime = 0
142
+ this.colorMap = new Map();
143
+ this.playing = false;
144
+ this.timer = null;
145
+ this.version = "v2"
146
+ this.init();
147
+ }
148
+
149
+ init(){
150
+ this.innerHTML=''
151
+ const shadow = this.attachShadow({mode: 'open'});
152
+ const style = document.createElement("style");
153
+ style.textContent = ".note.active {stroke: black;stroke-width: 0.75;stroke-opacity: 0.75;}";
154
+ const container = document.createElement('div');
155
+ container.style.display="flex";
156
+ container.style.height=`${this.config.noteHeight*128 + 25}px`;
157
+ const trackListContainer = document.createElement('div');
158
+ trackListContainer.style.width = "260px";
159
+ trackListContainer.style.minWidth = "260px";
160
+ trackListContainer.style.height = "100%";
161
+ trackListContainer.style.display="flex";
162
+ trackListContainer.style.flexDirection="column";
163
+ const trackList = document.createElement('div');
164
+ trackList.style.width = "100%";
165
+ trackList.style.height = "100%";
166
+ trackList.style.overflowY= "scroll";
167
+ trackList.style.display="flex";
168
+ trackList.style.flexDirection="column";
169
+ trackList.style.flexGrow="1";
170
+ const trackControls = document.createElement('div');
171
+ trackControls.style.display="flex";
172
+ trackControls.style.flexDirection="row";
173
+ trackControls.style.width = "100%";
174
+ trackControls.style.height = "50px";
175
+ trackControls.style.minHeight = "50px";
176
+ const allTrackBtn = document.createElement('button');
177
+ allTrackBtn.textContent = "All";
178
+ allTrackBtn.style.width = "50%";
179
+ allTrackBtn.style.height = "100%";
180
+ allTrackBtn.style.backgroundColor = "rgba(200, 200, 200, 0.3)";
181
+ allTrackBtn.style.color = 'inherit';
182
+ allTrackBtn.style.border = "none";
183
+ allTrackBtn.style.cursor = 'pointer';
184
+ let self = this;
185
+ allTrackBtn.onclick = function (){
186
+ self.trackMap.forEach((track, id) => {
187
+ track.setChecked(true);
188
+ })
189
+ };
190
+ const noneTrackBtn = document.createElement('button');
191
+ noneTrackBtn.textContent = "None";
192
+ noneTrackBtn.style.width = "50%";
193
+ noneTrackBtn.style.height = "100%";
194
+ noneTrackBtn.style.backgroundColor = "rgba(200, 200, 200, 0.3)";
195
+ noneTrackBtn.style.color = 'inherit';
196
+ noneTrackBtn.style.border = "none";
197
+ noneTrackBtn.style.cursor = 'pointer';
198
+ noneTrackBtn.onclick = function (){
199
+ self.trackMap.forEach((track, id) => {
200
+ track.setChecked(false);
201
+ });
202
+ };
203
+ const pianoRoll = document.createElement('div');
204
+ pianoRoll.style.overflowX= "scroll";
205
+ pianoRoll.style.flexGrow="1";
206
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
207
+ svg.style.height = `${this.config.noteHeight*128}px`;
208
+ svg.style.width = `${this.svgWidth}px`;
209
+ const timeLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
210
+ timeLine.style.stroke = "green"
211
+ timeLine.style.strokeWidth = "2";
212
+
213
+ if (isMobile()){
214
+ trackListContainer.style.display = "none";
215
+ timeLine.style.strokeWidth = "1";
216
+ }
217
+ shadow.appendChild(style)
218
+ shadow.appendChild(container);
219
+ container.appendChild(trackListContainer);
220
+ trackListContainer.appendChild(trackList);
221
+ trackListContainer.appendChild(trackControls);
222
+ trackControls.appendChild(allTrackBtn);
223
+ trackControls.appendChild(noneTrackBtn);
224
+ container.appendChild(pianoRoll);
225
+ pianoRoll.appendChild(svg);
226
+ svg.appendChild(timeLine)
227
+ this.container = container;
228
+ this.trackList = trackList;
229
+ this.pianoRoll = pianoRoll;
230
+ this.svg = svg;
231
+ this.timeLine= timeLine;
232
+ for(let i = 0; i < 128 ; i++){
233
+ this.colorMap.set(i, HSVtoRGB(i / 128, 1, 1))
234
+ }
235
+ this.setPlayTime(0);
236
+ }
237
+
238
+ addTrack(id, tr, cl, name, color){
239
+ const track = {id, tr, cl, name, color, empty: true,
240
+ lastCC: new Map(),
241
+ instrument: cl===9?"Standard Drum":"Acoustic Grand",
242
+ svg: document.createElementNS('http://www.w3.org/2000/svg', 'g'),
243
+ ccPaths: new Map()
244
+ }
245
+ this.svg.appendChild(track.svg)
246
+ const trackItem = this.createTrackItem(track);
247
+ this.trackList.appendChild(trackItem);
248
+ this.trackMap.set(id, track);
249
+ return track;
250
+ }
251
+
252
+ getTrack(tr, cl){
253
+ const id = tr * 16 + cl
254
+ let track = this.trackMap.get(id)
255
+ if (!!track){
256
+ return track
257
+ }
258
+ let color = this.colorMap.get((this.trackMap.size*53)%128)
259
+ return this.addTrack(id, tr, cl, `Track ${tr}, Channel ${cl}`, color)
260
+ }
261
+
262
+ createTrackItem(track) {
263
+ const trackItem = document.createElement('div');
264
+ trackItem.style.display = 'flex';
265
+ trackItem.style.alignItems = 'center';
266
+ trackItem.style.width = '100%';
267
+ trackItem.style.position = 'relative';
268
+
269
+ const colorBar = document.createElement('div');
270
+ colorBar.style.width = '5%';
271
+ colorBar.style.height = '100%';
272
+ colorBar.style.position = 'absolute';
273
+ colorBar.style.left = '0';
274
+ colorBar.style.top = '0';
275
+ let color = track.color;
276
+ colorBar.style.backgroundColor = `rgb(${color.r}, ${color.g}, ${color.b})`;
277
+ trackItem.appendChild(colorBar);
278
+
279
+ const content = document.createElement('div');
280
+ content.style.paddingLeft = '30px';
281
+ content.style.flexGrow = '1';
282
+ content.style.color = "grey"
283
+ content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
284
+ trackItem.appendChild(content);
285
+ track.updateInstrument = function (instrument){
286
+ track.instrument = instrument;
287
+ content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
288
+ }
289
+ track.setEmpty = function (empty){
290
+ if (empty!==track.empty){
291
+ content.style.color = empty?"grey":"inherit";
292
+ }
293
+ }
294
+
295
+ const toggleSwitch = document.createElement('input');
296
+ toggleSwitch.type = 'checkbox';
297
+ toggleSwitch.checked = true;
298
+ toggleSwitch.style.marginLeft = 'auto';
299
+ toggleSwitch.style.marginRight = '10px';
300
+ toggleSwitch.style.width = '20px';
301
+ toggleSwitch.style.height = '20px';
302
+ toggleSwitch.style.cursor = 'pointer';
303
+
304
+ toggleSwitch.onchange = function () {
305
+ track.svg.setAttribute('visibility',toggleSwitch.checked? "visible" : "hidden")
306
+ };
307
+ track.setChecked = function (checked){
308
+ toggleSwitch.checked = checked;
309
+ track.svg.setAttribute('visibility',toggleSwitch.checked? "visible" : "hidden")
310
+ }
311
+ trackItem.appendChild(toggleSwitch);
312
+ return trackItem;
313
+ }
314
+
315
+ clearMidiEvents(){
316
+ this.pause()
317
+ this.midiEvents = [];
318
+ this.activeNotes = [];
319
+ this.midiTimes = [];
320
+ this.trackMap = new Map()
321
+ this.patches = [];
322
+ for (let i=0;i<16;i++){
323
+ this.patches.push([[0,0]])
324
+ }
325
+ this.t1 = 0
326
+ this.setPlayTime(0);
327
+ this.totalTimeMs = 0;
328
+ this.playTimeMs = 0
329
+ this.lastUpdateTime = 0
330
+ this.trackList.innerHTML = ''
331
+ this.svgWidth = 0
332
+ this.svg.innerHTML = ''
333
+ this.svg.style.width = `${this.svgWidth}px`;
334
+ this.svg.appendChild(this.timeLine)
335
+ }
336
+
337
+ appendMidiEvent(midiEvent){
338
+ if(midiEvent instanceof Array && midiEvent.length > 0){
339
+
340
+ this.t1 += midiEvent[1]
341
+ let t = this.t1*this.timePreBeat + midiEvent[2]
342
+ midiEvent = [midiEvent[0], t].concat(midiEvent.slice(3))
343
+ if(midiEvent[0] === "note"){
344
+ let track = midiEvent[2]
345
+ let duration = 0
346
+ let channel = 0
347
+ let pitch = 0
348
+ let velocity = 0
349
+ if(this.version === "v1"){
350
+ duration = midiEvent[3]
351
+ channel = midiEvent[4]
352
+ pitch = midiEvent[5]
353
+ velocity = midiEvent[6]
354
+ }else if (this.version === "v2"){
355
+ channel = midiEvent[3]
356
+ pitch = midiEvent[4]
357
+ velocity = midiEvent[5]
358
+ duration = midiEvent[6]
359
+ }
360
+ let vis_track = this.getTrack(track, channel);
361
+ vis_track.setEmpty(false);
362
+ let x = (t/this.timePreBeat)*this.config.beatWidth
363
+ let y = (127 - pitch)*this.config.noteHeight
364
+ let w = (duration/this.timePreBeat)*this.config.beatWidth
365
+ let h = this.config.noteHeight
366
+ this.svgWidth = Math.ceil(Math.max(x + w, this.svgWidth))
367
+ let opacity = Math.min(1, velocity/127 + 0.1).toFixed(2)
368
+ let rect = this.drawNote(vis_track, x,y,w,h, opacity)
369
+ midiEvent.push(rect);
370
+ this.setPlayTime(t);
371
+ this.pianoRoll.scrollTo(this.svgWidth - this.pianoRoll.offsetWidth, this.pianoRoll.scrollTop)
372
+ }else if(midiEvent[0] === "patch_change"){
373
+ let track = midiEvent[2];
374
+ let channel = midiEvent[3];
375
+ this.patches[channel].push([t, midiEvent[4]]);
376
+ this.patches[channel].sort((a, b) => a[0] - b[0]);
377
+ this.getTrack(track, channel);
378
+ }else if(midiEvent[0] === "control_change"){
379
+ let track = midiEvent[2];
380
+ let channel = midiEvent[3];
381
+ let controller = midiEvent[4];
382
+ let value = midiEvent[5];
383
+ let vis_track = this.getTrack(track, channel);
384
+ this.drawCC(vis_track, t, controller, value);
385
+ this.setPlayTime(t);
386
+ }
387
+ this.midiEvents.push(midiEvent);
388
+ this.svg.style.width = `${this.svgWidth}px`;
389
+ }
390
+
391
+ }
392
+
393
+ drawNote(track, x, y, w, h, opacity) {
394
+ if (!track.svg) {
395
+ return null;
396
+ }
397
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
398
+ rect.classList.add('note');
399
+ const color = track.color;
400
+ rect.setAttribute('fill', `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`);
401
+ // Round values to the nearest integer to avoid partially filled pixels.
402
+ rect.setAttribute('x', `${Math.round(x)}`);
403
+ rect.setAttribute('y', `${Math.round(y)}`);
404
+ rect.setAttribute('width', `${Math.round(w)}`);
405
+ rect.setAttribute('height', `${Math.round(h)}`);
406
+ track.svg.appendChild(rect);
407
+ return rect
408
+ }
409
+
410
+ drawCC(track, t, controller, value){
411
+ if (!track.svg) {
412
+ return null;
413
+ }
414
+ let path = track.ccPaths.get(controller);
415
+ let x = (t/this.timePreBeat)*this.config.beatWidth
416
+ let y = (127 - value)*this.config.noteHeight
417
+ if (!path){
418
+ path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
419
+ path.setAttribute('visibility',"hidden");
420
+ path.setAttribute('fill', "transparent");
421
+ const color = track.color;
422
+ path.setAttribute('stroke', `rgba(${color.r}, ${color.g}, ${color.b}, 0.6)`);
423
+ path.setAttribute('stroke-width', "1");
424
+ path.setAttribute('d',
425
+ t===0?`M ${x} ${y}`:`M 0 ${127*this.config.noteHeight} H ${x} V ${y}`);
426
+ track.svg.appendChild(path);
427
+ track.ccPaths.set(controller, path);
428
+ track.lastCC.set(controller, value);
429
+ return path;
430
+ }
431
+ let lastVal = track.lastCC.get(controller);
432
+ if(lastVal !== value){
433
+ path.removeAttribute('visibility');
434
+ }
435
+ let d = path.getAttribute("d");
436
+ d += `H ${x} V ${y}`
437
+ path.setAttribute('d', d);
438
+ return path
439
+ }
440
+
441
+ finishAppendMidiEvent(){
442
+ this.pause()
443
+ let midiEvents = this.midiEvents.sort((a, b)=>a[1]-b[1])
444
+ let tempo = (60 / 120) * 10 ** 3
445
+ let ms = 0
446
+ let lastT = 0
447
+ this.midiTimes.push({ms:ms, t: 0, tempo: tempo})
448
+ midiEvents.forEach((midiEvent)=>{
449
+ let t = midiEvent[1]
450
+ ms += ((t- lastT) / this.timePreBeat) * tempo
451
+ if(midiEvent[0]==="set_tempo"){
452
+ tempo = (60 / midiEvent[3]) * 10 ** 3
453
+ this.midiTimes.push({ms:ms, t: t, tempo: tempo})
454
+ }
455
+ if(midiEvent[0]==="note"){
456
+ this.totalTimeMs = Math.max(this.totalTimeMs, ms + (midiEvent[3]/ this.timePreBeat)*tempo)
457
+ }else{
458
+ this.totalTimeMs = Math.max(this.totalTimeMs, ms);
459
+ }
460
+ lastT = t;
461
+ })
462
+ let x = (lastT/this.timePreBeat)*this.config.beatWidth;
463
+ this.trackMap.forEach((track, id)=>{
464
+ track.ccPaths.forEach((path, controller)=>{
465
+ let d = path.getAttribute("d");
466
+ d += `H ${x}`
467
+ path.setAttribute('d', d);
468
+ })
469
+ })
470
+ }
471
+
472
+ setPlayTime(t){
473
+ this.playTime = t
474
+ let x = Math.round((t/this.timePreBeat)*this.config.beatWidth)
475
+ this.timeLine.setAttribute('x1', `${x}`);
476
+ this.timeLine.setAttribute('y1', '0');
477
+ this.timeLine.setAttribute('x2', `${x}`);
478
+ this.timeLine.setAttribute('y2', `${this.config.noteHeight*128}`);
479
+
480
+ this.pianoRoll.scrollTo(Math.max(0, x - this.pianoRoll.offsetWidth/2), this.pianoRoll.scrollTop)
481
+
482
+ this.trackMap.forEach((track, id)=>{
483
+ let instrument = track.instrument
484
+ let cl = track.cl;
485
+ let patches = this.patches[cl]
486
+ let p = 0
487
+ for (let i = 0; i < patches.length ; i++){
488
+ let tp = patches[i]
489
+ if (t < tp[0])
490
+ break
491
+ p = tp[1]
492
+ }
493
+ if (cl === 9){
494
+ let drumKit = number2drum_kits[`${p}`];
495
+ if (!!drumKit)
496
+ instrument = drumKit + " Drum";
497
+ }else{
498
+ instrument = number2patch[p]
499
+ }
500
+ if (instrument !== track.instrument)
501
+ track.updateInstrument(instrument)
502
+ });
503
+
504
+ let dt = Date.now() - this.lastUpdateTime; // limit the update rate of ActiveNotes
505
+ if(this.playing && dt > 50){
506
+ let activeNotes = []
507
+ this.removeActiveNotes(this.activeNotes)
508
+ this.midiEvents.forEach((midiEvent)=>{
509
+ if(midiEvent[0] === "note"){
510
+ let time = midiEvent[1]
511
+ let duration = this.version==="v1"? midiEvent[3]:midiEvent[6]
512
+ let note = midiEvent[midiEvent.length - 1]
513
+ if(time <=this.playTime && time+duration>= this.playTime){
514
+ activeNotes.push(note)
515
+ }
516
+ }
517
+ });
518
+ this.addActiveNotes(activeNotes)
519
+ this.lastUpdateTime = Date.now();
520
+ }
521
+
522
+ }
523
+
524
+ setPlayTimeMs(ms){
525
+ this.playTimeMs = ms
526
+ let playTime = 0
527
+ for(let i =0;i<this.midiTimes.length;i++){
528
+ let midiTime = this.midiTimes[i]
529
+ if(midiTime.ms>=ms){
530
+ break;
531
+ }
532
+ playTime = midiTime.t + (ms-midiTime.ms) * this.timePreBeat / midiTime.tempo
533
+ }
534
+ this.setPlayTime(playTime)
535
+ }
536
+
537
+ addActiveNotes(notes){
538
+ notes.forEach((note)=>{
539
+ this.activeNotes.push(note)
540
+ note.classList.add('active');
541
+ });
542
+ }
543
+
544
+ removeActiveNotes(notes){
545
+ notes.forEach((note)=>{
546
+ let idx = this.activeNotes.indexOf(note)
547
+ if(idx>-1)
548
+ this.activeNotes.splice(idx, 1);
549
+ note.classList.remove('active');
550
+ });
551
+ }
552
+
553
+ play(){
554
+ this.playing = true;
555
+ }
556
+
557
+ pause(){
558
+ this.removeActiveNotes(this.activeNotes)
559
+ this.playing = false;
560
+ }
561
+
562
+
563
+ bindAudioPlayer(audio){
564
+ this.pause()
565
+ audio.addEventListener("play", (event)=>{
566
+ this.play()
567
+ })
568
+ audio.addEventListener("pause", (event)=>{
569
+ this.pause()
570
+ })
571
+ audio.addEventListener("loadedmetadata", (event)=>{
572
+ //I don't know why the calculated totalTimeMs is different from audio.duration*10**3
573
+ this.totalTimeMs = audio.duration*10**3;
574
+ })
575
+ }
576
+
577
+ bindWaveformCursor(cursor){
578
+ let self = this;
579
+ const callback = function(mutationsList, observer) {
580
+ for(let mutation of mutationsList) {
581
+ if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
582
+ let progress = parseFloat(mutation.target.style.left.slice(0,-1))*0.01;
583
+ if(!isNaN(progress)){
584
+ self.setPlayTimeMs(progress*self.totalTimeMs);
585
+ }
586
+ }
587
+ }
588
+ };
589
+ const observer = new MutationObserver(callback);
590
+ observer.observe(cursor, {
591
+ attributes: true,
592
+ attributeFilter: ['style']
593
+ });
594
+ }
595
+ }
596
+
597
+ customElements.define('midi-visualizer', MidiVisualizer);
598
+
599
+ (()=>{
600
+ function midi_visualizer_setup(idx, midi_visualizer){
601
+ let midi_visualizer_container_inited = null
602
+ let midi_audio_audio_inited = null;
603
+ let midi_audio_cursor_inited = null;
604
+ onUiUpdate((m)=>{
605
+ let app = gradioApp()
606
+ let midi_visualizer_container = app.querySelector(`#midi_visualizer_container_${idx}`);
607
+ if(!!midi_visualizer_container && midi_visualizer_container_inited!== midi_visualizer_container){
608
+ midi_visualizer_container.appendChild(midi_visualizer)
609
+ midi_visualizer_container_inited = midi_visualizer_container;
610
+ }
611
+ let midi_audio = app.querySelector(`#midi_audio_${idx}`);
612
+ if (!!midi_audio){
613
+ let midi_audio_cursor = midi_audio.deepQuerySelector(".cursor");
614
+ if(!!midi_audio_cursor && midi_audio_cursor_inited!==midi_audio_cursor){
615
+ midi_visualizer.bindWaveformCursor(midi_audio_cursor)
616
+ midi_audio_cursor_inited = midi_audio_cursor
617
+ }
618
+ let midi_audio_waveform = midi_audio.deepQuerySelector("#waveform");
619
+ if(!!midi_audio_waveform){
620
+ let midi_audio_audio = midi_audio_waveform.deepQuerySelector("audio");
621
+ if(!!midi_audio_audio && midi_audio_audio_inited!==midi_audio_audio){
622
+ midi_visualizer.bindAudioPlayer(midi_audio_audio)
623
+ midi_audio_audio_inited = midi_audio_audio
624
+ }
625
+ }
626
+ }
627
+ });
628
+ }
629
+
630
+ let midi_visualizers = []
631
+ for (let i = 0; i < MIDI_OUTPUT_BATCH_SIZE ; i++){
632
+ let midi_visualizer = document.createElement('midi-visualizer');
633
+ midi_visualizers.push(midi_visualizer);
634
+ midi_visualizer_setup(i, midi_visualizer)
635
+ }
636
+
637
+ let hasProgressBar = false;
638
+ let output_tabs_inited = null;
639
+ onUiUpdate((m)=>{
640
+ let app = gradioApp()
641
+ let output_tabs = app.querySelector("#output_tabs");
642
+ if(!!output_tabs && output_tabs_inited!== output_tabs){
643
+ output_tabs_inited = output_tabs;
644
+ }
645
+ });
646
+
647
+ function createProgressBar(progressbarContainer){
648
+ let parentProgressbar = progressbarContainer.parentNode;
649
+ let divProgress = document.createElement('div');
650
+ divProgress.className='progressDiv';
651
+ let rect = progressbarContainer.getBoundingClientRect();
652
+ divProgress.style.width = rect.width + "px";
653
+ divProgress.style.background = "#b4c0cc";
654
+ divProgress.style.borderRadius = "8px";
655
+ let divInner = document.createElement('div');
656
+ divInner.className='progress';
657
+ divInner.style.color = "white";
658
+ divInner.style.background = "#0060df";
659
+ divInner.style.textAlign = "right";
660
+ divInner.style.fontWeight = "bold";
661
+ divInner.style.borderRadius = "8px";
662
+ divInner.style.height = "20px";
663
+ divInner.style.lineHeight = "20px";
664
+ divInner.style.paddingRight = "8px"
665
+ divInner.style.width = "0%";
666
+ divProgress.appendChild(divInner);
667
+ parentProgressbar.insertBefore(divProgress, progressbarContainer);
668
+ hasProgressBar = true;
669
+ }
670
+
671
+ function removeProgressBar(progressbarContainer){
672
+ let parentProgressbar = progressbarContainer.parentNode;
673
+ let divProgress = parentProgressbar.querySelector(".progressDiv");
674
+ parentProgressbar.removeChild(divProgress);
675
+ hasProgressBar = false;
676
+ }
677
+
678
+ function setProgressBar(progress, total){
679
+ if (!hasProgressBar)
680
+ createProgressBar(output_tabs_inited)
681
+ if (hasProgressBar && total === 0){
682
+ removeProgressBar(output_tabs_inited)
683
+ return
684
+ }
685
+ let parentProgressbar = output_tabs_inited.parentNode;
686
+ // let divProgress = parentProgressbar.querySelector(".progressDiv");
687
+ let divInner = parentProgressbar.querySelector(".progress");
688
+ if(total===0)
689
+ total = 1;
690
+ divInner.style.width = `${(progress/total)*100}%`;
691
+ divInner.textContent = `${progress}/${total}`;
692
+ }
693
+
694
+ onMsgReceive((msgs)=>{
695
+ for(let msg of msgs){
696
+ if(msg instanceof Array){
697
+ msg.forEach((o)=>{handleMsg(o)});
698
+ }else{
699
+ handleMsg(msg);
700
+ }
701
+ }
702
+ })
703
+ function handleMsg(msg){
704
+ let idx;
705
+ switch (msg.name) {
706
+ case "visualizer_clear":
707
+ idx = msg.data[0];
708
+ let ver = msg.data[1];
709
+ midi_visualizers[idx].clearMidiEvents(false);
710
+ midi_visualizers[idx].version = ver;
711
+ break;
712
+ case "visualizer_append":
713
+ idx = msg.data[0];
714
+ let events = msg.data[1];
715
+ events.forEach( value => {
716
+ midi_visualizers[idx].appendMidiEvent(value);
717
+ })
718
+ break;
719
+ case "visualizer_end":
720
+ idx = msg.data;
721
+ midi_visualizers[idx].finishAppendMidiEvent()
722
+ midi_visualizers[idx].setPlayTime(0);
723
+ break;
724
+ case "progress":
725
+ let progress = msg.data[0]
726
+ let total = msg.data[1]
727
+ setProgressBar(progress, total)
728
+ break;
729
+ default:
730
+ }
731
+ }
732
+ })();