Convert routing directions into musical compositions using AI-powered style detection and music generation.
This project transforms GPS routing directions into personalized music by:
- Analyzing directions to infer musical style based on cultural and geographical context
- Generating music using ElevenLabs AI that matches the journey's character
- Serving audio files via a web-friendly API for playback
This project was created for a SpeedGeeking, which is always such a fun event. Presenters are giving 5-minute talks while the crowd is walking around in groups, randomly listening in. I did this at the Esri European Dev & Tech Summit 2025 in Frankfurt π Enjoy!
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β WebClient β β Client Express β β MCP Server β
β (Frontend) βββββΊβ (Port 3001) βββββΊβ (Port 3000) β
β β β β β β
β β’ User Input β β β’ Orchestration β β β’ AI Processing β
β β’ Audio Playbackβ β β’ File Serving β β β’ Music Gen β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
The system requires API keys for:
- Google Gemini AI (for style detection)
- ElevenLabs (for music generation)
# Navigate to MCP server directory
cd MCP/server
# Copy the template configuration
cp config.json.template config.json
# Edit config.json with your API keys
{
"googleGenAIApiKey": "YOUR_GOOGLE_GENAI_API_KEY_HERE",
"elevenLabsApiKey": "YOUR_ELEVENLABS_API_KEY_HERE"
}Security Note:
config.jsonis gitignored to protect your API keys. Never commit API keys to version control.
- Google Gemini: Get your API key from Google AI Studio
- ElevenLabs: Get your API key from ElevenLabs Dashboard β Profile β API Key
Install Node.js dependencies for both servers:
# Install MCP server dependencies
cd MCP/server
npm install
# Install Client server dependencies
cd ../client
npm installThe system consists of two Node.js servers that work together:
cd MCP/server
npm startThe MCP server handles:
- AI-powered musical style detection
- Music generation via ElevenLabs API
- Audio file creation and storage
cd MCP/client
npm startThe Client server provides:
/orchestrateendpoint for complete workflow- Static file serving for generated MP3s
- WebClient-friendly API responses
curl -X POST "http://localhost:3001/orchestrate" \
-H "Content-Type: application/json" \
-d '{
"directions": [
"Start at Times Square, New York",
"Head south on Broadway",
"Turn left on Houston Street",
"Continue to your destination"
],
"dummyMode": false
}'{
"success": true,
"styleCard": {
"genre": "jazz",
"songTitle": "Broadway Nights",
"instrumentation": ["piano", "saxophone"],
"mood": ["urban", "energetic"]
},
"audioUrl": "/audio/music_Broadway_Nights_2025-11-15T00-15-30-123Z.mp3",
"audioFile": "music_Broadway_Nights_2025-11-15T00-15-30-123Z.mp3",
"message": "Successfully generated jazz music: \"Broadway Nights\""
}curl -X GET "http://localhost:3001/health"curl -X GET "http://localhost:3001/audio-files"curl -X GET "http://localhost:3001/test"For testing without API usage/costs:
{
"directions": ["Sample directions..."],
"dummyMode": true
}Dummy mode uses pre-recorded responses for both style detection and music generation.
The Client Server provides REST API endpoints for web integration with async job processing to handle long-running music generation:
-
POST
/orchestrate- Start music generation job (returns immediately)- Input:
{ "directions": ["step1", "step2", ...], "dummyMode": boolean } - Output:
{ "success": true, "jobId": "job_123...", "status": "pending", "statusUrl": "/status/job_123..." }
- Input:
-
GET
/status/:jobId- Check job progress and results- Output: Job status, progress, and results when completed
-
GET
/jobs- List all jobs with statistics
- GET
/health- Health check with active job count - GET
/audio-files- List available audio files - GET
/test- Test endpoint with sample data - Static
/audio/*- Serve generated MP3 files
// Start job
const startResponse = await fetch('http://localhost:3001/orchestrate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
directions: [
"Start at SP414, 84069, Roccadaspide, Salernes",
"Go northwest on SP414",
"At the roundabout, take the second exit to stay on SP414"
],
dummyMode: false // Set to true for quick testing
})
});
const { jobId } = await startResponse.json();
// Poll for completion
const pollStatus = async () => {
const statusResponse = await fetch(`http://localhost:3001/status/${jobId}`);
const status = await statusResponse.json();
if (status.status === 'completed') {
// Play the generated music
const audio = new Audio('http://localhost:3001' + status.audioUrl);
audio.play();
console.log('Generated:', status.styleCard.songTitle);
} else if (status.status === 'failed') {
console.error('Generation failed:', status.error);
} else {
// Still processing, check again in 2 seconds
setTimeout(pollStatus, 2000);
}
};
pollStatus();"Could not read config file"
- Ensure
config.jsonexists inMCP/server/ - Check that API keys are properly formatted (no extra quotes/spaces)
"Failed to connect to localhost"
- Verify both servers are running (
npm startin each directory) - Check ports 3000 and 3001 are not blocked by firewall
"API key invalid"
- Verify API keys are active and have sufficient credits
- Check API key format matches the service requirements
"No audio file found"
- Check MCP server logs for file creation messages
- Verify dummy data files exist in
MCP/server/dummyData/
Checki Node process console outut:
- MCP Server: Shows composition plans and file operations
- Client Server: Shows orchestration steps and file discovery
