Music on Arduino

Music on Arduino

A Sketch for Arduino UNO that plays music in PLAY Qbasic command format

Once upon a time, in the 80s of the 20th century, the first 8-bit computers got the gift to play music. Music was programmmed on BASIC interpreters. In tat time the "computer" music was seemed very original and unusual. Now, when computers, phones, watches, toys and even greeting cards are clogged with MP3 music and polyphonic MIDI, is there any reason to buy a microcontroller that is significantly more expensive than the finished Chinese toy, and then try to program a melody by myself? However, the programming of music play when studying the microcontroller is the same "must be forever", like the unforgettable LED blinky programming.

Most examples that I found on the Web are too primitive or too complex and non-working, so I wrote my own music program. My article describes a program that uses the same note format as the  PLAY statement from QBasic. Thus, you can use the old BAS archive files or type and debug music in Qbasic, and then transfer the finished lines to the program (sketch) for Arduino.

There is one little problem. QBasic normally runs under Windows-98 and Windows-XP, but for later Windows versions and for XP it's better to run QBasic using the DOSBox emulator

In order to force the 8-bit Arduino microcontroller to play music, there is no complicated additional electronics is required. It is enough to take a piezo-pinch and connect it with one pin to the GND (Ground) terminal of the controller board, and the other to a free digital output (in this example, pin 8). If you want to use a low-impedance speaker, you must connect it via a 100 ohm resistor (see the figure) to avoid current overloading of the processor. All connections must be made naturally with the power off. The Arduino UNO board can be powered from either an external power source or via USB, and the power source selection is automatic. To power the circuit with the piezo-beeper it's enough to simply connect the board with a cable to the USB of the computer.




QBasic PLAY statement looks as follows:


PLAY "T120o3L8CDEFGGL16GFL8EFFL16FEDL8CDG4"

that means

T120 - the rate of 120 beats per minute
o3 - octave 3
L8 - default note length - eighths
Next are the codes of notes, and after the code of the note can go symbols + and - (sharp and flat), note duration or dot ("."). A full description of the PLAY statement is as follows:


PLAY commandstring$

commandstring$  A string expression that contains one or more of
                the following PLAY commands:

(PREFIXES)
Octave and tone commands:
Ooctave Sets the current octave (0 - 6).
<    Moves up one octave.
>    Moves down one octave.

Duration and tempo commands:
Llength Sets the length of each note (1 - 64). L1 is whole note,
        L2 is a half note, etc.
ML      Sets music legato.
        (Each note plays the full)
MN      Sets music normal.
        (Each note plays 7/8 of the time, then pause 1/8 time)
MS      Sets music staccato.
        (Each note plays 3/4 of the time, then pause 1/4 time)
Ttempo  Sets the tempo in quarter notes per minute (32 - 255).
        Default T120.

(NOTES)
A - G   Plays the specified note in the current octave.
        "CDEFGAB" - do,re,mi,fa,sol,la,si
Ppause  Specifies a pause (1 - 64). P1 is a whole-note pause,
        P2 is a half-note pause, etc.
Nnote   NOT SUPPORTED by my program.

(SUFFIXES)
Suffix commands:
# or +  Turns preceding note into a sharp.
        (sound a half tone higher)
-       Turns preceding note into a flat.
        (sound a half tone lower)
.       Plays the preceding note 3/2 as long as specified.

See also PLAY Statement

It seems, that to perform such a lot of of commands we need to write a very complex program. Not at all! In fact, the program (the source .ino) was only slightly longer than 3 KB. Definitions and global variables at the beginning of the program:


//PIN of beeper
#define SPEAKER 8
//PIN of onboard LED
#define LED 13
//to avoid mistakes: "eq" is better read and type then "=="
#define eq  ==
//a - digit ?
#define isdigit(a) (((a)>='0') && ((a)<='9'))
//a - lowercase letter ?
#define islower(a) (((a)>='a') && ((a)<='z'))
char octave=0;
char lendef=1;
char flagdiez=0;
int tempo=120;
char playmode='N'; 

Since the microcontroller is 8-bit one, we use, where possible, variables of the type char and unsigned char (to reduce the binary code of the program), but this one-byte type must be carefully monitored to avoid overflows and incorrect type conversions.

We come to the music. Music strings (in fact one long string) are stored in an array notes [] . For clarity music data strings are best stored in .h files. You just take the PLAY lines from the .BAS Qbasic program and copy them to a .h file. Do not forget that the music in this example should be declared just ones, all the others #include operators you need to comment out.

//Select music array notes[]. Comment all other include strings
//char notes[]="T120MSo3L8CDEFGGL16GFL8EFFL16FEDL8CDG4";
//#include "music.h"
//#include "jingle.h"
//#include "merry.h"
#include "silent.h"
//#include "bach1.h"
//#include "inventio.h"

There are two small functions for sound output playTone and playNote :


//hardware play tone itself. tone is a delay in microseconds
//(not frequancy),
//duration is a sound length in milliseconds
void playTone(int tone,int duration)
{digitalWrite(LED,HIGH)
for (unsigned long i=0;i < duration*1000L;i+=(tone*2))
  {digitalWrite(SPEAKER,HIGH);
   delayMicroseconds(tone);
   digitalWrite(SPEAKER,LOW);
   delayMicroseconds(tone);
  }
digitalWrite(LED,LOW)
}

//converts note letter to tone,if correct calls playTone
void playNote(char note,int duration)
{char names[]="CDEFGAB";
int tones[]={
//note tone
 1915 , 1700 , 1519 , 1432 , 1275 , 1136 , 1014 ,
//n+
 1806 , 1603 , 1433 , 1350 , 1202 , 1071 , 956 ,
//n-
 2029 , 1801 , 1610 , 1517 , 1351 , 1204 , 1074};
for (char i=0;i < 7;i++) 
    {if (names[i]==note)
     playTone(tones[i+flagdiez*7] >> octave,duration);}
}

The tones[] array contains the half-pulse duration in microseconds (not frequencies) of the notes, The first line of the array corresponds to the usual 7 notes (do, re, mi, fa, sol, la, si), the second line - these are the same tones as in the first, but divided by 1.06 (the root of the 12th degree of 2 or exp(log(2)/12)), the third line - The same values as in the first, but multiplied by 1.06. Thus, the second line corresponds to the sharps, and the third - to the flats.

Finally, we approach the interpreter of musical commands PLAY. The variable i moves the pointer to the characters of the array notes[i], while decoding commands and notes. Notice the highlighted line char notecode=notes[i];i++;. This line runs in a for loop unconditionally. Therefore, even if there is an error in the logic of processing "suffixes and prefixes" or something is confused in the letters notes[], the program does not hang, and the worst case is simple will lose any notes, but the for loop will certainly end.


//Main program
//Read music text fron notes[] and plays music
void setup()
{//variables declaration
...
pinMode(LED,OUTPUT);
pinMode(SPEAKER,OUTPUT);
tempo=120;int note1=(int)(240000L/tempo);
//notes[] string reading loop
for (int i=0;notes[i] != 0;)
{
retry:
//process prefix commands
...
//process note code
char notecode=notes[i];i++;
//process suffix commands
...
//play note
if (islower(notecode)) notecode-=32;
if (notecode=='P') delay(notelen); else
    {if      (playmode eq 'L') {playNote(notecode,notelen);}
     else if (playmode eq 'S') {playNote(notecode,notelen*3/4);
                                delay(notelen/4);}
     else                      {playNote(notecode,notelen*7/8);
                                delay(notelen/8);}
    }
} // end of for loop
} //end of setup function

2 April 2020 Some minor bugs fixed
Download sketch Music on Arduino (77 KB)

Download QBasic and the music collection (422 KB)

29 June 2020 Added some music from ZX Spectrum, Apple II, IBM PC XT
Download music from computers of 1980ies (122 KB)

Continue to More Simple 1 Byte Arduino Music