Karaoke API
Documentation
Create synchronized karaoke experiences with frame-perfect timing using AI-powered audio analysis
Markup Generation API
Generate synchronized karaoke markup from audio files and lyrics using our h3.5 format
/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 -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"
// 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)
{
"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.
// 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();
# 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
// 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>;
};
<!-- 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>
// 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.
Install H3.5 Compiler
Download and import our H3.5 compiler to process API responses
// 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();
}
Render Synchronized Lyrics
Create a real-time karaoke player with word-level synchronization
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;
Add CSS Styling
Create smooth animations and visual effects for the karaoke experience
.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
💻 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
Setup Environment
Configure your API keys and proxy endpoints
# Add to .env.local
KARAOKE_API_KEY=your-api-key
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);
}
};
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();
};
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;
}