Arduino Synthesizer With FM

4,637

22

27

Introduction: Arduino Synthesizer With FM

It is quite possible to create decent sound with an Arduino as has been demonstrated in plenty of other projects, for example:

Here is another demonstration of how a common Arduino Uno (or clone) can be used to generate a wide range of musical sounds.

The specs are as follows:

  • 31kHz sampling rate
  • 9-bit resolution
  • 4-fold polyphony (4 different tones can play simulateneously)
  • FM-synthesis with time-varying modulation amplitude
  • ADSR envelopes
  • 12 preset instruments
  • 18 keys covering 1.5 octave

Watch and listen to the video for a demo!

Of course it's nowhere close to the commercially digital synthesizers, but it's a big step from a simple 'beep' piano.

Instruments are defined by a set of 10 parameters, and it's easy to create new sounds by changing the parameters. After playing around a bit I found came up with 12 instruments that sound good and named them as follows:

  1. piano
  2. xylophone
  3. guitar
  4. cymbal
  5. bell
  6. funky
  7. vibrato
  8. metal
  9. violin
  10. bass
  11. trumpet
  12. harmonica

Please tell me in the feedback if you find new good sounds, then I can add them to the code.

The project described has the 19-button keyboard built with microbuttons on breadboard, but it lends itself perfectly to be built into existing objects, like furniture, toys or vintage equipment.

Step 1: Construction and Operation

Required materials

  • an Arduino Uno or clone
  • an 830-hole breadboard
  • 19 4-pin micro-push buttons
  • a 100muF electrolytic capacitor
  • a 10kOhm potentiometer
  • a 3.5 mm headphone jack
  • ~50cm of single core hookup wire
  • 21 jumper wires (14 long and 7 short)

Wire up all the components according to the indicated schematic. It's quite some work to get all the wiring in place, so it makes sense to start with connecting only the audio circuit and one button, and upload the software. If that works you can progressively add all the other buttons. For the electrolytic capacitor, make sure to connect the positive terminal to the Arduino side and the negative terminal to the potentiometer. The short connections within the breadboard are best done with short pieces of solid-core hookup wire, the longer connections to the Arduino better with flexible jumper wires.

Note that the buttons may be connected to any pin, with software it is possible to assign any key to any note. The only exception is pin D9, on which the audio signal is produced, it must not be used as a key input.

Load up the Arduino sketch from the next step and you can start playing.

The keys play the notes C4-F5, and the rightmost button changes the instrument. The time that a button is pushed changes the length of a note, but since these are simple digital pushbuttons, the loudness of the tone cannot be influenced by speed or force with which the keys are pushed.

Connect a stereo headphones to the 3.5 mm jack and with the potentiometer the volume can be regulated.

To hear the sound over a speaker, it needs to be amplified. Any amplifier that is designed to take headphone input will work. Alternatively a cheap amplifier module base on the LM386 or PAM8403 should be sufficient to bring up the volume.

Step 2: Code

Upload the attached with the Arduino IDE. I used version 1.8.7, but nothing fancy has been used so I expect it to work well with most past and future IDE's. Moreover, no external libraries are used.

After the 'Verify' step, I see:

Sketch uses 11630 bytes (36%) of program storage space. Maximum is 32256 bytes. Global variables use 757 bytes (36%) of dynamic memory, leaving 1291 bytes for local variables. Maximum is 2048 bytes.

So it is far from exhausting the 2kB RAM, 32kB flash of the Arduino Uno.

Instrument definitions

Each instrument is defined by 10 parameters. To discover new sounds, it's crucial to understand their meaning, and make sure you understand the basics of ADSR envelopes and FM sound synthesis .

ldness: loudness

Keeping this at 64 or below guarantees that the output does not go in overflow and produce horrible distortions. But for sounds that remain only very shortly at maximum amplitude, it's OK to go higher, since it's unlikely to have all 4 voices at maximum amplitude.

pitch0: pitch offset

Setting this to 12 results in 'normal' configuration where the leftmost key has the pitch of the C4. Setting it to 0 results in a shift of a full octave down, while the value 24 results in a full octave up.

ADSR_a: attack parameter

Speed with which the tone rises from 0 to max. A small number here means the sound starts very slowly. Example: ADSR_a=8192 takes 4ms to rise to max. ADSR_a=256 takes 128ms to rise to max

ADSR_d: decay parameter

Speed with which the tone decays from max to sustain. A small number here means the sound decays very slowly. Example: ADSR_d=8192 takes 4ms to decay from max to 0 . ADSR_d=256 takes 128ms to decay from max to 0. If sustain is different from 0, it actually takes less to reach the sustain value.

ADSR_s: sustain parameter

Relative loudness of the tone if the key remains pushed. ADSR_s=255 means it sustains at maximum volume. ADSR_s=192 means it sustains at 80% of the maximum volume. ADSR_s=0 means the sound dies off even if the key stays pressed.

ADSR_r: release parameter

Speed with which the tone decays after being released. A small number here means the sound continues for a long time after key release Example: ADSR_r=8192 takes 4ms to decay from max to 0 . ADSR_r=256 takes 128ms to decay from max to 0. If the tone was already below maximum at the moment of release, it takes less time to die.

FM_inc: FM frequency wrt pitch

The ratio of the modulation frequency to the pitch FM_inc=256 corresponds to a modulation frequency equal to the pitch, resulting in pure harmonics that are multiples of the pitch frequency. FM_inc=512, 768 or 1024 corresponds to a modulation frequency 2,3 or 4 times the pitch, resulting in pure harmonics that are multiples of the pitch frequency, but with more emphasis at the higher frequencies. FM_inc=128 corresponds to a modulation frequency half to the tone, resulting in semi-pure harmonics that are multiples of the half the pitch frequency, thus creating also undertones. FM_inc=384,640,896 corresponds to a modulation frequency 1.5, 2.5 or 3.,5 times the pitch, resulting in semi-pure harmonics that are multiples of the half the pitch frequency, thus creating also undertones, but with more emphasis at the higher frequencies. Any number for FM_inc that is not a multiple of 64 will result in anharmonic tone, typical of cylindrical vibrations. A value of FM-inc that differs slightly from a multiple of 256 will result in very nearby-frequency bands, which then results in a vibrato effect.

FM_a1: FM amplitude start

Frequency modulation amplitude at the beginning of a note. FM_a1=256 means that at the beginning of a note beta=1, resulting in a rich spectrum. A smaller number results in fewer sidebands and thus a purer note. Very large values, like FM_a1=2048 corresponds to beta=8 and gives rather crazy waveforms, which may result in quite interesting sounds.

FM_a2: FM amplitude end

Frequency modulation amplitude at the end of a note. In most instruments the high frequencies damp faster than the low frequencies, which can be emulated by a decreasing value of the FM amplitude, or FM_a2FM_a1 can help to create weird crazy funky sounds.

FM_dec: FM decay

The speed with which the FM amplitude changes from its starting value to its end value is determined by Fm_dec. Note that this change follows an exponential decay, so the transition is much smoother than for the ADSR envelope, where the transition is linear. FM_dec=256 means that the time constant of the exponential change is 128ms. A low value of FM_dec means that the sound keeps changing continuously. A high value of FM_dec results in a short initial burst of change, followed by a quick transition to a more stable final sound.

Keys

The Arduino uno has 20 io ports. Pin 9 is needed for audio out and one more pin for instrument select. Apart from that, any key can be connected to any i/o pin, and the corresponding note defined in software. For speed reasons, the keys are accessed though direct port manipulation. Thus, inside the code they are accessed as PORTD, PORTB and PORTC. However, in the block 'pin to key mapping', the comments in the code indicate which bit of which port correspond to which Arduino pin. If you make a version with fewer keys, you can define the uninstrumented pins here as 'nokey': it results in smaller and faster code and avoids unwanted tone generations if any of these keys accidentally gets shorted to ground. All keys use the internal pull-up resistors, thus avoiding the need for an extra 19 external pull-up resistors. The keys are read only once every loop (once every 0.48ms), so the effects of debounce are quite limited as well, and I found no need for hard- or software debounce protections.

Step 3: ‚ÄčTechnical Details

PWM

Pulse-width modulation is a well-known method to create semi-analog signals. For creating audio, it must be done with a frequency well above our hearing limit. The timer is set up to give a pulse every 512 clock cycles. Since the Arduino runs at 16MHz, this corresponds to a sampling frequency of 31250Hz, well above our hearing limit. This sampling frequency allows to produce sounds of up to half that frequency (according to the Nyquist theorem).

Timing without interrupts

One method to keep track of time and update the pulse width at the right moment is to use interrupts. However, interrupts have a significant overhead, so I chose instead to base the timing on checking the timer overflow bit. This way the available CPU time is divided more or less equally between the 'fast loop' and the 'slow loop'.

In the fast loop (setPWM) the pulse width is calculated for the 4 voices, at every tick of the timer. This is allowed to take up ~250 cycles, and it appears sufficient to update the 4 phases, do the frequency modulation, calculate the intensity and add up the 4 signals. To keep this fast, only 8- and 16-bit integer numbers are being used, and the sine values have been tabulated.

The call to fast loop is forced to be an inline functions, using the __attribute__((always_inline)) attribute. This means that the assembly code that is generated by the compiler repeats this piece of code every time that it is called. It takes up more space, but it is faster.

There is no time in 512 clock cycles to perform all other functions, such as checking the buttons, changing the instrument, choosing the voice, and modifying the time-evolution of the notes, so this functionality gets split up into 15 pieces and is interspersed between the fast loops. This allows to do some intermediate calculations with 32-bit precision, needed for the multiplication of two 16-bit numbers. The full loop takes 0.48 ms which fast enough for these functions.

FM synthesis

A simple sine wave sounds dull since it has no higher harmonics. A computationally efficient way to create complex sounds is frequency modulation. For practical reasons, it is implemented as phase modulation here, but for sinusoidal signals phase modulation and frequency modulation are equivalent. FM can produce a rich spectrum of sidebands next to the main pitch. The modulation frequency determines the position of these sidebands, and the amplitude determines their intensity. If the modulation frequency is not a simple ratio of the pitch, the resulting sound is anharmonic, and corresponds to the typical sound of vibrating cylindrical objects, such as a bell. Real musical instruments have a rich spectrum that varies over time, since some vibrations damp faster than others.

Remix Contest

Participated in the
Remix Contest

Be the First to Share

    Recommendations

    • Puzzles Speed Challenge

      Puzzles Speed Challenge
    • Secret Compartment Challenge

      Secret Compartment Challenge
    • Lighting Challenge

      Lighting Challenge

    27 Discussions

    0
    AlexS782
    AlexS782

    9 months ago

    Thank you for the amazing work! It really sounds great thanks to your speedy code. About that, would it be possible to add a sort of arpeggiator function when a key is pressed? More importantly, I was wondering if it would be possible to play a sequence of notes, automatically, without the need of pressing a button (grounding the pins). As far as I can tell it's not possible to do this in 'the loop()' and would take some time to rewrite. Can you prove me wrong? Thanks again, great project!

    0
    rgco
    rgco

    Reply 9 months ago

    Hi, I had to look up what an arpeggiator is! I have zero music education, anyway if it's about playing a sequence of notes after a single button press, there's nothing impossible about that! The coding is not trivial, but it's all a matter of putting little pieces of code in between the calls to update the PWM output. The core of the code is perfectly capable to play up to 4 tones simultaneously, even of different 'instruments'. Record and playback functions are tricky to program, I had a quick try at some moment but wasn't really happy with it and so published a 'bare' version here, and and even more basic versione here(https://www.instructables.com/id/Arduino-Soundlab/).

    0
    AlexS782
    AlexS782

    Reply 9 months ago

    Hey rgco, thanks for the reply! It's indeed about play a sequence after a note press. Have not managed yet tho haha. The sequence playing would be more about playing a stand-alone melody (without the need of pressing buttons, but predefined in an array for example). The soundlab code looks nice, indeed adding pots would be a next step. One question, did you forgot to add the link to the 'bare' version you mentioned?

    0
    rgco
    rgco

    Reply 9 months ago

    I have an ongoing project to compose and play rhythms, but I haven't touched it for a while. The replay works but I'm not sure if it'll be feasible to have a display.
    (with the 'bare' model I meant the one called 'soundlab' - it has pots for the instrument settings instead of predefined instruments)

    0
    AlexS782
    AlexS782

    Reply 9 months ago

    Ah thanks for clarifying! Can you give a hint in the direction of playing a sequence of notes? Im having a go at it, but cant get it to work. Should i define an array beforehand and loop through that in the main loop? It doesn't seem trivial indeed haha. Keep up the good work!

    0
    rgco
    rgco

    Reply 9 months ago

    Here's the code as is. It plays a song, though not a terribly interesting one (the next step is to test if it can play some interesting songs, then to make a user interface, squeeze the data format and store in EEPROM)
    First 8 instruments are defined. Then 8 sets of 8 sounds (defined by instrument, pitch, volume and length). Then 8 short tunes, which are combined into a song.

    0
    AlexS782
    AlexS782

    Reply 9 months ago

    Sounds amazing! The low bell wow! Of course the problem with interesting songs is that you have to deal with both the composition and notation (and storage). Maybe look into existing standards for this, such as RTTL https://en.wikipedia.org/wiki/Ring_Tone_Transfer_Language or of course midi files.

    0
    rgco
    rgco

    Reply 9 months ago

    Good idea! I had looked into midi but it seemed way too complicated. RTTTL seems doable, and there seem to be thousands of them readily available! Thanks!

    0
    AlexS782
    AlexS782

    Reply 9 months ago

    Exactly! Cant remember if it was monophonic only tho..

    0
    AlexS782
    AlexS782

    Reply 9 months ago

    Ha! Amazing, just in time! Thanks for sharing =)))

    0
    lorena.smiguel
    lorena.smiguel

    9 months ago

    Thank you for sharing this with us! I'm using it in my undergraduate thesis in Electric Engineering.

    0
    MissNewtype
    MissNewtype

    1 year ago

    I'd like to pose one more request of you, and that's to add a simple square wave and triangle wave instruments. This thing was destined for chiptune.

    0
    MissNewtype
    MissNewtype

    Reply 1 year ago

    That's amazing! Thanks for your work and I'm glad my feedback helped, if even a little.

    0
    MissNewtype
    MissNewtype

    1 year ago

    Hey awesome work on this! I'm currently building it myself, but I was hoping to do a couple modifications to it. I'm just not sure where to start.

    I'd love to add an SPI OLED 128x64 screen that displays the current sound font, and maybe other information.

    Is there any way to increase the amount of keys you can press at once?

    Also, would live modification of each font be possible with a few potentiometers?

    I'm still fairly new to Arduino so I'm not sure where to start coding wise for these few things.

    0
    rgco
    rgco

    Reply 1 year ago

    Glad you like it!
    Adding a screen requires pins so reduces the number of available keys. Also updating the screen might not be fast enough to do simultaneously with making sound, but that's not necessarily a problem.
    Increasing the level of polyphony from 4 to 8 is not trivial: It would take a reduction in sampling rate to 16kHz or a reduction in sound complexity (giving up on FM synthesis). Both will reduce the sound quality. May need to go to a faster microcontroller for that... In any case it would be a complete rewrite of the code since the 4-fold polyphony is hardcoded everywhere to save CPU cycles.
    Live modification should be possible with pots on the analog inputs. Every pot will use a pin so one key less (but the Arduino Nano has 2 extra analog pins A6 and A7). Regular 'analogRead' will be too slow, but accessing registers directly (similar as done in https://www.instructables.com/id/Another-Arduino-Oscilloscope/ ) it's possible to start an analog readout, keep 'working', and read the result when it's ready (instead regular analogRead waits doing nothing after the start of the ADC).
    Good luck and let me know your progress!

    0
    MissNewtype
    MissNewtype

    Reply 1 year ago

    Thanks for your reply! I actually have an expansion board if I run out of pins. I'd love to add those potentiometers.

    0
    MissNewtype
    MissNewtype

    Reply 1 year ago

    Would you happen to know how to code for say, 3 pots for sustain, attack, and decay?

    0
    rgco
    rgco

    Reply 1 year ago

    This.is a.nice idea. 4 pots for adsr and 4 for the fm parameters would really allow to experiment with sound synthesis. The black keys would have to go though. I might give it a try but it will take a while. Just ordering the pots takes 2 months...