; ======================================================================
; SPECTRANOS — a ZX Spectrum homage to a certain HBO drama's title
; sequence. Original art, original tune, generic silhouettes. Not
; affiliated with or endorsed by anyone who owns anything.
; From the article at https://thespeccy.com/articles/spectranos
;
; A non-interactive title-sequence demo: six hard-cut scenes, each with
; its own mood colour and a short musical phrase, looping forever.
; Press SPACE at any time to stop and return to BASIC.
;
; Build:  pasmo --tapbas spectranos.asm spectranos.tap
; ======================================================================

        org 32768

HORIZON: equ 15                 ; first ground row; bars grow upward from here
CAP1:    equ 22                 ; caption line 1 (most scenes' only line)
CAP2:    equ 23                 ; caption line 2 (title card's byline)

; ======================================================================
start:
        ld (savedsp),sp        ; remember BASIC's stack position: scenes
                                ; tail-call into playtune, so by the time
                                ; SPACE is seen we may be several calls
                                ; deep — restoring SP directly unwinds
                                ; all of it in one move, however deep
reel:
        call scene1
        call scene2
        call scene3
        call scene4
        call scene5
        call scene6
        jr reel                          ; loop the whole reel forever

; ----------------------------------------------------------------------
; scene 1 — the tunnel: darkness, a distant flashing light, no skyline
; ----------------------------------------------------------------------
scene1:
        call clearbitmap
        ld a,0                            ; black sky, black ink (void)
        call setsky
        ld a,0x07                         ; road: white ink, black paper
        ld de,road_cell
        call setground
        call cleartext
        ld de,t_s1
        ld b,CAP1
        ld c,4
        ld a,0x46
        call print
        ld b,7
        ld c,15
        ld de,solidcell
        call putcell                      ; give the light something to show
        ld a,0xC6                         ; flashing bright yellow: the light
        call setflash_rc
        ld de,tune1
        jp playtune

; ----------------------------------------------------------------------
; scene 2 — skyline: dusk-orange sky, black skyscraper silhouettes
; ----------------------------------------------------------------------
scene2:
        call clearbitmap
        ld a,0x50                         ; bright dusk-red sky (paper2)
        call setsky
        ld a,0x07                         ; road: white ink, black paper
        ld de,road_cell
        call setground
        ld hl,bars2
        ld a,0
        call drawbars
        call cleartext
        ld de,t_s2
        ld b,CAP1
        ld c,6
        ld a,0x47
        call print
        ld de,tune2
        jp playtune

; ----------------------------------------------------------------------
; scene 3 — the overpass: grey steel girders over the road
; ----------------------------------------------------------------------
scene3:
        call clearbitmap
        ld a,0x48                         ; bright steel-blue sky (paper1)
        call setsky
        ld a,0x07                         ; road: white ink, black paper
        ld de,road_cell
        call setground
        ld hl,bars3
        ld a,0
        call drawbars
        call cleartext
        ld de,t_s3
        ld b,CAP1
        ld c,8
        ld a,0x47
        call print
        ld de,tune3
        jp playtune

; ----------------------------------------------------------------------
; scene 4 — smokestacks: industrial haze, a flashing plume of smoke
; ----------------------------------------------------------------------
scene4:
        call clearbitmap
        ld a,0x10                         ; dim red industrial haze (paper2)
        call setsky
        ld a,0x07                         ; road: white ink, black paper
        ld de,road_cell
        call setground
        ld hl,bars4
        ld a,0
        call drawbars
        ld b,4
        ld c,7
        ld de,solidcell
        call putcell                      ; give the smoke something to show
        ld a,0xC7                         ; flashing bright white: smoke
        call setflash_rc
        call cleartext
        ld de,t_s4
        ld b,CAP1
        ld c,5
        ld a,0x46
        call print
        ld de,tune4
        jp playtune

; ----------------------------------------------------------------------
; scene 5 — the suburb: dusk blue sky, a line of trees, grass verge
; ----------------------------------------------------------------------
scene5:
        call clearbitmap
        ld a,0x08                         ; deep evening blue sky (paper1)
        call setsky
        ld a,0x44
        ld de,grass_cell
        call setground
        ld hl,bars5
        ld a,4                            ; silhouette ink: green (foliage)
        call drawbars
        call cleartext
        ld de,t_s5
        ld b,CAP1
        ld c,10
        ld a,0x46
        call print
        ld de,tune5
        jp playtune

; ----------------------------------------------------------------------
; scene 6 — home: a dark house, one lit window, the title card
; ----------------------------------------------------------------------
scene6:
        call clearbitmap
        ld a,0x00                         ; night
        call setsky
        ld a,0x44
        ld de,grass_cell
        call setground
        ld hl,bars6
        ld a,1                            ; silhouette ink: blue (visible on black)
        call drawbars
        ld b,12
        ld c,15
        ld de,solidcell
        call putcell                      ; give the porch light something to show
        ld a,0xC6                         ; flashing bright yellow: porch light
        call setflash_rc
        call cleartext
        ld de,t_s6a
        ld b,CAP1
        ld c,7
        ld a,0xC7
        call print
        ld de,t_s6b
        ld b,CAP2
        ld c,2
        ld a,0x45
        call print
        ld de,tune6
        call playtune
        ; hold on the title card for a little longer than the other cuts
        ld b,8
holdloop:
        push bc
        ld de,tune6b
        call playtune
        pop bc
        djnz holdloop
        ret

; ----------------------------------------------------------------------
; scene helpers
; ----------------------------------------------------------------------
; fill the whole sky (rows 0-14) with attribute A
setsky:
        ld c,a
        ld b,0
ssk:    push bc
        ld a,b
        call fillattrrow
        pop bc
        inc b
        ld a,b
        cp HORIZON
        jr nz,ssk
        ret

; fill the whole ground (rows HORIZON..21) with attribute A and
; texture DE (an 8-byte cell pattern)
setground:
        ld c,a
        ld b,HORIZON
sgd:    push bc
        push de
        ld a,b
        call fillattrrow
        pop de
        pop bc
        push bc
        push de
        ld a,0
        ld c,0
sgd2:   push bc                          ; C is the column; putcell preserves
        push de                          ; BC but clobbers A via LD A,(DE), so
        call putcell                     ; the loop counter must live in C
        pop de
        pop bc
        inc c
        ld a,c
        cp 32
        jr nz,sgd2
        pop de
        pop bc
        inc b
        ld a,b
        cp 22
        jr nz,sgd
        ret

; draw all bars from table HL (col,width,height triples, 0xFF terminator)
; using silhouette ink A
drawbars:
        ld (curink),a
dbr:    ld a,(hl)
        cp 0xFF
        ret z
        ld (b_col),a
        inc hl
        ld a,(hl)
        ld (b_wid),a
        inc hl
        ld a,(hl)
        ld (b_hgt),a
        inc hl
        push hl
        call drawbar
        pop hl
        jr dbr

; draw one bar using b_col,b_wid,b_hgt,curink
drawbar:
        ld a,(b_hgt)
        ld b,a
        ld a,HORIZON
        sub b
        ld (b_row),a
brl:    ld a,(b_row)
        cp HORIZON
        ret nc
        ld a,(b_col)
        ld (b_c2),a
        ld a,(b_wid)
        ld (b_w2),a
bcl:    ld a,(b_w2)
        or a
        jr z,bcd
        dec a
        ld (b_w2),a
        ld a,(b_row)
        ld b,a
        ld a,(b_c2)
        ld c,a
        ld de,solidcell
        call putcell
        call attradr_rc
        ld a,(hl)
        and 0xF8
        push bc
        ld b,a
        ld a,(curink)
        or b
        ld (hl),a
        pop bc
        ld a,(b_c2)
        inc a
        ld (b_c2),a
        jr bcl
bcd:    ld a,(b_row)
        inc a
        ld (b_row),a
        jr brl

; set the FLASH bit of the cell at row B col C
setflash_rc:
        push af
        call attradr_rc
        pop af
        ld (hl),a
        ret

; wipe the entire bitmap — called at the top of every scene so a cut
; never carries over the previous scene's silhouettes
clearbitmap:
        ld hl,16384
        ld de,16385
        ld bc,6143
        ld (hl),0
        ldir
        ret

; blank the caption band (rows 22-23) ready for new text
cleartext:
        ld b,CAP1
ct1:    push bc                          ; fillattrrow clobbers B, so save/restore
        ld a,b
        ld c,0x00
        call fillattrrow
        pop bc
        inc b
        ld a,b
        cp 24
        jr nz,ct1

        ld b,CAP1
ct2:    ld c,0
ct3:    push bc
        ld de,z_cell
        call putcell                     ; putcell preserves BC internally
        pop bc
        inc c
        ld a,c
        cp 32
        jr nz,ct3
        inc b
        ld a,b
        cp 24
        jr nz,ct2
        ret

; ----------------------------------------------------------------------
; text (font + print, same convention as the other Speccy demos)
; ----------------------------------------------------------------------
print:
        ld (textattr),a
pr1:    ld a,(de)
        or a
        ret z
        push de
        call printch
        pop de
        inc de
        inc c
        jr pr1

printch:
        push bc
        sub 32
        ld l,a
        ld h,0
        add hl,hl
        add hl,hl
        add hl,hl
        ld de,font
        add hl,de
        ex de,hl
        call scradr_rc
        ld b,8
pch1:   ld a,(de)
        ld (hl),a
        inc de
        inc h
        djnz pch1
        pop bc
        push bc
        call attradr_rc
        ld a,(textattr)
        ld (hl),a
        pop bc
        ret

; ----------------------------------------------------------------------
; music: DE = table of (cycles,pitch) byte pairs, terminated 0,0
; ----------------------------------------------------------------------
playtune:
        ld a,0x7F                        ; check SPACE between every note
        in a,(0xFE)
        and 1
        jp z,quit
        ld a,(de)
        or a
        ret z
        ld b,a
        inc de
        ld a,(de)
        ld c,a
        inc de
        call beep
        jr playtune

quit:
        call 0x0DAF                      ; ROM CLS, tidy up for BASIC
        ld sp,(savedsp)                  ; discard however many calls deep
        ret                              ; we are and return straight to BASIC

; beep: B = half-cycles, C = pitch delay
beep:
        ld a,0x10
bp1:    out (254),a
        xor 0x10
        push bc
bp2:    dec c
        jr nz,bp2
        pop bc
        djnz bp1
        xor a
        out (254),a
        ret

; ----------------------------------------------------------------------
; low-level addressing / blitting (as used across the site's demos)
; ----------------------------------------------------------------------
scradr_rc:
        ld a,b
        and 0x18
        or 0x40
        ld h,a
        ld a,b
        and 7
        rrca
        rrca
        rrca
        or c
        ld l,a
        ret

attradr_rc:
        ld a,b
        rrca
        rrca
        rrca
        and 3
        add a,0x58
        ld h,a
        ld a,b
        and 7
        rrca
        rrca
        rrca
        or c
        ld l,a
        ret

; copy 8 bytes DE -> cell at row B col C; preserves B,C
putcell:
        push bc
        call scradr_rc
        ld b,8
put1:   ld a,(de)
        ld (hl),a
        inc de
        inc h
        djnz put1
        pop bc
        ret

; fill attribute row A (0-23) with byte C, all 32 columns
fillattrrow:
        push af
        ld b,a
        ld a,b
        and 0x18
        rrca
        rrca
        rrca
        add a,0x58
        ld h,a
        ld a,b
        and 7
        rrca
        rrca
        rrca
        ld l,a
        ld b,32
far1:   ld (hl),c
        inc hl
        djnz far1
        pop af
        ret

; ----------------------------------------------------------------------
; data — scratch vars
; ----------------------------------------------------------------------
textattr: defb 0x47
curink:   defb 0
savedsp:  defw 0
b_col:    defb 0
b_wid:    defb 0
b_hgt:    defb 0
b_row:    defb 0
b_c2:     defb 0
b_w2:     defb 0

; cell textures
z_cell:     defs 8,0
solidcell:  defb 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
road_cell:  defb 0xAA,0x00,0x55,0x00,0xAA,0x00,0x55,0x00
grass_cell: defb 0x00,0x44,0x00,0x11,0x00,0x44,0x00,0x11

; bar tables: col,width,height triples, terminated 0xFF
bars2:  ; skyline
        defb 1,2,4,  5,1,7,  8,2,3,  12,1,9,  15,2,5
        defb 19,1,6,  22,2,8,  26,1,4,  29,2,6
        defb 0xFF
bars3:  ; overpass: a deck with support legs
        defb 2,28,1
        defb 4,1,4,  10,1,4,  16,1,4,  22,1,4,  27,1,4
        defb 0xFF
bars4:  ; smokestacks
        defb 4,1,8,  14,1,10,  24,1,7
        defb 0xFF
bars5:  ; trees: trunk then canopy, repeated
        defb 2,1,2,  1,3,3
        defb 9,1,2,  8,3,3
        defb 16,1,2, 15,3,3
        defb 23,1,2, 22,3,3
        defb 29,1,2, 28,3,3
        defb 0xFF
bars6:  ; a blocky house with a gabled roof suggestion
        defb 10,10,5
        defb 11,8,7
        defb 14,2,2
        defb 0xFF

; note tables: (cycles,pitch) pairs, terminated 0,0 — an original,
; descending minor-blues phrase, one segment per scene. Byte values
; solved for real ~100-110ms notes on actual 3.5MHz hardware (a first
; cut used far smaller values and blew through all six scenes in
; under a second — see tools/compose_tunes.py used to derive these).
tune1:  defb 157,150, 155,152, 153,154, 151,156, 150,158, 148,160, 150,158, 151,156, 153,154, 155,152, 154,153, 152,155, 150,157, 149,159, 147,161, 145,163, 147,161, 149,159, 150,157, 152,155, 162,146, 159,148, 157,150, 155,152, 153,154, 151,156, 153,154, 155,152, 157,150, 159,148, 0,0
tune2:  defb 255,60, 255,62, 255,64, 255,66, 255,68, 255,70, 255,72, 255,70, 255,68, 255,66, 255,64, 255,62, 255,63, 255,65, 255,67, 255,69, 255,71, 255,73, 255,75, 255,73, 255,71, 255,69, 255,67, 255,65, 255,56, 255,58, 255,60, 255,62, 255,64, 255,66, 255,68, 255,66, 255,64, 255,62, 255,60, 255,58, 0,0
tune3:  defb 247,90, 242,92, 237,94, 232,96, 228,98, 223,100, 228,98, 232,96, 237,94, 242,92, 239,93, 235,95, 230,97, 225,99, 221,101, 217,103, 221,101, 225,99, 230,97, 235,95, 255,86, 253,88, 247,90, 242,92, 237,94, 232,96, 237,94, 242,92, 247,90, 253,88, 0,0
tune4:  defb 255,55, 255,57, 255,59, 255,61, 255,63, 255,65, 255,63, 255,61, 255,59, 255,57, 255,58, 255,60, 255,62, 255,64, 255,66, 255,68, 255,66, 255,64, 255,62, 255,60, 255,51, 255,53, 255,55, 255,57, 255,59, 255,61, 255,59, 255,57, 255,55, 255,53, 0,0
tune5:  defb 255,85, 255,87, 255,89, 255,91, 251,93, 246,95, 251,93, 255,91, 255,89, 255,87, 255,88, 255,90, 253,92, 248,94, 243,96, 238,98, 243,96, 248,94, 253,92, 255,90, 255,81, 255,83, 255,85, 255,87, 255,89, 255,91, 255,89, 255,87, 255,85, 255,83, 0,0
tune6:  defb 255,50, 255,52, 255,54, 255,56, 255,58, 255,60, 255,58, 255,56, 255,54, 255,52, 255,53, 255,55, 255,57, 255,59, 255,61, 255,63, 255,61, 255,59, 255,57, 255,55, 255,46, 255,48, 255,50, 255,52, 255,54, 255,56, 255,54, 255,52, 255,50, 255,48, 0,0
tune6b: defb 255,100, 0,0

; strings
t_s1:   defb "SOMEWHERE ON THE TURNPIKE",0
t_s2:   defb "CITY IN THE MIRROR",0
t_s3:   defb "STEEL AND SMOKE",0
t_s4:   defb "INDUSTRY NEVER SLEEPS",0
t_s5:   defb "ALMOST HOME",0
t_s6a:  defb "* THE SPECTRANOS *",0
t_s6b:  defb "AN UNOFFICIAL 8-BIT TRIBUTE",0

        include "fontdata.inc"

        end start
