Real-time pitch and formant shifting with spectral freeze and a pitched feedback
delay. Built entirely from public pulp::signal
primitives — no third-party DSP — with a native GPU editor in Pulp's
Ink & Signal design language.
- Pitch ±12 semitones in real time, with formant control that can track the pitch (Link) or stand free, and a preservation mode that keeps the vocal character intact as the pitch moves.
- Freeze holds the current spectrum as a stationary resynthesis — averaged magnitudes, phases advancing at each bin's instantaneous frequency — so there is no loop seam to hear, and pitch and formant keep bending the held sound. Play it chromatically over MIDI.
- Pitched feedback delay, tempo-syncable, with a second pitch shifter inside the feedback loop so repeats climb or fall.
- Dry/wet with latency compensation, so the dry path stays aligned.
The XY pad drives pitch (horizontal) and formant (vertical) together. The spectrum below is the wet signal on a log-frequency axis.
Bendr is a worked example of composing Pulp's own DSP into a shipping plugin.
Every processing block is a public primitive from core/signal/:
| Primitive | Role |
|---|---|
RealtimePitchTimeProcessor |
pitch / formant shift, with built-in FreezeHold and TransientPhasePolicy |
PitchedFeedbackDelay |
tempo-syncable delay with an in-loop pitch shifter |
DryWetMixer |
latency-compensated dry/wet |
The editor is likewise assembled from stock pulp::view widgets — Panel,
Toggle, ToggleButton, Label — over a single theme_from_preset("ink-signal")
call at the root. Only the XY pad and the spectrum are custom canvas, because no
stock widget models a semitone grid or a log-frequency magnitude plot.
Click any numeric field to type a value. The caret is Pulp's shared
pulp::view::CaretBlink + paint_caret, so it holds solid while you type and
resumes blinking when you stop, at a rate independent of your display's refresh.
Download Bendr-<version>.pkg from Releases. It is signed and
notarized. The installer's Customize pane lets you pick only the formats your
host scans.
Requires macOS on Apple silicon (arm64). The published build is not universal and does not run on Intel Macs.
| Format | Installs to |
|---|---|
| Audio Unit (AU) | /Library/Audio/Plug-Ins/Components |
| VST3 | /Library/Audio/Plug-Ins/VST3 |
| CLAP | /Library/Audio/Plug-Ins/CLAP |
| Standalone | /Applications |
Bendr consumes an installed Pulp SDK through find_package(Pulp CONFIG); no
Pulp source tree is required.
# Install the SDK once, from a Pulp checkout:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DSKIA_DIR="$PWD/external/skia-build"
cmake --build build -j8
cmake --install build --prefix ~/pulp-sdk
# Then build Bendr:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DPULP_SDK_PREFIX=~/pulp-sdk
cmake --build build -j8
ctest --test-dir build --output-on-failureArtifacts land in build/{AU,VST3,CLAP} and build/Bendr.app.
Package a signed, notarized installer (requires Developer ID credentials in
~/.config/pulp/secrets):
./scripts/package.sh # sign → pkg → notarize → staple
SKIP_NOTARIZE=1 ./scripts/package.sh # local testing onlyCapture the editor headlessly — the editor requires a GPU host, so capture routes through the offscreen Dawn + Skia backend:
./build/bendr-ui-screenshot docs/bendr-ink-signal.png 760 560
BENDR_SHOT_FROZEN=1 ./build/bendr-ui-screenshot docs/bendr-frozen.png 760 560
BENDR_SHOT_CARET=0 ./build/bendr-ui-screenshot docs/bendr-caret.png 760 560BENDR_SHOT_CARET=<field> opens that field's type-in before capturing. A caret
is only painted while its field is being edited, so a plain capture can never
show one.
- The design language names Jost for display type; no Jost binary ships with Pulp, so the bundled Inter and JetBrains Mono stand in.
- Right-click on the XY pad or FREEZE arms MIDI learn for that parameter. In a DAW this needs a Pulp SDK newer than 0.626.0: before that, the macOS plugin view host had no right-button dispatch at all, so no context menu reached any view. The standalone has always worked.
MIT — see LICENSE. Same as Pulp.


