Syntax help with 2D array

I am trying to define a 2D array in Processing but I don’t seem to be able to get the syntax right.

I have defined the global variable

byte[][] chordBank2;

And in the setup function I have put

chordBank2 = new byte[5][7];

Then I want to put a function in to define my array

This is what I put

void defBank(){ // define array
  chordBank2[5][7] = {

                        {40, 45, 50, 55, 59, 64}; // open strings 0
                        {40, 47, 52, 56, 59, 64}; // E major      1
             { 41   ,48        , 53, 57, 60, 65}; // F major      2                   
             {           43, 47, 50, 55, 59, 67}; // G major      3
             { 33          , 45, 52, 57, 61, 64}; // A major      4
              {35          , 47, 54, 59, 63, 66}; // B major      5
             { 36          , 48, 52, 55, 60, 64}; // C major      6
             { 38  ,38          , 50, 57, 62, 66}; // D major     7
  }
}

But I keep getting

Syntax Error - Missing operator, semicolon, or ‘}’ near ‘{’?

I am unsure of which way round columns and rows go so I have swapped the 5 an 7 over but still no joy.

Can any one point me in the right direction to solving this.

Thanks for reading.

Hello @Grumpy_Mike,

There is an example here that may help to initialize the array:

Updated

Adding link below for any visitors to this topic in future.

There are tutorials here (including 2D arrays) that are a good starting point:
Tutorials / Processing.org

:)

I’m not convinced that you can use the { ... } initializer syntax directly within that function as you do. Looking into that article @glv posted should clear things up. Alternatively, you could assign each note array to it’s respective chord individually. Something like this:

chordBank2[0] = new byte[] {40, 45, 50, 55, 59, 64}; // open strings

And then repeat for all other chords. I feel this also keeps it somewhat cleaner and more structured.

Another big issue that jumps out is the whole “arrays start indexing at 0” thing that keeps tripping people up. You seem to be aware of this, though I believe you overcorrected in this particular case.

As I understand it, you want 8 chords, with 6 notes each. These numbers represent the lenghts of the arrays you want to use. When you initialise arrays, you have to think in length, not indexes. Hence, your initialization chordBank2 = new byte[7][5]; actually creates a 2D array of lengths 7 and 5. The whole thing flips when you want to access an array. Here you need to think with “starts at 0”.

Example:
A 1D array of length 8 would be initialized with exampleArray = new int[8].
But if you want to access the last entry in that array, you use exampleArray[7].

2 Likes

If that’s data that won’t change during the course of your sketch, you can simply declare it & initialize it at 1 single statement; no need for a separate function:

static final byte[][] CHORD_BANK2 = {
  { 40, 45, 50, 55, 59, 64 }, // open strings 0
  { 40, 47, 52, 56, 59, 64 }, // E major      1
  { 41, 48, 53, 57, 60, 65 }, // F major      2                   
  { 43, 47, 50, 55, 59, 67 }, // G major      3
  { 33, 45, 52, 57, 61, 64 }, // A major      4
  { 35, 47, 54, 59, 63, 66 }, // B major      5
  { 36, 48, 52, 55, 60, 64 }, // C major      6
  { 38, 38, 50, 57, 62, 66 }  // D major      7
};

The variable name CHORD_BANK2 is using the SCREAMING_SNAKE_CASE convention, hinting that its content shouldn’t ever be mutated.

P.S.: If you happen to have multiple chord banks, you can define a 3d-array instead for all of them:

static final byte[][][] CHORD_BANKS = {
  { // bank 0
    { 40, 45, 50, 55, 59, 64 }, // open strings 0
    { 40, 47, 52, 56, 59, 64 }, // E major      1
    { 41, 48, 53, 57, 60, 65 }, // F major      2                   
    { 43, 47, 50, 55, 59, 67 }, // G major      3
    { 33, 45, 52, 57, 61, 64 }, // A major      4
    { 35, 47, 54, 59, 63, 66 }, // B major      5
    { 36, 48, 52, 55, 60, 64 }, // C major      6
    { 38, 38, 50, 57, 62, 66 }  // D major      7
  },

  { // bank 1
    { 40, 45, 50, 55, 59, 64 }, // open strings 0
    { 40, 47, 52, 56, 59, 64 }, // E major      1
    { 41, 48, 53, 57, 60, 65 }, // F major      2                   
    { 43, 47, 50, 55, 59, 67 }, // G major      3
    { 33, 45, 52, 57, 61, 64 }, // A major      4
    { 35, 47, 54, 59, 63, 66 }, // B major      5
    { 36, 48, 52, 55, 60, 64 }, // C major      6
    { 38, 38, 50, 57, 62, 66 }  // D major      7
  },

  { // bank 2
    { 40, 45, 50, 55, 59, 64 }, // open strings 0
    { 40, 47, 52, 56, 59, 64 }, // E major      1
    { 41, 48, 53, 57, 60, 65 }, // F major      2                   
    { 43, 47, 50, 55, 59, 67 }, // G major      3
    { 33, 45, 52, 57, 61, 64 }, // A major      4
    { 35, 47, 54, 59, 63, 66 }, // B major      5
    { 36, 48, 52, 55, 60, 64 }, // C major      6
    { 38, 38, 50, 57, 62, 66 }  // D major      7
  }
};
1 Like

Thanks, that looks very promising and was the sort of solution I was looking for.

However, I can’t seem to make it work, that is I can’t seem to actually access the values in the table.
I needed to keep the

chord_Bank2 = new byte[5][7];

in the setup function otherwise I get a null pointer exception when trying to access the data.
I wrote some code to print out the data:-

void printChord(int chord){ // print a chord
    for(int i=0; i<6; i++){
    print(" ",chord_Bank2[chord][i]);
  }
  println();

}

but all it ever returns is 0 for each note

Yes this is the case that the table is never altered.
Is there anything else I need to include to make this work?
Thanks
Mike

Can you paste your code which is throwing the nullpointer exception? It may be that you’ve got your chord/notes order reversed in the array.

Just putting all the discussed parts together works fine for me.

The @GoToLoop approach:

static final byte[][] chordBank2 = {
  { 40, 45, 50, 55, 59, 64 }, // open strings 0
  { 40, 47, 52, 56, 59, 64 }, // E major      1
  { 41, 48, 53, 57, 60, 65 }, // F major      2                   
  { 43, 47, 50, 55, 59, 67 }, // G major      3
  { 33, 45, 52, 57, 61, 64 }, // A major      4
  { 35, 47, 54, 59, 63, 66 }, // B major      5
  { 36, 48, 52, 55, 60, 64 }, // C major      6
  { 38, 38, 50, 57, 62, 66 }  // D major      7
};

void setup() {
}

void draw() {
  printChord(2); // prints chord 2 
}

void printChord(int chord){ // print a chord
    for(int i=0; i<6; i++){
    print(" ",chordBank2[chord][i]);
  }
  println();
}

My approach:

byte[][] chordBank2 = new byte[8][6];

void setup() {  
  chordBank2[0] = new byte[] {40, 45, 50, 55, 59, 64}; // open strings
  chordBank2[1] = new byte[] {40, 47, 52, 56, 59, 64}; // E major
  chordBank2[2] = new byte[] {41, 48, 53, 57, 60, 65}; // F major
  chordBank2[3] = new byte[] {43, 47, 50, 55, 59, 67}; // G major
  chordBank2[4] = new byte[] {33, 45, 52, 57, 61, 64}; // A major
  chordBank2[5] = new byte[] {35, 47, 54, 59, 63, 66}; // B major
  chordBank2[6] = new byte[] {36, 48, 52, 55, 60, 64}; // C major
  chordBank2[7] = new byte[] {38, 50, 57, 62, 66, 69}; // D major 
}

void draw() {
  printChord(2); // prints chord 2 
}

void printChord(int chord){ // print a chord
    for(int i=0; i<6; i++){
    print(" ",chordBank2[chord][i]);
  }
  println();
}

It is simply caused by removing the

chord_Bank2 = new byte[5][7];`

in the setup function.

No, when I revers them I get an array overrun message as well as the returned zeros.

It is worth noting that I do not want to just print out the chord but access the individual notes to pass on to something else.

Can I just add that I a fine programming in C/C++ but the way thing happen in Processing is often different.

Two options:
You can access the 2D array directly. But then you need to compensate for the “starts indexing at 0” thing.
Or you use a helper function and pass in what positions you want, and the helper function compensates for the indexing.

byte[][] chordBank2 = new byte[8][6];

void setup() {  
  chordBank2[0] = new byte[] {40, 45, 50, 55, 59, 64}; // open strings
  chordBank2[1] = new byte[] {40, 47, 52, 56, 59, 64}; // E major
  chordBank2[2] = new byte[] {41, 48, 53, 57, 60, 65}; // F major
  chordBank2[3] = new byte[] {43, 47, 50, 55, 59, 67}; // G major
  chordBank2[4] = new byte[] {33, 45, 52, 57, 61, 64}; // A major
  chordBank2[5] = new byte[] {35, 47, 54, 59, 63, 66}; // B major
  chordBank2[6] = new byte[] {36, 48, 52, 55, 60, 64}; // C major
  chordBank2[7] = new byte[] {38, 50, 57, 62, 66, 69}; // D major 
}

void draw() {  
  // access 2D array directly, need to think in indexes
  int aSingleNote = chordBank2[6][3]; // from the 7th chord (6th index), get the 4th note (3rd index)
  println("Direct note get: "+ aSingleNote); 
  
  // via a function
  int anotherSingleNote = getNote(7,4); // from the 7th chord, get the 4th note
  println("Function note get: "+anotherSingleNote);   
   
  stop();
}

int getNote(int chordNum, int noteNum) {
  return chordBank2[chordNum-1][noteNum-1];
}

Although the approaches by @GoToLoop and @eightohnine are equally valid I prefer the former because it is more concise although I would remove the static keyword because it has no benefit in this context i.e. a basic Processing sketch.

@eightohnine does show clearly the structure of a 2D array - a 2D array is a 1D array of 1D arrays and we can extend that to a 3D array is a 1D array of 2D arrays

The following sketch combines some of the features of previous posts but also uses method / function overloading to improve flexibility in accessing chords and notes. Just in case you don’t know function overloading is where you have two or more functions with the same name but with different parameters. When you call the function the compiler looks ate the parameters you pass and selects the matching function for you.

final byte[][][] CHORD_BANKS = {
  { // bank 0
    { 40, 45, 50, 55, 59, 64 }, // open strings 0
    { 40, 47, 52, 56, 59, 64 }, // E major      1
    { 41, 48, 53, 57, 60, 65 }, // F major      2
    { 43, 47, 50, 55, 59, 67 }, // G major      3
    { 33, 45, 52, 57, 61, 64 }, // A major      4
    { 35, 47, 54, 59, 63, 66 }, // B major      5
    { 36, 48, 52, 55, 60, 64 }, // C major      6
    { 38, 38, 50, 57, 62, 66 }  // D major      7
  },

  { // bank 1
    { 40, 45, 50, 55, 59, 64 }, // open strings 0
    { 40, 47, 52, 56, 59, 64 }, // E major      1
    { 41, 48, 53, 57, 60, 65 }, // F major      2
    { 43, 47, 50, 55, 59, 67 }, // G major      3
    { 33, 45, 52, 57, 61, 64 }, // A major      4
    { 35, 47, 54, 59, 63, 66 }, // B major      5
    { 36, 48, 52, 55, 60, 64 }, // C major      6
    { 38, 38, 50, 57, 62, 66 }  // D major      7
  },

  { // bank 2
    { 40, 45, 50, 55, 59, 64 }, // open strings 0
    { 40, 47, 52, 56, 59, 64 }, // E major      1
    { 41, 48, 53, 57, 60, 65 }, // F major      2
    { 43, 47, 50, 55, 59, 67 }, // G major      3
    { 33, 45, 52, 57, 61, 64 }, // A major      4
    { 35, 47, 54, 59, 63, 66 }, // B major      5
    { 36, 48, 52, 55, 60, 64 }, // C major      6
    { 38, 38, 50, 57, 62, 66 }  // D major      7
  }
};

void setup() {
  byte[][] bank = getBank(2);
  byte[] chord = getChord(bank, 3);
  printBank(bank);
  printChord(chord);
  println("Note : " + getNote(0, 3, 2));
}

byte[][] getBank(int bankNbr) {
  return CHORD_BANKS[bankNbr];
}

byte[] getChord(byte[][] bank, int chord) {
  return bank[chord];
}

byte[] getChord(int bankNbr, int chord) {
  return CHORD_BANKS[bankNbr][chord];
}

byte getNote( byte[] chord, int n) {
  return chord[n];
}

byte getNote(int bankNbr, int chord, int n) {
  return CHORD_BANKS[bankNbr][chord][n];
}

void printBank(byte[][] bank) {
  println("Bank : ");
  for (int chordIdx = 0; chordIdx <  bank.length; chordIdx++) {
    byte[] chord = bank[chordIdx];
    print("Chord: " + chordIdx + "    Notes:  " );
    for (int noteIdx = 0; noteIdx < chord.length; noteIdx++) {
      print("  " + chord[noteIdx]);
    }
    println();
  }
}

void printBank(int bankNbr) {
  println("Bank munber : " + bankNbr);
  byte[][]  bank = CHORD_BANKS[bankNbr];
  for (int chordIdx = 0; chordIdx <  bank.length; chordIdx++) {
    byte[] chord = bank[chordIdx];
    print("Chord: " + chordIdx + "    Notes:  " );
    for (int noteIdx = 0; noteIdx < chord.length; noteIdx++) {
      print("  " + chord[noteIdx]);
    }
    println();
  }
}

void printChord(byte[] chord) {
  println("Chord : ");
  print("  Notes:  " );
  for (int noteIdx = 0; noteIdx < chord.length; noteIdx++) {
    print("  " + chord[noteIdx]);
  }
  println();
}

void printChord(int bankNbr, int chordNbr) {
  println("Bank munber : " + bankNbr  + "    Chord number : " +  chordNbr);
  byte[] chord = CHORD_BANKS[bankNbr][chordNbr];
  print("  Notes:  " );
  for (int noteIdx = 0; noteIdx < chord.length; noteIdx++) {
    print("  " + chord[noteIdx]);
  }
  println();
}
3 Likes

Neither static nor final are actually required for any code to run!

But when defining data which shouldn’t be mutated (regardless it’s enforced or not by the compiler), it’s pretty common to pair static final together, as we can see in this excerpt from Processing’s own source code:

Although I’d rename that array to PLATFORM_NAMES, which is the appropriate naming convention for read-only data.

Although Java arrays are mutable, when we see at least 2 of these elements together (static, final, SCREAMING_SNAKE_CASE), those’d be a telltale sign we should only read such data, but never modify it.

Actually, the SCREAMING_SNAKE_CASE naming convention alone means immutability for any programming language.

Keyword final makes the compiler disallow a variable/parameter/field to be re-assigned to another value later:

However, the keyword final in Java doesn’t protect an object from being mutated, only its variable from being re-assigned!

Keyword static, when applied to a field or a “global” variable, it enforces its associated data to be created a single time only, regardless how many times its class might be instantiated later:

Although when using the PDE (Processing’s IDE), it’s not easy to create multiple instances of a sketch running all at once at the same time, it’s still a good convention practice to add static when defining an immutable data, together w/ final and SCREAMING_SNAKE_CASE.

1 Like

I do prefer the solution from you.
This is just the first 7 lines of a data base that will extend, in the end, to 43 entries. I just used the first 7 to simplify matters.

Then could you explain why I can’t read the contents of the array, an only read back six zeros, please.

The solution from @eightohnine does work but this makes the setup function look very turgid when extended to 43 entries.

Wrap the whole bank/chord/note assignment into a function and call it in setup(). And park that long-ass function at the end of the sketch.

If you are using the Processing IDE, then you can even put that function into a separate tab.

byte[][] chordBank2 = new byte[8][6];

void setup() {  
  initializeBanks(); 
}

void draw() {  
  int anotherSingleNote = getNote(7,4); // from the 7th chord, get the 4th note
  println(anotherSingleNote);
  stop();
}

int getNote(int chordNum, int noteNum) {
  return chordBank2[chordNum-1][noteNum-1];
}


void initializeBanks() { 
  chordBank2[0] = new byte[] {40, 45, 50, 55, 59, 64}; // open strings
  chordBank2[1] = new byte[] {40, 47, 52, 56, 59, 64}; // E major
  chordBank2[2] = new byte[] {41, 48, 53, 57, 60, 65}; // F major
  chordBank2[3] = new byte[] {43, 47, 50, 55, 59, 67}; // G major
  chordBank2[4] = new byte[] {33, 45, 52, 57, 61, 64}; // A major
  chordBank2[5] = new byte[] {35, 47, 54, 59, 63, 66}; // B major
  chordBank2[6] = new byte[] {36, 48, 52, 55, 60, 64}; // C major
  chordBank2[7] = new byte[] {38, 50, 57, 62, 66, 69}; // D major 
  
  // add all other banks/chords/notes here... 
}

There is a massive semantic difference between static and final as the references you provide point out.

Now even beginners can grasp the concept of variables and constants and it is good programming practice to mark constants with the appropriate keyword (in Java final) and to use use SCREAMING_SNAKE_CASE for the identifier name. Identifying constants in the code can help reduce the risk of logic errors so is a good habit to get into, even for beginners.

On the other hand static is totally incomprehensible concept to anyone who is unfamiliar with object orientated programming. For that reason I wouldn’t use it in a basic Processing sketch unless there is a real need to do so.

In this sketch there is no significant difference between using

static final byte[][][] CHORD_BANKS = { ...
and
final byte[][][] CHORD_BANKS = { ...

So its a matter of personal choice and my stating a preference was not meant as a criticism of your choice.

2 Likes

1 possible reason is when we declare a local variable or parameter w/ the same name as a field or “global” variable.

When that happens it’s said the local variable is overshadowing the “global” variable.

Make sure you’re not re-declaring existing “global” variables!

2 Likes

This is the exact code I am using can you spot where this is wrong please:-

byte[][] chord_Bank2;
static final byte[][] CHORD_BANK2 = {
  { 40, 45, 50, 55, 59, 64 }, // open strings 0
  { 40, 47, 52, 56, 59, 64 }, // E major      1
  { 41, 48, 53, 57, 60, 65 }, // F major      2                   
  { 43, 47, 50, 55, 59, 67 }, // G major      3
  { 33, 45, 52, 57, 61, 64 }, // A major      4
  { 35, 47, 54, 59, 63, 66 }, // B major      5
  { 36, 48, 52, 55, 60, 64 }, // C major      6
  { 38, 38, 50, 57, 62, 66 }  // D major      7
};

void setup(){
  size(100, 100);
  chord_Bank2 = new byte[5][7]; 
  noLoop();  // Run once and stop
}

void draw(){
  background(125);
  printoutChord(2);
}

void printoutChord(int chord){ // print a chord    
for(int i=0; i<6; i++){    
print(" ",chord_Bank2[chord][i]); 
}
 println();

}
1 Like

You’ve pasted together lines from different sources without making sure that variable names are adapted.

Processing is case-sensitive, so it interprets chord_Bank2 and CHORD_BANK2 as two separate things.

First, you’re creating chord_Bank2 but never filling it. But you’re trying to access its contents and that will lead to an error (indexOutOfBounds or nullPointer).
You’re also creating and filling CHORD_BANK2 but then are never accessing any of its contents.

Pick one variable name and run with it.

1 Like

Doh!
Rookie mistake how embarrassing. Thanks for pointing that out. Functions correctly now. Who do you think sould get the solution?

1 Like

Not seeing the forest for the trees… happens to me every now and then.

I’ll leave you with the heavy, heavy burden of solving this thread. :wink:

Just to clarify your code

byte[][] chord_Bank2;

declares a variable called chord_Bank2 but does not define its contents. At this stage the variable chord_Bank2 has the value null

static final byte[][] CHORD_BANK2 = {
  { 40, 45, 50, 55, 59, 64 }, // open strings 0

This statement not only declares a 2D byte array it also defines the array contents.

Note both these statements are executed before the setup method is called.

chord_Bank2 = new byte[5][7];

This statements defines the contents of a previously declared array. Since the array elements are of type byte (a Java primitive data type) then all the elements take on the default value of zero which is why you got 0’s printed out.

You could change the statement to

chord_Bank2 = CHORD_BANK2;

and it would use the values in CHORD_BANK2.

One word of warning, using the final keyword with CHORD_BANK2 will prevent later statements like

CHORD_BANK2 = new byte[5][7];

being compiled but it does not prevent changes to values stored inside the array. In these statements

chord_Bank2 = CHORD_BANK2; // both reference the SAME array
chord_Bank2[2][3] = 999; // will also change the value in `CHORD_BANK2`

After these statements are executed

println(CHORD_BANK2[2][3]); 

will display 999

3 Likes

Great and detailed write-up here, I learned a lot from it. Especially the explanation of why 0s are created. I have no experience using the byte type.