0. Before you start

The article is roughly split into two halves: a theoretical dive into DSP and pitch-shifting algorithms, followed by an explanation of an existing STM32-based implementation. It draws on these concepts throughout, so some familiarity with sampling, time vs. frequency domain, FFT, filters, C, pointers, circular buffers, and microcontrollers will help you get the most out of it.

1. What is a guitar pedal ?

Starting with the basics, for those new to the guitar world, a guitar pedal is an electronic device placed between the instrument and the amplifier that modifies the audio signal before it reaches the amp. Because a guitar player uses both his hands to play, it is generally activated with a foot switch. Pedals cover a wide range of functions such as distortion, wah-wah, delay or pitch-shifting, the subject of this article. Here are some examples of pedal effect:

2. What is the difference between an analog and digital pedal ?

There are two types of guitar pedals, the most used is the analog pedal. Analog pedals process the signal as a continuous voltage, using discrete components (transistors, capacitors). The signal is never converted to numbers, it flows physically through the circuit, shaped by the electrical characteristics of each component.

Image 1: Boss - OS 2, overdrive/Distortion Boss - OS 2, OverDrive/Distortion

The second type is digital pedals, they convert the analog audio signal into a stream of numerical values via an Analog-to-Digital Converter (ADC), process those values on a microcontroller or DSP, then convert the result back into an analog signal via a Digital-to-Analog Converter (DAC). This approach provides far greater algorithmic flexibility: the effect can be reprogrammed entirely without changing the circuit. Because this conversion and calculation take time, digital pedals must operate under strict real-time constraints. If the total round-trip latency (ADC -> DSP -> DAC) exceeds roughly 5 to 10 milliseconds, the musician will begin to feel a disconnect between their hands and what they hear. So it is important for a digital pedal to execute calculations nearly instantly and to preserve the immediate, organic response of an analog rig. The DMA (Direct Memory Access) is the bridge that allows the CPU to process samples without the overhead of manual data movement. The DMA controller autonomously transfers data between the $I^2S$ audio interface and the system memory

3. What is a pitch-shifter ?

A pitch-shifter is an effect that modifies the perceived pitch (fundamental frequency) of a sound without necessarily altering its duration. Unlike a simple speed change, which shifts both pitch and tempo simultaneously, a pitch-shifter aims to decouple these two parameters. In order to achieve that change in pitch, a digital pedal must first understand exactly what it is listening to. This process is called tracking, and it relies entirely on the pedal’s ability to lock onto the fundamental frequency. If a pedal cannot accurately identify the fundamental, the resulting sound will suffer from tracking lag, warbling, or harsh digital artifacts. There are two types of tracking:

  • Monophonic Tracking: process only a single note at a time
  • Polyphonic Tracking: process multiple notes at a time, allowing its use when playing full chords

Historically, the first analog pitch-shifters relied on variable-rate delay lines using Bucket-Brigade Devices (BBD): chains of capacitors that pass a sampled voltage from one to the next, read back at a different clock speed than it was written in. It is conceptually identical to changing the Shift factor in the digital delay-line implementation covered later in this article, just done with clocked capacitors instead of a fractional read pointer in memory. Since there is no sampling, no math, and no processor involved, BBD pitch-shifters are cheap and have near-zero latency, but their pitch range is narrow and sound quality degrades quickly with heavier shifts (noise, aliasing-like artifacts). This is one of the main reasons the industry has almost entirely shifted to digital processing today, which is the focus of the rest of this article.

4. Theory Behind a Digital Pitch-Shifter

Now, let’s take a deeper dive into the theory behind a pitch-shift on a monophonic note. There are two ways to approach pitch-shifting: Overlap-Add (OLA) and Phase Vocoder

a. The Overlap-Add (OLA) Family

OLA is a family of time-domain algorithms that manipulate the waveform directly. Instead of changing the playback speed of an entire file, which alters both pitch and time together, they cut the audio into tiny blocks, rearrange their spacing in time, and blend them back together.

  • Classic Overlap-Add (OLA): The incoming waveform is chopped into fixed intervals, overlapped, and summed into an output buffer. To pitch-shift, the individual blocks are resampled or played back at a faster/slower rate while the algorithm drops or duplicates blocks to maintain the original duration. The drawback of this simple approach is that OLA cuts audio regardless of where the wave cycle is. If two blocks are glued together mid-wave, it creates a phase mismatch. This manifests as an aggressive, metallic fluttering or sputtering sound. Furthermore, a sharp transient (like a drum hit or pick attack) can accidentally be copied into multiple blocks, causing it to echo or duplicate in time.

Image 2: OLA illustration (Credits: Geraint Luff - ADC22) OLA illustration

  • Synchronized Overlap-Add (SOLA / WSOLA): To combat the fluttering, SOLA and Waveform Similarity OLA (WSOLA) introduce structural flexibility. Before summing an overlapping window into the output buffer, the algorithm uses a mathematical metric (like cross-correlation or measuring squared differences) to wiggle the block slightly left or right in time. It aligns the peaks and valleys of the waveforms perfectly before crossfading. This drastically reduces phase cancellation. These algorithms work wonderfully with monophonic tracks but struggle heavily with polyphonic chords.

b. The Phase Vocoder Family

To handle polyphonic material, the pedal must process audio in the frequency domain. It chops the audio into small overlapped blocks, tapers the edges with a windowing function to eliminate edge discontinuities (in order to improve the FFT results), and applies a Fast Fourier Transform (FFT) to turn the wave into a spectrum of frequency bins (slices of frequency). The spectrum is modified, then an Inverse FFT rebuilds a playable audio signal.

Reminder: blocks are time related, bins are frequency related, so for example bin x of block n will transition over time to bin x of block n+1.

Image 3: Frequency-Domain illustration (Credits: Geraint Luff - ADC22) Frequency-Domain illustration

  • The Standard Phase Vocoder: for every frequency bin, it tracks how the phase changes over time from block to block to estimate the exact frequency. It then shifts the spectral peaks to their new position, and recalculates the phase needed for a smooth transition. Because each bin runs this rotation independently, the system loses track of how frequencies line up with each other at any given instant. This creates parasitic artifacts (metallic swoosh, loss of punch, pre-echo) that soften transients like pick attacks.

  • Phase Vocoders with Transient Handling & Phase Resets: to fix this smearing, the pedal monitors energy spikes across bins. When a transient is detected, it forces a Phase Reset: every bin snaps its phase into perfect vertical alignment at that instant, restoring a tight, punchy attack at the cost of the smooth horizontal transition, traded back once the transient has passed.

c. The Hybrids Methods

The goal of this method is to smooth both the endless sustain and the sharp, punchy transients without resorting to a hard Phase Reset. The Hybrid Phase Method mathematically bridges the gap, allowing horizontal and vertical phase alignment to coexist smoothly.

  • The Hybrid Phase Method: A high-quality digital processor generates two simultaneous phase predictions for every point in the spectrum: a horizontal prediction (handling the smooth, singing sustain of the notes) and a vertical prediction (handling the immediate punch of the pick dynamics). The algorithm takes a weighted average of both predictions based on the local energy of the signal. If a note is sustaining solidly, the algorithm leans into the horizontal tonal phase, the moment a string is plucked, it seamlessly shifts weight to the vertical timing phase.

  • Hybrid Pitch and Time Stretcher: The algorithm scans ahead for sharp energy spikes, when an energy spike is detected, the engine passes that specific slice of time to WSOLA so the raw waveform stays perfectly intact. After the spike passes, it crossfade into The Phase Vocoder to handle the smooth sustain.

5. The implementation

The code I will explain is from YetAnotherElectronicsChannel, implementing a Delay-Line Pitch Shifter. Contrary to the algorithms we presented this one never chops the audio into separate windows or frame, instead it uses a single, continuous ring buffer. It smoothly slides a variable-speed playback pointer backward or forward through the buffer and crossfades between two running playback pointers to smooth the transitions. It is a much simpler implementation for educational purposes, it will work just fine for monophonic sound.

I also used Phil’s LAB’s DSP repository as a source. He created a repository about an electronic card for a digital pedal and implemented many pedal effects. Both recorded great youtube videos about these concepts that I more than encourage you to check (I will put a link to their youtube channels at the end of the article).

a. Hardware

Features STM32F405RGT6 STM32F405VGT6 STM32F407VGT6
Physical Package LQFP64 (Compact) LQFP100 (Standard) LQFP100 (Standard)
Pin Count (I/O) 64 pins (51 I/O) 100 pins (82 I/O) 100 pins (82 I/O)
Memory (Flash / RAM) 1 MB / 192 KB 1 MB / 192 KB 1 MB / 192 KB
TFT Screen Connection SPI Only (Not enough pins for parallel) SPI or FSMC (Parallel 8/16-bit) SPI or FSMC (Parallel, cleaner pin multiplexing)
Code / DIY Ecosystem Average (Requires code modification) Average (Requires code modification) Excellent (greatly used in open-source projects)
Estimated Price (Per Unit) ~11.90 € ~12.60 € ~13.20 €
Feature TLV320AIC3104IRHBR PCM1808PW (ADC) + PCM5102APWR (DAC) Combo
Architecture Single-Chip Audio Codec (Integrated ADC & DAC) Discrete Split Chips (Separate Dedicated ADC and DAC)
STM32 Bus Pins Used Low (Uses 1x I²S bus shared via DMA + 1x I2C for control) High (Requires 2x separate I²S data lanes on the STM32)
Audio Resolution 24-bit / up to 96 kHz 24-bit / 96 kHz (ADC) | 32-bit / 384 kHz (DAC)
Hardware Configuration Software-Controlled via I²S (Fully programmable registers) Hardware-Controlled (Fixed via physical PCB pin strapping)
On-Chip Features Integrated Programmable Gain Amplifiers (PGA), input mux, headphone driver Bare-bones conversion. Requires external analog preamps and filters.
PCB Layout Complexity Compact & Optimized (Single chip, shared analog/digital power planes) Highly Complex (Requires routing separate analog/digital lines for two chips)
Total Solution Cost ~4.90 € (Single Chip) ~5.40 € (Combined: ~1.80 € for ADC + ~3.60 € for DAC)

I will use an updated version of Phil’s Lab setup for easier future evolution: STM32F407VGT6, TLV320AIC3104IRHBR, and a TL072 input buffer.

Beware, the hardware is not the same as the implementation we will look at, so the drivers will need to be adapted (in this article we will only cover the pitch-shifter code).

b. Handling buffers

Real-time audio processing on the microcontroller is structurally managed using a double-buffering (ping-pong) DMA architecture to prevent audio discontinuities. While the I²S hardware fills one half of the memory with new samples, the processor is freed up to run the pitch-shifting algorithm on the other half.

However, the core algorithm inside the implementation by YetAnotherElectronicsChannel does not process data in static blocks or windowed frames like OLA or FFT methods do. Instead, it feeds the incoming samples into a single, continuous circular ring buffer, and reads it back through one fractional read pointer advancing at a different speed than the write pointer.

Buffer setup code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#define BufSize 1000
#define Overlap 100

int Buf[BufSize];
int WtrP;
float Rd_P;
float Shift;
float CrossFade;

WtrP = 0;
Rd_P = 0.0f;
Shift = 1.4f; // Note: Pitch-shift factor (>1 = higher pitch, <1 = lower pitch)
CrossFade = 1.0f;

c. The filter implementation

Beyond the core delay-line pitch-shifting logic, the implementation applies a high-pass filter to the incoming signal before it is written into the ring buffer. This is a second-order Infinite Impulse Response (IIR) biquad filter1, cut off around 300 Hz at a 96 kHz sample rate, computed from five fixed coefficients and two delay lines carrying the previous input and output samples.
Applied upstream, this filter removes DC offset and low-frequency rumble introduced when the two stereo channels are summed, preventing that energy from being amplified by the pointer manipulation further down the chain.

High-pass Filter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int Do_HighPass (int inSample) {
	//300Hz high-pass, 96k
	a0 = 0.9862117951198142f;
	a1 = -1.9724235902396283f;
	a2 = 0.9862117951198142f;
	b1 = -1.972233470205696f;
	b2 = 0.9726137102735608f;
	
	float inSampleF = (float)inSample;
	float outSampleF = a0 * inSampleF
						+ a1 * hp_in_z1
						+ a2 * hp_in_z2
						- b1 * hp_out_z1
						- b2 * hp_out_z2;
						  
	hp_in_z2 = hp_in_z1;
	hp_in_z1 = inSampleF;
	hp_out_z2 = hp_out_z1;
	hp_out_z1 = outSampleF;
	return (int) outSampleF;
}

By changing the difference in speed between the writing and reading speed, the pitch change will happen:

  • Pitch Shifting UP: The read pointer advances faster than the write pointer (e.g. Shift = 1.4 reads 1.4 samples per sample written), so the waveform is scanned faster than captured and the pitch rises.
  • Pitch Shifting DOWN: With a Shift factor below 1.0, the read pointer creeps forward slower than the write pointer, effectively stretching the waveform and lowering the pitch.

To eliminate discontinuity artifacts at the boundary where the read and write pointers inevitably collide, the algorithm generates a second reading position by simply offsetting the same read pointer by half the buffer length (a 180° phase shift), rather than maintaining two independently moving pointers. The two read pointers will progressively cross-fade when the write pointer is less than 100 samples away from the current read pointer.

Pitch-shifting code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int Do_PitchShift(int lSample, int rSample) {
	int sum = lSample + rSample;
	//sum up and do high-pass
	sum = Do_HighPass(sum);

	//write to ringbuffer
	Buf[WtrP] = sum;

	//read fractional readpointer and generate 0° and 180° read-pointer in integer
	int RdPtr_Int = roundf(Rd_P);
	int RdPtr_Int2 = 0;
	if (RdPtr_Int >= BufSize/2) RdPtr_Int2 = RdPtr_Int - (BufSize/2);
	else RdPtr_Int2 = RdPtr_Int + (BufSize/2);

	//read the two samples
	float Rd0 = (float) Buf[RdPtr_Int];
	float Rd1 = (float) Buf[RdPtr_Int2];

	//Check if first readpointer starts overlap with write pointer?
	// if yes -> do cross-fade to second read-pointer
	if (Overlap >= (WtrP-RdPtr_Int) && (WtrP-RdPtr_Int) >= 0 && Shift!=1.0f) {
		int rel = WtrP-RdPtr_Int;
		CrossFade = ((float)rel)/(float)Overlap;
	}
	else if (WtrP-RdPtr_Int == 0) CrossFade = 0.0f;

	//Check if second readpointer starts overlap with write pointer?
	// if yes -> do cross-fade to first read-pointer
	if (Overlap >= (WtrP-RdPtr_Int2) && (WtrP-RdPtr_Int2) >= 0 && Shift!=1.0f) {
		int rel = WtrP-RdPtr_Int2;
		CrossFade = 1.0f - ((float)rel)/(float)Overlap;
	}
	else if (WtrP-RdPtr_Int2 == 0) CrossFade = 1.0f;

	//do cross-fading and sum up
	sum = (Rd0*CrossFade + Rd1*(1.0f-CrossFade));

	//increment fractional read-pointer and write-pointer
	Rd_P += Shift;
	WtrP++;
	if (WtrP == BufSize) WtrP = 0;
	if (roundf(Rd_P) >= BufSize) Rd_P = 0.0f;

	return sum;
}

6. Conclusion

This implementation serves as an ideal architectural starting point for a low-cost, real-time pitch-shifter pedal. It provides a direct, hands-on application of foundational digital signal processing concepts such as circular ring buffers, fractional dual-pointer readback, and time-domain crossfading, all working within an efficient embedded C framework.

Once the core hardware abstraction layer and I²S DMA streaming are stabilized, the natural software evolution path is clear: transitioning from this simple sample-by-sample delay line to a block-based WSOLA algorithm to accommodate complex polyphonic signals, and ultimately to an FFT-based Phase Vocoder if studio-grade, phase-coherent shifting is required. Scaling up to these heavy algorithms will significantly increase the CPU load. To maintain strict real-time constraints at 96 kHz without saturating the processor, it is strongly advised to optimize critical mathematical paths, such as replacing floating-point operations and rounding functions with fixed-point arithmetic or leveraging CMSIS-DSP optimized libraries.

The hardware platform is also versatile enough to support other DSP effects such as delays, reverbs, or distortions, with room to add a TFT interface for a fully customizable multi-effects pedal

7. Credits & Sources

YetAnotherElectronicsChannel’s github

YetAnotherElectronicsChannel’s youtube

YetAnotherElectronicsChannel’s STM32_DSP_PitchShift

YetAnotherElectronicsChannel’s STM32_DSP_PitchShift video

JentGent’s pitch-shift

JentGent’s pitch-shift video

Four Ways To Write A Pitch-Shifter - Geraint Luff - ADC22

Phil’s LAB’s github

Phil’s LAB’s youtube

Phil’s LAB’s Electronic Card

Phil’s LAB’s pedal effects

Boss OS-2 Pedal


  1. IIR is a second-order recursive biquadratic digital filter. In this configuration, it will heavily attenuate everything above 300Hz 96000 times per seconds:

    $$y[n] = a_0 x[n] + a_1 x[n-1] + a_2 x[n-2] - b_1 y[n-1] - b_2 y[n-2]$$  ↩︎