Greasy Fork is available in English.

Google 音樂視覺化

讓Google頁面中的幣值歷史走線圖可以撥放音樂並顯示視覺化 沒有任何實際有用的功能

// ==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);
}