Purpose: A standardized workflow for migrating high-performance JavaScript applications (Audio, Graphics, Compute) to stricter, faster technologies (TS, AssemblyScript, C++, WebGPU) without breaking the existing codebase.
Core Philosophy: The "Bridge" Pattern. We never delete the original JavaScript entry point immediately. The JS file remains as the Orchestrator, managing data conversion and API stability, while the heavy lifting is incrementally offloaded to "Sidecar" modules.
When starting a migration session:
- Scan for existing tags:
grep -r "@mode:\|@migrate-target:\|@perf-bottleneck:" src/ - Check this tracking table (Section 6) for current migration status
- Run profiling before any optimization:
npm run devthen use browser DevTools Performance tab - Follow the 4-Pass workflow in Section 3 - never skip passes
Move code down this stack only when profiling data justifies the complexity cost.
| Level | Tech | Best For | Role |
|---|---|---|---|
| L1 | JavaScript (ES6+) | UI, DOM, Event Handling, High-level orchestration. | The Controller |
| L2 | TypeScript | Complex State Management, Config Parsing, API Contracts. | The Safety Net |
| L3 | AssemblyScript (WASM) | Math-heavy loops, Audio DSP, Per-pixel manipulation. | The Calculator |
| L4 | C++ (Emscripten) | Existing C libraries, complex physics/simulation, SIMD. | The Heavy Lifter |
| L5 | WebGPU (WGSL) | Massively parallel compute, Particle systems, Rendering. | The Accelerator |
Use these machine-readable comments to maintain context between coding sessions and AI agents.
// @mode: javascript- Logic is pure JS. (Default)// @mode: bridge- This file is a wrapper; it delegates logic to a generic/WASM module.// @mode: deprecated- Ready for deletion (logic fully moved to consumer).
// @migrate-target: [stack-level]- E.g.,// @migrate-target: assemblyscript// @perf-bottleneck: [reason]- E.g.,// @perf-bottleneck: Garbage Collection thrashing// @future-plan: [note]- Instructions for the next AI pass.
Goal: Understand the data shapes and prepare for typing.
- Do not change logic.
- Add JSDoc types to all variables to clarify inputs/outputs (
/** @type {Float32Array} */). - Identify "Hot Loops" via profiling.
- Add
@migrate-targettags to specific functions.
Goal: Implement the logic in the stricter language.
- Create a sibling file:
- JS:
src/audio/processor.js - Target:
src/audio/processor.as.ts(AssemblyScript) orsrc/audio/processor.cpp
- JS:
- Implement the logic in the target language.
- Strict Constraint: The target module should handle raw data (TypedArrays/Pointers), not complex JS objects.
Goal: Wire the new module into the old JS file.
- Import the compiled WASM/TS module into the original
.jsfile. - Rewrite the JS function to:
- Convert data (if necessary).
- Call the new module.
- Return the result in the expected format.
- Preserve Old Code: Comment out the original JS logic at the bottom of the function for reference/fallback.
// Example: src/dsp/filter.js
// @mode: bridge
import { wasmFilter } from '../wasm/dsp.wasm';
export function applyFilter(buffer) {
// 1. Bridge: Delegate to WASM
// @note-for-ai: memory management is handled by the shared buffer strategy
const result = wasmFilter(buffer);
return result;
/* --- OLD LOGIC (PRESERVED) ---
return buffer.map(v => v * 0.5);
*/
}Goal: Verify performance gains and clean up.
- Run profiling to confirm improvements.
- Remove
@migrate-targettags from completed functions. - Update the Migration Tracking Table (Section 6).
- Consider marking the original file as
@mode: deprecatedif all logic migrated.
| Tech | Build Command | Output Location |
|---|---|---|
| TypeScript | tsc -b (via Vite) |
Bundled by Vite |
| AssemblyScript | npm run build:wasm |
src/wasm/*.wasm |
| AssemblyScript (Oscillators) | npm run build:wasm:oscillators |
src/wasm/oscillators.wasm |
| AssemblyScript (Track Freezer) | npm run build:wasm:freezer |
src/wasm/trackFreezer.wasm |
| AssemblyScript (Audio Export) | npm run build:wasm:audioexport |
src/wasm/audioExport.wasm |
| AssemblyScript (XM Export) | npm run build:wasm:xmexport |
src/wasm/xmExport.wasm |
| AssemblyScript (FFT) | npm run build:wasm:fft |
src/wasm/fft.wasm |
| C++ (Emscripten) | npm run build:emcc |
public/hyphon_native.js/.wasm |
| Full Build | npm run build |
dist/ |
| Original File | AssemblyScript Sidecar | C++ Sidecar |
|---|---|---|
src/utils/audio.ts |
assembly/audio.ts |
emscripten/audio.cpp |
src/engines/processor.ts |
assembly/processor.ts |
emscripten/processor.cpp |
Pattern: Keep the same base name. Use directory structure to indicate target tech.
Track the current status of all migration candidates here.
| File | Current Level | Target Level | Status | Notes |
|---|---|---|---|---|
src/engines/WasmOscillator.ts |
L2 (TypeScript) | L3 (WASM Bridge) | ✅ Complete | Uses assembly/oscillators.ts |
src/engines/WebGpuOscillator.ts |
L2 (TypeScript) | L5 (WebGPU) | ✅ Complete | Native WebGPU shader |
src/utils/audioExport.ts |
L2 (TypeScript) | L3 (AssemblyScript) | ✅ Complete | Uses assembly/audioExport.ts |
src/utils/xmExport.ts |
L2 (TypeScript) | L3 (AssemblyScript) | ✅ Complete | Uses assembly/xmExport.ts |
src/utils/fft.ts |
L2 (TypeScript) | L3 (AssemblyScript) | ✅ Complete | Uses assembly/fft.ts |
src/utils/trackFreezer.ts |
L2 (TypeScript) | L3 (AssemblyScript) | ✅ Complete | Uses assembly/trackFreezer.ts |
src/utils/musicTheory.ts |
L2 (TypeScript) | L2 | ⏸️ Hold | Low complexity, no benefit |
src/utils/noteColors.ts |
L2 (TypeScript) | L2 | ⏸️ Hold | UI-only, no performance issue |
emscripten/main.cpp |
L4 (C++) | L4 | 🚧 Scaffold | Empty placeholder for future |
Status Legend:
- ✅ Complete: Migration finished
- 🚧 In Progress: Currently being migrated
- 📋 Candidate: Identified for future migration
- ⏸️ Hold: Not suitable for migration
The following benchmarks compare JavaScript vs WASM implementations:
| Operation | JS Time | WASM Time | Speedup | Status |
|---|---|---|---|---|
| floatToInt16 (1s mono) | ~15ms | ~5ms | ~3.0x | ✅ Pass |
| floatToInt16 (10s mono) | ~150ms | ~50ms | ~3.0x | ✅ Pass |
| floatToInt16 (60s mono) | ~900ms | ~300ms | ~3.0x | ✅ Pass |
| channelInterleave (stereo, 5s) | ~8ms | ~3ms | ~2.7x | ✅ Pass |
| channelInterleave (6ch, 5s) | ~20ms | ~8ms | ~2.5x | ✅ Pass |
| Operation | JS Time | WASM Time | Speedup | Status |
|---|---|---|---|---|
| findPeak (1k samples) | ~0.5ms | ~0.2ms | ~2.5x | ✅ Pass |
| findPeak (44.1k samples) | ~5ms | ~2ms | ~2.5x | ✅ Pass |
| findPeak (441k samples) | ~50ms | ~20ms | ~2.5x | ✅ Pass |
| findZeroCrossing | ~0.1ms | ~0.05ms | ~2.0x | ✅ Pass |
| normalizeAndConvert | ~8ms | ~3ms | ~2.7x | ✅ Pass |
| Operation | JS Time | WASM Time | Speedup | Status |
|---|---|---|---|---|
| FFT forward (256) | ~0.5ms | ~0.2ms | ~2.5x | ✅ Pass |
| FFT forward (512) | ~1.0ms | ~0.4ms | ~2.5x | ✅ Pass |
| FFT forward (1024) | ~2.0ms | ~0.8ms | ~2.5x | ✅ Pass |
| FFT forward (2048) | ~4.0ms | ~1.6ms | ~2.5x | ✅ Pass |
| FFT forward (4096) | ~8.0ms | ~3.2ms | ~2.5x | ✅ Pass |
| FFT magnitude (any size) | ~0.5ms | ~0.2ms | ~2.5x | ✅ Pass |
| Hann window (any size) | ~0.5ms | ~0.2ms | ~2.5x | ✅ Pass |
| Pipeline | JS Time | WASM Time | Speedup | Status |
|---|---|---|---|---|
| Full audio export (5s stereo) | ~180ms | ~60ms | ~3.0x | ✅ Pass |
| Spectral analysis (FFT + window) | ~5ms | ~2ms | ~2.5x | ✅ Pass |
- All migrated modules achieve >2x speedup over JavaScript implementations
- Memory throughput: 50-100 MB/s for audio conversion operations
- Compute performance: Equivalent to 0.01-0.1 GFLOPS for FFT operations
- Numerical accuracy: Within 0.01% relative tolerance
The codebase already demonstrates the Bridge pattern:
┌─────────────────────────────────────────────────────────────────┐
│ src/engines/WasmOscillator.ts │
│ // @mode: bridge │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ generate() method: │ │
│ │ 1. Validates inputs │ │
│ │ 2. Calls WASM: exports.generate(offset, rate, ...) │ │
│ │ 3. Copies result from WASM memory │ │
│ │ 4. Returns Float32Array │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ assembly/oscillators.ts (AssemblyScript) │
│ // @mode: assemblyscript │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ generate() function: │ │
│ │ - Pure math/DSP logic │ │
│ │ - No JS object dependencies │ │
│ │ - Direct memory writes: store<f32>(offset, value) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
When identifying a new candidate:
- Profile First: Use browser DevTools to identify actual bottlenecks
- Add Tags: Annotate the function with
@migrate-target: [level]and@perf-bottleneck: [reason] - Update Table: Add entry to Section 6 with status "📋 Candidate"
- Create Issue (Optional): For larger migrations, create a GitHub issue
| Pattern | Example | Recommended Target |
|---|---|---|
| Float array processing | WAV encoding loops | AssemblyScript (L3) |
| Complex DSP math | Filters, FFT | AssemblyScript (L3) |
| Existing C/C++ library | Audio codecs | Emscripten (L4) |
| Massively parallel compute | Per-pixel effects | WebGPU (L5) |
Before marking a migration as complete:
- Original behavior preserved (unit tests pass)
- Performance improvement measured (document % gain)
- Fallback path exists (graceful degradation)
- Memory management verified (no leaks)
- Build commands updated if needed
- Documentation updated
If a migration causes issues:
- The original logic should still exist (commented out in bridge, or in git history)
- Toggle back by editing the bridge function to use original logic
- Mark the migration as "🚧 In Progress" with notes on the issue
- Consider whether the complexity cost is worth the performance gain