114 lines
6.7 KiB
Markdown
114 lines
6.7 KiB
Markdown
# 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.
|