Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 69

Windowed Overlapping Buffers for Real-time Audio

Recommended resources

Hello! Welcome back to your capstone project.

In our last lesson, we successfully established a real-time audio stream, the "heartbeat" of our application. We learned how to use a callback function to stream audio from memory, sample by sample. While this is a crucial first step, many powerful signal processing techniques, especially those based on the Fourier Transform, operate not on individual samples but on blocks or "chunks" of samples.

Today's learning outcome is to implement a windowed, overlapping buffer for processing real-time audio chunks. This is the bridge between our simple sample-based stream and the world of spectral processing. We will build the essential data handling mechanism that allows us to analyze a continuous stream of audio by breaking it into a series of discrete, manageable frames.

1. From Continuous Stream to Discrete Frames: The Role of Windowing

In the real world, audio signals are non-stationary; their frequency content changes over time. To analyze these changes, we use the Short-Time Fourier Transform (STFT), which involves computing the Fourier Transform on short, sequential segments of the signal. The process of extracting these segments is called windowing.

Mathematically, this is equivalent to multiplying our signal by a window function that is zero everywhere except for a short duration. However, this simple act has profound consequences in the frequency domain.

As you know from your background in signal processing, multiplication in the time domain corresponds to convolution in the frequency domain. If our signal contains a pure sinusoid (a delta function in the frequency domain), multiplying it by a simple rectangular window (i.e., just taking a chunk of the signal) convolves its spectrum with a sinc function. This spreads the energy of our single frequency across a wide range of frequencies, an effect known as spectral leakage, which can obscure nearby frequency components.

To mitigate this, we use window functions with shapes that are tapered at the edges.

What is Windowing in Signal Processing?

To visualize these concepts, watch the following video from Iain Explains Signals, Systems, and Digital Comms. It provides an excellent explanation of why windowing is necessary and the spectral trade-offs involved.

Watch from the beginning to 09:00. Pay close attention to: The explanation of how multiplying by a rectangular window in the time domain leads to convolution with a sinc function in the frequency domain (spectral leakage). The introduction of a tapered window (a triangular window) and the trade-off it presents: suppressed side lobes (less leakage) in exchange for a wider main lobe (lower frequency resolution).

This trade-off is fundamental. Windows like the Hann or Hamming window provide better side-lobe suppression than a simple rectangular window, making them standard choices for spectral analysis. For our project, the key takeaway is that we don't just "grab" chunks of audio; we must multiply them by a carefully chosen window function before processing.


2. Reconstructing the Signal: The Overlap-Add Method

After we process each windowed frame (e.g., by taking an FFT, modifying it, and taking an IFFT), we need to stitch these frames back together to form a continuous output signal. A naive approach of simply concatenating the processed frames end-to-end would produce audible artifacts, especially since our window functions tapered the edges of each frame to zero.

The correct method is Overlap-Add (OLA). We advance our window by a number of samples smaller than the window length itself. This distance is called the hop size (). The resulting processed frames overlap, and we simply add them together.

For this process to perfectly reconstruct the original signal (assuming no processing was done), a critical condition must be met.

MUS421 Lecture 8A FFT Signal Processing: The Overlap-Add

The following resource from Stanford's CCRMA provides the mathematical foundation for the Overlap-Add method. Focus on the core principle it introduces.

Read the sections 'Overlap-Add Signal Decomposition' and 'Constant Overlap-Add (COLA) Constraint'. Your goal is to understand the equation that defines the COLA condition for perfect reconstruction.

The key insight from the reading is the Constant Overlap-Add (COLA) condition:

for all , where is a constant (ideally 1). This means that the sum of all overlapping, shifted window functions must be a constant value. If this condition holds, we can perfectly reconstruct the signal by summing the overlapping output frames (and scaling by if necessary).

For example, a Hann window of length satisfies the COLA condition with a hop size of (50% overlap) and (75% overlap).

Test your understanding!

Imagine you use a rectangular window of length . What is the largest hop size you can use that still satisfies the COLA condition?

Show answer

For a rectangular window where for and 0 otherwise, the largest hop size is . The windows are placed adjacent to each other with no overlap, and their sum is always 1. This is often called the "trivial" case of overlap-add.


3. A Real-Time Architecture: Fixed Internal Blocks with a FIFO

We now have the theoretical pieces: we need to extract overlapping, windowed frames from the input and use overlap-add to construct the output. But how do we implement this within our real-time audio callback?

The challenge is that the audio host gives us buffers of a certain size (e.g., 256 samples), but our FFT and window size might be different (e.g., 1024 samples). We can't simply process the incoming buffer. We need a way to assemble our larger processing blocks from the smaller buffers provided by the host.

The standard architecture for this is to use a First-In, First-Out (FIFO) buffer, often implemented as a circular buffer.

Fixed vs. Variable Buffer Processing in Real-Time Audio DSP

This article discusses the architectural trade-offs in real-time audio buffering. It's written for plugin developers, but the principles are universal. It provides excellent context for why the FIFO approach is a robust solution.

Read the section 'Fixed Internal Block with FIFO' and skim the code template in the section 'Practical Application in JUCE'. Don't worry about the specific JUCE syntax. Focus on the overall logic: Incoming samples are written to a FIFO. When the FIFO contains enough samples for a full internal block, that block is extracted and processed. The processed block is written to an output FIFO. The main audio callback reads samples from the output FIFO to send to the hardware.

This hybrid approach—accepting variable-sized buffers from the host but processing fixed-size internal blocks—gives us the predictability needed for FFT-based algorithms while remaining compatible with the host's audio stream.


4. Putting It All Together: A C++ Implementation

Let's see how this two-buffer system (input FIFO and output OLA buffer) is implemented in practice. We will use a detailed video tutorial that builds exactly this kind of overlap-add architecture for a real-time C++ application.

The core components are:

  • An input circular buffer that stores a history of incoming samples.
  • A hop counter to trigger processing every samples.
  • An output circular buffer to accumulate the results of the overlap-add process.
  • Separate read and write pointers for the buffers to manage the flow of data.

18: Phase Vocoder (part 1), C++ Real-Time Audio Programming with Bela

This video from the Bela Platform channel will guide us through building the complete overlap-add infrastructure. It's the practical culmination of all the theory we've discussed.

This is the main implementation guide. Please watch these sections carefully: Introduction (00:00 - 01:57): Introduces the goal of breaking a signal into overlapping windows and putting them back together with overlap-add. Overlap-Add Concept (06:25 - 07:36): Explains how processed blocks are stitched together in an output buffer. The Circular Buffer Implementation (13:58 - 20:45): This is the most critical part. It details the two-buffer strategy: one circular buffer for input to create overlapping windows, and another for the output to perform the overlap-add. Pay close attention to the roles of the read and write pointers for each buffer. Code Solution and Latency Analysis (23:37 - 30:26): The presenter walks through the final C++ code in the render function and analyzes the resulting latency, connecting it directly to the FFT/window size. This is a perfect example of theory meeting practice.

The Logic in the render function

Based on the video, the logic inside your audio callback should now look something like this:

  1. Input Stage:

    • For each incoming sample from the host, write it to the gInputBuffer at the current write position.
    • Increment the input write pointer (and wrap it if it reaches the end of the buffer).
    • Increment the gHopCounter.
  2. Processing Trigger:

    • Check if gHopCounter has reached the gHopSize.
    • If it has:
      • Reset gHopCounter to 0.
      • Call your block-processing function (e.g., process_fft()).
      • This function reads the last gFftSize samples from gInputBuffer, applies a window, and performs its processing.
      • The processed gFftSize samples are returned.
      • These samples are added to the gOutputBuffer, starting at the output write position.
      • The output write pointer is advanced by gHopSize.
  3. Output Stage:

    • For each sample you need to send to the audio hardware:
      • Read one sample from gOutputBuffer at the current output read position. This is your output sample.
      • Crucially, set the sample you just read in gOutputBuffer to zero. This clears it out for the next time the write pointer wraps around.
      • Increment the output read pointer (wrapping as needed).

This elegant system decouples the host's buffer size from your processing block size, allowing you to implement sophisticated block-based algorithms in a robust, real-time-safe manner. The latency analysis in the video, showing a delay equal to the FFT size (1024 samples), is a direct consequence of this buffering strategy and is the minimum achievable latency for this type of block processing.


Conclusion

In this lesson, you have built the fundamental scaffolding for all future spectral processing. You now have an architecture that can take a continuous real-time audio stream and correctly transform it into a series of windowed, overlapping blocks for analysis, and then reconstruct a continuous output stream.

Key Takeaways:

  • Windowing is used to extract finite-length frames from a signal for analysis, but it causes spectral leakage. Tapered windows (like Hann) are used to mitigate this.
  • Overlap-Add (OLA) is the method for reconstructing a signal from processed frames. It requires the window and hop size to satisfy the Constant Overlap-Add (COLA) condition for perfect reconstruction.
  • A real-time implementation uses a two-buffer (FIFO) architecture: an input buffer to assemble overlapping frames and an output buffer to perform the overlap-add summation.
  • This architecture introduces a minimum latency equal to the processing block size (e.g., your FFT size).

Preview of the Next Lesson:

You've built the container; now it's time to put something in it. In our next lesson, we will apply the FFT to buffered audio data to compute and visualize a real-time spectrogram. We will finally be able to "see" the frequency content of our audio stream changing in real time.