A six-scene demoscene production that pushes the interpreter to its edges on purpose: an LCG where there is no RND, redrawn strokes where the palette cannot be written, the sprite bank as the scrolltext, a supernova and black hole out of SOLID/COLLISION/RSPPOS, wireframe 3D from two rotations and a perspective divide, and a single-queue beeper-style soundtrack with a noise drum break. The same file runs in the standalone frontend and in the clockless screenshot host, which gets each scene as a still. Three interpreter lessons learned the hard way are recorded in the example's README and TODO.md section 4: a scalar first assigned in a GOSUB dies with its scope, predeclaring scratch runs the 128-slot variable pool dry, and a TI# busy-wait starves the PLAY queue of its one release per frame. Co-Authored-By: Claude Code (Fable 5) <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
1370 lines
32 KiB
QBasic
1370 lines
32 KiB
QBasic
REM =====================================================================
|
|
REM
|
|
REM A K L A B S M E G A D E M O ' 8 5
|
|
REM
|
|
REM CODE AND FX ............. TACHIKOMA
|
|
REM DEDICATED TO ............ GAMECUBE
|
|
REM MEGAGREETZ .............. STARFORT
|
|
REM
|
|
REM A demoscene production for the akbasic interpreter, written as if
|
|
REM it were 1985 and this dialect were the machine under the tree.
|
|
REM It leans on every corner of the interpreter on purpose:
|
|
REM
|
|
REM - there is no RND, so it carries chapter 17's LCG and seeds it
|
|
REM from the jiffy clock
|
|
REM - the palette cannot be rewritten, so every "colour cycle" is an
|
|
REM honest redraw of the same strokes in the next colour
|
|
REM - there are eight sprites, so the lettering IS the sprite bank:
|
|
REM each letter of GAMECUBE is drawn with the stroke font, captured
|
|
REM with SSHAPE and installed with SPRSAV
|
|
REM - SOLID walls and COLLISION 2 bounce that lettering around the
|
|
REM screen with RCOLLISION(n, 7) saying which axis to reverse
|
|
REM - the PLAY queue is one queue -- monophonic, one instrument per
|
|
REM batch (TODO.md 4) -- so the soundtrack is beeper-style: one
|
|
REM interleaved line that is its own kick, bass and arpeggio,
|
|
REM with a noise drum break every third batch. Voice 1 is music,
|
|
REM voice 2 the collision blips, voice 3 the scene sweeps
|
|
REM - 3D wireframes with no matrices: two composed rotations and a
|
|
REM perspective divide per vertex, floats kept on the left the
|
|
REM whole way because the left operand decides the arithmetic
|
|
REM
|
|
REM It probes for its hardware like a proper boot loader. No audio
|
|
REM device mutes it. No text grid drops the status row. A host with no
|
|
REM clock -- tools/screenshot.c -- gets each scene as a still and the
|
|
REM finale as the picture, because every animation loop is gated on
|
|
REM TI# actually moving.
|
|
REM
|
|
REM Q or ESC quits. TODO.md section 4 records what this program wanted
|
|
REM and could not have.
|
|
REM
|
|
REM =====================================================================
|
|
|
|
REM ---- every pool up front: the DIM pool never frees (chapter 13) ----
|
|
DIM FNC#(41)
|
|
DIM FNI#(41)
|
|
DIM FNS#(280)
|
|
DIM RBW#(7)
|
|
DIM STC#(4)
|
|
DIM BARC#(20)
|
|
DIM SBG#(9)
|
|
DIM BYO%(4)
|
|
DIM WVX%(14)
|
|
DIM WVY%(14)
|
|
DIM WVZ%(14)
|
|
DIM WPX#(14)
|
|
DIM WPY#(14)
|
|
DIM WQX#(14)
|
|
DIM WQY#(14)
|
|
DIM WE1#(24)
|
|
DIM WE2#(24)
|
|
|
|
ALPHA$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :-.!"
|
|
GC$ = "GAMECUBE"
|
|
SEED# = 314159
|
|
QF# = 0
|
|
RB# = 0
|
|
PH% = 0
|
|
MT# = 0
|
|
W# = 320
|
|
H# = 200
|
|
|
|
REM A scalar FIRST assigned inside a GOSUB lives in that call's scope
|
|
REM and dies at RETURN, so a subroutine's OUTPUT must be assigned here
|
|
REM first to make it a global -- that is why chapter 18 predeclares.
|
|
REM But only the outputs: there are 128 variable slots in the whole
|
|
REM machine and a predeclared scratch variable occupies one forever,
|
|
REM where a scoped one gives its slot back. The first cut of this
|
|
REM program predeclared everything and ran the pool dry.
|
|
RMAX# = 0
|
|
RND# = 0
|
|
TX% = 0
|
|
TB# = 0
|
|
|
|
REM =====================================================================
|
|
REM Probe the machine. Graphics is mandatory; everything else degrades.
|
|
REM =====================================================================
|
|
GFX# = 1
|
|
TRAP NOGFX
|
|
W# = RGR(1)
|
|
H# = RGR(2)
|
|
TRAP
|
|
IF GFX# = 1 THEN GOTO HAVEGFX
|
|
PRINT "THIS DEMO NEEDS THE SDL BUILD (-DAKBASIC_WITH_AKGL=ON)."
|
|
END
|
|
LABEL HAVEGFX
|
|
|
|
SND# = 1
|
|
TRAP NOSID
|
|
SOUND 1, 17175, 4
|
|
VOL 12
|
|
TRAP
|
|
|
|
TXT# = 1
|
|
TRAP NOGRID
|
|
WINDOW 0, 0, 39, 0
|
|
TRAP
|
|
|
|
KEYS# = 1
|
|
K# = 0
|
|
TRAP NOKEYS
|
|
GET K#
|
|
TRAP
|
|
|
|
REM A host that never sets the clock is the screenshot tool. TI# stays
|
|
REM zero there, so every wait would spin forever: render stills instead.
|
|
HL# = 0
|
|
SLEEP 0.3
|
|
IF TI# = 0 THEN HL# = 1
|
|
SEED# = SEED# + TI#
|
|
|
|
GOSUB LOADTABLES
|
|
GOSUB LOADWIRE
|
|
GOSUB LOADFONT
|
|
IF SND# = 1 THEN GOSUB SIDINIT
|
|
GOSUB TUNE
|
|
IF TXT# = 1 THEN CHAR 1, 0, 0, "AKLABS MEGADEMO 85 - PRESS Q TO EXIT"
|
|
|
|
REM =====================================================================
|
|
REM SCENE 1 : TITLE. Starfield from the LCG, the logo in rainbow,
|
|
REM and the "palette cycle" that is really a redraw.
|
|
REM =====================================================================
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
NS# = 120
|
|
GOSUB STARS
|
|
WIDTH 2
|
|
T$ = "AKLABS"
|
|
SZ% = 0.025 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.12 * H#
|
|
CM# = 1
|
|
GOSUB DRAWTEXT
|
|
CM# = 0
|
|
COLOR 1, 2
|
|
T$ = "PRESENTS"
|
|
SZ% = 0.008 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.45 * H#
|
|
GOSUB DRAWTEXT
|
|
COLOR 1, 4
|
|
T$ = "A 1985 DEMO FOR GAMECUBE"
|
|
SZ% = 0.006 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.58 * H#
|
|
GOSUB DRAWTEXT
|
|
GOSUB SWEEP
|
|
IF HL# = 1 THEN GOTO S1DONE
|
|
T$ = "AKLABS"
|
|
SZ% = 0.025 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.12 * H#
|
|
DL# = TI# + 420
|
|
DO WHILE TI# < DL#
|
|
RB# = RB# + 1
|
|
CM# = 1
|
|
GOSUB DRAWTEXT
|
|
CM# = 0
|
|
GOSUB POLLKEY
|
|
IF QF# = 1 THEN EXIT
|
|
SLEEP 0.12
|
|
LOOP
|
|
LABEL S1DONE
|
|
IF QF# = 1 THEN GOTO BYE
|
|
|
|
REM =====================================================================
|
|
REM SCENE 2 : LISSAJOUS BRAID. Twelve phase-shifted 3:2 curves, drawn
|
|
REM once each -- the drawing layer persists, so the braid accumulates.
|
|
REM =====================================================================
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
CX% = 0.5 * W#
|
|
CY% = 0.5 * H#
|
|
R1% = 0.3 * W#
|
|
R2% = 0.38 * H#
|
|
PN# = 0
|
|
DO WHILE PN# < 12
|
|
COLOR 1, RBW#(MOD(PN#, 7))
|
|
GOSUB SPIRO1
|
|
IF HL# = 0 THEN SLEEP 0.25
|
|
GOSUB POLLKEY
|
|
IF QF# = 1 THEN EXIT
|
|
PN# = PN# + 1
|
|
LOOP
|
|
WIDTH 2
|
|
COLOR 1, 2
|
|
T$ = "TACHIKOMA FX"
|
|
SZ% = 0.007 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.88 * H#
|
|
GOSUB DRAWTEXT
|
|
IF HL# = 0 THEN SLEEP 1.5
|
|
IF QF# = 1 THEN GOTO BYE
|
|
|
|
REM =====================================================================
|
|
REM SCENE 3 : WIREFRAMES. A cube and an octahedron out of one pooled
|
|
REM vertex and edge table -- two composed rotations and a perspective
|
|
REM divide per vertex, no matrices anywhere. Each frame erases the
|
|
REM previous edges in black and draws the new ones, the raster bars'
|
|
REM trick applied to twenty-four lines. A clockless host gets five
|
|
REM exposures without the erase: a tumble, as a still.
|
|
REM
|
|
REM The way out is the way in: the solids drift together, the point
|
|
REM they share ignites -- pulsing through the bar ramp table, because
|
|
REM it is the origin of the next scene -- the camera dives through
|
|
REM the frames as the perspective scale outgrows the window, and the
|
|
REM origin splits in two, rides the Y axis to the bars' band, and
|
|
REM stretches into the first two raster bars itself.
|
|
REM =====================================================================
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
NS# = 90
|
|
GOSUB STARS
|
|
WIDTH 2
|
|
COLOR 1, 2
|
|
T$ = "TWO SOLIDS - NO MATRICES - PURE SIN"
|
|
SZ% = 0.0045 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.06 * H#
|
|
GOSUB DRAWTEXT
|
|
SF% = 0.24 * H#
|
|
CY% = 0.55 * H#
|
|
A1% = 0.4
|
|
B1% = 0.9
|
|
A2% = 1.1
|
|
B2% = 0.3
|
|
FC# = 0
|
|
IF HL# = 0 THEN GOTO WIRELIVE
|
|
PN# = 0
|
|
DO WHILE PN# < 5
|
|
RA% = A1%
|
|
RB% = B1%
|
|
CX% = 0.3 * W#
|
|
VS# = 0
|
|
VE# = 8
|
|
ES# = 0
|
|
EE# = 12
|
|
GOSUB WIRESPIN
|
|
COLOR 1, RBW#(PN#)
|
|
GOSUB WIREDRAW
|
|
RA% = A2%
|
|
RB% = B2%
|
|
CX% = 0.7 * W#
|
|
VS# = 8
|
|
VE# = 14
|
|
ES# = 12
|
|
EE# = 24
|
|
GOSUB WIRESPIN
|
|
GOSUB WIREDRAW
|
|
A1% = A1% + 0.3
|
|
B1% = B1% + 0.19
|
|
A2% = A2% - 0.26
|
|
B2% = B2% + 0.17
|
|
PN# = PN# + 1
|
|
LOOP
|
|
GOTO WIREDONE
|
|
LABEL WIRELIVE
|
|
C1X% = 0.3 * W#
|
|
C2X% = 0.7 * W#
|
|
DL# = TI# + 360
|
|
DO WHILE TI# < DL#
|
|
GOSUB SPINSTEP
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
IF QF# = 1 THEN EXIT
|
|
GOSUB VSYNC
|
|
LOOP
|
|
IF QF# = 1 THEN GOTO WIREDONE
|
|
REM Converge: the two solids drift to the shared centre, spinning
|
|
REM the whole way in.
|
|
XS1% = (0.2 * W#) / 36
|
|
GF# = 0
|
|
DO WHILE GF# < 36
|
|
C1X% = C1X% + XS1%
|
|
C2X% = C2X% - XS1%
|
|
GOSUB SPINSTEP
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
GOSUB VSYNC
|
|
GF# = GF# + 1
|
|
LOOP
|
|
C1X% = 0.5 * W#
|
|
C2X% = 0.5 * W#
|
|
REM The origin point ignites inside them.
|
|
BF# = 0
|
|
PB% = 0
|
|
BBR% = 22.0
|
|
GF# = 0
|
|
DO WHILE GF# < 40
|
|
GOSUB SPINSTEP
|
|
GOSUB BALLFX
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
GOSUB VSYNC
|
|
GF# = GF# + 1
|
|
LOOP
|
|
REM The camera goes in: the perspective scale grows past the window
|
|
REM and the solids pass out of view around us. The drawing verbs
|
|
REM clip silently, so the coordinates are allowed to leave.
|
|
GOSUB ZOOMSND
|
|
GF# = 0
|
|
DO WHILE GF# < 26
|
|
SF% = 1.14 * SF%
|
|
BBR% = BBR% + 1.2
|
|
GOSUB SPINSTEP
|
|
GOSUB BALLFX
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
GOSUB VSYNC
|
|
GF# = GF# + 1
|
|
LOOP
|
|
ES# = 0
|
|
EE# = 24
|
|
GOSUB WIRERASE
|
|
REM The origin splits; each half rides the Y axis out to where the
|
|
REM bars are about to be born.
|
|
IF SND# = 1 THEN SOUND 2, 30000, 4
|
|
YO% = 0
|
|
GF# = 0
|
|
DO WHILE GF# < 18
|
|
DCI# = 1
|
|
DR% = PB%
|
|
DCX% = C1X%
|
|
DCY% = CY% - YO%
|
|
GOSUB DISC
|
|
DCY% = CY% + YO%
|
|
GOSUB DISC
|
|
YO% = YO% + 4.4
|
|
IF YO% > 70 THEN YO% = 70
|
|
BF# = BF# + 1
|
|
DR% = 16.0 + (6.0 * SIN(1.1 * BF#))
|
|
DCI# = BARC#(MOD(BF#, 20))
|
|
DCY% = CY% - YO%
|
|
GOSUB DISC
|
|
DCI# = BARC#(MOD(BF# + 10, 20))
|
|
DCY% = CY% + YO%
|
|
GOSUB DISC
|
|
PB% = DR%
|
|
GOSUB POLLKEY
|
|
GOSUB VSYNC
|
|
GF# = GF# + 1
|
|
LOOP
|
|
DCI# = 1
|
|
DR% = PB%
|
|
DCY% = CY% - YO%
|
|
GOSUB DISC
|
|
DCY% = CY% + YO%
|
|
GOSUB DISC
|
|
REM Each ball shoots out to the full width of the screen and is a
|
|
REM raster bar by the time it gets there.
|
|
XR% = 4.0
|
|
DO WHILE XR% < (0.5 * W#)
|
|
BY% = (CY% - YO%) - 9
|
|
BR# = 0
|
|
GOSUB PROTOBAR
|
|
BY% = (CY% + YO%) - 9
|
|
BR# = 5
|
|
GOSUB PROTOBAR
|
|
XR% = XR% + (0.045 * W#)
|
|
GOSUB POLLKEY
|
|
GOSUB VSYNC
|
|
LOOP
|
|
SLEEP 0.3
|
|
LABEL WIREDONE
|
|
IF QF# = 1 THEN GOTO BYE
|
|
|
|
REM =====================================================================
|
|
REM SCENE 4 : RASTER BARS. The palette is not writable, so these are
|
|
REM not palette tricks: every frame erases four bars in black and
|
|
REM redraws them two scanlines at a time. Racing TI# is the beam.
|
|
REM =====================================================================
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
NS# = 90
|
|
GOSUB STARS
|
|
WIDTH 2
|
|
COLOR 1, 2
|
|
T$ = "NO PALETTE WRITES - EVERY CYCLE REDRAWN"
|
|
SZ% = 0.0045 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.06 * H#
|
|
GOSUB DRAWTEXT
|
|
CY3% = 0.55 * H#
|
|
IF HL# = 0 THEN GOTO BARLIVE
|
|
BI# = 0
|
|
DO WHILE BI# < 4
|
|
BY% = CY3% + (70.0 * SIN(1.5 * BI#))
|
|
BR# = BI# * 5
|
|
EB# = 0
|
|
GOSUB DRAWBAR
|
|
BI# = BI# + 1
|
|
LOOP
|
|
GOTO BARDONE
|
|
LABEL BARLIVE
|
|
DL# = TI# + 480
|
|
DO WHILE TI# < DL#
|
|
BI# = 0
|
|
DO WHILE BI# < 4
|
|
BY% = BYO%(BI#)
|
|
BR# = BI# * 5
|
|
EB# = 1
|
|
GOSUB DRAWBAR
|
|
BI# = BI# + 1
|
|
LOOP
|
|
PH% = PH% + 0.13
|
|
BI# = 0
|
|
DO WHILE BI# < 4
|
|
BY% = CY3% + (70.0 * SIN(PH% + (1.5 * BI#)))
|
|
BR# = BI# * 5
|
|
EB# = 0
|
|
GOSUB DRAWBAR
|
|
BYO%(BI#) = BY%
|
|
BI# = BI# + 1
|
|
LOOP
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
IF QF# = 1 THEN EXIT
|
|
GOSUB VSYNC
|
|
LOOP
|
|
LABEL BARDONE
|
|
IF QF# = 1 THEN GOTO BYE
|
|
|
|
REM =====================================================================
|
|
REM SCENE 5 : THE SPRITE BANK IS THE SCROLLTEXT, AND THEN IT EXPLODES.
|
|
REM Eight letters, eight sprites: draw each with the stroke font,
|
|
REM SSHAPE it, SPRSAV it, surf it on a sine. Then the letters fall
|
|
REM into the middle, a supernova throws them at the walls -- SOLID
|
|
REM and COLLISION 2 do the physics while BASIC reverses a bearing --
|
|
REM and a black hole drags everything back to the centre as the
|
|
REM transition out. A clockless host gets the wave over a frozen
|
|
REM fireball as its still.
|
|
REM =====================================================================
|
|
WIDTH 2
|
|
SZ% = 7
|
|
L# = 1
|
|
DO WHILE L# < 9
|
|
GRAPHIC 1, 1
|
|
WIDTH 2
|
|
COLOR 1, 2
|
|
C$ = MID(GC$, L# - 1, 1)
|
|
GI# = INSTR(ALPHA$, C$)
|
|
GX2% = 10
|
|
GY2% = 10
|
|
GOSUB DRAWGLYPH
|
|
SSHAPE SH$, 6, 6, 42, 56
|
|
SPRSAV SH$, L#
|
|
SPRITE L#, 1, RBW#(MOD(L# - 1, 7))
|
|
L# = L# + 1
|
|
LOOP
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
NS# = 100
|
|
GOSUB STARS
|
|
WIDTH 2
|
|
SPX% = 0.055 * W#
|
|
WX0% = 0.5 * (W# - (SPX% * 8))
|
|
WCY% = 0.5 * H#
|
|
AMP% = 60.0
|
|
GOSUB WAVE
|
|
CX0% = 0.5 * W#
|
|
CY0% = 0.5 * H#
|
|
PR% = 0
|
|
IF HL# = 0 THEN GOTO SNLIVE
|
|
ER% = 130
|
|
GOSUB BOOMFX
|
|
GOTO WAVEDONE
|
|
LABEL SNLIVE
|
|
DL# = TI# + 420
|
|
DO WHILE TI# < DL#
|
|
PH% = PH% + 0.15
|
|
GOSUB WAVE
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
IF QF# = 1 THEN EXIT
|
|
GOSUB VSYNC
|
|
LOOP
|
|
IF QF# = 1 THEN GOTO BYE
|
|
COLOR 1, 2
|
|
T$ = "EIGHT SPRITES - ONE SUPERNOVA - ZERO MATHS"
|
|
SZ% = 0.0045 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.03 * H#
|
|
GOSUB DRAWTEXT
|
|
REM Wind-up: the letters fall into the middle on a slow spiral.
|
|
VC% = COS(0.1)
|
|
VS2% = SIN(0.1)
|
|
VF% = 0.78
|
|
GF# = 0
|
|
DO WHILE GF# < 14
|
|
GOSUB VORTEX
|
|
GOSUB VSYNC
|
|
GF# = GF# + 1
|
|
LOOP
|
|
REM The supernova. Every letter leaves the centre on its own bearing,
|
|
REM eight of them fanned evenly around the compass, fast, while the
|
|
REM fireball expands around them and the walls wait to ring.
|
|
GOSUB BOOMSND
|
|
SOLID 61, 0, 0, W# - 1, 12
|
|
SOLID 62, 0, H# - 13, W# - 1, H# - 1
|
|
SOLID 63, 0, 0, 12, H# - 1
|
|
SOLID 64, W# - 13, 0, W# - 1, H# - 1
|
|
COLLISION 2, BOUNCE
|
|
L# = 1
|
|
DO WHILE L# < 9
|
|
SBG#(L#) = MOD((L# * 45) + 23, 360)
|
|
MOVSPR L#, CX0%, CY0%
|
|
MOVSPR L#, SBG#(L#) # 14
|
|
L# = L# + 1
|
|
LOOP
|
|
ER% = 16
|
|
DO WHILE ER% < 430
|
|
GOSUB BOOMFX
|
|
ER% = (1.18 * ER%) + 6
|
|
GOSUB POLLKEY
|
|
IF QF# = 1 THEN EXIT
|
|
GOSUB VSYNC
|
|
LOOP
|
|
BCF# = 1
|
|
BR2% = PR%
|
|
GOSUB BOOMPASS
|
|
REM Debris. Let them ricochet for a while.
|
|
DL# = TI# + 300
|
|
DO WHILE TI# < DL#
|
|
GOSUB SHEPHERD
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
IF QF# = 1 THEN EXIT
|
|
SLEEP 0.05
|
|
LOOP
|
|
COLLISION 2
|
|
SOLID
|
|
L# = 1
|
|
DO WHILE L# < 9
|
|
MOVSPR L#, 0 # 0
|
|
L# = L# + 1
|
|
LOOP
|
|
IF QF# = 1 THEN GOTO WAVEDONE
|
|
REM The black hole. An iris of collapsing black circles swallows the
|
|
REM screen from the outside in, the accretion arcs spin over the top,
|
|
REM and the vortex drags every letter into the middle. The clear at
|
|
REM the end is the event horizon closing over the corners.
|
|
GOSUB HOLESND
|
|
VC% = COS(0.26)
|
|
VS2% = SIN(0.26)
|
|
VF% = 0.86
|
|
HA0# = 0
|
|
IR% = 470
|
|
HF# = 0
|
|
DO WHILE HF# < 34
|
|
GOSUB HOLEFX
|
|
IR% = IR% - 14
|
|
IF IR% < 24 THEN IR% = 24
|
|
HAC# = 0
|
|
GOSUB HOLEARC
|
|
HA0# = MOD(HA0# + 26, 360)
|
|
IF IR% > 70 THEN HAC# = 1
|
|
IF IR% > 70 THEN GOSUB HOLEARC
|
|
GOSUB VORTEX
|
|
GOSUB POLLKEY
|
|
GOSUB VSYNC
|
|
HF# = HF# + 1
|
|
LOOP
|
|
GRAPHIC 1, 1
|
|
LABEL WAVEDONE
|
|
IF QF# = 1 THEN GOTO BYE
|
|
|
|
REM =====================================================================
|
|
REM SCENE 6 : THE FINALE CARD. Everything at once: stars, bars, the
|
|
REM logo, the greetz, and the sprite bank surfing a sine. This is the
|
|
REM frame a clockless host leaves on the screen.
|
|
REM =====================================================================
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
NS# = 150
|
|
GOSUB STARS
|
|
WIDTH 2
|
|
EB# = 0
|
|
BY% = 26
|
|
BR# = 10
|
|
GOSUB DRAWBAR
|
|
BY% = H# - 24
|
|
BR# = 5
|
|
GOSUB DRAWBAR
|
|
T$ = "AKLABS"
|
|
SZ% = 0.02 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.07 * H#
|
|
CM# = 1
|
|
GOSUB DRAWTEXT
|
|
CM# = 0
|
|
COLOR 1, 2
|
|
T$ = "MEGADEMO 85"
|
|
SZ% = 0.009 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.32 * H#
|
|
GOSUB DRAWTEXT
|
|
COLOR 1, 4
|
|
T$ = "CODE AND FX : TACHIKOMA"
|
|
SZ% = 0.004 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.72 * H#
|
|
GOSUB DRAWTEXT
|
|
COLOR 1, 14
|
|
T$ = "DEDICATED TO GAMECUBE"
|
|
GOSUB CENTERX
|
|
TY% = 0.77 * H#
|
|
GOSUB DRAWTEXT
|
|
COLOR 1, 8
|
|
T$ = "MEGAGREETZ TO STARFORT - KEEP THE FORGE LIT"
|
|
GOSUB CENTERX
|
|
TY% = 0.82 * H#
|
|
GOSUB DRAWTEXT
|
|
COLOR 1, 16
|
|
T$ = "100 PCT BASIC - NO MACHINE CODE"
|
|
GOSUB CENTERX
|
|
TY% = 0.88 * H#
|
|
GOSUB DRAWTEXT
|
|
WCY% = 0.55 * H#
|
|
AMP% = 40.0
|
|
PH% = 1.0
|
|
GOSUB WAVE
|
|
IF HL# = 1 THEN GOTO BYE
|
|
T$ = "AKLABS"
|
|
SZ% = 0.02 * W#
|
|
GOSUB CENTERX
|
|
TY% = 0.07 * H#
|
|
DO WHILE QF# = 0
|
|
RB# = RB# + 1
|
|
CM# = 1
|
|
GOSUB DRAWTEXT
|
|
CM# = 0
|
|
PH% = PH% + 0.1
|
|
GOSUB WAVE
|
|
IF TI# > MT# THEN GOSUB TUNE
|
|
GOSUB POLLKEY
|
|
SLEEP 0.1
|
|
LOOP
|
|
GOTO BYE
|
|
|
|
REM =====================================================================
|
|
REM Shutdown: disarm the handler, retire the rectangles, mute the SID.
|
|
REM The picture stays -- that is what END is for.
|
|
REM =====================================================================
|
|
LABEL BYE
|
|
COLLISION 2
|
|
SOLID
|
|
IF SND# = 1 THEN VOL 0
|
|
IF TXT# = 1 THEN CHAR 1, 0, 0, "AKLABS 85 - KEEP THE SCENE ALIVE "
|
|
END
|
|
|
|
REM =====================================================================
|
|
REM Subroutines.
|
|
REM =====================================================================
|
|
|
|
REM Chapter 17's generator, verbatim: there is no RND in this dialect.
|
|
REM Answers 0 to RMAX#-1 in RND#, from the middle bits of the seed.
|
|
LABEL RANDOM
|
|
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
|
RND# = MOD((SEED# / 65536), RMAX#)
|
|
RETURN
|
|
|
|
REM NS# stars in four brightnesses. Two LCG pulls a star, and the
|
|
REM brightness rides the low bits of the seed for free.
|
|
LABEL STARS
|
|
SI# = 0
|
|
DO WHILE SI# < NS#
|
|
RMAX# = W#
|
|
GOSUB RANDOM
|
|
X# = RND#
|
|
RMAX# = H#
|
|
GOSUB RANDOM
|
|
Y# = RND#
|
|
B# = MOD(SEED#, 4)
|
|
COLOR 1, STC#(B#)
|
|
DRAW 1, X#, Y#
|
|
SI# = SI# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM One Lissajous pass: 3:2, phase-shifted 25 degrees per PN#. The
|
|
REM first point is plotted bare, the rest chain with TO.
|
|
LABEL SPIRO1
|
|
PX% = CX% + (R1% * SIN(RAD(25 * PN#)))
|
|
PY% = CY% + R2%
|
|
DRAW 1, PX%, PY%
|
|
FOR T# = 8 TO 360 STEP 8
|
|
X% = CX% + (R1% * SIN(RAD((3 * T#) + (25 * PN#))))
|
|
Y% = CY% + (R2% * COS(RAD(2 * T#)))
|
|
DRAW 1, PX%, PY% TO X%, Y%
|
|
PX% = X%
|
|
PY% = Y%
|
|
NEXT T#
|
|
RETURN
|
|
|
|
REM Centre T$ at scale SZ%: a glyph cell is five units wide.
|
|
LABEL CENTERX
|
|
LW% = SZ% * 5
|
|
TW% = LW% * LEN(T$)
|
|
TX% = 0.5 * (W# - TW%)
|
|
RETURN
|
|
|
|
REM Draw T$ at TX%, TY%, scale SZ%. CM# = 1 paints each letter from
|
|
REM the rainbow table, offset by RB# -- the redraw that stands in for
|
|
REM the palette cycle this machine does not have.
|
|
LABEL DRAWTEXT
|
|
TL# = LEN(T$)
|
|
TC# = 0
|
|
DO WHILE TC# < TL#
|
|
C$ = MID(T$, TC#, 1)
|
|
GI# = INSTR(ALPHA$, C$)
|
|
IF CM# = 1 THEN COLOR 1, RBW#(MOD(TC# + RB#, 7))
|
|
LW% = SZ% * 5
|
|
GX2% = TX% + (LW% * TC#)
|
|
GY2% = TY%
|
|
IF GI# >= 0 THEN GOSUB DRAWGLYPH
|
|
TC# = TC# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM One glyph. A DO rather than a FOR: single-stroke glyphs exist and
|
|
REM FOR 1 TO 1 never runs its body (TODO.md section 6).
|
|
LABEL DRAWGLYPH
|
|
GN# = FNC#(GI#)
|
|
IF GN# = 0 THEN RETURN
|
|
GP# = FNI#(GI#)
|
|
GK# = 0
|
|
DO WHILE GK# < GN#
|
|
P1# = FNS#(GP#)
|
|
P2# = FNS#(GP# + 1)
|
|
GP# = GP# + 2
|
|
X1% = GX2% + (SZ% * (P1# / 10))
|
|
Y1% = GY2% + (SZ% * MOD(P1#, 10))
|
|
X2% = GX2% + (SZ% * (P2# / 10))
|
|
Y2% = GY2% + (SZ% * MOD(P2#, 10))
|
|
DRAW 1, X1%, Y1% TO X2%, Y2%
|
|
GK# = GK# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM One raster bar: a five-step ramp, two double-width lines per step
|
|
REM for a twenty-pixel bar -- or all black when EB# says erase.
|
|
LABEL DRAWBAR
|
|
BL# = 0
|
|
DO WHILE BL# < 5
|
|
CI# = BARC#(BR# + BL#)
|
|
IF EB# = 1 THEN CI# = 1
|
|
COLOR 1, CI#
|
|
BLY% = BY% + (BL# * 4)
|
|
DRAW 1, 0, BLY% TO W# - 1, BLY%
|
|
BLY% = BLY% + 2
|
|
DRAW 1, 0, BLY% TO W# - 1, BLY%
|
|
BL# = BL# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM The wireframe engine. One pooled model: cube vertices 0-7 and
|
|
REM octahedron vertices 8-13, edge list indexing into the pool.
|
|
REM VS#/VE# and ES#/EE# select the object, CX%/CY% place it, SF%
|
|
REM scales it, RA%/RB% are its two rotation angles.
|
|
REM =====================================================================
|
|
|
|
REM Rotate the selected vertices about X then Y and project them.
|
|
REM Every product keeps the float on the left -- the left operand
|
|
REM decides the arithmetic, and one integer would floor the trig.
|
|
LABEL WIRESPIN
|
|
CS1% = COS(RA%)
|
|
SN1% = SIN(RA%)
|
|
CS2% = COS(RB%)
|
|
SN2% = SIN(RB%)
|
|
V# = VS#
|
|
DO WHILE V# < VE#
|
|
RY% = (WVY%(V#) * CS1%) - (WVZ%(V#) * SN1%)
|
|
RZ% = (WVY%(V#) * SN1%) + (WVZ%(V#) * CS1%)
|
|
RX% = (WVX%(V#) * CS2%) + (RZ% * SN2%)
|
|
RZ% = (RZ% * CS2%) - (WVX%(V#) * SN2%)
|
|
PJ% = SF% / (3.2 + RZ%)
|
|
WPX#(V#) = CX% + (RX% * PJ%)
|
|
WPY#(V#) = CY% + (RY% * PJ%)
|
|
V# = V# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM Draw the selected edges from the projected points. The caller has
|
|
REM already bound COLOR 1, so a rainbow costs nothing extra.
|
|
LABEL WIREDRAW
|
|
E# = ES#
|
|
DO WHILE E# < EE#
|
|
V1# = WE1#(E#)
|
|
V2# = WE2#(E#)
|
|
DRAW 1, WPX#(V1#), WPY#(V1#) TO WPX#(V2#), WPY#(V2#)
|
|
E# = E# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM Draw the previous frame's edges in black. The first frame erases
|
|
REM a degenerate point at the origin, which costs one black pixel.
|
|
LABEL WIRERASE
|
|
COLOR 1, 1
|
|
E# = ES#
|
|
DO WHILE E# < EE#
|
|
V1# = WE1#(E#)
|
|
V2# = WE2#(E#)
|
|
DRAW 1, WQX#(V1#), WQY#(V1#) TO WQX#(V2#), WQY#(V2#)
|
|
E# = E# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM Remember this frame's points so the next frame can erase them.
|
|
LABEL WIRESAVE
|
|
V# = VS#
|
|
DO WHILE V# < VE#
|
|
WQX#(V#) = WPX#(V#)
|
|
WQY#(V#) = WPY#(V#)
|
|
V# = V# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
LABEL CUBETICK
|
|
VS# = 0
|
|
VE# = 8
|
|
ES# = 0
|
|
EE# = 12
|
|
GOSUB WIRERASE
|
|
RA% = A1%
|
|
RB% = B1%
|
|
CX% = C1X%
|
|
GOSUB WIRESPIN
|
|
COLOR 1, RBW#(MOD(FC# / 8, 7))
|
|
GOSUB WIREDRAW
|
|
GOSUB WIRESAVE
|
|
RETURN
|
|
|
|
LABEL OCTATICK
|
|
VS# = 8
|
|
VE# = 14
|
|
ES# = 12
|
|
EE# = 24
|
|
GOSUB WIRERASE
|
|
RA% = A2%
|
|
RB% = B2%
|
|
CX% = C2X%
|
|
GOSUB WIRESPIN
|
|
COLOR 1, RBW#(MOD((FC# / 8) + 3, 7))
|
|
GOSUB WIREDRAW
|
|
GOSUB WIRESAVE
|
|
RETURN
|
|
|
|
REM One frame of both solids: advance the four angles, tick each.
|
|
LABEL SPINSTEP
|
|
FC# = FC# + 1
|
|
A1% = A1% + 0.061
|
|
B1% = B1% + 0.043
|
|
A2% = A2% - 0.053
|
|
B2% = B2% + 0.037
|
|
GOSUB CUBETICK
|
|
GOSUB OCTATICK
|
|
RETURN
|
|
|
|
REM A ball of light: concentric circles three pixels apart, solid at
|
|
REM WIDTH 2. DCX%/DCY% centre it, DR% sizes it, DCI# colours it --
|
|
REM DCI# = 1 is the eraser.
|
|
LABEL DISC
|
|
COLOR 1, DCI#
|
|
R3% = DR%
|
|
DO WHILE R3% > 1
|
|
CIRCLE 1, DCX%, DCY%, R3%, R3%
|
|
R3% = R3% - 3
|
|
LOOP
|
|
RETURN
|
|
|
|
REM The origin point: erase it where it was, redraw it pulsing on
|
|
REM SIN(BF#) and strobing through the bar ramp table -- the colours
|
|
REM of the next scene, because this is where its bars come from.
|
|
LABEL BALLFX
|
|
IF PB% <= 0 THEN GOTO BALLFX2
|
|
DCI# = 1
|
|
DR% = PB%
|
|
DCX% = C1X%
|
|
DCY% = CY%
|
|
GOSUB DISC
|
|
LABEL BALLFX2
|
|
DR% = BBR% + ((0.45 * BBR%) * SIN(1.1 * BF#))
|
|
DCI# = BARC#(MOD(BF#, 20))
|
|
DCX% = C1X%
|
|
DCY% = CY%
|
|
GOSUB DISC
|
|
PB% = DR%
|
|
BF# = BF# + 1
|
|
RETURN
|
|
|
|
REM A raster bar being born: the same five-step ramp as DRAWBAR, but
|
|
REM spanning only C1X% plus and minus XR%. Each frame's lines cover
|
|
REM the last frame's, so the stretch needs no eraser.
|
|
LABEL PROTOBAR
|
|
BL# = 0
|
|
DO WHILE BL# < 5
|
|
COLOR 1, BARC#(BR# + BL#)
|
|
PX1% = C1X% - XR%
|
|
PX2% = C1X% + XR%
|
|
BLY% = BY% + (BL# * 4)
|
|
DRAW 1, PX1%, BLY% TO PX2%, BLY%
|
|
BLY% = BLY% + 2
|
|
DRAW 1, PX1%, BLY% TO PX2%, BLY%
|
|
BL# = BL# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM The approach: one long sweep rising as the camera goes in.
|
|
LABEL ZOOMSND
|
|
IF SND# = 0 THEN RETURN
|
|
SOUND 3, 4000, 40, 1, 52000, 800
|
|
RETURN
|
|
|
|
REM Park the eight letter sprites on a sine.
|
|
LABEL WAVE
|
|
L# = 1
|
|
DO WHILE L# < 9
|
|
WX% = WX0% + (SPX% * (L# - 1))
|
|
WY% = WCY% + (AMP% * SIN(PH% + (0.8 * L#)))
|
|
MOVSPR L#, WX%, WY%
|
|
L# = L# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM The supernova and the black hole.
|
|
REM =====================================================================
|
|
|
|
REM Pull every letter one step around and toward (CX0%, CY0%): read
|
|
REM its position back, rotate the offset by the angle in VC%/VS2%,
|
|
REM shrink it by VF%, put it there. Fourteen frames of 0.78 is a
|
|
REM wind-up; thirty-four of 0.86 is an event horizon.
|
|
LABEL VORTEX
|
|
L# = 1
|
|
DO WHILE L# < 9
|
|
SX% = RSPPOS(L#, 0) - CX0%
|
|
SY% = RSPPOS(L#, 1) - CY0%
|
|
NX% = ((SX% * VC%) - (SY% * VS2%)) * VF%
|
|
NY% = ((SX% * VS2%) + (SY% * VC%)) * VF%
|
|
MOVSPR L#, CX0% + NX%, CY0% + NY%
|
|
L# = L# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM One frame of fireball: erase the previous radius, draw the new
|
|
REM one. PR% carries the radius to erase between calls.
|
|
LABEL BOOMFX
|
|
IF PR% <= 0 THEN GOTO BOOMFX2
|
|
BCF# = 1
|
|
BR2% = PR%
|
|
GOSUB BOOMPASS
|
|
LABEL BOOMFX2
|
|
BCF# = 0
|
|
BR2% = ER%
|
|
GOSUB BOOMPASS
|
|
PR% = ER%
|
|
RETURN
|
|
|
|
REM The fireball at radius BR2%: three rings, white heart to orange
|
|
REM rim, and twelve red spokes thrown past the outer ring. BCF# = 1
|
|
REM draws the whole thing in black, which is the eraser.
|
|
LABEL BOOMPASS
|
|
R3% = BR2%
|
|
IF BCF# = 1 THEN COLOR 1, 1 ELSE COLOR 1, 2
|
|
CIRCLE 1, CX0%, CY0%, R3%, R3%
|
|
R4% = 0.72 * R3%
|
|
IF BCF# = 1 THEN COLOR 1, 1 ELSE COLOR 1, 8
|
|
CIRCLE 1, CX0%, CY0%, R4%, R4%
|
|
R4% = 0.45 * R3%
|
|
IF BCF# = 1 THEN COLOR 1, 1 ELSE COLOR 1, 9
|
|
CIRCLE 1, CX0%, CY0%, R4%, R4%
|
|
IF BCF# = 1 THEN COLOR 1, 1 ELSE COLOR 1, 3
|
|
SK# = 0
|
|
DO WHILE SK# < 12
|
|
SA% = RAD((SK# * 30) + 15)
|
|
X1B% = CX0% + ((0.55 * R3%) * SIN(SA%))
|
|
Y1B% = CY0% - ((0.55 * R3%) * COS(SA%))
|
|
X2B% = CX0% + ((1.14 * R3%) * SIN(SA%))
|
|
Y2B% = CY0% - ((1.14 * R3%) * COS(SA%))
|
|
DRAW 1, X1B%, Y1B% TO X2B%, Y2B%
|
|
SK# = SK# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM One frame of iris: ten black circles two pixels apart, WIDTH 2,
|
|
REM a closed band from IR% outward that the loop walks inward.
|
|
LABEL HOLEFX
|
|
COLOR 1, 1
|
|
HC# = 0
|
|
R3% = IR%
|
|
DO WHILE HC# < 10
|
|
CIRCLE 1, CX0%, CY0%, R3%, R3%
|
|
R3% = R3% + 2
|
|
HC# = HC# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM The accretion disc: three arcs at three radii, rotated to HA0#.
|
|
REM HAC# = 0 is the eraser, drawn at the angle before the turn.
|
|
LABEL HOLEARC
|
|
IF HAC# = 1 THEN COLOR 1, 5 ELSE COLOR 1, 1
|
|
CIRCLE 1, CX0%, CY0%, 58, 58, HA0#, HA0# + 130
|
|
IF HAC# = 1 THEN COLOR 1, 13 ELSE COLOR 1, 1
|
|
CIRCLE 1, CX0%, CY0%, 40, 40, HA0# + 120, HA0# + 250
|
|
IF HAC# = 1 THEN COLOR 1, 7 ELSE COLOR 1, 1
|
|
CIRCLE 1, CX0%, CY0%, 25, 25, HA0# + 240, HA0# + 370
|
|
RETURN
|
|
|
|
REM The bang: a long rumble falling to the floor on voice 2 and a
|
|
REM crack sweeping down over it on voice 3.
|
|
LABEL BOOMSND
|
|
IF SND# = 0 THEN RETURN
|
|
SOUND 2, 9000, 45, 2, 150, 350
|
|
SOUND 3, 52000, 30, 2, 900, 1400
|
|
RETURN
|
|
|
|
REM The suck: one long sweep rising off the top of the register.
|
|
LABEL HOLESND
|
|
IF SND# = 0 THEN RETURN
|
|
SOUND 3, 1500, 55, 1, 58000, 950
|
|
RETURN
|
|
|
|
REM COLLISION 2 lands here between lines. BUMP(2) says who, and
|
|
REM RCOLLISION(n, 7) says which axis -- reflecting a bearing is one
|
|
REM subtraction per axis, done in BOUNCE1.
|
|
LABEL BOUNCE
|
|
BM# = BUMP(2)
|
|
BS# = 1
|
|
DO WHILE BS# < 9
|
|
BB# = SHL(1, BS# - 1)
|
|
IF (BM# AND BB#) <> 0 THEN GOSUB BOUNCE1
|
|
BS# = BS# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
LABEL BOUNCE1
|
|
AX# = RCOLLISION(BS#, 7)
|
|
IF AX# = 1 THEN SBG#(BS#) = MOD(720 - SBG#(BS#), 360)
|
|
IF AX# = 2 THEN SBG#(BS#) = MOD(540 - SBG#(BS#), 360)
|
|
MOVSPR BS#, SBG#(BS#) # 5
|
|
IF SND# = 1 THEN SOUND 2, 38000, 2
|
|
RETURN
|
|
|
|
REM A continuous mover can tunnel a thin wall between handler entries.
|
|
REM Anyone who escapes is rehomed to the centre with a fresh bearing.
|
|
LABEL SHEPHERD
|
|
L2# = 1
|
|
DO WHILE L2# < 9
|
|
SX% = RSPPOS(L2#, 0)
|
|
SY% = RSPPOS(L2#, 1)
|
|
OUT# = 0
|
|
IF SX% < 2 THEN OUT# = 1
|
|
IF SX% > W# - 40 THEN OUT# = 1
|
|
IF SY% < 2 THEN OUT# = 1
|
|
IF SY% > H# - 55 THEN OUT# = 1
|
|
IF OUT# = 1 THEN GOSUB REHOME
|
|
L2# = L2# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
LABEL REHOME
|
|
MOVSPR L2#, 0.5 * W#, 0.5 * H#
|
|
RMAX# = 360
|
|
GOSUB RANDOM
|
|
SBG#(L2#) = RND#
|
|
MOVSPR L2#, SBG#(L2#) # 5
|
|
RETURN
|
|
|
|
REM Hold for about a frame. The first cut spun on TI# -- the closest
|
|
REM thing this machine has to a raster wait -- and the spin starved
|
|
REM the music: the host releases at most ONE queued PLAY note per
|
|
REM frame, and a frame is 256 *executed* statements, so a busy-wait
|
|
REM makes frames slow enough to drop the release rate under the
|
|
REM tune's fifteen notes a second. The queue backed up and overflowed
|
|
REM two scenes later. SLEEP holds the program at no per-step cost, so
|
|
REM the frames stay fast and the music drains on time. TODO.md
|
|
REM section 4 carries the measurement.
|
|
LABEL VSYNC
|
|
IF HL# = 1 THEN RETURN
|
|
SLEEP 0.02
|
|
RETURN
|
|
|
|
LABEL POLLKEY
|
|
IF KEYS# = 0 THEN RETURN
|
|
GET K#
|
|
IF K# = 113 THEN QF# = 1
|
|
IF K# = 27 THEN QF# = 1
|
|
RETURN
|
|
|
|
LABEL SWEEP
|
|
IF SND# = 0 THEN RETURN
|
|
SOUND 3, 62000, 16, 2, 5000, 700
|
|
RETURN
|
|
|
|
REM ENVELOPE's sixth argument is a waveform -- 0 triangle, 1 sawtooth,
|
|
REM 2 square, 3 noise -- which Chapter 7 does not mention. Preset 3 is
|
|
REM the sawtooth lead and preset 4 the noise drum kit.
|
|
LABEL SIDINIT
|
|
ENVELOPE 3, 0, 5, 9, 1, 1
|
|
ENVELOPE 4, 0, 6, 3, 1, 3
|
|
VOL 12
|
|
TEMPO 15
|
|
RETURN
|
|
|
|
REM The PLAY queue is one queue: each note holds it for its length,
|
|
REM and the envelope applied when a note sounds is whatever the last
|
|
REM T parsed selected -- so naming three voices plays three tracks
|
|
REM end to end with one instrument, not a chord (TODO.md section 4).
|
|
REM So this is beeper music, the way single-channel tracker tunes
|
|
REM were written: sixteenths that take turns being the kick (the O1
|
|
REM root), the pumping bass (O2/O3) and the rolling arpeggio (O4 up).
|
|
REM Batch A walks Am F C G, batch B turns Am F Dm E with the G sharp,
|
|
REM and every third batch is a bar of noise drums on preset 4.
|
|
REM
|
|
REM There is no way to ask whether the queue has drained, so each
|
|
REM batch stamps MT# with its own length plus a breath, and the scene
|
|
REM loops requeue when TI# passes it. A batch is 64 notes and the
|
|
REM queue holds 128, so a slightly early requeue appends safely.
|
|
LABEL TUNE
|
|
MT# = TI# + 300
|
|
IF SND# = 0 THEN RETURN
|
|
TRAP QFULL
|
|
IF TB# = 0 THEN GOSUB TUNEA
|
|
IF TB# = 1 THEN GOSUB TUNEB
|
|
IF TB# = 2 THEN GOSUB TUNED
|
|
TRAP
|
|
TB# = TB# + 1
|
|
IF TB# > 2 THEN TB# = 0
|
|
RETURN
|
|
|
|
LABEL TUNEA
|
|
PLAY "V1T3U9S O1AO2AO3AO2AO3AO4CEAO1AO2AO4CEAO5CO4AE O1FO2FO3FO2FO3FAO4CFO1FO2FO3AO4CFAO5CO4A O1CO2CO3CO2CO3CEGO4CO1CO2CO3EGO4CEGO5C O1GO2GO3GO2GO3GBO4DGO1GO2GO3BO4DGBO5DO4B"
|
|
MT# = TI# + 270
|
|
RETURN
|
|
|
|
LABEL TUNEB
|
|
PLAY "V1T3U9S O1AO2AO3AO2AO3AO4CEAO1AO2AO4CEAO5CEO4A O1FO2FO3FO2FO3FAO4CFO1FO2FO3AO4CFAO5CO4A O1DO2DO3DO2DO3DFAO4DO1DO2DO3FAO4DFAO5D O1EO2EO3EO2EO3E#GBO4EO1EO2EO3#GBO4E#GBO5E"
|
|
MT# = TI# + 270
|
|
RETURN
|
|
|
|
LABEL TUNED
|
|
PLAY "V1T4U9S O1ARO5ARO1AAO5ARO1ARO5ARO1AO5AAA"
|
|
MT# = TI# + 80
|
|
RETURN
|
|
|
|
REM Table loaders. READ walks every DATA line in file order, so these
|
|
REM run before LOADFONT and their DATA sits above the glyphs.
|
|
LABEL LOADTABLES
|
|
I# = 0
|
|
DO WHILE I# < 7
|
|
READ D#
|
|
RBW#(I#) = D#
|
|
I# = I# + 1
|
|
LOOP
|
|
I# = 0
|
|
DO WHILE I# < 4
|
|
READ D#
|
|
STC#(I#) = D#
|
|
I# = I# + 1
|
|
LOOP
|
|
I# = 0
|
|
DO WHILE I# < 20
|
|
READ D#
|
|
BARC#(I#) = D#
|
|
I# = I# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM The wireframe models: fourteen vertices, then twenty-four edges
|
|
REM as pairs of vertex indices. DM% because the octahedron's reach is
|
|
REM a float and READ types by the variable it lands in.
|
|
LABEL LOADWIRE
|
|
I# = 0
|
|
DO WHILE I# < 14
|
|
READ DM%
|
|
WVX%(I#) = DM%
|
|
READ DM%
|
|
WVY%(I#) = DM%
|
|
READ DM%
|
|
WVZ%(I#) = DM%
|
|
I# = I# + 1
|
|
LOOP
|
|
I# = 0
|
|
DO WHILE I# < 24
|
|
READ D#
|
|
WE1#(I#) = D#
|
|
READ D#
|
|
WE2#(I#) = D#
|
|
I# = I# + 1
|
|
LOOP
|
|
RETURN
|
|
|
|
REM The stroke font, shared with examples/breakout: one glyph per DATA
|
|
REM line, stroke count then point pairs, a point coded X*10+Y on a
|
|
REM grid four wide and seven tall. INSTR into ALPHA$ finds the glyph.
|
|
LABEL LOADFONT
|
|
GX# = 0
|
|
FOR I# = 0 TO 40
|
|
READ N#
|
|
FNC#(I#) = N#
|
|
FNI#(I#) = GX#
|
|
IF N# > 0 THEN GOSUB READGLYPH
|
|
NEXT I#
|
|
RETURN
|
|
|
|
LABEL READGLYPH
|
|
FOR K# = 1 TO N# * 2
|
|
READ D#
|
|
FNS#(GX#) = D#
|
|
GX# = GX# + 1
|
|
NEXT K#
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Trap handlers for the hardware probes. Each marks its device gone
|
|
REM and lets the probe fall through to the next line.
|
|
REM =====================================================================
|
|
LABEL NOGFX
|
|
GFX# = 0
|
|
RESUME NEXT
|
|
|
|
LABEL NOSID
|
|
SND# = 0
|
|
RESUME NEXT
|
|
|
|
LABEL NOGRID
|
|
TXT# = 0
|
|
RESUME NEXT
|
|
|
|
LABEL NOKEYS
|
|
KEYS# = 0
|
|
RESUME NEXT
|
|
|
|
REM A host slow enough to fall behind the tune finds the note queue
|
|
REM full. Dropping the rest of the batch is a skipped bar, not a
|
|
REM crash -- MT# is already stamped, so the music picks itself up.
|
|
LABEL QFULL
|
|
RESUME NEXT
|
|
|
|
REM =====================================================================
|
|
REM Data. The rainbow, the star brightnesses, four raster-bar ramps,
|
|
REM then the stroke font.
|
|
REM =====================================================================
|
|
REM rainbow: red orange yellow green lt-green cyan lt-blue
|
|
DATA 3, 9, 8, 6, 14, 4, 15
|
|
REM star brightnesses: dark grey, mid grey, light grey, white
|
|
DATA 12, 13, 16, 2
|
|
REM bar ramps, five entries each: red, green, blue, yellow
|
|
DATA 3, 11, 2, 11, 3
|
|
DATA 6, 14, 2, 14, 6
|
|
DATA 7, 15, 2, 15, 7
|
|
DATA 9, 8, 2, 8, 9
|
|
|
|
REM cube vertices 0-7, one per line, x y z
|
|
DATA -1, -1, -1
|
|
DATA 1, -1, -1
|
|
DATA 1, 1, -1
|
|
DATA -1, 1, -1
|
|
DATA -1, -1, 1
|
|
DATA 1, -1, 1
|
|
DATA 1, 1, 1
|
|
DATA -1, 1, 1
|
|
REM octahedron vertices 8-13; 1.4 so its reach matches the cube's
|
|
DATA 1.4, 0, 0
|
|
DATA -1.4, 0, 0
|
|
DATA 0, 1.4, 0
|
|
DATA 0, -1.4, 0
|
|
DATA 0, 0, 1.4
|
|
DATA 0, 0, -1.4
|
|
REM cube edges: bottom ring, top ring, four pillars
|
|
DATA 0, 1, 1, 2, 2, 3, 3, 0
|
|
DATA 4, 5, 5, 6, 6, 7, 7, 4
|
|
DATA 0, 4, 1, 5, 2, 6, 3, 7
|
|
REM octahedron edges: every equator vertex to every pole
|
|
DATA 8, 10, 8, 11, 8, 12, 8, 13
|
|
DATA 9, 10, 9, 11, 9, 12, 9, 13
|
|
DATA 10, 12, 10, 13, 11, 12, 11, 13
|
|
|
|
REM A
|
|
DATA 4, 0,30, 0,6, 30,36, 3,33
|
|
REM B
|
|
DATA 6, 0,6, 0,30, 3,33, 6,36, 30,33, 33,36
|
|
REM C
|
|
DATA 3, 0,30, 0,6, 6,36
|
|
REM D
|
|
DATA 6, 0,6, 0,20, 20,31, 31,35, 35,26, 26,6
|
|
REM E
|
|
DATA 4, 0,6, 0,30, 3,33, 6,36
|
|
REM F
|
|
DATA 3, 0,6, 0,30, 3,33
|
|
REM G
|
|
DATA 5, 0,30, 0,6, 6,36, 33,36, 13,33
|
|
REM H
|
|
DATA 3, 0,6, 30,36, 3,33
|
|
REM I
|
|
DATA 1, 10,16
|
|
REM J
|
|
DATA 3, 30,36, 6,36, 3,6
|
|
REM K
|
|
DATA 3, 0,6, 30,3, 3,36
|
|
REM L
|
|
DATA 2, 0,6, 6,36
|
|
REM M
|
|
DATA 4, 0,6, 30,36, 0,13, 13,30
|
|
REM N
|
|
DATA 3, 0,6, 30,36, 0,36
|
|
REM O
|
|
DATA 4, 0,30, 6,36, 0,6, 30,36
|
|
REM P
|
|
DATA 4, 0,6, 0,30, 3,33, 30,33
|
|
REM Q
|
|
DATA 5, 0,30, 6,36, 0,6, 30,36, 24,36
|
|
REM R
|
|
DATA 5, 0,6, 0,30, 3,33, 30,33, 13,36
|
|
REM S
|
|
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
|
|
REM T
|
|
DATA 2, 0,30, 10,16
|
|
REM U
|
|
DATA 3, 0,6, 30,36, 6,36
|
|
REM V
|
|
DATA 2, 0,16, 16,30
|
|
REM W
|
|
DATA 4, 0,6, 30,36, 6,13, 13,36
|
|
REM X
|
|
DATA 2, 0,36, 30,6
|
|
REM Y
|
|
DATA 3, 0,13, 30,13, 13,16
|
|
REM Z
|
|
DATA 3, 0,30, 30,6, 6,36
|
|
REM 0
|
|
DATA 5, 0,30, 6,36, 0,6, 30,36, 30,6
|
|
REM 1
|
|
DATA 2, 10,16, 1,10
|
|
REM 2
|
|
DATA 5, 0,30, 30,33, 3,33, 3,6, 6,36
|
|
REM 3
|
|
DATA 4, 0,30, 30,36, 3,33, 6,36
|
|
REM 4
|
|
DATA 3, 0,3, 3,33, 30,36
|
|
REM 5
|
|
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
|
|
REM 6
|
|
DATA 5, 0,30, 0,6, 3,33, 33,36, 6,36
|
|
REM 7
|
|
DATA 2, 0,30, 30,36
|
|
REM 8
|
|
DATA 5, 0,30, 6,36, 0,6, 30,36, 3,33
|
|
REM 9
|
|
DATA 5, 0,30, 0,3, 3,33, 30,36, 6,36
|
|
REM space
|
|
DATA 0
|
|
REM colon
|
|
DATA 2, 12,13, 14,15
|
|
REM dash
|
|
DATA 1, 3,33
|
|
REM full stop
|
|
DATA 1, 15,16
|
|
REM exclamation mark
|
|
DATA 2, 10,14, 15,16
|