draft: false
Name: Barber's Pole
short-description: "An infinite glissando — Shepard-Risset tones that rise or fall forever, as sine tones or by octave-stacking live audio"
Language: C++ (Pico SDK + ComputerCard)
Creator: Andy Jenkinson (uglifruit)
Version: "1.0.0"
Status: Released
License: MIT
repository: https://github.com/uglifruit/WorkshopShepard
Editor: none
date-created: 2026-08-28
date-updated: 2026-08-30
contact:
social:
github: https://github.com/uglifruit
uf2:
- path: UF2/shepard.uf2
name: Barber's Pole 1.0.0
tags:
- effect
- synth
- pitch-shifter
- shepard
- barbers-pole
- glissando
- scale
- octave-stack
- percussion
summary: |
BARBER'S POLE generates Shepard-Risset tones: the auditory illusion of a pitch that
glides forever upward or downward without ever leaving its range. Octave-spaced
components slide together through a fixed spectral envelope, each fading in at
the bottom and out at the top; because the stack is octave-spaced, sliding it
by one whole octave returns it exactly to where it started, so the glide loops
seamlessly.
Main sets speed and direction, with a deadzone at noon so the stationary
position can be found by feel — anticlockwise descends, clockwise ascends, and
CV In 1 is summed in. X sets density, 3 to 11 octave layers, stepped discretely with hysteresis so knob jitter cannot retrigger a change. Y crossfades
between two sources: the internal sine bank, and your own signal
OCTAVE-STACKED.
The live path transposes whatever is patched into Audio In 1 into N
octave-spaced copies, weighted by the same window as the oscillators - so a
220 Hz partial appears at 55, 110, 220 and 440 Hz and each copy rises and
fades exactly as an oscillator layer does. The internal voice is
octave-stacked sines; this is octave-stacked you. Feed it anything with clear
pitch content.
Switch Up is page two: Main quantises the glide to smooth glissando, 12-ET
chromatic, major, minor or pentatonic; X sets stereo width; Y sets output level.
Snapping is slewed over about 1.4 ms rather than jumped, because twelve
oscillators stepping together is audible as a thump.
Switch Down is spring-loaded and toggles FREEZE on each press. Audio In 2 is
a HOLD gate doing the same thing - it stops the glide while high and resumes
from exactly where it paused, so the glide does not run on underneath. Pulse
In 1 is an EVENT rather than a gate: its rising edge advances a scale step in
the stepped modes, and because it never holds the glide, stepping and gliding
work together - park Main at centre for pure manual stepping, or leave it
off-centre and the pulses nudge an already-moving pole. Pulse In 2 reverses
the glide while held. CV In 1 and 2 offset
speed and density. CV Out 1 is the master phase as a 0-1V ramp - one octave per cycle,
so an oscillator tracks the pole in tune - and CV Out 2 is the window level
at that phase, so the two together drive a VCA and oscillator as one Shepard
layer made external.
ALT-BOOT - HIDDEN BARBER: holding the switch DOWN at power-on makes the pole climb
SILENTLY. A trigger on Pulse In 1 voices a ping at whatever pitches the pole
has reached, and that ping decays WITHOUT climbing - it is a snapshot, not a
window onto the moving stack. Trigger repeatedly and each ping comes from a
different point on the pole, building a chord out of one rising structure, so
the barber's pole becomes something you play rather than something you listen
to. Four voices, oldest stolen. Page-two X sets the decay, 17ms to 1.7s; Main still quantises.
Patch Pulse Out 1 back to Pulse In 1 and it strikes itself once per octave.
Note that four voices of twelve layers each, with the live input
octave-stacked through all of them, is close to what an RP2040 can do at
48kHz - trigger fast enough that all four ring at once, with a long decay and
the input mixed in, and the card WILL distort. That is the processor running
out of time rather than a fault, and no setting removes it; shorten the
decay, use fewer layers, or play a little more sparsely.
With Y clockwise the live input is gated by the ping envelope AND transposed
to the struck pitches, so your own signal is what rings.
All fixed point: the RP2040 has no FPU, so a float multiply costs ~360ns
against a 20.8us per-sample budget. Two economies make a eleven-layer stereo
bank affordable — because the layers are exactly an octave apart every layer's
increment is layer 0's shifted left by i, so ONE exponential lookup serves all
eleven at 1.5kHz rather than 48kHz; and the octave stack's read heads all
share one delay buffer, which is the card's single large allocation. The envelope reuses the first half of
the sine table rather than needing one of its own, and sums to exactly N/2 at
every layer count and phase.
The fixed-point DSP is verified on the host before flashing — window
constant-sum, exponential accuracy, quadrature and sideband rejection, scale
quantisation across the octave wrap, delay loop stability run at DC, and
end-to-end gain. Two of those tests caught real bugs during development: a
truncated window lookup that rippled at odd layer counts only, and an output
stage running 4dB too hot. A renderer (tools/shepsim.py) runs the same integer
engine to WAV so the card can be heard before flashing, and checks the octave
wrap for a discontinuity — calibrating itself against a deliberately broken
build on each run, so a detector that cannot fire is never mistaken for a
pass.
Flash 1.4%, RAM 57%, both from the linker. Measured on hardware, the normal
engine has comfortable margin; HIDDEN BARBER at four simultaneous voices with the live
input mixed in sits close to the ceiling - see the note above.
panel:
inputs:
- id: AudioIn1
name: In
type: audio
description: >
Source for the octave stack, and for HIDDEN BARBER's gated voice.
Wants clear pitch content - a voice, a synth line, a drone, a chord.
Percussion works but smears, since each read head spans a 341 ms
window.
- id: AudioIn2
name: Hold
type: audio
description: >
HOLD while high - stops the glide and resumes from exactly where it
paused, so releasing never jumps ahead. Works in both modes; in HIDDEN
BARBER it holds the invisible pole still, so every strike gives the
same chord. Above about 1.2 V holds and below 0.6 V releases, with
hysteresis between them because this is an audio input rather than a
comparator. Unpatched it does nothing.
- id: CVIn1
name: Speed
type: cv
description: >
Bipolar glide speed, summed with the Main knob. Applied after the
knob's deadzone, so CV still drives the glide with Main parked at noon.
- id: CVIn2
name: Density
type: cv
description: Bipolar density offset, summed with the X knob on page one.
- id: PulseIn1
name: Step
type: pulse
description: >
An EVENT, never a gate - it does not hold the glide, which is what
lets stepping and gliding be used together. In normal boot its rising
edge advances one scale degree in the stepped modes, so the pole keeps
climbing between steps. In HIDDEN BARBER it is the strike trigger.
- id: PulseIn2
name: Reverse
type: pulse
description: >
Reverses the glide direction while high. Momentary rather than
latching, so it is playable against a clock.
outputs:
- id: AudioOut1
name: Left
type: audio
description: Left channel. Identical to the right at zero stereo width.
- id: AudioOut2
name: Right
type: audio
description: >
Right channel. Width decorrelates the two by offsetting the window
rather than the oscillator phase, so a mono sum stays valid and never
cancels.
- id: CVOut1
name: Pitch
type: cv
description: >
Master glide position as a 0-1V ramp - ONE OCTAVE per cycle, so an
oscillator patched here climbs in tune with the pole. Calibrated per
card rather than scaled by hand.
- id: CVOut2
name: Level
type: cv
description: >
The window level at the master phase - what the bottom layer of the
stack is doing. Patch CV Out 1 to an oscillator's V/oct and this to a
VCA and you get one Shepard layer made external: rising in pitch while
swelling and fading, then beginning again.
- id: PulseOut1
name: Octave
type: pulse
description: >
One trigger per octave wrap - the card's own clock, locked to the
glide. Self-patch it to Pulse In 1 to advance a scale step every cycle,
or use it to clock something else in time with the illusion.
controls:
switch:
middle:
name: Page 1
description: >
Speed, density and source mix. The card's normal playing page.
up:
name: Page 2
description: >
Quantise, stereo width and output level - or in HIDDEN BARBER,
quantise, ping decay and level. Knobs use PICKUP across a page change:
each holds its previous value until moved about 4 degrees, with its LED
pulsing until it does, so flicking pages never edits three parameters
at once.
down:
name: Freeze
description: >
Spring-loaded, and a button rather than a third page. Each press
toggles FREEZE - the stack stops moving through the envelope but keeps
sounding. LED 3 lights while frozen. A gate into Audio In 2 does the
same thing, and both resume from exactly where they paused.
knobs:
- when:
z: middle
main:
name: SPEED
description: >
Glide speed and direction. Centre is stationary, with a deadzone so
it can be found by feel; anticlockwise descends, clockwise ascends.
About 85% of the travel sits inside the illusion - 46 seconds per
octave just off centre, down to about one second at 85% - and the
last stretch runs out to roughly 8 octaves per second, which reads
as a siren rather than a glissando. A Shepard tone only works while
the ear cannot track the octave cycle, so the curve is weighted to
keep most of the knob below that limit. In HIDDEN BARBER this is
POLE SPEED: the same control over an invisible climb, setting how
far apart in pitch consecutive strikes land.
x:
name: DENSITY
description: >
Number of octave layers, 3 to 11. Higher counts make the illusion
smoother and more convincing; lower counts leave the individual
octaves audible. Stepped discretely with hysteresis, since adding a
layer respaces the whole stack and there is no entering layer to
fade. In HIDDEN BARBER it sets the layers in each strike.
y:
name: SOURCE
description: >
Crossfades from the internal sine bank (anticlockwise) to the
octave-stacked live input (clockwise), at constant power. In HIDDEN
BARBER the live input is also gated by the ping envelope, so your
own signal is what rings.
- when:
z: up
main:
name: QUANTISE
description: >
Smooth glissando, or one of six scales that repeat every octave:
chromatic (0 1 2 3 4 5 6 7 8 9 10 11), major (0 2 4 5 7 9 11), minor
(0 2 3 5 7 8 10), pentatonic (0 2 4 7 9), diminished
(0 2 3 5 6 8 9 11) or whole tone (0 2 4 6 8 10). In a rising glide
the chromatic setting is nearly indistinguishable from smooth, so
the modal ones are what change the character; the last two are
symmetric and repeat BELOW the octave, which suits a barber-pole
unusually well. Steps are even in time - the pole advances one
degree per unit time rather than a fixed pitch interval - and are
slewed over about 1.4ms rather than snapped. In HIDDEN BARBER it
makes strikes land on scale degrees, which is one of the most
playable things about that mode.
x:
name: WIDTH / DECAY
description: >
In normal boot, stereo width - by offsetting the window position
between channels, which spreads the image without ever separating an
octave from its neighbours, so the stack stays fused and a mono sum
stays valid. In HIDDEN BARBER it is ping DECAY instead, from a 17ms
click to a 1.7s ring, and width sits at a fixed modest spread.
y:
name: LEVEL
description: >
Output level. Holds its current value until the knob is actually
moved, so changing page never drops the volume.
leds:
- display: list
items:
- id: LED0
name: Phase (quarter 1)
description: >
LEDs 0 to 2 show the master glide position, one lighting per
quarter of the octave cycle - so the pole's progress is visible
even when the sound gives nothing away. At boot, LEDs 0, 2 and 4
light together for a normal boot and 1, 3 and 5 for the HIDDEN
BARBER alt-boot, held for about three quarters of a second after
the switch is read.
- id: LED1
name: Phase (quarter 2)
- id: LED2
name: Phase (quarter 3)
- id: LED3
name: Freeze
description: Lit while the stack is frozen or held.
- id: LED4
name: Page 1
description: >
Lit on page 1. Pulses instead while a knob is waiting to be picked
up after a page change.
- id: LED5
name: Page 2