Skip to main content
Glama
williamzujkowski

Strudel MCP Server

analyze_rhythm

Analyze musical rhythms to identify patterns and structures for music generation and live coding applications.

Instructions

Rhythm analysis

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Tool registration definition for 'analyze_rhythm' in the getTools() array.
    {
      name: 'analyze_rhythm',
      description: 'Rhythm analysis',
      inputSchema: { type: 'object', properties: {} }
    },
  • MCP tool handler in executeTool switch statement (currently stubbed, calls analyzeAudio but returns placeholder).
    case 'analyze_rhythm':
      if (!this.isInitialized) {
        return 'Browser not initialized. Run init first.';
      }
      const analysis = await this.controller.analyzeAudio();
      return {
        isPlaying: analysis.features?.isPlaying,
        tempo: 'Analysis pending implementation',
        pattern: 'Rhythm pattern analysis'
      };
  • Core rhythm analysis implementation in AudioAnalyzer class, performing onset detection, complexity analysis, syncopation detection, etc.
    async analyzeRhythm(page: Page): Promise<RhythmAnalysis> {
      // Get analyzer object from browser
      const analyzer = await page.evaluate(() => {
        return (window as any).strudelAudioAnalyzer;
      });
    
      if (!analyzer || !analyzer.isConnected) {
        return {
          pattern: 'X...',
          complexity: 0,
          density: 0,
          syncopation: 0,
          onsets: [],
          isRegular: true
        };
      }
    
      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?.onsets) {
          onsets = analysis.features.onsets;
        } else if (analysis?.features?.onsetTimes) {
          onsets = analysis.features.onsetTimes;
        } else {
          // No mock data, use real-time detection
          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
        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 2 onsets for rhythm analysis
      if (onsets.length < 2) {
        return {
          pattern: 'X...',
          complexity: 0,
          density: 0,
          syncopation: 0,
          onsets: [],
          isRegular: true
        };
      }
    
      // Calculate intervals
      const intervals = this.calculateIntervals(onsets);
    
      // Calculate density (events per second)
      const duration = (onsets[onsets.length - 1] - onsets[0]) / 1000;
      const density = duration > 0 ? (onsets.length - 1) / duration : 0;
    
      // Calculate complexity from interval variance
      const meanInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
      const variance = this.calculateVariance(intervals, meanInterval);
      const coefficientOfVariation = Math.sqrt(variance) / meanInterval;
    
      // Analyze subdivisions
      const subdivisionScore = this.analyzeSubdivisions(intervals);
    
      // Combine variance and subdivision complexity with higher sensitivity
      const varianceComponent = Math.min(1, coefficientOfVariation * 5);
      const complexity = Math.min(1, varianceComponent * 0.8 + subdivisionScore * 0.2);
    
      // Calculate syncopation (off-beat events)
      const syncopation = this.detectSyncopation(onsets, meanInterval);
    
      // Determine regularity
      const isRegular = coefficientOfVariation < 0.2;
    
      // Generate pattern string
      const pattern = this.generatePatternString(onsets, meanInterval);
    
      return {
        pattern,
        complexity,
        density,
        syncopation,
        onsets,
        isRegular
      };
    }
  • TypeScript interface defining the RhythmAnalysis return type (schema).
    export interface RhythmAnalysis {
      pattern: string;
      complexity: number; // 0-1 scale
      density: number; // events per second
      syncopation: number; // 0-1 scale
      onsets: number[];
      isRegular: boolean;
    }
  • Helper function for subdivision complexity calculation used in analyzeRhythm.
    private analyzeSubdivisions(intervals: number[]): number {
      if (intervals.length === 0) return 0;
    
      const meanInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
    
      // Count how many different subdivision levels are present
      const subdivisions = new Set<number>();
    
      for (const interval of intervals) {
        const ratio = interval / meanInterval;
        // Quantize to common subdivisions (1, 0.5, 0.25, 0.75, 0.33, etc.)
        const quantized = Math.round(ratio * 8) / 8; // Higher resolution
        subdivisions.add(quantized);
      }
    
      // More subdivision levels = more complex (more aggressive scaling)
      return Math.min(1, subdivisions.size / 4);
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/williamzujkowski/strudel-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server