Javascript: Alternative to setTimeOut for FAST Timer in MIDI Sequencer App - javascript

I'm working on a Javascript Music App that includes a Sequencer. For those who are not familiar, MIDI sequencers work pretty much like this: There is something called PPQ: pulses per quarter note. Each pulse is called "Tick". It depicts how may "subdivisions" there are per quarter note, like resolution. So Sequencers "play" the Events that are in the tracks one Tick at a time: Play Tick1, wait Tick Duration, Play tick2, Tick Duration, and so on.
Now, let's say we have a BPM (Beats per Min) of 120 with PPQ=96 (standard). That means that each Quarter Note Duration is 500ms, and each Tick Duration is 5.20833ms.
What Timer Alternatives we have in Javascript?
1) We have the old setTimeOut. It has several problems: the min. wait time is 4ms. (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Minimum_delay_and_timeout_nesting)
It is also subject to JITTER/time Variations. It is not precise and it is demanding, as call backs are stacked in the even loop.
2) There is an alternative to setTimeOut/setInterval which involves using requestAnimationFrame(). It is VERY precise and CPU efficient. However, the minimum time it can be set is around 16.7ms (the duration of a Frame in a typical 60FPS monitor)
Is there any other Alternative? To to precisely schedule an event every 2-5ms?
Note: the function done in side the loop, playEventsAtTick() is NOT demanding at all, so it would never take more time to execute than Tick Duration.
Thanks!
Danny Bullo

To maintain any sanity in doing this kind of thing, you're going to want to do the audio processing on a devoted thread. Better yet, use the Web Audio API and let people who have been thinking about these problems for a long time do the hard work of sample-accuracy.
Also check out Web MIDI (chrome only).

Thanks nvioli. I'm aware of Web Audio API. However, I don't think that can help here.
I'm not triggering AUDIO directly: I have MIDI events (or let's say just "EVENTS") stored in the TRACKS. And those events happen at any TICK. So the Sequencer needs to loop every Tick Duration to scan what to play at that particular tick.
Regards,
Danny Bullo

In a separate thread, such as a web worker, you can create an endless loop. In this loop, all you need to do is calculate the time between beats. After the time is valid, you can then send a message to the main process, to do some visuals, play a sound or what ever you would like to do.
Here is a Working example
class MyWorker {
constructor() {
// Keeps the loop running
this.run = true
// Beats per minute
this.bpm = 120
// Time last beat was called
this.lastLoopTime = this.milliseconds
}
get milliseconds() {
return new Date().getTime()
}
start() {
while (this.run) {
// Get the current time
let now = this.milliseconds
// Get the elapsed time between now and the last beat
let updateLength = now - this.lastLoopTime
// If not enough time has passed restart from the beginning of the loop
if (updateLength < (1000 * 60) / this.bpm) continue;
// Enough time has passed update the last time
this.lastLoopTime = now
// Do any processing that you would like here
// Send a message back to the main thread
postMessage({ msg: 'beat', time: now })
}
}
}
new MyWorker().start()
Next we can create the index page, which will run the worker, and flash a square everytime a message comes back from the worker.
<!DOCTYPE html>
<html lang="en">
<head>
<script>
// Start the worker
var myWorker = new Worker('worker.js')
// Listen for messages from the worker
myWorker.onmessage = function (e) {
var msg = e.data
switch (msg.msg) {
// If the message is a `beat` message, flash the square
case 'beat':
let div = document.querySelector('div')
div.classList.add('red')
setTimeout(() => div.classList.remove('red'), 100)
break;
}
}
</script>
<style>
div { width: 100px; height: 100px; border: solid 1px; }
.red { background: red; }
</style>
</head>
<body>
<div></div>
</body>
</html>

Get Off My Lawn: The approach you suggested does not completely work. Let's say I add a method to the web worker to STOP the Sequencer:
stop() {
this.run = false;
}
The problem is that the method myWorker.onmessage = function (e) {...} never get's triggered. I suspect it is because the Web Worker Thread is "TOO BUSY" with the endless loop. any way to solve that?
Also, while playing, it works.....but the CPU goes up considerably..... The only possible Solution would be a Sleep() method, but Real SLEEP that does not exist in Javascript...
Thanks

Related

Need Javascript help to avoid timeouts when loading audio files

I support several churches that don't have musicians, by providing a little website with a bunch of pure Javascript so they can select music for their services from a collection of about 1100 mp3 and m4a music files. Previously, they created playlists in iTunes or Media Player, but after a track completed, the player would immediately start the next track unless they quickly clicked 'Stop'. So my website allows them to select all their music ahead of time (up to 10 tracks), with a separate "Play" button for each. Hit "Play" and it plays that one track and stops. (Duh.)
I'm encountering delays in loading the files into my "audio" tags - and I need the file to load when they select it so I can display the track duration, which is frequently important to the selection of the music for the service. A delay doesn't occur very often, but often enough to be annoying. Also, the load will occasionally time out completely, even after several attempts. I've experimented played with various techniques, like using setTimeout with different values to allow several seconds before checking if it's loaded, or, loading 5 or 10 times with shorter timeout values until it's loaded. I created a test page that indicates that the timeouts vary greatly - from 2% to 5% of the time, to upwards of 25% occasionally (during tests of 1,000 to 10,000 random loads).
My first technique was relying on events (I tried both 'canplay' and 'canplaythrough' events with minimal difference):
const testAudio = document.getElementById('test-audio');
let timeStart = Date.now();
function loadMusic(p_file) {
testAudio.src = p_file;
testAudio.addEventListener('canplaythrough', musicLoaded);
timeStart = Date.now();
testAudio.load();
}
function musicLoaded() {
console.log('music loaded in ' + (Date.now()-timeStart) + 'ms');
testAudio.removeEventListener('canplaythrough', musicLoaded);
/* should I add/remove the listener each time I change the source file ? */
}
My second approach (from a post here: https://stackoverflow.com/questions/10235919/the-canplay-canplaythrough-events-for-an-html5-video-are-not-called-on-firefox) is to check the 'readyState' of the audio element after a specified timeout, rather than relying on an event. This question specifically addressed Firefox, so I should mention that in my tests Firefox has horrible load times for both the "events" and the "readyState" techniques. Chrome and Edge vary in the range of 2% to 6% load failure due to timeout and Firefox has 27% to 39% load timeouts.
let myTimeout = '';
function loadMusic(p_file) {
myTimeout = setTimeout(fileTimeout, 1000); /* I've tried various values here */
testAudio.src = p_file;
timeStart = Date.now();
testAudio.load();
}
function fileTimeout() {
if (testAudio.readyState > 3) {
console.log('music loaded in ' + (Date.now()-timeStart) + 'ms');
} else {
/* here, I've tried calling loadMusic again 5 to 10 times, which sometimes works */
/* or, just reporting that the load failed... */
console.log('music FAILED to load!');
}
}
I have a shared server hosting plan, and I suspect the delay might be due to traffic on my server. Unfortunately, my hosting service turns a deaf ear to anything that might be application or content related (not surprising). And this isn't worth upgrading to a dedicated server just to eliminate that variable. But I suspect that might be a major factor here.
I need a technique that will always work - even if it takes 30 seconds or more. As long as I can display an intermittent "Still loading..." type message I (and my users) would be satisfied. The "track X won't load" messages happen often enough to be annoying. Early on, I had a few files with bad characters in the file name that needed to be fixed before they would load. So the users think that problem persists. But I know I've fixed all them now.
Any and all suggestions are welcome - but I'd love to keep everything in plain Javascript.
Using an audio constructor:
function loadMusic(p_file) {
myTimeout = setTimeout(fileTimeout, 1000);
let audioConst = new Audio();
audioConst.src = p_file;
timeStart = Date.now();
}
function fileTimeout() {
if (audioConst.readyState > 3) {
console.log('music loaded in ' + (Date.now()-timeStart) + 'ms');
} else {
console.log('music FAILED to load!');
}
myTimeout = '';
}

Interval synced to Time

I want to execute a function in an interval. Yeah I could use setInterval but I need the interval to be synced to the timestamp or something.
Like I want to execute the interval on two different devices and they should run in the exact same second or even ms if possible. But depending on when I star the script these intervals would be offset if I would use setInterval method.
I've already tried this but it kinda acts weird.
setInterval(() => {
if (new Date().getTime() % 1000 * 10 == 0) {
console.log(new Date().toLocaleTimeString())
}
}, 1);
Like I want to execute the interval on two different devices and they should run in the exact same second or even ms if possible.
There's no guarantee that you can do this, not least because the JavaScript thread on one of the devices may be busy doing something else at that precise moment (it could even be tied up for several seconds).
Other than that, there's the issue of synchronizing the devices. Options are:
Some kind of synchronization event you send simultaneously to both devices. You'd run your code in response to the synchronization event received from your server. This is naturally subject to network delays, it requires a server to send the event (probably over web sockets), and is subject to the above caveat about the JavaScript thread being busy.
Relying on the devices being synced to exactly the same time source (for instance, perhaps they're both using a NIST time server or similar). If you know their times are synchronized sufficiently for your purposes, you can schedule your timer to fire at a precise moment, like this:
// Fire at exactly 14:30 GMT on 2021-04-21
const target = new Date(Date.UTC(2021, 3, 21, 14, 30)); // 3 = April, starts with 0 = Jan
const delay = Date.now() - target;
if (delay < 0) {
// It's already later than that
} else {
setTimeout(() => {
// Your code here
}, delay);
}
BUT, again, if the JavaScript thread is busy at that precise moment, the timer callback will run later, when the thread is free.
The code above schedules a single event, but if you need a recurring one, you can do the same basic logic: Determine the date/time you want the next callback to occur, find out how many milliseconds it is between now and then (Date.now() - target), and schedule the callback for that many milliseconds later.

Loop function for specified time, followed by delay

I have a small piece of code that continuously clicks a button called "See Older Messages" every 500 ms, in order to load infinitely-scrolled content from a webpage. Reasons for doing this are personal, but needless to say, I'm trying to automate something which would take me weeks of non-stop scrolling to do otherwise.
The problem is that the 500 ms delay gradually begins to drop as the script runs over time. After so many hours, it can take 5 seconds or more. I'm assuming this problem is caused by Facebook throttling my requests after so long, so to prevent this, I want to make the script run for an amount of time - say 2 minutes - followed by a delay of maybe 20 secs before it runs again for 2 mins, and so on. How would I go about doing this? I've racked my brains, but my limited knowledge of JavaScript hasn't come up with anything meaningful.
Below is the current code in its entirety.
setInterval(function () {
document.getElementById('see_older').getElementsByClassName('content')[0].click();
}, 500);
Thanks a lot in advance.
Keep track of when the script running started
While it's been less than 2 mins, keep clicking every 500ms.
After running for ~2 mins, stop and queue next run in 20s.
Go to step 2.
-
var lastChange;
function doClick() {
if (new Date() - lastChange < 120000 /* 2 mins */) {
document.getElementById('see_older').getElementsByClassName('content')[0].click();
setTimeout(doClick, 500);
} else setTimeout(runScript, 20000 /* 20s */);
}
(function runScript() {
lastChange = new Date();
doClick();
})();
-
I recommend using setTimeout over setInterval since, if the browser takes a while to execute, loses focus and stops executing JS, gets paged out, etc., then you will still get the time spacing between events that you want. See https://stackoverflow.com/a/731625/1059070.
Toggle whether or not your function does anything by setting another timer.
/* When true do load else don't. */
window.doLoad = true
setInterval(function () {
if window.doLoad {
document.getElementById('see_older').getElementsByClassName('content')[0].click();
}
}, 500);
/* This will toggle doLoad every two minutes. */
setInterval(function () {
if (window.onLoad == true) {
window.doLoad = false;
} else { window.doLoad = true; }
}, 120000); // two minutes of milliseconds
In your case though you might be better off using the Facebook Graph API.
Graph API documentation from Facebook
Here's an existing question with the API using Python to do basically the same thing you want to do.
JS question, also similar

Web Audio API and real current time when playing an audio file

I am having problems when I want to know the current time of a file playing using the Web Audio API. My code plays the file nicely and the current time returned by the getCurrentTime() function is accurate enough when it comes to short files which load fast.
But when I try to load big files, sometimes the current time returned by the getCurrentTime() function is accurate and sometimes not. Sometimes, after waiting for example for 20 seconds to hear the file playing, when it starts playing it says that the current time is about 20 seconds (which is not true because it is just playing the beginning of the file). It happens with any audio format (OGG, MP3, WAV...) but only sometimes.
I am using a slow system (Asus EEE PC 901 with an Intel Atom 1.60 Ghz and 2 GB RAM with Windows XP Home Edition and SP3) and Firefox 41.0.1.
I am not sure, but it seems that the source.start() method starts playing the sound way too late, so the line after calling that method, where I set the value for the startTime variable, is not the real starting time.
Here is the code (simplified):
var context, buffer, startTime, source;
var stopped = true;
function load(file, startAt)
{
//Here creates the AudioContext and loads the file through XHR (AJAX) and gets the buffer. All works fine.
//When it gots the buffer through XHR (AJAX) and all is fine, it calls play(startAt) function immediately.
//Note: normally, startAt is 0.
}
function play(startAt)
{
source = context.createBufferSource(); //Context created before.
source.buffer = buffer; //Buffer got before from XHR (AJAX).
//Creates a gain node to be able to set the volume later:
var gainNode = context.createGain();
source.connect(gainNode);
gainNode.connect(context.destination);
//Plays the sound:
source.loop = false;
source.start(startAt, 0, buffer.duration - 3); //I don't want the last 3 seconds.
//Stores the start time (useful for pause/resume):
startTime = context.currentTime - startAt; //Here I store the startTime but maybe the file has still not begun to play (should it be just startTime = context.currentTime?).
stopped = false;
}
function stop()
{
source.stop(0);
stopped = true;
}
function getCurrentTime()
{
return (stopped) ? 0 : context.currentTime - startTime;
}
How can I detect when exactly the source.start() method starts playing the file? So I can set the startTime variable value just at that moment, and never before.
Thank you very much in advance. I would really appreciate any kind of help.
From MDN (https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/start), about the first parameter of the start() function:
when (Optional)
The time, in seconds, at which the sound should begin to play, in the same time coordinate system used by the AudioContext. If when is less than (AudioContext.currentTime, or if it's 0, the sound begins to play at once. The default value is 0.
There is no evident issue with your code (although there is no example a call to play()): if you call play(0) or play(context.currentTime + someDelayInSeconds), start() should behave as expected. Unfortunately here the issue is that AudioBufferSource is not meant for big files. Again from the MDN doc of AudioBuffer (https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer):
Objects of these types are designed to hold small audio snippets, typically less than 45 s.
I suspect that for big something doesn't work very well with the "sound begins play at once" assumption (I also experienced it, although 20 seconds seems way too much...). Unfortunately there is no way to get the exact start time of AudioBufferSource in WebAudio yet.
If you don't have any real reason to load this big file with AudioBufferSource, I suggest you use a MediaElementSourceNode (https://developer.mozilla.org/en-US/docs/Web/API/MediaElementAudioSourceNode): as you can see from the example on the linked doc, it allows you to plug a simple HTML5 Audio element into the AudioContext. You then can have all usual control over the element itself, i.e. you also have access to the audioElement.currentTime property, which tells you the current playout time (in this case of the file itself, which is what you need). Additionally, you don't have to handle loading of the file in memory and could start playing as soon as some data is available.
context.currentTime starts counting the second you create the context object. That means if it takes 20 seconds for your audio to load, context.currentTime == 20.
To account for this delay, you can set a simple timer from the time that you create the context to the time that audio loading completes.
var context; //Create your context here
var audioLoadStart = new Date();
//Do audio load
var audioLoadOffset = (new Date() - audioLoadStart) / 1000;
currentTime = context.currentTime - audioLoadOffset - startTime;

Flash Player stops animating when out of viewport

I have several flash movies on a site. They all loop through an animation which lasts for approximately 15 seconds (but this is not set in stone). They all begin to play at the same time and are in sync.
However, when I resize the browser window, or scroll the page, if a flash player instance is not in the viewport, it stops playing. When it returns to the viewport, it resumes and subsequently is out of sync with the other flash player instances.
I am under the impression this is a flash player optimisation. Is there anyway of disabling this behaviour, possibly through JS / AS3? It appears to happen in Firefox and Chrome on Windows.
UPDATE
Just to clarify. I have methodologies in place using local connection and ExternalInterface to sync the ads. What I am looking for is a method to disable the "optimisation" of FlashPlayer which results in the frame rate being drastically reduced.
You can't disable this feature. It's put in place to lower the memory and CPU use when the flash application isn't visible.
What is available for you though is something called the Throttle API. This is a dedicated API created to allow creators of Flash applications the ability to be notified exactly when their application is going to be slowed down/throttled.
Here's an example.
addEventListener(ThrottleEvent.THROTTLE, onThrottleEventHandler);
function onThrottleEventHandler(e:ThrottleEvent):void
{
if(e.state == ThrottleType.THROTTLE)
{
/**
* the player is about to be slowed down to a frame rate
* set in e.targetFrameRate
*/
}
else if(e.state == ThrottleType.PAUSE)
{
/**
* the player is going to be paused completely, AKA 0 fps
*/
}
else if(e.state == ThrottleType.RESUME)
{
/**
* the player is now back to normal as it has resumed
* from the paused or throttled state
*/
}
}
Now you can figure out a way that works best for you but my suggestion is to store the current time that has passed whenever being throttled or paused via:
currentTime = getTimer();
Then calculate how much time has passed once your application has resumed using:
passedTime = getTimer() - currentTime;
Then do what you like with this information.
Hopefully this has helped, should offer you a greater degree of control now that you're familiar with the Throttle API. For more information on it, check it out in the documentation here: ThrottleEvent
I belive this kind of behavior its normal, its kind of a bug of flash that has never been fixed.
I belive inha may got u a solution, but not on an enter_frame event. that just to brutal.
what I would do is:
create a timer event.. each X seconds.. so it will call a checkFunction,
in my checkFunction(). I would check if all my movieClips are syncronized.
and if I found 1 that is not.. ill put a simple gotoAndPlay(xFrame);
var aMovieClips:Array; //get all ur movieclips into this array.
var timeToCheck = 1000; //time to check for a unsincronized movieclip
var time:Timer=new Timer(timeToCheck,0);//do inifinite call each 1000 seconds
time.start();
time.addEventListener(TimerEvent.TIMER, checkFunction);
public function checkFunction(e:TimerEvent):void
{
var aCurrentFrames:Array;
for (var n:int = 0;n<aMovieClips.length();n++)
aCurrentFrames.push(aMovieClips[n].currentFrame);
//so now u have all current frames of all movie clips.. so u can add a
//gotoAndPlay(x); to any unsyncronized movieclip
}
If it is very important that each SWF progresses simultaneously, I would control it by JavaScript.
Assuming each SWF file is a simple MovieClip, something like this is how I would go about it:
Set the Document Class of each FLA file to this:
package {
import flash.display.MovieClip;
import flash.external.ExternalInterface;
public class ExternalInterfaceControlledMC extends MovieClip {
public function ExternalInterfaceControlledMC() {
this.stop();
if (ExternalInterface.available) {
try {
ExternalInterface.addCallback("setFrame", jsSetFrame);
} catch (error:Error) {
trace("An Error occurred:", error.message);
}
} else {
trace("ExternalInterface is not available");
}
}
private function jsSetFrame(value:String):void {
var frameNumber = int(value) % this.totalFrames;
this.gotoAndStop(frameNumber);
}
}
}
In the JavaScript, you would add a reference of each instance of the SWFs into an array, then use a timer to tell each SWF to progress to a frame number.
var swfElements; //Array
var currFrame = 1;
function onPageLoad() {
//Init timer
setInterval("progressFrame", 1000 / 30); //30 fps
}
function progressFrame() {
for (var i = 0; i < swfElements.length; i++) {
swfElements[i].setFrame(currFrame);
}
currFrame++;
}
Please beware that nothing about this code is tested and is only meant to be used to illustrate my train of thought.

Categories

Resources