passive buzzer/Playing it/07. A melody is notes and lengths
Playing it · 07 of 10

A melody is notes and lengths

A tune is a list of pairs, a pitch and a length, played in order. The sketch sounds each note for 90 % of its length and waits out the whole of it, so two equal notes in a row are heard as two. The tune is the opening of Ode to Joy, in the sixth octave, where this buzzer is clearer than an octave down.

Two numbers a note

A melody, to a buzzer, is nothing but pitches and lengths in order. The sketch keeps them as an array of pairs and walks it.

A melody is notes and lengths
Each note sounds for
Note
waiting
tone() gets
nothing
Played
0 of 15
Whole tune
4.8 s
Each note is two numbers: a pitch and a length. The sketch calls tone() for 90 % of the length and waits for all of it, so a short silence falls between every pair of notes. Press Play and watch the start: two E6 notes, with a gap between them.

The pitch is a frequency in hertz, as in the pitch article. The sketch names five of them once, C6 to G6, so the tune reads as notes rather than numbers. The length is in beats, and BEAT_MS turns beats into milliseconds: change that one number and the whole tune speeds up or slows down.

The gap

Each note sounds for 90 % of its length, and the loop waits for all of it. The last 10 % is silence.

That silence is what makes the first two notes, both E6, sound like two notes. Without it the second begins the instant the first ends, at the same pitch, and there is nothing to tell them apart: a buzzer only knows a frequency and whether it is sounding. Switch the figure to All of it and the pairs merge.

Why the sixth octave

The same tune could start on E5, at 659 Hz. On this buzzer's datasheet curve the fifth octave, 523 to 988 Hz, sits 15 to 20 dB under its 2.7 kHz peak. The sixth, where this tune falls between 1047 and 1568 Hz, is on average a few decibels nearer, with small bumps and dips along the way. The curve is read by eye, so the numbers are rough; the direction is what matters. A small buzzer sounds best high up, and a melody written for a piano usually wants moving up an octave or two.

Both boards, one sketch

tone() behaves slightly differently on an Uno and an ESP32. On an Uno a new call replaces the note that is playing; on an ESP32 a call with a length waits behind the one before it. This sketch never calls tone() while a note is still sounding, because the delay() waits out each one, so it plays the same on both. What tone() borrows has the rest of the differences.

An earlier version of this block, with a round buzzer, on a Raspberry Pi Pico. The block you have carries the square buzzer and wires the same way.

The code

tk37_melody.ino

No library. The tune is an array of notes, each a frequency and a length in beats; the loop walks it, calls tone() for 90 % of each note and delay() for all of it.

// TK37 Passive Buzzer: a melody, on an ESP32-S3.
//
// Wiring, TK37 header left to right (parts up, pins down):
//   GND    -> ESP32 GND
//   VCC    -> ESP32 3V3   (5V works too, and is louder)
//   NC     -> nothing
//   SIGNAL -> GPIO4
//
// Arduino IDE: Tools > Board > esp32 > ESP32S3 Dev Module,
// Tools > USB CDC On Boot > Enabled, then Tools > Port.
// No library needed. Serial Monitor at 115200.

// The pin SIGNAL is wired to. Uno: 9. ESP32: 4. Pico: 15.
const int BUZZER_PIN = 4;

// Notes in the sixth octave, in Hz (A4 = 440).
const int C6 = 1047, D6 = 1175, E6 = 1319, F6 = 1397, G6 = 1568;

struct Note {
  int hz;       // the pitch
  float beats;  // the length
};

// Ode to Joy, the opening line.
const Note TUNE[] = {
  {E6, 1}, {E6, 1}, {F6, 1}, {G6, 1},
  {G6, 1}, {F6, 1}, {E6, 1}, {D6, 1},
  {C6, 1}, {C6, 1}, {D6, 1}, {E6, 1},
  {E6, 1.5}, {D6, 0.5}, {D6, 2},
};

const int BEAT_MS = 300;  // one beat: change it for the tempo

void setup() {
  Serial.begin(115200);
}

void loop() {
  for (const Note &n : TUNE) {
    int ms = n.beats * BEAT_MS;
    tone(BUZZER_PIN, n.hz, ms * 9 / 10);  // sound for 90 %
    delay(ms);                            // the rest is the gap
  }
  Serial.println("again in 2 s");
  delay(2000);
}

To play another tune, change the list. To change the tempo, change BEAT_MS. The same sketch plays on an Uno with BUZZER_PIN set to 9: there a new tone() replaces the note playing, and on an ESP32 it would queue behind it, but the delay() waits out each note on both, so neither difference is heard.

When it does not work

Repeated notes run together.

Each tone() is lasting the whole length of its note, so there is no silence between two equal ones. Keep the length passed to tone() shorter than the delay() after it; 90 % is a good start, and 80 % sounds more detached.

The tune is recognisable but thin and quiet.

It is probably written in the fourth or fifth octave. Double every frequency to move it up an octave; this buzzer is louder and clearer from about 1 kHz up, and loudest at 2.7 kHz.

Notes come out in the wrong rhythm.

Check that the delay() uses the note's own length, not a fixed number, and that the lengths add up the way the tune does: a dotted note is 1.5 beats, the note after it 0.5. Print n.hz and ms to the Serial Monitor to see what is actually played.

The melody stops everything else in my sketch.

delay() holds the loop for the whole tune. To play a tune while the loop keeps working, keep the index of the current note and the time it started, and check millis() each time round the loop instead of waiting.

Where this goes next

The only volume control a buzzer has, and why 255 is not the loudest setting.

Volume is the duty cycle

Edit this page — content/books/passive-buzzer/a-melody-is-notes-and-lengths.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Passive Buzzer

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum