Google 音乐视觉化

让Google页面中的币值历史走线图可以拨放音乐并显示视觉化 没有任何实际有用的功能

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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