This post is about how to produce retro sound effects using Python. These kinds of effects added greatly to the excitement of playing early computer games and are still loved by many.
In my youth I spent a lot of time playing games (as well as programming in BASIC) on what were then modern home computers such as the ZX Spectrum and the BBC Micro Computer. These computers had some great features in terms of making sounds, which are quite hard to reproduce using today’s modern languages.
I have spent quite a bit of time trying to get this to work in Python and I’ve finally found a solution I’m happy with, and I want to share it with you.
The function get_sound()
in the code listing provided below allows you to create samples based either on piano key numbers or smaller pitch intervals. One example is given for playing a natura minor scale – a b c d e f g a'
.
In order to use the function, you will need to install the simpleaudio package as well as numpy. simpleaudio mangaes the actual sound production, while numpy is used for the maths which create the samples.
I’ve set the sample rate to a very low value of 8000
since we a re not looking for hi-fidelity sound – just good old beeps and buzzes.
Warning: turn down you speakers before trying this code, to protect your ears and speakers!
Here’s a break down of what the function does:
- Formulas for converting note vaues into frequencies, either with 12 semitones per octave or 48
- Creating a whole bunch of values for the sample base on the
sin
function - Amplify the values to the biggest 16-bit positive integer available also taking into account the volume argument
- Handle clipping. If you have a loud sound that drops off suddenly it sounds bad. This part of the code smooths off the clipping in many cases
Getting Creative with Retro Sound Effects in Python
I’ve given you a foundation with the code below for producing retro sound effects in Python. The rest is really up to you. There are so many ways you could use this functionality.
In the early days of computer sound effects, they may not have thought about it like this, but they were basically engaging in algorithmic composition and now you can too.
Here’s a few things to try:
- Generating random pitches
- Using loops to move through sequences of pitches
- Using the modules (
%
) operator to wrap pitches into a certain range. - Creating weird and wonderful sound effects or just good old fashioned chiptune
Retro Sound Effects in Python – Code Listing
import numpy as np
import simpleaudio as sa
SAMPLE_RATE = 8000
def get_sound(note, duration, volume=0.5):
time_vector = np.linspace(0, duration, int(duration * SAMPLE_RATE), False)
# frequency = 440 * 2 ** ((note - 49) / 12) # 440Hz is key 49 on a piano
frequency = 220 * 2 ** (note / 48)
# Generate audio samples
audio = np.sin(frequency * time_vector * 2 * np.pi)
# normalize to 16-bit range
audio *= volume * 32767 / np.max(np.abs(audio))
# Fade out the end
release_time = 0.05 # Seconds
if duration >= release_time:
release_samples = int(np.ceil(release_time * SAMPLE_RATE))
fade_curve = np.linspace(volume, 0.0, num=release_samples)
audio[-release_samples:] *= fade_curve
# convert to 16-bit data
audio = audio.astype(np.int16)
return audio
##scale = [get_sound(note, 0.5) for note in [49, 51, 52, 54, 56, 57, 59, 61]]
##for note in scale:
## wave_object = sa.WaveObject(note, 1, 2, SAMPLE_RATE)
## play_object = wave_object.play()
## play_object.wait_done()
blips = [get_sound(i, 0.1) for i in range(48, 92, 1)]
for blip in blips:
wave_object = sa.WaveObject(blip, 1, 2, SAMPLE_RATE)
play_object = wave_object.play()
play_object.wait_done()
blips = [get_sound(i, 0.1) for i in range(48, 92, 1)]
for blip in blips[::-1]:
wave_object = sa.WaveObject(blip, 1, 2, SAMPLE_RATE)
play_object = wave_object.play()
play_object.wait_done()
I hope you have fun making retro sound effects with Python – let me know in the comments how you get on.
Happy computing. 🙂