Skip to content

feat(stages): add tremolo effect with sine→square killswitch shape#258

Merged
OpenSauce merged 3 commits into
mainfrom
feat/tremolo-stage
Jun 13, 2026
Merged

feat(stages): add tremolo effect with sine→square killswitch shape#258
OpenSauce merged 3 commits into
mainfrom
feat/tremolo-stage

Conversation

@OpenSauce

Copy link
Copy Markdown
Owner

What

Adds a Tremolo effect — amplitude modulation by an LFO — registered as an Effect-category stage. It shows up in the Effects tab next to Delay/Reverb/EQ.

A phase-accumulator sine LFO modulates signal gain. A continuous Shape control morphs the LFO from a smooth sine (vintage tremolo) toward a hard square via tanh waveshaping. At full Depth + full Shape this becomes a square-wave killswitch stutter — the Tom Morello toggle-switch chop — in a single stage.

Controls

Param Range Default
Rate 0.1–20 Hz 5 Hz
Depth 0–100% 50%
Shape sine → square 0% (sine)

DSP notes

  • Gain is mapped to [1 − depth, 1], so full depth dips all the way to silence at each trough.
  • Depth is one-pole smoothed (~30 ms) to suppress zipper noise; the LFO output is not smoothed, so the square edges stay crisp for the chop.
  • Rate needs no smoothing (phase is continuous).

Surface touched

Standard stage-registration set:

  • rustortion-core — new tremolo.rs stage + TremoloConfig; StageType/StageConfig arms (category → Effect)
  • rustortion-ui — new tremolo.rs view (3 sliders) + gui_stage_registry! line; EN/ZH i18n (stage_tremolo, rate, depth, shape); minimap abbreviation
  • rustortion-pluginTremoloSlotParams + per-slot array (host-automation parity)

Tests

9 new core unit tests: dry passthrough at depth 0, full-chop silence + unity at the killswitch extreme, gain stays in [0,1], LFO periodicity, sine fidelity at shape 0, parameter validation, constructor clamping, default config.

Verification

  • cargo fmt --all — clean
  • cargo clippy --workspace --all-targets -D warnings -D clippy::all -D clippy::pedantic -D clippy::nursery — clean
  • make test — 162 core tests pass (incl. 9 new tremolo)

Follow-ups (deferred)

  • Tempo sync (note-division lock to host BPM / standalone metronome) — intentionally out of scope for this first cut.

Amplitude-modulation effect registered as an Effect-category stage,
appearing in the Effects tab alongside Delay/Reverb/EQ.

A phase-accumulator sine LFO modulates signal gain. A continuous Shape
control morphs the LFO from a smooth sine (vintage tremolo) toward a hard
square via tanh waveshaping — at full Depth + full Shape this is a
square-wave "killswitch" stutter (Tom Morello toggle-switch chop). Depth
is one-pole smoothed to avoid zipper noise; the LFO output is left
unsmoothed so square edges stay crisp.

Controls: Rate (0.1–20 Hz), Depth (0–100%), Shape (sine→square).

Wires the standard stage-registration surface: core stage + config,
StageType/StageConfig, UI view + gui_stage_registry, EN/ZH i18n,
minimap abbreviation, and plugin per-slot params. Covered by 9 unit
tests (passthrough, full-chop silence/unity, unit-range gain, LFO
periodicity, sine fidelity, validation, clamping).
Copilot AI review requested due to automatic review settings June 13, 2026 22:23

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

Adds a new Tremolo effect stage (amplitude modulation via an LFO) across the core DSP, UI stage registry/view, localization, minimap labeling, and plugin parameter surfaces so it can be inserted and automated like existing Effects stages.

Changes:

  • Implemented a new TremoloStage + TremoloConfig in rustortion-core, wired into StageType/StageConfig.
  • Registered the Tremolo stage in the UI (stage registry + dedicated view) and extended EN/ZH translations + minimap abbreviation.
  • Added per-slot Tremolo automation parameter structs/arrays in the plugin params.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
rustortion-core/src/amp/stages/tremolo.rs New DSP stage implementation + unit tests for tremolo LFO, shaping, and parameter validation.
rustortion-core/src/amp/stages/mod.rs Exposes the new tremolo stage module.
rustortion-core/src/preset/stage_config.rs Adds Tremolo to stage enums/config wiring and marks it as an Effect-category stage.
rustortion-ui/src/stages/tremolo.rs New UI view + message/apply plumbing for the tremolo controls.
rustortion-ui/src/stages/mod.rs Registers Tremolo in the gui_stage_registry! so it appears as a selectable stage.
rustortion-ui/src/i18n/mod.rs Adds Tremolo label + control labels (rate, depth, shape) for EN and ZH_CN.
rustortion-ui/src/components/minimap.rs Adds a minimap abbreviation for Tremolo stages.
rustortion-plugin/src/params.rs Adds TremoloSlotParams and an 8-slot tremolo params array for host automation parity.

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

Comment on lines +69 to +72
// Morph sine -> square. At `MIN_DRIVE` the ratio reproduces `raw`;
// at `MAX_DRIVE` it clamps to ~±1 everywhere but the zero crossings.
let drive = (MAX_DRIVE - MIN_DRIVE).mul_add(self.shape, MIN_DRIVE);
let sharp = (raw * drive).tanh() / drive.tanh();
…test

Address review feedback on the tremolo stage:

- Cache `drive` and `drive_norm = 1/tanh(drive)` in the stage and refresh
  them only in `new()` and on `set_parameter("shape", _)`, instead of
  recomputing `drive.tanh()` every sample. Saves one tanh per sample on
  the audio thread; the per-sample numerator is unchanged. Output is
  bit-for-bit equivalent (same formula, just hoisted).
- Add `tremolo_stage_does_not_allocate` to the no_alloc RT-safety suite,
  matching the convention every other effect stage follows.
- Add `set_shape_matches_constructed` guarding the new cache: changing
  shape via set_parameter must match a stage built with that shape.
@OpenSauce OpenSauce enabled auto-merge (squash) June 13, 2026 22:32
@OpenSauce OpenSauce merged commit 13a1bd2 into main Jun 13, 2026
7 checks passed
@OpenSauce OpenSauce deleted the feat/tremolo-stage branch June 13, 2026 22:34
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