Implement complete search results + dual player UI

Backend Enhancements:
- Add ParsedMIDIInfo with track analysis and quality issues
- New /api/midi/parse endpoint for MIDI metadata extraction
- Enhanced MIDIService with parseMIDI method

MIDI Preview Player:
- Complete MIDIPlayer class with polyphonic playback
- Basic oscillator mapping (bass=square, mid=triangle, high=sine)
- Lookahead scheduling with proper cleanup
- Automatic stopping when MIDI ends

New UI Flow:
1. Search → Results table with metadata
2. Select MIDI → Shows parsed info (duration, tracks, tempo)
3. Dual transport: Preview Original vs Generate Motif
4. Try Next Result button for easy A/B testing

Search Results Table:
- Title, source, confidence bar, duration, track count
- Quality issues display (short duration, few notes, etc.)
- Auto-select best result, manual selection available
- Visual confidence bars and issue warnings

This makes the project feel "real" - you can now:
- See exactly what MIDI was found
- Verify it's the right song via preview
- Compare original vs procedural synthesis
- Easily try different results

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
b1rdmania
2025-12-17 21:04:29 +00:00
parent 2d4c748d4c
commit 177e8fbed3
8 changed files with 731 additions and 36 deletions
+16
View File
@@ -27,6 +27,22 @@ export class MotifEngine {
this.roleMapper = new RoleMapper();
}
async generateFromMIDI(events: NoteEvent[]): Promise<void> {
// Process events directly (bypass search/fetch)
const features = this.midiProcessor.extractFeatures(events);
const roleAssignments = this.roleMapper.assignRoles(features, events);
this.currentFeatures = features;
// Initialize audio context and synthesis engine
if (!this.audioContext) {
this.audioContext = new AudioContext();
}
this.synthesisEngine = new SynthesisEngine(this.audioContext, this.config);
this.synthesisEngine.setupLayers(roleAssignments);
}
async generateFromSong(songName: string): Promise<void> {
let events: NoteEvent[];