How To Use NumPy for Audio Signal Processing

How To Use NumPy for Audio Signal Processing
Image by Author | Canva

Audio is everywhere, from the music we stream to the voice notes we send and the calls we make. But raw audio isn’t always ready to use straight out of the mic or file. That’s where audio processing comes in.

Audio processing simply means cleaning, changing, or analyzing audio signals to make them more useful. You might want to reduce background noise, detect spoken words, increase volume, or turn sound into visual data. Whether you’re building a podcast app, working with speech recognition, or just playing around with audio effects, being able to process audio is a valuable skill.

Big companies like Spotify, Google, Apple, and Amazon all rely heavily on audio processing. Spotify uses it for music analysis and recommendations, Google and Apple use it in their voice assistants, and Amazon uses it in Alexa to understand voice commands.

In this article, you’ll learn how to use NumPy, a fast and powerful library in Python for working with audio signals. We’ll keep things simple and focus on the basics you need to get started.

Prerequisites

Before we start, there are a few libraries we need to have installed. We’ll be using NumPy for processing the audio data as arrays, matplotlib for visualizing waveforms, and scipy for resampling operations. We’ll also use pydub to load and export audio files like MP3s and WAVs easily, which helps us skip low-level decoding headaches and just focus on working with the actual audio data.

pip install numpy matplotlib scipy pydub

pydub depends on ffmpeg to handle MP3s and other formats. If it’s not already installed on your system, you can install it like this:

For macOS (using Homebrew) users:

brew install ffmpeg

For Ubuntu/Linux users:

sudo apt install ffmpeg

For Windows users:
Download the binaries from https://ffmpeg.org/download.html and add them to your system PATH. Once everything is set up, you’re ready to start working with audio in Python!

Hands-On Project: Audio Signal Processing with NumPy

Now let’s bring everything together in a project. For this, we’ll use pydub for audio handling and numpy for processing. The idea is to show you how these transformations work in practice and explain the “why” and “how” along the way. We will also perform some actions such as viewing the waveform, normalization and scaling, trimming and slicing, reversing the audio, changing Volume (Amplitude Scaling), and so much more.

Step 1: Setting Up and Loading Audio

We’ll begin by loading the audio file using pydub. This library makes it easy to read and manipulate audio files of different formats. Once we load the file, we’ll convert it to a NumPy array so we can process the waveform directly.

from pydub import AudioSegment
import numpy as np
import matplotlib.pyplot as plt

# Load your audio file (MP3, WAV, etc.)
audio_path = "/Users/user/Downloads/pianos-by-jtwayne-7-174717.mp3"
audio = AudioSegment.from_file(audio_path)

# Convert to mono to keep things simple
audio = audio.set_channels(1)

# Extract raw audio samples as a NumPy array
samples = np.array(audio.get_array_of_samples()).astype(np.float32)

# Normalize the waveform to range [-1, 1]
samples /= np.max(np.abs(samples))

# Get sample rate (frames per second)
sample_rate = audio.frame_rate

# Print summary
print(f"Audio duration: {len(samples)/sample_rate:.2f} seconds")
print(f"Sample rate: {sample_rate} Hz")

Output:

Audio duration: 18.86 seconds
Sample rate: 44100 Hz

Step 2: Viewing the Waveform

Let’s plot the waveform to see what the raw audio signal looks like. This will help us understand the structure of the sound.

# Plot waveform using matplotlib
plt.figure(figsize=(12, 4))
plt.plot(samples, color='slateblue')
plt.title("Original Audio Waveform")
plt.xlabel("Sample Index")
plt.ylabel("Amplitude")
plt.grid(True)
plt.tight_layout()
plt.show()

Output:

Image showing the audio Waveform
Image showing the audio Waveform

Step 3: Normalization and Scaling

Even though we normalized earlier, let’s say you want to rescale your audio to control loudness manually.

# Scale audio to 50% volume
scaled_samples = samples * 0.5

Step 4: Trimming and Slicing

Let’s trim the first and last few seconds of the audio.

# Trim audio: keep only the middle 10 seconds
start_time = 5 * sample_rate  # 5 seconds in samples
end_time = 15 * sample_rate   # 15 seconds in samples
trimmed_samples = samples[int(start_time):int(end_time)]

Step 5: Reversing the Audio

Flipping the waveform gives us the reversed version of the audio.

# Reverse the audio
reversed_samples = samples[::-1]

Step 6: Changing Volume (Amplitude)

You can increase or decrease volume by scaling the amplitude up or down.

# Increase volume by 1.5x (be careful of clipping!)
louder_samples = samples * 1.5

# Clip values between -1 and 1 to avoid distortion
louder_samples = np.clip(louder_samples, -1.0, 1.0)

Step 7: Adding a Simple Echo Effect

Let’s add a basic echo by mixing the original signal with a delayed version of itself.

# Create an echo effect
delay_samples = int(0.2 * sample_rate)  # 200ms delay
echo = np.zeros(len(samples) + delay_samples)
echo[:len(samples)] += samples
echo[delay_samples:] += 0.5 * samples  # lower volume for echo tail

# Normalize echo
echo /= np.max(np.abs(echo))

Step 8: Speed Up or Slow Down (Resampling)

Changing the speed means changing how fast we play the samples.

from scipy.signal import resample

# Speed up by resampling to fewer points
speed_up = resample(samples, int(len(samples) * 0.75))  # 1.33x speed

# Slow down
slow_down = resample(samples, int(len(samples) * 1.25))  # 0.8x speed

Step 9: Basic Pitch Shift

This is a naive pitch shift done by speeding up or slowing down — it affects both pitch and duration.

# Shift pitch up (naive approach)
pitch_up = resample(samples, int(len(samples) * 0.8))  # higher pitch

# Shift pitch down
pitch_down = resample(samples, int(len(samples) * 1.2))  # lower pitch

Saving and Exporting Audio

Let’s save the processed audio as a file. We’ll convert it from NumPy to AudioSegment and export it.

from pydub import AudioSegment

def save_numpy_audio(np_samples, sample_rate, out_path):
    # Convert float [-1,1] to int16
    int_samples = (np_samples * 32767).astype(np.int16)
    
    # Create AudioSegment from raw data
    output_audio = AudioSegment(
        int_samples.tobytes(),
        frame_rate=sample_rate,
        sample_width=2,  # 16-bit audio
        channels=1
    )
    
    # Export to file
    output_audio.export(out_path, format="wav")
    print(f"Saved audio to {out_path}")

# Save example
save_numpy_audio(echo, sample_rate, "output_with_echo.wav")

Final Thoughts

While NumPy gives you a lot of control when working with audio, it does come with a few limitations. For example, it doesn’t handle audio formats or playback by itself, you have to use other tools alongside it. Also, things like real-time processing, advanced effects, and working with compressed formats can get tricky if you stick to just NumPy.

That said, NumPy is the backbone for many popular audio libraries like librosa, scipy, and even parts of pydub. It helps you understand what’s really happening under the hood when you apply effects or manipulate sound.

If you enjoyed this project, try building something small — maybe an audio visualizer or a tool that adds cool effects to recordings. Keep experimenting. The more you play with audio data, the better your understanding will get.

Leave a Reply

Your email address will not be published. Required fields are marked *