Executing 'realtime' javascript without hanging the browser - javascript

I'm trying to write a simple music-sequencer in Javascript.
Sounds will be played by SoundManager2
I quickly realised that setTimeout and setInterval are useless for this kind of timing. Their accuracy is simply not good enough.
So what I am trying instead is this:
Create a queue of sound-events (a 2d array [ note, time ] )
Process the queue in a while loop
In pseudo code it could look like this:
// The queue of time/note values (millisecs)
var q = [[0, C], [250, D], [500, E]]
var begin = (new Date).getTime()
while(q.length > 0 ){
var now = (new Date).getTime()
var eventTime = q[0][0] + begin
if( now >= eventTime){
playNote(q[0][1]) // Play the note
q.shift() // Now that the note has been played, discard it.
}
}
Through debugging I've found that this method seems precise enough (the playNote call is made at the exact time it's supposed to).
However while the 'sequence' is playing, all other Javascript (including the bit that actually MAKES the sound) is suspended which is hardly a surprise.
This means that I have silence for as long time it takes to run the sequence and then ALL of the calls to playNote are executed.
I've tried isolating the while-loop in it's own function and then call it through setTimeout, in the hopes that this would create a background thread executing it's own thing (namely playing the sequence) without halting all other JS execution. That doesn't work
I've also tried calling the playNote function using setTimeout, in the hopes that while indeed the UI is frozen at least the sequence is playing correctly, but that doesn't work either.
I've also tried a combination, but as you probably already guessed, that doesn't work either.
So my question is:
How can I have a queue of events processed with precise timing, while NOT shutting down all other code-execution during the run

I don't know if there is a solution for older browers, but web workers are intented for doing parrallel execution in JavaScript.
They are supported in recent versions of Chrome and Firefox, I don't know about other browsers.

If you replace your while loop with a recursive function (it doesn't have to be self-executing) the browser wont freeze.
(function foo (q) {
var now = (new Date).getTime(),
eventTime = q[0][0] + begin;
if (q.length > 0) {
if (now >= eventTime) {
playNote(q[0][1]); // Play the note
q.shift(); // Discard played note
}
return foo(q); // Pass q into the next round
} else {
return; // End the "loop"
}
}(q));
Edit: The problem with recursive calls + timer accuracy should be taken care of by setInterval:
var timer = setInterval(function foo () {
var now = (new Date).getTime(),
eventTime = q[0][0] + begin;
if (q.length > 0) {
if (now >= eventTime) {
playNote(q[0][1]); // Play the note
q.shift(); // Discard played note
}
} else {
clearInterval(timer); // End the "loop"
}
}, 10); // This is in ms (10 is just a random number)

Related

Performence: When animating using requestAnimationFrame, is an interval better than manually calculating the ellasped time?

In examples I see two different ways how to handle animations using requestAnimationFrame():
The first one using setTimeout()
const e = document.getElementById('e');
let count = 0;
function move(timestamp) {
e.style.left = ++count + 'px';
setTimeout(f=>{
requestAnimationFrame(move);
}, 200);
};
requestAnimationFrame(move);
Try it in this jsfiddle.
The second one by calulating the ellapsed time yourself
const e = document.getElementById('e');
let count = 0;
let past = null;
function move(timestamp) {
if (past !== null && timestamp - past < 200) {
requestAnimationFrame(move);
return;
}
past = timestamp;
e.style.left = ++count + 'px';
requestAnimationFrame(move);
};
requestAnimationFrame(move);
Try it in this jsfiddle.
Now my question is: Which one performs better?
My guess is, that if an interval is applicable, it will perform better. There aren't so many logical expressions to evaluate and so many calculations to do. But that's just a guess. So, are their any benefits of one way over the other?
You should do the first one. However you shouldn't assume it will be exactly 200ms. If you need to know exactly how many milliseconds it's been, you must calculate it every time like in your second example, but keep using the structure of your first example.
The problem with the second example is that it calculates the new frames just as often as the first example, but it runs the function just to check that it doesn't need to run the function every other time it runs. Lots of wasted computation there.
You need to calculate the time elapsed (if you need accuracy) because the setTimeout combined with the requestAnimationFrame will not be guaranteed to run in 200ms. The requestAnimationFrame in particular will add time onto that if it feels it needs to before it's ready to give you permission to animate (which is basically what requestAnimationFrame is -- it's giving you permission to animate, saying 'I'm ready now').

Scheduling callbacks in Web Audio buffers

I'm using the Web Audio API to play an MP3 file. I'm unaware of how to schedule events to happen at certain intervals, namely at 0.25, 0.5, 0.75 1 second intervals for quarter notes when using a source buffer (i.e., not creating the sound like certain guides suggest: http://middleearmedia.com/timed-rhythms-with-web-audio-api-and-javascript/).
My context is setup like the following:
function setupAudioNodes() {
javascriptNode = context.createScriptProcessor(2048, 1, 1);
javascriptNode.connect(context.destination);
analyser = context.createAnalyser();
analyser.smoothingTimeConstant = 0.3;
analyser.fftSize = 32;
sourceNode = context.createBufferSource();
sourceNode.connect(analyser);
analyser.connect(javascriptNode);
sourceNode.connect(context.destination);
}
I can retrieve the current time using context.currentTime where context is an audio context, but assigning a callback on that doesn't happen regularly.
I've tried something trivial such as:
if ( context.currentTime.toFixed(2).split(".")[1] === .25 ) {
runFunc();
}
However, this doesn't not happen at regular intervals. If I print out the output it doesn't always print out the same time intervals which means that this approach doesn't appear to work well.
Another approach I've tried is using setInterval which I know is far from ideal for lots of reasons but that doesn't work either and will call the function once following the normal interval pace (1 second) and then breaks down into calling it many times.
javascriptNode.onaudioprocess = function() {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
setInterval(myFunc(array), 1000); // first time ok, after it runs many times a second
}
My last, somewhat naive approach, is to create a range of acceptable values. The application I'm working with doesn't need to be extremely precise so I'm hoping I can listen for ranges such as 0.26 - 0.28. This approach almost works except that it still calls the function several times but several times only every second.
if (time >= 0 && time <= 25 ) {
myFunc(array);
}
Where am I going wrong?
You can run a checking function very often, for example every 10ms and check if specified time has exceeded. If yes, run the callback and clear the interval:
var intervalId = setInterval(function() {
if (time >= 25) {
runFunc();
clearInterval(intervalId);
}
}, 10);

Stop Javascript function if it's freeze to long

I've got
function doSome(){}
What should I do if I want stop execution of this function when it's executing more then 200 mills?
Thanks a lot.
I guess 200 milliseconds it's a little to short, but:
if you have a while loop, create a Date object before entering the loop and a new one in the loop. If the difference is greater than 200, break it
start = new Date()
while (1) {
end = new Date()
if (end - start > 200) break
// work
{
If you're executing Ajax call, see aborting AJAX call
A web worker can be terminated with its terminate function. There is no other way to interrupt execution of Javascript from within Javascript itself. Note that many browsers also have slow script detection which prompts the user when a script runs a certain amount of time or number of instructions. This is not controllable from Javascript, however.
Depending on the kind of algorithm that is implemented within the function, you could check how long it's been running, but it isin't very precise and you would have to make sure that each loop iteration takes very little time to process.
For instance:
function doSomething() {
var startTime = new Date.now(),
i = -1,
itemsToProcess = [1, 2, 3, 4],
len = itemsToProcess.length;
while ((Date.now() - startTime) <= 200 && ++i < len) {
//process items here
}
}
You could also avoid the freezing by giving the browser some time to breath using
setTimeout or setInterval. That is probably a better approach if you want to task to always complete.
function startDoSomething(items, delay) {
var i = arguments[2] || 0;
//process
console.log('processing item at: ' + i);
if (++i < items.length) {
setTimeout(function () {
startDoSomething(items, delay, i);
}, delay);
}
}
Finally, a much more efficient way to handle long-running processes would be to use a web worker, you can't directly update the DOM from the worker.

Function to slow down a loop using callbacks gets messed up when used recursively

note: I may be using the word recursively wrong
I'm trying to create a script that runs through a long string of text, searching for the recurrence of substrings. For this I essentially do a for-loop from 12 to 3 (lengths of strings I want to check for recurrence), and for each of those lengths I use another for loop to define if the string of that length is present multiple times. A dumbed down example (untested, just for demonstration):
for(var len=12; len>3; len--) {
var recurring = [];
for(var pos = 0; pos < (inputString.length - len); pos++) {
var mystring = inputString.substr(pos, len);
// do a regex to check if mystring occurs more than once
// if so, push it into recurring and remove from inputString to prevent partial duplicates when mystring gets shorter
}
// output recurring strings
}
This all works, but obviously when I run this on a very long inputString it gets very slow. In fact, it freezes the browser, and any progress output that I want to show while the script runs is paused and shows up all at once when the script is done.
To prevent this, I created a function that is supposed to run through the for-loop in small batches at a time, using a callback function to proceed to the next batch. By using a basic version of this, I was able to output progress onto the page during what used to be a for-loop, even if it's going through thousands of loops:
var fragmentedFor = function($aFragmentLength, $aTotal, $aFunction, $aCallback)
{
var $lStart = 0,
$lFragmentLength = $aFragmentLength,
$lTotal = $aTotal,
$lFunction = $aFunction,
$lCallback = $aCallback;
// simulate a for loop, but in fragments to prevent the browser from freezing
(function forLoop ($aStart)
{
// run x loops at a time
var $lEnd = $lStart + $lFragmentLength;
// can't run the loop past the total length
if($lEnd > $lTotal )
$lEnd = $lTotal;
// the fragmented for loop
for(var $i = $lStart; $i < $lEnd; $i++) {
// run the function here
$lFunction({count: $i});
}
// next time, start from where we just left
$lStart += $lFragmentLength;
// is there room for more loops?
if($lStart < $aTotal) {
setTimeout(forLoop, 10);
} else {
// if not, run the callback
$lCallback();
}
})();
}
When I run this just once it seems to do what I want:
function myLoop(x) {
new fragmentedFor(
10, // amount of loops per run
x, // total amount of loops
function($aData) { console.log($aData.count); },
function() { console.log('finished') }
);
}
myLoop(1000);
However, when I want to call this one nested within another instance of such a loop, I run into trouble:
new fragmentedFor(
1,
5,
function($aData) { myLoop($aData.count * 100) },
function() { console.log('finished all') }
)
(I have copied my current files here: http://files.litso.com/vigenere/so/, which includes the fragmentedFor function so you can essentially paste these two functions in the console and see the result. This was a lot easier than putting it on jsfiddle and making that work, if you guys don't mind)
What appears to be happening is that the first run of myLoop is started, but since it's trying to run 0 * 100 times it ends safely, going to the second run. The second run of myLoop should go up to 100, but it runs to 19 before suddenly run 3 of myLoop kicks in.
From there on, the output seems to be totally mixed up, I figure because my callbacks are implemented incorrectly and the loops are not really waiting for eachother to finish.
What am I doing wrong here? How can I even start debugging where the problem lies, and how can I make sure that the independent runs of the for loop actually wait for eachother to finish?
-edit-
Here's a jsfiddle of an older working copy: http://jsfiddle.net/u2aKX/
This did not incorporate the fragmentedFor loop, it does have some callback functions which seemed to improve the performance compared to regular nested for-loops by 100%.
I figure because my callbacks are implemented incorrectly and the loops are not really waiting for eachother to finish.
What am I doing wrong here?
Your fragmentedFor expects the $aFunction to be synchronous, and as soon as it returns it schedules the next loop turn.
Yet, by using a nested fragmentedFor in that function, you're making it asynchronous - and the iterations of the inner loops will start to crossfire.
As you recogniced, you will have to let them wait to finish - which means hooking the next iteration on the callback of the previous one. You will have to write another fragmentedFor that deals with asynchronous iteration steps:
function fragmentedForAsync(from, to, body, callback) {
var i = from;
(function loop() {
if (i < to)
body(i++, loop); // pass itself as callback
else
callback();
})();
}
and then use it like this:
fragmentedForAsync(1, 5, function(i, callback) {
fragmentedFor(10, i*100, function(j) {
console.log(j);
}, function() {
console.log('finished '+1);
callback(); // next turn of outer loop
});
}, function() {
console.log('finished all')
});

JS hangs on a do while loop

I am wondering how I can solve a hanging page with JS.
I have a JS loop which I'm testing like this, but it seems to hang forever - is there a way to stop it hanging while still completing the script ?:
<div id="my_data"></div>
<script>
function test(value){
output= [];
do{
value++;
output.push(value);
document.getElementById('my_data').innerHTML = (output.join(''));
}while(value < 10000000);
alert('end'); // never occurs
}
test(0);
</script>
You're updating the DOM with an increasingly long string ten million times.
If you're some sort of super-being from the future, then maybe this makes sense.
Javascript is single-threaded, so nothing else can happen while it's running your loop. It's running ten million times, and on each run it's setting the innerHTML of #my_data to something new. It's very inefficient and seems useless. What are you trying to accomplish?
You are concatenating 10,000,000 consecutive numbers together into a string, which really will not work well on any modern supercomputer.
If you'd like to poll the progress, setup a timer to do it somewhat slower. It isn't practical, but it looks nice: http://jsfiddle.net/V6jjT/2/
HTML:
<div id="my_data"></div>
Script:
var value = 0;
var interval = setInterval(function() {
if (value > 100) {
clearInterval(interval);
return;
}
value++;
document.getElementById('my_data').innerHTML += ' ' + value;
}, 10);
Running a tight loop like that will not update anything until it's done; besides, 10M array items?!
If you really want to do this and not let the browser hang until forever you have to use setInterval to allow the browser to refresh in between.
function updater(value, max, interval) {
var output = [],
node = document.getElementById('my_data'),
tid = setInterval(function() {
if (value++ < max) {
output.push(value);
node.innerHTML = (output.join(''));
} else {
alert('done');
clearInterval(tid);
}
}, interval);
}
updater(0, 10, 200);
You should not update the HTML each iteration.
function test(value){
output= [];
do {
value++;
output.push(value);
} while(value < 10000000);
document.getElementById('my_data').innerHTML = (output.join(''));
alert('end'); // never occurs
}
should work (although 10 million numbers in a HTML will still need their time to render).
If you want to see numbers running, you should have a look at window.setInterval - the browser needs some time between js execution to refresh its view. So, you will have to rewrite your code running several chunks of data asynchrously. See also:
Running a long operation in javascript? (example code for your loop)
How can I force the browser to redraw while my script is doing some heavy processing?
Javascript async loop processing
Prevent long running javascript from locking up browser
DOM refresh on long running function
Best way to iterate over an array without blocking the UI
Javascript - how to avoid blocking the browser while doing heavy work?
Tight loop is likely to always exhaust computing power. I would do it in chunks. Say, divide your list into smaller lists and pause 5ms in between lists. I can provide an example if you need.

Categories