Arduino Synth using IFS Fractals

Erich Izdepski
Geek Culture
Published in
8 min readDec 2, 2022

--

I am making a synthesizer with an Arduino Mega. I am taking small steps and documenting as I go. The end goal is to make a small, playable desktop synth with two voices (at least). I already know it will sound great because of a special technique I discovered- see the demo below.

The synth uses IFS (iterated function system) Fractals as the algorithm for Generative Music. The algorithm is normally used to make pictures (fern leafs in this case) and produces x,y coordinant pairs. These are perfect for converting to a pitch and a duration. Since this is Generative Music, the synth just plays whenever it powers on. I intend to make it controllable using Arduino sensors and also to play the pitches found in Western Music. I might even add MIDI! My code will be available for others to exploit as they wish.

Just Show Me The Code

Okay, expert. It is at the bottom of the post. It is also here: https://github.com/mogrifier/arduino but changing rapidly! Look in the fractalmusic folder and be sure to star and follow!

Secret Sauce

Even using a single audio output with the tone function you can get a great sound with a little trick. Play very short sounds; for example, a 10 or 12 millisec duration of a tone and a 15 millisec loop. This creates a second audible waveform in addition to the tone. Think of it this way- it plays 10 out of every 15 millseconds. That is a 67% duty cycle. The pulse has a 15 millisecond period which equates to a 66Hz sound which is technically BELOW what an Arduino can output with the tone function (lowest is around 100Hz). Combining this pulse with the tone function gives the sound heard below.

Generative Music

I want to focus on the sound and not spend time yet on making a keyboard-based synth. I do have an old Casio and instructions on how its key scanner works so I could use that in the future. In the meantime, I am going to use software to generate music, but not randomly. I have written some software using Iterated Function Systems that can be used to create fractals. The numbers stay well-bound so you can map the function output to a pitch. This using very little memory and does not use complex numbers so it is fast. Each value depends on the values before it so I hope that will lead to more musical qualities. The output is two-dimensional (a pair of coordinates, usually for a pixel) so you can get a pitch and a duration. Duration is a little tricky to apply since I still want my “sync” tone, but I know that I can latch in a specific pitch for any amount of time relative to the duration value and still achieve the sound I want. This will also allow a simple portamento affect when changing pitches.

Different starting matrices produce entirely new sounding musical passages. A different starting point for the IFS also causes changes. The matrix values can also be changed in a real-time and controlled by an analog sensor input. You can also use a potentiometer to control the range of pitch values or change the tempo. Some way to control whether pitch is proper western notes or something else may be desirable. Ultimately, the system should be playable and not just sound like a typical sample and hold.

The video below is the first cut at my Arduino synth playing data from an IFS Fractal. Sounds great even without using the Eventide Space.

Arduino Synth playing data from IFS Fractal formula

Wiring

This is the simplest wiring design for this code. The speaker is 8 ohm. The volume pot is 100k (which is too much- 10K ohm is plenty). The vibrato controller is small- 500 ohm? The speaker output is digital pin 9. The potentiometer for vibrato control is connectd to analog pin 0. It simply gets 5v from the board and reduces it through the potentiometer. It’s really that simple.

Breadboard with simple Arduino speaker hookup with volume and a sensor

The Code

If new to Arduino, the tips below will help a little if trying to run the code. I am using the Arduino Mega with the ATMega 1280 Processor. You can get the exact processor model off the biggest chip on the board with a magnifying glass. With IDE 2.0 you need to set 3 separate things to get it to work (that’s annoying).

From this menu you must select the correct board, the port that it is on, and the processor. In my case, there are two different processors used on the Arduino Mega. If you select the wrong one, you cannot upload sketches and will receive a timeout error. When uploading code you also need to ensure that the serial terminal in the IDE (or Putty) is not running, since that will prevent using the serial data connectio for uploading the code.

If building this with sensors and other electrical components, ensure you have a common ground for all sources of power. I found when trying to control a Speak and Spell my sensors were not working since the Arduino was on one power supply and the Speak and Spell was on another. You must unify the power sources.

Worth noting that the Arduino uses flash memory so once you upload a program to it, it stays in memory even after the power is removed. Start the board back up, and your program will start running. Exactly the right behavior if creating a little device!

/*
play tones. Uses Arduino MEGA 1280 or other mega. Not using too many resources so would run on other models. This is creating really nice and usable sync-like tones. All with with output voice.
Very worthwhile to put separate analog controls on the variables for freq, modRange, maxMod and maybe loop delay.
This is a continuous player- run through FX for sure!
duration should be less than loopdelay.
*/
const int SPEAKERPIN = 9;
const int MODPIN = 0;
const int FREQ = 500;
const int LOOPDELAY = 15;
const int DURATION = 12; //you can hear down to 11ms or so. combined with LOOPDELAY this creates extra sound
const int MAXMOD = 10;
const float TWELFTHROOT = 1.05946;
const float LOWA = 27.5;
int count = 0;
int deltaFreq = -5;
int inc = 3;
int totalIterations = 0;
//array for pitch and duration (first value is pitch)
int music[2];
//starting point for IFS code. Store past and present for iteration.
float x = 0;
float y = 0;
float next_x = 0;
float next_y = 0;
int note_duration = 0;
int freq = 0;
//numbers of times a given note is played. cycles * duration >= note_duration
int cycles = 0;
//arrays for holding IFS matrix
float a[4];
float b[4];
float c[4];
float d[4];
float e[4];
float f[4];
void setup() {
// put your setup code here, to run once:
Serial.begin(57600);
//random start points
init_xy();
//initial IFS matrix data for a fern
a[0] = 0;
a[1] = 0.85;
a[2] = 0.2;
a[3] = -0.15;
b[0] = 0;
b[1] = 0.04;
b[2] = -0.26;
b[3] = 0.28;
c[0] = 0;
c[1] = -0.04;
c[2] = 0.23;
c[3] = 0.26;
d[0] = 0.16;
d[1] = 0.85;
d[2] = 0.22;
d[3] = 0.44;
e[0] = 0;
e[1] = 0;
e[2] = 0;
e[3] = 0;
f[0] = 0;
f[1] = 1.6;
f[2] = 1.6;
f[3] = 0.44;
}// put your main code here, to run repeatedly:
void loop() {
//reading a potentiometer for setting vibrato amount
int sensor = analogRead(MODPIN);
if (cycles * DURATION >= note_duration) {
//note has played for at least the amount of milliseconds specified so get a new note
//reset cycle counter
cycles = 0;
compute_music();
freq = music[0];
note_duration = music[1];
char buffer[40];
sprintf(buffer, "Pitch %d and duration %d", freq, note_duration);
Serial.println(buffer);
}
else {
//use previous pitch and duration, but the duration has to "count down"
freq = music[0];
note_duration = music[1];
cycles += 1;
}
//map vibrato sensor reading to range 5-30
int modRange = map(sensor, 0, 1023, 5, 30);
//Serial.print("modrange= ");
//Serial.println(modRange);
count++;
//this creates vibrato
if (count % modRange == 0) {
deltaFreq += inc;
if (deltaFreq >= MAXMOD) {
inc = -inc;
}
if (deltaFreq <= -MAXMOD) {
inc = -inc;
}
}
tone(SPEAKERPIN, freq + deltaFreq, DURATION);
delay(LOOPDELAY);
}
/*
Get the next pitch and duration from the IFS code. Just compute all as needed.
*/
void compute_music() {
totalIterations += 1;
int k = get_chance();
next_x = a[k] * x + b[k] * y + e[k];
next_y = c[k] * x + d[k] * y + f[k];
x = next_x;
y = next_y;
//the next note to play is next_x with a duration of next_y //scale values so in bounds and make sense for pitch frequency and duration in milliseconds
int scale_x = int(abs(x) * 100);
if (scale_x > 100) {
scale_x = 100;
}
//constrain the piano key range to one the arduino can play and also not too high since unpleasant
int piano_key = map(scale_x, 0, 100, 25, 74);
//y has a range up to 3.5 or so
int scale_y = int(abs(y) * 600 + 400);

music[0] = get_freq(piano_key);
music[1] = scale_y;

if (totalIterations > 100) {
//reset to new starting point for iteration
init_xy();
totalIterations = 0;
}
}/*
Choose array indices based on a hard-coded probability distribution.
*/
int get_chance() {
float r = (float)random(1, 100)/100;
if (r <= 0.1)
return 0;
if (r <= 0.2)
return 1;
if ( r <= 0.4)
return 2;
else
return 3;
}
void init_xy() {
x = (float)random(1, 100)/100;
y = (float)random(1, 100)/100;
}
/*
Convert the piano key position (1 to 88) to the corresponding frequency
*/
int get_freq(int key) {
int octave = (int)(key/12);
int note = (key % 12) - 1;
float freq = LOWA * pow(2, octave) * pow(TWELFTHROOT, note);
return int(freq);
}

What Next?

Add a second voice plus more sensors! This will be a knobby synth. I know I can control the following.

  • range of pitch and duration values
  • quantize duration to fixed values
  • control a second audio channel to make it slightly detune or go into coarse tuning for a harmony
  • vibrato amount
  • IFS matrix parameters to change pattern of notes (effectively infinite)
  • controlling when the IFS goes back to the start point (creates arpeggios!)

May find more things I can control as I go.

--

--

Erich Izdepski
Geek Culture

Software engineer and architect who’s built web, mobile and desktop apps in multiple industries over a span of more than 25 years. CTO @ BTS Software Solutions.