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
+63
View File
@@ -0,0 +1,63 @@
import type { SearchAdapter, MIDICandidate } from '../types.js';
import { BitMidiAdapter } from '../adapters/BitMidiAdapter.js';
import { DongraysAdapter } from '../adapters/DongraysAdapter.js';
export class MIDISearchService {
private adapters: SearchAdapter[];
constructor() {
this.adapters = [
new BitMidiAdapter(),
new DongraysAdapter()
];
}
async search(query: string): Promise<MIDICandidate[]> {
const allResults: MIDICandidate[] = [];
// Search all adapters in parallel
const searchPromises = this.adapters.map(async adapter => {
try {
const results = await adapter.search(query);
console.log(`${adapter.name}: Found ${results.length} results`);
return results;
} catch (error) {
console.error(`${adapter.name} search failed:`, error);
return [];
}
});
const results = await Promise.all(searchPromises);
// Combine and deduplicate results
for (const adapterResults of results) {
allResults.push(...adapterResults);
}
// Remove duplicates (same MIDI URL)
const uniqueResults = this.deduplicateResults(allResults);
// Sort by confidence score
uniqueResults.sort((a, b) => b.confidence - a.confidence);
// Return top 10 results
return uniqueResults.slice(0, 10);
}
private deduplicateResults(results: MIDICandidate[]): MIDICandidate[] {
const seen = new Set<string>();
const unique: MIDICandidate[] = [];
for (const result of results) {
// Create dedup key from MIDI URL or title+source
const key = result.midiUrl || `${result.title}_${result.source}`;
if (!seen.has(key)) {
seen.add(key);
unique.push(result);
}
}
return unique;
}
}