; 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.