# Audio Recording Guide

This guide explains how to add audio recording to minigames.

## Two Approaches

### 1. Audio Stream Recording (Recommended for most games)

Records the actual audio output as the user hears it. Perfect for:
- Piano/keyboard instruments
- Synthesizers with continuous parameter changes
- Any game where the exact audio matters

**Pros:**
- Simple to implement
- Captures exactly what the user hears
- Works with any audio source

**Cons:**
- Larger file sizes
- Can't modify recording after capture

Use the `useAudioRecorder` hook:

```typescript
import { useAudioRecorder } from "@/lib/useAudioRecorder";
import { RecordingControls } from "@/components/RecordingControls";

const {
  recordingState,
  recordedAudioUrl,
  connectToRecording,
  startRecording,
  stopRecording,
  clearRecording,
} = useAudioRecorder({ audioContext });

// When creating sounds, connect to recorder:
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(audioContext.destination); // Always connect to speakers
connectToRecording(gain); // Also connect to recorder

// Render controls:
<RecordingControls
  recordingState={recordingState}
  recordedAudioUrl={recordedAudioUrl}
  onStartRecording={startRecording}
  onStopRecording={stopRecording}
  onClearRecording={clearRecording}
  disabled={!audioContext}
/>
```

### 2. Event-Based Recording (For sequencer-like games)

Records timestamps of events and replays by triggering the same sounds. Perfect for:
- Drum pads with discrete hits
- Step sequencers
- Rhythm games

**Pros:**
- Tiny file sizes (just event data)
- Can modify playback (tempo, swap sounds)
- Can export as MIDI

**Cons:**
- More complex implementation
- Must track all events manually
- Playback depends on sound generation being deterministic

Implementation:

```typescript
interface Event {
  type: "drum" | "note";
  data: any;
  time: number; // milliseconds from start
}

const [events, setEvents] = useState<Event[]>([]);
const recordingStartTime = useRef(0);

// When recording starts:
recordingStartTime.current = Date.now();

// When event happens:
if (recordingState === "recording") {
  const time = Date.now() - recordingStartTime.current;
  setEvents(prev => [...prev, { type: "drum", data: drumIndex, time }]);
}

// Playback:
events.forEach(event => {
  setTimeout(() => {
    playSound(event.data);
  }, event.time);
});
```

### 3. Hybrid Approach (Used in drum-pad)

Combines both approaches:
- Records events for replay with playDrum()
- Also records audio stream for export

This gives you the best of both worlds but is more complex.

## Step-by-Step: Adding Recording to Piano

Here's a complete example of adding recording to the piano game:

```typescript
// 1. Import the hook and component
import { useAudioRecorder } from "@/lib/useAudioRecorder";
import { RecordingControls } from "@/components/RecordingControls";

// 2. Add the hook
const recorder = useAudioRecorder({ audioContext });

// 3. Update playNote to connect to recorder
const playNote = (freq: number, noteName: string) => {
  if (!audioContext) return;

  const oscillator = audioContext.createOscillator();
  const gainNode = audioContext.createGain();

  oscillator.type = "triangle";
  oscillator.frequency.setValueAtTime(freq, audioContext.currentTime);

  oscillator.connect(gainNode);
  gainNode.connect(audioContext.destination);

  // ADD THIS: Also connect to recorder
  recorder.connectToRecording(gainNode);

  oscillator.start(audioContext.currentTime);
  // ... rest of implementation
};

// 4. Update stopNote similarly
const stopNote = (noteName: string) => {
  const oscillator = oscillators.current.get(noteName);
  if (!oscillator || !audioContext) return;

  const gainNode = oscillator.context.createGain();
  oscillator.disconnect();
  oscillator.connect(gainNode);
  gainNode.connect(audioContext.destination);

  // ADD THIS: Also connect to recorder
  recorder.connectToRecording(gainNode);

  // ... rest of fade out
};

// 5. Add controls to UI
<RecordingControls
  recordingState={recorder.recordingState}
  recordedAudioUrl={recorder.recordedAudioUrl}
  onStartRecording={recorder.startRecording}
  onStopRecording={recorder.stopRecording}
  onClearRecording={recorder.clearRecording}
  disabled={!audioContext}
/>

// 6. Add space bar shortcut
useEffect(() => {
  const handleKeyDown = (e: KeyboardEvent) => {
    if (e.key === " ") {
      e.preventDefault();
      if (recorder.recordingState === "idle" || recorder.recordingState === "recorded") {
        recorder.startRecording();
      } else {
        recorder.stopRecording();
      }
    }
  };
  window.addEventListener("keydown", handleKeyDown);
  return () => window.removeEventListener("keydown", handleKeyDown);
}, [recorder.recordingState]);
```

## Key Concepts

### MediaStreamDestinationNode

This Web Audio API node creates a MediaStream that can be recorded with MediaRecorder. Think of it as a "virtual microphone" inside your audio graph.

```
[Sound Source] → [Effects] → [Split to 2 destinations]
                                  ↓              ↓
                            [speakers]    [recorder]
```

### connectToRecording helper

The `connectToRecording` function from the hook safely connects any audio node to the recording destination. It handles cases where recording isn't initialized yet.

### Recording States

- `idle`: Not recording, no recording available
- `recording`: Currently recording
- `recorded`: Recording finished, playback available

## Best Practices

1. **Always connect to both destinations:**
   ```typescript
   gain.connect(audioContext.destination); // Speakers
   recorder.connectToRecording(gain); // Recorder
   ```

2. **Handle keyboard shortcuts:**
   - Space bar: Start/stop recording
   - Prevents conflicts with other game controls

3. **Visual feedback:**
   - Show recording indicator when recording
   - Show playback controls when recorded
   - Disable buttons appropriately

4. **Clean up:**
   - The hook automatically cleans up audio URLs
   - But be aware of memory if recording very long sessions

## Troubleshooting

**No sound in recording:**
- Make sure you connect audio nodes to the recording destination
- Check that recording started before playing sounds

**Recording works but playback is silent:**
- Check browser console for MediaRecorder errors
- Try a different mimeType (webm vs ogg)

**Timing issues (faster/slower playback):**
- Check browser console for timing mismatch warnings
- This can happen due to sample rate differences between AudioContext and MediaRecorder
- The hook now logs diagnostic info to help identify the issue
- Look for: `⚠️ Timing mismatch detected!` in console
- Common causes:
  - AudioContext at 48kHz but MediaRecorder encoding at 44.1kHz (or vice versa)
  - Browser resampling issues
  - Codec-specific timing problems
- Workaround: Use event-based recording for games with discrete events (drums, notes)

**Large file sizes:**
- Consider event-based recording for longer sessions
- Or implement time limits on recordings

**Can't record on mobile:**
- Some mobile browsers don't support MediaRecorder
- Show appropriate error message to users

## Debugging Timing Issues

The `useAudioRecorder` hook now includes comprehensive logging. Open your browser console when recording to see:

1. **MediaStream settings**: Sample rate and channel info
2. **Recording start**: Codec and bitrate info
3. **Recording complete**: File size and actual recording duration
4. **Duration comparison**: Compares wall-clock time with recorded audio duration
5. **Timing mismatch warning**: Alerts if durations differ by >0.1 seconds

Example console output:
```
MediaStream audio track settings: {sampleRate: 48000, channelCount: 2, audioContextSampleRate: 48000}
Recording started with audio/webm;codecs=opus at auto bps
Recording complete: 125.42KB, type: audio/webm, actual duration: 5.23s
Recorded audio duration: 5.45s (expected: 5.23s)
⚠️ Timing mismatch detected! Recorded: 5.45s, Expected: 5.23s
```
