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
Related
I am trying to capture the Sound Frequency value from the Device Microphone from a Web Browser, utilizing the Web Audio API. The Instructions only covers how to play a sound file and manipulate its output, ect.
I need to listen to a Humming Sound from a person and figure out the Sound frequency, which I will then convert to a musical note. I need to build an array with all the frequencies that are generated from a Single Musical Note that I play from a digital Piano keyboard. I will then get the mode of all the frequencies to get the actual Musical note.
I am able to display the frequency and notes that I detect onto the Console.log(), however, I am not able to build the array, get the mode, and then clear-out the array when I play a new note. My array remains active because the code is on an event listening mode and remains active to listen for the next note.
This is my code:
var arrayList = [];
function getModes(array) {
var frequency = []; // array of frequency.
var maxFreq = 0; // holds the max frequency.
var modes = [];
for (var i in array) {
frequency[array[i]] = (frequency[array[i]] || 0) + 1; // increment frequency.
if (frequency[array[i]] > maxFreq) { // is this frequency > max so far ?
maxFreq = frequency[array[i]]; // update max.
}
}
for (var k in frequency) {
if (frequency[k] == maxFreq) {
modes.push(k);
}
}
return modes;
}
function updatePitch(time) {
var cycles = new Array;
analyser.getFloatTimeDomainData(buf);
var ac = autoCorrelate(buf, audioContext.sampleRate);
// TODO: Paint confidence meter on canvasElem here.
if (DEBUGCANVAS) { // This draws the current waveform, useful for debugging
waveCanvas.clearRect(0, 0, 512, 256);
waveCanvas.strokeStyle = "red";
waveCanvas.beginPath();
waveCanvas.moveTo(0, 0);
waveCanvas.lineTo(0, 256);
waveCanvas.moveTo(128, 0);
waveCanvas.lineTo(128, 256);
waveCanvas.moveTo(256, 0);
waveCanvas.lineTo(256, 256);
waveCanvas.moveTo(384, 0);
waveCanvas.lineTo(384, 256);
waveCanvas.moveTo(512, 0);
waveCanvas.lineTo(512, 256);
waveCanvas.stroke();
waveCanvas.strokeStyle = "black";
waveCanvas.beginPath();
waveCanvas.moveTo(0, buf[0]);
for (var i = 1; i < 512; i++) {
waveCanvas.lineTo(i, 128 + (buf[i] * 128));
}
waveCanvas.stroke();
}
if (ac == -1) {
detectorElem.className = "vague";
pitchElem.innerText = "--";
noteElem.innerText = "-";
detuneElem.className = "";
detuneAmount.innerText = "--";
} else {
detectorElem.className = "confident";
pitch = ac;
pitchElem.innerText = Math.round(pitch);
var note = noteFromPitch(pitch);
// Here is where I am converting the frequency to a note letter
var noteString = noteStrings[note % 12];
console.log(noteString);
// This is where I am building the array range with the notes that I find
// I have a nice array, but it keeps building and I do not know how to clear it for
// the next session.
if (note >=36 && note <= 96) {
if (arrayList) {
arrayList.push(noteString);
}
console.log(noteString);
}
else {
console.log("not note");
var MyNote = getModes(arrayList)
noteElem.innerHTML = MyNote;
arrayList = [];
}
// This function remains active and continues to listen for the next not to
// generate and new note letter
var detune = centsOffFromPitch(pitch, note);
if (detune == 0) {
detuneElem.className = "";
detuneAmount.innerHTML = "--";
} else {
if (detune < 0)
detuneElem.className = "flat";
else
detuneElem.className = "sharp";
detuneAmount.innerHTML = Math.abs(detune);
}
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = window.webkitRequestAnimationFrame;
rafID = window.requestAnimationFrame(updatePitch);
}
How do I clear the array and use a new array when I play a new note?
thank you for the support...
I am trying to find an efficient way to go through a big amount of data to determine how many units are processed at once.
So the data that I am receiving are just simple pairs:
{timestamp: *, solvetime: *}
What I need, is to see how many things are processed at each second.
To help you visualize what I mean: here is an example of data that I receive:
{{timestamp: 5, solvetime: 3},{timestamp: 7, solvetime: 5},{timestamp: 8, solvetime: 2},{timestamp: 12, solvetime: 10},{timestamp: 14, solvetime: 7}}
The chart below should help you understand how it looks in time:
https://i.stack.imgur.com/LEIhW.png
This is a simple case where the final calculation contains every second, but if the timeframe is much wider I show only 205 different times in this timeframe. E.g. if the time btw the first and the last timestamp is 20500 seconds I would calculate the usage for every second and divide the time into 205 parts - 100 seconds each and show only the second with the highest usage.
What I am doing right now is to iterate through all the pairs of input data and create a map of all the seconds, once I have it I go through this map again to find the highest usage in each time period (of 205 time periods I divide the whole time-frame in) and append it to the map of 205 timestamps.
It's working correctly, but it's very very slow and I feel like there is some better way to do it, a table might be faster but it is still not too efficient is it?
Here is the actual code that does it:
// results contain all the timestamps and solvetimes
// Timeframe of the chart
var start = Math.min.apply(Math, msgDetailsData.results.map((o) => { return o.timestamp; }))
var end = Math.max.apply(Math, msgDetailsData.results.map((o) => { return o.timestamp; }))
// map of all seconds in desired range (keys) the values are counter ofprocesses run in a given second
let mapOfSecondsInRange = new Map();
for (let i = start; i <= end; i++) {
mapOfSecondsInRange.set(i, 0);
}
// we go through every proces and add +1 to the value of each second in which the task was active
for (let element of msgDetailsData.results) {
var singleTaskStart = element.timestamp - Math.ceil(element.solveTime);
if (singleTaskStart < start) {
for (let i = singleTaskStart; i < start; i++) {
mapOfSecondsInRange.set(i, 0);
}
start = singleTaskStart;
}
for (let i = singleTaskStart; i < element.timestamp; i++) {
mapOfSecondsInRange.set(i, mapOfSecondsInRange.get(i) + 1);
}
}
// Preparation for the final map - all the seconds in the range divided into 205 parts.
const numberOfPointsOnChart = 205;
var numberOfSecondsForEachDataPoint = Math.floor((end - start) / numberOfPointsOnChart) + 1;
var leftoverSeconds = ((end - start) % numberOfPointsOnChart) + 1;
var highestUsageInGivenTimeframe = 0;
var timestampOfHighestUsage = 0;
let mapOfXXXDataPoints = new Map();
var currentElement = start;
for (let i = 0; i < numberOfPointsOnChart; i++) {
if (leftoverSeconds === 0) {
numberOfSecondsForEachDataPoint = numberOfSecondsForEachDataPoint - 1;
}
if (currentElement <= end) {
for (let j = 0; j < numberOfSecondsForEachDataPoint; j++) {
if (j === 0) {
highestUsageInGivenTimeframe = mapOfSecondsInRange.get(currentElement);
timestampOfHighestUsage = currentElement;
}
else {
if (mapOfSecondsInRange.get(currentElement) > highestUsageInGivenTimeframe) {
highestUsageInGivenTimeframe = mapOfSecondsInRange.get(currentElement);
timestampOfHighestUsage = currentElement;
}
}
currentElement = currentElement + 1;
}
mapOfXXXDataPoints.set(timestampOfHighestUsage, highestUsageInGivenTimeframe);
leftoverSeconds = leftoverSeconds - 1;
}
}
Coniser a variable list = ["A", "B",...] as list of strings. I want to use a Javascript programm that picks three strings from this list once a day and writes it into a HTML field.
Currently I use
function getRandom(arr, n) {
var result = new Array(n),
len = arr.length,
taken = new Array(len);
if (n > len)
throw new RangeError("getRandom: more elements taken than available");
while (n--) {
var x = Math.floor(Math.random() * len);
result[n] = arr[x in taken ? taken[x] : x];
taken[x] = --len in taken ? taken[len] : len;
}
return result;
}
smallList = getRandom(list, 3);
var htmlTags = [
"tag1",
"tag2",
"tag3"
];
for (var i = 0; i < htmlTags.length; i++) {
document.getElementById(htmlTags[i]).innerHTML = smallList[i];
}
Now this list gets new entries every time I refresh the website. Is there a way that smallList is only set once a day/hour/min/ in a pedriod of time only using javascript?
So you want to:
Pick three values from your list and show them on your web page
Each day, pick three new values to show for the whole day
Everyone who visits the page should see the same values regardless of client
As others have suggested, it would be a better candidate for a server-side task than client-side.
For example, you might have a server page which checks for the existence of a value stored in cache. The cache would be set to 24 hours. If the cache is not available, then a new cache object is created and given a half-life of 24 hours. Inside the cache, you could also store the values you wish to retrieve.
Then, you could retrieve the cache and output the values. The particular implementation of the cache would depend on your server-side language.
OKAY: Via Stored Values (COOKIE, SESSION, LOCAL STORAGE, MEMORY):
per user you'd have to use a cookie, session, or write to local storage in the browser.
for all users you'd have to use a server variable somewhere like a database, file, or memory.
you'd set the value to expire in a day and regenerate if expired. this is the answer you will get from most people because it is the only way they know how to solve this, single set value polled from all locations.
BETTER: Via Deterministic Pseudo-Random Number Generator Seeded with Date:
or if you are really ambitious and don't want to rely on a value that you set somewhere, you could use or write a:
deterministic pseudo-random number generator that you seed off of the date. Since deterministic pseudo-random generators produce reproducible "randoms" from the same seed, seeding the date gives you a unique seed per day, hence a unique random per day.
function RC4(seed) {
this.s = new Array(256);
this.i = 0;
this.j = 0;
for (var i = 0; i < 256; i++) {
this.s[i] = i;
}
if (seed) {
this.mix(seed);
}
};
RC4.getStringBytes = function(string) {
var output = [];
for (var i = 0; i < string.length; i++) {
var c = string.charCodeAt(i);
var bytes = [];
do {
bytes.push(c & 0xFF);
c = c >> 8;
} while (c > 0);
output = output.concat(bytes.reverse());
}
return output;
};
RC4.prototype._swap = function(i, j) {
var tmp = this.s[i];
this.s[i] = this.s[j];
this.s[j] = tmp;
};
RC4.prototype.mix = function(seed) {
var input = RC4.getStringBytes(seed);
var j = 0;
for (var i = 0; i < this.s.length; i++) {
j += this.s[i] + input[i % input.length];
j %= 256;
this._swap(i, j);
}
};
RC4.prototype.next = function() {
this.i = (this.i + 1) % 256;
this.j = (this.j + this.s[this.i]) % 256;
this._swap(this.i, this.j);
return this.s[(this.s[this.i] + this.s[this.j]) % 256];
};
function RNG(seed) {
if (seed == null) {
seed = '' + Math.random() + Date.now();
} else if (typeof seed === "function") {
// Use it as a uniform number generator
this.uniform = seed;
this.nextByte = function() {
return ~~(this.uniform() * 256);
};
seed = null;
} else if (Object.prototype.toString.call(seed) !== "[object String]") {
seed = JSON.stringify(seed);
}
this._normal = null;
if (seed) {
this._state = new RC4(seed);
} else {
this._state = null;
}
}
RNG.prototype.nextByte = function() {
return this._state.next();
};
RNG.prototype.uniform = function() {
var BYTES = 7; // 56 bits to make a 53-bit double
var output = 0;
for (var i = 0; i < BYTES; i++) {
output *= 256;
output += this.nextByte();
}
return output / (Math.pow(2, BYTES * 8) - 1);
};
RNG.prototype.random = function(n, m) {
if (n == null) {
return this.uniform();
} else if (m == null) {
m = n;
n = 0;
}
return n + Math.floor(this.uniform() * (m - n));
};
RNG.$ = new RNG();
Date.prototype.yyyymmdd = function() {
var mm = this.getMonth() + 1; // getMonth() is zero-based
var dd = this.getDate();
return [this.getFullYear(), !mm[1] && '0', mm, !dd[1] && '0', dd].join(''); // padding
};
// Using the Date like so will give you the same random between 40 and 50 for the same day
var rng = new RNG((new Date).yyyymmdd()); rng.random(40, 50);
// Test with dates
var rng = new RNG('20180301'); rng.random(40, 50);
var rng = new RNG('20180302'); rng.random(40, 50);
var rng = new RNG('20180301'); rng.random(40, 50);
Store the list in localStorage or a Cookie. Also store the timestamp.
Use setTimeout(function(){...}, n) to examine the timestamp and update the values as needed.
If the page refreshes or is loaded anew, then perform the check on what is stored. If nothing exists, create your list and set the timestamp. If data does exist, then compare the timestamp and update the list as needed.
If you need the list to be consistent across users, then everything needs to be stored, examined and calculated on the server-side.
localStorage.savedData = {
timestamp: new Date(),
dataList: ['a','b','c']
}
To get the values from localStorage:
// you don't have to create variables, you can just use localStorage.[property] to get compare any value
let ts = localStorage.timestamp; // Date object
let dl = localStorage.dataList; // Array of values
For more information on localStorage see (or search the web) -> https://www.w3schools.com/html/html5_webstorage.asp
i was trying to implement the A* algorithm and followed the wikipedia pseudo code to make this.
when i pass a predefined object pixel to the a funtion getG() it says that the object is null. I'm sorry if i am not pointing to a specific problem but i am not even sure how to really specify the problem by name. i have tried commenting out the code to increase readability.
git repository link of the whole project - https://github.com/NirobNabil/WhirlWind
(things are a little messy here because i didn't use github at first and i uploaded it just a little ago for posting the problem)
[ i'm actually making this to use a* to find path for my bot which is powered by arduino. thats why i'm using involt. ]
here goes the code,
$(function() {
// define the height, width and bot size in centemeter
total_width = 200;
total_height = 200;
bot_size = 20;
total_box = (total_height / bot_size) * (total_width / bot_size);
box_in_x = total_width / bot_size;
box_in_y = total_height / bot_size;
//populating the pixels array
populate(total_width / bot_size, total_height / bot_size, "UND");
pathfind(pixels, pixels[13], pixels[pixels.length - 1]);
})
var pixels = []; //an array to hold all the objects(the grid)
var obstacles = []; //array to hold the obstacles
function pixel(X, Y, obs) {
this.X_co_ordinate = X;
this.Y_co_ordinate = Y;
this.state = obs; //availale states OPN, UND, OBS, DIS, NULL
this.g = 0;
this.h = 0;
this.f = 0;
this.last = null;
} //every block in the grid is a pixel
//01719372596
function populate(height, width, obs_val = "UND") {
pixels[0] = new pixel(0, 10, obs_val);
for (h = height, i = 0; h >= 0; h--) {
for (w = 0; w < width; w++, i++) {
var temp_obs = new pixel(w, h, obs_val);
temp_obs.last = pixels[0];
pixels[i] = temp_obs; //saving temp_pixel object to pixels array
}
}
} //populating the grid AKA pixels with pixel objects or blocks
// this funtion is where the problem shows up
function getG(current, start) {
let g = 1;
while (current != start && current.last != start && current) {
current = current.last;
g++;
}
return g;
} //get the g val(cost to come to this pixel from the start) of the current pixel
function getH(current, end) {
let I = Math.abs(current.X_co_ordinate - end.X_co_ordinate) + Math.abs(current.Y_co_ordinate - end.Y_co_ordinate);
return I;
} //get the h val(heuristic) of the current pixel
function getF(start, current, end) {
let G = getG(current, start);
let H = getH(current, end);
return G + H;
} //get the f val(total) of the current pixel
function lowFinArray(arr, start, end) {
// here arr is the grid/pixel
let current_low = arr[0];
for (let i = 0; i < arr.length; i++) {
let getF1 = getF(start, current_low, end);
let getF2 = getF(start, arr[i], end);
if (getF1 < getF2) {
current_low = arr[i];
}
}
console.log("current low");
console.log(current_low);
return current_low;
}
function getneighbours(grid, current) {
let neightbours = [];
neightbours.push(grid[getIndex(current.X_co_ordinate - 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate + 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate - 1)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate + 1)]);
/*
for(i=0; i<neightbours.length; i++){
neightbours[i].last = current;
}*/
console.log("neightbours");
console.log(neightbours);
return neightbours;
} //get the neighbour pixels of the current pixel
//main algo
function pathfind(grid, start, end) {
let closedSet = [];
let openSet = [];
openSet.push(start);
let current = start;
//trying to debug
console.log("low F in arr");
console.log(lowFinArray(grid, start, end));
console.log(start);
console.log(current);
console.log(end);
console.log(grid);
let x = 0;
while (openSet.length > 0) {
//trying to debug
console.log("executed " + (x++));
console.log("openset");
console.log(openSet);
current = lowFinArray(grid, start, end); //assigning the pixel with lowest f val to current
console.log("current");
console.log(current);
if (current == end) {
console.log(getPath(current));
}
let neighbours = getneighbours(grid, current);
for (let i = 0; i < neighbours.length; i++) {
let neighbour = neighbours[i];
if (closedSet.includes(neighbour)) {
continue;
}
if (!openSet.includes(neighbours)) {
openSet.push(neighbours);
}
//console.log(current);
let getg = getG(current, start);
let geth = getH(current, end);
//console.log(getg);
let tGscore = getg + geth; //here getH is being used as a distance funtion
if (tGscore >= getg) {
continue;
}
neighbour.last = current;
neighbour.g = tGscore;
neighbour.f = getF(neighbour);
}
if (x > 10) {
return 0;
}; //the loop was running forever so i tried this to stop the loop after 10 iterations
}
}
function getPath(current) {
let path = [current];
while (current.last != null) {
path.push(current.last);
}
return path;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
and here is what the console says,
Uncaught TypeError: Cannot read property 'last' of null
at getG (app.js:226)
at getF (app.js:241)
at lowFinArray (app.js:249)
at pathfind (app.js:292)
at HTMLDocument.<anonymous> (app.js:92)
at mightThrow (jquery-3.1.1.js:3570)
at process (jquery-3.1.1.js:3638)
You're doing your checks in the wrong order:
while (current != start && current.last != start && current) {
There's no point in using && current after you've already used current.last.
Perhaps changing the order would solve the problem. It will at least get rid of your current error:
while (current && current != start && current.last != start) {
Regarding the title of this question:
In javascript, after i pass a non null object to a funtion it says the object is null
It may very well be non-null 100% of the time you pass it into the function, but you are repeatedly overwriting its parameter within the function, so all bets are off.
Here is my function for determining if a given timecode in the buffer of an html5 video element (learned about this here).
I think there must be a faster way. Maybe a binary search over the start times?
I considered an interval tree, but the cost to maintain that data structure seems excessive given a system level data structure is provided.
isTimecodeInBuffer = function( _tc ) {
var r = $(html5VideoEl).buffered;
var i;
var iMax = r.length;
var within = false;
//todo: better seek here
for (i=0; i<iMax; ++i) {
if (_tc >= r.start(i) && _tc < r.end(i)) {
within = true;
break;
}
}
return within;
};
You can do this with a standard binary search that is slightly modified to test for matching a time range, rather than an exact match. It's not worth storing any kind of data structure, since the data will change quite frequently as additional data is buffered.
function bufferedGreater(haystack, index, value) {
return haystack.end(index) <= value;
}
function bufferedLess(haystack, index, value) {
return haystack.start(index) > value;
}
function binarySearch(haystack, needle, greaterThan, lessThan) {
var minIndex = 0,
maxIndex = haystack.length - 1,
currentIndex;
while (minIndex <= maxIndex) {
currentIndex = Math.floor((minIndex + maxIndex) / 2);
if (greaterThan(haystack, currentIndex, needle)) {
minIndex = currentIndex + 1;
} else if (lessThan(haystack, currentIndex, needle)) {
maxIndex = currentIndex - 1;
} else {
return currentIndex;
}
}
return -1;
}
var buffered = binarySearch(video.buffered, 10, bufferedGreater, bufferedLess) >= 0;
There is a working demo at http://jsbin.com/vifedogi/1/edit?html,js,console,output
Note: you'll want to access the buffered object directly on the video element, not on the jQuery object, like var r = html5VideoEl.buffered;