Files
motif/MIDI-playback-integration-plan.md
T
b1rdmania 4fdc6984bc Enhance MIDI search results and dual player UI
- Improved search results table with detailed metadata display
- Added functionality to preview original MIDI alongside generated motifs
- Implemented automatic selection of the best result with visual confidence indicators
- Enhanced user experience with a "Try Next Result" button for A/B testing

These updates provide users with better insights into MIDI quality and facilitate easier comparisons between original and generated compositions.
2025-12-18 12:18:44 +00:00

6.8 KiB
Raw Blame History

MIDI playback MVP — detailed technical integration plan

Product target (MVP)

  • User flow:
    1. User types a song name (e.g. “Hotel California”)
    2. App searches MIDI sources and shows a ranked list
    3. User selects a result and can play the MIDI “correctly” using General MIDI soundfonts
    4. User can then click Generate Motif to synthesize a “similar-but-different” version from the same parsed MIDI

Current code reality (gaps to close)

  • Search is currently biased toward mock/synthetic, not real MIDI:
    • server/src/services/MIDISearchService.ts includes MockAdapter first.
  • Fetch can silently replace real URLs with synthetic MIDI:
    • server/src/services/MIDIFetchService.ts generates synthetic MIDI when URL contains bitmidi.com/uploads, preventing true BitMidi playback.
  • Frontend preview is oscillator-based, not GM soundfont playback.

Architecture (what well ship)

flowchart LR
User -->|typesQuery| FrontendUI
FrontendUI -->|GET /api/midi/search?q=...| BackendSearch
BackendSearch -->|rankedResults| FrontendUI
FrontendUI -->|selectResult + GET /api/midi/fetch?u=...| BackendFetch
BackendFetch -->|midiBytes| FrontendParse
FrontendParse -->|NoteEvents + TrackMeta| PreviewPlayerGM
FrontendParse -->|NoteEvents| MotifEngine
MotifEngine -->|roles + chords| SynthesisEngine
PreviewPlayerGM --> AudioOut
SynthesisEngine --> AudioOut

Phase 0 — “Real MIDI mode” defaults (backend hardening)

0.1 Gate mock adapter behind env flag

  • Change: In server/src/services/MIDISearchService.ts, make adapters:
    • Default: [BitMidiAdapter, DongraysAdapter]
    • Optional: prepend MockAdapter only if USE_MOCK_ADAPTER=1 (or similar)
  • Acceptance:
    • Searching “Hotel California” returns non-synthetic:* results when internet is available.
    • Devs can still run offline with USE_MOCK_ADAPTER=1.

0.2 Stop auto-synth overriding real URLs in fetch

  • Change: In server/src/services/MIDIFetchService.ts, only synthesize when:
    • url.startsWith('synthetic:') (and optionally if USE_SYNTHETIC_FETCH=1)
  • Remove: url.includes('bitmidi.com/uploads') synthetic shortcut
  • Acceptance:
    • Selecting a BitMidi result triggers a real network fetch and caches the real bytes.
    • If the remote file is invalid, it fails explicitly with a clear error.
  • Change: Add response fields or headers indicating source:
    • Example: X-Motif-Source: real|synthetic|cache
  • Acceptance:
    • Frontend can display “cached”/“live”/“synthetic fallback” badges (helps debugging + trust).

Phase 1 — GM soundfont playback (frontend “Preview” becomes correct MIDI playback)

1.1 Dependency choice & asset strategy

  • Dependency: add a browser-friendly soundfont player dependency (e.g. soundfont-player).
  • Soundfont hosting:
    • Prefer static hosting under /public/soundfonts/ (versioned with the app)
    • Or use a CDN, but pin versions and handle CORS
  • MVP instrument set:
    • Minimum viable: Acoustic Grand Piano for all melodic tracks, and a basic drum fallback
    • Better: load instruments on demand per track program

1.2 Implement SoundfontMIDIPlayer

Create src/synthesis/SoundfontMIDIPlayer.ts with:

  • Responsibilities
    • Load instruments (program → soundfont instrument name)
    • Schedule note-on/note-off with WebAudio timing
    • Provide load(midi) / play() / stop() / setVolume() APIs
  • Inputs
    • Best: use @tonejs/midis Midi object (tracks include instrument.number, notes, channel)
    • Alternate: keep using NoteEvent[], but youll lose program/channel unless you extend the event model
  • Timing correctness
    • Use seconds-based timing from @tonejs/midi notes (time, duration are in seconds)
    • Ensure AudioContext resumes on user gesture
  • Drums
    • If channel 9/10 is detected: either map to a percussion kit if supported, or skip drums for MVP (but be explicit in UI)

1.3 Wire UI to use soundfont preview

  • In src/main.ts, replace/augment the current oscillator MIDIPlayer usage:
    • Preview buttons should play via SoundfontMIDIPlayer
    • Keep existing Motif buttons intact
  • Keep oscillator preview only as a fallback if soundfonts fail to load (optional).

Phase 2 — “Play the real song correctly” UX loop

2.1 Search UX requirements

  • Result list must show:
    • title, source, confidence
    • parsed metadata: duration, track count, issues
  • Selection behavior:
    • Selecting a row fetches + parses once; enables Preview + Generate Motif

2.2 Error handling (user-facing)

Define user-facing error classes/messages:

  • Search: “No results”, “Backend unavailable”, “Rate limited / source blocked”
  • Fetch: “MIDI file blocked”, “Invalid MIDI header”, “Quality rejected”
  • Parse: “Unsupported MIDI features” / “Parse failed”
  • Preview playback: “Soundfont failed to load” / “Audio not allowed until click”

2.3 Observability (dev-facing)

  • Backend: log adapter failures per source + timings
  • Frontend: log selected MIDI URL, parse duration, instrument load times
  • Optional: a small “Debug” accordion showing chosen URL, cache hit, parse issues, loaded instruments

Phase 3 — Motif generation stays step 2 (but align data model)

  • Keep the current path:
    • MotifEngine.generateFromMIDI(NoteEvent[]) then MotifEngine.play()
  • Recommended alignment work:
    • Decide whether Motif should later consume richer track metadata (program/channel) to improve role mapping.

Integration milestones & acceptance checks

Milestone A — Real MIDI end-to-end

  • Search returns results from BitMidi/Dongrays with mock disabled by default
  • Fetch returns real bytes and caches them
  • Parse endpoint /api/midi/parse works for metadata

Milestone B — “Correct” preview playback

  • Preview produces recognizable instrument playback (piano at minimum)
  • Stop reliably stops scheduled notes
  • Works in Chrome/Safari with autoplay policies (requires click)

Milestone C — Motif as second step

  • Generate Motif still works on the same loaded MIDI
  • Preview and Motif can be A/B tested without reloading the page

Backend engineer

  • Implement env gating for mock/synthetic
  • Tighten fetch behavior + ensure BitMidi URLs are truly fetched
  • Add explicit “source = real/cache/synthetic” marker

Frontend engineer

  • Add the chosen soundfont library
  • Implement SoundfontMIDIPlayer
  • Wire src/main.ts preview buttons to soundfont playback
  • Add user-facing error messaging for soundfont failures

QA / test harness

  • Maintain a short list of known-good queries (35 songs) and confirm:
    • search results appear
    • at least one MIDI fetches and plays
    • Motif plays afterward