ArduboyBeep: low overhead tone generation

Discussions in the Dark&Under topic and others, about wishing to squeeze some form of sound into large games, got me thinking about what it would take to generate sounds with minimum code size and CPU usage. Basically, my idea was:

  • Both of the (production) Arduboy speaker pins are able to be connected to a timer output. This means the timer(s) could be configured in a “set and forget” way to start generating a given tone. Once a tone was started, no CPU usage would be required.
  • Most games use the library’s nextFrame() function to run at a fixed frame rate. We could take advantage of this to count a given number of frames after starting a tone to determine when to stop it, in order to play a tone for a desired amount of time. This would eliminate the need for an interrupt driven (or other separate) technique to time the duration of a tone in the background.
  • The frequency of a tone is determined by the count loaded into the timer. Converting from frequency to a count requires division, which produces a fair amount of code. If we were to pre-calculate the count values, either manually or at compile time, conversion code wouldn’t be required in the sketch.

Based on these points, I’ve written a set of functions, enclosed as classes for convenience. The tones generated are simple square waves, just like the ArduboyTones and ArduboyPlaytune libraries produce.

I intend to include this in the next release of the Arduboy2 library but for anyone wishing to use it now, you can just include the files locally. Here’s a brief description of what to do, based on using the Arduboy2 library:

Edit: As of release version 5.0.0 the BeepPin classes have been included in the Arduboy2 library.

  • Put copies of the ArduboyBeep.h and ArduboyBeep.cpp files in your sketch’s directory. The code is small enough that I’ve just included it as code sections at the end of this post. You can copy and paste to create the files.

  • Add #include "ArduboyBeep.h" to your sketch.

  • There are two classes: BeepPin1 and BeepPin2. They both contain the same functions but each controls either speaker pin 1 or speaker pin 2. Using BeepPin1 is more desirable, as its 16 bit Timer 3 can produce a greater frequency range and resolution than the 10 bit Timer 4 used by BeepPin2. However, if you also wanted to include other sound generating code (such as ArduboyTones), which uses speaker pin 1, you could use BeepPin2 to avoid conflict. Also, it’s actually possible to use both ArduboyBeep classes (BeepPin1 and BeepPin2) in the same sketch, to play two tones at the same time (at the expense of producing more code).

  • All of the functions are static members of their class so you don’t have to create an instance of the class if you don’t want to. However, using an instance doesn’t produce any more code and I find it easier, so that’s how I’ll describe things.

  • (Optionally) create an instance of the class at the same place you create the Arduboy2 object:
    Arduboy2 arduboy;
    BeepPin1 beep;

  • In setup(), use the begin() function to initialise the hardware:
    beep.begin();
    (or without an object instance):
    BeepPin1::begin();

  • In loop(), add the timer() function somewhere after the nextFrame() test, so it will be called once per frame. The class variable duration will be set when a tone is started. timer() decrements duration and stops the tone when the count reaches 0. Therefore durations are specified as the “number of frames to sound the tone”, and will be dependent on the frame rate. For example, if you wanted a half second tone and the frame rate is 60 FPS, your duration would be 30. Note that, for efficiency, duration is only 8 bits wide, so its maximum value is 255 (which would allow a 4.25 second tone at 60 FPS).

void loop() {
  if (!arduboy.nextFrame()) {
    return;
  }

  beep.timer();

  // ...
  • To generate a tone use tone(), giving the count to be loaded into the timer, and the duration in frames. For a desired frequency, calculating the count that you must provide is different depending on which speaker pin is used.
    For BeepPin1:
    count = 1000000 / Hz - 1
    For BeepPin2:
    count = 62500 / Hz - 1
    (rounded to the nearest unsigned 16 bit integer)
    For convenience, the freq() function is provided, which will do the conversion. It is intended to only be used with a constant parameter, so evaluation will take place at compile time. If you provide it with a variable value, code to do floating point math will be added, taking up a large amount of space, thus defeating the purpose. At 60 FPS:
    beep.tone(beep.freq(1000), 15);
    will play a 1000Hz tone for 1/4 second.

  • You can also call the tone() function with only a count parameter, in which case it will play the tone until replaced by another tone() call, or stopped with the provided noTone() function. You can use this to control durations by some means other than timer(). It’s safe to call noTone() even if a tone isn’t playing. There’s also no need to call noTone() before calling tone() again while a tone is playing.

  • The duration variable used by timer() is publicly accessible. You can write and read it directly at any time. You can determine if a timed tone is still playing by testing if duration is non-zero. E.g.:

  if (beep.duration) {
    // do something if a tone is playing`
  }

Here’s a basic example sketch:

#include <Arduboy2.h>
#include "ArduboyBeep.h" // (local) "beep" classes

Arduboy2 arduboy;
BeepPin1 beep; // class instance for speaker pin 1

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(60);
  beep.begin(); // initialise the hardware
}

void loop() {
  if (!arduboy.nextFrame()) {
    return;
  }

  beep.timer(); // handle tone duration
  
  arduboy.pollButtons();

  if (arduboy.justPressed(A_BUTTON)) {
    beep.tone(beep.freq(1000), 120); // 1000Hz for 2 seconds
  }
  if (arduboy.justPressed(B_BUTTON)) {
    beep.noTone(); // stop a tone if one is playing
  }
}

Copy and paste the following to a file named ArduboyBeep.h

/**
 * @file ArduboyBeep.h
 * \brief
 * Classes to generate simple square wave tones on the Arduboy speaker pins.
 */

#ifndef ARDUBOY_BEEP_H
#define ARDUBOY_BEEP_H

class BeepPin1
{
 public:

  static uint8_t duration;

  static void begin();

  static void tone(uint16_t count, uint8_t dur);

  static void tone(uint16_t count);

  static void timer();

  static void noTone();

  static constexpr uint16_t freq(const float hz)
  {
    return (uint16_t) (((F_CPU / 8 / 2) + (hz / 2)) / hz) - 1;
  }
};

class BeepPin2
{
 public:

  static uint8_t duration;

  static void begin();

  static void tone(uint16_t count, uint8_t dur);

  static void tone(uint16_t count);

  static void timer();

  static void noTone();

  static constexpr uint16_t freq(const float hz)
  {
    return (uint16_t) (((F_CPU / 128 / 2) + (hz / 2)) / hz) - 1;
  }
};

#endif

Copy and paste the following to a file named ArduboyBeep.cpp

/**
 * @file ArduboyBeep.cpp
 * \brief
 * Classes to generate simple square wave tones on the Arduboy speaker pins.
 */

#include <Arduino.h>
#include "ArduboyBeep.h"

// Speaker pin 1, Timer 3A, Port C bit 6, Arduino pin 5

uint8_t BeepPin1::duration = 0;

void BeepPin1::begin()
{
  TCCR3A = 0;
  TCCR3B = (bit(WGM32) | bit(CS31)); // CTC mode. Divide by 8 clock prescale
}

void BeepPin1::tone(uint16_t count, uint8_t dur)
{
  duration = dur;
  tone(count);
}

void BeepPin1::tone(uint16_t count)
{
  TCCR3A = bit(COM3A0); // set toggle on compare mode (which connects the pin)
  OCR3A = count; // load the count (16 bits), which determines the frequency
}

void BeepPin1::timer()
{
  if (duration && --duration == 0) {
    noTone();
  }
}

void BeepPin1::noTone()
{
  TCCR3A = 0; // set normal mode (which disconnects the pin)
}


// Speaker pin 2, Timer 4A, Port C bit 7, Arduino pin 13

uint8_t BeepPin2::duration = 0;

void BeepPin2::begin()
{
  TCCR4A = 0; // normal mode. Disable PWM
  TCCR4B = bit(CS43); // Divide by 128 clock prescale
  TCCR4D = 0; // normal mode
  TC4H = 0;  // toggle pin at count = 0
  OCR4A = 0; //  "
}

void BeepPin2::tone(uint16_t count, uint8_t dur)
{
  duration = dur;
  tone(count);
}

void BeepPin2::tone(uint16_t count)
{
  TCCR4A = bit(COM4A0); // set toggle on compare mode (which connects the pin)
  TC4H = highByte(count); // load the count (10 bits),
  OCR4C = lowByte(count); //  which determines the frequency
}

void BeepPin2::timer()
{
  if (duration && --duration == 0) {
    noTone();
  }
}

void BeepPin2::noTone()
{
  TCCR4A = 0; // set normal mode (which disconnects the pin)
}
4 Likes

Looks great Scott and just right for simple sound effects in games.

What does it add to the compilation in terms of size?

As a test of the ArduboyBeep functions, I’ve forked @filmote’s Steve sketch from the tutorials and added sounds for jumping, ducking and game over. The code (for 3 sounds) adds 108 bytes (not including the arduboy.audio.begin() call and anything required to mute the sound, such as arduboy.systemButtons(), which would be required for any sound library).

Excellent. Nearly every sketch can accomodate 108 bytes!

Maybe we can add an extra article on the end of the tutorials showing the various sound options?

2 Likes

This is a very nice idea, well done! I am wondering if the two timers could be setup to run at the same rate so the freq->count constants could be used interchangeably for both timers. Also the API could do something like beep = give_me_a_beep() to use timers in a round robin fashion. Then the game can re-use the same beep when it needs to change its pitch mid-effect and get a new one on SFX start.

1 Like

It can be done but the problem is getting the best frequency range and precision from each timer. Timer 3, on pin 1, is 16 bits. Timer 4, on pin 2 is only 10 bits. In order to run at the same rate, the same clock prescaling divider value must be used. Each timer has a different set of prescaler options available but Timer 4 can do every one that Timer 3 has available, and then some. The matching values available are 1, 8, 64, 256 and 1024. Another thing to note is that, in addition to being only 10 bits, the minimum compare count that can be loaded into Timer 4 is 3.

From this we can calculate the minimum and maximum frequencies that could be produced with each prescale setting, given the 16MHz system I/O clock frequency and that one frequency cycle requires 2 count periods.

Using the formula:
frequency = clock / prescale / 2 / (count + 1)
where:
Timer 3 count is min 0, max 65535
Timer 4 count is min 3, max 1023

Timer Prescale Min Max
3 1 122.07 8000000
4 1 7812.5 2000000
3 8 15.259 1000000
4 8 976.56 250000
3 64 1.9073 125000
4 64 122.07 31250
3 256 0.4768 31250
4 256 30.518 7812.5
3 1024 0.1192 7812.5
4 1024 7.6294 1953.1

Ideally, you want to choose the highest acceptable lowest frequency so that you have better possible precision for all frequencies, especially the high ones. Personally, I wanted to be able to go down to at least 60Hz. Even though the speaker can’t really go this low, the overtones from the square wave give some interesting raspy sounds at lower frequencies.

This would mean a presale value of 256 to allow about 30Hz on Timer 4. However the maximum for Timer 4 would then be only 7812Hz, so you wouldn’t get much precision for high frequencies. The only other reasonable choice is 64, which would only allow going down to 122Hz.

The main goal of these functions is to generate sounds with minimum code overhead, and for this the assumption is that only one timer/pin will be used. In this case, using a different prescaler for each timer isn’t an issue and allows the best frequency range for each.

For Timer 4, a divide by 128 prescale is also available, which gives a low frequency of 61.035Hz. I felt this was the best value to use. For Timer 3, a prescale of 8 makes the most sense.

However, there’s no reason you can’t modify a local copy of the code to use the same prescaler for both timers (and change the freq() functions to match), provided that the resulting available frequencies are acceptable for your purposes.

What I’ve provided is meant to be lean, efficient and reasonably easy to understand and use. You can easily build upon this functionality to implement the kind of API you’ve proposed, in your own sketch code.

1 Like

Thank you for the reply.

Sure. I was just throwing the idea out there because I think, from the point of view of the application (game) developer, not having to hardwire which timer reproduces a certain SFX is a big bonus so it was worth a few extra bytes. I hope to code that up sometimes soon :slight_smile:

Again, the assumption is that these functions will be used when the developer needs most of the available code space for game play, with a minimal amount dedicated to producing simple sounds. In this case, only one timer would be used.

However, if you come up with something that would be of general benefit, is straighforward and might be frequently used, I would consider including it in the library.

Ack, I’ll get in touch if I come up with something :slight_smile:

is it possible to use pin1 and pin2 to beep a tone on the separate pins at the same time?

You missed reading some of the original post.

Not exactly- I interpreted it as I can use Tones + Beep at the same time.

I don’t know how to sound a tone from a specific pin would it be alone the lines of

BeepPin1 beep;
BeepPin2 something;
beep.tone(beep.freq(1000), 120);
beep.something(beep.freq(1000), 120);

or

beep.tone(beep.freq(1000), 120);
beep.tone(something.freq(1000), 120);

Sorry. Yes, I now see how it could be misinterpreted. I should have made it clearer:

Also, it’s actually possible to use both ArduboyBeep classes (BeepPin1 and BeepPin2) in the same sketch, to play two tones at the same time (at the expense of producing more code).

Neither.

BeepPin1 beep;
BeepPin2 something;

beep.tone(beep.freq(1000), 120);
something.tone(something.freq(1000), 120);

But better names for the objects may make it clearer

BeepPin1 beep1;
BeepPin2 beep2;

beep1.tone(beep1.freq(1000), 120);
beep2.tone(beep2.freq(1000), 120);

Note that if you play the exact same frequency on both pins, you will get a single tone at higher volume, no sound, or some volume in between, just like playing the same frequency on two speakers either in phase or out of phase. (You’ll have to try it to see exactly what happens.)

1 Like

Thanks for clarifying Scott.
I knew the answer was staring me in the face but it just wasn’t clicking.

I noticed when testing this on Humnity Revenge @60 frames I’m having to use really short durations of 5 or less to get the rapid “pip pip pip” effect for bullet shots but so far the results sound pretty good.

1 Like

At 60 FPS a frame takes about 16ms, so that’s the shortest duration possible. At that frame rate, a duration of 5 would produce a tone for 80ms.

1 Like

Here’s the results for comparison between Tones, BeepPin1 and BeepPin1 + BeepPin2 tested with Humanity Revenge DC

ArduboyTones

Sketch uses 28536 bytes (99%) of program storage space. Maximum is 28672 bytes.
Global variables use 1595 bytes (62%) of dynamic memory, leaving 965 bytes for local variables. Maximum is 2560 bytes.

ArduboyBeep Pin 1

Sketch uses 27886 bytes (97%) of program storage space. Maximum is 28672 bytes.
Global variables use 1567 bytes (61%) of dynamic memory, leaving 993 bytes for local variables. Maximum is 2560 bytes.

ArduboyBeep Pin1 + Pin2

Sketch uses 27886 bytes (97%) of program storage space. Maximum is 28672 bytes.
Global variables use 1567 bytes (61%) of dynamic memory, leaving 993 bytes for local variables. Maximum is 2560 bytes.

When using BeepPin1 enemy hits can only be heard in between players shots.

By assigning the menu sounds and player shots to BeepPin1 and all other in game sounds to BeepPin2 I am able to hear enemy hits along with player shots at the same time.

This will likely be obvious to most but to assist anyone else I found with help of the ide nagging me I also had to add a .begin() for each class aswell as individual timers in the main loop.

#include "Arduboy2.h"
//#include "ArduboyTones.h"
#include "ArduboyBeep.h"

const float pi = 3.141593;
Arduboy2Base arduboy;
//ArduboyTones sound(arduboy.audio.enabled);
BeepPin1 beep1; // class instance for speaker pin 
BeepPin2 beep2;
// game states
#define MENU_SCREEN           0
#define SHIP_SELECTION        1
#define GAMING                2
#define INTRO                 3
#define HELP                  4
#define GAME_OVER             5
#define VICTORY               6
#define HIGH_SCORE            7

byte gamestate = MENU_SCREEN;

#include "globals.h"
#include "font.h"
#include "sprites.h"
#include "menu.h"
#include "shipweapon.h"
#include "bomb.h"
#include "ship.h"
#include "levels.h"
#include "background.h"
#include "enemy.h"
#include "boss.h"
#include "enemymanager.h"
#include "explosions.h"
#include "items.h"
#include "highscore.h"
#include "game.h"

Game game;

void setup() {
  // put your setup code here, to run once:
  arduboy.boot();
// arduboy.setFrameRate(60);
#ifdef EEPROM_ENABLED
  setHrDcEEPROM();
#endif
#ifdef SCREEN_MIRRORING
  Serial.begin(9600);
#endif
beep1.begin(); // initialise the hardware
beep2.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  if (!arduboy.nextFrame())
    return;
  beep1.timer(); // handle tone duration
  beep2.timer();

By using this library I was also able to just cram arduboy.begin() in instead of arduboy.boot() using both pin classes and actually keep the code reduced.

Sketch uses 28464 bytes (99%) of program storage space. Maximum is 28672 bytes.
Global variables use 1567 bytes (61%) of dynamic memory, leaving 993 bytes for local variables. Maximum is 2560 bytes.

Unless background music is required this library really should be the first option before using Tones for basic sound effects

1 Like

These requirements are both spelled out in the instructions (and in the provided example) in the original post:

The only point of confusion may be that you have to do it individually for each pin. But as you infer, it’s obvious if you understand that each class (BeepPin1, BeepPin2) is totally independent of the other.

And that’s why I’ll be including it as part of the Arduboy2 library in the next release.

Even if background music is required, you could use ArduboyBeep (BeepPin2 only) along with ArdbuoyTones, instead of using ArduboyPlaytune. This would save space in many (most?) circumstances, with the down side being that your music could only be single channel monotonic instead of two channel polyphonic.

1 Like

As I said it will be obvious to most but not necessarily for those like me that have little to no programming experience, the simpler things may not always be noticeable. I find it best and courteous to share my findings to assist others whom may run into the same issue even if may be trivial and a slight cause of irritation to the experienced user, I apologise if this is the case.

This is actually my next route of tinkering…

One of the abilities I like about this library Is being able to jump from pin to pin and possibly avoid conflicts with other timers being used especially those used by the RGB LED that I feel are under-utilised.

Anyways i’ll refrain from delving too much into the chitter chatter risking taking the topic too much off in a tangent as I often do in my ramblings.

1 Like

No apology necessary. It’s fine if you wish to point out things that you have trouble with. I’ll try to provide more information on using both pins at the same time, when I document ArduboyBeep in the Arduboy2 library.

I believe ArduboyPlaytune is the only library that causes problems with the RGB LED because it uses Timer 1. The RGB LED uses Timer 1 for red and blue, and Timer 0 for green. All other sound libraries that I’m aware of only use Timers 3 and/or 4.

Also, ArduboyPlaytune should only cause problems when using the RGB LED in analog mode using setRGBled(). Using digitalWriteRGB() should always work fine (but being digital, it can only set each LED either fully on or fully off).

1 Like