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.
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
- 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. 🙂