I’m racking my head not understanding why this doesn’t work.
i was expecting the sampleBuffer array to become a length 32 array containing 32 arrays each with 3 random numbers. Instead I get a length 32 array of 32 arrays with 3 identical numbers. What am I doing wrong?
let sampleBuffer=[];
let bandsEnergy=[];
function setup() {
}
function draw() {
for(let i=0; i<3; i++) {
bandsEnergy[i] = random(10);
}
sampleBuffer.push(bandsEnergy);
if(sampleBuffer.length==32) {
console.log(sampleBuffer);
sampleBuffer.splice(0,1);
}
}
All I want to do is push an array of numbers into another array and then reference those numbers to make a calculation. Something is wrong. The splice just removes the last array from the bunch. This is an example of what I want it to do that I did after I posted. Unfortunately this method is not working in my main program. I don’t understand what the problem is. Passing an array into another array should be fine to do, as it’s an object. Not sure why this second example works by putting the array inside an object, and then I have to use something like sampleBuffer[0].band[x]. Maybe I need to make a 2d array. I’ve never had such an annoying problem in p5.
var sampleBuffer=[];
var bandsEnergy=[];
function setup() {
}
function draw() {
for(let i=0; i<3; i++) {
bandsEnergy[i] = random(10);
}
var b = { band: bandsEnergy };
sampleBuffer.push(b);
if(sampleBuffer.length==32) {
//console.log(sampleBuffer);
sampleBuffer.splice(0,1);
}
console.log(sampleBuffer);
// console.log(sampleBuffer[0].band.length);
}
If you are hoping for an array of 32 inner arrays, where the inner arrays each contain three random numbers, and where the inner arrays differ from each other, this will do it:
let sampleBuffer=[];
let bandsEnergy=[];
function setup() {
}
function draw() {
for(let i=0; i<3; i++) {
bandsEnergy[i] = random(10);
}
sampleBuffer.push(bandsEnergy.slice(0, 3));
if(sampleBuffer.length==32) {
noLoop();
console.log(sampleBuffer);
}
}
After testing this, you can remove the call to the noLoop function.
But perhaps we are mistaken regarding what you are trying to do. If so, please clarify it for us
EDIT (August 5, 2021):
Perhaps the following example can help:
let sampleBuffer=[];
let bandsEnergy=[];
function setup() {
createCanvas(400, 500);
for (let i=0; i<20; i++) {
for (let j=0; j<3; j++) {
bandsEnergy[j] = random(10);
}
sampleBuffer.push(bandsEnergy.slice(0, 3));
}
frameRate(10);
}
function draw() {
background(255);
for (let j=0; j<3; j++) {
bandsEnergy[j] = random(10);
}
for (let i=0; i<sampleBuffer.length; i++) {
text(sampleBuffer[i], 10, i * 20 + 20);
}
sampleBuffer.push(bandsEnergy.slice(0, 3));
sampleBuffer.splice(0,1);
}
The loop is running every frame of draw. So it runs three times, but forever. That’s what’s the splice does - it removes old values from the buffer, so there are only 32 values in there.
Let me clarify. This is for a beat detection algorithm, and something that we are studying in school. I haven’t put the whole code here because it will get confusing, and it’s wrong.
The reason for the splice is so the array gets to a certain size, and then removes the first element in the array and pushes the newest element to the end, so it stays at a constant size once populated. It needs to run at 60 frames also. I’d like to avoid splicing / slicing arrays. The sampleBuffer should just accept an array into it (imagine you are storing values of 32 bands of an eq over time that all need to be averaged later). What I’m not understanding is why the values are getting replicating and why the other example with an object works (var b = { band: bandsEnergy }).
I guess I could ask this more generally. In draw, I want to make a loop that creates an array of values (each set in the array is different). This array is then pushed into another history array that is removing the first element so it stays at a constant size of desired length over time.
Nevertheless, posting your code may help us understand what you are trying to do.
The example below initializes sampleBuffer with 32 inner Arrays of 3 random numbers each within the setup() function. The inner Arrays are not replicated.
Within draw(), it pushes an Array of 3 new random numbers onto the top of sampleBuffer and removes the oldest Array of 3 numbers from the beginning of sampleBuffer during each frame. It also calls a processTheData function during each frame. As written here, that function displays the contents of sampleBuffer. Run the code, and notice the changes in the data during each frame. You can call frameRate to slow it down temporarily, if necessary.
let sampleBuffer=[];
let bandsEnergy=[];
function setup() {
createCanvas(400, 660);
// initialize sampleBuffer
for (let i=0; i<32; i++) {
sampleBuffer.push([random(10), random(10), random(10)]);
}
}
function draw() {
background(239);
processTheData(sampleBuffer);
// push new data on sampleBuffer
sampleBuffer.push([random(10), random(10), random(10)]);
// remove data at index 0 of sampleBuffer
sampleBuffer.shift();
}
function processTheData(data) {
for (let i=0; i<data.length; i++) {
text(data[i], 10, i * 20 + 20);
}
}
You can rewrite the processData function to do this.
That is because you populated sampleBuffer with 32 references to bandsEnergy.
We should take a moment here and make sure @atagger understands the concept of a reference. When you create an array and assign values to it as you do with bandsEnergy, when you use that array, passing it to a function, or adding it to another array/data structure, you are adding a reference to the original array. That means that if you change the array later, those changes will be reflected in all the places that reference the original array.
The slice() function is a common way to make a quick copy of an array. The original will not be modified, and the array returned by slice() will be a brand new array that contains the same values (note: don’t confuse slice with splice, they are very different). So specifically in the context of your code:
// this will add a reference to bandsEnergy, and if you change bandsEnergy later
// those changes will be reflected by each reference to that one array, which is
// why you would end up with an array of 32 identical arrays containing just the
// latest value
sampleBuffer.push(bandsEnergy);
// This, on the other hand, will make a copy of bandsEnergy and should achieve
// your desired behavior
sampleBuffer.push(bandsEnergy.slice());
// Note that slice takes two parameters, a start index and a length, and returns
// just that range of the array. However both of these parameters are optional.
There are other ways to solve this issue:
The spread operator
// The array initialize with the spread operator:
sampleBuffer.push([...bandsEnergy]);
Make bandsEnergy a local variable and initialize it to a new array bandsEnergy = [] in each call to draw.
Thank you. This is brilliant and what I was trying to understand. I never encountered this problem or limitation before, and did not understand it was passed as a reference. The three solutions you provided work. I had tried the spread operator but only like this: sampleBuffer.push(…bandsEnergy); which does not work, it needs the brackets.
The best solution is just redeclaring the bassEnergy[] every time on draw, it does exactly what I want it to do. And thank you also javagar! The frame rate tip was something I totally forgot about to be able to read stuff in the console.
You guys are awesome. This is what I was making ( the full code that the above problem was a snippet from ). I am having problems with this algorithm in the below code. Specifically the guy in the article uses 250 as his constant C (See beat algorithm 2 - frequency selected sound energy), no idea how he got that number, but it doesn’t work at all for me. See what you think, maybe this belongs in another post all together, but thought I’d share.
let sound;
let size;
function preload() {
//put your sound file here
sound = loadSound("../audio/17.mp3");
}
function setup() {
createCanvas(500,500);
frameRate(60);
beatDetect = new BeatDetect();
sound.loop();
}
function draw() {
background(0);
if( beatDetect.detectBeat() ) {
fill(255,0,0);
//console.log(size);
ellipse(width/2,height/2,size,size);
}
}
//BeatDetect constructor
function BeatDetect() {
//http://archive.gamedev.net/archive/reference/programming/features/beatdetection/
let sampleBuffer=[]; //sample history buffer
let bandsAverage=[]; //array of averages of the bands
let energyBuffer=[]; //array of current sound energies per band
let fourier = new p5.FFT();
this.detectBeat = function() {
//analyze fourier
let spectrum=fourier.analyze();
var count=0; // reset count every frame
var isBeat=false; // reset isBeat every frame
let bandsEnergy=[]; // start a new array each frame, so we don't pass in an array reference :)
let bands_temp=[]; // temp array to split the frequency bands
for(let i=0; i < spectrum.length; i++) {
energyBuffer[i] = spectrum[i] * spectrum[i];
bands_temp.push(energyBuffer[i]); // temp array to split into grouped bands
//collect 32 samples (one band of 1024 i.e. 1024/32 = 32 bands)
if(bands_temp.length == 32) {
let sum=0; // init the sum to zero
for(let j=0; j < bands_temp.length; j++) {
sum+=bands_temp[j]; // get the sum of the band's samples
bandsEnergy[count] = sum; // get average energy of band's grouping
}
bands_temp=[]; //clear temp bands_sum array for next set of 32 samples;
count++; //increment count index, reset this every frame;
}
}
sampleBuffer.push(bandsEnergy);
//+1 since we remove a value each frame
if(sampleBuffer.length == 43 + (1)) {
for(let j = 0; j < bandsEnergy.length; j++) {
let bandSum=0; // reset bandSum when we get to a new band
//loop through all arrays in history buffer
for(let k=0; k < sampleBuffer.length; k++) {
bandSum += sampleBuffer[k][j]; // get the sum for each freq. band in the buffer
}
//calc avg from sum in each band of the buffer
bandsAverage[j] = bandSum/sampleBuffer.length;
}
//remove the oldest sample from the array
sampleBuffer.splice(0,1);
}
for(let k=0; k < sampleBuffer.length; k++) {
let c = 1; //???
if(bandsEnergy[k] > bandsAverage[k]*c) {
isBeat=true;
size=(bandsEnergy[k])*.0002;
return isBeat;
}
}
//console.log(bandsEnergy.length);
//console.log(sampleBuffer.length);
//console.log( bandsAverage[14] + "bandAverage");
//console.log( bandsEnergy[14] + "bandEnergy");
} // end DETECT
} // end CONSTRUCTOR