Vaadin based audio visualization demo

I hacked together an audio visualization component and demo for Vaadin last Sunday.

Instead of me trying to describe it, go and try it yourself at
http://vj.jole.fij
(note - url updated)

Hints:

  • Only use Chrome for now, please
  • Turn the volume as high as possible
  • Click white dot in the lower-left corner
  • Try resetting your script from
  • Challenge your friend to “multiplayer-mode” with VJ^2 button (disabled for now as it was quite buggy…)
  • Enjoy!

If your can come up with a real use-case for the visualization component, I will promise to upload it to directory :)

BTW: VJ stands for Visual DJ. But could be read as Vaadin DJ as well…

Here’s one script I quickly threw together. It’s based on the Mandelbrot set, and the end result is pretty psychedelic. NB: increasing the Iters variable value will quickly overload your browser :slight_smile:


var w = 50;
var h = 50;
var Iters = 30;
var blocksz = 20;
var c = canvas.getContext('2d');
c.fillStyle = "black";
for(y=0; y<=h; ++y) {
    var Im = -1.5+3*y/h;
    for(x=0; x<=w; ++x) {
        var Re = -2+3*x/w;
        var Zr = Re;
        var Zi = Im;
        var n = 0;
        for(; n<=Iters; n++) {
            var Zr2 = Zr*Zr;
            var Zi2 = Zi*Zi;
            if(Zr2+Zi2 > 4) break;
            Zi = 2*Zr*Zi+Im; Zr = Zr2-Zi2+Re;
        }
        if(n>Iters) n=0;
        var c1 = (n<Iters/2 ? Math.round(255*2*n/Iters) : 255);
        var c2 = (n>=Iters/2 ? Math.round(255*2*(n-Iters/2)/Iters) : 0);
        var num1 = (c1<10 ? "0" : "")+Number(c1).toString(16);
        var num2 = (c2<10 ? "0" : "")+Number(c2).toString(16);
        c.fillStyle = ((num1 + num2*256)*freq[0]
).toString(16);
        c.fillRect(x*blocksz, y*blocksz, blocksz, blocksz)
    }
}

Just paste that to the demo Joonas mentions above.

Please come up with a better version using that as a basis!

BTW, Safari works great as well.

My first take… gotta refine it still at some point :smiley:

// canvas : Your canvas filling the whole screen
// time   : Seconds from the beginning of the song
// freq[] : Result of 1024 point FFT grouped to 16 frequency groups

var c = canvas.getContext('2d');
c.fillStyle = "#000000";
c.globalAlpha = 0.2;
c.fillRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
c.globalAlpha = 0.8;
c.lineWidth = 6;
var columns = 5;
var rows = 5;
var depth = 5;
for(i = 0; i < rows+1; i++){
  for(j = 0; j < columns; j++){
    for(k = 0; k < depth; k++){
      var colorDepth = k/depth/1;
      c.strokeStyle = 'rgba(150,0,50,' + colorDepth + ')';
      var x=(canvas.offsetWidth/rows)*i;
      var y=(canvas.offsetHeight/columns)*j+canvas.offsetWidth/20;
      var xOffset = Math.sin(time)*100;
      var yOffset = (Math.cos(time))*100;
      var zOffset = 20*k;
      c.lineWidth = 3+k/2;
      var r=20*freq[0]
/1000000+(k*4);

      var finalX = x+xOffset+(zOffset*Math.cos(time/1.2));
      var finalY = y+yOffset+(zOffset*Math.sin(time/1.5));  
      c.beginPath();
      c.arc(finalX,finalY,r,0,Math.PI*2,true);
      c.closePath();
      
      c.stroke();
    } 
  }
}

Jens - did not work for me. Blank screen…

Henri - Amazing!

Two amazing animations from Matti V and Marc… Please guys start posting these here…

Matti:


var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.fillRect(0,0,canvas.height, canvas.width);
for (var y = 1 ; y <= canvas.height ; y++) {
var y2 = Math.sin((y*2 / 10 )  / 100) * 2 *Math.sin(y / 100 + time*4 ) * 100
var mid = canvas.width / 2;
var  x1 = mid + Math.sin((y2 ) * 3.14 / 256.0) * 128;
var  x2 = mid + Math.sin((y2 + 128) * 3.14 / 256.0) * 128;
var  x3 = mid + Math.sin((y2 + 256) * 3.14 / 256.0) * 128;
var  x4 = mid + Math.sin((y2 + 384) * 3.14 / 256.0) * 128;
  if (x2 - x1 > 0) {
     ctx.fillStyle = "yellow";
     ctx.fillRect(x1,y,x2-x1,1);
   }
  if (x3 - x2 > 0) {
  ctx.fillStyle = "magenta";
  ctx.fillRect(x2,y,x3-x2,1);
  }
  if (x4 - x3 > 0) {
  ctx.fillStyle = "red";
  ctx.fillRect(x3,y,x4-x3,1);
}
  if (x1 - x4 > 0) {
  ctx.fillStyle = "green";
  ctx.fillRect(x4,y,x1-x4,1);
}
}

Marc:


var c = canvas.getContext('2d');
c.fillStyle = "#000000";
c.globalAlpha = 0.1;
c.fillRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
c.globalAlpha = 0.5;

for (i=0;i<16;i++) {
  var x=(canvas.offsetWidth/16)*i+(canvas.offsetWidth/32);
  var y=canvas.offsetHeight/2;
  var r=50*freq[i]
/100000;
  var col = Math.round(r/2).toString(16);
  c.fillStyle = "#"+col+"B4F0";
  var o = Math.sin(i*time)*(r/2);
  c.font = r+"px Helvetica";
  c.fillText("\u007D\u003E",x,y+o);
}

Ok, Matti V:s thing (that he called ‘a classic’ - I think he knows his stuff) was so cool I just had to modify it.
I have no experience in this kind of stuff myself, so I just steal and improvise:

var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.fillRect(0,0,canvas.width, canvas.height);
for (var y = 1 ; y <= canvas.height ; y++) {
var y2 = Math.sin((y*2 / 10 )  / 100) * 2 *Math.sin(y / 100 + time*4 ) * 100
var mid = canvas.width / 2;
var  x1 = mid + Math.sin((y2 ) * 3.14 / 256.0) * (20+freq[0]
/3000);
var  x2 = mid + Math.sin((y2 + 128) * 3.14 / 256.0) * (50+freq[4]
/4000);
var  x3 = mid + Math.sin((y2 + 256) * 3.14 / 256.0) * (70+freq[8]
/6000);
var  x4 = mid + Math.sin((y2 + 384) * 3.14 / 256.0) * (100+freq[14]
/8000);

var f = Math.PI*2/10;
var w = 127;
var c = 128;

var r1 = Math.sin(f*time + 2 + 0) * w + c;
var g1 =  Math.sin(f*time + 0 + 0) * w + c;
var b1 =  Math.sin(f*time + 4 + 0) * w + c;

var r2 = Math.sin(f*time + 2 + 5) * w + c;
var g2 =  Math.sin(f*time + 0 + 5) * w + c;
var b2 =  Math.sin(f*time + 4 + 5) * w + c;

var r3 = Math.sin(f*time + 2 + 15) * w + c; 
var g3 =  Math.sin(f*time + 0 + 15) * w + c;
var b3 =  Math.sin(f*time + 4 + 15) * w + c;

var r4 = Math.sin(f*time + 2 + 50) * w + c;
var g4 =  Math.sin(f*time + 0 + 50) * w + c;
var b4 =  Math.sin(f*time + 4 + 50) * w + c;


  if (x2 - x1 > 0) {
     ctx.fillStyle = RGB2Color(r1,g1,b1);
     ctx.fillRect(x1,y,x2-x1,1);
   }

  if (x3 - x2 > 0) {
  ctx.fillStyle = RGB2Color(r2,g2,b2);
  ctx.fillRect(x2,y,x3-x2,1);
  }
  if (x4 - x3 > 0) {
  ctx.fillStyle = RGB2Color(r3,g3,b3);
  ctx.fillRect(x3,y,x4-x3,1);
}
  if (x1 - x4 > 0) {
  ctx.fillStyle = RGB2Color(r4,g4,b4); 
  ctx.fillRect(x4,y,x1-x4,1);
}
}

function RGB2Color(r,g,b)
  {
    return '#' + byte2Hex(r) + byte2Hex(g) + byte2Hex(b);
  }
function byte2Hex(n)
  {
    var nybHexString = "0123456789ABCDEF";
    return String(nybHexString.substr((n >> 4) & 0x0F,1)) + nybHexString.substr(n & 0x0F,1);
  }

Yeah, it turns out you can make functions. Should have used that more…

Have a good weekend, everyone!

// Marc

For some reason my viz had ‘freq’ instead of 'freq[0]
'. Maybe the forum tried to interpret it, or something, as it worked when I pasted it directly from my text editor, but not when I first posted it to the forum and from there to the VJ. Well, now it is fixed and seems to work.

I have plans on what to do, I only have to find the time. :slight_smile:

VJ seems to have a plenty of bugs. Killed a bag of them and deployed to another server. Enjoy at
http://vj.jole.fi
.

(yes - there are still plenty of bugs left - even when I disabled “2 player mode”)

Also added saving the whole script to URI fragment. Might not be the best design practice, but works.

Just try
http://tinyurl.com/cp4en8z
.

So feel free to fork any of the provided scripts, hack it to something wonderful and tweet your creation…

(and do not forget to post it here)

Here’s an evolved version of the Mandelbrot visualizer:

var c = canvas.getContext('2d');

c.save()
c.translate(canvas.width/2, canvas.height/2)

c.fillStyle = v(3) < 99 ? "black" : "white"
c.fillRect(-canvas.width, -canvas.height, canvas.width*2, canvas.height*2) 

var bsize = 25 - v(2) / 15;
var w = canvas.width/bsize, h = canvas.height/bsize;
var iterations = 50;
var scale = 3 / time/(v(1) /25);
var cx = -0.82, cy = -0.2;
var xoff = cx - w / 2 * scale, yoff = cy - h / 2 * scale;

var palette = calcPalette(time, iterations)

for (y = 0; y < h; ++y) {
  var location_y = y * scale + yoff;
  for (x = 0; x < w; ++x) {
    var location_x = x * scale + xoff;
    if(optimized(location_x, location_y)) continue;
    var current_x = location_x, current_y = location_y, temp_x = location_x;
    var iteration = 0;
    while (iteration < iterations) {
      if (current_x * current_x + current_y * current_y > 2.236) break;
      temp_x = current_x * current_x - current_y * current_y + location_x;
      current_y = 2 * current_x * current_y + location_y;
      current_x = temp_x;
      ++iteration;
    }

    if(iteration == iterations) continue;

    c.fillStyle = palette[iteration]
;
    c.fillRect(x*bsize-canvas.width/2, y*bsize-canvas.height/2, bsize, bsize);	
  }
}

c.restore()

function v(idx) {
  var offset = idx == 0 ? 0 : idx == 1 ? 4 : idx == 2 ? 8 : 12;
  return (normfreq(offset, 100) + normfreq(offset+1, 100) + normfreq(offset+2, 100) + normfreq(offset+3, 100))/4;
}

// Normalize a frequency value to 0 .. maxV
function normfreq(idx, maxV) {
  return Math.min(maxV, Math.max(0, freq[idx]
 - 50000) / 600000 * maxV)
}

function calcPalette(time, size) {
  var result = Array(size);
  var idx = (Math.round(time) % 256) / 32
  for(i = 0; i < size; ++i) result[ i ]
 = hsvToRgb(idx, i/size + 1/v(3)/2)  
  return result;
}
	
// Simplified HSV->RGB with S == V == 1
function hsvToRgb(h,d) {
  var i = Math.floor(h * 6), f = h * 6 - i, r, g, b;
  switch(i % 6) {
    case 0: r = 1, g = f, b = 0; break;
    case 1: r = 1-f, g = 1, b = 0; break;
    case 2: r = 0, g = 1, b = f; break;
    case 3: r = 0, g = 1-f, b = 1; break;
    case 4: r = f, g = 0, b = 1; break;
    case 5: r = 1, g = 0, b = 1-f; break;
  }

return "rgb(" + Math.round(r*d*255) + "," + Math.round(g*d*255) + "," + Math.round(b*d*255) + ")"
}

function optimized(x, y) {
  // Check if (x,y) is within the central cardioid
  var pc = 0.5 - 0.5 * Math.cos(Math.atan2(y, x - 0.25));
  var p = Math.sqrt((x - 0.25) * (x - 0.25) + y * y);

  if (p <= pc) return true;

  // Check if (x,y) is within one of the left circles
  var r = 0.25, x1 = 1;
  for (i = 0; i < 3; ++i) {
    if (Math.sqrt((x + x1) * (x + x1) + y * y) < r) return true;
    r /= 4;
	x1 += 5 * r;
  }
  return false;
}

Works with eg. “Action at a Distance” and “Fake French” :slight_smile:

No fractal for me - just black-n-white blinking…


Tested
with chrome…

Ah, ran into a bug in the Liferay forum. On line 55 there was supposed to be a [ i ]
(without the spaces) after the result array, and instead of printing it verbatim as code the forum interpreted it as a italic directive.

I added some spaces in there and now it should work.

WOW. Now works great.

=> Added to default set of visualizations.

Finally added a proper built-in linking as well as twitter/facebook/… sharing:


Merry Christmas

BTW: Amazon SimpleDB is very handy for this kind of “low io” applications (for built in url shortener)

Added support for any external mp3s and hacked together a bit more christmas spirited visualization:
http://vj.jole.fi/#119444471042

To use your own songs, just add the following comment line to your script:
//mp3=http://whatever.url.com/to/your/song.mp3

Happy hacking :)


Dancing worms…

I know this thread is old, but is there a chance to take a look at the source code you used for the audio file interpretation, Joonas? I plan to write a waveform display component for Vaadin, and it would be nice to have a base for that. Thanks in advance!