; SMS VGM player
; by Maxim
; VRAM mapping stuff
.define SpriteSet 1 ; 0 for sprites to use tiles 0-255, 1 for 256+
.define NameTableAddress $3800 ; must be a multiple of $800; usually $3800; fills $700 bytes (unstretched)
.define SpriteTableAddress $3f00 ; must be a multiple of $100; usually $3f00; fills $100 bytes
;==============================================================
; WLA-DX banking setup
; Note that this is a frame 2-only setup, allowing large data
; chunks in the first 32KB.
;==============================================================
.memorymap
DEFAULTSLOT 0
SLOTSIZE $8000
SLOT 0 $0000
SLOTSIZE $4000
SLOT 1 $8000
.ENDME
.ROMBANKMAP
BANKSTOTAL 1
BANKSIZE $8000
BANKS 1
;BANKSIZE $4000
;BANKS 1
.ENDRO
.bank 0 slot 0
.org $0000
;==============================================================
; SDSC tag and SMS rom header
;==============================================================
.sdsctag 0.40,"SMS VGM player",SDSCNotes,SDSCAuthor
.section "SDSC notes" FREE
SDSCNotes:
; 123456789012345678901234567890123456789012345678901234567890123
.db "Beta version. Please note that"
.db " only the first 32KB is the actual program, and any data after"
.db " that is appended VGM music data.",0
SDSCAuthor:
.db "Maxim",0
.ends
;==============================================================
; Memory usage
;==============================================================
.enum $c000
VGMMemoryStart ds 256
ButtonState db
LastButtonState db
VisBuffer ds 32 ; 32 bytes
VisNumber db
IsPalConsole db
IsJapConsole db
FMChipDetected db
VBlankRoutine dw
VisRoutine dw ; Routine to calculate vis
VisDisplayRoutine dw ; Routine to call in VBlank to update vis
VisChanged db ; Flag to signal that the vis needs to be initialised
SecondsChanged db
LoopsChanged db
.ENDE
;Useful defines and macros:
.include "..\graphics.inc"
.org $0000
;==============================================================
; Boot section
;==============================================================
.section "!Boot section" FORCE ; Standard stuff (for the SMS anyway)
di ; disable interrupts (re-enable later)
im 1 ; Interrupt mode 1
ld sp, $dff0 ; load stack pointer to not-quite-the-end of user RAM (avoiding paging regs)
jp main ; jump to main program
; Put a string between here and 0x0038 to make it easy to identify for Cowering
.db " "
.db "- See SDSC tag -"
.db "-for information"
.db " "
.ends
.org $0038
;==============================================================
; Interrupt handler
;==============================================================
.section "!Interrupt handler" FORCE
in a,($bf) ; satisfy interrupt
; No checking of the value because I know I only have VBlank interrupts
ld hl,(VBlankRoutine)
call callHL
reti
callHL:
jp (hl)
NoVBlank:
ret
VGMPlayerVBlank:
call CheckInput ; Read input into memory
call ShowTime ; Update time display
call ShowLoopNumber
ld a,(VisChanged)
cp 1
call z,InitialiseVisRoutine ; If the vis has changed then I need to do the initialisation in the vblank
ld hl,(VisDisplayRoutine) ; Draw vis
call callHL
ret
.ends
.org $0066
;==============================================================
; Pause button handler
;==============================================================
.section "!NMI handler" FORCE
; Debug resetter
; jp $0000
; Dodgy PAL/NTSC speed switch
push hl
push de
call VGMGetSpeed
ld de,735
and $ff
sbc hl,de
jp z,_ChangeToPAL
ld hl,735
jp _SetSpeed
_ChangeToPAL:
ld hl,882
_SetSpeed:
call VGMSetSpeed
pop de
pop hl
retn
.ends
;==============================================================
; Main program
;==============================================================
main:
ld a,$02
ld ($ffff),a
; Load VDP with default values, thanks to Mike G :P
; hl = address of data
; b = size of data
; c = port
; otir = while (b>0) do {out (hl),c; b--}
ld hl,VdpData
ld b,VdpDataEnd-VdpData
ld c,$bf
otir
call IsPAL
ld (IsPalConsole),a
call IsJapanese
ld (IsJapConsole),a
call HasFMChip
ld (FMChipDetected),a
; Startup screen
call ClearVRAM
call DisplayVGMLogo
call TurnOffScreen
call ClearVRAM
call NoSprites
; Load palette
ld hl,PaletteData
ld b,(PaletteDataEnd-PaletteData)
ld c,0
call LoadPalette
ld c,31
ld b,1
call LoadPalette
; Load tiles
ld hl,0 ; Load font
ld ix,TileData
ld bc,95
ld d,3
call LoadTiles
ld hl,$60 ; Load vis tiles
ld ix,ScaleData
ld bc,9
call LoadTiles
.define BigNumbersOffset 105
ld hl,BigNumbersOffset ; Load big numbers
ld ix,BigNumbers
ld bc,41
call LoadTiles
ld hl,146
ld ix,Pad ; Load pad image
ld bc,104
ld d,4
call LoadTiles
ld hl,250
ld ix,PianoTiles
ld bc,10
ld d,3
call LoadTiles
ld hl,$1c0-4
ld ix,HandTiles
ld bc,4
ld d,3
call LoadTiles
; Initial button state values (all off)
ld a,$ff
ld (LastButtonState),a
ld (ButtonState),a
; Draw text
ld iy,NameTableAddress
ld hl,TextData
call WriteASCII
; Draw pad image
ld bc,$0c0b ; 12x11
ld ix,PadData
ld iy,NameTableAddress+2*20
call DrawImageBytes
; Reset VGM player
call VGMInitialise
; Initialise visualisation
call InitialiseVis
; Debug: show detected information
ld hl,NameTableAddress+2*(32*27+4)
call VRAMToHL
ld a,(IsJapConsole)
call WriteNumber
ld hl,NameTableAddress+2*(32*27+11)
call VRAMToHL
ld a,(IsPalConsole)
call WriteNumber
ld hl,NameTableAddress+2*(32*27+17)
call VRAMToHL
ld a,(FMChipDetected)
call WriteNumber
; Set VBlank routine
ld hl,VGMPlayerVBlank
ld (VBlankRoutine),hl
; Fake a VBlank to initialise various stuff
call VGMPlayerVBlank
; Turn screen on
ld a,%11110010
; ||||| |`- Zoomed sprites -> 16x16 pixels
; ||||| `-- Doubled sprites -> 2 tiles per sprite, 8x16
; ||||`---- 30 row/240 line mode
; |||`----- 28 row/224 line mode
; ||`------ VBlank interrupts
; |`------- Enable display
; `-------- Must be set (VRAM size bit)
out ($bf),a
ld a,$81
out ($bf),a
InfiniteLoop: ; to stop the program
call VGMUpdate ; Write sound data
call ProcessInput ; Process input from the last VBlank
ld hl,(VisRoutine) ; Update vis data
call callHL
ei
halt
jp InfiniteLoop
;==============================================================
; VGM offset to SMS offset convertor
; Inputs:
; a = offset of dword in VGM file
; Outputs:
; a = page number ($02+) *
; hl = offset ($8000-$bfff)
; * If dword is zero then a will be zero to show that
;==============================================================
.section "VGM to SMS offset" SEMIFREE
VGMOffsetToPageAndOffset:
push ix
push bc
push de
; Remember the offset value in c becasue I need to use a
ld c,a
; Page in the first page, remembering the current page on the stack
ld a,($ffff)
push af
ld a,$02
ld ($ffff),a
; offset of dword in .sms -> ix
ld b,$80
push bc
pop ix
; Value stored there -> dehl
ld l,(ix+0)
ld h,(ix+1)
ld e,(ix+2)
ld d,(ix+3)
; See if it's zero
; If it is, h|l|d|e=0
ld a,h
or l
or d
or e
jp nz,_NonZero
ld c,$00
jp _end
_NonZero:
; Add offset, ie. dehl + c -> dehl
ld b,$00
and $ff ; reset carry
adc hl,bc
ld bc,$0000 ; I've finished with c now
push hl
ld h,d
ld l,e
adc hl,bc
ld d,h
ld e,l
pop hl
; Figure out which page it's on and store that in c now
; ddddddddeeeeeeeehhhhhhhhllllllll
; Page -----^^^^^^^^
ld b,e
sla b
sla b
ld a,h
rlc a
rlc a
and %00000011
or b
add a,2
ld c,a
; And then get the offset by modding by $4000 and adding $8000
; Do this by hl = (hl & 0x3fff) | 0x8000
; Quite neat in asm :P
res 6,h
set 7,h
_end:
; Restore original page
pop af
; ld ($ffff),a
; Output a=page number
ld a,c
pop de
pop bc
pop ix
ret
.ends
...and that's as much as you're getting.