FMP AudioLabs
C1

Frequency and Pitch


Following Section 1.3.2 of [Müller, FMP, Springer 2015], we cover in this notebook the relation between frequency and pitch.

Sinusoids

A sound wave can be visually represented by a waveform. If the points of high and low air pressure repeat in an alternating and regular fashion, the resulting waveform is called periodic. In this case, the period of the wave is defined as the time required to complete a cycle. The frequency, measured in Hertz (Hz), is the reciprocal of the period. The simplest type of periodic waveform is a sinusoid, which is completely specified by its frequency, its amplitude (the peak deviation of the sinusoid from its mean), and its phase (determining where in its cycle the sinusoid is at time zero). The following figure shows a sinusoid with frequency 4 Hz.

C1

Audible Frequency Range

The higher the frequency of a sinusoidal wave, the higher it sounds. The audible frequency range for humans is between about 20 Hz and 20000 Hz (20 kHz). Other species have different hearing ranges. For example, the top end of a dog's hearing range is about 45 kHz, a cat's is 64 kHz, while bats can even detect frequencies beyond 100 kHz. This is why one can use a dog whistle, which emits ultrasonic sound beyond the human hearing capability, to train and to command animals without disturbing nearby people.

In the following experiment, we generate a chirp signal which frequency increases by a factor of two (one octave) every second. Starting with 80 Hz, the frequency raises to 20480 Hz over a total duration of 8 seconds.

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

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

Fs = 44100
dur = 1
freq_start = 80 * 2**np.arange(8)
for f in freq_start:
    if f==freq_start[0]:
        chirp, t = libfmp.c1.generate_chirp_exp_octave(freq_start=f, dur=dur, Fs=Fs, amp=1)
    else:
        chirp_oct, t = libfmp.c1.generate_chirp_exp_octave(freq_start=f, dur=dur, Fs=Fs, amp=1)
        chirp = np.concatenate((chirp, chirp_oct))

ipd.display(ipd.Audio(chirp, rate=Fs))

Similarly, we generate a chirp signal starting with 640 Hz and going down 20 Hz over a total duration of 10 seconds.

In [2]:
Fs = 8000
dur = 2
freq_start = 20 * 2**np.arange(5)
for f in freq_start:
    if f==freq_start[0]:
        chirp, t = libfmp.c1.generate_chirp_exp_octave(freq_start=f, dur=dur, Fs=Fs, amp=1)
    else:
        chirp_oct, t = libfmp.c1.generate_chirp_exp_octave(freq_start=f, dur=dur, Fs=Fs, amp=1)
        chirp = np.concatenate((chirp,chirp_oct))    
        
chirp = chirp[::-1]    
ipd.display(ipd.Audio(chirp, rate=Fs))

Pitches and Center Frequencies

The sinusoid can be considered the prototype of an acoustic realization of a musical note. Sometimes the sound resulting from a sinusoid is called a harmonic sound or pure tone. The notion of frequency is closely related to what determines the pitch of a sound. In general, pitch is a subjective attribute of sound. In the case of complex sound mixtures its relation to frequency can be especially ambiguous. In the case of pure tones, however, the relation between frequency and pitch is clear. For example, a sinusoid having a frequency of 440 Hz corresponds to the pitch A4. This particular pitch is known as concert pitch, and it is used as the reference pitch to which a group of musical instruments are tuned for a performance. Since a slight change in frequency does not necessarily lead to a perceived change, one usually associates an entire range of frequencies with a single pitch.

Two frequencies are perceived as similar if they differ by a power of two, which has motivated the notion of an octave. For example, the perceived distance between the pitches A3 (220 Hz) and A4 (440 Hz is the same as the perceived distance between the pitches A4 and A5 (880 Hz). In other words, the human perception of pitch is logarithmic in nature. This perceptual property has already been used to define the equal-tempered scale that subdivides an octave into twelve semitones based on a logarithmic frequency axis. More formally, using the MIDI note numbers, we can associate to each pitch p[0:127] a center frequency Fpitch(p) (measured in Hz) defined as follows:

Fpitch(p)=2(p69)/12440.

The MIDI note number p=69 serves as reference and corresponds to the pitch A4 (440 Hz). Increasing the pitch number by 12 (an octave) leads to an increase by a factor of two. The following figure shows a portion of a piano keyboard with the keys labeled by the pitch names and the MIDI note numbers.

C1

Using the formula for Fpitch, we compute in the next code cell the center frequencies for all MIDI pitch p[21:108] corresponding to the notes of a standard piano keyboard (A0 to C8).

In [3]:
def f_pitch(p):
    """Compute center frequency for (single or array of) MIDI note numbers

    Notebook: C1/C1S3_FrequencyPitch.ipynb

    Args:
        p (float or np.ndarray): MIDI note numbers

    Returns:
        freq_center (float or np.ndarray): Center frequency
    """
    freq_center = 2 ** ((p - 69) / 12) * 440
    return freq_center

chroma = ['A ', 'A#', 'B ', 'C ', 'C#', 'D ', 'D#', 'E ', 'F ', 'F#', 'G ', 'G#']

for p in range(21, 109):
    print('p = %3d (%2s%1d), freq = %7.2f ' % (p, chroma[(p-69) % 12], (p//12-1), f_pitch(p)))
p =  21 (A 0), freq =   27.50 
p =  22 (A#0), freq =   29.14 
p =  23 (B 0), freq =   30.87 
p =  24 (C 1), freq =   32.70 
p =  25 (C#1), freq =   34.65 
p =  26 (D 1), freq =   36.71 
p =  27 (D#1), freq =   38.89 
p =  28 (E 1), freq =   41.20 
p =  29 (F 1), freq =   43.65 
p =  30 (F#1), freq =   46.25 
p =  31 (G 1), freq =   49.00 
p =  32 (G#1), freq =   51.91 
p =  33 (A 1), freq =   55.00 
p =  34 (A#1), freq =   58.27 
p =  35 (B 1), freq =   61.74 
p =  36 (C 2), freq =   65.41 
p =  37 (C#2), freq =   69.30 
p =  38 (D 2), freq =   73.42 
p =  39 (D#2), freq =   77.78 
p =  40 (E 2), freq =   82.41 
p =  41 (F 2), freq =   87.31 
p =  42 (F#2), freq =   92.50 
p =  43 (G 2), freq =   98.00 
p =  44 (G#2), freq =  103.83 
p =  45 (A 2), freq =  110.00 
p =  46 (A#2), freq =  116.54 
p =  47 (B 2), freq =  123.47 
p =  48 (C 3), freq =  130.81 
p =  49 (C#3), freq =  138.59 
p =  50 (D 3), freq =  146.83 
p =  51 (D#3), freq =  155.56 
p =  52 (E 3), freq =  164.81 
p =  53 (F 3), freq =  174.61 
p =  54 (F#3), freq =  185.00 
p =  55 (G 3), freq =  196.00 
p =  56 (G#3), freq =  207.65 
p =  57 (A 3), freq =  220.00 
p =  58 (A#3), freq =  233.08 
p =  59 (B 3), freq =  246.94 
p =  60 (C 4), freq =  261.63 
p =  61 (C#4), freq =  277.18 
p =  62 (D 4), freq =  293.66 
p =  63 (D#4), freq =  311.13 
p =  64 (E 4), freq =  329.63 
p =  65 (F 4), freq =  349.23 
p =  66 (F#4), freq =  369.99 
p =  67 (G 4), freq =  392.00 
p =  68 (G#4), freq =  415.30 
p =  69 (A 4), freq =  440.00 
p =  70 (A#4), freq =  466.16 
p =  71 (B 4), freq =  493.88 
p =  72 (C 5), freq =  523.25 
p =  73 (C#5), freq =  554.37 
p =  74 (D 5), freq =  587.33 
p =  75 (D#5), freq =  622.25 
p =  76 (E 5), freq =  659.26 
p =  77 (F 5), freq =  698.46 
p =  78 (F#5), freq =  739.99 
p =  79 (G 5), freq =  783.99 
p =  80 (G#5), freq =  830.61 
p =  81 (A 5), freq =  880.00 
p =  82 (A#5), freq =  932.33 
p =  83 (B 5), freq =  987.77 
p =  84 (C 6), freq = 1046.50 
p =  85 (C#6), freq = 1108.73 
p =  86 (D 6), freq = 1174.66 
p =  87 (D#6), freq = 1244.51 
p =  88 (E 6), freq = 1318.51 
p =  89 (F 6), freq = 1396.91 
p =  90 (F#6), freq = 1479.98 
p =  91 (G 6), freq = 1567.98 
p =  92 (G#6), freq = 1661.22 
p =  93 (A 6), freq = 1760.00 
p =  94 (A#6), freq = 1864.66 
p =  95 (B 6), freq = 1975.53 
p =  96 (C 7), freq = 2093.00 
p =  97 (C#7), freq = 2217.46 
p =  98 (D 7), freq = 2349.32 
p =  99 (D#7), freq = 2489.02 
p = 100 (E 7), freq = 2637.02 
p = 101 (F 7), freq = 2793.83 
p = 102 (F#7), freq = 2959.96 
p = 103 (G 7), freq = 3135.96 
p = 104 (G#7), freq = 3322.44 
p = 105 (A 7), freq = 3520.00 
p = 106 (A#7), freq = 3729.31 
p = 107 (B 7), freq = 3951.07 
p = 108 (C 8), freq = 4186.01 

From this formula, it follows that the frequency ratio of two subsequent pitches p+1 and p is constant:

Fpitch(p+1)/Fpitch(p)=21/121.059463

Generalizing the notion of semitones, the cent denotes a logarithmic unit of measure used for musical intervals. By definition, an octave is divided into 1200 cents, so that each semitone corresponds to 100 cents. The difference in cents between two frequencies, say ω1 and ω2, is given by

log2(ω1ω2)1200.

The interval of one cent is much too small to be heard between successive notes. The threshold of what is perceptible, also called the just noticeable difference, varies from person to person and depends on other aspects such as the timbre and the musical context. As a rule of thumb, normal adults are able to recognize pitch differences as small as 25 cents very reliably, with differences of 10 cents being recognizable only by trained listeners. As in illustration, we generate a sinusoid of 440 Hz used as reference and further sinusoids with various differences.

In [4]:
def difference_cents(freq_1, freq_2):
    """Difference between two frequency values specified in cents

    Notebook: C1/C1S3_FrequencyPitch.ipynb

    Args:
        freq_1 (float): First frequency
        freq_2 (float): Second frequency

    Returns:
        delta (float): Difference in cents
    """
    delta = np.log2(freq_1 / freq_2) * 1200
    return delta
 
def generate_sinusoid(dur=5, Fs=1000, amp=1, freq=1, phase=0):
    """Generation of sinusoid

    Notebook: C1/C1S3_FrequencyPitch.ipynb

    Args:
        dur (float): Duration (in seconds) (Default value = 5)
        Fs (scalar): Sampling rate (Default value = 1000)
        amp (float): Amplitude of sinusoid (Default value = 1)
        freq (float): Frequency of sinusoid (Default value = 1)
        phase (float): Phase of sinusoid (Default value = 0)

    Returns:
        x (np.ndarray): Signal
        t (np.ndarray): Time axis (in seconds)

    """
    num_samples = int(Fs * dur)
    t = np.arange(num_samples) / Fs
    x = amp * np.sin(2*np.pi*(freq*t-phase))
    return x, t

dur = 5
Fs = 4000
pitch = 69
ref = f_pitch(pitch)
freq_list = ref + np.array([0,2,5,10,ref])
for freq in freq_list:
    x, t = generate_sinusoid(dur=dur, Fs=Fs, freq=freq)
    print('freq = %0.1f Hz (MIDI note number 69 + %0.2f cents)' % (freq, difference_cents(freq,ref)))
    ipd.display(ipd.Audio(data=x, rate=Fs))  
freq = 440.0 Hz (MIDI note number 69 + 0.00 cents)
freq = 442.0 Hz (MIDI note number 69 + 7.85 cents)
freq = 445.0 Hz (MIDI note number 69 + 19.56 cents)
freq = 450.0 Hz (MIDI note number 69 + 38.91 cents)
freq = 880.0 Hz (MIDI note number 69 + 1200.00 cents)
Acknowledgment: This notebook was created by Meinard Müller and Stefan Balke.
C0 C1 C2 C3 C4 C5 C6 C7 C8