Skip to content

fix(tremolo): perceptual shape curve so the whole knob morphs#259

Open
OpenSauce wants to merge 1 commit into
mainfrom
fix/tremolo-shape-curve
Open

fix(tremolo): perceptual shape curve so the whole knob morphs#259
OpenSauce wants to merge 1 commit into
mainfrom
fix/tremolo-shape-curve

Conversation

@OpenSauce

Copy link
Copy Markdown
Owner

Problem

Follow-up to #258. The Shape knob "doesn't really change much" — and the numbers confirm it. Shape morphs the LFO from sine → hard square via tanh waveshaping, but that morph saturates with drive, and drive was mapped linearly (0 → 12). Result: ~80% of the morph happens in the bottom third of the knob, and the entire upper half (shape 0.5 → 1.0) only crawls from 80% → 90% square. The top of the knob is effectively inert.

(Shape is also inherently subtle at low Depth — it's a volume effect; the sine-vs-square character only gets dramatic near full depth. That part is expected.)

Fix

The morph saturates with drive, so equal knob steps should map to equal ratios of drive — a geometric sweep, not a linear one. Drive now ramps geometrically from SHAPE_MIN_DRIVE (0.5) to MAX_DRIVE (raised 12 → 16 for a crisper top-end square), with a true sine preserved at exactly shape = 0. Still computed only on shape change, off the RT path.

Measured squareness across the knob (shape 0..1 in eighths):

before (linear):  0  29  59  73  80  84  86  88  90    step σ 11.1
after  (geom.):   0  10  21  38  56  72  82  88  92    step σ  4.7

Now the morph is spread evenly — shape 0.5 sits at 56% (was 80%), so the upper half of the knob actually does something.

Tests

Adds upper_shape_knob_still_morphs: the shape 0.5 vs 1.0 LFO envelopes must differ by MAE > 0.03 (linear map ≈ 0.019 → would fail; geometric ≈ 0.066 → passes). All existing tremolo tests still pass (true sine at shape 0, full-chop silence/unity at shape 1, gain in [0,1], periodicity, cache correctness).

Verification

  • cargo clippy --workspace --all-targets -D warnings -D pedantic -D nursery — clean
  • make test — 164 core + 30 no-alloc pass (11 tremolo)

…rphs

The linear drive map (sine→square via tanh) finished ~80% of the morph
by shape 0.5 and only crept 80%→90% across the entire upper half, so the
top of the Shape knob felt inert. The morph saturates with drive, so
equal knob steps need equal *ratios* of drive: sweep drive geometrically
from SHAPE_MIN_DRIVE (0.5) to MAX_DRIVE (raised 12→16 for a crisper top
square), keeping a true sine at exactly shape 0.

Measured squareness across the knob (0..1 in eighths):
  before:  0 29 59 73 80 84 86 88 90   (step σ 11.1)
  after:   0 10 21 38 56 72 82 88 92   (step σ  4.7)

Adds upper_shape_knob_still_morphs regression test: shape 0.5 vs 1.0 LFO
envelopes must differ (MAE > 0.03; was ~0.019 linear, ~0.066 geometric).
Copilot AI review requested due to automatic review settings June 13, 2026 22:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adjusts the tremolo stage’s Shape control mapping so the sine→square morph is distributed more evenly across the knob’s full range, addressing the previously “inert” upper half of the control.

Changes:

  • Replace the shape→drive mapping from a linear sweep to a geometric sweep, with a special-case at shape == 0 to preserve a (near) pure sine.
  • Increase MAX_DRIVE (12 → 16) to achieve a crisper near-square at full shape.
  • Add a regression test ensuring the upper half of the Shape knob still produces meaningfully different LFO envelopes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +87
self.drive = if self.shape <= 0.0 {
MIN_DRIVE
} else {
SHAPE_MIN_DRIVE * (MAX_DRIVE / SHAPE_MIN_DRIVE).powf(self.shape)
};
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.

2 participants