Skip to content

Add Transpose knob: ±12 semitone pitch shift on raw input - #133

Open
VivekRadhakrishna77 wants to merge 3 commits into
tone-3000:mainfrom
VivekRadhakrishna77:feature/transpose
Open

VivekRadhakrishna77 wants to merge 3 commits into
tone-3000:mainfrom
VivekRadhakrishna77:feature/transpose

Conversation

@VivekRadhakrishna77

@VivekRadhakrishna77 VivekRadhakrishna77 commented Sep 13, 2026

Copy link
Copy Markdown

Closes #77

Summary

  • Adds a Transpose knob (faceplate, between Input and Gate): whole-semitone pitch shift (±12) applied to the raw guitar signal before the NAM/amp/cab chain, same placement as Neural DSP's X-series plugins.
  • Implemented via TransposeProcessor wrapping Signalsmith Stretch (phase vocoder), configured with an 80ms analysis window / 4x STFT overlap.
  • Bypassed at 0 semitones (default): zero added latency, bit-exact passthrough — preserves the existing "empty chain is transparent" guarantee.
  • When active: adds ~80ms of latency, correctly re-reported to the host via setLatencySamples() on the bypass↔active edge so PDC stays accurate.
  • No formant preservation (not available in this version of the library) — large shifts will sound more "chipmunk/Darth Vader" than a formant-aware shifter.

Design notes / known limitation

  • The 80ms window is a deliberate trade-off, arrived at empirically: shorter windows (tried 40/60/65/70/75ms) warble badly on chords because window length also sets frequency resolution, and chords need enough resolution to keep closely-spaced simultaneous fundamentals from blurring together in the engine's peak-picking. Longer windows (85–120ms) were no cleaner, just more latency.
  • This is a known limitation of a single-window phase vocoder, not a bug — a TODO is left in TransposeProcessor.h proposing a multi-resolution STFT as the real fix if ~80ms proves too much for live monitoring. Deliberately deferred as separate follow-up work rather than blocking this version.

Automated testing

  • New test/src/transpose_tests.cpp: 8 tests covering bypass bit-exactness/zero-latency, active-latency bounds, mono-buffer-against-stereo-configured-engine (regression for an OOB read), sample-rate/block-size changes, extreme (±12) shift stability, bypass↔active toggling churn, and octave-up/down pitch-shift correctness (Goertzel-based frequency detection).
  • Full DspTests suite: 148/148 passing after this change.

Manual testing

  • Standalone macOS app, single notes at various shifts: clean at all tested semitone values.
  • Chords: warbled at the original 20ms and 40ms windows; bisected up through 60/65/70/75ms (all still warbled) to 80ms, which held up cleanly; confirmed 85–120ms gave no further improvement, so 80ms was kept.
  • Verified against a real NAM chain end-to-end, not just the isolated processor.
  • Separately investigated and ruled out an unrelated Standalone-app amp-crackling issue during this work — confirmed via controlled A/B (not caused by this branch or by Transpose; root cause was the local Debug build's dev-server WebView, not present in a Release build).

VivekRadhakrishna77 and others added 3 commits September 10, 2026 22:43
Adds an input-stage Transpose control (-12..+12 semitones, default 0)
that pitch-shifts the raw DI signal before it reaches NAM/amp/cab
modeling, mirroring the Neural DSP X-series input transpose.

- TransposeProcessor wraps Signalsmith Stretch (MIT), configured with
  a ~20ms analysis window (vs. the library's ~120ms offline preset)
  to keep added latency low for live monitoring.
- Bypassed at 0 semitones (the default): zero added latency and a
  bit-exact passthrough, preserving this codebase's "empty chain is
  transparent" invariant. Latency is re-reported to the host via
  setLatencySamples() on the bypass<->active edge so PDC stays
  correct.
- Wired into the React/WebView Faceplate UI between Input and Gate,
  following the existing knob relay/attachment convention.

Includes screenshots of the knob and its hover help text
(docs/screenshots/).

Closes tone-3000#77.
Single notes tracked fine at the previous 20ms window, but chords
warbled badly: a phase vocoder's window size sets frequency resolution
as well as pitch stability, and 20ms wasn't enough to keep closely-
spaced simultaneous fundamentals from blurring together in the
engine's peak-picking. Manually bisected up through 40/60/65/70/75ms
(all still warbled on chords) to 80ms, the shortest window that holds
up; 85-120ms confirmed no cleaner, just more latency, so 80ms is the
one to keep for now. Left a TODO on a multi-resolution STFT as the
real fix if this latency proves too much for live monitoring.

Claude-Session: https://claude.ai/code/session_01JPfwsFhvizbEChYzzE5WEg
@woodybury

Copy link
Copy Markdown
Member

pretty cool excited to check this out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Input Transpose

2 participants