Step-by-Step Guide to Real-Time Acoustic Signal Processing with Open-Source Tools

Ever tried to catch a bird song on a rainy afternoon and found the recording sounded like static? That frustration is why real‑time acoustic signal processing matters now more than ever. With cheap microphones, cheap computers, and a flood of open‑source software, anyone can turn raw sound into useful data the moment it arrives. In this post I’ll walk you through a practical, hands‑on workflow that I use in my lab at Acoustic Innovations and that you can replicate at home.

Why Real‑Time Matters Today

Most of us think of acoustic analysis as something you do after the fact—record, upload, run a batch script, wait for results. That works for research papers, but it falls short when you need instant feedback. Imagine a DIY vibration sensor that warns you when a washing machine goes off‑balance, or a wildlife monitor that alerts you the moment a rare frog calls. In those cases latency of even a few seconds can render the system useless. Real‑time processing lets you close the loop: sense, decide, act—all in one breath.

Choosing the Right Toolbox

Open‑source tools have come a long way. Below are the three pillars I rely on:

  • Python – the language of choice for most signal work because of its readability and huge ecosystem.
  • NumPy & SciPy – numerical libraries that handle arrays and provide fast Fourier transforms (FFT) out of the box.
  • PyAudio – a thin wrapper around PortAudio that gives you live audio streams from any sound card.

All three are free, cross‑platform, and have active communities. If you prefer a visual environment, you can replace PyAudio with Audacity for quick testing, but for a true real‑time loop you’ll need a programmatic interface.

Setting Up Your Development Environment

Install Python and Packages

  1. Download the latest Python 3.x from python.org. Choose the installer that adds Python to your system PATH – it saves a lot of headaches.
  2. Open a terminal (Command Prompt on Windows, Terminal on macOS/Linux) and run:
pip install numpy scipy pyaudio

If you hit a permission error, add --user to the command. On some Linux systems you may need to install the PortAudio development headers first:

sudo apt-get install portaudio19-dev

Verify Your Microphone

Run this tiny script to list available audio devices:

import pyaudio
p = pyaudio.PyAudio()
for i in range(p.get_device_count()):
    print(i, p.get_device_info_by_index(i)['name'])

Pick the index that corresponds to your USB mic or built‑in laptop mic. I keep a sticky note on my desk with the number – it saves me from hunting through the list each time.

Building the Real‑Time Loop

Below is a minimal but complete example that captures audio, computes its frequency spectrum, and prints the dominant frequency every 100 ms.

import numpy as np
import pyaudio
import time

CHUNK = 1024            # samples per frame
RATE = 44100            # samples per second
DEVICE_INDEX = 1        # replace with your mic index

p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16,
                channels=1,
                rate=RATE,
                input=True,
                input_device_index=DEVICE_INDEX,
                frames_per_buffer=CHUNK)

def dominant_freq(data):
    # Convert byte data to numpy array of ints
    audio_data = np.frombuffer(data, dtype=np.int16)
    # Apply a window to reduce spectral leakage
    window = np.hanning(len(audio_data))
    fft_result = np.fft.rfft(audio_data * window)
    freqs = np.fft.rfftfreq(len(audio_data), 1.0 / RATE)
    magnitude = np.abs(fft_result)
    return freqs[np.argmax(magnitude)]

print("Listening... press Ctrl+C to stop.")
try:
    while True:
        data = stream.read(CHUNK, exception_on_overflow=False)
        freq = dominant_freq(data)
        print(f"Dominant frequency: {freq:.1f} Hz")
        time.sleep(0.1)   # limit output rate
except KeyboardInterrupt:
    print("\nStopped.")
finally:
    stream.stop_stream()
    stream.close()
    p.terminate()

What the Code Does

  • Chunk size – 1024 samples gives a time window of about 23 ms at 44.1 kHz. Small enough for quick updates, large enough for a decent frequency resolution.
  • Window function – the Hanning window tapers the edges of each chunk, which reduces “spectral leakage” (spreading of energy into neighboring bins). Think of it as smoothing the edges before you look at the frequency content.
  • FFT – the fast Fourier transform converts the time‑domain signal into its frequency components. np.fft.rfft returns only the positive frequencies, which is all we need for a real‑valued signal.
  • Dominant frequency – we simply pick the frequency bin with the highest magnitude. For more sophisticated work you might track peaks, apply thresholds, or use machine‑learning classifiers.

Adding Simple Filtering

Raw microphone data can be noisy, especially in a home workshop. A quick way to clean it up is a band‑pass filter that only lets through frequencies you care about. SciPy makes this easy.

from scipy.signal import butter, lfilter

def butter_bandpass(lowcut, highcut, fs, order=4):
    nyq = 0.5 * fs
    low = lowcut / nyq
    high = highcut / nyq
    b, a = butter(order, [low, high], btype='band')
    return b, a

def bandpass_filter(data, lowcut=300.0, highcut=3000.0, fs=RATE):
    b, a = butter_bandpass(lowcut, highcut, fs)
    return lfilter(b, a, data)

Insert audio_data = bandpass_filter(audio_data) right after converting the byte buffer. In my own project detecting the hum of a 60 Hz AC line, I set lowcut=50 and highcut=70 to isolate that narrow band.

Visual Feedback with Matplotlib (Optional)

If you enjoy watching the spectrum, add a live plot. It adds a few lines but makes debugging much faster.

import matplotlib.pyplot as plt

plt.ion()
fig, ax = plt.subplots()
line, = ax.plot([], [])
ax.set_xlim(0, RATE/2)
ax.set_ylim(0, 1)

while True:
    data = stream.read(CHUNK, exception_on_overflow=False)
    audio = np.frombuffer(data, dtype=np.int16)
    audio = bandpass_filter(audio)
    fft = np.abs(np.fft.rfft(audio))
    freqs = np.fft.rfftfreq(len(audio), 1.0/RATE)
    line.set_data(freqs, fft)
    ax.relim()
    ax.autoscale_view()
    fig.canvas.draw()
    fig.canvas.flush_events()

The plot updates in near‑real time, letting you see exactly where the energy lives. When I first tried this on a windy balcony, the plot looked like a fireworks show—until I added the band‑pass filter and the noise settled down.

Deploying on a Raspberry Pi

For field work, I often move the whole script onto a Raspberry Pi 4. The steps are the same; just install the packages with sudo apt-get install python3-pyaudio (the pip version sometimes struggles with the Pi’s ARM architecture). Pair the Pi with a USB mic and you have a portable, low‑cost acoustic monitor that can run for days on a battery pack.

Tips for a Stable Real‑Time System

  1. Avoid blocking callsstream.read can block if the buffer underflows. Using exception_on_overflow=False keeps the loop alive, but you may still miss data if your processing takes too long. Keep the per‑frame work under the chunk duration.
  2. Prioritize the audio thread – on Linux you can set the process priority with nice -n -10 python3 yourscript.py. On Windows, run the script from an elevated command prompt and set the priority in Task Manager.
  3. Test with known tones – generate a sine wave with a phone app and verify that the dominant frequency matches. This sanity check catches calibration errors early.

Bringing It All Together

Real‑time acoustic signal processing is no longer a niche reserved for big labs. With a few lines of Python, a modest microphone, and the open‑source stack I described, you can build anything from a DIY drum‑hit detector to a smart home leak alarm. The key is to start simple: capture, transform, act. Once the loop works, layer on filters, visualizations, or machine‑learning models as needed.

At Acoustic Innovations we love sharing these building blocks because they empower makers, students, and fellow researchers to listen to the world in a new way. The next time you hear a faint click or a distant chirp, you’ll have the tools to capture it, understand it, and maybe even make it useful.

Reactions