CoolFace
Apppublic

cocktailpeanut/midi-composer

sourceHugging Faceapache-2.0updated 2y agoView on Hugging Face
0likes
app.js730 linesDownload Raw Back to javascript
1const MIDI_OUTPUT_BATCH_SIZE=4;2//Do not change MIDI_OUTPUT_BATCH_SIZE. It will be automatically replaced.3 4/**5 * 自动绕过 shadowRoot 的 querySelector6 * @param {string} selector - 要查询的 CSS 选择器7 * @returns {Element|null} - 匹配的元素或 null 如果未找到8 */9function 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 DOM24    const shadowHosts = root.querySelectorAll('*');25 26    for (let i = 0; i < shadowHosts.length; i++) {27      const host = shadowHosts[i];28 29      // 检查当前元素是否有 shadowRoot30      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 44Element.prototype.deepQuerySelector = deepQuerySelector;45Document.prototype.deepQuerySelector = deepQuerySelector;46 47function gradioApp() {48    const elems = document.getElementsByTagName('gradio-app')49    const gradioShadowRoot = elems.length == 0 ? null : elems[0].shadowRoot50    return !!gradioShadowRoot ? gradioShadowRoot : document;51}52 53uiUpdateCallbacks = []54msgReceiveCallbacks = []55 56function onUiUpdate(callback){57    uiUpdateCallbacks.push(callback)58}59 60function onMsgReceive(callback){61    msgReceiveCallbacks.push(callback)62}63 64function runCallback(x, m){65    try {66        x(m)67    } catch (e) {68        (console.error || console.log).call(console, e.message, e);69    }70}71function executeCallbacks(queue, m) {72    queue.forEach(function(x){runCallback(x, m)})73}74 75document.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 82function 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 104function isMobile(){105  return /(iPhone|iPad|iPod|iOS|Android|Windows Phone)/i.test(navigator.userAgent);106}107 108const 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']109const number2drum_kits = {0: "Standard", 8: "Room", 16: "Power", 24: "Electric", 25: "TR-808", 32: "Jazz", 40: "Blush", 48: "Orchestra"}110 111class 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 = null124        this.pianoRoll = null;125        this.svg = null;126        this.timeLine = null;127        this.config = {128            noteHeight : 4,129            beatWidth: 32130        }131        if (isMobile()){132            this.config.noteHeight = 1;133            this.config.beatWidth = 16;134        }135        this.timePreBeat = 16136        this.svgWidth = 0;137        this.t1 = 0;138        this.totalTimeMs = 0139        this.playTime = 0140        this.playTimeMs = 0141        this.lastUpdateTime = 0142        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 + cl254        let track = this.trackMap.get(id)255        if (!!track){256            return track257        }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 = 0326        this.setPlayTime(0);327        this.totalTimeMs = 0;328        this.playTimeMs = 0329        this.lastUpdateTime = 0330        this.trackList.innerHTML = ''331        this.svgWidth = 0332        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 = 0346                let channel = 0347                let pitch = 0348                let velocity = 0349                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.beatWidth363                let y = (127 - pitch)*this.config.noteHeight364                let w = (duration/this.timePreBeat)*this.config.beatWidth365                let h = this.config.noteHeight366                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 rect408    }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.beatWidth416        let y = (127 - value)*this.config.noteHeight417        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 path439    }440 441    finishAppendMidiEvent(){442        this.pause()443        let midiEvents = this.midiEvents.sort((a, b)=>a[1]-b[1])444        let tempo = (60 / 120) * 10 ** 3445        let ms = 0446        let lastT = 0447        this.midiTimes.push({ms:ms, t: 0, tempo: tempo})448        midiEvents.forEach((midiEvent)=>{449            let t = midiEvent[1]450            ms += ((t- lastT) / this.timePreBeat) * tempo451            if(midiEvent[0]==="set_tempo"){452                tempo = (60 / midiEvent[3]) * 10 ** 3453                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 = t474        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.instrument484            let cl = track.cl;485            let patches = this.patches[cl]486            let p = 0487            for (let i = 0; i < patches.length ; i++){488                let tp = patches[i]489                if (t < tp[0])490                    break491                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 ActiveNotes505        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 = ms526        let playTime = 0527        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.tempo533        }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**3573            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 597customElements.define('midi-visualizer', MidiVisualizer);598 599(()=>{600    function midi_visualizer_setup(idx, midi_visualizer){601        let midi_visualizer_container_inited = null602        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_cursor617                }618                let midi_audio_audio = midi_audio.deepQuerySelector("audio");619                if(!!midi_audio_audio && midi_audio_audio_inited!==midi_audio_audio){620                    midi_visualizer.bindAudioPlayer(midi_audio_audio)621                    midi_audio_audio_inited = midi_audio_audio622                }623            }624        });625    }626 627    let midi_visualizers = []628    for (let i = 0; i < MIDI_OUTPUT_BATCH_SIZE ; i++){629        let midi_visualizer = document.createElement('midi-visualizer');630        midi_visualizers.push(midi_visualizer);631        midi_visualizer_setup(i, midi_visualizer)632    }633 634    let hasProgressBar = false;635    let output_tabs_inited = null;636    onUiUpdate((m)=>{637        let app = gradioApp()638        let output_tabs = app.querySelector("#output_tabs");639        if(!!output_tabs && output_tabs_inited!== output_tabs){640            output_tabs_inited = output_tabs;641        }642    });643 644    function createProgressBar(progressbarContainer){645        let parentProgressbar = progressbarContainer.parentNode;646        let divProgress = document.createElement('div');647        divProgress.className='progressDiv';648        let rect = progressbarContainer.getBoundingClientRect();649        divProgress.style.width = rect.width + "px";650        divProgress.style.background = "#b4c0cc";651        divProgress.style.borderRadius = "8px";652        let divInner = document.createElement('div');653        divInner.className='progress';654        divInner.style.color = "white";655        divInner.style.background = "#0060df";656        divInner.style.textAlign = "right";657        divInner.style.fontWeight = "bold";658        divInner.style.borderRadius = "8px";659        divInner.style.height = "20px";660        divInner.style.lineHeight = "20px";661        divInner.style.paddingRight = "8px"662        divInner.style.width = "0%";663        divProgress.appendChild(divInner);664        parentProgressbar.insertBefore(divProgress, progressbarContainer);665        hasProgressBar = true;666    }667 668    function removeProgressBar(progressbarContainer){669        let parentProgressbar = progressbarContainer.parentNode;670        let divProgress = parentProgressbar.querySelector(".progressDiv");671        parentProgressbar.removeChild(divProgress);672        hasProgressBar = false;673    }674 675    function setProgressBar(progress, total){676        if (!hasProgressBar)677            createProgressBar(output_tabs_inited)678        if (hasProgressBar && total === 0){679            removeProgressBar(output_tabs_inited)680            return681        }682        let parentProgressbar = output_tabs_inited.parentNode;683        // let divProgress = parentProgressbar.querySelector(".progressDiv");684        let divInner = parentProgressbar.querySelector(".progress");685        if(total===0)686            total = 1;687        divInner.style.width = `${(progress/total)*100}%`;688        divInner.textContent = `${progress}/${total}`;689    }690 691    onMsgReceive((msgs)=>{692        for(let msg of msgs){693            if(msg instanceof Array){694                msg.forEach((o)=>{handleMsg(o)});695            }else{696                handleMsg(msg);697            }698        }699    })700    function handleMsg(msg){701        let idx;702        switch (msg.name) {703            case "visualizer_clear":704                idx = msg.data[0];705                let ver = msg.data[1];706                midi_visualizers[idx].clearMidiEvents(false);707                midi_visualizers[idx].version = ver;708                break;709            case "visualizer_append":710                idx = msg.data[0];711                let events = msg.data[1];712                events.forEach( value => {713                    midi_visualizers[idx].appendMidiEvent(value);714                })715                break;716            case "visualizer_end":717                idx = msg.data;718                midi_visualizers[idx].finishAppendMidiEvent()719                midi_visualizers[idx].setPlayTime(0);720                break;721            case "progress":722                let progress = msg.data[0]723                let total = msg.data[1]724                setProgressBar(progress, total)725                break;726            default:727        }728    }729})();730