Karaoke API
Documentation

Create synchronized karaoke experiences with frame-perfect timing using AI-powered audio analysis

10msPrecision
v3.5.0JSON Format
Multi-trackSupport

Markup Generation API

Generate synchronized karaoke markup from audio files and lyrics using our h3.5 format

POST/api/v1/get_markup

Transform your audio files and lyrics into synchronized karaoke data. Perfect for frontend developers who want to add karaoke functionality to their websites.

📤 Request Format

cURL Example
curl -X POST https://api.monroemusic.io/get_markup \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: multipart/form-data" \
  -F "song=@path/to/your-song.mp3" \
  -F "lyrics=@path/to/your-lyrics.txt"
JavaScript/Frontend Example
// Frontend implementation
const generateKaraoke = async (audioFile, lyricsText) => {
  const formData = new FormData();
  formData.append('song', audioFile); // File object from input
  
  // Create lyrics blob from text
  const lyricsBlob = new Blob([lyricsText], { type: 'text/plain' });
  formData.append('lyrics', lyricsBlob, 'lyrics.txt');

  try {
    const response = await fetch('https://api.monroemusic.io/get_markup', {
  method: 'POST',
      headers: {
        'X-API-Key': 'your-api-key',
      },
  body: formData,
});

    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }

const karaokeData = await response.json();
    return karaokeData;
  } catch (error) {
    console.error('Failed to generate karaoke:', error);
    throw error;
  }
};

// Usage with file input
const fileInput = document.getElementById('audio-input');
const lyricsTextarea = document.getElementById('lyrics-textarea');

fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const lyrics = lyricsTextarea.value;
  
  if (file && lyrics) {
    const karaokeData = await generateKaraoke(file, lyrics);
    console.log('Generated karaoke data:', karaokeData);
  }
});

📥 Response Format (h3.5)

Production-Ready h3.5 Response
{
  "version": "3.5.0",
  "metadata": {
    "title": "Your Song Title",
    "artist": "Artist Name",
    "duration": 180.5,
    "format": "h3.5"
  },
  "tracks": [
    {
      "id": "lead",
      "name": "Lead Vocal",
      "role": "LEAD",
      "color": "#4ade80",
      "lines": [
        {
          "id": "line-1",
          "startTime": 10.5,
          "endTime": 14.2,
          "segments": [
            {
              "type": "PRIMARY_VOCAL",
              "text": "Hello world, this is karaoke",
              "words": [
                {
                  "text": "Hello",
                  "start": 10.5,
                  "end": 11.0,
                  "letters": [
                    {"char": "H", "start": 10.5, "end": 10.6},
                    {"char": "e", "start": 10.6, "end": 10.7},
                    {"char": "l", "start": 10.7, "end": 10.8},
                    {"char": "l", "start": 10.8, "end": 10.9},
                    {"char": "o", "start": 10.9, "end": 11.0}
                  ]
                },
                {
                  "text": "world",
                  "start": 11.2,
                  "end": 11.8,
                  "letters": [
                    {"char": "w", "start": 11.2, "end": 11.3},
                    {"char": "o", "start": 11.3, "end": 11.4},
                    {"char": "r", "start": 11.4, "end": 11.5},
                    {"char": "l", "start": 11.5, "end": 11.6},
                    {"char": "d", "start": 11.6, "end": 11.8}
                  ]
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "harmony",
      "name": "Harmony",
      "role": "HARMONY",
      "color": "#06b6d4",
      "lines": [
        // Additional harmony vocals with same structure
      ]
    },
    {
      "id": "rap",
      "name": "Rap Section",
      "role": "RAP",
      "color": "#f59e0b",
      "lines": [
        // Rap sections with faster word timing
      ]
    }
  ]
}

📦 Download Karaoke Compiler

Get the latest version of our H3.5 compiler to process API responses in your project. The compiler is updated automatically with new features and bug fixes.

⚡ Auto-Update Feature

Our compiler automatically checks for updates when imported. You'll always have the latest features and bug fixes without manual updates.

Auto-update implementation
// The compiler includes production-ready auto-update functionality
import H35Compiler, { VERSION_INFO, formatTime, getActiveLines, getWordProgress } from './lib/karaokeCompilers';

// Version is checked automatically on import (every hour in production, every minute in dev)
// Smart console logging based on environment

// Check current version
console.log('Karaoke Compiler version:', VERSION_INFO.version); // v3.6.0

// Force update check
const updateStatus = await VERSION_INFO.checkForUpdates(true);
if (updateStatus.hasUpdate) {
  console.log('New version available:', updateStatus.latestVersion);
}

// Listen for automatic update notifications
window.addEventListener('karaokeCompilerUpdate', (event) => {
  const { currentVersion, latestVersion, changelog, updateUrl } = event.detail;
  console.log(`Update available: v${currentVersion} → v${latestVersion}`);
  
  // Show update notification to user
  showUpdateNotification({
    version: latestVersion,
    changelog,
    updateUrl
  });
});

// Enable debug logging in production
VERSION_INFO.enableDebugLogging(); // Stores in localStorage

// Disable debug logging
VERSION_INFO.disableDebugLogging();
Installation Methods
# Method 1: Direct download (recommended for TypeScript projects)
curl -o src/lib/karaokeCompilers.ts https://monroemusic.io/compilers/karaokeCompilers.ts

# Method 2: Using wget 
wget https://monroemusic.io/compilers/karaokeCompilers.ts -P src/lib/

# Method 3: Manual download
# Visit: https://monroemusic.io/compilers/karaokeCompilers.ts
# Save to your project's src/lib/ directory
Dynamic CDN Loading (for runtime import)
// Method 4: Dynamic import from CDN (ES modules)
// Best for: Runtime loading, always latest version

const loadKaraokeCompiler = async () => {
  try {
    // Dynamic import from CDN - always gets latest version
    const { H35Compiler, VERSION_INFO, formatTime, getActiveLines, getWordProgress } = 
      await import('https://monroemusic.io/compilers/karaokeCompilers.js');
    
    console.log('Loaded compiler version:', VERSION_INFO.version);
    return { H35Compiler, VERSION_INFO, formatTime, getActiveLines, getWordProgress };
  } catch (error) {
    console.error('Failed to load karaoke compiler from CDN:', error);
    // Fallback to local version if available
    const localCompiler = await import('./lib/karaokeCompilers');
    return localCompiler;
  }
};

// Usage in async function
const initializeKaraoke = async () => {
  const { H35Compiler, formatTime } = await loadKaraokeCompiler();
  
  // Now you can use the compiler
  const compiledData = H35Compiler.compile(apiResponse);
  console.log('Duration:', formatTime(compiledData.metadata.duration));
};

// Usage in React component
const KaraokePlayer = () => {
  const [compiler, setCompiler] = useState(null);
  
  useEffect(() => {
    loadKaraokeCompiler().then(setCompiler);
  }, []);
  
  if (!compiler) return <div>Loading karaoke compiler...</div>;
  
  // Use compiler.H35Compiler, compiler.formatTime, etc.
  return <div>Karaoke player ready!</div>;
};
Script Tag Loading (for vanilla JS)
<!-- Method 5: Script tag loading -->
<!-- Add to your HTML head -->
<script type="module">
  // Load from CDN with script tag
  import { H35Compiler, VERSION_INFO } from 'https://monroemusic.io/compilers/karaokeCompilers.js';
  
  // Make available globally
  window.KaraokeCompiler = {
    H35Compiler,
    VERSION_INFO
  };
  
  console.log('Karaoke Compiler loaded:', VERSION_INFO.version);
</script>

<!-- Then use in your main script -->
<script>
  // Wait for compiler to load
  window.addEventListener('load', () => {
    const { H35Compiler } = window.KaraokeCompiler;
    
    // Use the compiler
    const compiledData = H35Compiler.compile(karaokeData);
    console.log('Compiled:', compiledData);
  });
</script>
Fetch-based Loading (advanced)
// Method 6: Fetch and evaluate (for maximum control)
// Best for: Custom loading logic, caching, offline support

class KaraokeCompilerLoader {
  private static compiler: any = null;
  private static loadPromise: Promise<any> | null = null;
  
  static async load(options: { 
    cache?: boolean; 
    fallbackUrl?: string;
    timeout?: number;
  } = {}) {
    // Return cached compiler if available
    if (this.compiler && options.cache !== false) {
      return this.compiler;
    }
    
    // Return existing load promise if already loading
    if (this.loadPromise) {
      return this.loadPromise;
    }
    
    this.loadPromise = this.fetchAndEvaluate(options);
    this.compiler = await this.loadPromise;
    this.loadPromise = null;
    
    return this.compiler;
  }
  
  private static async fetchAndEvaluate(options: any) {
    const urls = [
      'https://monroemusic.io/compilers/karaokeCompilers.js',
      options.fallbackUrl || './lib/karaokeCompilers.js'
    ].filter(Boolean);
    
    for (const url of urls) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000);
        
        const response = await fetch(url, {
          signal: controller.signal,
          cache: 'no-cache'
        });
        
        clearTimeout(timeoutId);
        
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        
        const code = await response.text();
        
        // Create module scope
        const module = { exports: {} };
        const exports = module.exports;
        
        // Evaluate the code in controlled scope
        const func = new Function('module', 'exports', 'require', code);
        func(module, exports, (id: string) => {
          throw new Error(`Module '${id}' not available in browser`);
        });
        
        console.log(`✅ Loaded karaoke compiler from: ${url}`);
        return module.exports;
        
      } catch (error) {
        console.warn(`Failed to load from ${url}:`, error);
        continue;
      }
    }
    
    throw new Error('All CDN sources failed to load');
  }
  
  // Preload for better performance
  static preload() {
    this.load({ cache: true }).catch(console.warn);
  }
}

// Usage examples:
// Preload early in your app
KaraokeCompilerLoader.preload();

// Load when needed
const { H35Compiler, VERSION_INFO } = await KaraokeCompilerLoader.load();

// Load with custom options
const compiler = await KaraokeCompilerLoader.load({
  cache: true,
  timeout: 5000,
  fallbackUrl: '/static/js/karaokeCompilers.js'
});

🎯 Which Method to Choose?

  • TypeScript/Next.js Projects: Use Method 1 (Direct download) for best type support and build optimization
  • Always Latest Version: Use Method 4 (Dynamic import) for automatic updates
  • Vanilla JavaScript: Use Method 5 (Script tag) for simple setup
  • Custom Loading Logic: Use Method 6 (Fetch-based) for advanced control
  • Offline Support: Use Method 1 with fallback to Method 4
  • Performance Critical: Use Method 1 with build-time bundling

Auto-Update Features

All methods include automatic version checking and smart console notifications. The compiler only shows update messages when new versions are available, preventing spam.

1

Install H3.5 Compiler

Download and import our H3.5 compiler to process API responses

Import H35Compiler
// Import the production-ready H3.5 compiler
import H35Compiler, { VERSION_INFO, formatTime, getActiveLines, getWordProgress } from './lib/karaokeCompilers';

// The compiler automatically handles damaged JSON and provides error recovery
const processKaraokeData = (apiResponse) => {
  // H35Compiler is the default export - optimized for h3.5 format
  const compiledData = H35Compiler.compile(apiResponse);
  
  // The compiler always returns valid data structure
  console.log(`✅ Compiled ${compiledData.lines.length} lines across ${compiledData.tracks.length} tracks`);
  console.log('Metadata:', compiledData.metadata);
  
  // Advanced error handling is built-in
  if (compiledData.tracks.length === 0) {
    console.warn('No tracks found - check API response');
  }
  
  return compiledData;
};

// Check compiler version
console.log('Using Karaoke Compiler', VERSION_INFO.version);

// Enable debug mode for development
if (process.env.NODE_ENV === 'development') {
  VERSION_INFO.enableDebugLogging();
}
2

Render Synchronized Lyrics

Create a real-time karaoke player with word-level synchronization

React Karaoke Component
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import H35Compiler, { getActiveLines, getWordProgress, formatTime } from '@/lib/karaokeCompilers';

interface KaraokePlayerProps {
  apiResponse: any; // Response from /api/v1/get_markup
  audioUrl: string;
  onError?: (error: Error) => void;
}

const KaraokePlayer: React.FC<KaraokePlayerProps> = ({ 
  apiResponse, 
  audioUrl, 
  onError 
}) => {
  const [currentTime, setCurrentTime] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [duration, setDuration] = useState(0);
  const [isLoading, setIsLoading] = useState(true);
  
  const audioRef = useRef<HTMLAudioElement>(null);
  const lyricsRef = useRef<HTMLDivElement>(null);
  const animationRef = useRef<number>();
  
  // Compile API response with H3.5 compiler (memoized)
  const karaokeData = useMemo(() => {
    try {
      const compiled = H35Compiler.compile(apiResponse);
      if (!compiled) throw new Error('Failed to compile karaoke data');
      return compiled;
    } catch (error) {
      console.error('Compilation error:', error);
      if (onError) onError(error as Error);
      return null;
    }
  }, [apiResponse, onError]);
  
  // Audio event handlers
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    
    const handleLoadedData = () => {
      setIsLoading(false);
      setDuration(audio.duration);
    };
    
    const handleError = () => {
      setIsLoading(false);
      if (onError) onError(new Error('Failed to load audio'));
    };
    
    const handleEnded = () => {
      setIsPlaying(false);
      setCurrentTime(0);
    };
    
    audio.addEventListener('loadeddata', handleLoadedData);
    audio.addEventListener('error', handleError);
    audio.addEventListener('ended', handleEnded);
    
    return () => {
      audio.removeEventListener('loadeddata', handleLoadedData);
      audio.removeEventListener('error', handleError);
      audio.removeEventListener('ended', handleEnded);
    };
  }, [onError]);
  
  // Real-time sync with requestAnimationFrame
  useEffect(() => {
    const updateTime = () => {
      if (audioRef.current && isPlaying) {
        setCurrentTime(audioRef.current.currentTime);
        animationRef.current = requestAnimationFrame(updateTime);
      }
    };

    if (isPlaying) {
      updateTime();
    }
    
    return () => {
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  }, [isPlaying]);

  // Auto-scroll to active line
  useEffect(() => {
    if (!lyricsRef.current || !isPlaying) return;
    
    const activeElement = lyricsRef.current.querySelector('.line.active');
    if (activeElement) {
      activeElement.scrollIntoView({ 
        behavior: 'smooth', 
        block: 'center' 
      });
    }
  }, [currentTime, isPlaying]);

  const togglePlay = useCallback(async () => {
    const audio = audioRef.current;
    if (!audio || !karaokeData) return;
    
    try {
      if (isPlaying) {
        audio.pause();
        setIsPlaying(false);
      } else {
        await audio.play();
        setIsPlaying(true);
      }
    } catch (error) {
      console.error('Playback error:', error);
      if (onError) onError(error as Error);
    }
  }, [isPlaying, karaokeData, onError]);
  
  const handleSeek = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    const audio = audioRef.current;
    if (!audio || !duration) return;
    
    const rect = e.currentTarget.getBoundingClientRect();
    const clickX = e.clientX - rect.left;
    const newTime = (clickX / rect.width) * duration;
    
    audio.currentTime = newTime;
    setCurrentTime(newTime);
  }, [duration]);

  // Get currently active lines across all tracks
  const activeLines = useMemo(() => {
    if (!karaokeData) return [];
    return getActiveLines(karaokeData.lines, currentTime);
  }, [karaokeData, currentTime]);
  
  if (!karaokeData) {
    return (
      <div className="karaoke-player error">
        <p>Failed to load karaoke data. Please check the API response.</p>
      </div>
    );
  }

  return (
    <div className="karaoke-player">
      <audio 
        ref={audioRef} 
        src={audioUrl}
        preload="metadata"
      />
      
      <div className="controls">
        <button 
          onClick={togglePlay}
          className="play-button"
          disabled={isLoading}
        >
          {isPlaying ? '⏸️' : '▶️'} {isPlaying ? 'Pause' : 'Play'}
      </button>
      
        <span className="time-display">
          {formatTime(currentTime)} / {formatTime(duration)}
        </span>
        
        <div 
          className="progress-bar"
          onClick={handleSeek}
          role="slider"
          aria-label="Seek"
          aria-valuemin={0}
          aria-valuemax={duration}
          aria-valuenow={currentTime}
        >
          <div 
            className="progress-fill"
            style={{ width: `${duration ? (currentTime / duration) * 100 : 0}%` }}
          />
        </div>
      </div>

      <div ref={lyricsRef} className="lyrics-container">
        {isLoading ? (
          <div className="loading">Loading karaoke data...</div>
        ) : (
          karaokeData.tracks.map((track) => (
            <div key={track.id} className="track">
              <h4 style={{ color: track.color }}>
                {track.name} ({track.role})
              </h4>
              
              {karaokeData.lines
                .filter(line => line.trackId === track.id)
                .map((line) => {
                  const isActive = activeLines.some(l => l.id === line.id);
                  const isPast = currentTime > line.endTime;
          
          return (
                    <div 
                      key={line.id} 
                      className={`line ${isActive ? 'active' : isPast ? 'past' : 'future'}`}
                    >
                      <span className="line-time">
                        {formatTime(line.startTime)}
                      </span>
                      <div className="line-content">
                        {line.segments.map((segment, idx) => (
                          <span key={idx} className="segment">
                            {segment.words?.map((word, wordIdx) => {
                              const progress = getWordProgress(word, currentTime);
                    
                    return (
                                <span key={wordIdx} className="word-wrapper">
                        <span className="word-base">{word.text}</span>
                        <span 
                          className="word-fill"
                                    style={{
                                      width: `${progress * 100}%`,
                                      color: track.color
                                    }}
                        >
                          {word.text}
                                  </span>{" "}
                      </span>
                    );
                  }) || segment.text}
                </span>
              ))}
                      </div>
            </div>
          );
        })}
      </div>
          ))
        )}
      </div>
    </div>
  );
};

export default KaraokePlayer;
3

Add CSS Styling

Create smooth animations and visual effects for the karaoke experience

Karaoke Styles
.karaoke-player {
  background: linear-gradient(135deg, rgba(0, 0, 0, 0.9), rgba(20, 20, 25, 0.95));
  border-radius: 16px;
  padding: 2rem;
  color: white;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.track {
  margin-bottom: 2rem;
}

.track h4 {
  font-size: 1.1rem;
  margin-bottom: 1rem;
  opacity: 0.8;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.line {
  padding: 0.75rem 0;
  font-size: 1.4rem;
  line-height: 1.5;
  transition: all 0.3s ease;
  border-radius: 8px;
  margin: 0.5rem 0;
}

.line.future {
  opacity: 0.3;
  filter: blur(1px);
}

.line.past {
  opacity: 0.5;
  color: #666;
}

.line.active {
  opacity: 1;
  transform: scale(1.02);
  background: rgba(255, 255, 255, 0.05);
  padding: 1rem;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

.word-wrapper {
  position: relative;
  display: inline-block;
  margin-right: 0.3rem;
}

.word-base {
  color: #666;
}

.word-fill {
  position: absolute;
  top: 0;
  left: 0;
  white-space: nowrap;
  overflow: hidden;
  font-weight: bold;
  text-shadow: 0 0 15px currentColor;
  transition: width 0.1s linear;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}

🎯 Precision Timing

Our h3.5 format provides millisecond-accurate word and letter timing for smooth karaoke experiences

🎤 Multi-track Support

Handle multiple singers, harmonies, and vocal roles with separate track data and colors

⚡ Easy Integration

Drop-in H35Compiler handles all the data processing - just focus on your UI and animations

💡 Best Practices for Frontend Integration

  • Use requestAnimationFrame for smooth 60fps updates instead of setInterval
  • Implement auto-scroll to keep active lyrics centered on screen
  • Add visual feedback like blur effects for future/past lines and highlighting for active lines
  • Handle loading states - API processing can take 1-3 minutes for longer songs
  • Support mobile devices with touch-friendly controls and responsive layouts
🚀

Ready to Build Karaoke?

The H35Compiler and utility functions handle all the complex timing calculations. Just focus on creating beautiful UI and smooth animations for your users.

Live Demo & Source Code

Experience our H3.5 karaoke format in action with a real multi-track song. Switch between Demo and Code views to see the complete implementation including React components, CSS animations, and integration with our H35Compiler.

🎮

Interactive Demo Features

Real-time sync with word-level highlighting using requestAnimationFrame
Multi-track support with Lead/RAP vocal roles and synchronized timing
Two view modes: Apple Music-style Ladder view and Timeline view
Complete source code in React/TypeScript with CSS animations
Production-ready integration examples using H35Compiler

🎤 Live Karaoke Demo

Loading karaoke data...

💻 What's in the Code Tab?

  • React Component: Complete KaraokePlayer with hooks and real-time synchronization
  • CSS Styles: Gradient animations, blur effects, and responsive design
  • API Integration: Next.js proxy routes and React hooks for API calls
  • H35Compiler usage: Real implementation showing how to process API responses

Integration Guide

1

Setup Environment

Configure your API keys and proxy endpoints

# Add to .env.local
KARAOKE_API_KEY=your-api-key
2

Create Upload Interface

Build a drag-and-drop interface for audio files and lyrics

const [file, setFile] = useState<File | null>(null);
const [lyrics, setLyrics] = useState("");

const handleDrop = (e: React.DragEvent) => {
  e.preventDefault();
  const droppedFile = e.dataTransfer.files[0];
  if (droppedFile?.type.startsWith("audio/")) {
    setFile(droppedFile);
  }
};
3

Process with API

Send files to the karaoke API and handle responses

const generateKaraoke = async () => {
  const formData = new FormData();
  formData.append('song', file);
  formData.append('lyrics', new Blob([lyrics], { type: 'text/plain' }));
  
  const response = await fetch('/api/karaoke-proxy', {
    method: 'POST',
    body: formData,
  });
  
  return await response.json();
};
4

Implement Player

Create a synchronized karaoke player with visual effects

.word-wrapper {
  position: relative;
  display: inline-block;
}

.word-fill {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  color: #4ade80;
  font-weight: bold;
  text-shadow: 0 0 20px currentColor;
}