152 lines
10 KiB
Markdown
152 lines
10 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, scrollers of five kinds (sine, bounce, zoom, spin, round a
|
|
circle), a 3D scroller, and a perspective crawl, flashes and strobes, chrome and glass materials, a camera-filling
|
|
quad, a torus knot, and three effects worked out a pixel at a time in Lua every frame and handed to
|
|
a sprite with `spriteSetPixels`: a plasma with a cycling palette, a lookup-table tunnel, and a
|
|
tape's snow. Nothing in any of the three demos is a video.
|
|
|
|
Every demo opens on the Singe logo, faded up from black to white, held, and faded out, before its
|
|
music starts, and closes the same way on the Kangaroo Punch logo after the music ends and the
|
|
picture fades to black. The logos are rendered from assets/ into demos/art/ by util/demoLogos.sh.
|
|
They are on the demo's own clock (the opening card runs from -3.5 s to 0), so util/demoCapture.sh
|
|
takes them with a negative start or a stretch past the end.
|
|
|
|
Scroller text can be changed freely. Each scroller is given the bars it runs between, not a speed:
|
|
`K.scroller(t, font, text, y, amp, startTime, endTime, style)` works its speed out from the text so
|
|
the first letter enters as the scene starts and the last leaves as it ends. Text too short to fill
|
|
the time at a readable speed is repeated until it does; text too long to read at its speed still
|
|
ends on time and puts a line in the trace (`DEMO scroller too long to read`, with about how many
|
|
characters would fit). `K.scroller3D(t, blocks, text, y, startTime, endTime)` is timed the same
|
|
way, with solid letters from `K.blockFont(glyphs, camera, fov, cell, alpha)`: a pixel font
|
|
extruded into blocks, hung off the camera in the scene's front layer (`nodeSetLayer`), so they
|
|
stand over 2D and 3D alike. Its letters are larger, so it is allowed to go slower before it
|
|
repeats. The text round the circle is written in over the first half of its scene,
|
|
and the crawl spaces whatever lines it is given over its scene, the same way.
|
|
|
|
## 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, fast: two streetlights pass the car on every beat, a 3D city on the horizon; a bouncing scroller |
|
|
| 29-30 | bass out | the car flies off the crest in slow motion, the road dropping away below, and lands on bar 31 |
|
|
| 31-38 | main section | the chrome knot and glass, a cut on every downbeat; a spinning scroller |
|
|
| 39-46 | main section | slaloming round the towers of a neon city, a swerve a bar and a barrel roll every two |
|
|
| 47-48 | filtered drop | the tape rewinds the flight |
|
|
| 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-64 | loud | plasma, a rotozoomed logo, a ring of palms, text written round a circle |
|
|
| 65-68 | loud | low and fast over neon mountains toward the sun, under a perspective crawl of Singe's features |
|
|
| 69-76 | breakdown, then the kick | a cloth flag waving on the night grid, gusting with the kicks once they return; a zooming scroller |
|
|
| 77-78 | riser, dead stop | into hyperspace, white, and black for the stop |
|
|
| 79-94 | finale | road, city, knot, and mountains, cut every two bars, fireworks on the snares; over the road the logotype and the dragon, beating its wings on the beat |
|
|
| 95- | ending | the set switches off |
|
|
|
|
**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-28 | full | a mirrored kaleidoscope of plasma under vector bobs |
|
|
| 29-32 | | a cloud of glowing dots that takes a new shape every bar |
|
|
| 33-40 | choppy again | the club, cut on every beat, black in the gaps, the lasers changing pattern every bar |
|
|
| 41-48 | denser hats | the twister, drawn a scanline at a time, over copper bars |
|
|
| 49-52 | horns | a checkered ball bouncing on the beat; a ball drops on every snare and piles up |
|
|
| 53-56 | | an equalizer city, a band of the music to each column and its last moments running back |
|
|
| 57-60 | | glenz glass in a tunnel scrubbed by the kick |
|
|
| 61-64 | | a turntable, the record scratched on every snare, the camera cutting every two beats |
|
|
| 65-72 | dense | lasers in the haze: 24 beams in six patterns, a new one every two beats |
|
|
| 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, cutting faster: club, dots, equalizer, club spinning, turntable, club |
|
|
| 113- | the last downbeat | the ball shatters and the lights go down |
|
|
|
|
**Insert Coin** (bar n starts at 0.0067 + (n - 1) x 1.77778 s)
|
|
|
|
From bar 1 to bar 121, a large scroller of translucent 3D block letters (`FEATURE_TEXT` near the
|
|
top of insertCoin.singe, in the demo's own pixel font from `art/font.lua`) runs over every era,
|
|
saying what Singe is and what it can do.
|
|
|
|
| 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 | analog: 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-48 | full | vector: glowing wireframe asteroids, split by the ship's aimed shot on every snare |
|
|
| 49-56 | full | 32-bit: a flat-shaded ship weaving a winding path through gates, one a beat, banking into the turns |
|
|
| 57-64 | heavy | light gun: targets slide along three rails, and each snare flashes the screen and drops the one under the crosshair |
|
|
| 65-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-88 | melody returns | versus: two fighters trade blows on the kicks and snares, ROUND 1, FIGHT!, K.O. |
|
|
| 89-96 | melody | the boss, firing on the kick, the hats, and the snare |
|
|
| 97-104 | full | the bonus stage: coins rain down far along the tunnel and ride its floor off the screen |
|
|
| 105-112 | full | rhythm: a note highway whose notes are the song's own kicks, snares, and hats |
|
|
| 113-120 | melody | the high score table, which is the credits |
|
|
| 121-136 | final | every era again, a bar each, then GAME ON |
|
|
| 137- | outro | GAME OVER, and the set switched off on the last hit |
|
|
|
|
## Making them again
|
|
|
|
Everything in `art/` and `sync.lua` is generated:
|
|
|
|
- `util/neonCoastArt.py`, `util/mirrorballArt.py`, `util/insertCoinArt.py` draw each demo's
|
|
pictures (PIL and numpy).
|
|
- `util/neonCoastCar.sh` renders Neon Coast's car with Singe's own 3D renderer
|
|
(`util/neonCoastCar.singe` builds the model): seven steering angles, each drawn over black and
|
|
over white so every pixel's true alpha comes out of the pair.
|
|
- `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/demoRecord.sh neonCoast out.mp4` records a whole demo with its music, using Singe's own
|
|
recorder: `--deterministic` and `--record` together keep every frame and generate the sound in
|
|
step with the picture, and Neon Coast's three minutes take about two and a half on a desktop.
|
|
|
|
`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.
|