I use the following function below to update and call a drawAccel();function that builds out an animated strip chart.
function messagecb(header, message) {
if(header.type == 6) {
// processEchoReply(message);
} else if(header.type == 4) {
// accel
var accels = message.b64UnpackAccelMsg();
for(var index = 0; index < accels.length; ++index) {
var accel = accels[index];
var totalClock = accelEpochAdjust(accel.clock);
addAccelDatum(totalClock, accel.x, accel.y, accel.z);
}
if ( typeof messagecb.counter == 'undefined' ) {
messagecb.counter = 0;
}
++messagecb.counter;
if (messagecb.counter % 10 === 0) {
drawAccel();
}
} else if(header.type == 3) {
// info
var info2 = message.b64UnpackInfo2Msg();
displayCurrentPosition(info2.fixtime, info2.lat, info2.lon, info2.alt);
displayMobileStatus(info2.rssi, info2.bandClass, info2.batt);
} else if(header.type == 11) {
btReceive(header, message);
}
}
I come across some intermittent performance issues in IE8 though. So I would like to
collect the elapsed wall time running inside the update for loop, and not call the drawAccel() renderer unless I'm using less than 50% of the wall time.
Pseudo code example:
if ((lastEnteredTime - lastExitedTime)/(currentTime - lastEnteredTime) < .5){
drawAccel();
} else {
//do nothing
}
My problem is I'm not sure how I can go about getting the last entered time and the last exited time of the loop so that I can run this condition. Any ideas? Thanks!
It's not clear to me exactly what you're trying to do, but something like this should get you close. +new Date() will give you the number of milliseconds since 1/1/1970, so making that same call at various places should be able to get you what you want
var start = +new Date();
for(var index = 0; index < accels.length; ++index) {
var accel = accels[index];
var totalClock = accelEpochAdjust(accel.clock);
var current = +new Date();
var timeElapsedInMs = current - start;
//not sure the exact test you want to run here
addAccelDatum(totalClock, accel.x, accel.y, accel.z);
}
Edit based on your comment. So if you always want to have a lastEntered and lastExited values, something like this might be what you want
var lastEntered, lastExisted = +new Date();
for(var index = 0; index < accels.length; ++index) {
lastEntered = +new Date();
var accel = accels[index];
var totalClock = accelEpochAdjust(accel.clock);
var timeElapsedInMs = current - start;
//not sure the exact test you want to run here
addAccelDatum(totalClock, accel.x, accel.y, accel.z);
lastExisted = +new Date();
}
And from there you can do whatever comparisons you need.
Related
I need to edit a few hundred or even a few thousand calendar events through Google Apps Script. It is about just minor changes to title (event.setTitle()) and description (event.setDescription()), nothing fancy. Trying with about 600 events the maximum execution time of 360 seconds is already exceeded.
var cal = CalendarApp.getCalendarById("Calendar Id");
var startTime = new Date(1850, 0, 1);
var endTime = new Date(2100, 0, 1);
var events = cal.getEvents(startTime, endTime);
for (var i = 0; i < events.length; i++) {
events[i].setTitle(events[i].getTitle() + " something");
events[i].setDescription(events[i].getDescription() + " something else");
}
How to process the events in several chunks successively?
Answer:
Use PropertiesService to save where you got to so you can continue where you left of on next run.
Code:
You can use a PropertiesService key value pair to save the value of the count through events[]:
function renameEvents() {
var cal = CalendarApp.getCalendarById("Calendar Id");
var startTime = new Date(1850, 0, 1);
var endTime = new Date(2100, 0, 1);
var events = cal.getEvents(startTime, endTime);
var sp = PropertiesService.getScriptProperties();
if (!(sp.getProperty("count")) || sp.getProperty("count") == 0) {
var count = 0;
else if ((sp.getProperty("count") > 0) {
var count = sp.getProperty("count");
}
for (var i = count; i < events.length; i++) {
events[i].setTitle(events[i].getTitle() + " something");
events[i].setDescription(events[i].getDescription() + " something else");
sp.setProperty("count", i)
}
}
It'll make the script a little slower, but each time you run it it'll continue along the calendar events from where the last one stopped.
References:
Class PropertiesService | Apps Script | Google Developers
I have previously used something similar to the following:
At the start of each loop check that a sufficient buffer time is available to complete the loop.
Update the buffer time whenever a single loop time exceeds it.
Use the PropertiesService to store both the buffer and the last index.
var cal = CalendarApp.getCalendarById("Calendar Id");
var startTime = new Date(1850, 0, 1);
var endTime = new Date(2100, 0, 1);
var events = cal.getEvents(startTime, endTime);
var ps = PropertiesService.getScriptProperties();
var startIndex = ps.getProperty('lastIndex') || 0;
var buffer = ps.getProperty('buffer') || 100;
var startTime = new Date();
var maxTime = 1000*60*6; //6 mins
for (var i = startIndex; i < events.length; i++) {
var loopStart = new Date()
if (loopStart - startTime > (maxTime-buffer) ) {
ps.setProperty('lastIndex', i);
break;
}
events[i].setTitle(events[i].getTitle() + " something");
events[i].setDescription(events[i].getDescription() + " something else");
var loopTime = new Date() - loopStart;
if (loopTime > buffer ) buffer = loopTime * 1.5
}
ps.setProperty('buffer',buffer)
i have been try to solve the sudoku with Blacktracking algo, everything is good, canvar is called and i able to see the number but the things is number are not moving i.e the logic is not exectuing
current.i === 0; is where i'm get the error! even i have declared a sperate variable for the num also the problem is not sloved. only if i remove the .num current == 0 than its not showing any error but still the number is not moving
enter image description here
var cell = [];
var stack = [];
var sudoku = [2,3,0,9,4,0,6,7,0,
8,0,0,3,2,5,9,1,4,
9,0,0,7,6,0,3,2,0,
1,0,0,0,0,0,7,9,2,
5,0,3,2,1,0,4,8,6,
4,0,0,6,8,0,5,3,1,
7,0,0,1,0,0,0,0,9,
6,5,9,8,7,2,1,4,3,
3,0,0,0,9,0,0,0,7];
var current;
var number = 1;
function setup(){
createCanvas(450,450);
var a=0;
var b=0;
for(var i=0;i<81;i++){
if(a%9==0 && i!=0){
b = b+50;
a = 0;
}
each[i] = new each(a,b,i,sudoku[i]);
a = a+50;
}
current = cell[0];
}
function draw(){
background(10);
for(var i=0;i<81;i++){
each[i].show();
}
if(current.num === 0){ //the error is typeerror can't read the property of num
if(! sameColumn(current.i,number) && ! sameRow(current.i,number) && ! sameSquare(current.i,number) && number<(10)){
current.num = number;
stack.push(current);
number = 0;
current.each[current.i+1];
}
else {
if(number > 8){
current.num = 0;
current = stack.pop();
number = current.num;
current.num = 0;
}
}
}
else{
current = each[current+1];
number = 0;
}
number++;
}
function each(a,b,i,num){
this.a = a;
this.b = b;
this.i = i;
this.num = num;
this.show = function(){
noFill();
stroke(255);
rect(this.a,this.b,50,50);
textSize(32);
text(this.num,a+12,b+40);
}
}
The error is pretty much straight forward. current = cell[0]; becomes undefined since you defined cell as an empty array and didn't manipulated it after that.
From what I have observed so far, many parts of your code logically does not work, for example,
same Column(current.i,number) && ! sameRow(current.i,number) && ! sameSquare(current.i,number)
will definitely throw you an error is it is executed (it is not since the execution does not reach to that line), unless you have a separate js file that contains these functions.
Another one is
current = cell[current+1];
if the current variable is to store the cell object, it does not make sense to add 1 to it, and vice versa.
Now I believe this is how setup function was meant to look like:
function setup(){
createCanvas(450,450);
var a=0;
var b=0;
for(var i=0;i<81;i++){
if(a%9==0 && i!=0){
b = b+50;
a = 0;
}
cell[i] = new Cell(a,b,i,sudoku[i]); //changed each[i] to cell[i], also renamed the 'each' class
a = a+50;
}
current = cell[0];
}
If possible, please edit in a little more information about what exactly does your code do. Cheers :)
I'm creating a project that is a player attendance log. Every player is an object I created using class syntax and in theory the values of the object should change based on the time of day. This doesn't work because the values are either true or false regardless of what time of day it is. What is going wrong and how can I fix it? here's a link to my project https://github.com/bloodwolf616/project9
Problem code:
const today = new Date();
const hour = today.getHours();
const second = today.getSeconds();
for (let i = 0; i < attendance.length; i++){
if (hour > 10 && hour < 20) {
if(player1 && player2 && player3 && player4) {attendance[i]._online = true;}
} else {
attendance[i]._online = false;
}
if (second > 20 || second < 40) {
if(error) {
attendance[i]._online = true;}
} else {
attendance[i]._online = false;
}
I'm interested in using the JavaScript WebAudioAPI to detect song beats, and then render them in a canvas.
I can handle the canvas part, but I'm not a big audio guy and really don't understand how to make a beat detector in JavaScript.
I've tried following this article but cannot, for the life of me, connect the dots between each function to make a functional program.
I know I should show you some code but honestly I don't have any, all my attempts have failed miserably and the relevant code it's in the previously mentioned article.
Anyways I'd really appreciate some guidance, or even better a demo of how to actually detect song beats with the WebAudioAPI.
Thanks!
The main thing to understand about the referenced article by Joe Sullivan is that even though it gives a lot of source code, it's far from final and complete code. To reach a working solution you will still need both some coding and debugging skills.
This answer draws most of its code from the referenced article, original licensing applies where appropriate.
Below is a naïve sample implementation for using the functions described by the above article, you still need to figure out correct thresholds for a functional solution.
The code consists of preparation code written for the answer:
reading a local file over the FileReader API
decoding the file as audio data using the AudioContext API
and then, as described in the article:
filtering the audio, in this example with a low-pass filter
calculating peaks using a threshold
grouping interval counts and then tempo counts
For the threshold I used an arbitrary value of .98 of the range between maximum and minimum values; when grouping I added some additional checks and arbitrary rounding to avoid possible infinite loops and make it an easy-to-debug sample.
Note that commenting is scarce to keep the sample implementation brief because:
the logic behind processing is explained in the referenced article
the syntax can be referenced in the API docs of the related methods
audio_file.onchange = function() {
var file = this.files[0];
var reader = new FileReader();
var context = new(window.AudioContext || window.webkitAudioContext)();
reader.onload = function() {
context.decodeAudioData(reader.result, function(buffer) {
prepare(buffer);
});
};
reader.readAsArrayBuffer(file);
};
function prepare(buffer) {
var offlineContext = new OfflineAudioContext(1, buffer.length, buffer.sampleRate);
var source = offlineContext.createBufferSource();
source.buffer = buffer;
var filter = offlineContext.createBiquadFilter();
filter.type = "lowpass";
source.connect(filter);
filter.connect(offlineContext.destination);
source.start(0);
offlineContext.startRendering();
offlineContext.oncomplete = function(e) {
process(e);
};
}
function process(e) {
var filteredBuffer = e.renderedBuffer;
//If you want to analyze both channels, use the other channel later
var data = filteredBuffer.getChannelData(0);
var max = arrayMax(data);
var min = arrayMin(data);
var threshold = min + (max - min) * 0.98;
var peaks = getPeaksAtThreshold(data, threshold);
var intervalCounts = countIntervalsBetweenNearbyPeaks(peaks);
var tempoCounts = groupNeighborsByTempo(intervalCounts);
tempoCounts.sort(function(a, b) {
return b.count - a.count;
});
if (tempoCounts.length) {
output.innerHTML = tempoCounts[0].tempo;
}
}
// http://tech.beatport.com/2014/web-audio/beat-detection-using-web-audio/
function getPeaksAtThreshold(data, threshold) {
var peaksArray = [];
var length = data.length;
for (var i = 0; i < length;) {
if (data[i] > threshold) {
peaksArray.push(i);
// Skip forward ~ 1/4s to get past this peak.
i += 10000;
}
i++;
}
return peaksArray;
}
function countIntervalsBetweenNearbyPeaks(peaks) {
var intervalCounts = [];
peaks.forEach(function(peak, index) {
for (var i = 0; i < 10; i++) {
var interval = peaks[index + i] - peak;
var foundInterval = intervalCounts.some(function(intervalCount) {
if (intervalCount.interval === interval) return intervalCount.count++;
});
//Additional checks to avoid infinite loops in later processing
if (!isNaN(interval) && interval !== 0 && !foundInterval) {
intervalCounts.push({
interval: interval,
count: 1
});
}
}
});
return intervalCounts;
}
function groupNeighborsByTempo(intervalCounts) {
var tempoCounts = [];
intervalCounts.forEach(function(intervalCount) {
//Convert an interval to tempo
var theoreticalTempo = 60 / (intervalCount.interval / 44100);
theoreticalTempo = Math.round(theoreticalTempo);
if (theoreticalTempo === 0) {
return;
}
// Adjust the tempo to fit within the 90-180 BPM range
while (theoreticalTempo < 90) theoreticalTempo *= 2;
while (theoreticalTempo > 180) theoreticalTempo /= 2;
var foundTempo = tempoCounts.some(function(tempoCount) {
if (tempoCount.tempo === theoreticalTempo) return tempoCount.count += intervalCount.count;
});
if (!foundTempo) {
tempoCounts.push({
tempo: theoreticalTempo,
count: intervalCount.count
});
}
});
return tempoCounts;
}
// http://stackoverflow.com/questions/1669190/javascript-min-max-array-values
function arrayMin(arr) {
var len = arr.length,
min = Infinity;
while (len--) {
if (arr[len] < min) {
min = arr[len];
}
}
return min;
}
function arrayMax(arr) {
var len = arr.length,
max = -Infinity;
while (len--) {
if (arr[len] > max) {
max = arr[len];
}
}
return max;
}
<input id="audio_file" type="file" accept="audio/*"></input>
<audio id="audio_player"></audio>
<p>
Most likely tempo: <span id="output"></span>
</p>
I wrote a tutorial here which shows how to do this with the javascript Web Audio API.
https://askmacgyver.com/blog/tutorial/how-to-implement-tempo-detection-in-your-application
Outline of Steps
Transform Audio File into an Array Buffer
Run Array Buffer Through Low Pass Filter
Trim a 10 second Clip from the Array Buffer
Down Sample the Data
Normalize the Data
Count Volume Groupings
Infer Tempo from Groupings Count
This code below does the heavy lifting.
Load Audio File Into Array Buffer and Run Through Low Pass Filter
function createBuffers(url) {
// Fetch Audio Track via AJAX with URL
request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function(ajaxResponseBuffer) {
// Create and Save Original Buffer Audio Context in 'originalBuffer'
var audioCtx = new AudioContext();
var songLength = ajaxResponseBuffer.total;
// Arguments: Channels, Length, Sample Rate
var offlineCtx = new OfflineAudioContext(1, songLength, 44100);
source = offlineCtx.createBufferSource();
var audioData = request.response;
audioCtx.decodeAudioData(audioData, function(buffer) {
window.originalBuffer = buffer.getChannelData(0);
var source = offlineCtx.createBufferSource();
source.buffer = buffer;
// Create a Low Pass Filter to Isolate Low End Beat
var filter = offlineCtx.createBiquadFilter();
filter.type = "lowpass";
filter.frequency.value = 140;
source.connect(filter);
filter.connect(offlineCtx.destination);
// Render this low pass filter data to new Audio Context and Save in 'lowPassBuffer'
offlineCtx.startRendering().then(function(lowPassAudioBuffer) {
var audioCtx = new(window.AudioContext || window.webkitAudioContext)();
var song = audioCtx.createBufferSource();
song.buffer = lowPassAudioBuffer;
song.connect(audioCtx.destination);
// Save lowPassBuffer in Global Array
window.lowPassBuffer = song.buffer.getChannelData(0);
console.log("Low Pass Buffer Rendered!");
});
},
function(e) {});
}
request.send();
}
createBuffers('https://askmacgyver.com/test/Maroon5-Moves-Like-Jagger-128bpm.mp3');
You Now Have an Array Buffer of the Low Pass Filtered Song (And Original)
It's comprised of a number of entries, sampleRate (44100 multiplied by the number of seconds of the song).
window.lowPassBuffer // Low Pass Array Buffer
window.originalBuffer // Original Non Filtered Array Buffer
Trim a 10 Second Clip from the Song
function getClip(length, startTime, data) {
var clip_length = length * 44100;
var section = startTime * 44100;
var newArr = [];
for (var i = 0; i < clip_length; i++) {
newArr.push(data[section + i]);
}
return newArr;
}
// Overwrite our array buffer to a 10 second clip starting from 00:10s
window.lowPassFilter = getClip(10, 10, lowPassFilter);
Down Sample Your Clip
function getSampleClip(data, samples) {
var newArray = [];
var modulus_coefficient = Math.round(data.length / samples);
for (var i = 0; i < data.length; i++) {
if (i % modulus_coefficient == 0) {
newArray.push(data[i]);
}
}
return newArray;
}
// Overwrite our array to down-sampled array.
lowPassBuffer = getSampleClip(lowPassFilter, 300);
Normalize Your Data
function normalizeArray(data) {
var newArray = [];
for (var i = 0; i < data.length; i++) {
newArray.push(Math.abs(Math.round((data[i + 1] - data[i]) * 1000)));
}
return newArray;
}
// Overwrite our array to the normalized array
lowPassBuffer = normalizeArray(lowPassBuffer);
Count the Flat Line Groupings
function countFlatLineGroupings(data) {
var groupings = 0;
var newArray = normalizeArray(data);
function getMax(a) {
var m = -Infinity,
i = 0,
n = a.length;
for (; i != n; ++i) {
if (a[i] > m) {
m = a[i];
}
}
return m;
}
function getMin(a) {
var m = Infinity,
i = 0,
n = a.length;
for (; i != n; ++i) {
if (a[i] < m) {
m = a[i];
}
}
return m;
}
var max = getMax(newArray);
var min = getMin(newArray);
var count = 0;
var threshold = Math.round((max - min) * 0.2);
for (var i = 0; i < newArray.length; i++) {
if (newArray[i] > threshold && newArray[i + 1] < threshold && newArray[i + 2] < threshold && newArray[i + 3] < threshold && newArray[i + 6] < threshold) {
count++;
}
}
return count;
}
// Count the Groupings
countFlatLineGroupings(lowPassBuffer);
Scale 10 Second Grouping Count to 60 Seconds to Derive Beats Per Minute
var final_tempo = countFlatLineGroupings(lowPassBuffer);
// final_tempo will be 21
final_tempo = final_tempo * 6;
console.log("Tempo: " + final_tempo);
// final_tempo will be 126
Dygraphs options provide 'rollPeriod' to support rolling averages and 'stepPlot' to support step plots. When set together when some data is missing in between, they give very unexpected results. For example, attached image link shows graph for original data (rollPeriod=1) and rollPeriod=5. (http://imgur.com/a/9ajh8)
At 40,000 for example, the rolling average must be zero. But, dygraphs takes average of last 5 datapoints instead of last 5 seconds.
Is it possible to get rolling average that maintains notion of time rather than data points. Thanks in advance !
PS- Sorry for image link. SO won't allow me to directly post images due to lack of reputation. :(
As you noticed, dygraphs averages the last five data points, not the last five seconds. This is all it can do, since it doesn't know the cadence of your data. Fortunately, you can fix this by adding explicit missing values:
Datetime,Value
2014-08-01 12:34:55,0
2014-08-01 12:34:56,
2014-08-01 12:34:57,0
2014-08-01 12:34:58,
2014-08-01 12:34:59,0
The zeros are values, the blanks are missing values.
See http://dygraphs.com/data.html for more information, or one of these two demos for examples.
Due to lack of this functionality, I implemented it by myself. I am putting the code here for someone in similar situation. Code uses internal function extractSeries_ in dygraph library and Queue.js. Use with extreme caution !
function calcAvg_(minDate, maxDate, dispData){
var windowSize = Math.round((maxDate-minDate)/100);
if(windowSize <= 1){
return dispData;
}
var energy = 0;
var lastS = new Queue();
var series = dispData;
var lastAvg = 0;
// Initially lastS elements are all 0
// lastS shall always be maintained of windowSize.
// Every enqueue after initialization in lastS shall be matched by a dequeue
for(j=0; j<windowSize; j++){
lastS.enqueue(0);
}
var avg_series = [];
var prevTime = minDate - windowSize;
var prevVal = 0;
avg_series.push([prevTime, prevVal]);
//console.log( "calcAvg_ min: " + minDate + " max: " + maxDate + " win: " + windowSize );
for(j=0; j<series.length; j++){
var time = series[j][0];
var value = series[j].slice(1);
if(time > minDate){
var k = 0;
while(k < windowSize && prevTime + k < time){
var tail = lastS.dequeue();
lastS.enqueue(prevVal);
lastAvg = lastAvg + (prevVal - tail)/windowSize;
avg_series.push([prevTime+k, lastAvg]);
k++;
}
}
prevTime = time;
prevVal = value;
if(time > maxDate){
break;
}
}
if(j == series.length){
//console.log("Fix last value");
var k = 0;
while(k < windowSize && prevTime + k < maxDate){
var tail = lastS.dequeue();
lastS.enqueue(prevVal);
lastAvg = lastAvg + (prevVal - tail)/windowSize;
avg_series.push([prevTime+k, lastAvg]);
k++;
}
}
//console.log(avg_series);
avg_series.push([maxDate, 0]);
return avg_series;
}
var blockRedraw = false;
myDrawCallback_ = function(gs, initial) {
if (blockRedraw) return;
blockRedraw = true;
var range = gs.xAxisRange();
var yrange = gs.yAxisRange();
var series = calcAvg_(range[0], range[1],
gs.extractSeries_(gs.rawData_, 0, false));
gs.updateOptions( {
dateWindow: range,
valueRange: yrange,
file: series } );
blockRedraw = false;
}