Google Music Visualization

Let Google's currency historical chart in the webpage to play music and display visualizations, unuseful tool.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Google Music Visualization
// @name:zh-TW   Google 音樂視覺化
// @name:zh-CN   Google 音乐视觉化
// @namespace    https://github.com/maplelan/Maplelan_Tampermonkey_Script/blob/main/Google%20%E8%A6%96%E8%A6%BA%E5%8C%96.user.js
// @version      1.0
// @description  Let Google's currency historical chart in the webpage to play music and display visualizations, unuseful tool.
// @description:zh-TW  讓Google頁面中的幣值歷史走線圖可以撥放音樂並顯示視覺化 沒有任何實際有用的功能
// @description:zh-CN  让Google页面中的币值历史走线图可以拨放音乐并显示视觉化 没有任何实际有用的功能
// @author       Maplelan
// @license      MIT
// @match        https://www.google.com/search?q=*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// ==/UserScript==

(function() {
    setTimeout(()=>{
        let svg = document.getElementsByClassName("uch-psvg");
        console.log(svg);
        for(let i=0;i<svg.length;i++){
            let svg_item = svg[i];
            let path = svg_item.getElementsByTagName("path");
            if(path.length >= 2){
                let main_path = path[1];
                let vb = svg_item.getAttribute("viewBox").split(" ");
                let vb_w = parseFloat(vb[2]),vb_h = parseFloat(vb[3]);
                let d = main_path.getAttribute("d");
                let L = d.split("L");
                let header = {},content = [],end = [];
                for(let j=0;j<L.length;j++){
                    if(L[j].startsWith("M")){
                        let M = L[j].trim().split(" ");
                        header.x = parseFloat(M[1]);
                        header.y = parseFloat(M[2]);
                    }else{
                        let C = L[j].trim().split(" ");
                        let item = {};
                        item.x = parseFloat(C[0]);
                        item.y = parseFloat(C[1]);
                        if(item.x <= vb_w && item.x >= 0){
                            content.push(item);
                        }else{
                            end.push(item);
                        }
                    }
                }
                console.log(d);
                let top = svg_item.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
                let btpos = top.firstChild.firstChild;
                let numb_place = top.parentNode.firstChild.firstChild.lastChild.firstChild;
                const or_num = parseFloat(numb_place.innerText);

                console.log(or_num);
                console.log(btpos);
                const file = document.createElement('input');
                file.type = "file";
                file.accept="audio/*";
                btpos.append(file);

                const audio = document.createElement('audio');
                audio.controls = true;;
                btpos.append(audio);

                const select = document.createElement('select');
                select.innerHTML = `<option value="FloatFrequency">FloatFrequency</option>
                             <option value="FloatTimeDomain">FloatTimeDomain</option>
                             <option value="ByteFrequency">ByteFrequency</option>
                             <option value="ByteTimeDomain">ByteTimeDomain</option>`;
                btpos.append(select);

                let aniID = null;
                file.onchange = function() {
                    console.log("file");
                    if(aniID != null){
                        cancelAnimationFrame(aniID);
                    }
                    let files = this.files;
                    audio.src = URL.createObjectURL(files[0]);
                    audio.load();
                    audio.play();

                    let vis_type = "FloatFrequency";
                    let context = new AudioContext();
                    let src = context.createMediaElementSource(audio);
                    let analyser = context.createAnalyser();

                    src.connect(analyser);
                    analyser.connect(context.destination);

                    console.log(select.value);
                    vis_type = select.value;
                    switch(vis_type){
                        case "FloatFrequency":{
                            analyser.fftSize = 2048;

                            break;
                        }
                        case "ByteFrequency":{
                            analyser.fftSize = 128;

                            break;
                        }
                        case "FloatTimeDomain":
                        case "ByteTimeDomain":{
                            analyser.fftSize = 8192;

                            break;
                        }
                    }

                    let bufferLength = analyser.frequencyBinCount;
                    console.log(bufferLength);

                    let dataArray = new Uint8Array(bufferLength);
                    switch(vis_type){
                        case "FloatFrequency":
                        case "FloatTimeDomain":{
                            dataArray = new Float32Array(bufferLength);
                            break;
                        }
                        case "ByteFrequency":
                        case "ByteTimeDomain":{
                            dataArray = new Uint8Array(bufferLength);
                            break;
                        }
                    }

                    function renderFrame() {
                        requestAnimationFrame(renderFrame);

                        let start = 0,to_end = bufferLength;

                        switch(vis_type){
                            case "FloatFrequency":{
                                analyser.getFloatFrequencyData(dataArray);
                                break;
                            }
                            case "FloatTimeDomain":{
                                analyser.getFloatTimeDomainData(dataArray);

                                start = 512;
                                to_end = bufferLength-start;

                                break;
                            }
                            case "ByteFrequency":{
                                analyser.getByteFrequencyData(dataArray);
                                break;
                            }
                            case "ByteTimeDomain":{
                                analyser.getByteTimeDomainData(dataArray);

                                start = 1024;
                                to_end = bufferLength-start;

                                break;
                            }
                        }
                        //console.log(dataArray);


                        let evn = 0,a_max,a_min;
                        let H,C=[];
                        for (let i = start; i < to_end; i++) {
                            let barHeight = dataArray[i];
                            let item = {};
                            item.x = map_range(i,start,to_end-1,0,vb_w);

                            //console.log(i + " " + item.x);

                            switch(vis_type){
                                case "FloatFrequency":{
                                    item.y = vb_h - map_range(barHeight,-170,-10,0,vb_h);
                                    break;
                                }
                                case "FloatTimeDomain":{
                                    const v = dataArray[i] * 50;
                                    item.y = (vb_h / 2) + v;

                                    break;
                                }
                                case "ByteFrequency":{
                                    item.y = vb_h - map_range(barHeight,0,255,0,vb_h);
                                    break;
                                }
                                case "ByteTimeDomain":{
                                    const v = dataArray[i] / 128.0;
                                    item.y = vb_h - (v * (vb_h / 2));

                                    break;
                                }
                            }

                            evn += barHeight;
                            if(i==start){
                                H = item;
                                a_max = dataArray[i];
                                a_min = dataArray[i];
                            }else{
                                C.push(item);
                                if(dataArray[i] > a_max){
                                    a_max = dataArray[i];
                                }
                                if(dataArray[i] < a_min){
                                    a_min = dataArray[i];
                                }
                            }
                        }

                        switch(vis_type){
                            case "ByteFrequency":
                            case "ByteTimeDomain":{
                                evn = (evn/(to_end-start))/255;
                                a_max = a_max - 128;
                                a_min = a_min - 128;
                                break;
                            }
                            case "FloatFrequency":
                            case "FloatTimeDomain":{
                                evn = map_range(evn/(to_end-start),-150,-30,0,100);

                                a_max = a_max - ((a_max+a_min)/2);
                                a_min = a_min - ((a_max+a_min)/2);
                                break;
                            }
                        }

                        let svg_r = document.getElementsByClassName("uch-psvg");
                        for(let i_r=0;i_r<svg_r.length;i_r++){
                            let svg_item_r = svg_r[i_r];
                            let path_r = svg_item_r.getElementsByTagName("path");
                            if(path_r.length >= 2){
                                let main_path_r = path_r[1];
                                main_path_r.setAttribute("d",merg(H,C,end));
                                let top_r = svg_item_r.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
                                let numb_place_r = top_r.parentNode.firstChild.firstChild.lastChild.firstChild;
                                const max = parseFloat(svg_item_r.parentNode.parentNode.firstChild.childNodes[2].lastChild.textContent);
                                const min = parseFloat(svg_item_r.parentNode.parentNode.firstChild.firstChild.lastChild.textContent);
                                //console.log(max);

                                let range = 0;

                                switch(vis_type){
                                    case "FloatFrequency":{
                                        range = (evn*(max-min));

                                        break;
                                    }
                                    case "FloatTimeDomain":{
                                        range = (((Math.abs(a_max) > Math.abs(a_min)) ? a_max : a_min)*(max-min))*8;

                                        break;
                                    }
                                    case "ByteFrequency":{
                                        range = (evn*100*(max-min))*0.7;

                                        break;
                                    }
                                    case "ByteTimeDomain":{
                                        range = (((Math.abs(a_max) > Math.abs(a_min)) ? a_max : a_min)*(max-min))*0.1;

                                        break;
                                    }
                                }

                                numb_place_r.textContent = (or_num+range).toFixed(2);
                                //console.log((or_num+range).toFixed(2));
                            }
                        }
                    }

                    audio.play();
                    aniID = window.requestAnimationFrame(renderFrame());
                };
                //console.log(header);
                //console.log(content);
                //console.log(end);
                //randC(header,content,vb_h);
                /*setTimeout(()=>{
                    console.log(merg(header,content,end));
                    main_path.setAttribute("d",merg(header,content,end));
                }, 3000);*/
            }
        }
    }, 1000);
})();

function merg(H,C,E){
    let str = "M " + xystr(H);
    C.forEach(item => {
        str += " L " + xystr(item);
    });
    E.forEach(item => {
        str += " L " + xystr(item);
    });
    return str;
}

function xystr(xy){
    return xy.x + " " + xy.y;
}

function randC(H,C,max){
    H.y = random(0,max,2);
    for(let i=0;i<C.length;i++){
        C[i].y = random(0,max,2);
    }
}

function random(min, max, dec = 0){//隨機函式 min=最小值 max=最大值 dec=小數點後的位數(預設為0)
    let usedec = Math.pow(10, dec);
    let maxc = Math.floor(max * usedec);
    let minc = min < max ? Math.floor(min * usedec) : 0;
    return (Math.floor(Math.random() * (maxc - minc + 1)) + minc) / usedec;
}

function map_range(value, low1, high1, low2, high2) {
    if(value < low1){
        return low2;
    }
    return low2 + (high2 - low2) * (value - low1) / (high1 - low1);
}