Создайте форму волны полной дорожки с помощью API веб-аудио.

StackOverflow https://stackoverflow.com//questions/22073716

Вопрос

Движущаяся форма волны в реальном времени

В настоящее время я играю с 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)?

ДЕМО

добавьте файл MP3/MP4 и подождите.(проверено в Chrome)

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

Но есть много проблем.Я не могу найти соответствующую документацию по различным аудиофильтрам.

Кроме того, если вы посмотрите на спектр, вы заметите, что после 70% или диапазона данных нет.Что это значит?что может быть от 16кГц до 20кГц нет звука?Я бы применил текст к холсту, чтобы показать различные HZ.но где??

Я обнаружил, что возвращаемые данные - это мощность длиной 32 с максимумом 2048 года, а высота всегда 256.

НО настоящий вопрос...Я хочу создать движущуюся форму волны, как в тракторе.

Я уже делал это некоторое время назад с помощью PHP: он преобразует файл в низкий битрейт, затем извлекает данные и преобразует их в изображение.где-то я нашел сценарий... но не помню где...примечание:потребности ХРОМОЙ

<?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;
};

?>

Скрипт работает...но вы ограничены максимальным размером изображения в 4 КБ пикселей.

так что у вас не будет хорошей формы сигнала, если она должна воспроизводиться всего несколько миллисекунд.

Что мне нужно для хранения/создания сигнала в реальном времени, например, приложения traktors или этого PHP-скрипта?Кстати, у трактора также есть цветная форма волны (в 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');
Это было полезно?

Решение

Хорошо, я бы загрузил звук с помощью 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 пикселей это составляет 8000 строк на пиксель...
В приведенном выше коде холст выполняет «повторную выборку», рисуя линии с низкой непрозрачностью и в «более светлом» режиме композиции (например,r,g,b пикселя будут складываться).
Чтобы ускорить работу, вам придется выполнить повторную выборку самостоятельно, но чтобы получить некоторые цвета, это не просто понижающая выборка, вам придется обрабатывать набор (скорее всего, в массиве производительности) «ведров», по одному на каждый горизонтальный пиксель (например, 1024), и в каждом сегменте вы вычисляете накопленное звуковое давление, дисперсию, мин, максимум, а затем, во время отображения, вы решаете, как вы будете отображать это с помощью цветов.
Например :
значения между 0 и положительными минутами очень ясны.(любой образец находится ниже этой точки).
значения между позитивным мин и позитивным средним — дисперсия темнее,
значения между позитивным средним — дисперсия и позитивным средним + дисперсия темнее,
и значения между позитивнымАвераже+дисперсия и позитивнымМаксом светлее.
(То же самое для негативных значений), которые производят 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');
}

Другие советы

// 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');

Привет, также столкнулся с проблемой времени загрузки.Просто я контролировал это, уменьшив количество линий, которые нужно нарисовать, и немного разместив вызов функции холста.см. следующий код для справки.

// 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');

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top