Arduino Synthesizer With FM

13,139

44

43

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

    • Backyard Contest

      Backyard Contest
    • Remote Control Contest

      Remote Control Contest
    • Eggs Challenge

      Eggs Challenge

    43 Comments

    0
    mutz03 Zockt
    mutz03 Zockt

    5 months ago

    hey, i wanted to ask the community here.
    I have used this code on one of my projects. and i have made it MIDI compatible.

    setting:
    i have hooked up a second Arduino, which handles the MIDI in, and all the User interface.
    it sends the midi data, as well as the instrument parameters to the primary arduino, whitch only runs teh code you have made.
    i guessed, because its really important to have the right timing. i made that this way. and it seems to work very well.
    problem:
    exept it does not really.
    on its own the sound out put sound really beautiful and clean, but when i started to implement the MIDI support, the sound started to become really weird, there is a really High pitched noise when i send a note. and some notes just dont get played under certain circumastances
    for example when i play a F4 after an E4, it will not play the F....
    i guess it is a timing related error, but i am on my limits of what i can do.
    does anyone know whats wrong?

    sources:
    a video of the noices: https://cdn.discordapp.com/attachments/90808290177...
    the code on the primary arduino: http://sprunge.us/YvUvK6
    and the code on the secondary Arduino http://sprunge.us/motVXb

    thanks for any help

    0
    DavidMMM
    DavidMMM

    10 months ago

    I'm trying to see the generated frequency on my Oscope, but am getting too much noise on the pin (31khz with 2.27vrms). Seems to only happen on this code... maybe something to do with the fast pwm? Any ideas? I tried adding a pulldown resistor.

    Gonna try using this on a 7 string laser harp :)
    Any suggestions on what notes to exclude?

    0
    rgco
    rgco

    Reply 10 months ago

    Sorry a bit late. What you see is the bare PWM signal. We can't hear frequencies above 20kHz, so they get naturally filtered out. You can also remove them with an RC filter so that the scope image improves, e.g. using R=10k, C=10nF (cut-off 10kHz). But there is no need to filter the sound.

    0
    DavidMMM
    DavidMMM

    Reply 10 months ago

    Thanks for the reply. Yeah, trying some RC filters as using a class D amp (TPA3118), I do get a little noise from the PWM keeping the amp 'on'. Its hard to scope (on my hobby o-scope DSO138 Mini), to make sure its not any other frequencies besides the 31.25 from the fast pwm since this is all I can see on the scope atm.

    BTW- the sounds are great! I'm hesitant to go the midi route, and your code makes the best sounds I've heard out of the arduino directly.

    0
    rgco
    rgco

    Reply 10 months ago

    Ah, with a class D amp it may give issues, since it also samples, and the frequency difference may result in the output and be audible; you may need a 2nd order RC filter to get rid of that. Just yesterday I started porting the project to the raspberry pi pico, the higher speed should improve the sound quality to 11 or 12 bits and allow for higher levels of polyphony. Attached is a scope picture of the raw PWM signal in yellow compared to that from an RC filter (R=10kOhm C=4.7nF, but the sampling frequency is 61kHz)

    20211010_220434.jpg
    0
    DavidMMM
    DavidMMM

    Reply 10 months ago

    Cool, was thinking of 2nd order filter too, will try that out along with some active low pass tests.
    Any thoughts on making this a library for Arduino? I know it would be very time consuming... happy to make a small crypto donation if you did pursue it.

    0
    DavidMMM
    DavidMMM

    Reply 10 months ago

    Here's an active butterworth low pass filter for ~15khz, drops the db of the 31.25khz pwm noise pretty considerably, and makes the class d quieter.

    2nd Order low Pass Butterworth.png
    0
    rgco
    rgco

    Reply 9 months ago

    Nice! beware though that in theory these may work well, in practice an active filter requires on opamp that requires either level-shifting or double (+ and -) power supplies - quite an added complication for this simple circuit. One can also just put 2 RC in series - that's not optimal because of impedance matching, but if I recall well there are fine 2nd order RC filters based on just 2Rs and 2C's.

    0
    rgco
    rgco

    Reply 9 months ago

    Sorry I have no experience with making libraries, I even try to avoid using them whenever I can! It's quite involved to make (and maintain!) the code work such that it integrates with other people's code. Here in particular the code is using timed loops to emulate multitasking what normally an operating system would do.

    0
    g4ll3yh34d
    g4ll3yh34d

    Question 1 year ago on Introduction

    Hello! I don't know if you check in on this project any more, but I am planning to use it in a Vulcan harp I'm building for a friend. Is there a way you could recommend to add a potentiometer controlled pitch bend to the output?

    0
    g4ll3yh34d
    g4ll3yh34d

    Reply 1 year ago

    Oh, sorry. There is a knob on the front of the Vulcan harp that is clearly not functional in the prop, but has been used by people who have made replicas to bend the pitch of the output note either sharp or flat. Maybe sort of like a whammy bar on a guitar. I had originally set out to replicate an old set of plans that used a top octave generator chip (which I was able to source from old stock), but I can't get it working and found your project(s) while searching for a way to use the Arduino to make some sounds. I did see your update project. Maybe I can adapt that. My current plan is to have 4 buttons that will generate 4 fixed tones that will serve sort of as drones that can harmonize with the strings. Being able to use the big knob on the front kind of like this guy is doing:
    Thanks for the inspiration! I will be putting together one (or both) of your projects this week to see if I can just come up with something rough. The guy for whom I'm building the harp can't even play an instrument. He's just a Star Trek fan.

    0
    rgco
    rgco

    Reply 1 year ago

    Sure, changing the pitch based on a pot reading should be straightforward! I hope it works!

    0
    g4ll3yh34d
    g4ll3yh34d

    Reply 1 year ago

    Got it roughed together last night, and it seems promising. Playing with the ADSR envelopes on some of your preset sounds yielded sounds that should be sufficiently Star Trekky. If you have a moment, and wouldn't mind, would you be able to point me in the right direction to read a pot input and use that to change the pitch created when a button is pushed? I'm not an Arduino ninja, but am willing to hack away at a solution on my own, if you can suggest a general approach. Thanks again!

    0
    rgco
    rgco

    Reply 1 year ago

    OK, for pot input, the usual 'AnalogRead' is not good: it starts up the ADC and waits ill its done which takes by default ~0,1ms, which interrupts the waveform production.
    Check the code from soundlab: (or better start from that)
    setup has the following lines:

    //setup the ADC
    ADCSRA=B11110100; // prescale 16 -> 13 mus per sample, auto trigger
    ADCSRB=B00000000; // freerun
    ADMUX =B01100000; // Vcc ref, left-align, ch0

    This increases the speed by a factor 8 and sets it up to keep running code while performing the ADC conversion.


    later in the code you'll see

    //setup one POT
    ADMUX &=B11111000; ADMUX |= ipot;
    setPWM(); //#2
    //readout one pot
    byte potval=ADCH;

    This starts up the ADC, runs one PWM 'tick' -after which the ADC value is ready.
    Here the 8 pots are being cyclically read through the 'ipot' variable, but if you have only one 'ipot' can stay at 0.

    the value of potval can then be used to modify the sound.

    If you want it to change the frequency, you could do that at the line
    inc_base[nextch] = tone_inc[keypressed];

    and change it for example
    inc_base[nextch] = tone_inc[keypressed]*exp8[potval]/2048

    or

    inc_base[nextch] = tone_inc[keypressed]*exp8[potval/2]/1024

    or

    inc_base[nextch] = tone_inc[keypressed]*exp8[potval/4]/512

    depending on the amount of pitch bending

    0
    g4ll3yh34d
    g4ll3yh34d

    Reply 1 year ago

    Wow! Thank you so much for taking the time to reply in such detail. I will dig into that. I'm also thinking about trying to modify things to have a button push play a chord instead of a single note. There are four buttons on the front of the harp. If I can have each play a chord and have the big knob on the soundboard act as a pitch bend, that should be more than sufficient to have this project pay off in something that's a bit more than a wall hanging. I really appreciate your help, and am very glad to have found your project!

    0
    AlexS782
    AlexS782

    2 years 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 2 years 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 2 years 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 2 years 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)