Skip to content

feat!: Add worklet ecosystem, offline rendering, and processor metrics; drop legacy ScriptProcessorNode API#65

Merged
cutterbl merged 30 commits into
masterfrom
feat/processRefactor
May 14, 2026
Merged

feat!: Add worklet ecosystem, offline rendering, and processor metrics; drop legacy ScriptProcessorNode API#65
cutterbl merged 30 commits into
masterfrom
feat/processRefactor

Conversation

@cutterbl
Copy link
Copy Markdown
Owner

Summary

This PR introduces a major expansion of the SoundTouchJS worklet ecosystem and cleans up the core API by removing the long-deprecated ScriptProcessorNode path. Targets v2.0.0.

⚠️ Breaking Changes

ScriptProcessorNode API removed

PitchShifter, SimpleFilter, WebAudioBufferSource, getWebAudioNode, FilterSupport, minsSecs, and noop are no longer exported from @soundtouchjs/core. SoundTouch no longer exposes rate, tempo, virtualRate, virtualTempo, or their change setters — these are now derived internally from virtualPitch. SoundTouchNode no longer has tempo or rate AudioParams; playback speed is controlled by mirroring playbackRate on both the source node and SoundTouchNode.

AudioWorklet is supported in all targeted browsers including iOS Safari 14.5+. The deprecated ScriptProcessorNode path has no replacement path within this library.

License

Upgraded from LGPL-2.1 to LGPL-3.0. Review compliance obligations if you redistribute.

Interpolation strategy IDs renamed

Strategy IDs hann8, blackman8, kaiser8, and lanczos8 are now hann, blackman, kaiser, and lanczos. The kernel width parameter is now consistently zeroCrossings across all strategies.


New Packages

Package Description
@soundtouchjs/stretch-phase-vocoder Phase vocoder WSOLA stretch algorithm for @soundtouchjs/core
@soundtouchjs/phase-vocoder-worklet AudioWorklet node/processor pair using the phase vocoder stretch pipeline
@soundtouchjs/formant-correction-worklet AudioWorklet node/processor pair with built-in formant correction
@soundtouchjs/interpolation-strategy-hann Hann-windowed interpolation strategy
@soundtouchjs/interpolation-strategy-blackman Blackman-windowed interpolation strategy
@soundtouchjs/interpolation-strategy-kaiser Kaiser-windowed interpolation strategy

Features

  • @soundtouchjs/core: Add StretchPipe interface and stretchFactory option, allowing the WSOLA stretch stage to be swapped out at construction time. Expose WSOLA timing parameters (sequenceMs, seekWindowMs, overlapMs).
  • processOffline(): All three worklet packages now export a processOffline() helper for non-real-time rendering via OfflineAudioContext.
  • Processor observability: ProcessorMetrics now includes outputRms and outputPeak (per 128-frame block, both channels) alongside the existing framesBuffered, underrunCount, blockCount, and timestamp fields.
  • Storybook: Added live processor metrics display to all playground stories. Full MDX docs for all new packages and updated API tables for changed packages.

Refactoring

  • Standardized interpolation strategy IDs and parameter names across all strategy packages, tests, and docs.
  • Refactored the processing pipeline internals to decouple rate transposition from stretch stage selection.

CI / Tooling

  • Switched Storybook GitHub Pages deploy to GitHub Actions.
  • nx.json release config: added explicit changelog properties to all conventionalCommits.types entries so changelog generation is not silently governed by the angular preset defaults.
  • Pre-commit hook now runs both typecheck and lint.

cutterbl added 29 commits May 7, 2026 16:12
…s\n\n- Rename all strategy IDs (hann8, blackman8, kaiser8, lanczos8) to (hann, blackman, kaiser, lanczos)\n- Standardize on zeroCrossings for kernel width param\n- Update all code, tests, and docs for new naming and parameter conventions\n- Remove deprecated lanczos8-strategy.mdx and update navigation\n- Add and update kitchen sink playgrounds for all strategies
…controls

BREAKING CHANGE: PitchShifter, SimpleFilter, WebAudioBufferSource,
getWebAudioNode, and FilterSupport are no longer exported from
@soundtouchjs/core. SoundTouch no longer exposes rate, tempo, virtualRate,
or virtualTempo. SoundTouchNode no longer exposes tempo or rate AudioParams.

Remove PitchShifter, SimpleFilter, WebAudioBufferSource, getWebAudioNode,
FilterSupport, minsSecs, and noop — all solely supported the deprecated
ScriptProcessorNode path. AudioWorklet is supported in all targeted browsers
including iOS Safari 14.5+.

Simplify SoundTouch to pitch-only public control: remove rate, tempo,
virtualRate, virtualTempo, rateChange, and tempoChange setters. Internal
_rate and _tempo are now always derived from virtualPitch.

Remove tempo and rate AudioParams from SoundTouchNode. Playback speed is
controlled exclusively by mirroring playbackRate on both the source node
and SoundTouchNode.

Add CLAUDE.md with mandatory code change requirements: JSDoc, tests, and
documentation gates, plus Conventional Commits table and project conventions.

Update all READMEs, Storybook stories, and .mdx docs to reflect the current
public API and remove all references to removed symbols. Consolidate three
duplicate root-level .mdx files into their beginner-friendly core/ versions.
…t conventions

Add Commands and Architecture sections documenting Nx task commands, the
SoundTouch processing pipeline, AudioWorklet pattern, worklet processor
bundling, interpolation strategy plugin system, test setup, and Nx
auto-detection rules. Correct the test command from pnpm --filter to
pnpm nx test, add feat!/BREAKING CHANGE commit row, and document required
files for new worklet packages.

fix(audio-worklet): Add outputChannelCount option to SoundTouchNode

Add optional outputChannelCount?: 1 | 2 to SoundTouchNodeConstructorOptions
so callers can connect SoundTouchNode to mono destinations without creating
a separate ChannelMergerNode. Defaults to 2 (stereo). Document the existing
mono input fallback in processor.ts with inline comments. Add tests for all
three cases (default, 1, 2) and add an explicit mono output processor test.
Add StretchParameters interface (sequenceMs, seekWindowMs, overlapMs, quickSeek)
and setStretchParameters() method to Stretch and SoundTouch for fine-grained
control of the WSOLA time-stretch algorithm at runtime.

Add public overlapMs getter/setter and quickSeek getter to Stretch, making the
previously-hidden timing internals accessible without replacing all parameters
at once. Add SetStretchParametersMessage to the processor message union so
SoundTouchNode.setStretchParameters() can queue updates to the render thread.
…dering

Add processOffline() helper that renders an AudioBuffer through SoundTouch in
an OfflineAudioContext without requiring a live audio device. Accepts pitch,
pitchSemitones, playbackRate, interpolationStrategy, stretchParameters, and
sampleBufferType options. Output length is estimated as ceil(input.length /
playbackRate).
Processor posts framesBuffered, underrunCount, and blockCount to the
main thread every 100 render blocks. SoundTouchNode stores the latest
snapshot as ProcessorMetrics, exposes it via a metrics getter, and
dispatches a metrics CustomEvent for event-driven monitoring.
…olution

Documents how to resolve processorModuleUrl in Vite, webpack 5, and
static hosting setups, plus OfflineAudioContext usage and common
first-time mistakes (registration order, CORS, relative paths, autoplay).
Introduces StretchPipe as a structural interface for the time-stretch
stage, implemented by the existing Stretch class. SoundTouchOptions
gains a stretchFactory callback so callers can substitute a custom
stage (e.g. a phase vocoder) without subclassing. Also fixes stale
@cxing/ scopes and missing packages in nx.json release.projects.
Adds two new packages:
- @soundtouchjs/stretch-phase-vocoder: FFT-based StretchPipe implementation
  using radix-2 Cooley-Tukey FFT, Hann-windowed overlap-add synthesis, and
  per-bin phase accumulation. Includes createPhaseVocoderFactory for drop-in
  use with SoundTouchOptions.stretchFactory.
- @soundtouchjs/phase-vocoder-worklet: AudioWorklet integration wrapping
  PhaseVocoderNode (mirrors SoundTouchNode API) and PhaseVocoderProcessor
  (uses SoundTouch with the phase vocoder stretchFactory). Adds fftSize and
  overlapFactor constructor options. Full metrics observability support.
Adds @soundtouchjs/formant-correction-worklet — an AudioWorkletNode
that applies SoundTouch pitch-shifting with LPC-based formant
preservation. Use FormantCorrectionNode instead of SoundTouchNode when
pitching vocals without the chipmunk/giant timbre shift.

Key additions:
- lpc.ts: autocorrelate, levinsonDurbin, applyAnalysisFilter,
  applySynthesisFilter (LPC order 16, 512-sample Hamming window)
- formant-correction-processor.ts: per-block LPC correction with
  formantStrength AudioParam blend (0 = raw, 1 = corrected)
- FormantCorrectionNode.ts: AudioWorkletNode with pitch, pitchSemitones,
  playbackRate, and formantStrength AudioParams; metrics getter/event
- Storybook playground with formantStrength slider and A/B toggle
- Full Vitest test suite (37 tests), README, and Storybook MDX docs
…grounds

Add StretchParametersPlayground with live sliders for sequenceMs,
seekWindowMs, overlapMs, and a quickSeek toggle — all wired to
SoundTouchNode.setStretchParameters() and applied immediately to the
running audio graph during playback.

Add ProcessOfflinePlayground that fetches a track, renders it through
processOffline() in an OfflineAudioContext, then plays the resulting
AudioBuffer. Shows render time, output duration, and live code sample
reflecting current parameter values.

Also fix a pre-existing MDX parse failure in getting-started.mdx caused
by smart/curly quotes throughout the file that prevented the Storybook 9
acorn indexer from parsing the compiled MDX output.
- Wire ProcessorMetrics event listener into AudioWorkletPlayground,
  StretchParametersPlayground, FormantCorrectionPlayground, and
  PhaseVocoderPlayground; metrics panel is always visible, defaulting to 0
- Fix Storybook static build: change processor/installer imports from
  ?url to ?worker&url so Vite bundles each dependency inline
- Add worker: { format: 'es' } and publicDir to storybook vite.config.mts
- Add .github/workflows/storybook-pages.yml for GitHub Pages deployment
- Restructure CI: merge test + coverage-summary into single validation job
- Raise @soundtouchjs/core coverage thresholds to branches: 85, functions: 95
- Add interpolationStrategyRegistry tests to reach 85.75% branch coverage
- Migrate ESLint config from .eslintrc.cjs to per-package .eslintrc.json
- Add static-storybook to .gitignore
- Add processOffline() to phase-vocoder-worklet and formant-correction-worklet
- Add outputRms and outputPeak to ProcessorMetrics for all worklet packages
- Update demo metrics panel to display outputRms and outputPeak
- Update PhaseVocoderPlayground to surface expanded metrics
- Update Storybook and README docs for offline rendering and observability APIs
- Fix nx release changelog rules and semverBump for non-code commit types
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

Coverage check

Result: ✅ passed

Generated: 2026-05-14T21:37:12.644Z

Status Package Branches Functions +5 Target Test Run
🟢 @soundtouchjs/audio-worklet 96.07% 100.00% YES PASS
🟢 @soundtouchjs/core 85.75% 96.79% YES PASS
🟢 @soundtouchjs/formant-correction-worklet 88.23% 100.00% YES PASS
🟢 @soundtouchjs/interpolation-strategy-blackman 86.04% 100.00% YES PASS
🟢 @soundtouchjs/interpolation-strategy-hann 87.50% 100.00% YES PASS
🟢 @soundtouchjs/interpolation-strategy-kaiser 87.80% 100.00% YES PASS
🟢 @soundtouchjs/interpolation-strategy-lanczos 96.29% 100.00% YES PASS
🟢 @soundtouchjs/interpolation-strategy-linear 89.74% 100.00% YES PASS
🟢 @soundtouchjs/phase-vocoder-worklet 88.52% 100.00% YES PASS
🟢 @soundtouchjs/stretch-phase-vocoder 86.95% 100.00% YES PASS
🟢 TOTAL 88.02% 98.39% YES n/a

@cutterbl cutterbl merged commit 5846c08 into master May 14, 2026
2 checks passed
@cutterbl cutterbl deleted the feat/processRefactor branch May 15, 2026 15:02
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.

1 participant