Building an Audio Soundwalk Map
Building an Audio Soundwalk Map
Overview
Create an interactive web map where users click locations to hear field recordings from that spot. Perfect for sonic ethnography, acoustic ecology projects, or immersive audio storytelling.
What you'll build:
- Interactive map centered on your recording area
- Custom markers for each recording location
- Click marker → audio player appears with recording
- Info windows showing recording metadata (date, equipment, notes)
- Optional: Polyline showing suggested walking route
Tech stack:
- Google Maps JavaScript API (map + markers)
- Places API / Geocoding API (location data)
- Maps Datasets API (for 20+ recordings)
- HTML5
<audio>elements (playback) - Your field recording files (hosted somewhere)
Time: 3-4 hours for basic version, 8+ hours with advanced features
Pre-Project Checklist
✅ Field recordings collected and organized ✅ Recording metadata documented (location, date, equipment, notes) ✅ Audio files hosted (web server, cloud storage, or CDN) ✅ Google Cloud project with Maps JavaScript API enabled ✅ API key created and restricted to your domain
Step 1: Organize Recording Metadata
Create a spreadsheet or JSON file with your recordings:
CSV format:
csvname,address,latitude,longitude,audio_url,date,equipment,notes Tijuana Jazz Club - Friday Night,Av. Revolución 1006 Tijuana,32.5327,-117.0363,/audio/tj-jazz-01.mp3,2025-11-01,Zoom H6,Live jazz trio Black Box - Experimental,Av. Revolución 1217 Tijuana,32.5307,-117.0366,/audio/black-box-01.mp3,2025-10-15,Zoom H6,Heavy bass Zona Centro Ambience,Av. Revolución 1100 Tijuana,32.5315,-117.0365,/audio/zona-centro-ambient.mp3,2025-11-03,Sony PCM-D100,Street sounds
Or JSON format:
json[ { "name": "Tijuana Jazz Club - Friday Night", "coords": {"lat": 32.5327, "lng": -117.0363}, "audio_url": "/audio/tj-jazz-01.mp3", "date": "2025-11-01", "equipment": "Zoom H6, Rode NTG3", "duration": "3:45", "notes": "Live jazz trio, moderate audience noise, 9:30 PM set" }, { "name": "Black Box - Experimental Electronic", "coords": {"lat": 32.5307, "lng": -117.0366}, "audio_url": "/audio/black-box-01.mp3", "date": "2025-10-15", "equipment": "Zoom H6, Audio-Technica BP4025", "duration": "2:18", "notes": "Heavy bass, spatial audio recording, midnight set" } ]
If you only have addresses (no coordinates):
python# Use MCP tool to geocode addresses import json recordings = [...] # Your data with addresses for recording in recordings: result = maps_geocode(recording['address']) recording['coords'] = { 'lat': result['location']['lat'], 'lng': result['location']['lng'] } with open('recordings-with-coords.json', 'w') as f: json.dump(recordings, f, indent=2)
Step 2: Set Up Basic HTML Page
index.html:
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tijuana Soundwalk</title> <style> body { font-family: 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 0; background: #1a1a1a; color: #f0f0f0; } header { background: #2a2a2a; padding: 20px; text-align: center; border-bottom: 2px solid #00ff88; } h1 { margin: 0; font-size: 2em; color: #00ff88; } #map { width: 100%; height: 600px; } .info-window { max-width: 300px; } .info-window h3 { margin: 0 0 10px 0; color: #00ff88; } .info-window audio { width: 100%; margin: 10px 0; } .metadata { font-size: 0.9em; color: #999; margin-top: 10px; } footer { background: #2a2a2a; padding: 20px; text-align: center; margin-top: 20px; font-size: 0.9em; color: #999; } </style> </head> <body> <header> <h1>Tijuana Soundwalk 2025</h1> <p>Click markers to hear recordings from each location</p> </header> <div id="map"></div> <footer> <p>Recorded by [Your Name] | November 2025</p> <p>Total recordings: <span id="recording-count">0</span></p> </footer> <script src="recordings.js"></script> <script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"> </script> <script src="soundwalk.js"></script> </body> </html>
Step 3: Create Recordings Data File
recordings.js:
javascriptconst recordings = [ { name: "Tijuana Jazz Club - Friday Night", coords: {lat: 32.5327, lng: -117.0363}, audio_url: "/audio/tj-jazz-01.mp3", date: "2025-11-01", equipment: "Zoom H6, Rode NTG3", duration: "3:45", notes: "Live jazz trio, moderate audience noise, 9:30 PM set" }, { name: "Black Box - Experimental Electronic", coords: {lat: 32.5307, lng: -117.0366}, audio_url: "/audio/black-box-01.mp3", date: "2025-10-15", equipment: "Zoom H6, Audio-Technica BP4025", duration: "2:18", notes: "Heavy bass, spatial audio recording, midnight set" }, { name: "Zona Centro Street Ambience", coords: {lat: 32.5315, lng: -117.0365}, audio_url: "/audio/zona-centro-ambient.mp3", date: "2025-11-03", equipment: "Sony PCM-D100", duration: "5:20", notes: "Afternoon street sounds, vendors, traffic, conversation" } // Add all your recordings... ];
Step 4: Initialize Map with Markers
soundwalk.js:
javascriptlet map; let markers = []; let currentInfoWindow = null; function initMap() { // Calculate center point (average of all recording coords) const center = calculateCenter(recordings); // Initialize map map = new google.maps.Map(document.getElementById("map"), { center: center, zoom: 15, mapId: "tijuana_soundwalk", // Create custom Map ID for styling disableDefaultUI: false, zoomControl: true, mapTypeControl: false, streetViewControl: false, fullscreenControl: true }); // Add markers for each recording recordings.forEach((recording, index) => { addRecordingMarker(recording, index); }); // Update recording count document.getElementById('recording-count').textContent = recordings.length; // Optionally: Draw route connecting recordings if (recordings.length > 1) { drawSoundwalkRoute(); } } function calculateCenter(recordings) { const avg = recordings.reduce((acc, rec) => { return { lat: acc.lat + rec.coords.lat, lng: acc.lng + rec.coords.lng }; }, {lat: 0, lng: 0}); return { lat: avg.lat / recordings.length, lng: avg.lng / recordings.length }; } function addRecordingMarker(recording, index) { const marker = new google.maps.Marker({ position: recording.coords, map: map, title: recording.name, label: { text: String(index + 1), color: "#ffffff", fontSize: "14px", fontWeight: "bold" }, icon: { path: google.maps.SymbolPath.CIRCLE, scale: 20, fillColor: "#00ff88", fillOpacity: 0.9, strokeColor: "#ffffff", strokeWeight: 3 } }); // Create info window content const infoContent = createInfoWindowContent(recording); const infoWindow = new google.maps.InfoWindow({ content: infoContent }); // Click marker → open info window + play audio marker.addListener("click", () => { // Close previous info window if (currentInfoWindow) { currentInfoWindow.close(); pauseAllAudio(); } infoWindow.open(map, marker); currentInfoWindow = infoWindow; }); markers.push(marker); } function createInfoWindowContent(recording) { return ` <div class="info-window"> <h3>${recording.name}</h3> <audio controls preload="none"> <source src="${recording.audio_url}" type="audio/mpeg"> Your browser does not support audio playback. </audio> <div class="metadata"> <p><strong>Date:</strong> ${recording.date}</p> <p><strong>Duration:</strong> ${recording.duration}</p> <p><strong>Equipment:</strong> ${recording.equipment}</p> <p><strong>Notes:</strong> ${recording.notes}</p> </div> </div> `; } function pauseAllAudio() { // Stop any playing audio when switching markers const audios = document.querySelectorAll('audio'); audios.forEach(audio => audio.pause()); } function drawSoundwalkRoute() { // Optional: Draw line connecting recording points in order const path = recordings.map(r => r.coords); const walkPath = new google.maps.Polyline({ path: path, geodesic: true, strokeColor: "#00ff88", strokeOpacity: 0.6, strokeWeight: 3, map: map }); }
Step 5: Test Locally
bash# Start simple HTTP server python3 -m http.server 8000 # Or using Node.js npx http-server
Visit http://localhost:8000 and test:
- ✅ Map loads and centers correctly
- ✅ All markers appear
- ✅ Click marker → info window opens
- ✅ Audio player appears and plays
- ✅ Metadata displays correctly
Step 6: Add Advanced Features (Optional)
Filters by Date or Category
javascript// Add filter controls in HTML <div id="filters"> <label> <input type="checkbox" checked data-category="live_music"> Live Music </label> <label> <input type="checkbox" checked data-category="ambient"> Ambient </label> </div> // Filter markers based on checkboxes function filterMarkers() { const activeCategories = Array.from( document.querySelectorAll('#filters input:checked') ).map(input => input.dataset.category); markers.forEach((marker, index) => { const recording = recordings[index]; marker.setVisible(activeCategories.includes(recording.category)); }); } document.querySelectorAll('#filters input').forEach(input => { input.addEventListener('change', filterMarkers); });
Search Box (Places Autocomplete)
javascript// Add search input to HTML <input id="search-box" type="text" placeholder="Search locations..."> // Initialize autocomplete const searchBox = new google.maps.places.SearchBox( document.getElementById('search-box') ); searchBox.addListener('places_changed', () => { const places = searchBox.getPlaces(); if (places.length > 0) { map.setCenter(places[0].geometry.location); map.setZoom(17); } });
Download All Recordings
javascript// Add download button <button onclick="downloadPlaylist()">Download All (ZIP)</button> function downloadPlaylist() { // Generate M3U playlist file const m3u = recordings.map(r => `#EXTINF:-1,${r.name}\n${r.audio_url}` ).join('\n'); const blob = new Blob([m3u], {type: 'audio/x-mpegurl'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'tijuana-soundwalk.m3u'; a.click(); }
Weather/Air Quality Context
javascript// Fetch environmental data for each recording async function addEnvironmentalContext(recording) { const airQuality = await fetch(`https://airquality.googleapis.com/v1/currentConditions:lookup?key=YOUR_KEY`, { method: 'POST', body: JSON.stringify({location: recording.coords}) }).then(r => r.json()); recording.uaqi = airQuality.universalAqi; recording.weather = await fetchWeather(recording.coords); }
Step 7: Deploy
Option 1: GitHub Pages (Free)
bashgit init git add . git commit -m "Tijuana soundwalk map" git branch -M main git remote add origin https://github.com/yourusername/tijuana-soundwalk.git git push -u origin main # Enable GitHub Pages in repo settings → Pages → Source: main branch
Option 2: Netlify (Free)
- Drag-and-drop your project folder to netlify.com
- Automatic SSL, custom domain support
Option 3: Your own web server
- Upload files via FTP/SFTP
- Ensure audio files are accessible
Troubleshooting
Markers not appearing:
- Check browser console for errors
- Verify coordinates are {lat, lng} not {lng, lat}
- Ensure Map ID is created in Cloud Console
Audio not playing:
- Check audio file URLs (use relative paths like
/audio/file.mp3) - Verify CORS headers if hosting audio on separate domain
- Test audio files directly in browser
API key errors:
- Verify Maps JavaScript API is enabled
- Check API key restrictions (HTTP referrers)
- Try unrestricted key for testing (restrict before deployment)
Map style not applying:
- Create Map ID in Cloud Console
- Associate Map ID with custom style JSON
- Use
mapIdparameter when initializing map
Next Steps
- Add more recordings over time (update
recordings.js) - Create multiple soundwalk routes (different GeoJSON files)
- Integrate with social media (share specific recordings)
- Mobile app version (Maps SDK for Android/iOS)
- Print QR codes linking to specific markers
Related Workflows
- Mapping Water Infrastructure with Custom Data - Similar techniques for research data
- Using MCP Tools for Google Maps Integration - Backend data processing
Resources
- Maps JavaScript API Examples
- Custom map styling
- Audio hosting: SoundCloud API, AWS S3, or your own server
- GeoJSON.io for planning routes visually