Help - push an array into array simple question

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);

      }

  }

Your loop only runs 3 times.

You probably want 32 there, not 3.

Is it your intent to push a reference or a copy here?:

      sampleBuffer.push(bandsEnergy);

The above will push a reference to bandsEnergy onto sampleBuffer.

For a copy of it instead, there are various possible solutions. One of them is to do this:

      sampleBuffer.push(bandsEnergy.slice(0, 3));

Try that, then check the contents of sampleBuffer.

1 Like

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);
 }

Image capture:
array_display

Please let us know how it works out.

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.

You would get something like this:

sampleBuffer[0] = [val1, val2, val3 ]


sampleBuffer[31] = [val1, val2, val3 ]

where all the “vals” are the numbers generated in the initial loop (in this case it’s just random).

The following information on Arrays may be useful:

Consider whether these particular methods would help:

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.

1 Like

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:

  1. The spread operator
// The array initialize with the spread operator:
sampleBuffer.push([...bandsEnergy]);
  1. Make bandsEnergy a local variable and initialize it to a new array bandsEnergy = [] in each call to draw.
4 Likes

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.

1 Like

This is my take on using shift() + push() as a queue of Float32Array which should be slightly faster: :racing_car:

/**
 * Shift-Push 2D Queue (v1.0.0)
 * GoToLoop (2021-Aug-05)
 *
 * https://Discourse.Processing.org/t/
 * help-push-an-array-into-array-simple-question/31577/12
 */

'use strict';

const MAX_SAMPLES = 32, ENERGIES = 3, MAX_VAL = 10, sampleBuffer = [];

function setup() {
  createCanvas(400, 300).mousePressed(redraw);
  noLoop();
}

function draw() {
  background('#' + hex(~~random(0x1000), 3));

  const bandsEnergy = sampleBuffer.length >= MAX_SAMPLES &&
                      sampleBuffer.shift() || new Float32Array(ENERGIES);

  sampleBuffer.push(randomFill(bandsEnergy));

  console.table(sampleBuffer);
}

function randomFill(arr, maxVal = MAX_VAL) {
  for (var i = 0; i < arr.length; arr[i++] = random(maxVal));
  return arr;
}
1 Like

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.

GameDev Beat Detection

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