singe/demos/README.md
2026-10-06 16:54:44 -05:00

12 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, 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.

The demos run at 1920 by 1080, the canvas and window in each games.dat. They are laid out in design units, 480 tall and as wide as the screen's shape makes them (853 at 16:9), and the kit turns those into the window's pixels as it draws, K.S (2.25) to a unit: K.draw and K.drawGrid for sprites, K.text and K.font for lettering rendered at full resolution, K.width and K.height for sizes. Art the util scripts draw is made 2.25 pixels to a unit and loaded with K.sprite(path, density), so it draws pixel for pixel; Insert Coin's pixel art is drawn a whole number of screen pixels to each of its own instead, so every art pixel is the same size. A whole-screen 2D layer would cost the CPU milliseconds a frame on a 1920 by 1080 overlay, so those are quads on the GPU: K.fill (a background), K.under (a sprite or a colour), and K.turn (a sprite turned and scaled, which on the overlay would be rebuilt every frame) between the 3D world and the overlay, K.backdrop behind the world, and K.flash and K.cover (scanlines) over everything. Each demo calls K.screen(camera, fov, far) once its camera is made.

Every demo opens on the Singe dragon and lettering, faded up on black, 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. util/demoLogos.sh copies the logos' SVGs from media/ into demos/art/ (the lettering turned near-white for black), and the kit draws them from the SVG at the screen's resolution (K.vector). 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 has left a second and a half before it ends, so the words are finished before the cut. 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.