XXIIVV
Spacetime 6502 picture
20R02 — Spacetime 6502

Spacetime is a weird function sequencer.

This is a Famicom port of Study 3 for the Norns, written in 6502 assembly.

It plays a note on each step, ach step is a symbol for an action. It has 8 operators, I’ve added a snare hit to the random note operator for fun, and all operations wrap-around, since it only runs on 4 octaves.

Source

./asm6 cart.asm cart.nes

The source is written for the asm6 assembler, in the Lin6 style.

;; header

	.db "NES", $1a ; identification of the iNES header
	.db 1 ; number of 16KB PRG-ROM pages
	.db $01 ; number of 8KB CHR-ROM pages
	.db $00 ; NROM
	.dsb $09,$00 ; clear the remaining bytes
	.fillvalue $FF ; Sets all unused space in rom to value $FF
	.org $C000

;; constants

	PPUCTRL                 .equ $2000 
	PPUMASK                 .equ $2001 
	PPUSTATUS               .equ $2002 
	SPRADDR                 .equ $2003 
	PPUSCROLL               .equ $2005 
	PPUADDR                 .equ $2006 
	PPUDATA                 .equ $2007 
	APUCH1VOL               .equ $4000 ; APU
	APUCH1SWP               .equ $4001 ; APU
	APUCH1FRQ               .equ $4002 ; APU
	APUCH1LEN               .equ $4003 ; APU
	APUCH2VOL               .equ $4004 ; APU
	APUCH2SWP               .equ $4005 ; APU
	APUCH2FRQ               .equ $4006 ; APU
	APUCH2LEN               .equ $4007 ; APU
	APUCH3CNT               .equ $4008 ; APU
	APUCH3SWP               .equ $4009 ; APU
	APUCH3FRQ               .equ $400a ; APU
	APUCH3LEN               .equ $400b ; APU
	APUCH4VOL               .equ $400c ; APU
	APUCH4SWP               .equ $400d ; APU
	APUCH4FRQ               .equ $400e ; APU
	APUCH4LEN               .equ $400f ; APU
	SPRDMA                  .equ $4014 
	APUCTRL                 .equ $4015 
	JOY1                    .equ $4016 
	JOY2                    .equ $4017 
	BUTTON_RIGHT            .equ #$01  
	BUTTON_LEFT             .equ #$02  
	BUTTON_DOWN             .equ #$04  
	BUTTON_UP               .equ #$08  
	BUTTON_START            .equ #$10  
	BUTTON_SELECT           .equ #$20  
	BUTTON_B                .equ #$40  
	BUTTON_A                .equ #$80  

;; variables

	.enum $0000
	position                .dsb 1
	cursor                  .dsb 1
	note                    .dsb 1
	marker                  .dsb 1
	btndown                 .dsb 1
	btnlast                 .dsb 1
	btnqueue                .dsb 1
	rate                    .dsb 1
	timer                   .dsb 1
	seed                    .dsb 1
	.ende 

;; reset

RESET: 
	SEI                                ; disable IRQs
	CLD                                ; disable decimal mode
	LDX #$40
	STX $4017                          ; disable APU frame IRQ
	LDX #$FF
	TXS                                ; set up stack
	INX                                ; now X = 0
	STX PPUCTRL                        ; disable NMI
	STX PPUMASK                        ; disable rendering
	STX $4010                          ; disable DMC IRQs

Vblankwait1: ; First wait for vblank
	BIT PPUSTATUS
	BPL Vblankwait1

Clrmem: 
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x                       ; move sprites off screen
	INX 
	BNE Clrmem

Vblankwait2: ; Second wait for vblank
	BIT PPUSTATUS
	BPL Vblankwait2

;; setup phase

SetupPalettes: 
	LDA PPUSTATUS
	LDA #$3F
	STA PPUADDR
	LDA #$00
	STA PPUADDR
	LDX #$00
@loop: 
	LDA Palettes, x
	STA PPUDATA
	INX 
	CPX #$20
	BNE @loop

SetupAttributes: 
	LDA PPUSTATUS
	LDA #$23
	STA PPUADDR
	LDA #$C0
	STA PPUADDR
	LDX #$00
@loop: 
	LDA #$00
	STA PPUDATA
	INX 
	CPX #$40
	BNE @loop

SetupSequence: 
	LDX #$00
@loop: 
	LDA $00                            ; set entire sequence to "+"
	STA $10,x                          ; store on zeropage second row
	INX 
	CPX #$10
	BNE @loop

SetupVariables: 
	LDA #$08
	STA rate                           ; default rate = 08
	LDA #$20
	STA note                           ; default note = C
	LDA #$05
	STA marker

SetupAudio: 
	LDY #$0f                           ; init $4000-4013
@loop: 
	LDA ApuStates,y
	STA $4000,y
	DEY 
	BPL @loop
	LDA #$0f                           ; skip over $4014 (OAMDMA)
	STA $4015
	LDA #$40
	STA $4017

SetupInterface: 
@cursor: 
	LDA #$88
	STA $0200                          ; set tile.y pos
	LDA #$02
	STA $0201                          ; set tile.id
	LDA #$00
	STA $0202                          ; set tile.attribute
@marker: 
	LDA #$88
	STA $0204                          ; set tile.y pos
	LDA #$03
	STA $0205                          ; set tile.id
	LDA #$00
	STA $0206                          ; set tile.attribute

EnableSprites: 
	LDA #%10000000                     ; enableNMI sprites0 background0
	STA PPUCTRL
	LDA #%00011110                     ; sprites background noclip
	STA PPUMASK

;; main phase

MAIN: 
	LDA btnqueue
@style1: 
	CMP BUTTON_LEFT
	BEQ MoveLeft
	CMP BUTTON_RIGHT
	BEQ MoveRight
	CMP BUTTON_UP
	BEQ MoveUp
	CMP BUTTON_DOWN
	BEQ MoveDown
@style2: 
	CMP BUTTON_SELECT
	BEQ ResetSequence
	CMP BUTTON_START
	BEQ MoveMarker
	CMP BUTTON_A
	BEQ MoveUp
	CMP BUTTON_B
	BEQ MoveDown
	JMP MAIN                           ; infinite loop

;; General functions

MoveLeft: 
	DEC cursor
	LDA cursor
	CMP #$ff
	BNE @done                          ; when equal skip
	LDA #$0f                           ; wrap around
	STA cursor
@done: 
	JMP MoveEnd

MoveRight: 
	INC cursor
	LDA cursor
	CMP #$10
	BCC @done                          ; when greaterthan skip
	LDA #$00                           ; wrap around
	STA cursor
@done: 
	JMP MoveEnd

MoveDown: 
	LDX cursor
	DEC $10,x
	LDA $10,x
	CMP #$ff                           ; when equal skip
	BNE @done                          ; wrap around
	LDA #$07
	STA $10,x
@done: 
	JMP MoveEnd

MoveUp: 
	LDX cursor
	INC $10,x
	LDA $10,x
	CMP #$08                           ; when greaterthan skip
	BCC @done                          ; wrap around
	LDA #$00
	STA $10,x
@done: 
	JMP MoveEnd

MoveMarker: 
	LDA cursor
	STA marker
	STA RedrawInterface
	JMP MoveEnd

ResetSequence: 
	LDX #$00
@loop: 
	LDA #$01                           ; set entire sequence to "+"
	STA $10,x                          ; store on zeropage second row
	INX 
	CPX #$10
	BNE @loop
	JMP MoveEnd

MoveEnd: 
	LDA #$00
	STA btnqueue
	STA RedrawTimeline
	JMP MAIN

;; interface

RedrawTimeline: 
	LDX #$00
@loop: 
	LDA #$22                           ; sprite v pos
	STA PPUADDR
	TXA                                ; sprite x pos
	STA PPUADDR
	LDA #$10, x                        ; find selection
	CLC 
	ADC #$10
	CPX cursor                         ; if cursor
	BNE @continue
	CLC 
	ADC #$08                           ; then, highlight
@continue: 
	STA PPUDATA                        ; sprite id
	INX 
	CPX #$10
	BNE @loop
	RTS 

RedrawInterface: 
	LDA PPUSTATUS
@selection: 
	LDA cursor
	CLC 
	ADC #$20
	LDX #$40
	LDY #$22
	STY PPUADDR
	STX PPUADDR
	STA PPUDATA
@marker: 
	LDA marker
	CLC 
	ADC #$20
	LDX #$41
	LDY #$22
	STY PPUADDR
	STX PPUADDR
	STA PPUDATA
@position: 
	LDA position
	CLC 
	ADC #$20
	LDX #$42
	LDY #$22
	STY PPUADDR
	STX PPUADDR
	STA PPUDATA
@rate: 
	LDA rate
	CLC 
	ADC #$20
	LDX #$43
	LDY #$22
	STY PPUADDR
	STX PPUADDR
	STA PPUDATA
@sprites: 
	LDA position
	ASL 
	ASL 
	ASL 
	CLC 
	ADC #$40
	STA $0203                          ; set tile.x pos
	LDA marker
	ASL 
	ASL 
	ASL 
	CLC 
	ADC #$40
	STA $0207                          ; set tile.x pos
@done: 
	RTS 

IncNote: 
	INC note
	LDA note
	CMP #$48
	BNE @done
	LDA #$00                           ; when reached end
	STA note                           ; reset note
@done: 
	RTS 

DecNote: 
	DEC note
	LDA note
	CMP #$ff
	BNE @done
	LDA #$47
	STA note
@done: 
	RTS 

PlaySnare: 
	LDA #<$3f8
	STA APUCH4LEN
	LDA #>$3f8
	STA APUCH4FRQ
	LDA #%11000100
	STA APUCH4VOL
	RTS 

;; operators

DoInc: 
	LDX #$00
@loop: 
	JSR IncNote
	CPX marker
	INX 
	BCC @loop
	JMP DoOperationEnd

DoDec: 
	LDX #$00
@loop: 
	JSR DecNote
	CPX marker
	INX 
	BCC @loop
	JMP DoOperationEnd

DoTop: 
	LDA #$24
	STA note
	JMP DoOperationEnd

DoBottom: 
	LDA #$00
	STA note
	JMP DoOperationEnd

DoRandomNote: 
	LDA seed
@loop: 
	SEC 
	SBC #$48
	CMP #$48
	BCS @loop
	STA note
	INC seed
	JSR PlaySnare
	JMP DoOperationEnd

DoIncMetro: 
	INC rate
	LDA rate
	CMP #$0c
	BCC @done
	LDA #$01
	STA rate
@done: 
	JMP DoOperationEnd

DoDecMetro: 
	DEC rate
	LDA rate
	CMP #$00
	BNE @done
	LDA #$0c
	STA rate
@done: 
	JMP DoOperationEnd

DoRandomPosition: 
	LDA marker
	STA position
	JMP DoOperationEnd

DoOperation: 
	LDX position
	LDA $10,x
	CMP #$00
	BEQ DoInc
	CMP #$01
	BEQ DoDec
	CMP #$02
	BEQ DoBottom
	CMP #$03
	BEQ DoTop
	CMP #$04
	BEQ DoRandomNote
	CMP #$05
	BEQ DoIncMetro
	CMP #$06
	BEQ DoDecMetro
	CMP #$07
	BEQ DoRandomPosition

DoOperationEnd: ; return from operations
	RTS 

DoSound: 
	LDX note
@ch1: 
	LDA NotesHi,x
	STA APUCH1LEN
	LDA NotesLo,x
	STA APUCH1FRQ
	LDY position
	STY APUCH1SWP
@ch2: 
	LDA NotesHi,x
	STA APUCH2LEN
	LDA NotesLo,x
	STA APUCH2FRQ
	LDY marker
	STY APUCH2SWP
@ch3: 
	LDA NotesHi,x
	STA APUCH3LEN
	LDA NotesLo,x
	STA APUCH3FRQ
	LDY timer
	STY APUCH3SWP
	RTS 

;; nmi phase

NMI: ;

ReadJoy: 
	LDA #$01
	STA JOY1                           ; start reading
	STA btndown
	LSR a
	STA JOY1
@loop: 
	LDA JOY1
	LSR a
	ROL btndown
	BCC @loop

SaveJoy: 
	LDA btndown
	CMP btnlast
	BEQ @done
	STA btnlast
	CMP #$00
	BEQ @done
	STA btnqueue
@done: 
	LDA #$00
	STA SPRADDR
	LDA #$02
	STA $4014

RunTimer: 
	INC seed
	INC timer
	LDA timer
	CMP rate                           ; check if timer has reached rate
	BNE @done                          ; skip if timer is less than rate
	LDA #$00
	STA timer                          ; reset timer
	JSR DoOperation
	JSR DoSound
	INC position
	LDA position
	CMP #$10                           ; check if position has reached 16
	BNE @done
	LDA #$00
	STA position
@done: 

Cleanup: 
	JSR RedrawTimeline
	JSR RedrawInterface
	LDA PPUSTATUS
	LDA #$c0                           ; shift background to center
	STA PPUSCROLL
	LDA #$00
	STA PPUSCROLL
	RTI                                ; return from interrupt

;; tables

	.org $E000

Palettes: ;
	.db $0F,$3B,$16,$30, $3B,$3B,$0F,$0F, $0F,$3B,$0F,$0F, $0F,$3B,$0F,$0F
	.db $0F,$3B,$16,$3B, $0F,$3B,$0F,$0F, $0F,$0F,$0F,$0F, $0F,$0F,$0F,$0F

ApuStates: 
	.db #%10100111,$08,$00,$00
	.db #%00011101,$08,$00,$00
	.db #%01011101,$08,$00,$00
	.db #%01011011,$08,$00,$00

NotesHi: 
	.db $07,$07,$07,$06,$06,$05,$05,$05,$05,$04,$04,$04
	.db $03,$03,$03,$03,$03,$02,$02,$02,$02,$02,$02,$02
	.db $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

NotesLo: 
	.db $f1,$7f,$13,$ad,$4d,$f3,$9d,$4c,$00,$b8,$74,$34
	.db $f8,$bf,$89,$56,$26,$f9,$ce,$a6,$80,$5c,$3a,$1a
	.db $fb,$df,$c4,$ab,$93,$7c,$67,$52,$3f,$2d,$1c,$0c
	.db $fd,$ef,$e1,$d5,$c9,$bd,$b3,$a9,$9f,$96,$8e,$86
	.db $7e,$77,$70,$6a,$64,$5e,$59,$54,$4f,$4b,$46,$42
	.db $3f,$3b,$38,$34,$31,$2f,$2c,$29,$27,$25,$23,$21

;; Vectors

	.pad $FFFA
	.dw NMI
	.dw RESET
	.dw 0

;; Sprites

	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $fe,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$fe,$00,$00,$00,$00,$00,$00,$00
	.db $fe,$00,$00,$00,$00,$00,$00,$00,$fe,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $10,$10,$10,$fe,$10,$10,$10,$00,$10,$10,$10,$fe,$10,$10,$10,$00
	.db $00,$00,$00,$fe,$00,$00,$00,$00,$00,$00,$00,$fe,$00,$00,$00,$00
	.db $04,$08,$10,$20,$10,$08,$04,$00,$04,$08,$10,$20,$10,$08,$04,$00
	.db $20,$10,$08,$04,$08,$10,$20,$00,$20,$10,$08,$04,$08,$10,$20,$00
	.db $10,$54,$28,$c6,$28,$54,$10,$00,$10,$54,$28,$c6,$28,$54,$10,$00
	.db $ec,$92,$92,$92,$92,$92,$92,$00,$ec,$92,$92,$92,$92,$92,$92,$00
	.db $92,$92,$92,$92,$92,$92,$ec,$00,$92,$92,$92,$92,$92,$92,$ec,$00
	.db $10,$28,$44,$82,$44,$28,$10,$00,$10,$28,$44,$82,$44,$28,$10,$00
	.db $10,$10,$10,$fe,$10,$10,$10,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $00,$00,$00,$fe,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $04,$08,$10,$20,$10,$08,$04,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $20,$10,$08,$04,$08,$10,$20,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $10,$54,$28,$c6,$28,$54,$10,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $ec,$92,$92,$92,$92,$92,$92,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $92,$92,$92,$92,$92,$92,$ec,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $10,$28,$44,$82,$44,$28,$10,$00,$00,$00,$00,$00,$00,$00,$00,$00
	.db $7c,$82,$82,$82,$82,$82,$7c,$00,$7c,$82,$82,$82,$82,$82,$7c,$00
	.db $10,$30,$10,$10,$10,$10,$38,$00,$10,$30,$10,$10,$10,$10,$38,$00
	.db $7c,$82,$02,$7c,$80,$80,$fe,$00,$7c,$82,$02,$7c,$80,$80,$fe,$00
	.db $7c,$82,$02,$1c,$02,$82,$7c,$00,$7c,$82,$02,$1c,$02,$82,$7c,$00
	.db $22,$42,$82,$fe,$02,$02,$02,$00,$22,$42,$82,$fe,$02,$02,$02,$00
	.db $fe,$80,$80,$7c,$02,$82,$7c,$00,$fe,$80,$80,$7c,$02,$82,$7c,$00
	.db $7c,$82,$80,$fc,$82,$82,$7c,$00,$7c,$82,$80,$fc,$82,$82,$7c,$00
	.db $7c,$82,$04,$08,$10,$20,$40,$00,$7c,$82,$04,$08,$10,$20,$40,$00
	.db $7c,$82,$82,$7c,$82,$82,$7c,$00,$7c,$82,$82,$7c,$82,$82,$7c,$00
	.db $7c,$82,$82,$7e,$02,$02,$02,$00,$7c,$82,$82,$7e,$02,$02,$02,$00
	.db $7c,$82,$82,$fe,$82,$82,$82,$00,$7c,$82,$82,$fe,$82,$82,$82,$00
	.db $fc,$82,$82,$fc,$82,$82,$fc,$00,$fc,$82,$82,$fc,$82,$82,$fc,$00
	.db $3c,$42,$80,$80,$80,$42,$3c,$00,$3c,$42,$80,$80,$80,$42,$3c,$00
	.db $f8,$84,$82,$82,$82,$84,$f8,$00,$f8,$84,$82,$82,$82,$84,$f8,$00
	.db $7e,$80,$80,$f0,$80,$80,$7e,$00,$7e,$80,$80,$f0,$80,$80,$7e,$00
	.db $7e,$80,$80,$f0,$80,$80,$80,$00,$7e,$80,$80,$f0,$80,$80,$80,$00
	.db $3c,$42,$80,$80,$80,$42,$3c,$00,$3c,$42,$80,$80,$80,$42,$3c,$00
	.db $34,$4a,$84,$80,$80,$42,$3c,$00,$34,$4a,$84,$80,$80,$42,$3c,$00
	.db $f8,$84,$82,$82,$82,$84,$f8,$00,$f8,$84,$82,$82,$82,$84,$f8,$00
	.db $f4,$8a,$84,$82,$82,$84,$f8,$00,$f4,$8a,$84,$82,$82,$84,$f8,$00
	.db $7e,$80,$80,$f0,$80,$80,$7e,$00,$7e,$80,$80,$f0,$80,$80,$7e,$00
	.db $7e,$80,$80,$f0,$80,$80,$80,$00,$7e,$80,$80,$f0,$80,$80,$80,$00
	.db $74,$8a,$84,$f0,$80,$80,$80,$00,$74,$8a,$84,$f0,$80,$80,$80,$00
	.db $7e,$80,$80,$9c,$82,$82,$7c,$00,$7e,$80,$80,$9c,$82,$82,$7c,$00
	.db $74,$8a,$84,$98,$82,$82,$7c,$00,$74,$8a,$84,$98,$82,$82,$7c,$00
	.db $7c,$82,$82,$fe,$82,$82,$82,$00,$7c,$82,$82,$fe,$82,$82,$82,$00
	.db $74,$8a,$84,$fa,$82,$82,$82,$00,$74,$8a,$84,$fa,$82,$82,$82,$00
	.db $fc,$82,$82,$fc,$82,$82,$fc,$00,$fc,$82,$82,$fc,$82,$82,$fc,$00
	.dsw 3624,$0000
— Found a mistake? Submit an edit to spacetime.s.txt(652 lines)

incoming(3): nasu lin6 assembly

Last update on 20R03, edited 2 times. +2/6fh-----+