How to use the various rampToValueAtTime methods - javascript

When I use the following code to fade in a file it doesn't work as I expect. I expect a gradual fade in from 0 to 1 over the course of 5 seconds, instead I get an abrupt change five seconds into playing the file where the gain instantly goes from 0 to 1. What am I not understanding ?
soundObj.play = function() {
playSound.buffer = soundObj.soundToPlay;
playSound.connect(gainNode);
gainNode.gain.value = 0;
gainNode.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 5);
gainNode.connect(audioContext.destination);
playSound.start(audioContext.currentTime);
}
Update/Edit
I changed the above code to the following and it seems to work, now I am researching why. I've added a few comments. Mainly inquiring as to if adding a setValueAtTime method is necessary and if a non zero value is necessary for the gain.value properties default value.
soundObj.play = function() {
playSound.buffer = soundObj.soundToPlay;
playSound.connect(gainNode);
gainNode.gain.value = 0.001; // If set to 0 it doesn't fade in
gainNode.gain.setValueAtTime(gainNode.gain.value, audioContext.currentTime); // Is this needed to have the other RampToValue methods work ?
gainNode.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 7);
gainNode.connect(audioContext.destination);
playSound.start(audioContext.currentTime);
}

A non-zero positive value is necessary for exponentialRampToValueAtTime. This isn't a Web Audio thing as much as it's just a math thing.
There's really no way to exponentially grow a value of 0.
Here's a rough version of the algorithm Chrome uses (rewritten in JS):
// start value
var value1 = 0.1;
// target value
var value2 = 1;
// start time (in seconds)
var time1 = 0;
// end time (in seconds)
var time2 = 2;
// duration
var deltaTime = time2 - time1;
// AudioContext sample rate
var sampleRate = 44100;
// total number of samples
var numSampleFrames = deltaTime * sampleRate;
// time incrementer
var sampleFrameTimeIncr = 1 / sampleRate;
// current time (in seconds)
var currentTime = 0;
// per-sample multiplier
var multiplier = Math.pow( value2 / value1, 1 / numSampleFrames );
// output gain values
var values = new Array( numSampleFrames );
// set up first value
var value = value1 * Math.pow( value2 / value1, ( ( currentTime - time1 ) * sampleRate ) / numSampleFrames );
for ( var i = 0; i < numSampleFrames; ++i ) {
values[ i ] = value;
value *= multiplier;
currentTime += sampleFrameTimeIncr;
}
If you change value1 to zero, you'll see that the output array is basically full of NaN. But Chrome also adds a bit of extra code to save you from that by special-casing instances where your value is <= 0 so that you don't actually end up with gain values of NaN.
If none of that makes sense, let me put it this way. In order to exponentially grow a value, you basically need a loop that looks like this:
for ( var i = 0; i < length; ++i ) {
values[ i ] = value;
value *= multiplier;
}
But if your initial value is 0, well, 0 multiplied by any other number is always 0.
Oh, and if you're interested (and can read C++), here's a link to the code that Chrome uses: https://chromium.googlesource.com/chromium/blink/+/master/Source/modules/webaudio/AudioParamTimeline.cpp
Relevant stuff is on line 316.
Edit
Apologies for a Chrome-centric explanation. But the underlying math concept of not being able to exponentially grow a value of zero will hold with any implementation.

Related

Circular Queue in Javascript

I was going through a book on Data Structures and Algorithm with JavaScript when I found this piece of codes.
I need someone to help me explain the logic behind the code here, also the logic behind the value of var i in each method.
var i = (this._front + length) & (this._size - 1); //explain this in push()
var i = (this._front + length - 1) & (this._size - 1); // explain this in pop()
var i = (((( this._front - 1 ) & ( size - 1) ) ^ size ) - size );// explain this in unshift()
Please explain the general logic for each method, I have an issue with the use of & operator in the above statements, please why the use of & instead of %
var CircularDequeue = (()=> {
class CircularDequeue {
constructor() {
// pseudo realistic 2^x value
this._size = 1024;
this._length = 0;
this._front = 0;
this._data = [];
}
push (item) {
// get the length of the array
var length = this._length;
// calculate the end
var i = (this._front + length) & (this._size - 1);
// assign value to the current end of the data
this._data[i] = item;
// increment length for quick look up
this._length = length + 1;
// return new length
return this._length;
}
pop () {
// get the length of the array
var length = this._length;
// calculate the end
var i = (this._front + length - 1) & (this._size - 1);
// copy the value to return
var ret = this._data[i];
// remove the value from data
this._data[i] = undefined;
// reduce length for look up
this._length = length - 1;
// return value
return ret;
}
shift () {
// get the current front of queue
var front = this._front;
// capture return value
var ret = this._data[front];
// reset value in the data
this._data[front] = undefined;
// calculate the new front of the queue
this._front = (front + 1) & (this._size - 1);
// reduce the size
this._length = this._length - 1;
// return the value
return ret;
}
unshift (item) {
// get the size
var size = this._size;
// calculate the new front
var i = (((( this._front - 1 ) & ( size - 1) ) ^ size ) -
size );
// add the item
this._data[i] = item;
// increment the length
this._length = this._length + 1;
// update the new front
this._front = i;
// return the acknowledgement of the addition of the new
item
return this._length;
}
}
return CircularDequeue;
})();
module.exports = CircularDequeue;
I have tried to understand this logic but the use of bitwise & in calculating the values of var i instead of modulo operator(%) keeps confusing me
In this code something & (size - 1) is equivalent to something % size because size is a power of 2, and seeing the comment in the constructor, it is supposed to be a power of 2.
I don't see a good reason why the following has been done:
(((( this._front - 1 ) & ( size - 1) ) ^ size ) - size )
The first part ( this._front - 1 ) & ( size - 1) is always going to be a non-negative number that is less than size.
^ size will set a bit that is 0 (because the intermediate value is less than size) and then - size will clear that same bit again. So that ^ size ) - size part is a non-operation. It can be left out.
It is unclear why the author of this code preferred to work with the & operator than the %, as the latter one would also work if the size would not have been a power of two, while the & operator will only work as intended when size is a power of 2.
To see how & works, take for example that the left side is 1025, which means it is out of range. In binary 1025 is 10000000001. On the other hand we have size which is 1024. size - 1 in binary is 1111111111.
So we have this & operation:
10000000001
1111111111
----------- &
0000000001
So this operation effectively removes any excess bits from the left side operand, whether they come from a negative value or from a value that is not less than size.

Is there a minimum possible size change in CSS?

I wrote some JavaScript code to animate CSS properties of elements. I pass the following arguments to the function: amount, interval, and duration; amount being the change in the property (for example 200 could mean add 200 pixels to the element's width), interval being the time between two consecutive changes, and duration being the total duration of the animation.
The code works fine unless I pass the arguments in a way that the change in each interval becomes very small (like a tiny fraction of a pixel).
I know the code is working fine theoretically, as I get the change in console.
Any ideas about the problem?
Cheers.
UPDATE: the code:
function handleTimer (amount, interval, duration, execute, element) {
let i = 0;
let current = 0;
let stepsCount = countSteps(interval, duration);
let stepLength = calcStepLength(stepsCount, amount);
let count = setTimeout(function addOneMore () {
if ( i < stepsCount -1 ){
i++;
current += stepLength;
execute(stepLength, element);
if (current < amount) {
count = setTimeout(addOneMore, interval)
}
} else {
current = amount;
execute(amount - (stepsCount -1) * stepLength, element);
}
}, interval)
}
function countSteps (interval, duration) {
let remainder = duration % interval;
let stepsCount;
if (remainder) {
stepsCount = Math.floor(duration / interval) + 1;
} else {
stepsCount = duration / interval;
}
return stepsCount;
}
function calcStepLength(stepsCount, amount) {
return amount / stepsCount;
}
function resizeWidth (amount, element) {
let widthSTR = $(element).css('width');
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
$(element).css('width', `${width + amount}px`);
}
So this:
handleTimer(218, 5, 200, resizeWidth, '.box');
works fine, but this:
handleTimer(218, 5, 2000, resizeWidth, '.box');
doesn't.
UPDATE 2:
I know browsers are super accurate with pixels, like when you use percentages. Of course the value will be rounded before rendering since displays cant display half pixels, but the value is still calculated accurately.
I don't know at what decimal the rounding occurs.
This happens because parseInt is rounding your number up.
Pay attention to this line:
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
if width is a decimal number, like 22.5px, it will be rounded up to 22.
If amount is less than 1, it won't reach 23 and when you round up the number again, you'll get 22 again and it becomes a loop.
You have two solutions:
Use another variable to save the width value, avoiding to writing and reading it from CSS:
let initialWidth = $(element).css('width');
let savedWidth = widthSTR.substr(0, initialWidth, initialWidth.length - 2 ) );
function resizeWidth (amount, element) {
savedWidth += amount;
$(element).css('width', `${savedWidth}px`);
}
Just use parseFloat in place of parseInt to don't round your number up:
let width = parseFloat( widthSTR.substr( 0 , widthSTR.length - 2 ) );

countdown from n to 0 in given time, negative end value

I am working on simple script that should animate given value (for example 6345.23) to 0 by counting it down, it should also end up at 0 if specified amount of time have passed (for example 2 seconds.
I started by simple logic:
given config: initial value, time in sec, interval
time is given in seconds so convert it to milliseconds
calculate amount of ticks by dividing time in ms by interval
calculate amount of decreased value per tick by dividing initial value by amount of ticks
once above are known we can simply do: (simple model, not actual code)
intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
clearInterval(intId);
} else {
value -= amount_per_tick;
// update view
}
}, interval);
actual code:
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
Link do fiddle (in console you can observe how it works)
If you set time to 2 (2 seconds) its ok, but if you set time to for example 2.55 (2.55 seconds) it doesnt stop at all at 0, its passing by and going indefinitely in negative values.
How i can fix it so no matter what is set in seconds its always go precisly one by one until reaches perfectly 0?
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
You're relying on ticks_made === amount_of_ticks being an exact match. Chances are, due to rounding, you won't get an exact match, so you'd be better off doing:
if(ticks_made >= amount_of_ticks) {
kshetline's answer correctly addresses why you get into negative values. When dealing with fractional IEEE-754 double-precision binary numbers (in the normal range, or even whole numbers in very high ranges), == and === can be problematic (for instance, 0.1 + 0.2 == 0.3 is false). Dealing with values as small as the fractional values here are, accumulated imprecision is also a factor. It's inevitable to have to fudge the final step.
But there's a larger issue: You can't rely on timers firing on a precise schedule. Many, many things can prevent their doing so — other UI rendering work, other scripts, CPU load, the tab being inactive, etc.
Instead, the fundamental technique for animation on browsers is:
Update when you can
Update based on where you should be in the animation based on time, not based on how many times you've animated
Use requestAnimationFrame so your update synchronizes with the browser's refresh
Here's your code updated to do that, see comments:
// Tell in-snippet console to keep all lines (rather than limiting to 50)
console.config({maxEntries: Infinity});
var value = 212.45,
time = 2.55, // in seconds
time_in_ms = time * 1000,
amount_per_ms = value / time_in_ms,
interval = 100 / 6, // in milliseconds, ~16.66ms is a better fit for browser's natural refresh than 20ms
ticks_made = 0;
// A precise way to get relative milliseconds timings
var now = typeof performance !== "undefined" && performance.now
? performance.now.bind(performance)
: Date.now.bind(Date);
// Remember when we started
var started = now();
// Because of the delay between the interval timer and requestAnimationFrame,
// we need to flag when we're done
var done = false;
// Use the interval to request rendering on the next frame
var intId = setInterval(function() {
requestAnimationFrame(render);
}, interval);
// About half-way in, an artificial 200ms delay outside your control interrupts things
setTimeout(function() {
console.log("************DELAY************");
var stop = now() + 200;
while (now() < stop) {
// Busy-loop, preventing anything else from happening
}
}, time_in_ms / 2);
// Our "render" function (okay, so we just call console.log in this example, but
// in your real code you'd be doing a DOM update)
function render() {
if (done) {
return;
}
++ticks_made;
var elapsed = now() - started;
if (elapsed >= time_in_ms) {
console.log(ticks_made, "done");
done = true;
clearInterval(intId);
} else {
var current_value = value - (amount_per_ms * elapsed);
console.log(ticks_made, current_value);
}
}
/* Maximize in-snippet console */
.as-console-wrapper {
max-height: 100% !important;
}
If you run that, then scroll up to the "************DELAY************" line, you'll see that even though rendering was held up by "another process", we continue with the appropriate next value to render.
It would make sense to convert the result of .toFixed() to a number right away:
let amount_per_tick = +(value / amount_of_ticks).toFixed(5);
let value = +(value - amount_per_tick).toFixed(5);
(note the + signs)
Then you will never have to worry about type coercion or anything, and instead just focus on math.

How to grow number smoothly from 0 to 5,000,000

This is probably basic math that I don't seem to remember.
I'm trying to get from 0 to 5,000,000 in 10 seconds while having all the numbers ticking. I don't have to have the number reach exactly 5,000,000 because I can just do a conditional for it to stop when it's over.
Right now I have this:
count+= 123456
if (count > 5000000) {
count = 5000000;
}
It gives the sense of number moving you know? But It really starts off too high. I wanted to gradually climb up.
You could do something like this:
function timedCounter(finalValue, seconds, callback){
var startTime = (new Date).getTime();
var milliseconds = seconds*1000;
(function update(){
var currentTime = (new Date).getTime();
var value = finalValue*(currentTime - startTime)/milliseconds;
if(value >= finalValue)
value = finalValue;
else
setTimeout(update, 0);
callback && callback(value);
})();
}
timedCounter(5000000, 10, function(value){
// Do something with value
});
Demo
Note that with a number as big as 5000000 you won't see the last couple digits change. You would only see that with a small number like 5000. You could fix that; perhaps by adding in some randomness:
value += Math.floor(Math.random()*(finalValue/10000 + 1));
Demo with randomness
You can tween:
import fl.transitions.Tween;
import fl.transitions.easing.Regular;
var count = 0;
var tween:Tween = new Tween(this, "count", Regular.easeInOut,0,5000000,10, true);
This will tween you variable count from 0 to 5000000 in 10 seconds. Read about these classes if you want to expand on this code.
Tween
TweenEvent
Good luck!

I have a field calculating and need to display a minimum if the result is below 60

Sorry this is obviously my first time here, I am just learning how to work in javascript. My question is this: I have some basic calculations determing a price of a service for our non-profit. t is the number of rooms * 0.81. But we have a monthly minimum of $60. So I need to know how I would factor that into the pricing function. I know it goes that "if x < 60, then 60", just not sure how the language would be written. I will include the full js.
var listenerFieldIDs = {"roomCountID":"item4_text_1"}; //Currently the only form we are using for room count has this value set as its ID attribute.
var impactFields = ["item12_text_1","item1_text_1","item16_text_1","item18_text_1","item20_text_1"]; //Field IDs for the form that will be changed constantly.
var estimatedBottleSize = 1.5, occupancyRate = (60 / 100), collectionDuration = 365, soapOuncesRecoverable = 0.63, bottleOuncesRecoverable = 0.47,lbConversion = 0.0626, rate = 0.81;
var $ = function(id){ //Shortcut to save some typing. Instead of having to write out document.getElementById(elementID) every time I need to access an element, I can put $(elementID).property or $(elementID).method() I need more easily.
return document.getElementById(id);
}
var updateFormField = function(id,amount){ //Updates a form field when gives the element ID and the amount.
$(id).value = amount;
}
var updateForm = function(roomCount){
// This is called when all form data needs to be updated. This is generally invoked each time a keystroke in the room count field.
updateFormField(impactFields[0],calculateLbsOfSoap(roomCount).toFixed(2)); //Updating the first form field after calculating the total weight of soap in lbs.
updateFormField(impactFields[1],calculateLbsOfBottles(roomCount).toFixed(2)); //Same thing as above, but bottles/amenities.
updateFormField(impactFields[2],calculateBarsOfSoap(roomCount).toFixed(0)); //Updating the third form field after calculating the total number of distributed units.
updateFormField(impactFields[3],calculateBottles(roomCount).toFixed(0)); //Same as above, but bottles/amenities.
updateFormField(impactFields[4],("$" + calculatePrice(roomCount).toFixed(2))); //Updating price.
}
var listenForNumbers = function(event){ //This function is acting as a handler for when anything is entered into the field.
updateForm($(listenerFieldIDs["roomCountID"]).value);
}
var calculateLbsOfSoap = function (rmCnt){ // Calculate the weight of soap and return the amount.
return checkCount(rmCnt) ? 0 : ((soapOuncesRecoverable * lbConversion) * (rmCnt * occupancyRate) * collectionDuration);
}
var calculateLbsOfBottles = function (rmCnt){ // Calculate the weight of bottled amenities and return the amount.
return checkCount(rmCnt) ? 0 : ((bottleOuncesRecoverable * lbConversion) * (rmCnt * occupancyRate) * collectionDuration);
}
var calculateBarsOfSoap = function(rmCnt){ // Calculate how many bars are distributed if the room count is not 0.
return checkCount(rmCnt) ? 0 : ((calculateLbsOfSoap(rmCnt) * 16) / 3);
}
var calculateBottles = function(rmCnt){ // Calculate how many bottles are distributed if the room count is not 0.
return checkCount(rmCnt) ? 0 : (((calculateLbsOfBottles(rmCnt) * 16) / estimatedBottleSize) * (2 / 3));
}
var calculatePrice = function(rmCnt){
return checkCount(rmCnt) ? 0 : (rmCnt * rate);
}
var checkCount = function(count){ //If the count is 0 or less than 0, the number is useless so just return 0 to prevent odd results.
return (count < 0 || count == 0) ? true : false;
}
var initializeRealTimeCalcToForm = function(){
if(window.attachEvent){
$(listenerFieldIDs["roomCountID"]).attachEvent("onkeydown",listenForNumbers,false);
$(listenerFieldIDs["roomCountID"]).attachEvent("onkeyup",listenForNumbers,false);
$(listenerFieldIDs["roomCountID"]).attachEvent("onkeypress",listenForNumbers,false);
$(listenerFieldIDs["roomCountID"]).attachEvent("onchange",listenForNumbers,false);
} else{
//But if NOT IE... :-D
$(listenerFieldIDs["roomCountID"]).addEventListener("keydown",listenForNumbers,false);
$(listenerFieldIDs["roomCountID"]).addEventListener("keyup",listenForNumbers,false);
$(listenerFieldIDs["roomCountID"]).addEventListener("keypress",listenForNumbers,false);
$(listenerFieldIDs["roomCountID"]).addEventListener("change",listenForNumbers,false);
}
}
window.onload = function(){
initializeRealTimeCalcToForm();
}
If you only want to set a minimum value of 60 to the variable myvar, you can do
myvar=Math.max(60,myvar);
Edit:
Then, if you want the value returned by calculatePrice to be at least 60, use:
var calculatePrice=function(rmCnt){
return Math.max(60,checkCount(rmCnt) ? 0 : (rmCnt * rate));
}
Note 1:
Do you know that you can declare functions like this?
function calculatePrice(rmCnt){
return Math.max(60,checkCount(rmCnt) ? 0 : (rmCnt * rate));
}
It's shorter and this way you can call the function before declaring it!
Note 2:
If you want that value to be at least 60, I don't understand the following code:
checkCount(rmCnt) ? 0
Why 0?

Categories