The Idea
Every city has its own character. Walking through New York in January — that biting cold cutting between skyscrapers — has a completely different energy than Tokyo in August, where the humidity hits you like a wall the second you step outside. San Diego just hums along in its endless sunshine. I kept thinking: what if that relationship went both ways? What if the weather wasn't just a backdrop but the actual composer?
The pitch is simple: type a city, get a song. The song reflects what's actually happening there right now — not just vibes, but a composition derived from live weather data. Rainy London and sunny London sound different. Snowy Reykjavik and stormy Reykjavik sound different. Come back tomorrow and the song shifts with the forecast.
Version One: Procedural
The first version was fully procedural. Tone.js synthesizers and samplers, note-by-note scheduling, offline rendering to MP3 on AWS Fargate. Weather conditions mapped to genre palettes — rain triggered jazz, clear skies triggered bossa nova, storms triggered post-rock. Each palette defined chord progressions, drum grooves, melodic motifs, bass patterns, and instrument configurations. A seeded PRNG picked from each palette deterministically, and continuous weather parameters (temperature, wind, humidity) shaped the performance.
It worked. Technically. But the results sounded like what they were: a computer following rules about music. The chord progressions were correct. The rhythms were quantized perfectly. And that was the problem — it sounded like a MIDI file with nicer samples. No swing that wasn't mathematically defined. No dynamics that emerged from feel rather than a curve. No happy accidents.
I spent weeks chasing sample quality. The vibraphone was actually xylophone samples mislabeled. The celesta was harp samples. Some instruments had only 8-10 notes, so the sampler pitch-shifted aggressively and you could hear the artifacts. I built a multi-source sample pipeline pulling from VSCO2, Versilian, Karoryfer, and archived university collections. It helped, but the fundamental problem wasn't the samples — it was that procedural composition produces procedurally-sounding music.
The Pivot to AI
Once I admitted the real problem, the solution was obvious: let an AI model that actually understands music do the composing. The weather-to-music mapping pipeline — the part that decides what kind of music to make — was solid. The part that made the actual sounds was the weak link.
I evaluated five APIs. Suno and Udio have no official APIs and active copyright lawsuits. Meta's MusicGen caps at 30 seconds. Beatoven.ai caps at 2.5 minutes with only 8 genres. The real contenders were ElevenLabs (section-level structural control, ~$1.44/song) and Google's Lyria 3 Pro (~$0.08/song via the Gemini API).
Lyria won. More musical texture, better instrumental variety, 48kHz stereo, and roughly 30x cheaper per section than ElevenLabs. The free-tier Gemini API key works for prototyping, and production costs are about $1.60/day for 20 pre-generated cities.
How It Works Now
The mapping pipeline survived the pivot almost unchanged — it just outputs prompts instead of note schedules. Here's the flow:
Weather → Conditions. OpenWeatherMap's 3-hour forecast gets interpolated to 24 hourly entries. Each hour resolves to a condition through a priority chain: tornado, thunderstorm, squall, snow, fog, dust, hot-humid, rain, drizzle, mist, cold-clear, clear-night, clear, partly-cloudy, cloudy. First match wins.
Conditions → Sections. Consecutive hours with the same condition get grouped into sections. A day might produce 3-4 sections — morning fog, afternoon clear, evening rain. Each section gets its own generation.
Sections → Genres. Each weather condition has a pool of 3-4 genre options. Storms can trigger post-rock, industrial ambient, or drum & bass. Snow can become minimalist, neo-classical, ambient piano, or post-classical. Tornados get doom metal, dark drone, or breakcore. The PRNG picks from the pool, avoiding repeats across sections so the song has variety.
Geography → Flavor. The city's coordinates resolve to a cultural region — Caribbean, East Asian, Nordic, Middle Eastern, West African, and a dozen others. Each region boosts the weight of culturally relevant genres (Caribbean boosts reggae and dub, East Asia boosts city pop and j-jazz) and adds a flavor phrase to the prompt ("with Caribbean rhythmic pulse," "with Eastern melodic sensibility").
Weather → Musical Direction. Raw weather values map to musical aspects that get described in natural language: temperature drives tempo descriptions, wind speed drives energy, humidity drives groove feel, visibility drives spatial quality, cloud cover drives brightness, rainfall drives texture. These trend across the section — rising temperature becomes "gradually increasing tempo."
Everything → Prompt. The prompt builder assembles it all: genre styles, regional flavor, an evocative weather feel ("the tension and release of rolling thunder, dramatic swells"), musical aspect descriptions, key, duration, and fade hints for section transitions. This goes to Lyria.
Sections → Song. Lyria generates audio for each section in parallel. The raw audio clips come back as base64-encoded buffers. ffmpeg crossfades them together with 3-second overlaps, producing one continuous MP3. The final file and a JSON metadata sidecar get uploaded to S3.
What Survived the Pivot
The PRNG determinism still works, just differently. The seeded random generator (murmurhash3 + mulberry32, seeded from coordinates + date) now picks genres from pools and drives the weighted selection — but the actual audio generation isn't deterministic. Lyria produces different audio each call. That's fine, because we generate once per city per day and cache the result in S3. Same city + same date = same cached MP3. The determinism guarantee moved from "mathematically identical" to "generated once, served forever."
The weather feel descriptions are my favorite surviving piece. Instead of mapping humidity to a filter cutoff value, the system now describes what the weather feels like: "soft snowfall, quiet and padded, the world muffled in white" or "relentless rain hammering on rooftops, rhythmic and hypnotic." Heavy rain and light drizzle get different feels. It's a more natural way to direct a music model than numerical parameters ever were.
The Client
The SPA stayed largely the same through the pivot. Search a city, see a satellite map with animated RainViewer radar overlays, watch weather stats update as the song plays through the day's hours. A genre-colored progress bar shows mood shifts across sections — cool blues for snow, warm tones for clear days, dark for storms. A collapsible "How it works" panel shows the genre, weather condition, and expression parameters for each section in real time.
Two playback modes: "Full Day" plays all 24 hours as a 3-minute track, "Right Now" loops just the current hour. You can favorite cities, share direct links, and if you're the first person to listen to a city on a given day, you get a little toast celebrating it.
Go Listen
Go to cityweathersong.com and type your city. Try somewhere with interesting weather — a city in a storm, or a tropical city at night, or Reykjavik in winter. Then try somewhere completely different and hear how the genre shifts.
The thing that surprised me most: the AI-generated sections have musical personality that the procedural version never achieved. A foggy trip-hop section actually grooves. A stormy post-rock section actually builds tension. The mapping pipeline provides the intent — "this should sound like fog" — and Lyria provides the musicianship. It's the collaboration I was trying to brute-force with chord progression arrays and 16-step drum patterns, except now one side of the collaboration actually knows how to play.