Sweet and Easy Arduino Tunes
This Instructable shows an easy-to-build Arduino circuit that generates some sweet-sounding music. Does the world really need yet another Arduino music system? Sure - why not! Here's what's new:
- Instead of the usual buzzy arcade type of music, this system makes music using a pure tone (sine wave).
- You don't need to build additional electronic circuits or connect a Midi sound system.
Three applications are shown.
- Variable frequency sine wave generator.
- Simple light theremin. Wave a hand over a photoresistor to control frequency. Output is a pure tone, not a buzz. We add in some tremolo to get the weird science fiction effect
- A popular Arduino song player is modified by replacing the buzzer sound with a pure tone. A nice improvement
Supplies
Basic Parts for Song Player
- Arduino Uno, breadboard, jumpers
- (7x) 20K resistors
- (5x) 10K resistors
- 3.5 mm audio jack breakout board: https://www.amazon.com/Onyehn-Breakout-Stereo-Headphone-Arduino/dp/B07L3P93ZD/ref=sr_1_2?keywords=arduino+3.5+mm+jack&qid=1638929241&sr=8-2
- Audio output device: headphones, earbuds, amplified speaker, or M-M audio cable connected to PC Line-in
Optional Parts for Sinewave Generator and Theremin
- 10K or 50K potentiometer
- (1x) 10K resistor
- (1x) photoresistor
- (1x) pushbutton switch
- (1x) LED
- (1x) 220 ohm resistor (any resistance over 100 ohms will do)
Start With a Sine-wave Generator
The first circuit outputs a sine wave whose frequency is controlled by a potentiometer (AKA a "pot"). Here are the steps:
- Build the circuit shown in the Fritzing diagram
- Connect an audio device such as earbuds or amplified speaker (see below)
- Upload the program and fiddle with the pot.
- If it works, you can move to the next step
Getting the sound out. An Arduino cannot source enough current to connect a speaker directly. One approach is to add an audio amplifier circuit to drive a speaker. A simpler approach is to use equipment most people already have laying around, including: (1) headphones or earbuds, (2) an amplified speaker from an old PC, or (3) an audio cable connected to a PC Mic or Line-In input. If you do this, it's better to use the Line-In rather than the Mic (which can saturate).
Connect the audio jack like this: Wire the sleeve to ground, and the tip to the left-most 10K resistor as shown.
Note: the picture shows a Digilent "Analog Discovery" board attached to the breadboard. This is not required for the project, but simply provides a convenient way to visualize the output waveform.
Downloads
Make a Light Theremin
In a "light theremin" frequency is controlled by moving your hand to create a shadow over a photoresistor, which senses the changing light level. While it's an interesting toy, I find it nearly impossible to play a recognizable tune. Regardless, we'll convert our sine wave generator into a light theremin, and also add a tremolo effect. Tremolo is a sort of waa-waa effect caused by rapidly shifting frequency up and down about its center value. Think of it as a warble.
Build the circuit shown on the fritzing diagram. As you can see, it requires only a slight modification of the previous circuit. The potentiometer is no longer used so you can take it out or leave it in.
Upload the C program "LightTher.ino". The pushbutton turns tremolo on or off. See the theory step for more information on how tremolo is implemented.
Downloads
Finally - Here's the Music Player
This step requires no changes to the circuit. The potentiometer and photoresistor are not used so they can be removed if desired.
This Instructable builds on the music player developed by Robson Couto: https://dragaosemchama.com/en/2019/02/songs-for-arduino/. It's worth your time to try out his software, especially since his Arduino circuit requires only one part (a buzzer)!
The program MusicBasic.ino replaces the buzz code with a true sinewave. Several songs are included that are selected by uncommenting the desired song. Have a look in the code at about line 46 and you'll see that the active song is currently "silentNight". It looks like this:
int tempo = 100; //bigger numbers make the song go faster #include "silentNight.h" //suggest tempo = 100
The notes in the songs are defined in the ".h" files, which must be in the same directory as the .ino file. You can get some idea about how the program works by reading the theory section given in the last step.
A second program, MusicPlus.ino is also included. It has some interesting additions, but the program is a bit harder to understand. The new additions include:
- In MusicBasic, notes are turned on and off suddenly, which results in a slight pop at the beginning or ending of each note. This annoying effect is reduced by ramping the notes up at the beginning of the note and ramping down at the end of the note.
- There is a boolean variable in the code called "bend". This is false by default. If you edit the program and set "bend = true;" the result is to "bend" the notes. This means that notes don't abruptly change but fade from one note into the next. If tremolo is also turned on, the resulting combination sounds a little creepy - sort of like a human playing a theremin. Listen to the Silent111.wav file for a short demo.
Note: You can easily add more songs by downloading them from Robson Couto's web site and extracting the lines containing the melody array. Then just save each song as an .h file, making sure to place it in the .ino directory.
Perf Board Version
A disadvantage of the above breadboard implementation is that resistors can make poor connections to the breadboard resulting in noisy and unreliable performance. This is especially true of your resistors have skinny little wire legs. Building the R2R resistor network on a piece of perf board makes them much more reliable. If we build the DAC using a 7-pin angled header, the data pins fit nicely into Arduino gpio pins 8-13, and the 7-th pin goes into Arduino ground. A suggested layout is presented in the attached figure. I used perf boards like the ones in this Amazon link, and then used a Dremel tool to cut it down to a nice small size. https://www.amazon.com/ELEGOO-Prototype-Soldering-Compatible-Arduino/dp/B072Z7Y19F/ref=sr_1_3?keywords=perfboard&qid=1638995138&sr=8-3
Theory of Operation
This step discusses some design and construction notes. Feel free to skip it.
In step 1 we built a sine wave generator adapted from the excellent tutorials by Amanda Ghassaei. She describes a DIY digital-to-analog converter (DAC) using just a handful of resistors connected to the Arduino GPIO pins: https://www.instructables.com/Arduino-Audio-Output/
The sine wave is generated in software using a timer interrupt to output sine wave samples to the DAC at a fixed sample rate. Our project adds software to control the frequency in real time, which is adjusted using a potentiometer.
The Arduino cannot source enough current to connect a speaker directly. One approach is to add an op-amp circuit to drive a speaker, and this is described in Amanda’s tutorial. Although this works fine, a simpler approach is to use equipment most of us have laying around. Simply connect the audio equipment to the DAC using the audio jack. Some options include: (1) use headphones or earbuds, (2) use an amplified speaker from an old PC, or (3) use an audio cable to connect the DAC output to the Mic or Line-In input of your PC and use your computer’s sound system.
The circuit we build implements a 6-bit digital-to-analog converter (DAC) using pins Arduino 8-13. Sine wave samples are output at a rate of about16000 samples per second. On the Fritzing diagram, brown resistors are 20K and blue resistors are 10K. Using two colors was done to make the diagram less confusing, and you should not feel you need to find brown and blue resistors. After building the circuit and uploading the software, you may notice some noise or distortion. Sometimes this is caused by poor connections made by the resistors to the breadboard, especially if the resistos have very thin wire legs. Try pushing and re-socketing the resistors until you get a nice pure tone.
Sine wave amplitudes are represented by a 6-bit integer that is output to GPIO pins 8-13. These gpio pins connect into a series of resistors comprising an “R2R” ladder network. In our case it’s a sequence of 10K and 20K (the R and 2R values) resistances. This implements a sequence of voltage dividers such that each output bit contributes one half the voltage of the next higher bit. The voltages combine to represent a final amplitude ranging from 0 to 5 volts with 2^6 = 64 possible voltages. The signal-to-quantizer noise ratio of an N bit DAC and full-scale sinusoid is given by SQNR = 6.02N+1.79 dB (see: https://www.analog.com/media/en/training-seminars/tutorials/MT-001.pdf) For a 6-bit DAC, this gives 37.9 dB, which is excellent quality. The system described by Amanda Ghassaei is an 8-bit DAC using pins 0-7 giving 49.9 dB; which is even more excellent. It's unlikely you would perceive any difference in quality between 6- and 8-bit DACs. Note that this analysis only applies to a sine wave, whose amplitude remains at full scale (i.e., spans the maximum range of the N-bit integer). Signals such as speech or music have average amplitudes much lower than peak amplitude, which reduces the signal power (numerator) and makes SQNR smaller than for the sinusoid case.
Code walkthrough (just the highlights).
- The function FillSineTable() fills an array with a single cycle of a sine wave. The array length is trade-off. If it’s too large, you’ll run out of memory. If it’s smaller than about 80, you’ll start hearing noise and distortion.
- The function set_interrupts() sets up a timer interrupt that causes a special function to execute16130 times a second. The function is called an “Interrupt Service Routine” or ISR. Please read the excellent tutorial by Amanda Ghassaei for more information. The ISR is described next.
- The function ISR(TIMER2_COMPA_vect) is automatically called 16130 times each second. Its job is to output a single 6-bit value selected from the sine wave table directly to pins 8-13. For the Arduino Uno, pins 8-13 are mapped to an internal 8-bit register called PORTB (The two most significant bits are not accessible). Storing a 6-bit value into PORTB causes the bit values to immediately show up on pins 8-13 (our DAC). This is much faster than doing the same task using multiple calls to digitalWrite(). Here's a code snippet:
out = SineTable[findx]; //pull out a sample of the sinewave PORTB = out>>2; //convert the 8-bit value to a 6-bit value (throw out 2 lsbs)
- Frequency control: the desired output frequency is in a variable called “desiredfreq.” This controls how fast the findx variable (see previous bullet) indexes through the sine wave table. For example, if desiredFreq is large, the table index is incremented much faster than for smaller values of desiredFreq. Consequently, the rate that sine wave cycles are output is proportional to the desiredFreq variable. If you stare at the code long enough you can get a feel for how it works.
- For the sine-wave generator program, the main loop() function has the line:
desiredFreq = analogRead(A0)*5;
For pot value (A0) ranging from 0 to 1023, we get desiredFreq values between 0 and 5115 Hz. Given a sampling rate of about 16 kHz, we need to keep the frequency below Fs/2 or 8000 Hz.
- Keep in mind that the ISR operates completely in the background. This means that the loop() function can be very short - in this case it just focuses setting the desired frequency.
- The Amanda Ghassaei tutorial uses pins 0-7 to achieve an 8-bit DAC. One consequence of using pins 0 and 1 is that serial communication becomes unavailable. Since I like using serial communications, this Instructable implements a 6-bit DAC using pins 8-13, which leaves pins 0 and 1 unused. Serial.println can be used for outputting the current frequency and for debugging.