detect_tempo
Detect BPM (beats per minute) from audio to analyze tempo for music generation and live coding in Strudel patterns.
Instructions
BPM detection
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/AudioAnalyzer.ts:395-478 (handler)Core handler implementing tempo detection via spectral flux onset detection, inter-onset interval calculation, BPM derivation, and confidence scoring.async detectTempo(page: Page): Promise<TempoAnalysis | null> { // Get analyzer object from browser const analyzer = await page.evaluate(() => { return (window as any).strudelAudioAnalyzer; }); if (!analyzer || !analyzer.isConnected) { throw new Error('Audio analyzer not connected'); } let onsets: number[]; // Check if this is a mock with pre-calculated onset times (for testing) if (typeof analyzer.analyze === 'function') { const analysis = analyzer.analyze(); if (analysis?.features?.onsetTimes) { onsets = analysis.features.onsetTimes; } else if (analysis?.features?.fftData === null) { // Explicitly null FFT data in test throw new Error('Invalid audio data'); } else { // No mock data, use real-time detection if (!analyzer.dataArray) { throw new Error('Invalid audio data'); } const fftData = new Uint8Array(analyzer.dataArray); const flux = this.calculateSpectralFlux(fftData); if (flux > this.ONSET_THRESHOLD) { this._onsetHistory.push(Date.now()); if (this._onsetHistory.length > this.MAX_HISTORY_LENGTH) { this._onsetHistory.shift(); } } onsets = [...this._onsetHistory]; } } else { // No analyze function, use real-time detection if (!analyzer.dataArray) { throw new Error('Invalid audio data'); } const fftData = new Uint8Array(analyzer.dataArray); const flux = this.calculateSpectralFlux(fftData); if (flux > this.ONSET_THRESHOLD) { this._onsetHistory.push(Date.now()); if (this._onsetHistory.length > this.MAX_HISTORY_LENGTH) { this._onsetHistory.shift(); } } onsets = [...this._onsetHistory]; } // Need at least 4 onsets for reliable tempo detection if (onsets.length < 4) { return { bpm: 0, confidence: 0, method: 'onset' }; } // Calculate inter-onset intervals (IOIs) const intervals = this.calculateIntervals(onsets); // Calculate mean interval and derive BPM const meanInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; const bpm = 60000 / meanInterval; // Validate BPM range if (bpm < 40 || bpm > 200) { return { bpm: 0, confidence: 0, method: 'onset' }; } // Calculate confidence from interval consistency const variance = this.calculateVariance(intervals, meanInterval); const coefficientOfVariation = Math.sqrt(variance) / meanInterval; // More aggressive penalty for variation const confidence = Math.max(0, 1 - coefficientOfVariation * 1.5); return { bpm: Math.round(bpm), confidence: Math.min(1, confidence), method: 'onset' }; }
- MCP server handler for detect_tempo tool: checks initialization, calls controller.detectTempo(), formats response with BPM, confidence, and error handling.case 'detect_tempo': if (!this.isInitialized) { return 'Browser not initialized. Run init first.'; } try { const tempoAnalysis = await this.controller.detectTempo(); if (!tempoAnalysis || tempoAnalysis.bpm === 0) { return { bpm: 0, confidence: 0, message: 'No tempo detected. Ensure audio is playing and has a clear rhythmic pattern.' }; } return { bpm: tempoAnalysis.bpm, confidence: Math.round(tempoAnalysis.confidence * 100) / 100, method: tempoAnalysis.method, message: `Detected ${tempoAnalysis.bpm} BPM with ${Math.round(tempoAnalysis.confidence * 100)}% confidence` }; } catch (error: any) { return { bpm: 0, confidence: 0, error: error.message || 'Tempo detection failed' }; }
- src/server/EnhancedMCPServerFixed.ts:281-285 (registration)Tool registration in MCP server: defines name, description, and empty input schema for detect_tempo.{ name: 'detect_tempo', description: 'BPM detection', inputSchema: { type: 'object', properties: {} } },
- src/StrudelController.ts:291-295 (helper)Helper method in StrudelController that delegates tempo detection to the AudioAnalyzer instance.async detectTempo(): Promise<TempoAnalysis | null> { if (!this._page) throw new Error('Browser not initialized. Run init tool first.'); return await this.analyzer.detectTempo(this._page); }
- Input schema for detect_tempo tool: empty object (no parameters required).inputSchema: { type: 'object', properties: {} }