← Back to articles

Building an Audio Soundwalk Map

Path: Computer Tech/Development/Tech Companies/Google/Google Maps Platform/Workflows/Building an Audio Soundwalk Map.mdUpdated: 2/3/2026

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:

csv
name,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:

javascript
const 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:

javascript
let 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)

bash
git 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 mapId parameter 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

Resources