Add complete MIDI search and synthesis pipeline

Backend (MVP):
- Express API with /search and /fetch endpoints
- BitMidi and Dongrays search adapters with scoring heuristics
- CORS proxy with disk caching and validation
- Quality assessment and file size limits

Frontend Integration:
- Switch to @tonejs/midi parser for reliable MIDI parsing
- MIDIService for backend API communication
- MotifEngine updated to try real MIDI, fallback to synthetic
- Enhanced error handling and logging

Development:
- Concurrent frontend/backend npm scripts
- Basic project documentation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
b1rdmania
2025-12-17 20:45:40 +00:00
parent 4b7836b925
commit 11c894bd91
14 changed files with 834 additions and 6 deletions
+61
View File
@@ -0,0 +1,61 @@
interface MIDISearchResult {
id: string;
title: string;
source: string;
pageUrl: string;
midiUrl: string;
confidence: number;
}
interface MIDISearchResponse {
results: MIDISearchResult[];
count: number;
}
export class MIDIService {
private baseUrl: string;
constructor(baseUrl = 'http://localhost:3001') {
this.baseUrl = baseUrl;
}
async search(query: string): Promise<MIDISearchResult[]> {
try {
const response = await fetch(`${this.baseUrl}/api/midi/search?q=${encodeURIComponent(query)}`);
if (!response.ok) {
throw new Error(`Search failed: ${response.status}`);
}
const data: MIDISearchResponse = await response.json();
return data.results;
} catch (error) {
console.error('MIDI search error:', error);
return [];
}
}
async fetchMIDI(url: string): Promise<ArrayBuffer | null> {
try {
const response = await fetch(`${this.baseUrl}/api/midi/fetch?u=${encodeURIComponent(url)}`);
if (!response.ok) {
throw new Error(`Fetch failed: ${response.status}`);
}
return await response.arrayBuffer();
} catch (error) {
console.error('MIDI fetch error:', error);
return null;
}
}
async checkHealth(): Promise<boolean> {
try {
const response = await fetch(`${this.baseUrl}/health`);
return response.ok;
} catch {
return false;
}
}
}