Skip to content
Ahmed Dabak edited this page Jul 24, 2026 · 1 revision

Voice

Introduction

Voice drives the piezo buzzer. It is a facade over the Buzzer and Melody modules.

Voice::play(Sound::Happy);

Every sound call is blocking: Otto finishes the sound before the next line runs.

Playing built-in sounds

Voice::play(Sound::Hello);
Voice::play(Sound::Surprise);
Voice::play(Sound::Sad);

Nineteen sounds ship with the framework -- the built-in OttoDIYLib songs. The complete list is in the Icon, Sound and Gesture Reference. The ones you will reach for most:

Sound When
Sound::Hello Boot, connection, greeting
Sound::Goodbye Shutdown, disconnection
Sound::Happy, Sound::SuperHappy, Sound::HappyShort Success, praise
Sound::Sad, Sound::Confused Failure, "I do not understand"
Sound::Surprise, Sound::OhOoh Obstacle, being picked up
Sound::ButtonPushed Acknowledging input

Raw tones

Voice::playToneHz(440, 200);         // A4 for 200 ms
Voice::playToneHz(880, 100, 50);     // then 50 ms of silence afterwards

The third argument is a pause added after the note, which is what makes two consecutive beeps sound like two beeps instead of one long one:

for (int i = 0; i < 3; i++) {
  Voice::playToneHz(1200, 80, 60);
}

Melodies

Give playMelodyHz() two parallel tables -- note frequencies in Hz and beat lengths -- plus the number of notes and the duration of one beat:

static const uint16_t NOTES[] PROGMEM = {392, 392, 440, 392, 523, 494};
static const uint8_t  BEATS[] PROGMEM = {1,   1,   2,   2,   2,   4};

Voice::playMelodyHz(NOTES, BEATS, 6, 150);   // 150 ms per beat

A note frequency of 0 is a rest: silence for that beat. Storing the tables in PROGMEM keeps them in flash instead of the Nano's 2 KB of RAM.

One tune is built in, matching the official Otto example:

Voice::playHappyBirthday();

Muting

Voice::mute();
Voice::unmute();
bool quiet = Voice::isMuted();

While muted, every sound call is a silent no-op -- nothing errors, nothing is queued, and nothing plays later. Your sketch does not need if (soundEnabled) branches.

This is half of the bench-testing setup:

Motion::disable();   // no movement
Voice::mute();       // no noise

See Bench Testing.

To start muted, set it in the configuration instead of muting in setup():

OttoConfig cfg;
cfg.buzzer.muted = true;
OttoFlow::start(cfg);

The startup chirp

OttoFlow::start() plays a short connection sound when it finishes, so you know the firmware booted. Turn it off with:

OttoConfig cfg;
cfg.buzzer.helloOnStart = false;
OttoFlow::start(cfg);

A muted buzzer suppresses it too.

Going lower: Buzzer and Melody

The Buzzer module adds a frequency sweep the facade does not expose:

Buzzer::bendTonesHz(200, 800, 1.05, 10, 1);   // a rising "wheeeee"

The arguments are start frequency, end frequency, multiplicative step, duration of each step in milliseconds, and the silence between steps. A step above 1.0 sweeps up, below 1.0 sweeps down -- and the start and end must agree with that direction, or the sweep never terminates.

Clone this wiki locally