17. Transforming Fibonacci Numbers into Music.
By Bernd Klein. Last modified: 01 Feb 2022.
Fibonacci Sequence
The Fibonacci sequence or numbers - named after Leonardo of Pisa, also known as Fibonacci - have fascinated not only mathematicians for centuries. Fibonacci formulated an exercise about the rabbits and their reproduction: In the beginning there is one pair of rabbits (male and female), born out of the blue sky. It takes one month until they can mate. At the end of the second month the female gives birth to a new pair of rabbits. It works the same way with every newly born pair of rabbits, i.e. that it takes one month until the female one can mate and after another month she gives birth to a new pair of rabbits. Now let's suppose that every female rabbit will bring forth another pair of rabbits every month after the end of the first month. Be aware of the fact that these "mathematical" rabbits are immortal. So the population for the the generations look like this:
1, 1, 2, 3, 5, 8, 13, 21, ...
We can easily see that each new number is the sum of the previous two.
We get to music very soon, and feel free to skip the mathematics, but one thing is also worth mentioning. The Fibonacci numbers are strongly related to the Golden Ratio $\varphi$:
$$\varphi = {\frac {1 + \sqrt{5}} {2}}$$
because the quotient of last and the previous to last number in this seqence is getting closer and closer to $\varphi$:
$$\lim_{n\to\infty} { F_n \over F_{n-1}} = \varphi$$
($F_n stands for the n-th Fibonacci number)
The Fibonacci numbers, often presented in conjunction with the golden ratio, are a popular theme in culture. The Fibonacci numbers have been used in the visual art and architecture. The have been used in music very often. My favorite example is the song Lateralus by Tool. The text is rhythmically grouped in Fibonacci numbers. If you look at the following lines , you can count the syllables and you will get 1, 1, 2, 3, 5, 8, 5, 3, 13, 8, 5, 3:
Black
then
white are
all I see
in my infancy
Red and yellow then came to be,
reaching out to me
Lets me see
As below, so above and beyond, I imagine
Drawn beyond the lines of reason
Push the envelope,
watch it bend
We will create a piano score for Fibonacci numbers in this chapter. There is no unique way to do this. We will create both a PDF score our "composition" and a midi file, so that you can listen to the result. We will use LilyPond to create the score:
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
LilyPond
What is LilyPond? On the websitelypond.org they write the following:
LilyPond is a music engraving program, devoted to producing the highest-quality sheet music possible. It brings the aesthetics of traditionally engraved music to computer printouts. LilyPond is free software and part of the GNU Project.
You can learn more about Lilypond in the chapter Musical Scores with Python of our Python tutorial. The following page give you also a great impression how LilyPond works: 'Compiling' Music
%%writefile simple_example.ly
\version "2.14.1"
\include "english.ly"
\score {
\new Staff {
\key d \major
\numericTimeSignature
\time 2/4
<cs' d'' b''>16 <cs' d'' b''>8.
%% Here: the tie on the D's looks funny
%% Too tall? Left-hand endpoint is not aligned with the B tie?
~
<cs' d'' b''>8 [ <b d'' a''> ]
}
}
OUTPUT:
Overwriting simple_example.ly
!lilypond simple_example.ly
OUTPUT:
GNU LilyPond 2.20.0 Processing `simple_example.ly' Parsing... Interpreting music... Preprocessing graphical objects... Finding the ideal number of pages... Fitting music on 1 page... Drawing systems... Layout output to `/tmp/lilypond-R5QDyE'... Converting to `simple_example.pdf'... Deleting `/tmp/lilypond-R5QDyE'... Success: compilation successfully completed
You can see the result by looking at the pdf file simple_example.pdf. The original file from which the PDF was created is simple_example.ly
Fibonacci Score
The Fibonacci Function
To create our Fibonacci score we will use the following Fibonacci function. You can find further explanation - most probably not necessary for the understanding of this chapter - concerning the Fibonacci function in our chapter Recursive Functions and additionally in our chapters Memoization with Decorators and Generators and Iterators.
class FibonacciLike:
def __init__(self, i1=0, i2=1):
self.memo = {0:i1, 1:i2}
def __call__(self, n):
if n not in self.memo:
self.memo[n] = self.__call__(n-1) + self.__call__(n-2)
return self.memo[n]
fib = FibonacciLike()
Furthmore, we will use the function gcd, which calculates the greatest common divisor of two positive numbers:
def gcd(a, b):
""" returns the greatest common divisor of the
numbers a and b """
while b != 0:
a, b = b, a%b
return a
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Numbers to Notes
We map the numbers 0 to 10 to the notes of the E major scale:
%%writefile digits_to_notes.ly
<<
\relative c' {
\key g \major
\time 6/8
dis2 e2 fis2 gis2 a2 b2 cis2 dis2 e2 fis2
}
\addlyrics {
"0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
}
>>
OUTPUT:
Overwriting digits_to_notes.ly
!lilypond digits_to_notes.ly
OUTPUT:
GNU LilyPond 2.20.0 Processing `digits_to_notes.ly' Parsing... digits_to_notes.ly:1: warning: no \version statement found, please add \version "2.20.0" for future compatibility Interpreting music... Preprocessing graphical objects... Finding the ideal number of pages... Fitting music on 1 page... Drawing systems... Layout output to `/tmp/lilypond-KrL4cS'... Converting to `digits_to_notes.pdf'... Deleting `/tmp/lilypond-KrL4cS'... Success: compilation successfully completed
We need a mapping of digits to notes. We will use an e major scale and give each note the following numbers:
In Python we do this mapping by using a list and the indices correspond to the numbers abo
digits2notes = ["dis", "e", "fis",
"gis", "a", "b",
"cis'", "dis'", "e'",
"fis'"]
#digits2notes = ["b", "c", "d", "e", "f", "g", "a'", "b'", "c'", "d'"]
notes = str(fib(24)) + str(fib(12))
notes = str(fib(24))
notes
OUTPUT:
'46368'
def notesgenerator(func, *numbers,k=1):
"""
notesgenerator takes a function 'func', which will be applied to
the tuple of 'numbers'. The results are transformed into strings
and concatenated into a string 'notes'
The length of the string 'notes' is the first value which will be yielded
by a generator object.
After this it will cycle forever over the notes and will each
time return the next k digits. The value of k can be changed be
calling the iterator with send and a new k-value.
Example:
notesgenerator(fib, 1, 1, 2, 3, 5, 8, k=2)
The results of applying fib to the numbers are
1, 1, 1, 2, 5, 21
The string concatenations gives us
1112521
When called the first time, it will yield 7, i.e. the length of
the string notes.
After this it will yield 11, 12, 52, 11, 11, 25, 21, 11, 12, ...
"""
func_values_as_strings = [str(func(x)) for x in numbers]
notes = "".join( func_values_as_strings)
pos = 0
n = len(notes)
rval = yield n
while True:
new_pos = (pos + k) % n
if pos + k < n:
rval = yield notes[pos:new_pos]
else:
rval = yield notes[pos:] + notes[:new_pos]
if rval:
k = rval
pos = new_pos
from itertools import cycle
note = notesgenerator(fib, 5, 8, 13, k=1)
print("length of the created 'notes' string", next(note))
for i in range(5):
print(next(note), end=", ")
print("We set k to 2:")
print(note.send(2))
for i in range(5):
print(next(note), end=", ")
OUTPUT:
length of the created 'notes' string 6 5, 2, 1, 2, 3, We set k to 2: 35 21, 23, 35, 21, 23,
def create_variable_score(notes_gen,
upper,
lower,
beat_numbers,
number_of_notes=80):
""" create_variable_score creates the variable part of our score,
i.e. without the header. It consists of a melody with a chords accompaniment.
"""
counter = 0
old_res = ""
for beat in cycle(beat_numbers):
res = notes_gen.send(beat)
#print(res)
seq = ""
if beat == 1:
upper += digits2notes[int(res[0])] + "4 "
elif beat == 2:
for i in range(beat):
upper += digits2notes[int(res[i])] + "8 "
elif beat == 3:
upper += " \\times 2/3 { "
note = digits2notes[int(res[0])]
upper += note + "8 "
for i in range(1, beat):
note = digits2notes[int(res[i])]
upper += note + " "
upper += "}"
#elif beat == 4:
# for i in range(beat):
# upper += digits2notes[int(res[i])] + "16 "
elif beat == 5:
upper += " \\times 4/5 { "
note = digits2notes[int(res[0])]
upper += note + "16 "
for i in range(1, beat):
note = digits2notes[int(res[i])]
upper += note + " "
upper += "}"
elif beat == 8:
upper += " \\times 8/8 { "
note = digits2notes[int(res[0])]
upper += note + "32 "
for i in range(1, beat):
note = digits2notes[int(res[i])]
upper += note + " "
upper += "}"
if counter > number_of_notes:
break
if old_res:
accord = set()
for i in range(len(old_res)):
note = digits2notes[int(old_res[i])]
accord.add(note)
accord = " ".join(list(accord))
lower += "<" + accord + ">"
else:
lower += "r4 "
counter += 1
old_res = res
return upper + "}", lower + "}"
def create_score_header(key="e \major", time="5/4"):
""" This creates the score header """
upper = r"""
\version "2.20.0"
upper = \fixed c' {
\clef treble
\key """ + key + r"""
\time """ + time + r"""
"""
lower = r"""
lower = \fixed c {
\clef bass
\key """ + key + r"""
\time """ + time + r"""
"""
return upper, lower
def create_score(upper,
lower,
number_seq):
""" create score combines everything to the final score """
title = ",".join([str(x) for x in number_seq])
score = upper + "\n" + lower + "\n" + r"""
\header {
title = " """
score += title
score += r""" Fibonacci"
composer = "Bernd Klein"
}
\score {
\new PianoStaff \with { instrumentName = "Piano" }
<<
\new Staff = "upper" \upper
\new Staff = "lower" \lower
>>
\layout { }
\midi {\tempo 4 = 45 }
}
"""
return score
from itertools import cycle
number_seq = (2021,)
x = notesgenerator(fib, *number_seq)
print(next(x))
upper, lower = create_score_header()
t = (1, 1, 2, 3, 5, 5, 3, 2, 1, 1, 2, 3, 5, 8, 8, 5, 3, 2, 1, 1)
# = (1, 1, 2, 3, 5, 8)
ng = notesgenerator(fib, *number_seq)
number_of_notes = next(ng)
print(f"n of notes: {number_of_notes}")
upper, lower = create_variable_score(ng,
upper,
lower,
t,
number_of_notes)
score = create_score(upper, lower, number_seq)
open("score.ly", "w").write(score)
OUTPUT:
423 n of notes: 423 13555
With the previous code we created the file score.ly which is the lilypond reüresentation of our score. To create a PDF version and a midi file, we have to call lilypond on a command line with
lilypond score.ly
The following call with !lilypond score.ly
is the equivalent in a jupter notebook cell:
!lilypond score.ly
OUTPUT:
GNU LilyPond 2.20.0 Processing `score.ly' Parsing... Interpreting music...[8][16][24][32][40][48][56][64][72][80] Preprocessing graphical objects... Interpreting music... MIDI output to `score.midi'... Finding the ideal number of pages... Fitting music on 6 or 7 pages... Drawing systems... Layout output to `/tmp/lilypond-TcDnC5'... Converting to `score.pdf'... Deleting `/tmp/lilypond-TcDnC5'... Success: compilation successfully completed
In case you cannot create create it yourself, you can downoad it here:
We created the mp3 file with the following Linux commands:
timidity score.midi -Ow -o - | ffmpeg -i - -acodec libmp3lame -ab 64k score.mp3
The program timidity
can also be used to listen to the midi file by calling it on the command line:
timidity score.midi
Polyphonic Score from Fibonacci Numbers
While the first score consists of a melody underlaid with chords, we now want to create a score that has a melody both in the left and right hand.
def create_polyphone_score(notes_gen,
upper,
lower,
beat_numbers,
number_of_notes=80):
""" This function is similar to create_variable score
but it creates melodies in the left and right hand."""
counter = 0
old_res = ""
group = ""
print(beat_numbers)
for beat in cycle(beat_numbers):
res = notes_gen.send(beat)
#print(beat, res)
seq = ""
old_group = group
if beat == 1:
group = digits2notes[int(res[0])] + "4 "
upper += group
elif beat == 2:
group = ""
for i in range(beat):
group += digits2notes[int(res[i])] + "8 "
upper += group
elif beat == 3:
group = ""
group += "\\times 2/3 { "
note = digits2notes[int(res[0])]
group += note + "8 "
for i in range(1, beat):
note = digits2notes[int(res[i])]
group += note + " "
group += "}"
upper += group
#elif beat == 4:
# for i in range(beat):
# upper += digits2notes[int(res[i])] + "16 "
elif beat == 5:
group = ""
group += "\\times 4/5 { "
note = digits2notes[int(res[0])]
group += note + "16 "
for i in range(1, beat):
note = digits2notes[int(res[i])]
group += note + " "
group += "}"
upper += group
elif beat == 8:
group = ""
group += "{ "
note = digits2notes[int(res[0])]
group += note + "32 "
for i in range(1, beat):
note = digits2notes[int(res[i])]
group += note + " "
group += "}"
upper += group
elif beat == 13:
group = ""
group += "\\times 4/13 { "
note = digits2notes[int(res[0])]
group += note + "16 "
for i in range(1, beat):
note = digits2notes[int(res[i])]
group += note + " "
group += "}"
upper += group
if old_group:
lower += old_group
else:
lower += "r4 "
counter += beat
if counter > number_of_notes:
break
old_res = res
return upper + "}", lower + "}"
from itertools import cycle
upper, lower = create_score_header()
t = (1, 1, 2, 3, 5, 5, 3, 2, 1, 1, 2, 3, 5, 8, 8, 5, 3, 2, 1, 1)
t = (1, 1, 2, 3, 5, 8, 13, 5, 1, 1)
ng = notesgenerator(fib, *number_seq)
number_of_notes = next(ng)
print(f"n of notes: {number_of_notes}")
upper, lower = create_polyphone_score(ng,
upper,
lower,
t,
number_of_notes*3)
score = create_score(upper, lower, number_seq)
open("score2.ly", "w").write(score)
OUTPUT:
n of notes: 423 (1, 1, 2, 3, 5, 8, 13, 5, 1, 1) 14497
!lilypond score2.ly
OUTPUT:
GNU LilyPond 2.20.0 Processing `score2.ly' Parsing... Interpreting music...[8][16][24][32][40][48][56][64][64] Preprocessing graphical objects... Interpreting music... MIDI output to `score2.midi'... Finding the ideal number of pages... Fitting music on 4 or 5 pages... Drawing systems... Layout output to `/tmp/lilypond-jakQyo'... Converting to `score2.pdf'... Deleting `/tmp/lilypond-jakQyo'... Success: compilation successfully completed
You can download the previously created files:
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Pentatonic Score from Fibonacci Numbers
In the following example, we will use a Pentatonic scale:
digits2notes = ["a,", "c", "d",
"e", "g", "a",
"c'", "d'", "e'",
"g'"]
from itertools import cycle
upper, lower = create_score_header(key="a \minor", time="4/4")
t = (1, 1, 2, 3, 5, 5, 3, 2, 1, 1, 2, 3, 5, 8, 8, 5, 3, 2, 1, 1)
t = (1, 1, 2, 3, 5, 8, 13, 5, 1, 1)
ng = notesgenerator(fib, *number_seq)
number_of_notes = next(ng)
print(f"n of notes: {number_of_notes}")
upper, lower = create_polyphone_score(ng,
upper,
lower,
t,
number_of_notes*3)
score = create_score(upper, lower, number_seq)
open("score3.ly", "w").write(score)
OUTPUT:
n of notes: 423 (1, 1, 2, 3, 5, 8, 13, 5, 1, 1) 11591
!lilypond score3.ly
OUTPUT:
GNU LilyPond 2.20.0 Processing `score3.ly' Parsing... Interpreting music...[8][16][24][32][40][48][56][64][72][80][80] Preprocessing graphical objects... Interpreting music... MIDI output to `score3.midi'... Finding the ideal number of pages... Fitting music on 7 or 8 pages... Drawing systems... Layout output to `/tmp/lilypond-Lj1dnx'... Converting to `score3.pdf'... Deleting `/tmp/lilypond-Lj1dnx'... Success: compilation successfully completed
You can download the previously created files:
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.