singe/demos/README.md

6.7 KiB

Singe demos

Three demos in the old demoscene way: a song each, and everything on screen timed to it. Every one runs live, nothing on screen is a recording of the demo itself, and every motion takes its time from the music's clock (musicGetPosition), so a demo stays with its song on any machine at any frame rate.

Demo Song Length
Neon Coast "Neons in Napoli" by eliza_music 3:08
Mirrorball "Vintage Funky Groove" by miiira 4:05
Insert Coin "Game Is On" by poorartistt 4:10

Each folder has its own games.dat, so Singe runs a demo like any game: from this folder, Singe neonCoast/neonCoast.singe. They need the 3D scene. kit.lua is the code all three share: the music clock, beats and bars, the envelopes and drum hits, the scene runner, and the effects more than one demo uses (vector bobs, the sine scroller, flashes and strobes, chrome and glass materials, a camera-filling quad, a torus knot).

How the timing works

util/demoSync.py listens to a song once, offline, and writes sync.lua beside the demo:

  • Beats. A song on a fixed tempo gets a grid, found by folding the onsets of the whole track at every candidate period and keeping the sharpest; Neon Coast's is 122.000 beats a minute and Insert Coin's 135.000, with the tracked beats within 4 ms of them. Mirrorball's song speeds up from about 111 to 117, so it gets the tracker's beats, each moved to the strongest onset near it and smoothed through its neighbours, and carried on past where the tracker gave up.
  • Bars. Beat one is the beat where the loudness and the bass jump most often, since songs change section on a downbeat.
  • Envelopes. Kick, snare, and hat (the change in each drum's frequency band), bass, loudness, and brightness, sixty samples a second, each 0 to 1, packed one character a sample.
  • Hits. The times of the kicks, snares, and hats, for effects that fire once per hit.

A scene in a demo says which bars it spans, so cuts land on downbeats, and inside it effects read the envelopes and hits at the current time: a flash that decays from every kick, a formation that steps on every beat, a tunnel whose speed the kick pushes.

The songs, and what happens in them

Neon Coast (bar n starts at 0.1008 + (n - 1) x 1.96721 s)

Bars Song Demo
1-3 pads only a tape starts: snow, the deck's lettering, the title typed out
4-16 bass in the sunset grid; the chrome logotype rises at 9, the title slams in at 13
17-18 bass out, snare build the logotype turns, the fog closes in, strobes on the snare roll
19-28 full groove the night drive: a streetlight passes the car on every beat
29-30 bass out the car takes a crest and flies, in slow motion
31-46 main section the chrome knot and glass, a cut every two bars; plasma billboards from 39
47-48 filtered drop the tape rewinds
49-58 busy hats the tunnel, scrubbed by the kick; vector bobs and the scroller
59-60 bass out the tunnel stops, the bobs fall in, ARE YOU READY
61-68 loud plasma, a rotozoomed logo, a ring of palms
69-76 breakdown, then the kick a flag on the night grid, blown by the kicks once they return
77-78 riser, dead stop into hyperspace, white, and black for the stop
79-94 finale the road and the chrome, cut every two bars, fireworks on the snares
95- ending the set switches off; credits

Mirrorball (eight-bar phrases from bar 1 at 0.79 s; the bars shorten as the song speeds up)

Bars Song Demo
1-8 choppy drums and bass copper bars gated by the groove, the title dropping a letter at a time
9-16 chords the club: the ball comes down, spots sweep, the floor swaps on every beat
17-24 lead a rotozoomer, copper bars coloured by the note being played, the scroller
25-32 full a mirrored kaleidoscope of plasma under vector bobs
33-40 choppy again the club, cut on every beat, black in the gaps
41-48 denser hats the twister, drawn a scanline at a time, over copper bars
49-56 horns a checkered ball bouncing on the beat; a ball drops on every snare and piles up
57-64 glenz glass in a tunnel scrubbed by the kick
65-72 dense lasers in the haze
73-80 plasma and a rotozoomed logo
81-88 breakdown the dark club; the floor relights a tile a beat when the bass returns
89-96 build everything back on, strobing
97-112 full, then final the party, then everything at once
113- the last downbeat the ball shatters; credits

Insert Coin (bar n starts at 0.0067 + (n - 1) x 1.77778 s)

Bars Song Demo
1-4 intro a tube warms up; INSERT COIN, and a coin drops on the downbeat of 5
5-8 drums in 1-bit: bat and ball in phosphor, the ball meeting a bat on every beat
9-16 groove 8-bit: a formation that steps on the beat; the cannon fires on the snare
17-24 lead melody 16-bit: a runner over parallax hills, jumping on the backbeat
25-32 lead melody 16-bit: a kart round a track seen in perspective
33-40 breakdown, riser a tape loading
41-56 full 32-bit: a flat-shaded ship through rings, one a beat, snapped to a grid
57-72 heavy now: chrome balls into a wall of glossy bricks, real physics, on the kick
73-80 breakdown GAME OVER, and a countdown to continue, a number a bar
81-96 melody returns the boss, firing on the kick, the hats, and the snare
97-112 full the bonus stage: coins pour in on the beat
113-120 melody the high score table, which is the credits
121-136 final every era again, two bars each, then GAME ON
137- outro GAME OVER, and the set switched off on the last hit

Making them again

Everything in art/, video/, and sync.lua is generated:

  • util/neonCoastArt.py, util/mirrorballArt.py, util/insertCoinArt.py draw each demo's pictures (PIL and numpy).
  • util/demoVideo.py precalculates the per-pixel effects as videos: the tunnel (every frame a keyframe, so a demo can seek to any frame and run the tunnel at the music's speed), the plasma, and the tape's snow.
  • util/demoSync.py writes sync.lua from music.mp3. It needs librosa, which Singe does not: python3 -m venv env && env/bin/pip install librosa.

util/demoCapture.sh neonCoast 0 190 [every] [out.mp4] runs a stretch of a demo headless on a clock that moves exactly one 20 ms step a frame, saves the frames, and with every = 1 puts the same stretch of the music under them in an MP4. The engine plays the music on real time, so a capture is silent and the song is added afterwards; a demo run normally plays it itself. Set DEMO_STATS = true before loading a demo to have it print its frame rate every second.