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~\mathrm{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~\mathrm{Hz}$ and $20000~\mathrm{Hz}$ ($20~\mathrm{kHz}$). Other species have different hearing ranges. For example, the top end of a dog's hearing range is about $45~\mathrm{kHz}$, a cat's is $64~\mathrm{kHz}$, while bats can even detect frequencies beyond $100~\mathrm{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~\mathrm{Hz}$, the frequency raises to $20480~\mathrm{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~\mathrm{Hz}$ and going down $20~\mathrm{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~\mathrm{Hz}$ corresponds to the pitch $\mathrm{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 $\mathrm{A3}$ ($220~\mathrm{Hz}$) and $\mathrm{A4}$ ($440~\mathrm{Hz}$ is the same as the perceived distance between the pitches $\mathrm{A4}$ and $\mathrm{A5}$ ($880~\mathrm{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\in[0:127]$ a center frequency $F_\mathrm{pitch}(p)$ (measured in Hz) defined as follows:

$$ F_\mathrm{pitch}(p) = 2^{(p-69)/12} \cdot 440. $$

The MIDI note number $p=69$ serves as reference and corresponds to the pitch $\mathrm{A4}$ ($\mathrm{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 $F_\mathrm{pitch}$, we compute in the next code cell the center frequencies for all MIDI pitch $p\in[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:

$$ F_\mathrm{pitch}(p+1)/F_\mathrm{pitch}(p) = 2^{1/12} \approx 1.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 $\omega_1$ and $\omega_2$, is given by

$$ \log_2\left(\frac{\omega_1}{\omega_2}\right)\cdot 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~\mathrm{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