Skip to content

studio: add Autotune audio effect (real-time pitch correction, Rust/WASM DSP)#321

Open
SynthsBack-lab wants to merge 1 commit into
andremichelle:mainfrom
SynthsBack-lab:device-autotune
Open

studio: add Autotune audio effect (real-time pitch correction, Rust/WASM DSP)#321
SynthsBack-lab wants to merge 1 commit into
andremichelle:mainfrom
SynthsBack-lab:device-autotune

Conversation

@SynthsBack-lab

@SynthsBack-lab SynthsBack-lab commented Jul 21, 2026

Copy link
Copy Markdown

Adds Autotune, a stock audio-effect device for real-time monophonic pitch correction, listed in the Audio Effects browser as "Pitch Correction".

Draft for early feedback — happy to split, rename, or rework any part to fit the project's conventions.
Rebased onto current main (d998a2d7): the AudioComposite / Stereo Split / Frequency Split registrations (including EFFECT_COMPOSITES in engine-modules.ts) are preserved alongside the new device, and the DSP is Rust-only in line with the TypeScript audio engine's removal.

The Autotune device editor

Feature set

  • Real-time monophonic pitch correction — YIN pitch detection driving a TD-PSOLA pitch shifter (no doppler/"tape-speed" artefact; duration and formants preserved)
  • Key & scale snapping — root pitch class (C…B) × 8 scales (Chromatic, Major, Minor, Major/Minor Pentatonic, Blues, Dorian, Mixolydian); chromatic always snaps to the nearest semitone (note-decision hysteresis applies to diatonic scales only)
  • Amount — square-law-tapered correction depth for fine control in the light-correction range where an in-tune voice actually sits
  • Retune — one knob sweeping natural → hard-tune: sets both the note-glide speed (500 ms → 2 ms) and the vibrato-flatten depth (the robotic hard-tune character at the top end)
  • Smooth — damps the correction ratio for steady, S-curved note transitions without touching the sung vibrato
  • Shift — manual transpose (−12…+12 st) composed after the snap
  • Live tuner strip — detected note, target note, and cents needle streamed from the audio thread via the broadcast telemetry path (same mechanism as the peak meters)
  • Stability guards proven on real vocals — octave fold, voicing debounce/hangover, bounded vibrato-flatten path; detection verified at ≈98.6 % agreement (<50 cents) against CREPE (neural) ground truth on recorded vocals

Architecture: Rust-only DSP

All DSP is no_std, zero-allocation Rust compiled to WASM — there is no TypeScript audio path, matching the engine architecture on current main:

  • crates/dsp/src/autotune.rs — detection + note decision + correction dynamics (control core, never touches audio)
  • crates/dsp/src/psola.rs — streaming TD-PSOLA shifter
  • crates/stock-devices/device-autotune/ — the device (params, telemetry broadcast, per-block wiring)

The TypeScript side contributes only the parameter adapter (UI/automation value mappings) and the editor. Determinism is guarded by a Rust golden decision fixture plus an all-scales nearest-note property test, and the WASM↔adapter parameter mapping by an autotune case in param-mapping-parity.test.ts (EffectFactories.keyOfBox coverage included).

docs/autotune.md documents the full concept, every DSP stage with its constants, the parameters, and a step-by-step integration recipe for a Rust/WASM device.

Verification (all on this branch, standalone off main @ d998a2d7)

  • turbo build: 27/27 tasks (all packages incl. studio app + full core-wasm)
  • cargo test -p dsp: 95 passed, -p device-autotune: 3 passed
  • lib-dsp vitest: 95 passed; EffectFactories.test.ts: 3 passed
  • param-mapping parity (packages/app/wasm): 43 passed, including the new autotune WASM↔TS-adapter case
  • Manual: device instantiates next to FX Composite / Stereo Split / Frequency Split, editor renders (screenshot above), no console errors

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an Autotune audio effect with real-time pitch correction, scale-aware note targeting, retuning, smoothing, pitch shifting, and vibrato reduction.
    • Added controls for key, scale, correction amount, retune speed, smoothing, and shift.
    • Added a live tuner display showing detected notes and pitch deviation.
    • Integrated the effect into the device library, editor, project schema, and audio engine.
  • Documentation
    • Added comprehensive Autotune usage and implementation documentation.
  • Tests
    • Added coverage for pitch correction, shifting, parameter mapping, and tuner behavior.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a monophonic Autotune audio effect with YIN detection, scale snapping, TD-PSOLA pitch shifting, WASM integration, studio registration, parameter controls, telemetry, and a live tuner display.

Changes

Autotune DSP core

Layer / File(s) Summary
Pitch detection and correction control
crates/dsp/src/autotune.rs, crates/dsp/src/lib.rs
Adds fixed-buffer YIN detection, voicing gates, deterministic scale targeting, correction smoothing, vibrato flattening, telemetry, and unit tests.
TD-PSOLA pitch shifting
crates/dsp/src/psola.rs, crates/dsp/src/lib.rs
Adds streaming analysis-mark scheduling, correlation refinement, Hann-window overlap-add synthesis, fixed latency, and pitch-shifting tests.
DSP design documentation
docs/autotune.md
Documents the detection, targeting, correction, telemetry, parity, and signal-flow behavior.

Device runtime and studio integration

Layer / File(s) Summary
WASM device and schema
crates/stock-devices/device-autotune/*, packages/studio/forge-boxes/..., crates/studio-boxes/src/registry.rs
Defines the Autotune device crate, ABI functions, parameters, runtime processing, schema, registry entry, and device tests.
Build and module loading
packages/studio/core-wasm/build-wasm.sh, packages/studio/core-wasm/src/engine-modules.ts
Builds and loads device_autotune.wasm as a studio device module.
Effect factories and adapter
packages/studio/core/src/EffectBox.ts, packages/studio/core/src/EffectFactories.ts, packages/studio/adapters/...
Registers the effect type and factory, maps parameters, resolves its host, exposes audio output metadata, and adds mapping parity coverage.

Studio UI

Layer / File(s) Summary
Autotune editor and tuner
packages/app/studio/src/ui/devices/...
Adds the editor factory mapping, parameter knobs, peak meter, live tuner telemetry subscription, SVG note display, cents needle, and component styling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AudioInput
  participant AutotuneDevice
  participant Autotune
  participant Psola
  participant Tuner
  AudioInput->>AutotuneDevice: provide stereo audio block
  AutotuneDevice->>Autotune: feed audio and detect target
  AutotuneDevice->>Psola: set period and correction ratio
  Psola->>AudioInput: write processed output block
  AutotuneDevice->>Tuner: broadcast MIDI, target, and voiced state
Loading

Suggested reviewers: andremichelle

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 87.95% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the Autotune audio effect with Rust/WASM DSP pitch correction.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@SynthsBack-lab
SynthsBack-lab marked this pull request as ready for review July 21, 2026 07:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/studio/core-processors/src/devices/audio-effects/AutotuneDeviceProcessor.ts (1)

29-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move #tuner construction into the constructor for consistency.

#tuner is inline-initialized at the field declaration, while every sibling field (#output, #dsp, #core, #peaks) is constructed inside the constructor body. As per coding guidelines, "Move complex field initializations into the constructor instead of using inline field initializers."

♻️ Proposed fix
-    readonly `#tuner` = new Float32Array(3)
+    readonly `#tuner`: Float32Array

     `#source`: Option<AudioBuffer> = Option.None

     constructor(context: EngineContext, adapter: AutotuneDeviceBoxAdapter) {
         super(context)
         this.#adapter = adapter
         this.#output = new AudioBuffer()
         this.#dsp = new Psola(sampleRate)
         this.#core = new Autotune(sampleRate)
         this.#peaks = this.own(new PeakBroadcaster(context.broadcaster, adapter.address))
+        this.#tuner = new Float32Array(3)

Also applies to: 33-39

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/studio/core-processors/src/devices/audio-effects/AutotuneDeviceProcessor.ts`
at line 29, Move the Float32Array construction for `#tuner` from its field
declaration into the constructor, alongside the existing initialization of
`#output`, `#dsp`, `#core`, and `#peaks`. Keep `#tuner` declared as a readonly field and
preserve its length of 3.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/autotune.md`:
- Around line 449-450: Correct Step 7 in the autotune documentation to reference
packages/studio/core-wasm/build-wasm.sh, removing the incorrect src/ segment
while preserving the instruction to update DEVICE_CRATES.

---

Nitpick comments:
In
`@packages/studio/core-processors/src/devices/audio-effects/AutotuneDeviceProcessor.ts`:
- Line 29: Move the Float32Array construction for `#tuner` from its field
declaration into the constructor, alongside the existing initialization of
`#output`, `#dsp`, `#core`, and `#peaks`. Keep `#tuner` declared as a readonly field and
preserve its length of 3.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4aea1a52-5f8c-48d1-8992-d3dcdada9a84

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1920c and d287489.

⛔ Files ignored due to path filters (2)
  • crates/Cargo.lock is excluded by !**/*.lock
  • docs/autotune-device.png is excluded by !**/*.png
📒 Files selected for processing (32)
  • crates/dsp/src/autotune.rs
  • crates/dsp/src/lib.rs
  • crates/dsp/src/pitch_shift.rs
  • crates/dsp/src/psola.rs
  • crates/stock-devices/device-autotune/Cargo.toml
  • crates/stock-devices/device-autotune/src/lib.rs
  • crates/studio-boxes/src/registry.rs
  • docs/autotune.md
  • packages/app/studio/src/ui/devices/DeviceEditorFactory.tsx
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneDeviceEditor.sass
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneDeviceEditor.tsx
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneTuner.sass
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneTuner.tsx
  • packages/app/wasm/test/param-mapping-parity.test.ts
  • packages/lib/dsp/src/autotune.test.ts
  • packages/lib/dsp/src/autotune.ts
  • packages/lib/dsp/src/index.ts
  • packages/lib/dsp/src/pitch-shift.test.ts
  • packages/lib/dsp/src/pitch-shift.ts
  • packages/lib/dsp/src/psola.test.ts
  • packages/lib/dsp/src/psola.ts
  • packages/studio/adapters/src/BoxAdapters.ts
  • packages/studio/adapters/src/DeviceManualUrls.ts
  • packages/studio/adapters/src/devices/audio-effects/AutotuneDeviceBoxAdapter.ts
  • packages/studio/core-processors/src/DeviceProcessorFactory.ts
  • packages/studio/core-processors/src/devices/audio-effects/AutotuneDeviceProcessor.ts
  • packages/studio/core-wasm/build-wasm.sh
  • packages/studio/core-wasm/src/engine-modules.ts
  • packages/studio/core/src/EffectBox.ts
  • packages/studio/core/src/EffectFactories.ts
  • packages/studio/forge-boxes/src/schema/devices/audio-effects/AutotuneDeviceBox.ts
  • packages/studio/forge-boxes/src/schema/devices/index.ts

Comment thread docs/autotune.md Outdated
Comment on lines +449 to +450
7. **Add the crate to the WASM build:** append its name to `DEVICE_CRATES` in
`packages/studio/core-wasm/src/build-wasm.sh`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the actual build-wasm.sh under core-wasm to confirm the documented path.
fd -a build-wasm.sh packages/studio/core-wasm

Repository: andremichelle/openDAW

Length of output: 218


Fix the build-wasm.sh path Step 7 points to packages/studio/core-wasm/src/build-wasm.sh, but the file lives at packages/studio/core-wasm/build-wasm.sh. The extra src/ will send readers to the wrong place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/autotune.md` around lines 449 - 450, Correct Step 7 in the autotune
documentation to reference packages/studio/core-wasm/build-wasm.sh, removing the
incorrect src/ segment while preserving the instruction to update DEVICE_CRATES.

@andremichelle

Copy link
Copy Markdown
Owner

Thanks for this, it is a really strong contribution. The device follows the stock-device conventions closely, the abi AudioEffect wiring, the field and mapping constants, the telemetry broadcast at address.append(0), and the DSP is zero-allocation with fixed buffers and no_std, initialised in place from a zeroed state. I ran the tests locally and they pass (dsp 96, device-autotune 3). The thorough docs and the CREPE-verified detection are great to see.

One thing needs sorting before we can merge. The branch is based on main from July 16, so it predates the AudioComposite, Stereo Split, and Frequency Split work that has since landed on main. Your diff itself is clean, but a rebase onto current main will conflict in the device-registration files that both efforts append to. Could you rebase onto the latest main and re-resolve these:

  • packages/studio/core-wasm/src/engine-modules.ts
  • packages/studio/core/src/EffectBox.ts
  • packages/studio/core/src/EffectFactories.ts
  • packages/studio/core/src/EffectFactories.test.ts
  • packages/app/studio/src/ui/devices/DeviceEditorFactory.tsx
  • packages/studio/adapters/src/BoxAdapters.ts
  • packages/studio/core-processors/src/DeviceProcessorFactory.ts
  • crates/studio-boxes/src/registry.rs
  • packages/studio/forge-boxes/src/schema/devices/index.ts

They are all additive, both sides just add a new device or entry to the same lists, so it is a matter of keeping both. The one to watch is engine-modules.ts: the recent work added the EFFECT_COMPOSITES block and the effectComposites field on EngineModules, please keep those alongside your new autotune entry so the Frequency Split and composite devices are not dropped.

Once it is rebased and turbo build plus the test suites are green against current main, it is good to go. Thanks again, this is a great addition.

A stock audio-effect device: YIN pitch detection (2:1-decimated, f64-exact
decisions) drives a scale-mask note snap with hysteresis and octave fold,
glided by Retune/Smooth with a separate vibrato-flatten path, rendered
through a zero-allocation TD-PSOLA shifter (no doppler, formants preserved).

- Rust no_std DSP (crates/dsp: autotune, psola) + device crate compiled to
  WASM; no TypeScript audio path
- key/scale snapping across 8 scales; chromatic always snaps the nearest
  semitone (hysteresis is diatonic-only), verified by an all-scales
  nearest-note property test and a golden decision fixture
- TS parameter adapter with a param-mapping parity test case
- editor UI with live tuner telemetry (detected note, target, cents needle)
  streamed over the broadcast path
- docs/autotune.md: full concept, DSP reference, and integration guide

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/dsp/src/autotune.rs (1)

262-266: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor clippy cleanups (cosmetic only).

Static analysis flags a few purely stylistic spots; behavior is unaffected:

  • Line 263: ((note % 12) + 12) % 12 is a manual rem_euclidnote.rem_euclid(12) is equivalent and clearer.
  • Lines 275-281: the if/else if bodies are identical (best = candidate; best_distance = distance;); could be merged with ||, though keeping them separate arguably documents the two distinct tie-break rationales.
  • Line 349: f0 >= F0_MIN_HZ && f0 <= F0_MAX_HZ could use (F0_MIN_HZ..=F0_MAX_HZ).contains(&f0).

None of these affect correctness.

Also applies to: 268-284, 349-349

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/dsp/src/autotune.rs` around lines 262 - 266, Apply the cosmetic clippy
cleanups in the autotuning code: update allowed to use note.rem_euclid(12),
merge the identical best/candidate assignment branches around the tie-break
logic while preserving their combined condition, and replace the f0 range
comparisons with an inclusive range contains check. Preserve all existing
behavior and tie-break semantics.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/dsp/src/autotune.rs`:
- Around line 262-266: Apply the cosmetic clippy cleanups in the autotuning
code: update allowed to use note.rem_euclid(12), merge the identical
best/candidate assignment branches around the tie-break logic while preserving
their combined condition, and replace the f0 range comparisons with an inclusive
range contains check. Preserve all existing behavior and tie-break semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dda7d2eb-6dbd-4812-93d1-99494488413c

📥 Commits

Reviewing files that changed from the base of the PR and between d287489 and 0106cf8.

⛔ Files ignored due to path filters (2)
  • crates/Cargo.lock is excluded by !**/*.lock
  • docs/autotune-device.png is excluded by !**/*.png
📒 Files selected for processing (23)
  • crates/dsp/src/autotune.rs
  • crates/dsp/src/lib.rs
  • crates/dsp/src/psola.rs
  • crates/stock-devices/device-autotune/Cargo.toml
  • crates/stock-devices/device-autotune/src/lib.rs
  • crates/studio-boxes/src/registry.rs
  • docs/autotune.md
  • packages/app/studio/src/ui/devices/DeviceEditorFactory.tsx
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneDeviceEditor.sass
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneDeviceEditor.tsx
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneTuner.sass
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneTuner.tsx
  • packages/app/wasm/test/param-mapping-parity.test.ts
  • packages/studio/adapters/src/BoxAdapters.ts
  • packages/studio/adapters/src/DeviceManualUrls.ts
  • packages/studio/adapters/src/devices/audio-effects/AutotuneDeviceBoxAdapter.ts
  • packages/studio/core-wasm/build-wasm.sh
  • packages/studio/core-wasm/src/engine-modules.ts
  • packages/studio/core/src/EffectBox.ts
  • packages/studio/core/src/EffectFactories.test.ts
  • packages/studio/core/src/EffectFactories.ts
  • packages/studio/forge-boxes/src/schema/devices/audio-effects/AutotuneDeviceBox.ts
  • packages/studio/forge-boxes/src/schema/devices/index.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • crates/studio-boxes/src/registry.rs
  • packages/studio/forge-boxes/src/schema/devices/index.ts
  • packages/studio/core/src/EffectBox.ts
  • packages/studio/core-wasm/src/engine-modules.ts
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneDeviceEditor.tsx
  • packages/studio/adapters/src/BoxAdapters.ts
  • crates/stock-devices/device-autotune/Cargo.toml
  • packages/studio/forge-boxes/src/schema/devices/audio-effects/AutotuneDeviceBox.ts
  • packages/app/studio/src/ui/devices/DeviceEditorFactory.tsx
  • packages/studio/core/src/EffectFactories.ts
  • packages/app/studio/src/ui/devices/audio-effects/AutotuneTuner.tsx
  • packages/app/wasm/test/param-mapping-parity.test.ts
  • crates/stock-devices/device-autotune/src/lib.rs
  • crates/dsp/src/psola.rs
  • packages/studio/adapters/src/devices/audio-effects/AutotuneDeviceBoxAdapter.ts

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