Skip to content

Latest commit

 

History

History
168 lines (137 loc) · 4.34 KB

File metadata and controls

168 lines (137 loc) · 4.34 KB

Code Refactoring Summary

Priority 2: Extract Playback Functions ✅ COMPLETE

Changes Made

New Directory Structure

src/
└── audio/
    └── playback/
        ├── index.ts              # Module exports
        ├── synthPlayback.ts      # ~290 lines
        ├── drumPlayback.ts       # ~110 lines
        └── samplerPlayback.ts    # ~240 lines

Files Modified

  • src/hooks/useAudioEngine.ts - Reduced from ~715 lines to ~420 lines

Code Extracted

1. Synth Playback (src/audio/playback/synthPlayback.ts)

Functions:

  • playSynth() - Main synth playback with multiple modes
    • Open303 (TB-303 clone) routing
    • WAV buffer playback (wav-saw, wav-sqr)
    • Standard oscillators (sawtooth, square, triangle, sine)
    • ADSR envelope
    • Filter and delay effects
  • noteOnSynth() - Interactive note trigger
  • noteOffSynth() - Note release
  • stopAllSynthNotes() - Panic stop
  • apply303Params() - Parameter mapping for 303

Types:

interface SynthPlaybackContext {
    context: AudioContext;
    masterGain: GainNode;
    open303Engine?: Open303Oscillator | null;
    wavSawBuffer: AudioBuffer | null;
    wavSqrBuffer: AudioBuffer | null;
}

interface SynthNoteState {
    nextNoteId: number;
    activeNotes: Map<number, { stop: () => void }>;
}

2. Drum Playback (src/audio/playback/drumPlayback.ts)

Functions:

  • playDrum() - Drum sound synthesis
    • Kick (oscillator with pitch envelope)
    • Snare (tone + filtered noise)
    • Hi-hats (high-pass filtered noise)

Types:

interface DrumPlaybackContext {
    context: AudioContext;
    masterGain: GainNode;
    noiseBuffer: AudioBuffer | null;
}

3. Sampler Playback (src/audio/playback/samplerPlayback.ts)

Functions:

  • playSampler() - Sample playback
    • One-shot and looped modes
    • Time-stretching with SingingVoice
    • Phoneme slicing mode
  • noteOnSampler() - Interactive trigger
  • noteOffSampler() - Note release with envelope
  • stopAllSamplerNotes() - Panic stop
  • loadSampleToEngine() - Sample loading
  • prepareVocal() - Phoneme alignment

Types:

interface SamplerPlaybackContext {
    context: AudioContext;
    masterGain: GainNode;
    singingVoice?: SingingVoice | null;
}

interface SamplerState {
    loadedSampleBuffers: Map<string, AudioBuffer>;
    vocalAlignments: Map<string, AlignmentResult>;
    nextNoteId: number;
    activeNotes: Map<number, { source: AudioBufferSourceNode; envGain: GainNode }>;
}

Benefits

  1. Separation of Concerns

    • Audio engine initialization (useAudioEngine.ts)
    • Audio playback logic (playback modules)
    • Each module has a single responsibility
  2. Testability

    • Playback functions can be tested in isolation
    • No dependency on React hooks
    • Pure functions with clear inputs/outputs
  3. Maintainability

    • Smaller, focused files
    • Clear module boundaries
    • Easier to find and modify specific functionality
  4. Reusability

    • Playback functions can be used in other contexts
    • Not tied to the hook lifecycle

Statistics

Metric Before After Change
useAudioEngine.ts ~715 lines ~420 lines -41%
New playback files 0 3 files +640 lines
Total audio code ~715 lines ~1060 lines +48% (separation)

API Usage

The playback functions are used in useAudioEngine.ts by creating context objects:

const synthCtx: SynthPlaybackContext = {
    context,
    masterGain,
    open303Engine: open303EngineRef.current,
    wavSawBuffer: wavSawBufferRef.current,
    wavSqrBuffer: wavSqrBufferRef.current,
};

const drumCtx: DrumPlaybackContext = {
    context,
    masterGain,
    noiseBuffer: noiseBufferRef.current,
};

const samplerCtx: SamplerPlaybackContext = {
    context,
    masterGain,
    singingVoice: singingVoiceRef.current,
};

Then wrapped for the AudioEngine interface:

const wrappedPlaySynth = (params: SynthParams, note: string, time: number, durationSteps?: number, stepTime?: number) => {
    playSynth(synthCtx, params, note, time, durationSteps, stepTime);
};

Testing

All existing tests pass (175/180):

  • 5 pre-existing failures unrelated to refactoring
  • No new test failures introduced
  • TypeScript compiles without errors

Next Steps

Ready for Priority 3: Break up App.tsx