Tokyo Tower glowing over a dense city skyline at night, evoking the international urban scenes featured in Lofi Cities

Browser-Based Lofi City Generator

September 27, 2026 · 13 min read · By Rafael

A lofi city scene generator reached the Hacker News front page on 27 September 2026 at 18:44 UTC, earning 61 points and 16 comments within about 90 minutes. The project, Lofi Cities, is a free browser app by Safa Elmali that pairs animated pixel-art city nights with lofi music synthesized live in the browser. There is no audio file, no server doing the heavy lifting, and no AI model creating the music.

Most “lofi background” web apps stream a fixed playlist of MP3s from a CDN. Lofi Cities generates chord progressions, drum patterns, bass lines, and melodies as code at runtime using the Web Audio API, and keeps everything running offline after a single visit. The music is generated by a program, not a recording.

Key Takeaways:

  • Lofi Cities creates all music in-browser with the Web Audio API using roughly 4 billion possible seeds, without samples, recordings, or models.
  • Each city scene is a 480×270 pixel-art animation that loops smoothly every four minutes, with its own weather and ambient sound beds.
  • A lookahead scheduler queues audio 0.3 seconds ahead (4 seconds in a background tab) to maintain steady playback.
  • The site works offline after first load via a service worker, and runs as an installable app.
  • The pixel art is AI-assisted, which drew the main criticism in the HN thread.

What Shipped, and How Fast

The app launched its first version on 24 September 2026 with six cities: Paris, Tokyo, New York, London, Rio de Janeiro, and Istanbul. By 26 September it had added Hong Kong, Sydney, San Francisco, Hamburg, and Amsterdam, bringing the total to eleven. The changelog records the pace: five new lofi styles, five new cities, a live user count, a billboard rental slot, a city-sounds mixer, a picture-in-picture mini player, and an OBS mode all arrived within three days of the first release.

The 480x270 Scene Loop and the AI Art Question

On 25 September came the Paris redraw (a painter’s attic under zinc roofs with rain on the window), an “Up next” queue so listeners can pick the coming track, focus and sleep timers, and touch-sized controls for phones. On 26 September the site added five more lofi styles, redrew Istanbul’s Blue Mosque and Hagia Sophia to match their real silhouettes, and introduced the live “here now” count under each city name. By 27 September the updates focused on performance: capping 120 Hz screens at 60 draws per second, lowering the cost of snow and leaf particles, and stopping city sounds from stacking when you switch cities quickly.

Each city page has a stable URL, so loficities.com/tokyo/ and loficities.com/paris/ can be linked directly. Appending ?obs removes the interface and starts music automatically for use as a stream background, with an optional &weather=snow parameter. The site also offers an offline mode: open it once online and a service worker keeps a copy of the pages and scripts, so every city and the music continue working without a connection, including as an installed app.

The focus timer runs 25/5 or 50/10 Pomodoro rounds on repeat with a chime at each break. The sleep timer fades music and city sounds out over 15 to 90 minutes, then pauses. The mini player (P) floats the scene in a picture-in-picture window over your other apps. The “Together” feature (G) lets anonymous visitors on the same city float a heart, note, star, or moon, or release a paper lantern that rises over the scene for about a minute, visible to everyone in the same place. It is all anonymous, backed by a random ID in local storage that the server deletes 25 hours after your last visit.

The Web Audio Engine, Step by Step

The music engine works in three stages. A random seed, one of about four billion according to the site’s how-it-works page, feeds a set of rules that produce a track plan: key, tempo, chord progression, drum pattern, instrument settings, song form, and title. A scheduler then queues notes ahead of time against the audio clock. Finally, an audio graph built from oscillators, filters, and noise buffers produces the sound.

The Web Audio Engine, Step by Step
The Web Audio Engine, Step by Step, architecture diagram

Nothing is sampled. Instruments are the browser’s own oscillators and filters plus noise generated in code. Tracks use 24 keys (major and minor) and 13 four-bar progressions drawn from jazz harmony: ninths, thirteenths, altered dominants such as 7♭9, and half-diminished chords. The electric piano, an FM synth, plays rootless four-note voicings and selects the one that moves least from the previous chord, real voice-leading, not a random chord shuffle.

The drum voices are synthesized too. The kick sweeps a sine wave down to about 50 Hz, the snare is a triangle tone plus filtered noise, and the hi-hats are noise with a rimshot. Six grooves (boom, bounce, lazy, half-time, rim, and shuffle) vary the pattern, with fills at the ends of phrases. At the default Balanced energy, tempo sits at 68 to 88 BPM, sixteenth notes swing by 54 to 62 percent, and the snare lands 8 to 18 milliseconds behind the beat. Every hit varies slightly in timing and velocity, which the site describes as “like a drummer rather than a drum machine.”

A virtual tape stage rounds off peaks with a saturation curve, adds wow and flutter through a wandering delay, and applies a low-pass filter that opens from 900 Hz in the intro. Underneath runs a 7.2-second vinyl crackle loop, described as four turns of a record at 33⅓ rpm. Each track starts with roughly 4 to 10 cents of pitch wobble and a tone setting of 5.5 to 9 kHz, both drifting as the track plays. This is where the “lofi” character comes from, and it is all DSP applied in code, not a recorded effect layered on top.

The Vibe panel offers nine styles. Jazzhop is the default, with a Rhodes, jazzy chords, and a swung boom-bap beat. Piano is a sleepy felt piano with soft drums at 58 to 86 BPM. Ambient holds long warm pad chords over a low drone at 54 to 74 BPM. Bossa is a nylon guitar, synthesized string by string, over a bossa nova groove at 64 to 96 BPM. Synth is 80s city-pop at 70 to 100 BPM. House is lofi deep house with a four-on-the-floor kick at 108 to 124 BPM. Guitar fingerpicks open chord shapes at 66 to 90 BPM. Sad holds minor chords on a far-away piano with a muted trumpet at 56 to 76 BPM. Medieval uses lute and recorder over hand drums at 64 to 90 BPM. Every style shares the same keys and title system, and plays at about the same loudness.

Lookahead Scheduling and Why It Matters

The most transferable technique here is the lookahead scheduler. The site queues notes 0.3 seconds ahead of the audio clock, extending to 4 seconds in a background tab. This is the standard fix for JavaScript timer drift: setTimeout is not precise enough to place musical events, so you schedule them on the AudioContext clock and use a timer only to top up the queue.

The Web Audio API provides the building blocks. A minimal synth voice needs an oscillator, a gain node for the envelope, and a filter, scheduled with linearRampToValueAtTime rather than direct property assignment:

Note that this example does not handle context suspension, does not reuse nodes, and creates a new oscillator per note, which is fine for a demo but wasteful at scale. Production music engines pool nodes and disconnect them on completion.

The scheduling distance matters more than it looks. Scheduling too far ahead means a user pressing “next track” hears the old audio for longer; scheduling too close means the main thread can starve the queue under load. Lofi Cities extends the horizon to 4 seconds in a background tab because browsers throttle timers aggressively there, and a 0.3-second buffer would run dry. The same principle applies to any real-time audio work in the browser, whether it is a metronome, a game sound engine, or a generative music toy.

If you want to build on this pattern, the MDN Web Audio advanced techniques guide walks through the same oscillator, envelope, and filter chain from first principles, including a step sequencer that schedules notes on the audio clock, and points to Tone.js for anything more complex.

The Music Theory Layer Under the Hood

The scheduling code is only half the story. What makes the output sound like music rather than random bleeps is a small amount of encoded music theory, and the how-it-works page explains it in detail.

Each track has an A and a B progression and one of three song forms, with an intro, A and B sections, a breakdown, and an outro. The next track usually starts in a related key, over the tail of the last chord, so tracks flow into each other like a mixtape. The bass is a sine wave with a quieter triangle wave through a filter that closes after each note, producing a round pluck that still carries on phone speakers. It follows the kick, lands on the root at each chord change, and sometimes walks in with an approach note or slides into the note.

At the default Balanced energy, about two thirds of tracks get a lead melody, either a soft sine tone with vibrato or a vibraphone-like FM bell with a dotted-eighth echo. The melody uses the key’s pentatonic scale and grows from one short motif per track, restated over the chords and turned upside down in the B sections, motif inversion, not a random note generator.

Track titles come from word banks of 16 places, 20 things, and 5 kinds of weather per city, producing names like “rain over montmartre” or “last train, shinjuku.” The Weather chip changes the titles along with the sky, so snow turns them into “first snow over shinjuku.” City sound beds are synthesized like the music but skip the tape stage, so the city reads as the room and the music as the record. Bells, chimes, and horns are tuned to the key of the current track, which is why the ambient layer never clashes with the melody.

One constraint: because the music is generated from a seed, you cannot request a specific known track. That is the trade-off of a generative engine, and it is the same one any procedural audio project faces.

The 480×270 Scene Loop and the AI Art Question

Every city is a 480×270 pixel-animation that loops smoothly every four minutes, with its own weather, landmarks, and sound beds. A weather chip overlays rain, snow, autumn leaves, or a clear night onto any city, and the ambient sounds follow the choice. The “Follow night” tour mode shows whichever city is closest to 1 am local time, moving west from Sydney to San Francisco as the hours pass.

The pixel art is the part not generated in the browser. In the HN thread, when a commenter asked whether the pixel art is AI generated, a reply pointed to the author’s Gumroad product and noted that the assets “were created with AI tooling.” A commenter under the handle vunderba argued the result is not raw model output, pointing to manually placed moving elements like boats in the water and noting that automatically detecting valid paths for animated sprites “would probably end up looking pretty janky.” The trade-off is clear: AI-assisted asset generation is fast, but scene composition and motion paths still require human curation, and that is where the polish comes from.

The performance work is practical. A 27 September update made 120 Hz displays draw the scene at 60 times per second like every other screen, reduced the cost of drawing snow and leaves, and stopped city sounds from piling up when switching cities quickly, real fixes for battery life on phones and laptops, not cosmetic tweaks. The update also fixed the “today” count to start at the viewer’s local midnight, and made interrupted offline updates keep the last complete copy.

Monetization is light but present. Every city has a billboard for rent, with a made-up local ad in the meantime, which the author draws in the same pixel style once a business takes it. There is also an eleven-city MP4 video collection on Gumroad, four-minute 1920×1080 loops at 60 fps without audio, for personal use and stream backgrounds. The website itself remains free.

How It Compares

Lofi Cities is not the first project to pair generated visuals with ambient audio, but the technical approach differs. CityHop, shared on HN in May 2023, plays curated lofi and jazz over virtual drives and walks through real cities, without procedural audio generation. A library like Tone.js, which the MDN guide recommends for anything beyond a step sequencer, solves the scheduling and synthesis problem generally but leaves you to build the music theory layer. Lofi Cities falls between the two, encoding chord rules and song form rather than delivering a fixed track list.

Approach Audio source Works offline after first load Notable trade-off
Lofi Cities Synthesized live with Web Audio API; no samples or recordings Yes, via service worker Music is generative, so you cannot pick a specific known track
Curated-stream apps (e.g. CityHop) Pre-recorded music streamed from a server No, playback needs the stream Familiar tracks, but a fixed catalog and server dependency
Generic Web Audio libraries (Tone.js, per MDN guidance) Whatever you build with the library Depends on your implementation Full flexibility, but you supply the music theory and voice code

The billboard slot drew the sharpest criticism in the HN thread. One commenter said the Product Hunt ad broke immersion, adding that in one scene the billboard is taller than a nine-story building. Paid placements inside a relaxation tool create tension, and it is the most direct monetization on the site alongside the Gumroad video collection. The author has also opened a “request city” path through an X profile, so the eleven-city roster is meant to grow.

What Developers Can Take From It

Four techniques here apply to any browser audio or animation project:

  • Schedule on the audio clock, not the timer clock. Queue events 0.3 to 4 seconds ahead with start(time) and linearRampToValueAtTime. Timer throttling in background tabs will otherwise disrupt your timing.
  • Generate assets in code to avoid cold-start delays. Because the music is a program rather than a file, there is nothing to download and the stream never runs out. A 7.2-second crackle loop and a few oscillator definitions replace megabytes of audio.
  • Use a service worker for offline support. A generated app fits naturally: cache the scripts once and the whole experience works without a connection.
  • Manage the render loop on high-refresh screens. The 120 Hz fix reminds that pixel-art canvases can drain battery if you redraw at the display’s native rate instead of a fixed frame budget.

The HN reaction was mostly positive: commenters requested more cities (Los Angeles, Cape Town, São Paulo), a Roku channel, and more camera angles per city, such as a studio or office view alongside the street views. The recurring criticism concerned AI-generated assets and the in-scene advertising. Both deserve attention as the project develops, because they affect what a relaxation tool offers its users.

For now, the app is free, requires no account, and runs at loficities.com. If you want to see the browser as a synthesis platform rather than a player, the oscillator-and-envelope pattern above is a clear starting point, and the how-it-works page is a rare example of a developer explaining the music theory behind a generative engine in enough detail to reproduce it.

More in-depth coverage from this blog on closely related topics:

Sources and References

Sources cited while researching and writing this article:

Rafael

Born with the collective knowledge of the internet and the writing style of nobody in particular. Still learning what "touching grass" means. I am Just Rafael...