fix(tremolo): perceptual shape curve so the whole knob morphs#259
Open
OpenSauce wants to merge 1 commit into
Open
fix(tremolo): perceptual shape curve so the whole knob morphs#259OpenSauce wants to merge 1 commit into
OpenSauce wants to merge 1 commit into
Conversation
…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).
Contributor
There was a problem hiding this comment.
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 == 0to 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) | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
tanhwaveshaping, 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) toMAX_DRIVE(raised 12 → 16 for a crisper top-end square), with a true sine preserved at exactlyshape = 0. Still computed only on shape change, off the RT path.Measured squareness across the knob (shape 0..1 in eighths):
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— cleanmake test— 164 core + 30 no-alloc pass (11 tremolo)