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

程式 2022-05-29 09:51:55 1653789115 100
製作類似卡拉OK字幕逐字亮起的流程─02聲音檔標記製作

製作類似卡拉OK字幕逐字亮起的流程─02聲音檔標記製作

  1. 用js+input file載入單機聲音檔(mp3)
  2. 解析聲音時,可以變更取樣頻率
  3. 可得知聲音檔的聲道數、總長時間
  4. 繪製聲音波形(全部,部分)
  5. 攔截mouse在canvas中的正確座標
  6. 攔截鍵盤keycode,ctrl,alt,shift,capslock也可攔截
  7. 重組聲音振福,播放部分聲音
  8. (若是用 Audacity,則可先拖曳選取範圍,再用 Ctrl+B 標上標籤,最後用匯出標籤。使用記事本讀取標籤,裏頭有秒數資訊。)

前往 20220526_loadmp3.htm

●20220526_loadmp3.htm

<!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;
      }
      
      #divContainCanvas
      {
        display: inline-block;
        margin: 1em;
        border-width: 0;
        padding: 1em;
        background-color: #eef0a1;
      }
      
      .classBlock
      {
        display: inline-block;
        padding: 12px;
        border-style: solid;
        border-width: 1px;
        border-color: black;
        border-radius: 6px;
        background-color: #dddddd;
      }
      
      #divTimeBegin, #divTimeEnd
      {
        font-family: Consolas;
        font-size: 24pt;
      }
      
    </style>
  </head>
  <body onkeydown="bodyKeydown(event);">
    
    <div id="divWrapper">
      <div>
        <div class="classBlock">
          變更取樣頻率:<input type="text" name="inpSR" id="inpSR" value="8000" style="width: 5em;">Hz (若為空白,則不進行變更)
        </div>
        <div class="classBlock">
          檔案:<input type="file" name="oaAudioFile" id="oaAudioFile" onchange="loadAudioFile(this);">
        </div>
      </div>
      <div id="divLoadStatus"></div>
      <div style="padding: 6px;">
        點選左鍵可產生一個標記,連續兩個標記即成為一個區塊。<br>
        <div style="display: inline-block;">
          <input type="checkbox" name="inpKeyboardCapture" id="inpKeyboardCapture" checked="true">捕捉按鍵
        </div>
        <button type="button" onclick="doCmd('leftArrow');">左移 [←]/[a]</button>
        <button type="button" onclick="doCmd('rightArrow');">右移 [→]/[s]</button>
        <button type="button" onclick="doCmd('+');">放大 [+]/[wheel up]</button>
        <button type="button" onclick="doCmd('-');">縮小 [-]/[wheel down]</button>
        <button type="button" onclick="doCmd('goto');">前往 [ctrl]+[g]</button>
        <button type="button" onclick="rearrangeMarks();">重整數據</button>
        <button type="button" onclick="doCmd('export');">重整數據並作匯出 [ctrl]+[y]</button>
        <a>CapsLock模擬左鍵產生標記</a>
        <div>
          <div style="display: inline-block;">
            聲量係數<input type="text" id="inpVolume" style="width: 3em;" placeholder="2 代表兩倍聲量" value="2">
          </div>
          <div style="display: inline-block;">
            秒數區間用逗號分隔<input type="text" id="inpListenRange" style="width: 16em;" placeholder="空白即自動抓最後一個區塊" value="">
          </div>
          <button type="button" onclick="doCmd('q');">播放 [q]</button>
          <button type="button" onclick="doCmd('w');">停止 [w]</button>
          <button type="button" onclick="doCmd('e');">抓取滑鼠位置區塊並播放 [e]</button>
          <button type="button" onclick="doCmd('d');">抓取滑鼠位置區塊並刪除 [d]</button>
          <button type="button" onclick="doCmd('p');">抓取滑鼠位置區塊並試著分割 [p]</button>
        </div>
        <div class="classBlock">
          【自動標記聲音區塊】  
          起始秒數<input type="text" id="inpAutoTimeBegin" value="0">  
          終止秒數<input type="text" id="inpAutoTimeEnd" value="0">  
          檢查區塊間隔秒數<input type="text" id="inpAutoTimeSpan" value="0.01">  
          最短區塊秒數<input type="text" id="inpAutoTimeBlockMinSecs" value="0.2">  
          振幅閥值(0~2)<input type="text" id="inpAutoThreshold" value="0.02">  
          <button type="button" onclick="doCmd('autoMarking');">執行自動標記</button>
        </div>
        <br>
        <div class="classBlock">
          【聲音區塊播放】  
          <button type="button" onclick="doCmd('search4indexofsoundblock');">重整數據,抓取滑鼠位置區塊並取得索引[r]</button>
          索引(base0)<input type="text" id="inpIdxOfSoundBlock" value="0">  
          <button type="button" onclick="listenByIndex();">聽取並往下一個移動[n]</button>
          <span id="spanListenByIndeStatus"></span>
        </div>
      </div>
      <div id="divContainCanvas">
        <div>
          <div style="float: left;">
            <div>標記暫存區</div>
            <textarea id="taMarks" rows="8" cols="20"></textarea>
              
          </div>
          <div style="float: right;">
            <div style="width: 1600px;">
              <div id="divTimeBegin" style="display: inline-block; float: left;"></div>
              <div id="divTimeCursor" style="display: inline-block; position: relative; left: 45%;"></div>
              <div id="divTimeEnd" style="display: inline-block; float: right;"></div>
            </div>
            <canvas width="1600" height="100" id="myCanvas" 
              onmousemove="mouseMoving(event);"
              onclick="mouseClick(event);"
              onwheel="mouseWheel(event);"></canvas>
          </div>
        </div>
      </div>
      <div>
        <div style="float: left;">
          <div>聲音對應文字的沙盒</div>
          <textarea id="taPlayground1" rows="8" cols="50"></textarea>
        </div>
        <div style="float: left;">
          <div>準備要貼往EXCEL的輸出區</div>
          <textarea id="taPlayground2" rows="8" cols="50"></textarea>
        </div>
      </div>
    </div>
    <div id="divHidden" style="display: none;">
      <canvas width="1600" height="100" id="myBackupCanvas"></canvas>
    </div>
    
    <script>
      
      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);
      }
      
      var oAudioContext=null;
      var oBufferForMp3=null;
      var faAudioData=null;
      var fTimeBegin=null, fTimeEnd=null;
      var booLoadingAndDecoding=false;
      var iIntervalId4LoadingAndDecoding=null;
      
      function flashing()
      {
        if(booLoadingAndDecoding==false)
        {
          window.clearInterval(iIntervalId4LoadingAndDecoding);
          return;
        }
        
        var r=Math.floor(Math.random()*255);
        var g=Math.floor(Math.random()*255);
        var b=Math.floor(Math.random()*255);
        gebi("divLoadStatus").style.color="rgb("+r+","+g+","+b+")";
      }
      
      function loadAudioFile(oObj1)
      {
        gebi("divLoadStatus").innerHTML="準備載入音檔。";
        booLoadingAndDecoding=true;
        iIntervalId4LoadingAndDecoding=window.setInterval(flashing, 200);
        
        var oFR=new FileReader();
        
        oFR.onload=function()
        {
          gebi("divLoadStatus").innerHTML="已載入,準備開始解碼。";
          
          var strSR=gebi("inpSR").value.trim();
          if(strSR=="")
          { oAudioContext=new(window.AudioContext || window.webkitAudioContext); }
          else
          { oAudioContext=new(window.AudioContext || window.webkitAudioContext)({ sampleRate: strSR*1 }); }
          
          oAudioContext.decodeAudioData(
            oFR.result, 
            (oData) => 
            {
              oBufferForMp3=oData;
              faAudioData=Array.from(new Float32Array(oData.getChannelData(0)));
              booLoadingAndDecoding=false;
              gebi("divLoadStatus").style.color="black";
              gebi("divLoadStatus").innerHTML
                ="解碼完成,"
                +"sampleRate="+oBufferForMp3.sampleRate+"Hz,"
                +"numberOfChannels="+oBufferForMp3.numberOfChannels+","
                +"duration="+oBufferForMp3.duration+"秒,"
                +"length="+oBufferForMp3.length+","
                +"faAudioData.length="+faAudioData.length+"。";
              
              fTimeBegin=0;
              fTimeEnd=oBufferForMp3.duration;
              drawWave(faAudioData, "white", "black");
              drawMarks();
              
              gebi("inpAutoTimeBegin").value=0;
              gebi("inpAutoTimeEnd").value=oBufferForMp3.duration;
            },
            function(oError)
            {
              booLoadingAndDecoding=false;
              gebi("divLoadStatus").style.color="red";
              gebi("divLoadStatus").innerHTML="載入錯誤!"+oError;
            });
          
        }
        
        gebi("divLoadStatus").innerHTML="載入中,請稍候,謝謝。";
        oFR.readAsArrayBuffer(oObj1.files[0]); // 只讀取第一個檔案
      }
      
      window.addEventListener(
        "load",
        function() 
        {
        }
      );

    </script>
    
    <script>
      
      function getStrFormattedSecs(fSeconds)
      {
        return Math.floor(fSeconds*100000)/100000;
      }
      
      function getStrFormattedTime(fSeconds)
      {
        var fHours=0;
        var fMinutes=0;
        var fValue;
        
        fHours=Math.floor(fSeconds/3600);
        fMinutes=Math.floor((fSeconds-fHours*3600)/60);
        fValue=fSeconds-fHours*3600-fMinutes*60;
        fValue=Math.floor(fValue*100000)/100000;
        
        return fHours+":"+fMinutes+":"+fValue;
      }
      

      function drawWave(faAudioData, strFGColor, strBGColor)
      {
        if(faAudioData==null) { return; }
        
        gebi("divTimeBegin").innerHTML=getStrFormattedSecs(fTimeBegin)+"<br>"+getStrFormattedTime(fTimeBegin);
        gebi("divTimeEnd").innerHTML=getStrFormattedSecs(fTimeEnd)+"<br>"+getStrFormattedTime(fTimeEnd);
        
        var oCanvas=gebi("myCanvas");
        var oCtx=oCanvas.getContext("2d");
        var iCanvasWidth=oCanvas.width;
        var iCanvasHeight=oCanvas.height;
        
        var iDataBegin=Math.floor(fTimeBegin/oBufferForMp3.duration*(faAudioData.length-1));
        var iDataEnd=Math.floor(fTimeEnd/oBufferForMp3.duration*(faAudioData.length-1));
        var iDataStep=Math.floor((iDataEnd-iDataBegin)/iCanvasWidth/2);
        if(iDataStep<1) { iDataStep=1; }
        
        oCtx.beginPath();
        oCtx.rect(0,0,iCanvasWidth,iCanvasHeight);
        oCtx.fillStyle=strBGColor;
        oCtx.fill();
        
        oCtx.strokeStyle=strFGColor;
        oCtx.beginPath();
        
        var fYBottom=-1;
        var fYTop=1;
        
        oCtx.moveTo(0,iCanvasHeight/2);
        for(var iIdxData=iDataBegin; iIdxData<=iDataEnd; iIdxData+=iDataStep)
        {
          var x=(iIdxData-iDataBegin)/(iDataEnd-iDataBegin)*iCanvasWidth;
          
          var fMin=1, fMax=-1;
          for(var j=0; j<iDataStep; j++) 
          {
            if(faAudioData[iIdxData+j]<fMin) { fMin=faAudioData[iIdxData+j]; }
            if(faAudioData[iIdxData+j]>fMax) { fMax=faAudioData[iIdxData+j]; }
          }
          
          var yMin=(1-(fMin-fYBottom)/(fYTop-fYBottom))*iCanvasHeight;
          oCtx.lineTo(x,yMin);
          
          var yMax=(1-(fMax-fYBottom)/(fYTop-fYBottom))*iCanvasHeight;
          oCtx.lineTo(x,yMax);
        }
        oCtx.stroke();
        
        // backup the canvas
        gebi("myBackupCanvas").getContext("2d").drawImage(oCanvas,0,0);
      }
            
    </script>
    
    <script>
    
      function bodyKeydown(oE)
      {
        var strCmd="";
        
        console.log(oE);
        
        if(oE.ctrlKey==true && oE.altKey==false && oE.shiftKey==false && oE.keyCode==192) { strCmd="`"; }
        
        if(strCmd=="" && inpKeyboardCapture.checked==false)
        {
          console.log('good bye');
          return;
        }
        
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==107) { strCmd="+"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==109) { strCmd="-"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==true && oE.keyCode==61) { strCmd="+"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==true && oE.keyCode==173) { strCmd="-"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==37) { strCmd="leftArrow"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==39) { strCmd="rightArrow"; }
        if(oE.ctrlKey==true && oE.altKey==false && oE.shiftKey==false && oE.keyCode==71) { strCmd="goto"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==81) { strCmd="q"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==87) { strCmd="w"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==65) { strCmd="leftArrow"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==83) { strCmd="rightArrow"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==69) { strCmd="e"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==68) { strCmd="d"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==80) { strCmd="p"; }
        if(oE.ctrlKey==true && oE.altKey==false && oE.shiftKey==false && oE.keyCode==89) { strCmd="export"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==20) { strCmd="leftClick"; }
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==82) { strCmd="search4indexofsoundblock"; } // [r]
        if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==78) { strCmd="listenbyindex"; } // [n]

        if(strCmd!="")
        {
          doCmd(strCmd);
        }
      }
      
      function doCmd(strCmd)
      {
        console.log(strCmd);
        
        if(faAudioData!=null && strCmd=="leftArrow")
        {
          var fMoving=(fTimeEnd-fTimeBegin)/20;
          fTimeBegin-=fMoving;
          fTimeEnd-=fMoving;
          if(fTimeBegin<0)
          {
            fTimeEnd+=Math.abs(fTimeBegin);
            fTimeBegin=0;
          }
          
          drawWave(faAudioData, "white", "black");
          drawMarks();
        }
        else if(faAudioData!=null && strCmd=="rightArrow")
        {
          var fMoving=(fTimeEnd-fTimeBegin)/20;
          fTimeBegin+=fMoving;
          fTimeEnd+=fMoving;
          if(fTimeEnd>oBufferForMp3.duration)
          {
            fTimeEnd=oBufferForMp3.duration;
            fTimeBegin-=(fTimeEnd-oBufferForMp3.duration);
          }
          
          drawWave(faAudioData, "white", "black");
          drawMarks();
        }
        else if(faAudioData!=null && strCmd=="+")
        {
          var fTimeSpan=(fTimeEnd-fTimeBegin)/4;
          fTimeBegin+=fTimeSpan;
          fTimeEnd-=fTimeSpan;
          
          drawWave(faAudioData, "white", "black");
          drawMarks();
        }
        else if(faAudioData!=null && strCmd=="-")
        {
          var fTimeCenter=(fTimeBegin+fTimeEnd)/2;
          var fTimeSpan=(fTimeEnd-fTimeBegin)/2;
          fTimeBegin-=fTimeSpan;
          fTimeEnd+=fTimeSpan;
          
          if(fTimeBegin<0) { fTimeBegin=0; }
          if(fTimeEnd>oBufferForMp3.duration) { fTimeEnd=oBufferForMp3.duration; }
          
          drawWave(faAudioData, "white", "black");
          drawMarks();
        }
        else if(faAudioData!=null && strCmd=="goto")
        {
          var fTmpTimeBegin=null;
          var fTmpTimeSpan=null;
          
          while(true)
          {
            fTmpTimeBegin=prompt("請輸入起始秒數", fTimeBegin);
            if(fTmpTimeBegin==null) { break; }
            
            fTmpTimeSpan=prompt("請輸入秒數寬度", 4);
            if(fTmpTimeSpan==null) { break; }
            
            fTimeBegin=fTmpTimeBegin*1;
            fTimeEnd=fTimeBegin*1+fTmpTimeSpan*1;
            
            if(fTimeBegin<0) { fTimeBegin=0; }
            if(fTimeEnd>oBufferForMp3.duration) { fTimeEnd=oBufferForMp3.duration; }
            
            drawWave(faAudioData, "white", "black");
            drawMarks();
            
            break;
          }
        }
        else if(faAudioData!=null && strCmd=="q")
        {
          startListening();
        }
        else if(faAudioData!=null && strCmd=="w")
        {
          stopListening();
        }
        else if(faAudioData!=null && strCmd=="e")
        {
          search4BlockAndPlay();
        }
        else if(faAudioData!=null && strCmd=="d")
        {
          search4BlockAndDelete();
        }
        else if(faAudioData!=null && strCmd=="p")
        {
          search4BlockAndSeparate();
        }
        else if(faAudioData!=null && strCmd=="`")
        {
          gebi('inpKeyboardCapture').checked=!gebi('inpKeyboardCapture').checked;
        }
        else if(faAudioData!=null && strCmd=="export")
        {
          rearrangeMarksAndExport();
        }
        else if(faAudioData!=null && strCmd=="leftClick")
        {
          capslock_simulate_leftclick();
        }
        else if(faAudioData!=null && strCmd=="autoMarking")
        {
          autoMarking();
        }
        else if(faAudioData!=null && strCmd=="search4indexofsoundblock")
        {
          search4BlockAndShow();
        }
        else if(faAudioData!=null && strCmd=="listenbyindex")
        {
          listenByIndex();
        }
        
      }
    
    </script>
    
    <script>
      
      function drawMarks()
      {
        var oCanvas=gebi("myCanvas");
        var oCtx=oCanvas.getContext("2d");
        
        // 畫標記
        var straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        for(var iMark=0; iMark<straMark.length; iMark+=2)
        {
          var fTimeLeft=straMark[iMark]*1;
          var fLeftX=(fTimeLeft-fTimeBegin)/(fTimeEnd-fTimeBegin)*oCanvas.width;
          
          if((iMark+1)<straMark.length)
          {
            // 兩個
            var fTimeRight=straMark[iMark+1]*1;
            var fRightX=(fTimeRight-fTimeBegin)/(fTimeEnd-fTimeBegin)*oCanvas.width;
            
            oCtx.beginPath();
            oCtx.strokeStyle="blue";
            oCtx.lineWidth=2;
            oCtx.strokeRect(fLeftX,oCanvas.height/10,fRightX-fLeftX,oCanvas.height/10*8);
            oCtx.stroke();
          }
          else
          {
            // 一個
            oCtx.beginPath();
            oCtx.strokeStyle="green";
            oCtx.lineWidth=2;
            oCtx.moveTo(fLeftX,0);
            oCtx.lineTo(fLeftX,oCanvas.height);
            oCtx.stroke();
          }
        }
      }
      
      var fLastCursorX=0;
      function mouseMoving(oEvent)
      {
        if(faAudioData==null) { return; }
        
        var oCanvas=gebi("myCanvas");
        var oCtx=oCanvas.getContext("2d");
        
        var oBackupCanvas=gebi("myBackupCanvas");
        var oBackupCtx=oCanvas.getContext("2d");
        
        var fX = oEvent.clientX-(oCanvas.getBoundingClientRect()).left;
        var fY = oEvent.clientY-(oCanvas.getBoundingClientRect()).top;
        
        oCtx.beginPath();
        oCtx.drawImage(oBackupCanvas,0,0); // 畫面還原
        
        drawMarks();
        
        oCtx.beginPath();
        oCtx.strokeStyle="red";
        oCtx.lineWidth=2;
        oCtx.moveTo(fX,0);
        oCtx.lineTo(fX,oCanvas.height);
        oCtx.stroke();
        oCtx.lineWidth=1;
        
        fLastCursorX=fX;
        
        var fTimeCurrent=Math.floor((fTimeBegin+fLastCursorX/oCanvas.width*(fTimeEnd-fTimeBegin))*10000)/10000;
        gebi("divTimeCursor").innerHTML=fTimeCurrent;
      }
      
      function capslock_simulate_leftclick()
      {
        if(faAudioData==null) { return; }
        
        var oCanvas=gebi("myCanvas");
        var fX=fLastCursorX;
        
        var fCurrentTime=fTimeBegin + fX/oCanvas.width*(fTimeEnd-fTimeBegin);
        
        gebi("taMarks").value
          =gebi("taMarks").value
          +getStrFormattedSecs(fCurrentTime)+"\r\n";
      }
      
      function mouseClick(oEvent)
      {
        if(faAudioData==null) { return; }
        
        var oCanvas=gebi("myCanvas");
        var fX = oEvent.clientX-(oCanvas.getBoundingClientRect()).left;
        var fY = oEvent.clientY-(oCanvas.getBoundingClientRect()).top;
        
        var fCurrentTime=fTimeBegin + fX/oCanvas.width*(fTimeEnd-fTimeBegin);
        
        gebi("taMarks").value
          =gebi("taMarks").value
          +getStrFormattedSecs(fCurrentTime)+"\r\n";
      }
      
      function mouseWheel(oEvent)
      {
        if(faAudioData==null) { return; }
        
        oEvent.preventDefault();
        
        if(oEvent.deltaY<0) { doCmd("+"); }
        else { doCmd("-"); }
      }
      
    </script>
    
    <script>
      
      function rearrangeMarks()
      {
        var straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        var oaBuffer=[];
        var straFinal=[];
        
        var fVal1, fVal2;
        for(var iMark=0; iMark<straMark.length; iMark+=2)
        {
          if((iMark+1)<straMark.length)
          {
            fVal1=straMark[iMark]*1;
            fVal2=straMark[iMark+1]*1;
            
            if(fVal1<fVal2) 
            {
              oaBuffer.push([fVal1,fVal2]);
            }
            else
            {
              oaBuffer.push([fVal2,fVal1]);
            }
          }
          else
          {
            oaBuffer.push([straMark[iMark]*1]);
          }
        }
        
        oaBuffer.sort(
          function(ary1,ary2)
          {
            if(ary1[0]>ary2[0]) { return true; }
            return false;
          }
        );
        
        for(var i=0; i<oaBuffer.length; i++)
        {
          for(var j=0; j<oaBuffer[i].length; j++)
          {
            straFinal.push(oaBuffer[i][j]);
          }
          
          if(oaBuffer[i].length==1)
          {
            straFinal.push(oaBuffer[i][0]+0.0001);
          }
        }
        
        gebi("taMarks").value=straFinal.join("\r\n")+"\r\n";
      }
      
      function rearrangeMarksAndExport()
      {
        rearrangeMarks();
        
        var straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        
        var straFinal=[];
        for(var i=0; i<straMark.length; i+=2)
        {
          straFinal.push(straMark[i]+"\t"+straMark[i+1]);
        }
        
        gebi("taPlayground2").value=straFinal.join("\r\n");
      }
      
    </script>
    
    <script>
    
      function listenByIndex()
      {
        var straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        var iTotalSoundBlocks=Math.floor(straMark.length/2);
        
        if(faAudioData.length<1)
        {
          alert("抱歉,目前沒有載入聲音檔可供聆聽!");
          return;
        }
        
        if(iTotalSoundBlocks<1)
        {
          alert("抱歉,目前沒有任何標記的區塊可供聆聽!");
          return;
        }
        
        rearrangeMarks();
        
        var iIdxOfSoundBlock=gebi("inpIdxOfSoundBlock").value*1;
        if(iIdxOfSoundBlock<0) { iIdxOfSoundBlock=0; }
        if(iIdxOfSoundBlock>(iTotalSoundBlocks-1)) { iIdxOfSoundBlock=(iTotalSoundBlocks-1); }
        
        var fTime1=straMark[iIdxOfSoundBlock*2]*1;
        var fTime2=straMark[iIdxOfSoundBlock*2+1]*1;
        
        var fTimeWatch=fTime1-0.05;
        var fTimeRange=4;
        if((fTime2-fTimeWatch)>fTimeRange) { fTimeRange=(fTime2-fTimeWatch)*2; }
        
        gotoThisSoundBlock(fTimeWatch,fTimeRange);
        listenToThisRange(fTime1,fTime2);
        
        gebi("spanListenByIndeStatus").innerHTML="聆聽 "+fTime1+" ~ "+fTime2;
        
        // 移動到下一個區塊
        iIdxOfSoundBlock=(iIdxOfSoundBlock+1) % iTotalSoundBlocks;
        gebi("inpIdxOfSoundBlock").value=iIdxOfSoundBlock;
      }
      
      function gotoThisSoundBlock(fTime1,fTimeRange)
      {
        var fTmpTimeBegin=fTime1;
        var fTmpTimeSpan=fTimeRange;
        
        fTimeBegin=fTmpTimeBegin*1;
        fTimeEnd=fTimeBegin*1+fTmpTimeSpan*1;
            
        if(fTimeBegin<0) { fTimeBegin=0; }
        if(fTimeEnd>oBufferForMp3.duration) { fTimeEnd=oBufferForMp3.duration; }
            
        drawWave(faAudioData, "white", "black");
        drawMarks();
      }
      
      function search4BlockAndShow()
      {
        rearrangeMarks();
        
        var oCanvas=gebi("myCanvas");
        var fTimeCurrent=fTimeBegin+fLastCursorX/oCanvas.width*(fTimeEnd-fTimeBegin);
        
        straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        var iMax=straMark.length-(straMark.length%2);
        for(var i=0; i<iMax; i+=2)
        {
          var fTime1=straMark[i]*1;
          var fTime2=straMark[i+1]*1;
          
          var fTimeTmp;
          if(fTime1>fTime2)
          {
            fTimeTmp=fTime1;
            fTime1=fTime2;
            fTime2=fTimeTmp;
          }
          
          if(fTimeCurrent>=fTime1 && fTimeCurrent<=fTime2)
          {
            gebi("inpIdxOfSoundBlock").value=Math.floor(i/2);
            break;
          }
        }
      }

    </script>
    
    <script>
      
      var oAudioContext4Listen = new (window.AudioContext || window.webkitAudioContext); // 放在外面,一次就好!這樣資源配置才會順暢!
      var oBufferSource4Listen=null;
      function listenToThisRange(fTime1, fTime2)
      {
        if(faAudioData==null) { return; }
        
        var fVolume=gebi("inpVolume").value*1;
        
        try
        {
          stopListening();
          
          var iIdx1=Math.floor(fTime1/oBufferForMp3.duration*(faAudioData.length-1));
          var iIdx2=Math.floor(fTime2/oBufferForMp3.duration*(faAudioData.length-1));
          
          var faAudioBuffer=faAudioData.slice(iIdx1,iIdx2+1);
          var iBufferSize=faAudioBuffer.length;
          var oBuffer=oAudioContext4Listen.createBuffer(1, iBufferSize, oBufferForMp3.sampleRate);
          var oData=oBuffer.getChannelData(0);
          for(var i=0; i<faAudioBuffer.length; i++)
          {
            oData[i]=faAudioBuffer[i]*fVolume;
            if(oData[i]>1) { oData[i]=1; }
            if(oData[i]<-1) { oData[i]=-1; }
          }
          oBufferSource4Listen=oAudioContext4Listen.createBufferSource();
          oBufferSource4Listen.buffer=oBuffer;
          oBufferSource4Listen.connect(oAudioContext4Listen.destination);
          oBufferSource4Listen.start();  
        }
        catch(oError)
        {
          alert("play error!"+oError);
        }
      }
      
      function stopListening()
      {
        if(oBufferSource4Listen!=null)
        {
          oBufferSource4Listen.stop();
        }
      }
      
      function startListening()
      {
        var stra1=gebi("inpListenRange").value.split(" ").join("").split(",");
        var fTime1=null, fTime2=null;
        
        if(stra1.length==2)
        {
          fTime1=stra1[0]*1;
          fTime2=stra1[1]*1;
        }
        else
        {
          straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
          if(straMark.length>1)
          {
            var iIdx=((straMark.length-(straMark.length%2))/2)*2-2;
            fTime1=straMark[iIdx]*1;
            fTime2=straMark[iIdx+1]*1;
          }
        }
        
        if(fTime1==null || fTime2==null) { return; } // 沒有東西可以播放
        
        listenToThisRange(fTime1,fTime2);
        gebi("inpListenRange").value="";
      }
      
      function search4BlockAndPlay()
      {
        var oCanvas=gebi("myCanvas");
        var fTimeCurrent=fTimeBegin+fLastCursorX/oCanvas.width*(fTimeEnd-fTimeBegin);
        
        straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        var iMax=straMark.length-(straMark.length%2);
        for(var i=0; i<iMax; i+=2)
        {
          var fTime1=straMark[i]*1;
          var fTime2=straMark[i+1]*1;
          
          var fTimeTmp;
          if(fTime1>fTime2)
          {
            fTimeTmp=fTime1;
            fTime1=fTime2;
            fTime2=fTimeTmp;
          }
          
          if(fTimeCurrent>=fTime1 && fTimeCurrent<=fTime2)
          {
            listenToThisRange(fTime1,fTime2);
            break;
          }
        }
      }
      
      function search4BlockAndDelete()
      {
        rearrangeMarks();
        
        var oCanvas=gebi("myCanvas");
        var fTimeCurrent=fTimeBegin+fLastCursorX/oCanvas.width*(fTimeEnd-fTimeBegin);
        
        straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        var iMax=straMark.length-(straMark.length%2);
        for(var i=0; i<iMax; i+=2)
        {
          var fTime1=straMark[i]*1;
          var fTime2=straMark[i+1]*1;
          
          if(fTimeCurrent>=fTime1 && fTimeCurrent<=fTime2)
          {
            straMark[i]='';
            straMark[i+1]='';
          }
        }
        
        gebi("taMarks").value=straMark.filter(Boolean).join("\r\n")+"\r\n";
      }
      
      function search4BlockAndSeparate_goFindMiddle(fTime1,fTime2)
      {
        var fTimeMiddle=(fTime1+fTime2)/2;
        fAutoTimeSpan=gebi("inpAutoTimeSpan").value*1;
        
        var iDataIdxBegin=Math.floor(fTime1/oBufferForMp3.duration*(faAudioData.length-1));
        var iDataIdxEnd=Math.floor(fTime2/oBufferForMp3.duration*(faAudioData.length-1));
        var iDataSpan=Math.floor(fAutoTimeSpan*oBufferForMp3.sampleRate);
        
        var fTimeTrim=(fTime2-fTime1)/10;
        
        var faSearchBlock=faAudioData.slice(iDataIdxBegin,iDataIdxEnd+1);
        var fMax4All=Math.max(...faSearchBlock);
        var fMin4All=Math.min(...faSearchBlock);
        
        
        var oaMaxMin=[];
        for(var iDataIdx=iDataIdxBegin; iDataIdx<=iDataIdxEnd; iDataIdx+=iDataSpan)
        {
          var faSearchBlockNow=faAudioData.slice(iDataIdx,iDataIdx+iDataSpan);
          var fMax4Now=Math.max(...faSearchBlockNow);
          var fMin4Now=Math.min(...faSearchBlockNow);
          var fTimeMiddleNow=(iDataIdx+iDataSpan/2)/(faAudioData.length-1)*oBufferForMp3.duration;
          var fScore=(fMax4Now-fMin4Now)/(fMax4All-fMin4All);
          
          if((fMax4Now-fMin4Now)<((fMax4All-fMin4All)/2))
          {
            if(fTimeMiddleNow>=(fTime1+fTimeTrim) && fTimeMiddleNow<=(fTime2-fTimeTrim))
            {
              oaMaxMin.push({ fScore:fScore, fMaxMin4Now:(fMax4Now-fMin4Now), fTimeMiddleNow:fTimeMiddleNow });
            }
          }
        }
        
        oaMaxMin.sort(
          function(a,b)
          {
            if(a.fScore>b.fScore) { return 1; }
            else if(a.fScore<b.fScore) { return -1; }
            else { return 0; }
          }
        );
        console.log(oaMaxMin); // nmomtf
        
        if(oaMaxMin.length>0)
        {
          fTimeMiddle=oaMaxMin[0].fTimeMiddleNow;
        }
        
        return fTimeMiddle;
      }
      
      function search4BlockAndSeparate()
      {
        rearrangeMarks();
        
        var oCanvas=gebi("myCanvas");
        var fTimeCurrent=fTimeBegin+fLastCursorX/oCanvas.width*(fTimeEnd-fTimeBegin);
        
        straMark=gebi("taMarks").value.split(" ").join("").split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
        var iMax=straMark.length-(straMark.length%2);
        for(var i=0; i<iMax; i+=2)
        {
          var fTime1=straMark[i]*1;
          var fTime2=straMark[i+1]*1;
          
          if(fTimeCurrent>=fTime1 && fTimeCurrent<=fTime2)
          {
            var fTimeMiddle=search4BlockAndSeparate_goFindMiddle(fTime1,fTime2);
            
            straMark[i]=fTime1+"\r\n"+(fTimeMiddle-0.02);
            straMark[i+1]=(fTimeMiddle+0.02)+"\r\n"+fTime2;
          }
        }
        
        gebi("taMarks").value=straMark.filter(Boolean).join("\r\n")+"\r\n";
      }
      
    </script>

    <script>
      
      function load_from_www()
      {
        // 要從 https 的伺服器載入
        const request = new XMLHttpRequest();
        request.open("GET", "total.mp3");
        request.responseType = "arraybuffer";
        request.onload = function() 
        {
          console.log('onload');
          let undecodedAudio = request.response;
          oAudioContext.decodeAudioData(
            undecodedAudio, 
            (data) => 
            {
              oBuffer=data;
              console.log(oBuffer.getChannelData(0));
            });
        };
        request.send();
        console.log('send');
      }
      
    </script>
    
    <script>
      
      var fAutoTimeBegin=0;
      var fAutoTimeEnd=100;
      var fAutoTimeSpan=0.01; // 每 0.01 秒,一個區塊
      var fAutoThreshold=0.02; // -1 ~ +1 的振幅
      var fAutoTimeBlockMinSecs=0.2;
      
      
      function autoMarking()
      {
        fAutoTimeBegin=gebi("inpAutoTimeBegin").value*1;
        fAutoTimeEnd=gebi("inpAutoTimeEnd").value*1;
        fAutoTimeSpan=gebi("inpAutoTimeSpan").value*1;
        fAutoThreshold=gebi("inpAutoThreshold").value*1;
        fAutoTimeBlockMinSecs=gebi("inpAutoTimeBlockMinSecs").value*1;
      
        var iAutoDataIdxBegin=Math.floor(fAutoTimeBegin/oBufferForMp3.duration*(faAudioData.length-1));
        var iAutoDataIdxEnd=Math.floor(fAutoTimeEnd/oBufferForMp3.duration*(faAudioData.length-1));
        var iAutoDataSpan=Math.floor(fAutoTimeSpan*oBufferForMp3.sampleRate);
        if(iAutoDataIdxBegin<0) { iAutoDataIdxBegin=0; }
        if(iAutoDataIdxEnd<0) { iAutoDataIdxEnd=0; }
        if(iAutoDataIdxBegin>(faAudioData.length-1)) { iAutoDataIdxBegin=(faAudioData.length-1); }
        if(iAutoDataIdxEnd>(faAudioData.length-1)) { iAutoDataIdxEnd=(faAudioData.length-1); }
        if(iAutoDataSpan<1) { iAutoDataSpan=1; }
        
        var oAB1=null, oAB2=null;
        var iAutoDataFoundBegin=null, iAutoDataFoundEnd=null;
        var iAutoDataIdx=iAutoDataIdxBegin;
        while(iAutoDataIdx<=iAutoDataIdxEnd)
        {
          iAutoDataFoundBegin=null;
          iAutoDataFoundEnd=null;
          
          oAB1=getOAutoBlock(iAutoDataIdx,iAutoDataIdx+iAutoDataSpan);
          if(oAB1.booHasSound==true)
          {
            iAutoDataFoundBegin=iAutoDataIdx;
            iAutoDataFoundEnd=iAutoDataIdx+iAutoDataSpan-1;
            iAutoDataIdx+=iAutoDataSpan;
            
            while(iAutoDataIdx<=iAutoDataIdxEnd)
            {
              oAB2=getOAutoBlock(iAutoDataIdx,iAutoDataIdx+iAutoDataSpan);
              if(oAB2.booHasSound==true) { iAutoDataFoundEnd=iAutoDataIdx+iAutoDataSpan-1; }
              else { break; }
              iAutoDataIdx+=iAutoDataSpan;
            }
            
            if(iAutoDataFoundEnd>iAutoDataIdxEnd) { iAutoDataFoundEnd=iAutoDataIdxEnd; }
            
            var fAutoTimeFoundBegin=iAutoDataFoundBegin/(faAudioData.length-1)*oBufferForMp3.duration;
            var fAutoTimeFoundEnd=iAutoDataFoundEnd/(faAudioData.length-1)*oBufferForMp3.duration;
            
            if((fAutoTimeFoundEnd-fAutoTimeFoundBegin)>=fAutoTimeBlockMinSecs)
            {
              gebi("taMarks").value
                =gebi("taMarks").value
                +getStrFormattedSecs(fAutoTimeFoundBegin)+"\r\n"
                +getStrFormattedSecs(fAutoTimeFoundEnd)+"\r\n";
            }
          }
          
          iAutoDataIdx+=iAutoDataSpan;
        }
        
      }
      
      function getOAutoBlock(i1,i2)
      {
        var faAutoBlock=faAudioData.slice(i1,i2);
        var fAutoMinVal=Math.min(...faAutoBlock);
        var fAutoMaxVal=Math.max(...faAutoBlock);
        var booHasSound=(fAutoMaxVal-fAutoMinVal)>fAutoThreshold;
        
        if(i1>=faAudioData.length)
        {
          faAutoBlock=[];
          fAutoMinVal=0;
          fAutoMaxVal=0;
          booHasSound=false; 
        }
        
        return { faAutoBlock:faAutoBlock, fAutoMinVal:fAutoMinVal, fAutoMaxVal:fAutoMaxVal, booHasSound:booHasSound };
      }
      
    </script>
    
  </body>
</html>