Skip to content

feat: add Convolver IR, Limiter curve, and upgraded EQ3 visualizations#1417

Open
ChuxiJ wants to merge 2 commits intomainfrom
feat/issue-1241-effect-viz-v2
Open

feat: add Convolver IR, Limiter curve, and upgraded EQ3 visualizations#1417
ChuxiJ wants to merge 2 commits intomainfrom
feat/issue-1241-effect-viz-v2

Conversation

@ChuxiJ
Copy link
Copy Markdown

@ChuxiJ ChuxiJ commented Apr 3, 2026

Summary

  • ConvolverIRCurve: Bipolar IR waveform visualization with ER/tail region markers, pre-delay shading, and factory IR type badges (ROOM/HALL/PLATE/SPRING)
  • LimiterCurve: Input/output transfer function with ceiling line, gain reduction shading, soft/hard knee per style mode
  • EQ3Curve upgrade: From 150×40 to 200×80px with log-scale frequency grid (100/1k/10k), dB grid (±6dB), crossover markers, gradient fill, and effect color prop
  • Uses EFFECT_COLORS constants throughout (no hardcoded colors)

18 new unit tests for utility functions (convolverIR.ts, limiterCurve.ts).

Quality Gates

  • npx tsc --noEmit — 0 errors
  • npm test — 4025 passed (18 new)
  • Build succeeds

Test plan

  • Verify ConvolverIRCurve renders for all 4 factory IR types
  • Verify LimiterCurve transfer function with different gain/ceiling/style combos
  • Verify EQ3Curve frequency grid and band gain response
  • No visual regressions in existing effect cards

Closes #1241

https://claude.ai/code/session_01EeAnf2HpreRaQwsixZuRRE

#1241)

Implements remaining per-effect signature visualizations:
- ConvolverIRCurve: bipolar IR waveform with ER/tail markers, factory IR type badges
- LimiterCurve: input/output transfer function with ceiling, GR shading, soft knee
- EQ3Curve: upgraded from 150×40 to 200×80px with log freq grid, dB grid, crossovers

Includes utility functions (convolverIR.ts, limiterCurve.ts) with 18 unit tests.
Uses EFFECT_COLORS constants instead of hardcoded values.

Closes #1241

https://claude.ai/code/session_01EeAnf2HpreRaQwsixZuRRE
Copilot AI review requested due to automatic review settings April 3, 2026 21:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds new limiter and convolver visualizations plus upgrades the EQ3 curve rendering to support the “per-effect signature visualizations” initiative (Issue #1241), backed by new math utilities and unit tests.

Changes:

  • Introduces pure math generators for limiter transfer curves and convolver IR envelopes (+ unit tests).
  • Adds new canvas visual components: LimiterCurve and ConvolverIRCurve, and wires them into the effect cards.
  • Upgrades EQ3 visualization (larger canvas, log-frequency grid, dB grid, crossover markers, gradient fill, color prop).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/utils/limiterCurve.ts New math utility to generate limiter transfer curve points (with style-based knee).
src/utils/convolverIR.ts New math utility to generate synthetic IR envelope + ER spike positions/markers.
src/utils/tests/limiterCurve.test.ts Unit tests covering limiter curve invariants and style behavior.
src/utils/tests/convolverIR.test.ts Unit tests for IR envelope generation, ER spikes, and boundary/length helpers.
src/components/mixer/LimiterCurve.tsx New canvas visualization for limiter transfer curve + GR region rendering.
src/components/mixer/ConvolverIRCurve.tsx New canvas visualization for convolver IR waveform with ER/tail markers and pre-delay shading.
src/components/mixer/EffectCards.tsx Wires new visualizations into Limiter/Convolver cards and upgrades EQ3 curve rendering.

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

Comment on lines +38 to +46
export function generateIREnvelope(
irType: FactoryIRType,
preDelay: number,
/** Number of intervals; returns steps + 1 points including both endpoints */
steps: number = 160,
): IREnvelopePoint[] {
const profile = IR_PROFILES[irType];
const preDelayS = preDelay / 1000; // ms → seconds
const totalLength = preDelayS + profile.length;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The generateIREnvelope/getIRReflections APIs treat preDelay as milliseconds (they immediately divide by 1000), but the JSDoc for generateIREnvelope doesn’t document the unit. Please clarify the unit in the function docs/param description to prevent callers from accidentally passing seconds (this file’s utils are otherwise unit-explicit in comments).

Copilot uses AI. Check for mistakes.
Comment on lines +292 to +301
// Background
const grad = ctx.createRadialGradient(W / 2, drawH / 2, 0, W / 2, drawH / 2, W * 0.6);
grad.addColorStop(0, 'rgba(12, 16, 28, 0.92)');
grad.addColorStop(1, 'rgba(4, 6, 14, 0.98)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);

// Label area
ctx.fillStyle = 'rgba(0, 0, 0, 0.22)';
ctx.fillRect(0, drawH, W, labelH);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This EQ3 visualization reintroduces hardcoded background/label colors (rgba(12, 16, 28, …), rgba(0, 0, 0, 0.22)) instead of using the shared canvas theme helpers/constants (e.g., fillBackground, LABEL_AREA_BG from src/utils/canvasTheme.ts). For consistency with other mixer visualizations (e.g., FilterResponseCurve.tsx / ReverbDecayCurve.tsx), consider reusing the shared theme so global styling tweaks don’t require editing this component.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +32
export function LimiterCurve({
ceiling,
gain,
style,
width = 160,
height = 100,
color = '#d4a040',
}: LimiterCurveProps) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The default color prop is hardcoded to #d4a040, which duplicates EFFECT_COLORS.limiter and contradicts the PR description’s “no hardcoded colors” goal. Consider making color required (since callers already pass EFFECT_COLORS.limiter) or centralizing effect colors in a non-EffectCards module that visual components can import without creating circular dependencies.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +32
export function ConvolverIRCurve({
irType,
preDelay,
wet,
width = 160,
height = 100,
color = '#a07cc8',
}: ConvolverIRCurveProps) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The default color prop is hardcoded to #a07cc8, duplicating EFFECT_COLORS.convolver and conflicting with the PR description’s “no hardcoded colors” goal. Consider making color required (since callers pass EFFECT_COLORS.convolver) or moving effect color constants to a dedicated module that both EffectCards and visualization components can import.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +47
// Find a point near ceiling where they differ
const nearCeiling = warm.findIndex((p) => p.inputDb + 6 > -1);
if (nearCeiling > 0 && nearCeiling < warm.length - 1) {
// Warm should output slightly higher (less aggressive limiting) just below ceiling
const wVal = warm[nearCeiling - 2].outputDb;
const aVal = aggressive[nearCeiling - 2].outputDb;
// Both should be similar but warm transitions more gently
expect(Math.abs(wVal - aVal)).toBeLessThan(5);
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This test claims to verify that “warm style has softer knee than aggressive”, but the assertion only checks that the two values are within 5 dB of each other, which would still pass even if the knee behavior regressed or inverted. Consider asserting an explicit ordering/relationship at a point inside the knee region (e.g., warm output should be higher than aggressive for the same input near the ceiling) so the test actually protects the intended behavior.

Suggested change
// Find a point near ceiling where they differ
const nearCeiling = warm.findIndex((p) => p.inputDb + 6 > -1);
if (nearCeiling > 0 && nearCeiling < warm.length - 1) {
// Warm should output slightly higher (less aggressive limiting) just below ceiling
const wVal = warm[nearCeiling - 2].outputDb;
const aVal = aggressive[nearCeiling - 2].outputDb;
// Both should be similar but warm transitions more gently
expect(Math.abs(wVal - aVal)).toBeLessThan(5);
}
// Find a point inside the knee region, just before hitting the ceiling
const nearCeiling = warm.findIndex((p) => p.inputDb + 6 > -1);
expect(nearCeiling).toBeGreaterThan(1);
expect(nearCeiling).toBeLessThan(warm.length);
// Warm should output higher (less aggressive limiting) than aggressive
// at the same input just below the ceiling.
const kneeIdx = nearCeiling - 2;
const wVal = warm[kneeIdx].outputDb;
const aVal = aggressive[kneeIdx].outputDb;
expect(wVal).toBeGreaterThan(aVal);

Copilot uses AI. Check for mistakes.
- Add preDelay unit (ms) to convolverIR.ts JSDoc
- Make color prop required on LimiterCurve and ConvolverIRCurve (no hardcoded defaults)
- Use shared canvasTheme helpers (drawCanvasBackground, LABEL_AREA_BG) in EQ3Curve
- Strengthen limiter knee test to assert warm > aggressive output ordering

https://claude.ai/code/session_01EeAnf2HpreRaQwsixZuRRE
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.

feat: Per-Effect Signature Visualizations — Real-time Curves, Meters, Waveforms for Every Effect

2 participants