Hello, Scale

python
pydub
simpleaudio
Author

Alex Chisholm

Published

May 18, 2025

A major scale is foundational to Western music and is one of the first concepts taught to music students. Let’s start this blog by having Python play a C scale using Pydub and Simpleaudio.

A Primer on Musical Notes

A musical note can be represented in several ways. On sheet music, it appears on a staff, which uses a specific clef such as the treble clef to indicate pitch. The same note can also be written using a letter name (like C, D, or E) combined with a number to indicate its octave, with lower numbers representing deeper, lower-pitched sounds. Additionally, each note corresponds to a specific frequency, commonly measured in Hertz (Hz).

We can overlay these visually.

Listen to these eight notes that range from C1 (32.70 Hz) to C8 (4186.01 Hz):

Piano Frequencies

An 88-key piano ranges from A0 to C8 with the following frequencies. Notice that higher octaves have higher frequencies and that each octave jump doubles the frequency.

Frequencies by note letter and octave number
Note 0 1 2 3 4 5 6 7 8
C - 32.70 65.41 130.81 261.63 523.25 1046.50 2093.00 4186.01
C#/Db - 34.65 69.30 138.59 277.18 554.37 1108.73 2217.46 -
D - 36.71 73.42 146.83 293.66 587.33 1174.66 2349.32 -
D#/Eb - 38.89 77.78 155.56 311.13 622.25 1244.51 2489.02 -
E - 41.20 82.41 164.81 329.63 659.25 1318.51 2637.02 -
F - 43.65 87.31 174.61 349.23 698.46 1396.91 2793.83 -
F#/Gb - 46.25 92.50 185.00 369.99 739.99 1479.98 2959.96 -
G - 49.00 98.00 196.00 392.00 783.99 1567.98 3135.96 -
G#/Ab - 51.91 103.83 207.65 415.30 830.61 1661.22 3322.44 -
A 27.50 55.00 110.00 220.00 440.00 880.00 1760.00 3520.00 -
A#/Bb 29.14 58.27 116.54 233.08 466.16 932.33 1864.66 3729.31 -
B 30.87 61.74 123.47 246.94 493.88 987.77 1975.53 3951.07 -

The note C4 (261.63 Hz) is known as Middle C. The note A4 (440 Hz) is often the Tuning A used by orchestras before rehearsals and concerts to have musicians play in tune together.

Python the Musician

Load the packages

Now let’s see how Python can take the role of musician. We’ll begin by importing our helper packages.

Code
from pydub.generators import Sine
import simpleaudio as sa

Create a note dictionary

Next we’ll create a dictionary with the notes of a c scale beginning with middle C.

Code
notes = {
    "C4": 261.63,
    "D4": 293.66,
    "E4": 329.63,
    "F4": 349.23,
    "G4": 392.00,
    "A4": 440.00,
    "B4": 493.88,
    "C5": 523.25
}

Turn frequency values into tones

Next we’ll define in milliseconds how quickly we want the notes in the scale to be played by setting note_duration. Then we’ll loop through each of the values in our dictionary to build the scale, appending the tones to an empty list named scale. Finally, we’ll convert into a continuous audio clip by merging all the tones into a single Audio Segment with sum().

Code
note_duration = 400 # in milliseconds
scale = []
for freq in notes.values():
    tone = Sine(freq).to_audio_segment(duration=note_duration)  
    scale.append(tone)

c_scale = sum(scale)

The final c_scale PyDub Audio Segment contains a bunch of useful information.

Code
print("Duration (ms):", len(c_scale))  # Total duration in milliseconds
print("Frame rate (Hz):", c_scale.frame_rate)  # Sample rate
print("Channels:", c_scale.channels)  # Mono (1) or stereo (2)
print("Sample width (bytes):", c_scale.sample_width)  # Usually 2 (16-bit)
print("Frame width (bytes):", c_scale.frame_width)  # channels * sample_width
print("Number of frames:", c_scale.frame_count())  # Total number of frames
print("Max amplitude:", c_scale.max)  # Maximum absolute amplitude value
print("dBFS (volume level):", c_scale.dBFS)  # Average decibels relative to full scale
Duration (ms): 3200
Frame rate (Hz): 44100
Channels: 1
Sample width (bytes): 2
Frame width (bytes): 2
Number of frames: 141120.0
Max amplitude: 32767
dBFS (volume level): -3.0108529080336788

Playing the scale

Using Simpleaudio we can play the scale.

Code
play_obj = sa.play_buffer(
    audio_data = c_scale.raw_data,
    num_channels=c_scale.channels,
    bytes_per_sample=c_scale.sample_width,
    sample_rate=c_scale.frame_rate
)

play_obj.wait_done()

Exporting the WAV file

Finally, if we want, we can save the Audio Segment.

Code
c_scale.export("c_scale.wav", format="wav")

Not bad for a few lines of code!