備忘錄_20160105(定位) 修改 回首頁

程式 2022-05-04 21:40:30 1651671630 100
嘗試語音辨識 try speech recognition-step04

嘗試語音辨識 try speech recognition-step04

Cooley–Tukey FFT algorithm

偵測聲音,切割並繪製頻率振幅圖
偵測聲音,切割並繪製時間頻率圖(test)
20220506_Cooley–Tukey FFT algorithm - Wikipedia.pdf


●20220506.php

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
      
      *
      {
        font-family: "WenQuanYi Zen Hei","文泉驛正黑","Heiti TC","黑體-繁","LiHei Pro","儷黑 Pro","PingFang TC","Droid Sans","Roboto","Microsoft JhengHei","微軟正黑體",sans-serif;
        /* 【linux的字型】【ios字型】【android字型】【微軟正黑體】【無襯線字=黑體】 */
      }

      html, body 
      {
        margin: 0;
        border: 0;
        padding: 0;
        width: 100%;
        height: 100%;
      }

      a:link, a:visited
      {
        color: blue;
        text-decoration: none;
      }
      
      #divWrapper
      {
        padding: 1em;
      }
      
    </style>
  </head>
  <body>
    
    <div id="divWrapper">
      <div>
        <a target="_blank" href="https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm">Cooley–Tukey FFT algorithm</a><br>
        <a target="_blank" href="https://en.wikipedia.org/wiki/Bit-reversal_permutation">Bit-reversal permutation</a><br>
        <a target="_blank" href="https://zh.wikipedia.org/zh-tw/复数_(数学)">複數 (數學)</a><br>
        <a target="_blank" href="https://zh.wikipedia.org/zh-tw/快速傅里叶变换">快速傅立葉變換</a><br>
      </div>
      <div>
        <div id="divColor">●●●</div>
        <button type="button" onclick="goRecord();">錄音</button>
        <button type="button" onclick="goStop();">停止</button>
      </div>
      <div id="divOutput"></div>
    </div>
    
    <script>
      
      var iSampleRate=8000;
      var oMediaRecorder=null;
      var oaWords=null;
      var iIntervalId=null;
      var booStart=false;
      
      var oLastData=null;
      var booLastDataOkay=false;
      
      if(!String.prototype.ltrim) { String.prototype.ltrim = function() { return this.replace(/^\s+/,'');    }; }
      if(!String.prototype.rtrim) { String.prototype.rtrim = function() { return this.replace(/\s+$/,'');    }; }
      if(!String.prototype.trim ) { String.prototype.trim = function()  { return this.replace(/^\s+|\s+$/g,''); }; }
      
      function gebi(strId)
      {
        return document.getElementById(strId);
      }
      
      window.addEventListener(
        "load",
        function() 
        {
          if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) 
          {
            navigator.mediaDevices.getUserMedia({ audio: true})
              .then
              (
                function(oStream)
                {
                  oMediaRecorder=new MediaRecorder(oStream);
                  
                  oMediaRecorder.ondataavailable=function(oEvent)
                  {
                    console.log(oEvent.data);
                    oLastData=[];
                    oLastData.push(oEvent.data);
                    getBuffer();
                  }
                  
                  oMediaRecorder.onstop=function(oEvent)
                  {
                  }
                }
              )
              .catch
              (
                function(oErr)
                {
                  alert("遇到錯誤!"+oErr);
                }
              );
          }
          else
          {
            alert("您的瀏覽器不支援 getUserMedia!");
          }
        }
      );
      
      function goRecord()
      {
        gebi("divOutput").innerHTML="";
        booLastDataOkay=false;
        
        oaWords=[];
        oMediaRecorder.start();
        booStart=true;
        iIntervalId=window.setInterval(requestBlob, 100);
      }
      
      function goStop()
      {
        booStart=false;
        window.clearInterval(iIntervalId);
        console.log(oMediaRecorder.state);
        oMediaRecorder.stop();
        
        drawWords();
      }
      
      function requestBlob()
      {
        if(booStart==true)
        {
          var r=Math.floor(Math.random()*256);
          var g=Math.floor(Math.random()*256);
          var b=Math.floor(Math.random()*256);
          gebi("divColor").style.color="rgb("+r+","+g+","+b+")";
          oMediaRecorder.stop();
          oMediaRecorder.start();
        }
      }
      
      function getBuffer()
      {
        
        oLastData[0].arrayBuffer()
          .then
          (
            oBuffer=>
            {
              var oAC=new (window.AudioContext || window.webkitAudioContext)({sampleRate:iSampleRate});
              
              oAC.decodeAudioData(oBuffer)
                .then
                (
                  function(oDecodedData)
                  {
                    //for(var iChannel=0; iChannel<oDecodedData.numberOfChannels; iChannel++)
                    for(var iChannel=0; iChannel<1; iChannel++)
                    {
                      var faData=new Float32Array(oDecodedData.getChannelData(iChannel)); // -1 ~ +1
                      var fMin=Math.min(...faData);
                      var fMax=Math.max(...faData);
                      
                      if(((fMax-fMin)/2.0)>0.05)
                      {
                        if(booLastDataOkay==false)
                        {
                          oaWords.push([Array.from(faData)]);
                        }
                        else
                        {
                          oaWords[oaWords.length-1].push(Array.from(faData));
                        }
                        
                        booLastDataOkay=true;
                      }
                      else
                      {
                        /* output("silence"); */
                        
                        booLastDataOkay=false;
                      }
                    }
                    //output("......");
                  }
                )
                .catch
                (
                  function(oErr)
                  {
                    //output(oErr);
                  }
                );
  
                
            }
          )
          .catch
          (
            oErr=>{ output(oErr); }
          );
      }
      
      function output(strMessage)
      {
        gebi("divOutput").innerHTML=gebi("divOutput").innerHTML+"<br>\r\n"+strMessage;
      }
      
      function drawWords()
      {
        for(var iIdxWord=0; iIdxWord<oaWords.length; iIdxWord++)
        {
          var faCurrentWord=oaWords[iIdxWord][0];
          for(var iTmp1=1; iTmp1<oaWords[iIdxWord].length; iTmp1++)
          {
            faCurrentWord=faCurrentWord.concat(oaWords[iIdxWord][iTmp1]);
          }
          
          // 時域
          
          var oCanvas=document.createElement("canvas");
          oCanvas.setAttribute("width", 100*oaWords[iIdxWord].length);
          oCanvas.setAttribute("height", 200);
          var oCtx=oCanvas.getContext("2d");
          var iCanvasWidth=oCanvas.width;
          var iCanvasHeight=oCanvas.height;
          oCtx.beginPath();
          oCtx.rect(0,0,iCanvasWidth,iCanvasHeight);
          oCtx.fillStyle="black";
          oCtx.fill();
          
          oCtx.strokeStyle="white";
          oCtx.beginPath();
          var fMaxValue=2; // -1 ~ +1
          
          var iIdxData=0;
          var x=iIdxData/(faCurrentWord.length-1)*iCanvasWidth;
          var y=(fMaxValue-(faCurrentWord[iIdxData]+(fMaxValue/2)))/fMaxValue*iCanvasHeight;
          oCtx.moveTo(x,y);
          
          for(var iIdxData=1; iIdxData<faCurrentWord.length; iIdxData++)
          {
            x=iIdxData/(faCurrentWord.length-1)*iCanvasWidth;
            y=(fMaxValue-(faCurrentWord[iIdxData]+(fMaxValue/2)))/fMaxValue*iCanvasHeight;
            oCtx.lineTo(x,y)
          }
          oCtx.stroke();
          
          var oDiv3=document.createElement("div");
          oDiv3.innerHTML="y-amplitude(-1~1), x-time(seconds)";
          gebi("divOutput").appendChild(oDiv3);
          gebi("divOutput").appendChild(oCanvas);
          gebi("divOutput").appendChild(document.createElement("br"));
          
          
          // 頻域
          // Fourier Cosine Transform
          
          // 先準備陣列
          // 人耳 20Hz ~ 20000Hz 
          var o2={};
          o2.faSrc=faCurrentWord;
          iterative_fast_fourier_transform(o2);
          console.log(o2); // nmomtf
          
          // nmomtf
          
          // 準備陣列,兩邊是幾乎對稱的
          var faFreq=[];
          for(var iIdxData=0; iIdxData<(o2.oaDst.length/2); iIdxData++)
          {
            faFreq.push(o2.oaDst[iIdxData].v1);
          }
          for(var iIdxData=(o2.oaDst.length/2); iIdxData<o2.oaDst.length; iIdxData++)
          {
            faFreq[o2.oaDst.length-1-iIdxData]+=o2.oaDst[iIdxData].v1;
          }
          
          // 正規化,先取絕對值,然後再取對數
          for(var iIdx1=0; iIdx1<faFreq.length; iIdx1++)
          {
            faFreq[iIdx1]=Math.abs(faFreq[iIdx1]);
          }
          var fRefValue=Math.max(...faFreq);
          for(var iIdx1=0; iIdx1<faFreq.length; iIdx1++)
          {
            faFreq[iIdx1]=20*Math.log(faFreq[iIdx1]/fRefValue)/Math.log(10);
          }
          
          var fMinVal=Math.min(...faFreq);
          var fMaxVal=Math.max(...faFreq);
          console.log("check faFreq..."+fMinVal+" ~ "+fMaxVal); // nmomtf
          console.log(faFreq);
          
          // 畫圖
          
          var oCanvas=document.createElement("canvas");
          oCanvas.setAttribute("width", 200);
          oCanvas.setAttribute("height", 200);
          var oCtx=oCanvas.getContext("2d");
          var iCanvasWidth=oCanvas.width;
          var iCanvasHeight=oCanvas.height;
          oCtx.beginPath();
          oCtx.rect(0,0,iCanvasWidth,iCanvasHeight);
          oCtx.fillStyle="black";
          oCtx.fill();
          
          oCtx.strokeStyle="blue";
          oCtx.beginPath();
          var fMaxValue=fMaxVal-fMinVal; // fMinVal ~ fMaxVal

          for(var iIdxData=0; iIdxData<faFreq.length; iIdxData++)
          {
            x=iIdxData/(faFreq.length-1)*iCanvasWidth;
            y=(fMaxValue-(faFreq[iIdxData]-fMinVal))/fMaxValue*iCanvasHeight;
            oCtx.moveTo(x,y);
            oCtx.lineTo(x,iCanvasHeight);
          }
          oCtx.stroke();
          
          var oDiv1=document.createElement("div");
          oDiv1.innerHTML="y-logValue("+fMinVal+"~"+fMaxVal+"), x-frequency(Hz)";
          gebi("divOutput").appendChild(oDiv1);
          gebi("divOutput").appendChild(oCanvas);
          gebi("divOutput").appendChild(document.createElement("br"));
        }
      }
      
    </script>
    <script>
      
      function cn_add(oCN1,oCN2)
      {
        return getOComplexNumber(oCN1.v1+oCN2.v1, oCN1.v2+oCN2.v2);
      }
      
      function cn_sub(oCN1,oCN2)
      {
        return getOComplexNumber(oCN1.v1-oCN2.v1, oCN1.v2-oCN2.v2);
      }
      
      function cn_mul(oCN1,oCN2)
      {
        return getOComplexNumber(
          oCN1.v1*oCN2.v1-oCN1.v2*oCN2.v2,
          oCN1.v1*oCN2.v2+oCN1.v2*oCN2.v1);
      }
      
      function getOComplexNumber(v1,v2)
      {
        var o1={};
        o1.v1=v1;
        o1.v2=v2;
        return o1;
      }
      
      function getIReverseBits(iExponent, iValue)
      {
        var ia1=iValue.toString(2).padStart(iExponent,"0").split("").reverse();
        var iFinalValue=parseInt(ia1.join(""),2);
        return iFinalValue;
      }
      
      function bit_reverse_copy(oArrays)
      {
        // 補滿到2的指數數量
        var iN=oArrays.faSrc.length;
        var iExponent=Math.log2(iN);
        if(Number.isInteger(iExponent)==false)
        {
          iExponent=Math.floor(iExponent)+1;
        }
        iN=Math.pow(2,iExponent);
        var iBeginValue=oArrays.faSrc.length;
        for(var i=iBeginValue; i<iN; i++)
        {
          oArrays.faSrc.push(0);
        }
        
        // 開始複製陣列
        oArrays.oaDst=new Array(iN);
        for(iK=0; iK<iN; iK++)
        {
          oArrays.oaDst[getIReverseBits(iExponent,iK)]=getOComplexNumber(oArrays.faSrc[iK],0);
        }
      }
      
      function iterative_fast_fourier_transform(oArrays)
      {
        bit_reverse_copy(oArrays);
        var n=oArrays.oaDst.length;
        var sEnd=Math.log2(n); // nmomtf
        for(var s=1; s<=sEnd; s++)
        {
          var m=Math.pow(2,s);
          var wm=getOComplexNumber(Math.cos(-2*Math.PI/m),Math.sin(-2*Math.PI/m));
          for(var k=0; k<n; k+=m)
          {
            w=getOComplexNumber(1,0);
            for(var j=0; j<(m/2); j++)
            {
              var t=cn_mul(w,oArrays.oaDst[k+j+m/2]);
              var u=oArrays.oaDst[k+j];
              oArrays.oaDst[k+j]=cn_add(u,t);
              oArrays.oaDst[k+j+m/2]=cn_sub(u,t);
              w=cn_mul(w,wm);
            }
          }
        }
      }
      
    </script>
    
  </body>
</html>