2014-02-27 28 views
15

實時移動波形創建全程跟蹤的Web Audio API使用

我目前Web Audio API使用播放波形和使用的帆布製成的頻譜。

function animate(){ 
var a=new Uint8Array(analyser.frequencyBinCount), 
    y=new Uint8Array(analyser.frequencyBinCount),b,c,d; 
analyser.getByteTimeDomainData(y); 
analyser.getByteFrequencyData(a); 
b=c=a.length; 
d=w/c; 
ctx.clearRect(0,0,w,h); 
while(b--){ 
    var bh=a[b]+1; 
    ctx.fillStyle='hsla('+(b/c*240)+','+(y[b]/255*100|0)+'%,50%,1)'; 
    ctx.fillRect(1*b,h-bh,1,bh); 
    ctx.fillRect(1*b,y[b],1,1); 
} 
animation=webkitRequestAnimationFrame(animate); 
} 

小問題:有沒有辦法不寫2次new Uint8Array(analyser.frequencyBinCount)

DEMO

添加MP3/MP4文件,並等待。 (在Chrome測試)

http://jsfiddle.net/pc76H/2/

但也存在不少問題。我找不到各種音頻過濾器的正確文檔。另外,如果你看看頻譜,你會注意到70%或範圍之後沒有數據。那是什麼意思?也許從16k赫茲到20k赫茲是沒有聲音的?我會將一個文本應用於畫布以顯示各種HZ。但是哪裏??

我發現返回的數據是32長的電源2048 一個最大高度爲256總是

但真正的問題是...我要創造感動波形像拖拉機一樣。

我已經做了一段時間以前用PHP它將文件轉換爲低比特率提取數據並將其轉換爲圖像。我發現劇本的地方......但我不記得在哪裏... 注:需要LAME

<?php 
$a=$_GET["f"]; 
if(file_exists($a)){ 
    if(file_exists($a.".png")){ 
     header("Content-Type: image/png"); 
     echo file_get_contents($a.".png"); 
    }else{ 
     $b=3000;$c=300;define("d",3); 
     ini_set("max_execution_time","30000"); 
     function n($g,$h){ 
      $g=hexdec(bin2hex($g)); 
      $h=hexdec(bin2hex($h)); 
      return($g+($h*256)); 
     }; 
     $k=substr(md5(time()),0,10); 
     copy(realpath($a),"/var/www/".$k."_o.mp3"); 
     exec("lame /var/www/{$k}_o.mp3 -f -m m -b 16 --resample 8 /var/www/{$k}.mp3 && lame --decode /var/www/{$k}.mp3 /var/www/{$k}.wav"); 
     //system("lame {$k}_o.mp3 -f -m m -b 16 --resample 8 {$k}.mp3 && lame --decode {$k}.mp3 {$k}.wav"); 
     @unlink("/var/www/{$k}_o.mp3"); 
     @unlink("/var/www/{$k}.mp3"); 
     $l="/var/www/{$k}.wav"; 
     $m=fopen($l,"r"); 
     $n[]=fread($m,4); 
     $n[]=bin2hex(fread($m,4)); 
     $n[]=fread($m,4); 
     $n[]=fread($m,4); 
     $n[]=bin2hex(fread($m,4)); 
     $n[]=bin2hex(fread($m,2)); 
     $n[]=bin2hex(fread($m,2)); 
     $n[]=bin2hex(fread($m,4)); 
     $n[]=bin2hex(fread($m,4)); 
     $n[]=bin2hex(fread($m,2)); 
     $n[]=bin2hex(fread($m,2)); 
     $n[]=fread($m,4); 
     $n[]=bin2hex(fread($m,4)); 
     $o=hexdec(substr($n[10],0,2)); 
     $p=$o/8; 
     $q=hexdec(substr($n[6],0,2)); 
     if($q==2){$r=40;}else{$r=80;}; 
     while(!feof($m)){ 
      $t=array(); 
      for($i=0;$i<$p;$i++){ 
       $t[$i]=fgetc($m); 
      }; 
      switch($p){ 
       case 1:$s[]=n($t[0],$t[1]);break; 
       case 2:if(ord($t[1])&128){$u=0;}else{$u=128;};$u=chr((ord($t[1])&127)+$u);$s[]= floor(n($t[0],$u)/256);break; 
      }; 
      fread($m,$r); 
     }; 
     fclose($m); 
     unlink("/var/www/{$k}.wav"); 
     $x=imagecreatetruecolor(sizeof($s)/d,$c); 
     imagealphablending($x,false); 
     imagesavealpha($x,true); 
     $y=imagecolorallocatealpha($x,255,255,255,127); 
     imagefilledrectangle($x,0,0,sizeof($s)/d,$c,$y); 
     for($d=0;$d<sizeof($s);$d+=d){ 
      $v=(int)($s[$d]/255*$c); 
      imageline($x,$d/d,0+($c-$v),$d/d,$c-($c-$v),imagecolorallocate($x,255,0,255)); 
     }; 
     $z=imagecreatetruecolor($b,$c); 
     imagealphablending($z,false); 
     imagesavealpha($z,true); 
     imagefilledrectangle($z,0,0,$b,$c,$y); 
     imagecopyresampled($z,$x,0,0,0,0,$b,$c,sizeof($s)/d,$c); 
     imagepng($z,realpath($a).".png"); 
     header("Content-Type: image/png"); 
     imagepng($z); 
     imagedestroy($z); 
    }; 
}else{ 
    echo $a; 
}; 

?> 

該腳本...但你被限制在4K像素的最大圖像尺寸。

所以你沒有一個漂亮的波形,如果它應該只有幾毫秒。

我需要什麼來存儲/創建像traktors應用程序或此PHP腳本的實時波形? btw拖拉機也有一個彩色波形(PHP腳本不)。

編輯

我改寫了你的腳本,它適合我的想法......這是比較快的。

正如您可以在函數createArray中看到的一樣,我將各行按鍵作爲x座標推入到一個對象中。

我只是把最高的數字。

這裏是我們可以玩的顏色。

var ajaxB,AC,B,LC,op,x,y,ARRAY={},W=1024,H=256; 
var aMax=Math.max.apply.bind(Math.max, Math); 
function error(a){ 
console.log(a); 
}; 
function createDrawing(){ 
console.log('drawingArray'); 
var C=document.createElement('canvas'); 
C.width=W; 
C.height=H; 
document.body.appendChild(C); 
var context=C.getContext('2d'); 
context.save(); 
context.strokeStyle='#121'; 
context.globalCompositeOperation='lighter'; 
L2=W*1; 
while(L2--){ 
    context.beginPath(); 
    context.moveTo(L2,0); 
    context.lineTo(L2+1,ARRAY[L2]); 
    context.stroke(); 
} 
context.restore(); 
}; 
function createArray(a){ 
console.log('creatingArray'); 
B=a; 
LC=B.getChannelData(0);// Float32Array describing left channel 
L=LC.length; 
op=W/L; 
for(var i=0;i<L;i++){ 
    x=W*i/L|0; 
    y=LC[i]*H/2; 
    if(ARRAY[x]){ 
    ARRAY[x].push(y) 
    }else{ 
    !ARRAY[x-1]||(ARRAY[x-1]=aMax(ARRAY[x-1])); 
    // the above line contains an array of values 
    // which could be converted to a color 
    // or just simply create a gradient 
    // based on avg max min (frequency???) whatever 
    ARRAY[x]=[y] 
    } 
}; 
createDrawing(); 
}; 
function decode(){ 
console.log('decodingMusic'); 
AC=new webkitAudioContext 
AC.decodeAudioData(this.response,createArray,error); 
}; 
function loadMusic(url){ 
console.log('loadingMusic'); 
ajaxB=new XMLHttpRequest; 
ajaxB.open('GET',url); 
ajaxB.responseType='arraybuffer';  
ajaxB.onload=decode; 
ajaxB.send(); 
} 
loadMusic('AudioOrVideo.mp4'); 
+0

要生成位圖,將代表其時間表示波形?是嗎?並讓用戶下載它? – GameAlchemist

+0

php腳本已經做了你所說的,我想創建一個更先進的波形,然後生成一個高度詳細的表示。我還會了解拖拉機如何創建彩色波形 – cocco

+0

,以便用戶可以在編輯器中更改波形,然後保存此波形的圖形?不是原始數據本身?不確定這裏的用例。對於顏色,這只是關於如何以很低的分辨率獲得許多小的垂直線。一個像素屏幕線中的樣品越多,顏色就越深。 – GameAlchemist

回答

28

好了,我會做的是有一個XMLHttpRequest加載聲音,然後用webaudio對其進行解碼,然後顯示它「認真」有你正在尋找的顏色。

我只是做了一個快速的版本,複製,粘貼各種我的項目,那是相當的工作,你可能與此圖片中看到:

enter image description here

的問題是,它是緩慢的地獄。要有(更多)體面的速度,你必須做一些計算來減少在畫布上繪製的線條數量,因爲在441000赫茲時,你很快就會得到太多的線條繪製。

// AUDIO CONTEXT 
window.AudioContext = window.AudioContext || window.webkitAudioContext ; 

if (!AudioContext) alert('This site cannot be run in your Browser. Try a recent Chrome or Firefox. '); 

var audioContext = new AudioContext(); 
var currentBuffer = null; 

// CANVAS 
var canvasWidth = 512, canvasHeight = 120 ; 
var newCanvas = createCanvas (canvasWidth, canvasHeight); 
var context  = null; 

window.onload = appendCanvas; 
function appendCanvas() { document.body.appendChild(newCanvas); 
          context = newCanvas.getContext('2d'); } 

// MUSIC LOADER + DECODE 
function loadMusic(url) { 
    var req = new XMLHttpRequest(); 
    req.open("GET", url, true); 
    req.responseType = "arraybuffer";  
    req.onreadystatechange = function (e) { 
      if (req.readyState == 4) { 
      if(req.status == 200) 
        audioContext.decodeAudioData(req.response, 
        function(buffer) { 
          currentBuffer = buffer; 
          displayBuffer(buffer); 
        }, onDecodeError); 
      else 
        alert('error during the load.Wrong url or cross origin issue'); 
      } 
    } ; 
    req.send(); 
} 

function onDecodeError() { alert('error while decoding your file.'); } 

// MUSIC DISPLAY 
function displayBuffer(buff /* is an AudioBuffer */) { 
    var leftChannel = buff.getChannelData(0); // Float32Array describing left channel  
    var lineOpacity = canvasWidth/leftChannel.length ;  
    context.save(); 
    context.fillStyle = '#222' ; 
    context.fillRect(0,0,canvasWidth,canvasHeight); 
    context.strokeStyle = '#121'; 
    context.globalCompositeOperation = 'lighter'; 
    context.translate(0,canvasHeight/2); 
    context.globalAlpha = 0.06 ; // lineOpacity ; 
    for (var i=0; i< leftChannel.length; i++) { 
     // on which line do we get ? 
     var x = Math.floor (canvasWidth * i/leftChannel.length) ; 
     var y = leftChannel[i] * canvasHeight/2 ; 
     context.beginPath(); 
     context.moveTo(x , 0); 
     context.lineTo(x+1, y); 
     context.stroke(); 
    } 
    context.restore(); 
    console.log('done'); 
} 

function createCanvas (w, h) { 
    var newCanvas = document.createElement('canvas'); 
    newCanvas.width = w;  newCanvas.height = h; 
    return newCanvas; 
}; 


loadMusic('could_be_better.mp3'); 

編輯:這裏的問題是,我們有太多的數據來繪製。採取3分鐘的MP3,你會有3 * 60 * 44100 =大約8.000.000線畫。在具有1024像素分辨率的顯示器上,每個像素產生8.000行...
在上面的代碼中,畫布通過繪製具有低不透明度和「ligther」構圖的線進行「重採樣」模式(例如像素的r,g,b會加起來)。爲了加速事情,你必須自己重新抽樣,但爲了獲得一些顏色,它不僅僅是一個下采樣,你必須處理一個(最有可能是性能陣列中的)一個「桶」 ',每個水平像素一個(所以說1024),並在每個桶中計算累積聲壓,方差,最小值,最大值,然後在顯示時間決定如何渲染顏色。
例如:
0正值之間的值非常清楚。 (任何樣本低於該點)。
positiveMin和positiveAverage之間的值 - 方差較暗,
positiveAverage之間的值 - 方差和positiveAverage +方差較暗,
和positiveAverage +方差和positiveMax打火機之間的值。
(負值相同) 這使得每個存儲桶有5種顏色,而且它還是一些工作,供您編碼並供瀏覽器計算。
我不知道這樣的表現是否可以得到體面的,但我擔心你提到的軟件的統計準確性和顏色編碼無法在瀏覽器上顯示(顯然不是實時的),而且你必須做出一些妥協。

編輯2:
我試圖從統計數據中獲得一些顏色,但它很失敗。我的猜測是,現在,tracktor的傢伙也會根據頻率改變顏色....這裏有一些工作......

無論如何,只是爲了記錄,平均/平均變化的代碼如下。
(方差太低,我不得不使用平均變化)。

enter image description here

// MUSIC DISPLAY 
function displayBuffer2(buff /* is an AudioBuffer */) { 
    var leftChannel = buff.getChannelData(0); // Float32Array describing left channel  
    // we 'resample' with cumul, count, variance 
    // Offset 0 : PositiveCumul 1: PositiveCount 2: PositiveVariance 
    //  3 : NegativeCumul 4: NegativeCount 5: NegativeVariance 
    // that makes 6 data per bucket 
    var resampled = new Float64Array(canvasWidth * 6); 
    var i=0, j=0, buckIndex = 0; 
    var min=1e3, max=-1e3; 
    var thisValue=0, res=0; 
    var sampleCount = leftChannel.length; 
    // first pass for mean 
    for (i=0; i<sampleCount; i++) { 
     // in which bucket do we fall ? 
     buckIndex = 0 | (canvasWidth * i/sampleCount); 
     buckIndex *= 6; 
     // positive or negative ? 
     thisValue = leftChannel[i]; 
     if (thisValue>0) { 
      resampled[buckIndex ] += thisValue; 
      resampled[buckIndex + 1] +=1;    
     } else if (thisValue<0) { 
      resampled[buckIndex + 3] += thisValue; 
      resampled[buckIndex + 4] +=1;       
     } 
     if (thisValue<min) min=thisValue; 
     if (thisValue>max) max = thisValue; 
    } 
    // compute mean now 
    for (i=0, j=0; i<canvasWidth; i++, j+=6) { 
     if (resampled[j+1] != 0) { 
      resampled[j] /= resampled[j+1]; ; 
     } 
     if (resampled[j+4]!= 0) { 
      resampled[j+3] /= resampled[j+4]; 
     } 
    } 
    // second pass for mean variation (variance is too low) 
    for (i=0; i<leftChannel.length; i++) { 
     // in which bucket do we fall ? 
     buckIndex = 0 | (canvasWidth * i/leftChannel.length); 
     buckIndex *= 6; 
     // positive or negative ? 
     thisValue = leftChannel[i]; 
     if (thisValue>0) { 
      resampled[buckIndex + 2] += Math.abs(resampled[buckIndex] - thisValue);    
     } else if (thisValue<0) { 
      resampled[buckIndex + 5] += Math.abs(resampled[buckIndex + 3] - thisValue);       
     } 
    } 
    // compute mean variation/variance now 
    for (i=0, j=0; i<canvasWidth; i++, j+=6) { 
     if (resampled[j+1]) resampled[j+2] /= resampled[j+1]; 
     if (resampled[j+4]) resampled[j+5] /= resampled[j+4]; 
    } 
    context.save(); 
    context.fillStyle = '#000' ; 
    context.fillRect(0,0,canvasWidth,canvasHeight); 
    context.translate(0.5,canvasHeight/2); 
    context.scale(1, 200); 

    for (var i=0; i< canvasWidth; i++) { 
     j=i*6; 
     // draw from positiveAvg - variance to negativeAvg - variance 
     context.strokeStyle = '#F00'; 
     context.beginPath(); 
     context.moveTo(i , (resampled[j] - resampled[j+2])); 
     context.lineTo(i , (resampled[j +3] + resampled[j+5])); 
     context.stroke(); 
     // draw from positiveAvg - variance to positiveAvg + variance 
     context.strokeStyle = '#FFF'; 
     context.beginPath(); 
     context.moveTo(i , (resampled[j] - resampled[j+2])); 
     context.lineTo(i , (resampled[j] + resampled[j+2])); 
     context.stroke(); 
     // draw from negativeAvg + variance to negativeAvg - variance 
     // context.strokeStyle = '#FFF'; 
     context.beginPath(); 
     context.moveTo(i , (resampled[j+3] + resampled[j+5])); 
     context.lineTo(i , (resampled[j+3] - resampled[j+5])); 
     context.stroke(); 
    } 
    context.restore(); 
    console.log('done 231 iyi'); 
} 
+1

哇:),我沒有期待這樣的答案;)但讓我先看看答案。 – cocco

+0

不錯,它效果很好。您還知道dj軟件拖放器如何設法根據頻率創建這種顏色? – cocco

+0

這就是我正在尋找的......現在我只需要將數據存儲到一個數組中。所以我以後可以實時移動波形...希望它能夠工作。 – cocco

5

嗨還面臨着加載時間問題。只是我通過減少想要繪製的線條數量和小畫布函數調用位置來控制它。請參閱以下代碼以供參考。

// AUDIO CONTEXT 
 
window.AudioContext = (window.AudioContext || 
 
window.webkitAudioContext || 
 
window.mozAudioContext || 
 
window.oAudioContext || 
 
window.msAudioContext); 
 

 
if (!AudioContext) alert('This site cannot be run in your Browser. Try a recent Chrome or Firefox. '); 
 

 
var audioContext = new AudioContext(); 
 
var currentBuffer = null; 
 

 
// CANVAS 
 
var canvasWidth = window.innerWidth, canvasHeight = 120 ; 
 
var newCanvas = createCanvas (canvasWidth, canvasHeight); 
 
var context  = null; 
 

 
window.onload = appendCanvas; 
 
function appendCanvas() { document.body.appendChild(newCanvas); 
 
          context = newCanvas.getContext('2d'); } 
 

 
// MUSIC LOADER + DECODE 
 
function loadMusic(url) { 
 
    var req = new XMLHttpRequest(); 
 
    req.open("GET", url, true); 
 
    req.responseType = "arraybuffer";  
 
    req.onreadystatechange = function (e) { 
 
      if (req.readyState == 4) { 
 
      if(req.status == 200) 
 
        audioContext.decodeAudioData(req.response, 
 
        function(buffer) { 
 
          currentBuffer = buffer; 
 
          displayBuffer(buffer); 
 
        }, onDecodeError); 
 
      else 
 
        alert('error during the load.Wrong url or cross origin issue'); 
 
      } 
 
    } ; 
 
    req.send(); 
 
} 
 

 
function onDecodeError() { alert('error while decoding your file.'); } 
 

 
// MUSIC DISPLAY 
 
function displayBuffer(buff /* is an AudioBuffer */) { 
 
    
 
    var drawLines = 500; 
 
    var leftChannel = buff.getChannelData(0); // Float32Array describing left channel  
 
    var lineOpacity = canvasWidth/leftChannel.length ;  
 
    context.save(); 
 
    context.fillStyle = '#080808' ; 
 
    context.fillRect(0,0,canvasWidth,canvasHeight); 
 
    context.strokeStyle = '#46a0ba'; 
 
    context.globalCompositeOperation = 'lighter'; 
 
    context.translate(0,canvasHeight/2); 
 
    //context.globalAlpha = 0.6 ; // lineOpacity ; 
 
    context.lineWidth=1; 
 
    var totallength = leftChannel.length; 
 
    var eachBlock = Math.floor(totallength/drawLines); 
 
    var lineGap = (canvasWidth/drawLines); 
 

 
    context.beginPath(); 
 
    for(var i=0;i<=drawLines;i++){ 
 
     var audioBuffKey = Math.floor(eachBlock * i); 
 
     var x = i*lineGap; 
 
     var y = leftChannel[audioBuffKey] * canvasHeight/2; 
 
     context.moveTo(x, y); 
 
     context.lineTo(x, (y*-1)); 
 
    } 
 
    context.stroke(); 
 
    context.restore(); 
 
} 
 

 
function createCanvas (w, h) { 
 
    var newCanvas = document.createElement('canvas'); 
 
    newCanvas.width = w;  newCanvas.height = h; 
 
    return newCanvas; 
 
}; 
 

 

 
loadMusic('could_be_better.mp3');

4

// AUDIO CONTEXT 
 
window.AudioContext = (window.AudioContext || 
 
window.webkitAudioContext || 
 
window.mozAudioContext || 
 
window.oAudioContext || 
 
window.msAudioContext); 
 

 
if (!AudioContext) alert('This site cannot be run in your Browser. Try a recent Chrome or Firefox. '); 
 

 
var audioContext = new AudioContext(); 
 
var currentBuffer = null; 
 

 
// CANVAS 
 
var canvasWidth = window.innerWidth, canvasHeight = 120 ; 
 
var newCanvas = createCanvas (canvasWidth, canvasHeight); 
 
var context  = null; 
 

 
window.onload = appendCanvas; 
 
function appendCanvas() { document.body.appendChild(newCanvas); 
 
          context = newCanvas.getContext('2d'); } 
 

 
// MUSIC LOADER + DECODE 
 
function loadMusic(url) { 
 
    var req = new XMLHttpRequest(); 
 
    req.open("GET", url, true); 
 
    req.responseType = "arraybuffer";  
 
    req.onreadystatechange = function (e) { 
 
      if (req.readyState == 4) { 
 
      if(req.status == 200) 
 
        audioContext.decodeAudioData(req.response, 
 
        function(buffer) { 
 
          currentBuffer = buffer; 
 
          displayBuffer(buffer); 
 
        }, onDecodeError); 
 
      else 
 
        alert('error during the load.Wrong url or cross origin issue'); 
 
      } 
 
    } ; 
 
    req.send(); 
 
} 
 

 
function onDecodeError() { alert('error while decoding your file.'); } 
 

 
// MUSIC DISPLAY 
 
function displayBuffer(buff /* is an AudioBuffer */) { 
 
    
 
    var drawLines = 500; 
 
    var leftChannel = buff.getChannelData(0); // Float32Array describing left channel  
 
    var lineOpacity = canvasWidth/leftChannel.length ;  
 
    context.save(); 
 
    context.fillStyle = '#080808' ; 
 
    context.fillRect(0,0,canvasWidth,canvasHeight); 
 
    context.strokeStyle = '#46a0ba'; 
 
    context.globalCompositeOperation = 'lighter'; 
 
    context.translate(0,canvasHeight/2); 
 
    //context.globalAlpha = 0.6 ; // lineOpacity ; 
 
    context.lineWidth=1; 
 
    var totallength = leftChannel.length; 
 
    var eachBlock = Math.floor(totallength/drawLines); 
 
    var lineGap = (canvasWidth/drawLines); 
 

 
    context.beginPath(); 
 
    for(var i=0;i<=drawLines;i++){ 
 
     var audioBuffKey = Math.floor(eachBlock * i); 
 
     var x = i*lineGap; 
 
     var y = leftChannel[audioBuffKey] * canvasHeight/2; 
 
     context.moveTo(x, y); 
 
     context.lineTo(x, (y*-1)); 
 
    } 
 
    context.stroke(); 
 
    context.restore(); 
 
} 
 

 
function createCanvas (w, h) { 
 
    var newCanvas = document.createElement('canvas'); 
 
    newCanvas.width = w;  newCanvas.height = h; 
 
    return newCanvas; 
 
}; 
 

 

 
loadMusic('https://raw.githubusercontent.com/katspaugh/wavesurfer.js/master/example/media/demo.wav');