Drawing zoomable audio waveform timeline in Javascript - javascript

I have raw 44,1 kHz audio data from a song as Javascript array and I'd like to create a zoomable timeline out of it.
Example timeline from Audacity:
Since there are millions of timepoints normal Javascript graphics libraries probably don't cut it: I think, not sure, that normal graph libraries will die on this many timepoints. But does there exist already libraries for this sort of visualization for JS? Canvas, webGL, SVG all are acceptable solutions.
A solution preferably with zoom and pan.
Note that this happens strictly on client side and server-side solutions are not accetable.

I've looked into this same problem pretty extensively. To the best of my knowledge, the only existing project that does close to what you want is wavesurfer.js. I haven't used it, but the screenshots and the description sound promising.
See also this question.
Best of luck.

You cannot simply take the the waveform data and render all data points, this is terribly inefficient.
Variable explanation:
width: Draw area width in pixels, max is screen width
height: Same as width but then height of draw area
spp: Samples per pixel, this is your zoom level
resolution: Number of samples to take per pixel sample range, tweak for performance vs accuracy.
scroll: You will need virtual scrolling for performance, this is the scroll position in px
data: The raw audio data array, probably several million samples long
drawData: The reduced audio data used to draw
You are going to have to only take the samples that are in the viewport from the audio data and reduce those. Commenly this results in a data set that is 2 * width, you use this data set to render the image.
To zoom out increase spp, to zoom in decrease it. Changing scroll value pans it.
The following code has O(RN) complexity where N is width and R is resolution. Maximum accuracy is at spp <= resolution.
The code will look something like this, this gets the peak values, you could do rms or average as well.
let reduceAudioPeak = function(data, spp, scroll, width, resolution) {
let drawData = new Array(width);
let startSample = scroll * spp;
let skip = Math.ceil(spp / resolution);
// For each pixel in draw area
for (let i = 0; i < width; i++) {
let min = 0; // minimum value in sample range
let max = 0; // maximum value in sample range
let pixelStartSample = startSample + (i * spp);
// Iterate over the sample range for this pixel (spp)
// and find the min and max values.
for(let j = 0; j < spp; j += skip) {
const index = pixelStartSample + j;
if(index < data.length) {
let val = data[index];
if (val > max) {
max = val;
} else if (val < min) {
min = val;
}
}
}
drawData[i] = [min, max];
}
return drawData;
}
With this data you can draw it like this, you could use lines, svg etc:
let drawWaveform = function(canvas, drawData, width, height) {
let ctx = canvas.getContext('2d');
let drawHeight = height / 2;
// clear canvas incase there is already something drawn
ctx.clearRect(0, 0, width, height);
for(let i = 0; i < width; i++) {
// transform data points to pixel height and move to centre
let minPixel = drawData[i][0] * drawHeigth + drawHeight;
let maxPixel = drawData[i][1] * drawHeight + drawHeight;
let pixelHeight = maxPixel - minPixel;
ctx.fillRect(i, minPixel, 1, pixelHeight);
}
}

I have used RaphaelJS for SVG rendering in the browser at it has performed very well. It is what I would go for. Hopefully SVG will be up to the task.

Related

javascript game sprite positioning

I'm trying to create a chess board, and place it in the middle of the screen, so far i cannot get it to be directly in the center. i don't want to hard code the position to the screen because i'm going to be dealing with different screen sizes.
var winsize = cc.director.getWinSize();
var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
for (i=0; i<64; i++){
var tile = cc.Sprite.create(res.tile_png);
this.addChild(tile,0);
tile.setPosition(winsize.width+i%8*50/-10, winsize.height-Math.floor(i/8)*50);
}
But the tiles and positioning is completely off
#jumpman8947, if you're using Cocos2d js perhaps you have a similar line: cc.view.setDesignResolutionSize(480, 320, cc.ResolutionPolicy.SHOW_ALL);
In this particular case the game will scale to any sceeen, but still run in 480x320 resolution, so no matter what screen resoultion you use, the center in the cocos world would always be cc.p(240, 160) so no matter what's the window size or the screen resolution, the resolution of the game stays the same
You can read more about resolution policies here (and in official js-doc):
http://www.cocos2d-x.org/wiki/Multiple_Resolution_Policy_for_Cocos2d-JS
Also please be aware, that the Sprite position in Cocos is the position of the centre of the sprite, not bottom left corner
In your question it's not completely clear exactly what you want. However, I made some assumptions. The explanation for my solution is embedded in the comments in the code below.
// var winsize = cc.director.getWinSize();
// Here is some example hard-coded return values:
var winsize = {width: 600, height: 400};
// You can change these numbers to see how they influence
// the outcome.
// var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
// This line doesn't seem relevant for the question you asked.
// Or, rather, the following calculations will result in the tiles
// being centred on the screen anyway, so this calculation here
// is unnecessary.
// Being a chess board, I assume that you want the tiles to be square,
// i.e. to have the same width and height.
// If so, first find out which is the minimum dimension
// and calculate the tile size as being 1/8 of that.
var minDimn = Math.min(winsize.width, winsize.height);
var tileSize = minDimn / 8;
// Find out how far in from the left and how far down from the top
// you need the upper left corner of the upper left tile to start.
// This assumes that you don't need any "margin" around the board.
// (If you do need such a "margin", basically subtract it twice
// from each of winsize.width and winsize.height above.)
// Start with default values of 0 for each, but then add in the
// excess for the longer dimension, but divide it by two
// because that excess will be split between either
// the top and bottom or the left and right.
var xStart = 0, yStart = 0;
if (winsize.width > winsize.height) {
xStart = (winsize.width - winsize.height) / 2;
} else if (winsize.height > winsize.width) {
yStart = (winsize.height - winsize.width) / 2;
}
// Instead of looping through all 64 positions in one loop,
// loop through all the horizontal positions in an outer loop
// and all the vertical positions in an inner loop.
for (i = 0; i < 8; i++) {
// For the horizontal dimension, calculate x for each tile
// as the starting position of the left-most tile plus
// the width of the tile multiplied by the number of tiles (0-based)
var x = xStart + i * tileSize;
// Now the inner loop
for (j = 0; j < 8; j++) {
// Same type of calculation for the y value.
var y = yStart + j * tileSize;
// You can see the values in this demo here.
document.write("<pre>(" + x + ", " + y + ")</pre>");
// The following two lines don't seem to be relevant to the question.
// var tile = cc.Sprite.create(res.tile_png);
// this.addChild(tile,0);
// Use your calculated values in your function call.
// tile.setPosition(x, y);
}
}

Is ist possible to get/set the radius of an existing path.circle?

Starting with a path - or many of these ...
var c=paper.Path.Circle(centerPoint, 30);
c.strokeColor="";
I want to have that circle grow its radius linearly. I can do this:
var children = paper.project.activeLayer.children;
paper.view.onFrame = function(event) {
for (var i = 0; i < children.length; i++) {
var item = children[i];
item.scale(1.01);
}
But that increases the radius exponentially !
Can I get the radius of a circle and change it ? Or do I have to create a new one, deleting the old one ?
How does scale() do this ?
I also would like to delete circle larger that a given size.
Thanks,
Sebastian
You can get the radius of a circle though it's indirect.
var radius = circle.bounds.topCenter.y - circle.bounds.center.y;
or
var radius = circle.bounds.width / 2
give you the radius of a circle. But a circle is stored as 4 segments with handles in and out, not as a circle-type object, so the radius is not stored anywhere.
In order to make it seem to grow you will have to delete the old one and draw a new one of the larger size.
It's also possible to scale it, but you want to scale it without compounding the scaling. So if you want it to grow 1.01 then 1.02 instead of 1.0201, etc. you will need to adjust the scaling factor each time.
It's not perfectly clear how you want to grow the circle, but here's some code that makes some assumptions about what you want to do:
function Scale() {
this.original = 1.0;
this.current = 1.0;
}
// target refers to original size in fractional terms, e.g., to
// grow by 1% specify 1.01 or to shrink by 1% specify 0.99. It returns
// the scale factor to apply to the current scale to achieve the
// target. So to increase the scale by 10% of the original size each
// time:
//
// var s = new Scale();
//
// for (i = 1.1; i <= 2.05; i += 0.1) {
// var scaleFactor = s.scale(i);
// }
//
// note the i <= 2.05 to allow for real number math issues.
//
Scale.prototype.scale = function(target) {
// get the scaling factor from the original size
var oFactor = target / this.original;
// now get the factor to scale the current size by
var cFactor = oFactor / this.current;
this.current = oFactor;
return cFactor;
}

Control the size of object in Three.js

I am new in Three.js.
I need to implement one 3D screen in which there are many objects in sky and there is one scale in screen.
I can move the scale in up or down direction. When I move scale up then all objects come closer in endless manner and same when I move scale down then all objects goes far.
So ultimately I want the effect like I am moving in space and I am bypassing the starts.
So for getting this effect I have used Three.js.
The problem I am facing is the when the objects come closer to me their size is increased and when the come very close than their size become very large in size. I need to increase size for some fixed parameters. After that it should not increase the size of object when it come close to screen. How I can implement that?
This is the code of object rendering:
function renderobjects() {
if(speed != 0) {
if(textArray.length > 0 && textArray[0].material.opacity == 1) {
for(var i = 0; i < textArray.length; i++) {
textArray[i].material.opacity = 0;
}
}
camera.position.y += - mouseY * 0.01;
if (camera.position.y > 60) {
camera.position.y = 60;
}
if (camera.position.y < 35) {
camera.position.y = 35;
}
camera.position.z = (camera.position.z + 8*speed);
}
Please provide me the solution by which I can restrict the size of objects.
The OrthographicCamera may be of help to you, it uses parallel projection. You can then do your own selective scaling of objects based on your needs.

randomly mapping divs

I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question. So far it is going really well, I have a timer, count the right and wrong answers and when the game is started I have a number of divs called "characters" that appear in the container randomly at set times.
The problem I am having is that because it is completely random, sometimes the "characters" appear overlapped with one another. Is there a way to organize them so that they appear in set places in the container and don't overlap when they appear.
Here I have the code that maps the divs to the container..
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function scramble() {
var children = $('#container').children();
var randomId = randomFromTo(1, children.length);
moveRandom('char' + randomId);
}
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var pad = parseInt($('#container').css('padding-top').replace('px', ''));
var bHeight = $('#' + id).height();
var bWidth = $('#' + id).width();
maxY = cPos.top + cHeight - bHeight - pad;
maxX = cPos.left + cWidth - bWidth - pad;
minY = cPos.top + pad;
minX = cPos.left + pad;
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX);
$('#' + id).css({
top: newY,
left: newX
}).fadeIn(100, function () {
setTimeout(function () {
$('#' + id).fadeOut(100);
window.cont++;
}, 1000);
});
I have a fiddle if it helps.. http://jsfiddle.net/pUwKb/8/
As #aug suggests, you should know where you cannot place things at draw-time, and only place them at valid positions. The easiest way to do this is to keep currently-occupied positions handy to check them against proposed locations.
I suggest something like
// locations of current divs; elements like {x: 10, y: 40}
var boxes = [];
// p point; b box top-left corner; w and h width and height
function inside(p, w, h, b) {
return (p.x >= b.x) && (p.y >= b.y) && (p.x < b.x + w) && (p.y < b.y + h);
}
// a and b box top-left corners; w and h width and height; m is margin
function overlaps(a, b, w, h, m) {
var corners = [a, {x:a.x+w, y:a.y}, {x:a.x, y:a.y+h}, {x:a.x+w, y:a.y+h}];
var bWithMargins = {x:b.x-m, y:b.y-m};
for (var i=0; i<corners.length; i++) {
if (inside(corners[i], bWithMargins, w+2*m, h+2*m) return true;
}
return false;
}
// when placing a new piece
var box;
while (box === undefined) {
box = createRandomPosition(); // returns something like {x: 15, y: 92}
for (var i=0; i<boxes.length; i++) {
if (overlaps(box, boxes[i], boxwidth, boxheight, margin)) {
box = undefined;
break;
}
}
}
boxes.push(box);
Warning: untested code, beware the typos.
The basic idea you will have to implement is that when a random coordinate is chosen, theoretically you SHOULD know the boundaries of what is not permissible and your program should know not to choose those places (whether you find an algorithm or way of simply disregarding those ranges or your program constantly checks to make sure that the number chosen isn't within the boundary is up to you. the latter is easier to implement but is a bad way of going about it simply because you are entirely relying on chance).
Let's say for example coordinate 50, 70 is selected. If the picture is 50x50 in size, the range of what is allowed would exclude not only the dimensions of the picture, but also 50px in all directions of the picture so that no overlap may occur.
Hope this helps. If I have time, I might try to code an example but I hope this answers the conceptual aspect of the question if that is what you were having trouble with.
Oh and btw forgot to say really great job on this program. It looks awesome :)
You can approach this problem in at least two ways (these two are popped up in my head).
How about to create a 2 dimensional grid segmentation based on the number of questions, the sizes of the question panel and an array holding the position of each question coordinates and then on each time frame to position randomly these panels on one of the allowed coordinates.
Note: read this article for further information: http://eloquentjavascript.net/chapter8.html
The second approach follow the same principle, but this time to check if the panel overlap the existing panel before you place it on the canvas.
var _grids;
var GRID_SIZE = 20 //a constant holding the panel size;
function createGrids() {
_grids = new Array();
for (var i = 0; i< stage.stageWidth / GRID_SIZE; i++) {
_grids[i] = new Array();
for (var j = 0; j< stage.stageHeight / GRID_SIZE; j++) {
_grids[i][j] = new Array();
}
}
}
Then on a separate function to create the collision check. I've created a gist for collision check in Actionscript, but you can use the same principle in Javascript too. I've created this gist for inspirational purposes.
Just use a random number which is based on the width of your board and then modulo with the height...
You get a cell which is where you can put the mole.
For the positions the x and y should never change as you have 9 spots lets say where the mole could pop up.
x x x
x x x
x x x
Each cell would be sized based on % rather then pixels and would allow re sizing the screen
1%3 = 1 (x)
3%3 = 0 (y)
Then no overlap is possible.
Once the mole is positioned it can be show or hidden or moved etc based on some extended logic if required.
If want to keep things your way and you just need a quick re-position algorithm... just set the NE to the SW if the X + width >= x of the character you want to check by setting the x = y+height of the item which overlaps. You could also enforce that logic in the drawing routine by caching the last x and ensuring the random number was not < last + width of the item.
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX); if(newX > lastX + characterWidth){ /*needful*/}
There could still however be overlap...
If you wanted to totally eliminate it you would need to keep track of state such as where each x was and then iterate that list to find a new position or position them first and then all them to move about randomly without intersecting which would would be able to control with just padding from that point.
Overall I think it would be easier to just keep X starting at 0 and then and then increment until you are at a X + character width > greater then the width of the board. Then just increase Y by character height and Set X = 0 or character width or some other offset.
newX = 0; newX += characterWidth; if(newX + chracterWidth > boardWidth) newX=0; newY+= characterHeight;
That results in no overlap and having nothing to iterate or keep track of additional to what you do now, the only downside is the pattern of the displayed characters being 'checker board style' or right next to each other (with possible random spacing in between horizontal and vertical placement e.g. you could adjust the padding randomly if you wanted too)
It's the whole random thing in the first place that adds the complexity.
AND I updated your fiddle to prove I eliminated the random and stopped the overlap :)
http://jsfiddle.net/pUwKb/51/

HTML 5 canvas animation - objects blinking

I am learning ways of manipulating HTML 5 Canvas, and decided to write a simple game, scroller arcade, for better comprehension. It is still at very beginning of development, and rendering a background (a moving star field), I encountered little, yet annoying issue - some of the stars are blinking, while moving. Here's the code I used:
var c = document.getElementById('canv');
var width = c.width;
var height = c.height;
var ctx = c.getContext('2d');//context
var bgObjx = new Array;
var bgObjy = new Array;
var bgspeed = new Array;
function init(){
for (var i = 1; i < 50; i++){
bgObjx.push(Math.floor(Math.random()*height));
bgObjy.push(Math.floor(Math.random()*width));
bgspeed.push(Math.floor(Math.random()*4)+1);
}
setInterval('draw_bg();',50);
}
function draw_bg(){
var distance; //distace to star is displayed by color
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,width,height);
for (var i = 0; i < bgObjx.length; i++){
distance = Math.random() * 240;
if (distance < 100) distance = 100;//Don't let it be too dark
ctx.fillStyle = "rgb("+distance+","+distance+","+distance+")";
ctx.fillRect(bgObjx[i], bgObjy[i],1,1);
bgObjx[i] -=bgspeed[i];
if (bgObjx[i] < 0){//if star has passed the border of screen, redraw it as new
bgObjx[i] += width;
bgObjy[i] = Math.floor(Math.random() * height);
bgspeed[i] = Math.floor (Math.random() * 4) + 1;
}
}
}
As you can see, there are 3 arrays, one for stars (objects) x coordinate, one for y, and one for speed variable. Color of a star changes every frame, to make it flicker. I suspected that color change is the issue, and binded object's color to speed:
for (var i = 0; i < bgObjx.length; i++){
distance = bgspeed[i]*30;
Actually, that solved the issue, but I still don't get how. Would any graphics rendering guru bother to explain this, please?
Thank you in advance.
P.S. Just in case: yes, I've drawn some solutions from existing Canvas game, including the color bind to speed. I just want to figure out the reason behind it.
In this case, the 'Blinking' of the stars is caused by a logic error in determining the stars' distance (color) value.
distance = Math.random() * 240; // This is not guaranteed to return an integer
distance = (Math.random() * 240)>>0; // This rounds down the result to nearest integer
Double buffering is usually unnecessary for canvas, as browsers will not display the drawn canvas until the drawing functions have all been completed.
Used to see a similar effect when programming direct2d games. Found a double-buffer would fix the flickering.
Not sure how you would accomplish a double(or triple?)-buffer with the canvas tag, but thats the first thing I would look into.

Categories