├── screenshots ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png └── 6.png ├── src ├── gfx │ ├── font.png │ ├── hex.png │ ├── knob.png │ ├── arrows.png │ └── textbox.png ├── hram.asm ├── code │ ├── alias.asm │ ├── header.asm │ ├── video │ │ ├── copy.asm │ │ ├── callback.asm │ │ └── queue.asm │ ├── math.asm │ ├── lcd.asm │ ├── oam.asm │ ├── joypad.asm │ ├── int.asm │ ├── save.asm │ ├── rst.asm │ ├── init.asm │ ├── task.asm │ ├── sound.asm │ └── menu.asm ├── constants.asm ├── constants │ └── buttons.asm ├── core.asm ├── macros.asm ├── gbhw.asm └── main.asm ├── .gitignore ├── Makefile └── README.md /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/screenshots/4.png -------------------------------------------------------------------------------- /screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/screenshots/5.png -------------------------------------------------------------------------------- /screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/screenshots/6.png -------------------------------------------------------------------------------- /src/gfx/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/src/gfx/font.png -------------------------------------------------------------------------------- /src/gfx/hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/src/gfx/hex.png -------------------------------------------------------------------------------- /src/gfx/knob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/src/gfx/knob.png -------------------------------------------------------------------------------- /src/hram.asm: -------------------------------------------------------------------------------- 1 | enum_start $FF80 2 | 3 | enum hROMBank 4 | enum hGBC 5 | enum hDMA 6 | -------------------------------------------------------------------------------- /src/gfx/arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/src/gfx/arrows.png -------------------------------------------------------------------------------- /src/gfx/textbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/waveform-gb/HEAD/src/gfx/textbox.png -------------------------------------------------------------------------------- /src/code/alias.asm: -------------------------------------------------------------------------------- 1 | SECTION "hl", ROM0 2 | __hl__:: 3 | jp hl 4 | 5 | SECTION "de", ROM0 6 | __de__:: 7 | push de 8 | ret 9 | 10 | SECTION "bc", ROM0 11 | __bc__:: 12 | push bc 13 | ret 14 | -------------------------------------------------------------------------------- /src/constants.asm: -------------------------------------------------------------------------------- 1 | ; rst vector addresses are defined here. 2 | Bankswitch EQU 0 3 | FarCall EQU 8 4 | 5 | INCLUDE "gbhw.asm" 6 | INCLUDE "macros.asm" 7 | 8 | INCLUDE "hram.asm" 9 | 10 | INCLUDE "constants/buttons.asm" 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build 2 | *.o 3 | *.gb 4 | *.gbc 5 | *.1bpp 6 | *.2bpp 7 | 8 | # rgbds extras 9 | *.sym 10 | *.map 11 | 12 | # precompiled python 13 | *.pyc 14 | 15 | # swap files 16 | *.swp 17 | 18 | # save files 19 | *.sav 20 | -------------------------------------------------------------------------------- /src/code/header.asm: -------------------------------------------------------------------------------- 1 | SECTION "Entry", ROM0[$100] 2 | 3 | ; This is the entry point to the program. 4 | 5 | nop 6 | jp Init 7 | 8 | 9 | SECTION "Header", ROM0[$104] 10 | 11 | ; The header is created by rgbfix. 12 | ; The space here is allocated as a placeholder. 13 | 14 | ds $150 - $104 15 | -------------------------------------------------------------------------------- /src/code/video/copy.asm: -------------------------------------------------------------------------------- 1 | SECTION "Video Copy", ROM0 2 | 3 | DrawTilemapRect:: 4 | ; Fill a cxb rectangle at bg map address hl with a++. 5 | 6 | push af 7 | ld a, BG_WIDTH 8 | sub c 9 | ld e, a 10 | ld d, 0 11 | pop af 12 | 13 | .y 14 | push bc 15 | .x 16 | ld [hli], a 17 | inc a 18 | dec c 19 | jr nz, .x 20 | pop bc 21 | 22 | add hl, de 23 | dec b 24 | jr nz, .y 25 | 26 | ret 27 | -------------------------------------------------------------------------------- /src/code/math.asm: -------------------------------------------------------------------------------- 1 | SECTION "Math", ROM0 2 | 3 | ; multiply d x e 4 | ; return result in hl 5 | Multiply:: 6 | ; push af 7 | ld hl, 0 8 | ld a, d 9 | ld d, 0 10 | and a 11 | .loop 12 | jr z, .done 13 | add hl, de 14 | dec a 15 | jr .loop 16 | .done 17 | ; pop af 18 | ret 19 | 20 | ; add bc to hl a times 21 | AddATimes:: 22 | and a 23 | .loop 24 | jr z, .done 25 | add hl, bc 26 | dec a 27 | jr .loop 28 | .done 29 | ret 30 | -------------------------------------------------------------------------------- /src/constants/buttons.asm: -------------------------------------------------------------------------------- 1 | A_BUTTON EQU %00000001 2 | B_BUTTON EQU %00000010 3 | SELECT EQU %00000100 4 | START EQU %00001000 5 | D_RIGHT EQU %00010000 6 | D_LEFT EQU %00100000 7 | D_UP EQU %01000000 8 | D_DOWN EQU %10000000 9 | 10 | A_BUTTON_F EQU 0 11 | B_BUTTON_F EQU 1 12 | SELECT_F EQU 2 13 | START_F EQU 3 14 | D_RIGHT_F EQU 4 15 | D_LEFT_F EQU 5 16 | D_UP_F EQU 6 17 | D_DOWN_F EQU 7 18 | -------------------------------------------------------------------------------- /src/core.asm: -------------------------------------------------------------------------------- 1 | INCLUDE "constants.asm" 2 | 3 | INCLUDE "code/int.asm" 4 | INCLUDE "code/rst.asm" 5 | INCLUDE "code/header.asm" 6 | INCLUDE "code/init.asm" 7 | INCLUDE "code/alias.asm" 8 | INCLUDE "code/joypad.asm" 9 | INCLUDE "code/video/copy.asm" 10 | INCLUDE "code/task.asm" 11 | INCLUDE "code/lcd.asm" 12 | INCLUDE "code/oam.asm" 13 | INCLUDE "code/math.asm" 14 | INCLUDE "code/sound.asm" 15 | INCLUDE "code/save.asm" 16 | INCLUDE "code/menu.asm" 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | .SUFFIXES: .asm 3 | 4 | name = waveform 5 | src = src 6 | obj = src/main.o src/core.o 7 | 8 | all: clean $(name).gb 9 | 10 | clean: 11 | @rm -f $(obj) $(name).gb $(name).sym 12 | 13 | gfx: 14 | @find -iname "*.png" -exec sh -c 'rgbgfx -o $${1%.png}.2bpp $$1' _ {} \; 15 | 16 | .asm.o: 17 | @rgbasm -E -i $(src)/ -o $@ $< 18 | 19 | $(name).gb: gfx $(obj) 20 | @rgblink -n $(name).sym -o $@ $(obj) 21 | @rgbfix -jv -i XXXX -k XX -l 0x33 -m 0x03 -p 0 -r 1 -t WAVEFORM $@ 22 | -------------------------------------------------------------------------------- /src/code/lcd.asm: -------------------------------------------------------------------------------- 1 | SECTION "LCD", ROM0 2 | 3 | DisableLCD:: 4 | xor a 5 | ld [rIF], a 6 | ld a, [rIE] 7 | ld b, a 8 | res 0, a 9 | ld [rIE], a 10 | .waitVBlank 11 | ld a, [rLY] 12 | cp a, $91 13 | jr nz, .waitVBlank 14 | ld a, [rLCDC] 15 | and a, $7F 16 | ld [rLCDC], a 17 | ld a, b 18 | ld [rIE], a 19 | ret 20 | 21 | EnableLCD:: 22 | ld a, [rLCDC] 23 | set 7, a 24 | ld [rLCDC], a 25 | ret 26 | 27 | DisableWindow:: 28 | ld a, [rLCDC] 29 | res 5, a ; disable window 30 | ld [rLCDC], a 31 | ret 32 | 33 | EnableWindow:: 34 | ld a, [rLCDC] 35 | set 5, a ; enable window 36 | set 6, a ; use bg map 1 37 | ld [rLCDC], a 38 | ret 39 | -------------------------------------------------------------------------------- /src/code/oam.asm: -------------------------------------------------------------------------------- 1 | OAM_SPRITES EQU 40 2 | 3 | 4 | SECTION "OAM WRAM", WRAM0, ALIGN[8] 5 | 6 | wOAM:: 7 | ds 4 * OAM_SPRITES 8 | 9 | 10 | SECTION "OAM", ROM0 11 | 12 | ; copy DMA routine to HRAM 13 | WriteDMATransferToHRAM: 14 | ld c, LOW(hDMA) 15 | ld b, DMATransferEnd - DMATransfer 16 | ld hl, DMATransfer 17 | .copyLoop 18 | ld a, [hli] 19 | ld [$FF00+c], a 20 | inc c 21 | dec b 22 | jr nz, .copyLoop 23 | ret 24 | 25 | ; this routine is copied to HRAM and executed there every vblank 26 | DMATransfer: 27 | ld a, wOAM >> 8 28 | ld [rDMA], a 29 | ld a, OAM_SPRITES 30 | .waitLoop 31 | dec a 32 | jr nz, .waitLoop 33 | ret 34 | DMATransferEnd: 35 | -------------------------------------------------------------------------------- /src/code/joypad.asm: -------------------------------------------------------------------------------- 1 | BUTTONS EQU $10 2 | D_PAD EQU $20 3 | DONE EQU $30 4 | 5 | 6 | SECTION "Joypad", ROM0 7 | 8 | Joypad:: 9 | put [rJOYP], D_PAD 10 | REPT 2 11 | ld a, [rJOYP] 12 | ENDR 13 | 14 | cpl 15 | and %1111 16 | swap a 17 | 18 | ld b, a 19 | 20 | put [rJOYP], BUTTONS 21 | REPT 6 22 | ld a, [rJOYP] 23 | ENDR 24 | 25 | cpl 26 | and %1111 27 | or b 28 | 29 | ld b, a 30 | 31 | put [rJOYP], DONE 32 | 33 | ld a, [wJoy] 34 | ld [wJoyLast], a 35 | ld e, a 36 | xor b 37 | ld d, a 38 | 39 | ; ld a, d 40 | and e 41 | ld [wJoyReleased], a 42 | 43 | ld a, d 44 | and b 45 | ld [wJoyPressed], a 46 | 47 | ld a, b 48 | ld [wJoy], a 49 | 50 | ret 51 | 52 | 53 | SECTION "Joypad WRAM", WRAM0 54 | 55 | wJoy:: ds 1 56 | wJoyLast:: ds 1 57 | wJoyPressed:: ds 1 58 | wJoyReleased:: ds 1 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waveform.gb 2 | 3 | This program visualizes the wave form used by the wave channel. 4 | The wave form can be edited freely and playback of the wave is updated immediately. 5 | 6 | Up to 8 custom wave forms can be saved. 7 | There are 8 built-in wave forms that can be loaded at any time. 8 | 9 | ## Screenshots 10 | ![Screenshot 1](screenshots/1.png) ![Screenshot 2](screenshots/2.png) 11 | ![Screenshot 3](screenshots/3.png) ![Screenshot 4](screenshots/4.png) 12 | ![Screenshot 5](screenshots/5.png) ![Screenshot 6](screenshots/6.png) 13 | 14 | ## Building 15 | 16 | To build, install [rgbds][rgbds] and put it in your path. 17 | Then run `make`. This will create `waveform.gb`. 18 | 19 | This project is based off of [bootstrap.gb][bootstrap.gb] by [yenatch][yenatch]. 20 | 21 | [rgbds]: https://github.com/rednex/rgbds 22 | [bootstrap.gb]: https://github.com/yenatch/bootstrap.gb 23 | [yenatch]: https://github.com/yenatch 24 | -------------------------------------------------------------------------------- /src/code/int.asm: -------------------------------------------------------------------------------- 1 | ; Hardware interrupts 2 | 3 | SECTION "VBlank int", ROM0[$40] 4 | jp VBlank 5 | 6 | SECTION "HBlank int", ROM0[$48] 7 | reti 8 | 9 | SECTION "Timer int", ROM0[$50] 10 | reti 11 | 12 | SECTION "Serial int", ROM0[$58] 13 | reti 14 | 15 | SECTION "Joypad int", ROM0[$60] 16 | reti 17 | 18 | 19 | 20 | SECTION "VBlank", ROM0 21 | 22 | VBlank: 23 | push af 24 | push bc 25 | push de 26 | push hl 27 | call hDMA 28 | 29 | call CheckGfxQueue 30 | call nc, RunCallbacks 31 | 32 | call RunTasks 33 | 34 | put [wVBlank], 1 35 | 36 | pop hl 37 | pop de 38 | pop bc 39 | pop af 40 | reti 41 | 42 | 43 | SECTION "VBlank Wait", ROM0 44 | 45 | WaitVBlank:: 46 | xor a 47 | ld [wVBlank], a 48 | .wait 49 | halt 50 | ld a, [wVBlank] 51 | and a 52 | jr z, .wait 53 | ret 54 | 55 | 56 | SECTION "VBlank WRAM", WRAM0 57 | 58 | wVBlank:: ds 1 59 | 60 | 61 | INCLUDE "code/video/queue.asm" 62 | INCLUDE "code/video/callback.asm" 63 | -------------------------------------------------------------------------------- /src/code/save.asm: -------------------------------------------------------------------------------- 1 | NUM_WAVES EQU 8 2 | 3 | SECTION "Save SRAM", SRAM 4 | 5 | sMarker: ds 2 6 | sWaves: ds WAVE_SIZE * NUM_WAVES 7 | 8 | 9 | SECTION "Save", ROM0 10 | 11 | ; save current wave into slot a 12 | SaveSAV:: 13 | ld hl, sWaves 14 | ld bc, WAVE_SIZE 15 | call AddATimes 16 | push hl 17 | put [MBC3_SRAM], SRAM_ENABLE 18 | put [MBC3_SRAMBank], 0 19 | put [sMarker], "W" 20 | put [sMarker + 1], "F" 21 | ld hl, wWave 22 | pop de 23 | call CopyWave 24 | put [MBC3_SRAM], SRAM_DISABLE 25 | ret 26 | 27 | ; load current wave with slot a 28 | LoadSAV:: 29 | ld hl, sWaves 30 | ld bc, WAVE_SIZE 31 | call AddATimes 32 | put [MBC3_SRAM], SRAM_ENABLE 33 | put [MBC3_SRAMBank], 0 34 | call VerifySave 35 | jr c, .noSave 36 | ld de, wWave 37 | call CopyWave 38 | jr .done 39 | .noSave 40 | call InitSAV 41 | .done 42 | put [MBC3_SRAM], SRAM_DISABLE 43 | ret 44 | 45 | VerifySave: 46 | ld a, [sMarker] 47 | jne "W", .noSave 48 | ld a, [sMarker + 1] 49 | jne "F", .noSave 50 | and a 51 | ret 52 | .noSave 53 | scf 54 | ret 55 | 56 | InitSAV: 57 | fill sWaves, WAVE_SIZE * NUM_WAVES, 0 58 | ret 59 | -------------------------------------------------------------------------------- /src/code/rst.asm: -------------------------------------------------------------------------------- 1 | ; rst vectors are single-byte calls. 2 | 3 | ; Here, farcall is used as a pseudoinstruction. 4 | ; The other vectors are free to use for any purpose. 5 | 6 | 7 | SECTION "rst Bankswitch", ROM0[Bankswitch] 8 | ld [hROMBank], a 9 | ld [MBC5_ROMBank], a 10 | ret 11 | 12 | SECTION "rst FarCall", ROM0[FarCall] 13 | jp FarCall_ 14 | 15 | 16 | SECTION "rst $10", ROM0[$10] 17 | SECTION "rst $18", ROM0[$18] 18 | SECTION "rst $20", ROM0[$20] 19 | SECTION "rst $28", ROM0[$28] 20 | SECTION "rst $30", ROM0[$30] 21 | SECTION "rst $38", ROM0[$38] 22 | 23 | 24 | SECTION "FarCall", ROM0 25 | 26 | FarCall_: 27 | ld [wFarCallHold + 0], a 28 | put [wFarCallHold + 1], l 29 | put [wFarCallHold + 2], h 30 | 31 | pop hl 32 | put [wFarCallBank], [hli] 33 | put [wFarCallTarget], $C3 ; 34 | put [wFarCallAddress + 0], [hli] 35 | put [wFarCallAddress + 1], [hli] 36 | push hl 37 | 38 | ld hl, wFarCallHold + 1 39 | ld a, [hli] 40 | ld h, [hl] 41 | ld l, a 42 | 43 | ld a, [hROMBank] 44 | push af 45 | ld a, [wFarCallBank] 46 | rst Bankswitch 47 | 48 | ld a, [wFarCallHold + 0] 49 | 50 | call wFarCallTarget 51 | 52 | push af 53 | 54 | add sp, 2 55 | pop af ; hROMBank 56 | add sp, -4 57 | rst Bankswitch 58 | 59 | pop af 60 | add sp, 2 61 | ret 62 | 63 | 64 | SECTION "FarCall WRAM", WRAM0 65 | 66 | wFarCallHold: ds 3 67 | wFarCallBank: ds 1 68 | wFarCallTarget: ds 1 ; jp 69 | wFarCallAddress: ds 2 70 | -------------------------------------------------------------------------------- /src/code/init.asm: -------------------------------------------------------------------------------- 1 | SECTION "Stack", WRAM0[$C080] 2 | 3 | ds $80 - 1 4 | wStack:: 5 | 6 | 7 | SECTION "Init", ROM0 8 | 9 | Init: 10 | di 11 | 12 | cp $11 13 | ld a, 1 14 | jr z, .cgb 15 | xor a 16 | .cgb 17 | ld [hGBC], a 18 | 19 | xor a 20 | ldx [rIF], [rIE] 21 | ldx [rRP] 22 | ldx [rSCX], [rSCY] 23 | ldx [rSB], [rSC] 24 | ldx [rWX], [rWY] 25 | ldx [rBGP], [rOBP0], [rOBP1] 26 | ldx [rTMA], [rTAC] 27 | 28 | put [rTAC], rTAC_4096Hz 29 | 30 | .wait 31 | ld a, [rLY] 32 | cp 144 33 | jr c, .wait 34 | 35 | xor a 36 | ld [rLCDC], a 37 | 38 | 39 | ld sp, wStack 40 | 41 | fill $C000, $2000, 0 42 | 43 | ld a, [hGBC] 44 | and a 45 | jr z, .cleared_wram 46 | 47 | ld a, 7 48 | .wram_bank 49 | push af 50 | ld [rSVBK], a 51 | fill $D000, $1000, 0 52 | pop af 53 | dec a 54 | cp 1 55 | jr nc, .wram_bank 56 | .cleared_wram 57 | 58 | ld a, [hGBC] 59 | push af 60 | fill $FF80, $7F, 0 61 | pop af 62 | ld [hGBC], a 63 | 64 | fill $8000, $2000, 0 65 | 66 | fill $FE00, $A0, 0 67 | 68 | 69 | put [rJOYP], 0 70 | put [rSTAT], 8 ; hblank enable 71 | put [rWY], $90 72 | put [rWX], 7 73 | 74 | put [rLCDC], %11100011 75 | 76 | IF def(NormalSpeed) ; not implemented yet 77 | ld a, [hGBC] 78 | and a 79 | call nz, NormalSpeed 80 | ENDC 81 | 82 | put [rIF], 0 83 | put [rIE], %1111 84 | 85 | ei 86 | 87 | halt 88 | 89 | call WriteDMATransferToHRAM 90 | call Main 91 | 92 | ; if Main returns, restart the program 93 | jp Init 94 | -------------------------------------------------------------------------------- /src/code/video/callback.asm: -------------------------------------------------------------------------------- 1 | NUM_CALLBACKS EQU 5 2 | CALLBACK_LENGTH EQU 3 3 | 4 | 5 | SECTION "Callback WRAM", WRAM0 6 | 7 | wCallbacks: 8 | ds 1 9 | ds CALLBACK_LENGTH * NUM_CALLBACKS 10 | 11 | 12 | SECTION "Callback", ROM0 13 | 14 | Callback:: 15 | ; Call a:hl during the next vblank. 16 | 17 | push bc 18 | push de 19 | 20 | ld b, a 21 | ld d, h 22 | ld e, l 23 | 24 | ld hl, wCallbacks 25 | ld a, [hli] 26 | and a 27 | jr z, .ok 28 | cp NUM_CALLBACKS 29 | jr nc, .exit 30 | 31 | push bc 32 | ld bc, CALLBACK_LENGTH 33 | .loop 34 | add hl, bc 35 | dec a 36 | jr nz, .loop 37 | pop bc 38 | .ok 39 | 40 | ld [hl], b 41 | inc hl 42 | ld [hl], e 43 | inc hl 44 | ld [hl], d 45 | inc hl 46 | 47 | ld a, [wCallbacks] 48 | inc a 49 | ld [wCallbacks], a 50 | 51 | .exit 52 | pop de 53 | pop bc 54 | ret 55 | 56 | 57 | ;RunCallback: 58 | ; ld hl, wCallbacks 59 | ; ld a, [hli] 60 | ; and a 61 | ; ret z 62 | ; 63 | ; ld a, [hROMBank] 64 | ; push af 65 | ; ld a, [hli] 66 | ; rst Bankswitch 67 | ; 68 | ; ld a, [hli] 69 | ; ld h, [hl] 70 | ; ld l, a 71 | ; 72 | ; call __hl__ 73 | ; 74 | ; pop af 75 | ; rst Bankswitch 76 | ; 77 | ; ret 78 | 79 | 80 | RunCallbacks: 81 | ld hl, wCallbacks 82 | ld a, [hli] 83 | and a 84 | ret z 85 | 86 | ld c, a 87 | put b, [hROMBank] 88 | .loop 89 | push bc 90 | 91 | ld a, [hli] 92 | rst Bankswitch 93 | 94 | ld a, [hli] 95 | ld b, a 96 | ld a, [hli] 97 | 98 | push hl 99 | ld h, a 100 | ld l, b 101 | 102 | call __hl__ 103 | 104 | pop hl 105 | pop bc 106 | 107 | dec c 108 | jr nz, .loop 109 | 110 | ld a, b 111 | rst Bankswitch 112 | 113 | xor a 114 | ld [wCallbacks], a 115 | ret 116 | 117 | WaitForCallbacks:: 118 | ld a, [wCallbacks] 119 | jz .done 120 | call WaitVBlank 121 | jr WaitForCallbacks 122 | .done 123 | ret 124 | -------------------------------------------------------------------------------- /src/code/task.asm: -------------------------------------------------------------------------------- 1 | NUM_TASKS EQU 8 2 | TASK_LENGTH EQU 1 + 3 + 6 3 | 4 | RSRESET 5 | TASK_ON RB 1 6 | TASK_FUNC RB 3 7 | TASK_DATA RB 6 8 | 9 | 10 | SECTION "Task WRAM", WRAM0 11 | 12 | wTasks: 13 | ds TASK_LENGTH * NUM_TASKS 14 | 15 | 16 | SECTION "Task", ROM0 17 | 18 | CreateTask:: 19 | ; Create a new task and set its function to a:de. 20 | ; Return its id in a and its address in hl. 21 | ; If there were no available tasks, return carry. 22 | push de 23 | ld d, a 24 | push de 25 | call ReserveTask 26 | pop de 27 | ld a, d 28 | pop de 29 | ret c 30 | ; fallthrough 31 | 32 | SetTaskFunc:: 33 | ; Set the function of the task at hl to a:de. 34 | inc hl 35 | ld [hl], a 36 | inc hl 37 | ld [hl], e 38 | inc hl 39 | ld [hl], d 40 | dec hl 41 | dec hl 42 | dec hl 43 | ret 44 | 45 | ReserveTask:: 46 | ; Reserve a new task. 47 | ; Return its id in a and its address in hl. 48 | ; If there are no available tasks, return carry. 49 | 50 | ld hl, wTasks 51 | ld de, TASK_LENGTH 52 | ld c, 0 53 | .next: 54 | ld a, [hl] 55 | and a 56 | jr z, .found 57 | add hl, de 58 | inc c 59 | ld a, c 60 | cp NUM_TASKS 61 | jr c, .next 62 | 63 | .failed: 64 | scf 65 | ret 66 | 67 | .found: 68 | ld [hl], 1 69 | ld a, c 70 | ret 71 | 72 | GetTask:: 73 | ; Get task a. 74 | ld hl, wTasks 75 | ld de, TASK_LENGTH 76 | and a 77 | ret z 78 | 79 | ld c, a 80 | .loop: 81 | add hl, de 82 | dec c 83 | jr nc, .loop 84 | ret 85 | 86 | DestroyTask:: 87 | ; Free task a. 88 | call GetTask 89 | ld [hl], 0 90 | ret 91 | 92 | RunTasks:: 93 | ; Call all the active tasks. 94 | ; Enter with a = taskId, hl = &task->data 95 | 96 | ld hl, wTasks 97 | ld de, TASK_LENGTH - 1 98 | ld c, 0 99 | .loop: 100 | ld a, [hli] 101 | and a 102 | jr z, .next 103 | 104 | ld a, [hli] 105 | ld b, a 106 | ld a, [hli] 107 | ld e, a 108 | ld d, [hl] 109 | inc hl 110 | 111 | ld a, [hROMBank] 112 | push af 113 | ld a, b 114 | rst Bankswitch 115 | 116 | ld a, c 117 | push hl 118 | push bc 119 | call __de__ 120 | pop bc 121 | pop hl 122 | 123 | pop af 124 | rst Bankswitch 125 | 126 | ld de, TASK_LENGTH - 4 127 | 128 | .next: 129 | add hl, de 130 | inc c 131 | ld a, c 132 | cp NUM_TASKS 133 | jr c, .loop 134 | 135 | ret 136 | -------------------------------------------------------------------------------- /src/macros.asm: -------------------------------------------------------------------------------- 1 | put: MACRO 2 | ld a, \2 3 | ld \1, a 4 | ENDM 5 | 6 | ldx: MACRO 7 | REPT _NARG 8 | ld \1, a 9 | SHIFT 10 | ENDR 11 | ENDM 12 | 13 | farcall: MACRO 14 | rst FarCall 15 | db bank(\1) 16 | dw \1 17 | ENDM 18 | 19 | callback: MACRO 20 | ld a, bank(\1) 21 | ld hl, \1 22 | call Callback 23 | ENDM 24 | 25 | task: MACRO 26 | ld a, bank(\1) 27 | ld de, \1 28 | call CreateTask 29 | ENDM 30 | 31 | fill: MACRO 32 | ld hl, \1 33 | ld bc, \2 34 | .loop\@ 35 | ld [hl], \3 36 | inc hl 37 | dec bc 38 | ld a, b 39 | or c 40 | jr nz, .loop\@ 41 | ENDM 42 | 43 | 44 | ; jp to \2 if a == \1 45 | je: MACRO 46 | IF "\1" == "0" 47 | and a 48 | ELSE 49 | cp \1 50 | ENDC 51 | jp z, \2 52 | ENDM 53 | 54 | ; jp to \2 if a != \1 55 | jne: MACRO 56 | IF "\1" == "0" 57 | and a 58 | ELSE 59 | cp \1 60 | ENDC 61 | jp nz, \2 62 | ENDM 63 | 64 | ; jp to \2 if a < \1 65 | jl: MACRO 66 | cp \1 67 | jp c, \2 68 | ENDM 69 | 70 | ; jp to \2 if a > \1 71 | jg: MACRO 72 | cp \1 73 | jr z, .notGreater\@ 74 | jr c, .notGreater\@ 75 | jp , \2 76 | .notGreater\@ 77 | ENDM 78 | 79 | ; jp to \2 if a <= \1 80 | jle: MACRO 81 | cp \1 82 | jp z, \2 83 | jp c, \2 84 | ENDM 85 | 86 | ; jp to \2 if a >= \1 87 | jge: MACRO 88 | cp \1 89 | jp nc, \2 90 | ENDM 91 | 92 | ; jp to \1 if a == 0 93 | jz: MACRO 94 | je 0, \1 95 | ENDM 96 | 97 | ; jp to \1 if a != 0 98 | jnz: MACRO 99 | jne 0, \1 100 | ENDM 101 | 102 | ; jp to \3 if bit \1 of register \2 == 1 103 | ; or 104 | ; jp to \2 if bit \1 of register a == 1 105 | jb: MACRO 106 | IF _NARG > 2 107 | bit \1, \2 108 | jp nz, \3 109 | ELSE 110 | bit \1, a 111 | jp nz, \2 112 | ENDC 113 | ENDM 114 | 115 | ; jp to \3 if bit \1 of register \2 == 0 116 | ; or 117 | ; jp to \2 if bit \1 of register a == 0 118 | jbz: MACRO 119 | IF _NARG > 2 120 | bit \1, \2 121 | jp z, \3 122 | ELSE 123 | bit \1, a 124 | jp z, \2 125 | ENDC 126 | ENDM 127 | 128 | ; ld 8-bit register \2 into 16-bit register \1 129 | ldr: MACRO 130 | IF "\1" == "bc" 131 | ld c, \2 132 | ld b, 0 133 | ENDC 134 | IF "\1" == "de" 135 | ld e, \2 136 | ld d, 0 137 | ENDC 138 | IF "\1" == "hl" 139 | ld l, \2 140 | ld h, 0 141 | ENDC 142 | ENDM 143 | 144 | 145 | RGB: MACRO 146 | dw (\1) + (\2) << 5 + (\3) << 10 147 | ENDM 148 | 149 | 150 | enum_start: MACRO 151 | IF _NARG 152 | __enum__ = \1 153 | ELSE 154 | __enum__ = 0 155 | ENDC 156 | ENDM 157 | 158 | enum: MACRO 159 | REPT _NARG 160 | \1 = __enum__ 161 | __enum__ = __enum__ + 1 162 | SHIFT 163 | ENDR 164 | ENDM 165 | -------------------------------------------------------------------------------- /src/code/video/queue.asm: -------------------------------------------------------------------------------- 1 | GFX_QUEUE_LIMIT EQU 10 2 | GFX_QUEUE_LENGTH EQU 2 + 2 + 1 3 | 4 | 5 | SECTION "Gfx Queue WRAM", WRAM0 6 | 7 | wGfxQueue: 8 | ds 1 9 | ds GFX_QUEUE_LENGTH * GFX_QUEUE_LIMIT 10 | 11 | 12 | SECTION "Gfx Queue", ROM0 13 | 14 | QueueGfx:: 15 | ; a: length 16 | ; bc: source 17 | ; de: dest 18 | push hl 19 | 20 | .add 21 | ld hl, wGfxQueue + 1 22 | push af 23 | ld a, [wGfxQueue] 24 | and a 25 | jr z, .ok 26 | cp GFX_QUEUE_LIMIT 27 | jr z, .exit 28 | 29 | push bc 30 | ld bc, GFX_QUEUE_LENGTH 31 | .loop 32 | add hl, bc 33 | dec a 34 | jr nz, .loop 35 | pop bc 36 | .ok 37 | pop af 38 | 39 | ld [hl], c 40 | inc hl 41 | ld [hl], b 42 | inc hl 43 | ld [hl], e 44 | inc hl 45 | ld [hl], d 46 | inc hl 47 | 48 | push af 49 | 50 | cp 8 51 | jr c, .good 52 | ld a, 8 53 | .good 54 | ld [hli], a 55 | 56 | ld a, [wGfxQueue] 57 | inc a 58 | ld [wGfxQueue], a 59 | 60 | pop af 61 | 62 | sub 8 63 | jr c, .done 64 | jr z, .done 65 | 66 | push af 67 | 68 | ld a, $80 69 | add c 70 | ld c, a 71 | ld a, b 72 | adc 0 73 | ld b, a 74 | 75 | ld a, $80 76 | add e 77 | ld e, a 78 | ld a, d 79 | adc 0 80 | ld d, a 81 | 82 | pop af 83 | jr .add 84 | 85 | .exit 86 | pop af 87 | .done 88 | pop hl 89 | ret 90 | 91 | 92 | CheckGfxQueue: 93 | ld hl, wGfxQueue 94 | ld a, [hli] 95 | and a 96 | ret z 97 | 98 | call LoadGfxFromQueue 99 | 100 | ld a, [wGfxQueue] 101 | dec a 102 | ld [wGfxQueue], a 103 | 104 | call ShiftGfxQueue 105 | 106 | scf 107 | ret 108 | 109 | 110 | LoadGfxFromQueue: 111 | ld c, [hl] 112 | inc hl 113 | ld b, [hl] 114 | inc hl 115 | ld e, [hl] 116 | inc hl 117 | ld d, [hl] 118 | inc hl 119 | ld a, [hli] 120 | 121 | ; and a 122 | ; ret z 123 | 124 | ; only call this directly if LCD is disabled 125 | ; otherwise use graphics queue 126 | LoadGfx:: 127 | push hl 128 | ld l, e 129 | ld h, d 130 | .loop 131 | ld e, a 132 | 133 | REPT 16 134 | ld a, [bc] 135 | inc bc 136 | ld [hli], a 137 | ENDR 138 | 139 | ld a, e 140 | dec a 141 | jr nz, .loop 142 | pop hl 143 | 144 | ret 145 | 146 | 147 | ShiftGfxQueue: 148 | ld hl, wGfxQueue + 1 149 | ld de, wGfxQueue + 1 + GFX_QUEUE_LENGTH 150 | ld c, (GFX_QUEUE_LIMIT - 1) * GFX_QUEUE_LENGTH 151 | .loop 152 | ld a, [de] 153 | inc de 154 | ld [hli], a 155 | dec c 156 | jr nz, .loop 157 | 158 | ; xor a 159 | ; REPT GFX_QUEUE_LENGTH 160 | ; ld [hli], a 161 | ; ENDR 162 | 163 | ret 164 | 165 | WaitForGfx:: 166 | ld a, [wGfxQueue] 167 | jz .done 168 | call WaitVBlank 169 | jr WaitForGfx 170 | .done 171 | ret 172 | -------------------------------------------------------------------------------- /src/code/sound.asm: -------------------------------------------------------------------------------- 1 | SECTION "Sound WRAM", WRAM0 2 | 3 | wPitch: ds 1 4 | wOctave: ds 1 5 | 6 | 7 | SECTION "Sound", ROM0 8 | 9 | InitSound:: 10 | put [rNR52], %10000000 ; sound enabled 11 | put [rNR51], %01000100 ; all output for ch3 12 | put [rNR50], $77 ; stereo panning 13 | put [rNR34], %00000000 ; counter mode off 14 | put [rNR32], %00100000 ; 100% volume 15 | 16 | xor a 17 | call LoadDefaultWave 18 | 19 | put [rNR30], %10000000 ; ch3 on 20 | 21 | put [wPitch], 0 22 | put [wOctave], 4 23 | ret 24 | 25 | ; load default wave with index a 26 | LoadDefaultWave:: 27 | ld hl, DefaultWaves 28 | ld bc, WAVE_SIZE 29 | call AddATimes 30 | ld de, wWave 31 | call CopyWave 32 | ld hl, wWave 33 | call LoadWave 34 | ret 35 | 36 | ; load wave at hl to rWave 37 | LoadWave:: 38 | ld a, [rNR30] 39 | push af 40 | xor a 41 | ld [rNR30], a 42 | ld de, rWave 43 | call CopyWave 44 | pop af 45 | ld [rNR30], a 46 | ret 47 | 48 | ; copy wave at hl to de 49 | CopyWave:: 50 | ld c, WAVE_SIZE 51 | .copyLoop 52 | put [de], [hli] 53 | inc de 54 | dec c 55 | jr nz, .copyLoop 56 | ret 57 | 58 | DefaultWaves: 59 | db $89, $AB, $CD, $EF, $FE, $DC, $BA, $98, $76, $54, $32, $10, $01, $23, $45, $67 60 | db $88, $99, $AA, $BB, $CC, $DD, $EE, $FF, $00, $11, $22, $33, $44, $55, $66, $77 61 | db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $00, $00, $00, $00, $00, $00, $00, $00 62 | db $8A, $CD, $EE, $FF, $FF, $EE, $DC, $A8, $75, $32, $11, $00, $00, $11, $23, $57 63 | db $8A, $CD, $EE, $FF, $FF, $EE, $DC, $A8, $01, $23, $45, $67, $89, $AB, $CD, $EF 64 | db $01, $23, $45, $67, $89, $AB, $CD, $EF, $02, $46, $8A, $CE, $02, $46, $8A, $CE 65 | db $00, $FF, $11, $EE, $22, $DD, $33, $CC, $44, $BB, $55, $AA, $66, $99, $77, $88 66 | db $00, $00, $EE, $EE, $22, $22, $CC, $CC, $44, $44, $AA, $AA, $66, $66, $88, $88 67 | 68 | PlayNote:: 69 | put b, [wOctave] 70 | ld a, [wPitch] 71 | call CalculateFrequency 72 | ld a, e 73 | ld [rNR33], a 74 | ld a, d 75 | res 6, a 76 | ld [rNR34], a 77 | ret 78 | 79 | ; return the frequency for note a, octave b in de 80 | CalculateFrequency: 81 | ld h, 0 82 | ld l, a 83 | add hl, hl 84 | ld d, h 85 | ld e, l 86 | ld hl, Pitches 87 | add hl, de 88 | ld e, [hl] 89 | inc hl 90 | ld d, [hl] 91 | ld a, b 92 | .loop 93 | cp 7 94 | jr z, .done 95 | sra d 96 | rr e 97 | inc a 98 | jr .loop 99 | .done 100 | ret 101 | 102 | Pitches: 103 | dw $F82C ; C_ 104 | dw $F89D ; C# 105 | dw $F907 ; D_ 106 | dw $F96B ; D# 107 | dw $F9CA ; E_ 108 | dw $FA23 ; F_ 109 | dw $FA77 ; F# 110 | dw $FAC7 ; G_ 111 | dw $FB12 ; G# 112 | dw $FB58 ; A_ 113 | dw $FB9B ; A# 114 | dw $FBDA ; B_ 115 | -------------------------------------------------------------------------------- /src/gbhw.asm: -------------------------------------------------------------------------------- 1 | ; Graciously aped from http://nocash.emubase.de/pandocs.htm. 2 | 3 | 4 | ; VRAM 5 | vChars0 EQU $8000 6 | vChars1 EQU $8800 7 | vChars2 EQU $9000 8 | vBGMap0 EQU $9800 9 | vBGMap1 EQU $9c00 10 | 11 | TILE_WIDTH EQU 8 12 | BITS_PER_BYTE EQU 8 13 | BITS_PER_PIXEL EQU 2 14 | PIXELS_PER_TILE EQU TILE_WIDTH * TILE_WIDTH 15 | PIXELS_PER_BYTE EQU BITS_PER_BYTE / BITS_PER_PIXEL 16 | BYTES_PER_TILE EQU PIXELS_PER_TILE / PIXELS_PER_BYTE 17 | 18 | BG_WIDTH EQU 32 19 | BG_HEIGHT EQU 32 20 | 21 | SCREEN_WIDTH EQU 20 22 | SCREEN_HEIGHT EQU 18 23 | 24 | SCREEN_WIDTH_PIXELS EQU SCREEN_WIDTH * TILE_WIDTH 25 | SCREEN_HEIGHT_PIXELS EQU SCREEN_HEIGHT * TILE_WIDTH 26 | 27 | WINDOW_MIN_X EQU 7 28 | WINDOW_RIGHT EQU WINDOW_MIN_X + SCREEN_WIDTH_PIXELS 29 | WINDOW_MAX_X EQU (WINDOW_RIGHT) - 2 30 | 31 | 32 | ; interrupt flags 33 | VBLANK EQU 0 34 | LCD_STAT EQU 1 35 | TIMER EQU 2 36 | SERIAL EQU 3 37 | JOYPAD EQU 4 38 | 39 | ; OAM attribute flags 40 | OAM_PALETTE EQU %111 41 | OAM_TILE_BANK EQU 3 42 | OAM_OBP_NUM EQU 4 ; Non CGB Mode Only 43 | OAM_X_FLIP EQU 5 44 | OAM_Y_FLIP EQU 6 45 | OAM_PRIORITY EQU 7 ; 0: OBJ above BG, 1: OBJ behind BG (colors 1-3) 46 | 47 | 48 | ; Hardware registers 49 | rJOYP EQU $ff00 ; Joypad (R/W) 50 | rSB EQU $ff01 ; Serial transfer data (R/W) 51 | rSC EQU $ff02 ; Serial Transfer Control (R/W) 52 | rSC_ON EQU 7 53 | rSC_CGB EQU 1 54 | rSC_CLOCK EQU 0 55 | rDIV EQU $ff04 ; Divider Register (R/W) 56 | rTIMA EQU $ff05 ; Timer counter (R/W) 57 | rTMA EQU $ff06 ; Timer Modulo (R/W) 58 | rTAC EQU $ff07 ; Timer Control (R/W) 59 | rTAC_ON EQU 2 60 | rTAC_4096Hz EQU 0 61 | rTAC_262144Hz EQU 1 62 | rTAC_65536Hz EQU 2 63 | rTAC_16384Hz EQU 3 64 | rIF EQU $ff0f ; Interrupt Flag (R/W) 65 | rNR10 EQU $ff10 ; Channel 1 Sweep register (R/W) 66 | rNR11 EQU $ff11 ; Channel 1 Sound length/Wave pattern duty (R/W) 67 | rNR12 EQU $ff12 ; Channel 1 Volume Envelope (R/W) 68 | rNR13 EQU $ff13 ; Channel 1 Frequency lo (Write Only) 69 | rNR14 EQU $ff14 ; Channel 1 Frequency hi (R/W) 70 | rNR21 EQU $ff16 ; Channel 2 Sound Length/Wave Pattern Duty (R/W) 71 | rNR22 EQU $ff17 ; Channel 2 Volume Envelope (R/W) 72 | rNR23 EQU $ff18 ; Channel 2 Frequency lo data (W) 73 | rNR24 EQU $ff19 ; Channel 2 Frequency hi data (R/W) 74 | rNR30 EQU $ff1a ; Channel 3 Sound on/off (R/W) 75 | rNR31 EQU $ff1b ; Channel 3 Sound Length 76 | rNR32 EQU $ff1c ; Channel 3 Select output level (R/W) 77 | rNR33 EQU $ff1d ; Channel 3 Frequency's lower data (W) 78 | rNR34 EQU $ff1e ; Channel 3 Frequency's higher data (R/W) 79 | rNR41 EQU $ff20 ; Channel 4 Sound Length (R/W) 80 | rNR42 EQU $ff21 ; Channel 4 Volume Envelope (R/W) 81 | rNR43 EQU $ff22 ; Channel 4 Polynomial Counter (R/W) 82 | rNR44 EQU $ff23 ; Channel 4 Counter/consecutive; Inital (R/W) 83 | rNR50 EQU $ff24 ; Channel control / ON-OFF / Volume (R/W) 84 | rNR51 EQU $ff25 ; Selection of Sound output terminal (R/W) 85 | rNR52 EQU $ff26 ; Sound on/off 86 | rWave EQU $ff30 ; Channel 3 Wave Pattern 87 | rLCDC EQU $ff40 ; LCD Control (R/W) 88 | rSTAT EQU $ff41 ; LCDC Status (R/W) 89 | rSCY EQU $ff42 ; Scroll Y (R/W) 90 | rSCX EQU $ff43 ; Scroll X (R/W) 91 | rLY EQU $ff44 ; LCDC Y-Coordinate (R) 92 | rLYC EQU $ff45 ; LY Compare (R/W) 93 | rDMA EQU $ff46 ; DMA Transfer and Start Address (W) 94 | rBGP EQU $ff47 ; BG Palette Data (R/W) - Non CGB Mode Only 95 | rOBP0 EQU $ff48 ; Object Palette 0 Data (R/W) - Non CGB Mode Only 96 | rOBP1 EQU $ff49 ; Object Palette 1 Data (R/W) - Non CGB Mode Only 97 | rWY EQU $ff4a ; Window Y Position (R/W) 98 | rWX EQU $ff4b ; Window X Position minus 7 (R/W) 99 | rKEY1 EQU $ff4d ; CGB Mode Only - Prepare Speed Switch 100 | rVBK EQU $ff4f ; CGB Mode Only - VRAM Bank 101 | rHDMA1 EQU $ff51 ; CGB Mode Only - New DMA Source, High 102 | rHDMA2 EQU $ff52 ; CGB Mode Only - New DMA Source, Low 103 | rHDMA3 EQU $ff53 ; CGB Mode Only - New DMA Destination, High 104 | rHDMA4 EQU $ff54 ; CGB Mode Only - New DMA Destination, Low 105 | rHDMA5 EQU $ff55 ; CGB Mode Only - New DMA Length/Mode/Start 106 | rRP EQU $ff56 ; CGB Mode Only - Infrared Communications Port 107 | rBGPI EQU $ff68 ; CGB Mode Only - Background Palette Index 108 | rBGPD EQU $ff69 ; CGB Mode Only - Background Palette Data 109 | rOBPI EQU $ff6a ; CGB Mode Only - Sprite Palette Index 110 | rOBPD EQU $ff6b ; CGB Mode Only - Sprite Palette Data 111 | rUNKN1 EQU $ff6c ; (FEh) Bit 0 (Read/Write) - CGB Mode Only 112 | rSVBK EQU $ff70 ; CGB Mode Only - WRAM Bank 113 | rUNKN2 EQU $ff72 ; (00h) - Bit 0-7 (Read/Write) 114 | rUNKN3 EQU $ff73 ; (00h) - Bit 0-7 (Read/Write) 115 | rUNKN4 EQU $ff74 ; (00h) - Bit 0-7 (Read/Write) - CGB Mode Only 116 | rUNKN5 EQU $ff75 ; (8Fh) - Bit 4-6 (Read/Write) 117 | rUNKN6 EQU $ff76 ; (00h) - Always 00h (Read Only) 118 | rUNKN7 EQU $ff77 ; (00h) - Always 00h (Read Only) 119 | rIE EQU $ffff ; Interrupt Enable (R/W) 120 | 121 | 122 | NUM_WAVE_SAMPLES EQU 32 123 | BITS_PER_WAVE_SAMPLE EQU 4 124 | WAVE_SAMPLES_PER_BYTE EQU BITS_PER_BYTE / BITS_PER_WAVE_SAMPLE 125 | WAVE_SIZE EQU NUM_WAVE_SAMPLES / WAVE_SAMPLES_PER_BYTE 126 | 127 | 128 | ; MBC3 129 | MBC3_SRAM EQU $0000 130 | MBC3_ROMBank EQU $2000 131 | MBC3_SRAMBank EQU $4000 132 | MBC3_Latch EQU $6000 133 | MBC3_RTC EQU $a000 134 | 135 | ; MBC5 136 | MBC5_SRAM EQU $0000 137 | MBC5_ROMBank EQU $2000 138 | MBC5_ROMBankH EQU $3000 139 | MBC5_SRAMBank EQU $4000 140 | 141 | SRAM_DISABLE EQU $00 142 | SRAM_ENABLE EQU $0a 143 | 144 | RTC_S EQU $08 ; Seconds 0-59 (0-3Bh) 145 | RTC_M EQU $09 ; Minutes 0-59 (0-3Bh) 146 | RTC_H EQU $0a ; Hours 0-23 (0-17h) 147 | RTC_DL EQU $0b ; Lower 8 bits of Day Counter (0-FFh) 148 | RTC_DH EQU $0c ; Upper 1 bit of Day Counter, Carry Bit, Halt Flag 149 | ; Bit 0 Most significant bit of Day Counter (Bit 8) 150 | ; Bit 6 Halt (0=Active, 1=Stop Timer) 151 | ; Bit 7 Day Counter Carry Bit (1=Counter Overflow) 152 | -------------------------------------------------------------------------------- /src/code/menu.asm: -------------------------------------------------------------------------------- 1 | TEXTBOX_BASE_TILE EQU 1 2 | TEXTBOX_TL EQU $81 ; top left 3 | TEXTBOX_H EQU $82 ; horizontal 4 | TEXTBOX_TR EQU $83 ; top right 5 | TEXTBOX_BL EQU $84 ; bottom left 6 | TEXTBOX_V EQU $85 ; vertical 7 | TEXTBOX_BR EQU $86 ; bottom right 8 | 9 | SECTION "Menu WRAM", WRAM0 10 | 11 | wMenuCursor: ds 1 12 | wMenuWidth: ds 1 13 | wMenuMaxOption: ds 1 14 | wMenuOptions: ds 2 15 | wMenuMap: ds SCREEN_WIDTH * SCREEN_HEIGHT 16 | 17 | 18 | SECTION "Menu", ROM0 19 | 20 | ; open menu with the options table at hl 21 | ; and default menu cursor position in a 22 | ; display inactive cursor at initial position if c flag is set 23 | OpenMenu:: 24 | push af 25 | ld [wMenuCursor], a 26 | put [wMenuOptions + 0], l 27 | put [wMenuOptions + 1], h 28 | call CalcMenuWidth 29 | call LoadTextBoxTiles 30 | call ClearMenuMap 31 | call DrawTextBox 32 | call LoadOptions 33 | call RefreshMenu 34 | put [rWY], 0 35 | put [rWX], WINDOW_MAX_X 36 | call EnableWindow 37 | call ScrollWindowIn 38 | call PlaceMenuCursor 39 | pop af 40 | call c, PlaceInitialMenuCursor 41 | .menuLoop 42 | call Joypad 43 | ld a, [wJoyPressed] 44 | je D_UP, .pressedUp 45 | je D_DOWN, .pressedDown 46 | je A_BUTTON, .pressedA 47 | je B_BUTTON, .pressedB 48 | jr .menuLoop 49 | 50 | .pressedUp 51 | ld a, [wMenuCursor] 52 | jz .isMin 53 | dec a 54 | ld [wMenuCursor], a 55 | call PlaceMenuCursor 56 | .isMin 57 | jr .menuLoop 58 | 59 | .pressedDown 60 | ld a, [wMenuCursor] 61 | ld hl, wMenuMaxOption 62 | je [hl], .isMax 63 | inc a 64 | ld [wMenuCursor], a 65 | call PlaceMenuCursor 66 | .isMax 67 | jr .menuLoop 68 | 69 | .pressedA 70 | call .closeMenu 71 | ; do the thing 72 | put l, [wMenuOptions + 0] 73 | put h, [wMenuOptions + 1] 74 | inc hl 75 | inc hl 76 | ld a, [wMenuCursor] 77 | sla a 78 | sla a 79 | ldr bc, a 80 | add hl, bc 81 | ld a, [hli] 82 | ld h, [hl] 83 | ld l, a 84 | ld a, [wMenuCursor] 85 | call __hl__ 86 | ld a, [wMenuCursor] 87 | ret 88 | 89 | .pressedB 90 | call .closeMenu 91 | ld a, -1 92 | ret 93 | 94 | .closeMenu 95 | call RemoveMenuCursors 96 | call ScrollWindowOut 97 | call DisableWindow 98 | ret 99 | 100 | ; calculate the inner width of the menu 101 | ; based on the longest label in the options at hl 102 | ; fix in the range [2, 18] 103 | CalcMenuWidth: 104 | ld e, 0 105 | .optionsLoop 106 | put c, [hli] 107 | put b, [hli] 108 | or c 109 | jr z, .done 110 | call CalcStringLength 111 | ld a, d 112 | jle e, .notLonger 113 | ld e, a 114 | .notLonger 115 | inc hl 116 | inc hl 117 | jr .optionsLoop 118 | .done 119 | ld a, e 120 | inc a ; plus one for arrow 121 | jge 2, .bigEnough 122 | ld a, 2 123 | .bigEnough 124 | jle 18, .smallEnough 125 | ld a, 18 126 | .smallEnough 127 | ld [wMenuWidth], a 128 | ret 129 | 130 | ; return the length of 0-terminated string at bc in d 131 | CalcStringLength: 132 | ld d, 0 133 | .loop 134 | ld a, [bc] 135 | inc bc 136 | jz .done 137 | inc d 138 | jr .loop 139 | .done 140 | ret 141 | 142 | LoadTextBoxTiles: 143 | ld bc, TextBoxTiles 144 | ld de, vChars1 + TEXTBOX_BASE_TILE * BYTES_PER_TILE 145 | ld a, 6 146 | call QueueGfx 147 | ret 148 | 149 | ClearMenuMap: 150 | fill wMenuMap, SCREEN_WIDTH * SCREEN_HEIGHT, 0 151 | ret 152 | 153 | RefreshMenu: 154 | call WaitForCallbacks 155 | callback RefreshMenu_1 156 | call WaitForCallbacks 157 | callback RefreshMenu_2 158 | call WaitForCallbacks 159 | callback RefreshMenu_3 160 | call WaitForCallbacks 161 | callback RefreshMenu_4 162 | call WaitForCallbacks 163 | callback RefreshMenu_5 164 | call WaitForCallbacks 165 | callback RefreshMenu_6 166 | call WaitForCallbacks 167 | ret 168 | 169 | RefreshMenu_1: 170 | ld de, wMenuMap + SCREEN_WIDTH * 0 171 | ld hl, vBGMap1 + BG_WIDTH * 0 172 | jr RefreshMenu_ 173 | 174 | RefreshMenu_2: 175 | ld de, wMenuMap + SCREEN_WIDTH * 3 176 | ld hl, vBGMap1 + BG_WIDTH * 3 177 | jr RefreshMenu_ 178 | 179 | RefreshMenu_3: 180 | ld de, wMenuMap + SCREEN_WIDTH * 6 181 | ld hl, vBGMap1 + BG_WIDTH * 6 182 | jr RefreshMenu_ 183 | 184 | RefreshMenu_4: 185 | ld de, wMenuMap + SCREEN_WIDTH * 9 186 | ld hl, vBGMap1 + BG_WIDTH * 9 187 | jr RefreshMenu_ 188 | 189 | RefreshMenu_5: 190 | ld de, wMenuMap + SCREEN_WIDTH * 12 191 | ld hl, vBGMap1 + BG_WIDTH * 12 192 | jr RefreshMenu_ 193 | 194 | RefreshMenu_6: 195 | ld de, wMenuMap + SCREEN_WIDTH * 15 196 | ld hl, vBGMap1 + BG_WIDTH * 15 197 | ; jr RefreshMenu_ 198 | 199 | RefreshMenu_: 200 | ld b, SCREEN_HEIGHT / 6 201 | .outerLoop 202 | ld c, SCREEN_WIDTH 203 | .innerLoop 204 | put [hli], [de] 205 | inc de 206 | dec c 207 | jr nz, .innerLoop 208 | dec b 209 | jr z, .done 210 | push bc 211 | ld bc, BG_WIDTH - SCREEN_WIDTH 212 | add hl, bc 213 | pop bc 214 | jr .outerLoop 215 | .done 216 | ret 217 | 218 | DrawTextBox: 219 | put c, [wMenuWidth] 220 | ld b, SCREEN_HEIGHT - 2 ; menu height 221 | ld a, SCREEN_WIDTH - 2 222 | sub c 223 | ldr de, a 224 | ld hl, wMenuMap 225 | push bc 226 | 227 | ; top row 228 | put [hli], TEXTBOX_TL 229 | ld a, TEXTBOX_H 230 | .loop1 231 | ld [hli], a 232 | dec c 233 | jr nz, .loop1 234 | put [hli], TEXTBOX_TR 235 | 236 | ; middle rows 237 | pop bc 238 | ld a, b 239 | ld b, 0 240 | push bc 241 | add hl, de 242 | .loop2 243 | push af 244 | put [hli], TEXTBOX_V 245 | add hl, bc 246 | ld [hli], a 247 | add hl, de 248 | pop af 249 | dec a 250 | jr nz, .loop2 251 | 252 | ; bottom row 253 | pop bc 254 | put [hli], TEXTBOX_BL 255 | ld a, TEXTBOX_H 256 | .loop3 257 | ld [hli], a 258 | dec c 259 | jr nz, .loop3 260 | put [hli], TEXTBOX_BR 261 | .done 262 | ret 263 | 264 | LoadOptions: 265 | put [wMenuMaxOption], -1 266 | put e, [wMenuOptions + 0] 267 | put d, [wMenuOptions + 1] 268 | ld hl, wMenuMap + SCREEN_WIDTH * 1 + 2 269 | .optionsLoop 270 | push hl 271 | put c, [de] 272 | inc de 273 | put b, [de] 274 | inc de 275 | or c 276 | jr z, .done 277 | ld a, [wMenuWidth] 278 | dec a 279 | call PutString 280 | ld hl, wMenuMaxOption 281 | inc [hl] 282 | pop hl 283 | ld bc, SCREEN_WIDTH * 2 284 | add hl, bc 285 | inc de 286 | inc de 287 | jr .optionsLoop 288 | .done 289 | pop hl 290 | ret 291 | 292 | ; copy the 0-terminated string at bc to hl 293 | ; copy no more than 'a' characters 294 | PutString: 295 | push af 296 | jz .done 297 | ld a, [bc] 298 | inc bc 299 | jz .done 300 | ld [hli], a 301 | pop af 302 | dec a 303 | jr PutString 304 | .done 305 | pop af 306 | ret 307 | 308 | PlaceMenuCursor: 309 | put d, [wMenuCursor] 310 | ld e, TILE_WIDTH * 2 311 | call Multiply 312 | ld a, TILE_WIDTH * 3 313 | add l 314 | ld [wOAM + 4], a 315 | put b, [wMenuWidth] 316 | ld a, SCREEN_WIDTH 317 | sub b 318 | ld d, a 319 | ld e, TILE_WIDTH 320 | call Multiply 321 | ld a, l 322 | ld [wOAM + 5], a 323 | put [wOAM + 6], 1 324 | put [wOAM + 7], 0 325 | ret 326 | 327 | PlaceInitialMenuCursor: 328 | put d, [wMenuCursor] 329 | ld e, TILE_WIDTH * 2 330 | call Multiply 331 | ld a, TILE_WIDTH * 3 332 | add l 333 | ld [wOAM + 8], a 334 | put b, [wMenuWidth] 335 | ld a, SCREEN_WIDTH 336 | sub b 337 | ld d, a 338 | ld e, TILE_WIDTH 339 | call Multiply 340 | ld a, l 341 | ld [wOAM + 9], a 342 | put [wOAM + 10], 3 343 | put [wOAM + 11], 0 344 | ret 345 | 346 | RemoveMenuCursors: 347 | xor a 348 | ld hl, wOAM + 4 349 | ld [hli], a 350 | ld [hli], a 351 | ld [hli], a 352 | ld [hli], a 353 | ld [hli], a 354 | ld [hli], a 355 | ld [hli], a 356 | ld [hli], a 357 | ret 358 | 359 | ScrollWindowIn: 360 | ld a, [wMenuWidth] 361 | add 2 362 | ld d, a 363 | ld e, TILE_WIDTH 364 | call Multiply 365 | ld a, WINDOW_RIGHT 366 | sub l 367 | ld b, a 368 | .scrollLoop 369 | ld a, [rWX] 370 | sub 6 371 | jle b, .done 372 | ld [rWX], a 373 | call WaitVBlank 374 | jr .scrollLoop 375 | .done 376 | put [rWX], b 377 | ret 378 | 379 | ScrollWindowOut: 380 | ld a, [rWX] 381 | add 6 382 | jge WINDOW_MAX_X, .done 383 | ld [rWX], a 384 | call WaitVBlank 385 | jr ScrollWindowOut 386 | .done 387 | put [rWX], WINDOW_MAX_X 388 | ret 389 | 390 | TextBoxTiles: 391 | INCBIN "gfx/textbox.2bpp" 392 | -------------------------------------------------------------------------------- /src/main.asm: -------------------------------------------------------------------------------- 1 | INCLUDE "constants.asm" 2 | 3 | KNOB_BASE_TILE EQU 0 4 | KNOB_TRACK_TILE EQU 1 5 | KNOB_LEFT_TILE EQU 2 6 | KNOB_RIGHT_TILE EQU 3 7 | KNOB_BOTH_TILE EQU 4 8 | KNOB_START_X EQU 2 9 | KNOB_START_Y EQU 1 10 | NUM_COLUMNS EQU WAVE_SIZE 11 | NUM_ROWS EQU 1 << BITS_PER_WAVE_SAMPLE 12 | KNOB_HEIGHT EQU 1 13 | KNOB_WIDTH EQU 5 14 | 15 | NUMBER_X EQU (KNOB_START_X) - 1 16 | NUMBER_Y EQU KNOB_START_Y 17 | 18 | FONT_BASE_TILE EQU $20 19 | FONT_HEIGHT EQU 6 20 | FONT_WIDTH EQU 16 21 | 22 | HEX_BASE_TILE EQU $10 23 | HEX_X EQU KNOB_START_X 24 | HEX_Y EQU KNOB_START_Y + NUM_ROWS 25 | HEX_HEIGHT EQU 1 26 | HEX_WIDTH EQU 16 27 | 28 | ARROW_TILE EQU 0 29 | ARROW_X EQU 8 * KNOB_START_X + 6 30 | ARROW_Y EQU 8 * (KNOB_START_Y + 1) 31 | ARROW_HEIGHT EQU 1 32 | ARROW_WIDTH EQU 4 33 | 34 | MIN_ARROW_POS EQU 0 35 | MAX_ARROW_POS EQU (NUM_WAVE_SAMPLES) - 1 36 | 37 | 38 | SECTION "Main WRAM", WRAM0 39 | 40 | wWave:: ds WAVE_SIZE 41 | wWaveSlot:: ds 1 42 | wCursorPos: ds 1 43 | wKnobColumn: ds NUM_ROWS 44 | wHexTiles: ds BYTES_PER_TILE * HEX_WIDTH 45 | wNewHexTile: ds BYTES_PER_TILE 46 | 47 | 48 | SECTION "Main", ROM0 49 | 50 | Main:: 51 | call Setup 52 | .loop 53 | call WaitVBlank 54 | call Update 55 | jr .loop 56 | 57 | Update: 58 | call Joypad 59 | ld c, 0 60 | ld a, [wCursorPos] 61 | jbz 0, .even 62 | ld c, 1 63 | .even 64 | srl a 65 | ldr de, a 66 | ld hl, wWave 67 | add hl, de 68 | ld a, [wJoyPressed] 69 | je D_UP, .pressedUp 70 | je D_DOWN, .pressedDown 71 | je D_LEFT, .pressedLeft 72 | je D_RIGHT, .pressedRight 73 | je START, .pressedStart 74 | ret 75 | 76 | .pressedUp 77 | ld a, [hl] 78 | jb 0, c, .skipOdd1 79 | swap a ; skip for odd knobs 80 | .skipOdd1 81 | and $0F 82 | je $F, .isMax 83 | inc a 84 | jb 0, c, .skipOdd2 85 | swap a ; skip for odd knobs 86 | .skipOdd2 87 | ld b, a 88 | ld a, [hl] 89 | jb 0, c, .skipOdd3 90 | and $0F ; for even knobs 91 | jr .skipEven1 92 | .skipOdd3 93 | and $F0 ; for odd knobs 94 | .skipEven1 95 | or b 96 | ld [hl], a 97 | jr .afterWaveChange 98 | .isMax 99 | ret 100 | 101 | .pressedDown 102 | ld a, [hl] 103 | jb 0, c, .skipOdd4 104 | swap a ; skip for odd knobs 105 | .skipOdd4 106 | and $0F 107 | jz .isMin 108 | dec a 109 | jb 0, c, .skipOdd5 110 | swap a ; skip for odd knobs 111 | .skipOdd5 112 | ld b, a 113 | ld a, [hl] 114 | jb 0, c, .skipOdd6 115 | and $0F ; for even knobs 116 | jr .skipEven2 117 | .skipOdd6 118 | and $F0 ; for odd knobs 119 | .skipEven2 120 | or b 121 | ld [hl], a 122 | jr .afterWaveChange 123 | .isMin 124 | ret 125 | 126 | .afterWaveChange 127 | call UpdateKnobTilemap_Defer 128 | call UpdateHexTile_Defer 129 | call UpdateWave 130 | call PlayNote 131 | ret 132 | 133 | .pressedLeft 134 | ld a, [wCursorPos] 135 | dec a 136 | jne (MIN_ARROW_POS) - 1, .noUnderflow 137 | ld a, MAX_ARROW_POS 138 | .noUnderflow 139 | ld [wCursorPos], a 140 | ld d, a 141 | ld e, 4 142 | call Multiply 143 | ld bc, ARROW_X 144 | add hl, bc 145 | put [wOAM + 1], l 146 | ret 147 | 148 | .pressedRight 149 | ld a, [wCursorPos] 150 | inc a 151 | jne MAX_ARROW_POS + 1, .noOverflow 152 | ld a, MIN_ARROW_POS 153 | .noOverflow 154 | ld [wCursorPos], a 155 | ld d, a 156 | ld e, 4 157 | call Multiply 158 | ld bc, ARROW_X 159 | add hl, bc 160 | put [wOAM + 1], l 161 | ret 162 | 163 | .pressedStart 164 | ld a, [wCursorPos] 165 | jle 8, .noHide 166 | ; push arrow oam data 167 | ld a, [wOAM + 0] 168 | push af 169 | ld a, [wOAM + 1] 170 | push af 171 | put [wOAM + 0], 0 172 | put [wOAM + 1], 0 173 | .noHide 174 | put [wOAM + 2], 2 175 | ld hl, StartMenuOptions 176 | xor a 177 | call OpenMenu 178 | ld a, [wCursorPos] 179 | jle 8, .noShow 180 | ; pop arrow oam data 181 | pop af 182 | ld [wOAM + 1], a 183 | pop af 184 | ld [wOAM + 0], a 185 | .noShow 186 | put [wOAM + 2], 0 187 | ret 188 | 189 | StartMenuOptions: 190 | dw SaveLabel, SaveAction 191 | dw LoadLabel, LoadAction 192 | dw DefaultsLabel, DefaultsAction 193 | dw CancelLabel, CancelAction 194 | dw 0 195 | 196 | SaveLabel: 197 | db "Save...", 0 198 | 199 | SaveAction: 200 | ld hl, SaveMenuOptions 201 | ld a, [wWaveSlot] 202 | scf 203 | call OpenMenu 204 | je -1, .cancelled 205 | ld [wWaveSlot], a 206 | .cancelled 207 | ret 208 | 209 | LoadLabel: 210 | db "Load...", 0 211 | 212 | LoadAction: 213 | ld hl, LoadMenuOptions 214 | ld a, [wWaveSlot] 215 | scf 216 | call OpenMenu 217 | je -1, .cancelled 218 | ld [wWaveSlot], a 219 | .cancelled 220 | ret 221 | 222 | DefaultsLabel: 223 | db "Defaults...", 0 224 | 225 | DefaultsAction: 226 | ld hl, DefaultsMenuOptions 227 | xor a 228 | call OpenMenu 229 | je -1, .cancelled 230 | call LoadDefaultAction 231 | .cancelled 232 | ret 233 | 234 | RefreshWave: 235 | call WaitForCallbacks 236 | ld a, [wCursorPos] 237 | push af 238 | put [wCursorPos], 0 239 | ld c, NUM_COLUMNS 240 | .refreshLoop 241 | push bc 242 | call UpdateKnobTilemap_Defer 243 | call UpdateHexTile_Defer 244 | call WaitForCallbacks 245 | ld a, [wCursorPos] 246 | add 2 247 | ld [wCursorPos], a 248 | pop bc 249 | dec c 250 | jr nz, .refreshLoop 251 | pop af 252 | ld [wCursorPos], a 253 | ret 254 | 255 | CancelLabel: 256 | db "Cancel", 0 257 | 258 | CancelAction: 259 | ret 260 | 261 | SaveMenuOptions: 262 | dw Wave1Label, SaveWaveAction 263 | dw Wave2Label, SaveWaveAction 264 | dw Wave3Label, SaveWaveAction 265 | dw Wave4Label, SaveWaveAction 266 | dw Wave5Label, SaveWaveAction 267 | dw Wave6Label, SaveWaveAction 268 | dw Wave7Label, SaveWaveAction 269 | dw Wave8Label, SaveWaveAction 270 | ; dw CancelLabel, CancelAction 271 | dw 0 272 | 273 | Wave1Label: 274 | db "Wave 1", 0 275 | 276 | Wave2Label: 277 | db "Wave 2", 0 278 | 279 | Wave3Label: 280 | db "Wave 3", 0 281 | 282 | Wave4Label: 283 | db "Wave 4", 0 284 | 285 | Wave5Label: 286 | db "Wave 5", 0 287 | 288 | Wave6Label: 289 | db "Wave 6", 0 290 | 291 | Wave7Label: 292 | db "Wave 7", 0 293 | 294 | Wave8Label: 295 | db "Wave 8", 0 296 | 297 | SaveWaveAction: 298 | call SaveSAV 299 | ret 300 | 301 | LoadMenuOptions: 302 | dw Wave1Label, LoadWaveAction 303 | dw Wave2Label, LoadWaveAction 304 | dw Wave3Label, LoadWaveAction 305 | dw Wave4Label, LoadWaveAction 306 | dw Wave5Label, LoadWaveAction 307 | dw Wave6Label, LoadWaveAction 308 | dw Wave7Label, LoadWaveAction 309 | dw Wave8Label, LoadWaveAction 310 | ; dw CancelLabel, CancelAction 311 | dw 0 312 | 313 | LoadWaveAction: 314 | call LoadSAV 315 | call UpdateWave 316 | call RefreshWave 317 | call PlayNote 318 | ret 319 | 320 | DefaultsMenuOptions: 321 | dw Default1Label, LoadDefaultAction 322 | dw Default2Label, LoadDefaultAction 323 | dw Default3Label, LoadDefaultAction 324 | dw Default4Label, LoadDefaultAction 325 | dw Default5Label, LoadDefaultAction 326 | dw Default6Label, LoadDefaultAction 327 | dw Default7Label, LoadDefaultAction 328 | dw Default8Label, LoadDefaultAction 329 | ; dw CancelLabel, CancelAction 330 | dw 0 331 | 332 | Default1Label: 333 | db "Triangle", 0 334 | 335 | Default2Label: 336 | db "Sawtooth", 0 337 | 338 | Default3Label: 339 | db "50% Square", 0 340 | 341 | Default4Label: 342 | db "Sine", 0 343 | 344 | Default5Label: 345 | db "Sine Saw", 0 346 | 347 | Default6Label: 348 | db "Double Saw", 0 349 | 350 | Default7Label: 351 | db "Arrow 1", 0 352 | 353 | Default8Label: 354 | db "Arrow 2", 0 355 | 356 | LoadDefaultAction: 357 | call LoadDefaultWave 358 | call RefreshWave 359 | call PlayNote 360 | ret 361 | 362 | UpdateWave: 363 | ld hl, wWave 364 | call LoadWave 365 | ret 366 | 367 | Setup: 368 | call InitSound 369 | put [wWaveSlot], 0 370 | call LoadSAV 371 | call UpdateWave 372 | 373 | call DisableLCD 374 | 375 | ld bc, KnobGraphics 376 | ld de, vChars2 + KNOB_BASE_TILE * BYTES_PER_TILE 377 | ld a, KNOB_WIDTH * KNOB_HEIGHT 378 | call LoadGfx 379 | 380 | ld bc, HexGraphics 381 | ld de, wHexTiles 382 | ld a, HEX_WIDTH * HEX_HEIGHT 383 | call LoadGfx 384 | 385 | ld bc, FontGraphics 386 | ld de, vChars2 + FONT_BASE_TILE * BYTES_PER_TILE 387 | ld a, FONT_WIDTH * FONT_HEIGHT 388 | call LoadGfx 389 | 390 | ld bc, ArrowsGraphics 391 | ld de, vChars0 + ARROW_TILE * BYTES_PER_TILE 392 | ld a, ARROW_WIDTH * ARROW_HEIGHT 393 | call LoadGfx 394 | 395 | put [wCursorPos], 0 396 | REPT NUM_COLUMNS 397 | call UpdateKnobTilemap 398 | call UpdateHexTile 399 | ld a, [wCursorPos] 400 | add 2 401 | ld [wCursorPos], a 402 | ENDR 403 | put [wCursorPos], MIN_ARROW_POS 404 | call DrawNumberTilemap 405 | 406 | ld hl, vBGMap0 + BG_WIDTH * HEX_Y + HEX_X 407 | ld a, HEX_BASE_TILE 408 | REPT NUM_COLUMNS 409 | ld [hli], a 410 | inc a 411 | ENDR 412 | 413 | ld hl, wOAM 414 | put [hli], ARROW_Y 415 | put [hli], ARROW_X + MIN_ARROW_POS * 4 416 | put [hli], ARROW_TILE 417 | put [hli], $00 418 | 419 | call SetPalette 420 | 421 | call EnableLCD 422 | 423 | call PlayNote 424 | ret 425 | 426 | ; update knob tilemap and copy to vram immediately 427 | UpdateKnobTilemap: 428 | call UpdateKnobTilemap_ 429 | call CopyKnobTilemap 430 | ret 431 | 432 | ; update knob tilemap and copy to vram on vblank 433 | UpdateKnobTilemap_Defer: 434 | call UpdateKnobTilemap_ 435 | callback CopyKnobTilemap 436 | ret 437 | 438 | ; update knob tilemap by updating the current column 439 | ; clear the column, then place the left and right knob 440 | ; use KNOB_BOTH_TILE if both knobs have same value 441 | UpdateKnobTilemap_: 442 | ld hl, wKnobColumn 443 | ld a, KNOB_TRACK_TILE 444 | REPT NUM_ROWS 445 | ld [hli], a 446 | ENDR 447 | ld a, [wCursorPos] 448 | srl a 449 | ldr bc, a 450 | ld hl, wWave 451 | add hl, bc 452 | ld a, [hl] 453 | ld b, a 454 | and $0F 455 | push af 456 | ld a, b 457 | swap a 458 | and $0F 459 | ld b, a 460 | ld a, $0F 461 | sub b 462 | ld d, a ; backup 463 | ldr bc, a 464 | ld hl, wKnobColumn 465 | add hl, bc 466 | ld [hl], KNOB_LEFT_TILE 467 | pop af 468 | ld b, a 469 | ld a, $0F 470 | sub b 471 | ldr bc, a 472 | ld hl, wKnobColumn 473 | add hl, bc 474 | ld [hl], KNOB_RIGHT_TILE 475 | jne d, .different 476 | ld [hl], KNOB_BOTH_TILE 477 | .different 478 | ret 479 | 480 | ; copy the updated knob column to vram 481 | CopyKnobTilemap: 482 | ld a, [wCursorPos] 483 | srl a 484 | ldr bc, a 485 | ld hl, vBGMap0 + BG_WIDTH * KNOB_START_Y + KNOB_START_X 486 | add hl, bc 487 | ld de, wKnobColumn 488 | ld bc, BG_WIDTH 489 | REPT NUM_ROWS 490 | put [hl], [de] 491 | add hl, bc 492 | inc de 493 | ENDR 494 | ret 495 | 496 | ; update hex digit tile and copy to vram immediately 497 | UpdateHexTile: 498 | call UpdateHexTile_ 499 | call CopyHexTile 500 | ret 501 | 502 | ; update hex digit tile and copy to vram on vblank 503 | UpdateHexTile_Defer: 504 | call UpdateHexTile_ 505 | callback CopyHexTile 506 | ret 507 | 508 | ; update hex digit tile by using the values of the left 509 | ; and right knob of the current column 510 | UpdateHexTile_: 511 | ld a, [wCursorPos] 512 | srl a 513 | ldr bc, a 514 | ld hl, wWave 515 | add hl, bc 516 | ld a, [hl] 517 | push af 518 | and $F0 519 | ld b, a 520 | pop af 521 | and $0F 522 | swap a 523 | ld hl, wHexTiles 524 | ldr de, a 525 | add hl, de 526 | push hl 527 | pop de 528 | ld a, b 529 | ld hl, wHexTiles 530 | ldr bc, a 531 | add hl, bc 532 | push hl 533 | pop bc 534 | 535 | ; combine tiles at bc and de 536 | ; left half of tile at bc gets placed in left half of tile at hl 537 | ; left half of tile at de gets placed in right half of tile at hl 538 | ld hl, wNewHexTile 539 | ld a, BYTES_PER_TILE 540 | .hexLoop 541 | push af 542 | ld a, [bc] 543 | inc bc 544 | push bc 545 | and $F0 546 | ld b, a 547 | ld a, [de] 548 | inc de 549 | and $F0 550 | swap a 551 | or b 552 | ld [hli], a 553 | pop bc 554 | pop af 555 | dec a 556 | jr nz, .hexLoop 557 | ret 558 | 559 | ; copy the updated hex digit tile to vram 560 | CopyHexTile: 561 | ld a, [wCursorPos] 562 | srl a 563 | swap a 564 | ldr bc, a 565 | ld hl, vChars2 + HEX_BASE_TILE * BYTES_PER_TILE 566 | add hl, bc 567 | ld de, wNewHexTile 568 | REPT BYTES_PER_TILE 569 | put [hli], [de] 570 | inc de 571 | ENDR 572 | ret 573 | 574 | ; draw the knob values to the left of the knob columns 575 | DrawNumberTilemap: 576 | ld de, .numberTilemap 577 | ld hl, vBGMap0 + BG_WIDTH * NUMBER_Y + NUMBER_X 578 | ld bc, BG_WIDTH 579 | .numberLoop 580 | ld a, [de] 581 | inc de 582 | jz .numbersDone 583 | ld [hl], a 584 | add hl, bc 585 | jr .numberLoop 586 | .numbersDone 587 | ret 588 | 589 | .numberTilemap: 590 | db "FEDCBA9876543210", 0 591 | 592 | SetPalette: 593 | ld a, %11100100 ; quaternary: 3210 594 | ld [rOBP0], a 595 | ld [rOBP1], a 596 | ld [rBGP], a 597 | ret 598 | 599 | KnobGraphics: 600 | INCBIN "gfx/knob.2bpp" 601 | 602 | HexGraphics: 603 | INCBIN "gfx/hex.2bpp" 604 | 605 | FontGraphics: 606 | INCBIN "gfx/font.2bpp" 607 | 608 | ArrowsGraphics: 609 | INCBIN "gfx/arrows.2bpp" 610 | --------------------------------------------------------------------------------