Files
motif/Phase 2 Implementation v2.0.md
b1rdmania 5e127c3a3e Add v2 Game Boy sound engine (isolated from v1)
- Authentic DMG-CPU sound chip implementation:
  - 4 Pulse channels with duty cycle control (12.5%, 25%, 50%, 75%)
  - 2 Wave channels with 4-bit wavetables
  - 2 Noise channels with LFSR (7-bit and 15-bit modes)

- GameBoy Colorizer effect chain:
  - Low-pass filter (natural GB rolloff)
  - Bit-crushing (4-bit DAC simulation)
  - Sample rate reduction
  - Saturation and high-pass filter
  - Presets: DMG, GBC, GBA, Clean

- Intelligent MIDI processing:
  - Track analysis and role detection (bass, lead, drums, etc.)
  - Automatic channel mapping to GB channels
  - Chord arpeggiator for polyphony handling
  - GameBoy Arranger for fuller sound

- BitMidi search integration
- Completely isolated from v1 (no changes to src/)
2026-01-20 19:36:13 +00:00

604 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Wario Synth v2: Phase 2 Implementation Plan
**Project:** Wario Synthesis Engine v2
**Owner:** Andy @ birdmania
**Status:** Implementation Phase
**Target:** Game Boy-authentic MIDI conversion with enhanced polyphony
**Repository:** https://github.com/b1rdmania/motif
---
## Project Overview
Build a Game Boy-authentic synthesis engine (v2) that captures the true DMG-CPU sound chip character while preserving v1's "any MIDI works" philosophy. The v2 engine will be hosted separately and won't intrude on v1's functionality.
**Key Architecture Decision:** 8-channel "Super Game Boy" setup (4 pulse, 2 wave, 2 noise) instead of authentic 4-channel limitation. This preserves GB sound character while handling complex MIDI files gracefully.
---
## Critical Constraint: v1 Isolation
**v1 MUST remain completely untouched and functional throughout v2 development.**
### What stays UNTOUCHED (v1):
- `src/synthesis/SynthesisEngine.ts` - current engine, no changes
- `src/core/MotifEngine.ts` - current orchestration, no changes
- All existing files in `src/` - completely untouched
- `index.html`, `play.html`, `embed.html` - no modifications
- Live site at wario.style continues to work exactly as today
### What gets CREATED (v2):
- Brand new directory: `src-v2/` - entirely separate codebase
- New entry point: `v2.html` - doesn't touch existing HTML files
- Separate Vite build config if needed
- Could become separate repo later if desired
### What v2 CAN import (read-only):
- `src/midi/MIDIParser.ts` - reuse existing MIDI parsing
- Shared types from `src/types/index.ts` if compatible
- Nothing else - all synthesis code is fresh
### Benefits of this approach:
1. Develop v2 while v1 stays live and stable
2. If v2 has issues, v1 is completely unaffected
3. A/B test with simple URL switch (`/` vs `/v2`)
4. Rollback is trivial - just don't deploy v2 code
5. Can eventually merge or keep separate forever
---
## Repository Structure
```
/Users/andy/MOTIF/
├── src/ # v1 (current, unchanged)
│ ├── core/MotifEngine.ts
│ ├── synthesis/SynthesisEngine.ts
│ └── ...
├── src-v2/ # v2 (NEW - separate tree)
│ ├── audio/
│ │ ├── apu/
│ │ │ ├── APU.ts # Main 8-channel coordinator
│ │ │ ├── PulseChannel.ts # GB pulse with duty cycles
│ │ │ ├── WaveChannel.ts # 4-bit wavetable
│ │ │ ├── NoiseChannel.ts # LFSR noise
│ │ │ └── Mixer.ts # Stereo mixing
│ │ ├── synthesis/
│ │ │ ├── DutyCycle.ts # 4 GB duty patterns
│ │ │ ├── LFSR.ts # Noise generator
│ │ │ ├── WaveTable.ts # 4-bit quantization
│ │ │ └── FrequencyCalc.ts # GB frequency formulas
│ │ └── midi/
│ │ ├── TrackAnalyzer.ts # Analyze MIDI structure
│ │ ├── ChannelMapper.ts # Assign tracks to 8 channels
│ │ └── Arpeggiator.ts # Convert chords to arps
│ ├── core/
│ │ └── GameBoyPlayer.ts # Main v2 entry point
│ └── types/
│ └── index.ts # v2-specific types
├── public/
│ └── v2/ # v2 UI assets
└── v2.html # v2 demo page
```
---
## Phase 1: Core Sound Engine (Week 1-2)
**Goal:** Implement authentic GB sound generation with Web Audio API
### 1.1 Duty Cycle Implementation
Create `src-v2/audio/synthesis/DutyCycle.ts`:
- Define 4 GB duty patterns (12.5%, 25%, 50%, 75%)
- Convert patterns to `PeriodicWave` for Web Audio
- Pattern format: 8-step arrays `[0,0,0,0,0,0,0,1]` etc
Create `src-v2/audio/apu/PulseChannel.ts`:
- Pre-create all 4 duty waveforms on init
- `playNote(midiNote, duration, velocity)` method
- Use GB frequency formula (not standard MIDI)
- Simple envelope (fast attack, quick release)
- Return `{ osc, gain }` for cleanup
**Success Criteria:** Each duty cycle sounds distinctly different when tested
### 1.2 GB Frequency Formulas
Create `src-v2/audio/synthesis/FrequencyCalc.ts`:
- `calculatePulseFrequency(midiNote)` → Hz
- Formula: `131072 / (2048 - registerValue)`
- Convert MIDI → standard freq → register → GB freq
- `calculateWaveFrequency(midiNote)` → Hz
- Formula: `65536 / (2048 - registerValue)`
- `calculateNoiseFrequency(pitch, mode)` → Hz
- Formula: `524288 / divisor / 2^(shift+1)`
**Success Criteria:** Frequencies are slightly "off" from standard tuning (GB characteristic)
### 1.3 LFSR Noise Generator
Create `src-v2/audio/synthesis/LFSR.ts`:
- Implement 15-bit LFSR (default mode)
- Implement 7-bit LFSR (tonal mode)
- `clock()` method returns 0 or 1
- XOR bits 0 and 1, shift right, set bit 14
Create `src-v2/audio/apu/NoiseChannel.ts`:
- `playNoise(duration, frequency, velocity)` method
- Generate buffer with LFSR output
- Clock LFSR at calculated rate
- Apply envelope to buffer playback
**Success Criteria:** Noise sounds crunchy/metallic, not smooth white noise
### 1.4 Wave Channel with 4-bit Quantization
Create `src-v2/audio/synthesis/WaveTable.ts`:
- Store 32 samples × 4-bit (0-15 values)
- `quantize(value)` rounds to 4-bit
- Presets: `generateBass()`, `generatePad()`, `generateLead()`
- `createBuffer(audioContext)` converts to AudioBuffer
Create `src-v2/audio/apu/WaveChannel.ts`:
- Load wavetable on construction
- `playNote(midiNote, duration, velocity)` method
- Use `BufferSource` with looping
- Set playback rate for pitch
- Volume levels: 0, 100%, 50%, 25% (bit-shift style)
**Success Criteria:** Wave channel has audible digital "staircase" effect
### 1.5 Testing Phase 1
Create `src-v2/audio/test/soundTest.ts`:
- Test all 4 duty cycles sequentially
- Test wave channel with bass preset
- Test both noise modes (7-bit and 15-bit)
- Play test sequence: duty sweeps, bass note, drum hits
---
## Phase 2: Channel Manager & 8-Channel APU (Week 2-3)
**Goal:** Coordinate 8 independent GB channels with mixing
### 2.1 APU Coordinator
Create `src-v2/audio/apu/APU.ts`:
- Initialize 4 pulse, 2 wave, 2 noise channels
- Channel IDs: `p1-p4`, `w1-w2`, `n1-n2`
- Master gain connected to destination
- Per-channel gain nodes for mixing
- `scheduleNote(note: ChannelNote)` routes to appropriate channel
- Track which channels are busy (`channelBusy` map)
- `isChannelFree(channelId, atTime)` for voice allocation
**Key Methods:**
- `scheduleNote({ channel, midiNote, startTime, duration, velocity })`
- `schedulePulseNote()`, `scheduleWaveNote()`, `scheduleNoiseNote()`
- `setChannelPan(channelId, pan)` for stereo
### 2.2 Channel Gain & Mixing
In `src-v2/audio/apu/APU.ts`:
- Each channel connects to individual `GainNode`
- Individual gains connect to master gain
- Master gain at ~0.7 to prevent clipping
- Per-channel volumes match role importance
**Success Criteria:** Can play 8 simultaneous notes without clipping
### 2.3 Integration Test
Create `src-v2/audio/test/apuTest.ts`:
- Schedule notes on all 8 channels simultaneously
- Verify no audio glitches or pops
- Test channel busy/free logic
- Test master volume control
---
## Phase 3: MIDI Intelligence Layer (Week 3-4)
**Goal:** Smart track analysis and channel assignment for arbitrary MIDIs
### 3.1 Track Analyzer
Create `src-v2/audio/midi/TrackAnalyzer.ts`:
- `analyzeTrack(track)` returns `TrackAnalysis`
- Detect drums (channel 9 or percussive patterns)
- Calculate note range (min, max, avg pitch)
- Calculate note density (notes per second)
- Detect chords (simultaneous notes)
- Assign role: `drums`, `bass`, `lead`, `harmony`, `pad`, `fx`
**Analysis Logic:**
- Drums: channel 9 OR very short notes with low pitch variation
- Bass: average pitch < 48 (C3)
- Lead: high pitch (>72) with high density (>5 notes/sec)
- Pad: low density (<2 notes/sec), long notes
- Harmony: medium density with detected chords
- FX: very high density (>10 notes/sec)
### 3.2 Arpeggiator
Create `src-v2/audio/midi/Arpeggiator.ts`:
- `arpeggiate(notes, speed)` converts chords to fast note sequences
- Group notes by time (10ms tolerance)
- Single notes pass through unchanged
- Chords (2+ simultaneous notes) → fast arpeggio
- Default speed: 1/64 note
- Sort chord notes low-to-high
- Cycle through chord notes for full duration
**Success Criteria:** 3-note chord becomes smooth fast arpeggio
### 3.3 Channel Mapper
Create `src-v2/audio/midi/ChannelMapper.ts`:
- `mapTracks(midiTracks)` returns array of `ChannelAssignment`
- Analyze all tracks first
- Sort by priority (drums > bass > lead > harmony)
- Assign intelligently:
- Drums → `n1`, `n2` (noise channels)
- Bass → `w1` (wave bass preset)
- Pads → `w2` (wave pad preset)
- Lead → `p1`, `p2` (pulse with sweep, 50% duty)
- Harmony → `p3`, `p4` (pulse, 25% duty, arpeggiated)
- Mark which tracks need arpeggiator
- Specify duty cycle per assignment
**Priority Calculation:**
- Drums +30 points
- Bass +25 points
- Lead +20 points
- Note density +up to 20 points
- Velocity +up to 10 points
**Success Criteria:** Mario theme maps melody to pulse, no bass assigned (no bass in song)
### 3.4 Integration Test
Create `src-v2/audio/test/mapperTest.ts`:
- Load test MIDI (simple melody + bass + drums)
- Run through analyzer and mapper
- Verify drum tracks → noise channels
- Verify bass → wave channel
- Verify melody → pulse channel
- Print channel assignments for inspection
---
## Phase 4: Main Player & Integration (Week 4)
**Goal:** Complete end-to-end MIDI → GB audio pipeline
### 4.1 Game Boy Player
Create `src-v2/core/GameBoyPlayer.ts`:
- Main entry point for v2 engine
- `async playMIDI(midiBuffer: ArrayBuffer)`
- Parse MIDI using existing `src/midi/MIDIParser.ts`
- Analyze tracks → assign channels → convert to GB notes
- Schedule all notes in APU
- `stop()` method resets APU
- Return playback info (duration, assignments for UI)
**Pipeline:**
1. Parse MIDI → `NoteEvent[]`
2. Analyze tracks → `TrackAnalysis[]`
3. Map to channels → `ChannelAssignment[]`
4. Apply arpeggiator where needed
5. Convert to `ChannelNote[]` format
6. Schedule in APU
### 4.2 V2 Types
Create `src-v2/types/index.ts`:
- `ChannelNote` interface
- `ChannelAssignment` interface
- `TrackAnalysis` interface
- `ArpNote` interface
- GB-specific config types
### 4.3 Demo Page
Create `v2.html`:
- Simple test UI for v2 engine
- File upload input for MIDI
- Play/stop buttons
- Volume slider
- Display channel assignments
- Show which channels are active (visual)
Create `src-v2/main.ts`:
- Wire up UI to `GameBoyPlayer`
- Handle file uploads
- Display playback state
- Show assignment information
### 4.4 Integration Test
Test with reference MIDIs:
- **Mario theme:** Simple melody (should use p1)
- **Tetris theme:** Bass + lead (should use w1 + p1)
- **Pokémon theme:** Chords (should arpeggiate to p3/p4)
- **Hotel California:** Complex (use all 8 channels)
**Success Criteria:**
- All test MIDIs sound recognizable
- No audio glitches or pops
- Drums sound punchy (noise)
- Bass sounds solid (wave)
- Melody is clear (pulse)
- Chords arpeggiate smoothly
---
## Phase 5: Polish & Optimization (Week 5)
### 5.1 Performance Optimization
In `src-v2/audio/apu/APU.ts`:
- Limit simultaneous notes to 32 total
- Implement voice stealing (oldest note first)
- Add `onended` cleanup for oscillators
- Pre-create reusable nodes where possible
### 5.2 Browser Compatibility
In `src-v2/core/GameBoyPlayer.ts`:
- Add AudioContext resume on user interaction
- Handle Safari audio quirks
- Add mobile audio unlock
- Test on Chrome, Firefox, Safari
### 5.3 Advanced Features (Nice-to-Have)
- **Stereo Panning:** GB-style hard L/R/center per channel
- **Duty Cycle Switching:** Change duty mid-playback for variation
- **Custom Wavetables:** User-editable wave presets
- **Export to WAV:** Offline rendering to downloadable file
---
## Phase 6: Deployment Strategy
### 6.1 Alpha Testing (Week 6)
- Deploy v2 to staging URL (e.g., `v2.wario.style` or `wario.style/beta`)
- Keep v1 at main URL unchanged
- Test with small group (5-10 people)
- Gather feedback on authenticity
- Fix critical bugs
### 6.2 Beta Release (Week 7)
- Deploy to production behind feature flag
- Add "Try v2 Beta" button on main site
- A/B test user preferences (v1 vs v2)
- Monitor performance metrics
- Gradual rollout: 10% → 50% → 100%
### 6.3 Full Release (Week 8)
- Make v2 the default engine
- Keep v1 available as "Classic Mode"
- Update README and docs
- Social media announcement
- Monitor error rates and feedback
**Rollback Plan:**
- Feature flag can instantly revert to v1
- Maintain "problematic MIDI" database
- User preference saved in localStorage
---
## Technical Notes
### Web Audio Implementation
**Key Web Audio APIs:**
- `PeriodicWave` for duty cycles
- `OscillatorNode` for pulse channels
- `AudioBufferSourceNode` for wave/noise
- `GainNode` for volume/envelopes
- `audioContext.currentTime` for precise scheduling
**Memory Management:**
```typescript
source.onended = () => {
source.disconnect()
gain.disconnect()
}
```
**Latency Target:** <50ms from schedule to sound
### What Matters vs What Doesn't
**✅ CRITICAL (Implement):**
- Exact duty cycle patterns
- LFSR noise generation
- 4-bit wave quantization
- GB frequency formulas
- Fast arpeggios
- Simple envelopes
**❌ SKIP (Emulator minutiae):**
- Length counter edge cases
- DIV-APU timing sync
- Wave RAM corruption bugs
- Sweep overflow quirks
- DAC pop suppression
- High-pass filter modeling
---
## Testing Strategy
Testing follows a layered approach - technical specs for implementation correctness, community feedback for authenticity.
### Layer 1: Technical Sanity Checks (Automated, Quick)
Quick checks that catch implementation bugs:
- **Waveform visualization** - view duty cycles in browser dev tools or canvas oscilloscope
- **LFSR sequence verification** - first 20 values match known GB sequence
- **Frequency spot-check** - play A4 (440Hz), verify it's slightly off (~438.5Hz due to GB register rounding)
Setup time: ~30 minutes. Run on every build.
### Layer 2: Reference MIDI Corpus (Manual, Essential)
Core QA loop with 5 test MIDIs:
| MIDI | What it tests |
|------|---------------|
| Mario Bros theme | Simple melody on pulse channels |
| Tetris theme | Bass + lead separation |
| Pokemon battle music | Chord arpeggiation |
| Any pop song with drums | Noise channel percussion |
| Hotel California | Complex multi-track mapping |
Process: Run each through v2, listen with headphones, note what sounds wrong.
### Layer 3: Community Vibe Check (Subjective, Final)
Post short clips to:
- r/chiptunes subreddit
- Chiptune Café Discord
Ask: "Does this sound like a Game Boy?"
Real chiptune people will identify specific issues ("duty cycles wrong", "noise too clean", etc.)
### What to Skip
- Automated audio comparison (too complex, diminishing returns)
- Cycle-accurate timing tests (emulator territory, not our goal)
- Formal A/B studies (overkill for this project)
### Success Metrics
- All Layer 1 checks pass
- All 5 reference MIDIs sound recognizable
- Community feedback: "yes, sounds like GB"
- Zero audio glitches or pops
- Works on Chrome, Firefox, Safari
- <100ms latency
---
## File Checklist
### Core Sound Engine (Phase 1)
- [ ] `src-v2/audio/synthesis/DutyCycle.ts`
- [ ] `src-v2/audio/synthesis/FrequencyCalc.ts`
- [ ] `src-v2/audio/synthesis/LFSR.ts`
- [ ] `src-v2/audio/synthesis/WaveTable.ts`
- [ ] `src-v2/audio/apu/PulseChannel.ts`
- [ ] `src-v2/audio/apu/WaveChannel.ts`
- [ ] `src-v2/audio/apu/NoiseChannel.ts`
- [ ] `src-v2/audio/test/soundTest.ts`
### APU & Mixing (Phase 2)
- [ ] `src-v2/audio/apu/APU.ts`
- [ ] `src-v2/audio/apu/Mixer.ts`
- [ ] `src-v2/audio/test/apuTest.ts`
### MIDI Intelligence (Phase 3)
- [ ] `src-v2/audio/midi/TrackAnalyzer.ts`
- [ ] `src-v2/audio/midi/Arpeggiator.ts`
- [ ] `src-v2/audio/midi/ChannelMapper.ts`
- [ ] `src-v2/audio/test/mapperTest.ts`
### Integration (Phase 4)
- [ ] `src-v2/core/GameBoyPlayer.ts`
- [ ] `src-v2/types/index.ts`
- [ ] `src-v2/main.ts`
- [ ] `v2.html`
### Documentation
- [ ] `docs/GB_SOUND_SPECS.md` (technical reference)
- [ ] `docs/V2_ARCHITECTURE.md` (system overview)
- [ ] `CHANGELOG_V2.md` (version history)
---
## Future Enhancements (v3+)
**Short Term:**
- User-adjustable duty cycles via UI
- Custom wavetable editor
- Real-time parameter tweaking
- Oscilloscope visualizer
- MIDI file upload (not just search)
**Medium Term:**
- Frequency sweep on pulse channels
- Vibrato effects
- Echo/delay using note repeats
- Better envelope shaping (ADSR editor)
- Recording/export to WAV
**Long Term:**
- Full tracker-style sequencer
- Multiple retro chips (NES APU, C64 SID)
- VST plugin version
- Mobile app with touch controls
- Collaborative editing
---
**Document Version:** 2.0
**Last Updated:** January 2026
**Status:** Ready for implementation