Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way get something like decibel levels from an audio file and transform that information into a json array?

So that I can use the information to coordinate a page animation like making elements brighter as the decibel levels get higher

like image 640
coiso Avatar asked Dec 05 '12 23:12

coiso


2 Answers

This approach will work in Chrome / Safari:

+function(){
  
  var ctx = new AudioContext()
    , url = 'https://cf-media.sndcdn.com/OfjMZo27DlvH.128.mp3?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiKjovL2NmLW1lZGlhLnNuZGNkbi5jb20vT2ZqTVpvMjdEbHZILjEyOC5tcDMiLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE1MTUwNDM5Njd9fX1dfQ__&Signature=FfmL2qUssAKs3Z7EPoYo0Yq8-SAg8rKLPs65EasXwuVkfsOB4joFqeCvVR2elpaG-lJaV4hXpXFiRCDWXNOYyAtO4Oz~sexiPwIoSk8-jWiVbGQRS8TMmUmj7TJzxemMOIj7ugWJKk6PHsrUdgqs9woDpHzxmkGCzk6sfqJEIsdeZJ4rWUFAh4iGWn9M6b0xfzTgndAJmytkNj9raCpWCBVmdr5u-r9nt~q5uF1easNSW9oaFilM4s1Hq2ei~VJye8zW9bzvrGm8idVdy-tiPeMWAKcE8J2VuaS1Ret6jRTRaHTDuiNgA5sZvgTzNpEpKtWI7UmAWI5TrqNVSlxpgQ__&Key-Pair-Id=APKAJAGZ7VMH2PFPW6UQ'  
    , audio = new Audio(url)
    // 2048 sample buffer, 1 channel in, 1 channel out  
    , processor = ctx.createScriptProcessor(2048, 1, 1)
    , meter = document.getElementById('meter')
    , source
    
  audio.crossOrigin = 'anonymous'

  audio.addEventListener('canplaythrough', function(){
    source = ctx.createMediaElementSource(audio)
    source.connect(processor)
    source.connect(ctx.destination)
    processor.connect(ctx.destination)
    audio.play()
  }, false);
  
  // loop through PCM data and calculate average
  // volume for a given 2048 sample buffer
  processor.onaudioprocess = function(evt){
    var input = evt.inputBuffer.getChannelData(0)
      , len = input.length   
      , total = i = 0
      , rms
    while ( i < len ) total += Math.abs( input[i++] )
    rms = Math.sqrt( total / len )
    meter.style.width = ( rms * 100 ) + '%'
  }
  
}()
#meter {
  width: 0%;
  height: 15px;
  margin: 2px 0;
  background: green;
  -webkit-transition: width .05s;
}
<div id="meter"></div>

The important stuff happens here:

processor.onaudioprocess = function(evt){
  var input = evt.inputBuffer.getChannelData(0)
    , len = input.length   
    , total = i = 0
    , rms
  while ( i < len ) total += Math.abs( input[i++] )
  rms = Math.sqrt( total / len )
  meter.style.width = ( rms * 100 ) + '%' 
}

Basically, you grab the raw PCM data (values from -1 to 1) every 2048 samples and you loop through them, calculating the average signal level over the given period of time.

You can then use that value to do your animations.

Edit: Updated to use RMS, which as Jason pointed out is a more meaningful measurement.

like image 170
Kevin Ennis Avatar answered Nov 09 '22 13:11

Kevin Ennis


Yes, you need to grab the raw PCM samples (like Kennis mentions). However, to calculate the overall volume levels, you want to grab the RMS (Root Mean Square) of the values. Also, you will likely want to pay attention to all the channels in the stream, not just the first channel (so you can accurately reflect the volume level for a stereo stream for example).

There are some tricks (make sure you use multiplication of the same samples across channels, not addition). Then you will be adding them all together (again like Kennis is doing). If you want it an actual decibels, there is a log step that is required as well.

There is an example as an answer to this other question.

Relevant code:

var rms = Math.sqrt(sum / (_buffer.length / 2));
var decibel = 20 * (Math.log(rms) / Math.log(10));
like image 36
Jason Olson Avatar answered Nov 09 '22 15:11

Jason Olson