FMP AudioLabs
C1

Pythagorean Tuning


Following Excercise 1.10 and Section 5.1.1.2 of [Müller, FMP, Springer 2015], we discuss in this notebook the tuning system introduced by Pythagoras as well as the Pythagorean comma.

Pythagorean Comma

The oldest known tuning system was introduced by the Greek philosopher and mathematician Pythagoras (sixth century BC). Pythagorean tuning is a system of musical tuning in which the frequency ratios of all intervals are based on the ratio $3:2$ as found in the harmonic series. This ratio is also known as the perfect fifth. We now construct a scale starting with the center frequency $\omega$ of a root note (corresponding to the frequency ratio $1$). Then, we successively multiply the frequency value by a factor of $3/2$, and if necessary, divide it by two such that all frequency values lie between $\omega$ and $2\cdot\omega$ (corresponding to frequency ratios between $1$ and $2$). We repeat this procedure to produce $13$ frequency values (and $13$ frequency ratios). The last (the 13$^\mathrm{th}$) frequency ratio is also known as the Pythagorean comma, which indicates the degree of inconsistency when trying to define a twelve-tone scale using only perfect fifths.

In the following code example, we construct the thirteen frequency ratios. Furthermore, these frequency ratios are compared with the one obtained from equal-tempered scale (the difference is specified in cents).

In [1]:
import numpy as np
import IPython.display as ipd

import sys
sys.path.append('..')
import libfmp.c1

def compute_ratio(num3, num2):
    return (3 ** num3) / (2 ** num2)

num3 = 0
num2 = 0
note = 0
diff = 0
s = np.zeros((13, 6))
s[0] = [0, note, num3, num2, compute_ratio(num3, num2), diff] 

for m in range(1, 13):
    note = (note + 7) % 12
    if note == 0: note = 12
    num3 = num3 + 1
    num2 = num2 + 1 
    ratio = compute_ratio(num3, num2)
    if ratio > 2:
        num2 = num2 + 1
        ratio = compute_ratio(num3, num2)
    diff = (np.log2(ratio) - 1 / 12) * 1200
    diff = np.remainder(diff, 100)        
    s[m] = [m, note, num3, num2, compute_ratio(num3, num2), diff]   

for m in range(13):
    print('m = %2i, note = %2i, num3 = %2i, num2 = %2i, ratio = %6i:%6i = %7.4f, diff = %+6.2f' 
           % (s[m, 0], s[m, 1], s[m, 2], s[m, 3], 3 ** s[m, 2], 2 ** s[m, 3], s[m, 4], s[m, 5]))    
    
print('Pythagorean comma: %7.4f (%+6.2f cents)' % (s[12, 4], s[12, 5]))    

dur = 4  # seconds
Fs = 4000  # sampling rate
freq = 440
x, t = libfmp.c1.generate_sinusoid(dur=dur, Fs=Fs, freq=freq)
freq_pyt_comma = freq * s[12, 4]
x_pyt_comma, t = libfmp.c1.generate_sinusoid(dur=dur, Fs=Fs, freq=freq_pyt_comma)

print()
print('Sinsoid of 440 Hz (A4):')
ipd.display(ipd.Audio(data=x, rate=Fs))
print('Sinsoid with %5.4f * 440 = %7.4f Hz:' % (s[12, 4], freq_pyt_comma))
ipd.display(ipd.Audio(data=x_pyt_comma, rate=Fs))
    
m =  0, note =  0, num3 =  0, num2 =  0, ratio =      1:     1 =  1.0000, diff =  +0.00
m =  1, note =  7, num3 =  1, num2 =  1, ratio =      3:     2 =  1.5000, diff =  +1.96
m =  2, note =  2, num3 =  2, num2 =  3, ratio =      9:     8 =  1.1250, diff =  +3.91
m =  3, note =  9, num3 =  3, num2 =  4, ratio =     27:    16 =  1.6875, diff =  +5.87
m =  4, note =  4, num3 =  4, num2 =  6, ratio =     81:    64 =  1.2656, diff =  +7.82
m =  5, note = 11, num3 =  5, num2 =  7, ratio =    243:   128 =  1.8984, diff =  +9.78
m =  6, note =  6, num3 =  6, num2 =  9, ratio =    729:   512 =  1.4238, diff = +11.73
m =  7, note =  1, num3 =  7, num2 = 11, ratio =   2187:  2048 =  1.0679, diff = +13.69
m =  8, note =  8, num3 =  8, num2 = 12, ratio =   6561:  4096 =  1.6018, diff = +15.64
m =  9, note =  3, num3 =  9, num2 = 14, ratio =  19683: 16384 =  1.2014, diff = +17.60
m = 10, note = 10, num3 = 10, num2 = 15, ratio =  59049: 32768 =  1.8020, diff = +19.55
m = 11, note =  5, num3 = 11, num2 = 17, ratio = 177147:131072 =  1.3515, diff = +21.51
m = 12, note = 12, num3 = 12, num2 = 19, ratio = 531441:524288 =  1.0136, diff = +23.46
Pythagorean comma:  1.0136 (+23.46 cents)

Sinsoid of 440 Hz (A4):
Sinsoid with 1.0136 * 440 = 446.0030 Hz:

Pythagorean Scale

The oldest known tuning system was introduced by the Greek philosopher and mathematician Pythagoras (sixth century BC). As said before, the geometrically motivated Pythagorean tuning is based only on the frequency ratio $2:1$ of the octave and the ratio $3:2$ of the fifth. When only adding fifths and possibly subtracting octaves, one obtains the scale defined above. When allowing adding and subtracting fifths and octaves, one obtains the Pythagorean scale. This results in intervals that can be expressed by frequency ratios that involve only powers of two or powers of three.

In the following code, we consider the chromatic scale starting C4 and ending with C5. For all 13 notes, we produces sinusoids once using the center frequencies of the equal-tempered scale and once the frequencies obtained by the Pythagorean intervals.

In [2]:
import pandas as pd
from collections import OrderedDict

# Computation of frequencies and differences
pyt_frac = ['1:1', '$2^8:3^5$', '$3^2:2^3$', '$2^5:3^3$', '$3^4:2^6$', '$2^2:3$', '$3^6:2^9$', 
                   '$3:2$', '$2^7:3^4$', '$3^3:2^4$', '$2^4:3^2$', '$3^5:2^7$', '$2:1$']
pyt_ratio = np.asarray([1, 256/243, 9/8, 32/27, 81/64, 4/3, 729/512, 3/2, 128/81, 27/16, 16/9, 243/128, 2])
p = 60
freq = libfmp.c1.f_pitch(p)
freq_pyt = pyt_ratio * freq
notes = np.asarray(range(p, p + 13))
freq_center = libfmp.c1.f_pitch(notes)
freq_deviation_cents =  libfmp.c1.difference_cents(freq_pyt, freq_center)

# Generation of sinusoids
dur = 4 #seconds
Fs = 4000 #sampling rate

sinusoid_freq_center = []
for freq in freq_center:
    x, t = libfmp.c1.generate_sinusoid(dur=dur, Fs=Fs, freq=freq)
    sinusoid_freq_center.append(x)

sinusoid_freq_pyt = []    
for freq in freq_pyt:
    x, t = libfmp.c1.generate_sinusoid(dur=dur, Fs=Fs, freq=freq)
    sinusoid_freq_pyt.append(x)    

# Generation of html table    
audio_tag_html_center = []
for i in range(len(freq_center)):
    audio_tag = ipd.Audio( sinusoid_freq_center[i], rate=Fs)
    audio_tag_html = audio_tag._repr_html_().replace('\n', '').strip()
    audio_tag_html = audio_tag_html.replace('<audio ', '<audio style="width: 100px; "')  
    audio_tag_html_center.append(audio_tag_html)
    
audio_tag_html_pyt = []
for i in range(len(freq_pyt)):
    audio_tag = ipd.Audio(sinusoid_freq_pyt[i], rate = Fs)
    audio_tag_html = audio_tag._repr_html_().replace('\n', '').strip()
    audio_tag_html = audio_tag_html.replace('<audio ', '<audio style="width: 100px; "')    
    audio_tag_html_pyt.append(audio_tag_html)

pd.set_option('display.max_colwidth', None)    
df = pd.DataFrame(OrderedDict([('Note', ['C4', 'C$^\sharp$4', 'D4', 'D$^\sharp$4', 'E4', 'F4',
                                         'F$^\sharp$4', 'G4', 'G$^\sharp$4', 'A4', 'A$^\sharp$4',
                                         'B4', 'C4']),
                               ('ET Freq. (Hz)', freq_center),
                               ('ET Sinusoid', audio_tag_html_center),  
                               ('Pyt. Ratio ', pyt_frac),
                               ('Pyt. Freq. (Hz)', freq_pyt),
                               ('Pyt. Sinusoid', audio_tag_html_pyt),                               
                               ('Difference (Cents)', freq_deviation_cents)]))

df.index = np.arange(1, len(df) + 1)
ipd.HTML(df.to_html(escape=False, float_format='%.2f'))
Out[2]:
Note ET Freq. (Hz) ET Sinusoid Pyt. Ratio Pyt. Freq. (Hz) Pyt. Sinusoid Difference (Cents)
1 C4 261.63 1:1 261.63 0.00
2 C$^\sharp$4 277.18 $2^8:3^5$ 275.62 -9.78
3 D4 293.66 $3^2:2^3$ 294.33 3.91
4 D$^\sharp$4 311.13 $2^5:3^3$ 310.07 -5.87
5 E4 329.63 $3^4:2^6$ 331.12 7.82
6 F4 349.23 $2^2:3$ 348.83 -1.96
7 F$^\sharp$4 369.99 $3^6:2^9$ 372.51 11.73
8 G4 392.00 $3:2$ 392.44