Skip to main content

Raspberry Pi Pico Piano – Part 2

Referring to the future development of the previous article -- "Raspberry Pi Pico Piano – Part 1", I hope to build a piano that can press multiple notes simultaneously. In this sharing, I will share how to make a piano that can play two notes simultaneously.

Raspberry Pi Pico Piano

In this sharing, I will only focus on how to make a piano that can play two notes simultaneously in one octave. If you want to know how to make it on an 88-key piano, you can go back to “Raspberry Pi Pico Piano”, which simply shares how to make an 88-key piano. In order for the piano to play two notes at the same time, this is demonstrated in the video below, on how to make the buzzer have two sounds.

The circuit and the connection you can also refer to "Raspberry Pi Pico Piano – Part 1".

The difference with “Raspberry Pi Pico Piano – Part 1”, is the tones.h library isn’t defining the notes in frequency. I define notes in units of time. The reason why I defined in units of time I will explain later. To covert the notes in units of frequency to time is simple. Take an example on Notes “A4”, f=1/t, 440=1/t, t=2273us. Then, we can first convert all notes into units of time.

// TONES  ==========================================
#define NC4 3822
#define NCS4 3608
#define ND4 3405
#define NDS4 3214
#define NE4 3034
#define NF4 2863
#define NFS4 2703
#define NG4 2551
#define NGS4 2408
#define NA4 2273
#define NAS4 2145
#define NB4 2025

pinMode is set the Arduino digital pin as output or input

#define BUZZER1 12
#define BUZZER2 15

#define TEMPO 3500
#define PAUSE 100     // length of pause between notes

const int c = 11;
const int cs = 10;
const int d = 9;
const int ds = 8;
const int e = 7;
const int f = 6;
const int fs = 5;
const int g = 4;
const int gs = 3;
const int a = 2;
const int as = 1;
const int b = 0;

void setup() { 
  pinMode(BUZZER1, OUTPUT);
  pinMode(BUZZER2, OUTPUT);

  pinMode(c, INPUT);
  digitalWrite(c,HIGH);

  pinMode(cs, INPUT);
  digitalWrite(cs,HIGH);
  
  pinMode(d, INPUT);
  digitalWrite(d,HIGH);

  pinMode(ds, INPUT);
  digitalWrite(ds,HIGH);

  pinMode(e, INPUT);
  digitalWrite(e,HIGH);
  
  pinMode(f, INPUT);
  digitalWrite(f,HIGH);

  pinMode(fs, INPUT);
  digitalWrite(fs,HIGH);
  
  pinMode(g, INPUT);
  digitalWrite(g,HIGH);

  pinMode(gs, INPUT);
  digitalWrite(gs,HIGH);
  
  pinMode(a, INPUT);
  digitalWrite(a,HIGH);

  pinMode(as, INPUT);
  digitalWrite(as,HIGH);
  
  pinMode(b, INPUT);
  digitalWrite(b,HIGH);
}

After we get all notes in the units of time, we can start to do the function for playing two notes simultaneously.

The idea is to switch the voltage of the buzzer. First, we initialize the buzzer in the off phase. Then, check if tone1 and tone2 stages are greater than 1. The tone1 and tone2 stages are the time-based notes we mentioned above. After checking the tone stage, check whether the duration is larger than 0. The duration stage will be set in the main function. After that, output the PWM for the buzzer.

void play2Tones(long tone1, long tone2, long duration) {
  byte s1, s2;    // state of the buzzers
  long sum1, sum2, cur, next, n1, n2;
  cur = next = sum1 = sum2 = 0;

  // Init buzzers
  s1 = s2 = LOW;
  digitalWrite(BUZZER1, s1);
  digitalWrite(BUZZER2, s2);

  if (tone1 > 0 && tone2 > 0) { 
    duration -= PAUSE;
    tone1 = tone1 << 1
    tone2 = tone2 << 1
    while (cur < duration) {
      next = min(min(sum1 + tone1, sum2 + tone2), duration);
      delayMicroseconds(next-cur);
      if(sum1 + tone1 == next) {
        if(s1 == HIGH){
            s1 = LOW;
        }else{
          s1 = HIGH;  
        }
        digitalWrite(BUZZER1, s1);
        Serial.println(s1);
        sum1 += tone1;
      }
      if(sum2 + tone2 == next) {
        if(s2 == HIGH){
            s2 = LOW;
        }else{
          s2 = HIGH;  
        }
        digitalWrite(BUZZER2, s2);
        Serial.println(s2);
        sum2 += tone2;
      }
      cur = next;
    } 
    delayMicroseconds(PAUSE);
  }
}

After making the piano play two notes simultaneously, we can move on to the main function.

First, we know that in one Octave, there have 12 Notes (i.e., beginning with C to B). Therefore, there has a (12C1 = 12) combination for two buzzers.

Take the example on the Note base on “C4”, there have “C4 & C4”, “C4 & CS4”, “C4 & D4”, “C4 & DS4”, and so on. And then, we set the duration for each case.

//C C
  if(digitalRead(c) == HIGH && digitalRead(cs) == LOW && digitalRead(d) == LOW 
    && digitalRead(ds) == LOW && digitalRead(e) == LOW && digitalRead(f) == LOW 
    && digitalRead(fs) == LOW && digitalRead(g) == LOW  && digitalRead(gs) == LOW  
    && digitalRead(a) == LOW  && digitalRead(as) == LOW && digitalRead(b) == LOW)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NC4, beats1[i] * 4 * TEMPO);
    }
  }
//C CS
  if(digitalRead(c) == HIGH && digitalRead(cs) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NCS4, beats1[i] * 4 * TEMPO);
    }
  }
//C D
  if(digitalRead(c) == HIGH && digitalRead(d) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,ND4, beats1[i] * 4 * TEMPO);
    }
  }
//C DS
  if(digitalRead(c) == HIGH && digitalRead(ds) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NDS4, beats1[i] * 4 * TEMPO);
    }
  }
//C E
  if(digitalRead(c) == HIGH && digitalRead(e) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NE4, beats1[i] * 4 * TEMPO);
    }
  }
//C F
  if(digitalRead(c) == HIGH && digitalRead(f) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NF4, beats1[i] * 4 * TEMPO);
    }
  }
//C FS
  if(digitalRead(c) == HIGH && digitalRead(fs) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NFS4, beats1[i] * 4 * TEMPO);
    }
  }
//C G
  if(digitalRead(c) == HIGH && digitalRead(g) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NG4, beats1[i] * 4 * TEMPO);
    }
  }
//C GS
  if(digitalRead(c) == HIGH && digitalRead(gs) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NGS4, beats1[i] * 4 * TEMPO);
    }
  }
//C A
  if(digitalRead(c) == HIGH && digitalRead(a) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NA4, beats1[i] * 4 * TEMPO);
    }
  }
//C AS
  if(digitalRead(c) == HIGH && digitalRead(as) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NAS4, beats1[i] * 4 * TEMPO);
    }
  }
//C B
  if(digitalRead(c) == HIGH && digitalRead(b) == HIGH)
  {
    int n = sizeof(beats1) / 2;
    for (int i=0; i<n; i++) {
      play2Tones(NC4,NB4, beats1[i] * 4 * TEMPO);
    }
  }

For the remaining Notes (i.e. CS4, D4, DS4, ..., B4), we are doing similar things.

Then, in part 3, I will deeply focus on the hardware part, which is about how the PWM is generated and how to make the sound better.

JulianWong has not written a bio yet…
DesignSpark Electrical Logolinkedin