Skip to content

Frequency Shifter

Tier: Color | ComponentType: 42 | Params: 4

Bode frequency shifter via Hilbert transform with single-sideband modulation and feedback.

Overview

FrequencyShifter implements a Bode-style frequency shifter that translates all frequency components of the input signal by a constant offset in Hz. Unlike pitch shifting (which preserves harmonic ratios), frequency shifting adds a fixed value to every partial, destroying harmonic relationships and producing bell-like, metallic, or alien timbres. A 5 Hz shift on a 440 Hz tone yields 445 Hz, but the second harmonic at 880 Hz becomes 885 Hz -- no longer a perfect octave.

The core technique is single-sideband (SSB) modulation using a Hilbert transform. The input signal is split into in-phase (I) and quadrature (Q) components via two parallel 8-stage allpass chains with carefully chosen coefficients that maintain approximately 90 degrees phase difference across the audio band. These analytic signal components are then multiplied by cosine and sine of the shift oscillator to produce upper and lower sidebands independently. The Direction parameter crossfades between upward shifting (adding frequency) and downward shifting (subtracting frequency).

The feedback path routes the shifted output back into the input, creating cascading frequency shifts on each pass. With a 5 Hz shift and moderate feedback, the signal spirals up or down through the spectrum, producing the characteristic "barber pole" effect. Feedback is hard-clamped to \([-1, 1]\) to prevent runaway oscillation.

File Locations

Path
Header Sources/FolioDSP/include/FolioDSP/Color/FrequencyShifter.h
Implementation Sources/FolioDSP/src/Color/FrequencyShifter.cpp
Tests Tests/FolioDSPTests/FrequencyShifterTests.swift
Bridge Sources/FolioDSPBridge/src/FolioDSPBridge.mm (FrequencyShifterBridge)

Parameters

Index Name Description Min Max Default Min Default Max Default Unit
0 Shift Frequency offset applied to all partials -5000 5000 -50 50 0 Hz
1 Direction Blend between upward (0) and downward (1) shifting 0.0 1.0 0.0 1.0 0.0
2 Feedback Amount of shifted output fed back to input 0.0 0.95 0.0 0.8 0.0
3 Mix Dry/wet blend 0 100 0 100 100 %

Processing Algorithm

The process() function executes these steps for each input sample:

1. Feedback Addition

The previous shifted output (clamped) is mixed with the current input:

\[x_{\text{fb}} = x + f_{\text{prev}} \cdot F\]

where \(F\) is the feedback amount and \(f_{\text{prev}}\) is the previous shifted output clamped to \([-1, 1]\).

2. Hilbert Transform

The signal passes through two parallel 8-stage allpass chains. Each allpass stage computes:

\[y_{\text{out}} = c_k \cdot (x_{\text{in}} - y_{\text{prev}}) + x_{\text{prev}}\]

The I-path uses coefficients \(c^I_k\) and the Q-path uses coefficients \(c^Q_k\), chosen to maintain approximately 90 degrees phase difference across the audio band:

\[I = \text{allpass}^I_7(\text{allpass}^I_6(\cdots\text{allpass}^I_0(x_{\text{fb}})))\]
\[Q = \text{allpass}^Q_7(\text{allpass}^Q_6(\cdots\text{allpass}^Q_0(x_{\text{fb}})))\]

3. SSB Modulation

The analytic signal components are modulated by the shift oscillator to produce upper and lower sidebands:

\[y_{\uparrow} = I \cos(\varphi) - Q \sin(\varphi)\]
\[y_{\downarrow} = I \cos(\varphi) + Q \sin(\varphi)\]

The Direction parameter crossfades between them:

\[y_{\text{shifted}} = y_{\uparrow} \cdot (1 - D) + y_{\downarrow} \cdot D\]

4. Phase Advance

The shift oscillator phase is advanced per sample:

\[\varphi \mathrel{+}= \frac{2\pi \cdot f_{\text{shift}}}{f_s}\]

Phase is wrapped to \([-2\pi, 2\pi]\) to prevent floating-point precision loss.

5. Feedback Storage

The shifted output is clamped and stored for the next sample:

\[f_{\text{prev}} = \text{clamp}(y_{\text{shifted}},\; -1,\; 1)\]

6. Dry/Wet Mix

\[w = \frac{\text{mix}}{100}\]
\[y = x \cdot (1 - w) + y_{\text{shifted}} \cdot w\]

Core Equations

\[I, Q = \text{Hilbert}(x + f_{\text{prev}} \cdot F)\]
\[y_{\uparrow} = I\cos\varphi - Q\sin\varphi\]
\[y_{\downarrow} = I\cos\varphi + Q\sin\varphi\]
\[y = (1-D) \cdot y_{\uparrow} + D \cdot y_{\downarrow}\]

Snapshot Fields

Field Type Range Unit Description
Input Level Float 0--1 Smoothed input amplitude
Output Level Float 0--1 Smoothed output amplitude
Shift Frequency Float -5000--5000 Hz Current shift amount (smoothed)
Shift Phase Float 0--6.28 rad Current shift oscillator phase
Direction Float 0--1 Current direction blend (smoothed)
Feedback Float 0--0.95 Current feedback amount (smoothed)

Implementation Notes

  • Allpass coefficients are hardcoded constants empirically chosen to maintain approximately 90 degrees phase difference between I and Q paths across the audible frequency range (roughly 20 Hz to 20 kHz).
  • Feedback clamping to \([-1, 1]\) prevents runaway oscillation. This is the same safety pattern used in the Overdrive feedback topology.
  • ParamSmoother is used for all four parameters to prevent zipper noise and audible stepping on rapid parameter changes.
  • Phase wrapping handles both positive and negative shift frequencies, wrapping the oscillator phase at \(\pm 2\pi\).
  • Level tracking uses exponential smoothing with coefficient 0.01 for display-rate visualization.
  • At Shift = 0 Hz, the component passes audio through unchanged (the SSB modulator with zero frequency is an identity operation).
  • All atomic parameters use std::memory_order_relaxed for lock-free thread safety.
  • Snapshot emission is decimated to ~60 fps (every 735 samples at 44.1 kHz).

Equation Summary

y = SSB(Hilbert(x)) +/- feedback